From 66e922a971579388f7f70719aa04cfa670bcd665 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 11 Jan 2026 14:18:43 -0800 Subject: [PATCH] fixing issue with timestamp using lua 5.1/luajit (#72) * fixing issue with timestamp using lua 5.1/luajit * fixed issues, testing * bumped up version number * updated docs --- docs/changes.md | 14 +++ init.lua | 189 ++++++++++++++++++++++++++------ rockspecs/multi-16.2-0.rockspec | 42 +++++++ 3 files changed, 209 insertions(+), 36 deletions(-) create mode 100644 rockspecs/multi-16.2-0.rockspec diff --git a/docs/changes.md b/docs/changes.md index 3c3dd05..a8d7a06 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,6 +1,7 @@ # Changelog Table of contents --- +[Update 16.2.0 - TimeStamps and UUIDs](#update-1620---timestamps-and-uuids)
[Update 16.1.0 - The Flow State](#update-1610---the-flow-state)
[Update 16.0.1 - Bug fix](#update-1601---bug-fix)
[Update 16.0.0 - Connecting the dots](#update-1600---getting-the-priorities-straight)
@@ -60,6 +61,19 @@ Table of contents [Update: EventManager 1.0.0 - Error checking](#update-eventmanager-100---error-checking)
[Version: EventManager 0.0.1 - In The Beginning things were very different](#version-eventmanager-001---in-the-beginning-things-were-very-different) +# Update 16.2.0 - TimeStamps and UUIDs and bugfixes +Added +--- +- threadedfunctions, multiobjs and threads contain new method: `GetCreationTimestamp()` With will return a string formated date time using ISO 8601 + +Changed +--- +- Internal uuid functions to not use bitwise operators + +Fixed +--- +- [Bitwise operations break lua 5.1/luajit](https://github.com/rayaman/multi/issues/73) + # Update 16.1.0 - The Flow State Added --- diff --git a/init.lua b/init.lua index 508f240..cedf31a 100644 --- a/init.lua +++ b/init.lua @@ -92,7 +92,7 @@ function multi.getTypes() return types end -multi.Version = "16.1.0" +multi.Version = "16.2.0" multi.Name = "root" multi.NIL = {Type="NIL"} local NIL = multi.NIL @@ -702,49 +702,21 @@ local time = os.time local ok, chronos = pcall(require, "chronos") -- hpc if ok then - math.randomseed(chronos.nanotime()*1000000000) + math.randomseed(chronos.nanotime()*100000000) else math.randomseed(time()) end -function multi.UUID() - - -- random bytes - local value = {} - for i = 1, 16 do - value[i] = math.random(0, 255) - end - - -- current timestamp in ms - local timestamp = time() * 1000 - - -- timestamp - value[1] = (timestamp >> 40) & 0xFF - value[2] = (timestamp >> 32) & 0xFF - value[3] = (timestamp >> 24) & 0xFF - value[4] = (timestamp >> 16) & 0xFF - value[5] = (timestamp >> 8) & 0xFF - value[6] = timestamp & 0xFF - - -- version and variant - value[7] = (value[7] & 0x0F) | 0x70 - value[9] = (value[9] & 0x3F) | 0x80 - res = "" - for i = 1, #value do - res = res .. string.format('%02x', value[i]) - if i == 4 or i == 6 or i == 8 or i == 10 then - res = res .. "-" - end - end - return res -end - function multi:create(ref) - ref.UID = self.UUID() + ref.UID = multi.generate_uuid7() self.OnObjectCreated:Fire(ref, self) return self end +function multi:GetCreationTimestamp() + return multi.extract_uuid7_timestamp(self.UID).iso8601 +end + function multi:setName(name) self.Name = name return self @@ -1602,7 +1574,10 @@ end function thread:newFunctionBase(generator, holdme, TYPE) return function() - local tfunc = {} + local UID = multi.generate_uuid7() + local tfunc = { + GetCreationTimestamp = function() return multi.extract_uuid7_timestamp(UID).iso8601 end, + } tfunc.Active = true function tfunc:Pause() self.Active = false @@ -1810,6 +1785,10 @@ function thread:newThread(name, func, ...) return self._isPaused end + function c:GetCreationTimestamp() + return multi.extract_uuid7_timestamp(self.UID).iso8601 + end + local resumed = false function c:Pause() if not self._isPaused then @@ -2677,6 +2656,144 @@ function multi.success(...) io.write("\x1b[92mSUCCESS:\x1b[0m " .. table.concat(t," ") .. "\n") end +-- UUID Handling +local function get_timestamp_ms() + -- os.time() gives seconds, we need milliseconds + -- For sub-second precision, we'd need a C extension in real use + -- Here we'll use seconds * 1000 + a pseudo-random millisecond component + local sec = os.time() + return sec * 1000 +end + +-- Convert number to hex string with specified length +local function to_hex(num, len) + local hex = string.format("%x", num) + return string.rep("0", len - #hex) .. hex +end + +-- Generate random hex string of specified length +local function random_hex(len) + local result = "" + for i = 1, len do + result = result .. string.format("%x", math.random(0, 15)) + end + return result +end + +multi.generate_uuid7 = function() + -- Seed random number generator with current time + math.randomseed(os.time() * os.clock() * 1000000) + + -- Get timestamp in milliseconds + local timestamp_ms = get_timestamp_ms() + + -- Convert timestamp to 12 hex characters (48 bits) + -- Split into high and low parts + local high = math.floor(timestamp_ms / 16777216) -- Upper 24 bits + local low = timestamp_ms % 16777216 -- Lower 24 bits + + local time_high = to_hex(high, 6) + local time_low = to_hex(low, 6) + + -- Version and random bits (4 bits version + 12 bits random = 16 bits = 4 hex) + local ver_rand = random_hex(3) + local version_field = "7" .. ver_rand -- Version 7 + + -- Variant and random bits (2 bits variant + 14 bits random = 16 bits = 4 hex) + local rand_val = math.random(0, 16383) -- 14 bits of random + local variant_field = to_hex(0x8000 + rand_val, 4) -- Set variant bits to 10 + + -- Remaining random bits (48 bits = 12 hex) + local random_field = random_hex(12) + + -- Assemble UUID: xxxxxxxx-xxxx-7xxx-xxxx-xxxxxxxxxxxx + local uuid = string.format("%s%s-%s-%s-%s-%s", + time_high:sub(1, 2), + time_high:sub(3, 6) .. time_low:sub(1, 2), + time_low:sub(3, 6), + version_field, + variant_field, + random_field + ) + return uuid +end + +local function is_leap(y) + return (y % 4 == 0 and y % 100 ~= 0) or (y % 400 == 0) +end + +local function format_date(seconds) + local days = math.floor(seconds / 86400) + local remainder = seconds % 86400 + local hours = math.floor(remainder / 3600) + remainder = remainder % 3600 + local minutes = math.floor(remainder / 60) + local secs = remainder % 60 + + -- Calculate year, month, day from days since epoch (1970-01-01) + local year = 1970 + local month = 1 + local day = 1 + days + + local days_in_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} + + while true do + local days_in_year = is_leap(year) and 366 or 365 + if day <= days_in_year then break end + day = day - days_in_year + year = year + 1 + end + + while true do + local dim = days_in_month[month] + if month == 2 and is_leap(year) then dim = 29 end + if day <= dim then break end + day = day - dim + month = month + 1 + end + + return string.format("%04d-%02d-%02d %02d:%02d:%02d", + year, month, day, hours, minutes, secs) +end + +multi.extract_uuid7_timestamp = function(uuid_str) + -- Remove hyphens from UUID + local hex = uuid_str:gsub("-", "") + + -- Validate length + if #hex ~= 32 then + return nil, "Invalid UUID length" + end + + -- Extract first 12 hex characters (48 bits = timestamp in ms) + local timestamp_hex = hex:sub(1, 12) + + -- Convert hex to decimal - need to handle large numbers + -- Break into two parts to avoid overflow + local high = tonumber(timestamp_hex:sub(1, 6), 16) + local low = tonumber(timestamp_hex:sub(7, 12), 16) + local timestamp_ms = high * 16777216 + low -- 16^6 = 16777216 + + if not timestamp_ms then + return nil, "Failed to parse timestamp" + end + + -- Convert milliseconds to seconds + local timestamp_sec = math.floor(timestamp_ms / 1000) + local ms_remainder = timestamp_ms % 1000 + + -- Manual date calculation for large timestamps + + local date_str = format_date(timestamp_sec) + + return { + milliseconds = timestamp_ms, + seconds = timestamp_sec, + date = date_str, + iso8601 = date_str:gsub(" ", "T") .. string.format(".%03dZ", ms_remainder) + } +end + -- Old things for compatability multi.GetType = multi.getType multi.IsPaused = multi.isPaused diff --git a/rockspecs/multi-16.2-0.rockspec b/rockspecs/multi-16.2-0.rockspec new file mode 100644 index 0000000..a7477ac --- /dev/null +++ b/rockspecs/multi-16.2-0.rockspec @@ -0,0 +1,42 @@ +package = "multi" +version = "16.2-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v16.2.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. Features non coroutine based multi-tasking, coroutine based multi-tasking, and system threading (Requires use of an integration). + Check github for documentation. + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + ["multi"] = "init.lua", + ["multi.integration.lanesManager"] = "integration/lanesManager/init.lua", + ["multi.integration.lanesManager.extensions"] = "integration/lanesManager/extensions.lua", + ["multi.integration.lanesManager.threads"] = "integration/lanesManager/threads.lua", + ["multi.integration.loveManager"] = "integration/loveManager/init.lua", + ["multi.integration.loveManager.extensions"] = "integration/loveManager/extensions.lua", + ["multi.integration.loveManager.threads"] = "integration/loveManager/threads.lua", + ["multi.integration.loveManager.utils"] = "integration/loveManager/threads.lua", + --["multi.integration.lovrManager"] = "integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "integration/lovrManager/threads.lua", + ["multi.integration.pseudoManager"] = "integration/pseudoManager/init.lua", + ["multi.integration.pseudoManager.extensions"] = "integration/pseudoManager/extensions.lua", + ["multi.integration.pseudoManager.threads"] = "integration/pseudoManager/threads.lua", + ["multi.integration.luvitManager"] = "integration/luvitManager.lua", + ["multi.integration.threading"] = "integration/threading.lua", + ["multi.integration.sharedExtensions"] = "integration/sharedExtensions/init.lua", + ["multi.integration.priorityManager"] = "integration/priorityManager/init.lua", + --["multi.integration.networkManager"] = "integration/networkManager.lua", + } +} \ No newline at end of file