19 Commits
Author SHA1 Message Date
server 8b5d10cbb6 updated tests and added check in connections 2026-06-09 23:04:02 -07:00
server 0796788fcb removed dup func 2026-06-08 11:04:25 -07:00
server ee5e3bde7f fixed int issue with lua 5.5 2026-05-30 21:21:32 -07:00
server c86413351c updated opt for connections 2026-05-14 22:29:34 -07:00
server 4a52cd5e14 working on new documentation 2026-04-04 01:00:50 -07:00
server 14beeb686a making some changes 2026-04-01 22:22:05 -07:00
server 60783fdb16 remove luajit and 5.5 not supported with current actions 2026-03-23 22:07:57 -07:00
server b1a7e61c03 added lua 5.5 and using luajit 2026-03-23 22:06:18 -07:00
server 9fa0e7c2cb changed branch to master 2026-03-23 22:01:52 -07:00
server ef243f87d4 removed love2d test 2026-03-23 22:01:12 -07:00
server 178283e0b4 Merge branch 'master' into v16.3.0 2026-03-23 22:00:29 -07:00
server 7f35265a8e new actions (#78) 2026-03-23 21:59:47 -07:00
server 0501f068e4 testing new action 2026-03-23 21:55:35 -07:00
server ffd7ac15f6 added new unit tests 2026-03-23 21:52:56 -07:00
server 869368af56 updated docs 2026-03-23 21:26:40 -07:00
server eeb75490dc default priority is normal 2026-03-23 21:18:27 -07:00
server 2fec5b39b8 fixing bugs 2026-03-23 20:40:58 -07:00
server 0a92890290 working on task management 2026-03-15 01:13:00 -07:00
server 40de9a8d22 Improving tests 2026-01-12 23:15:31 -08:00
98 changed files with 30508 additions and 28894 deletions
-25
View File
@@ -1,25 +0,0 @@
name: Build & Run tests Love2d
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
strategy:
fail-fast: false
runs-on: "ubuntu-latest"
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Install love2d
run: |
sudo apt install fuse
wget https://github.com/love2d/love/releases/download/11.4/love-11.4-x86_64.AppImage -O love.AppImage
sudo chmod +x love.AppImage
- name: Run Tests
run: |
./love.AppImage tests
+3 -3
View File
@@ -2,9 +2,9 @@ name: Lua Unit Tests (Matrix)
on:
push:
branches: [ main ]
branches: [ master ]
pull_request:
branches: [ main ]
branches: [ master ]
jobs:
test:
@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
luaVersion: ["5.1", "5.2", "5.3", "5.4", "luajit-2.1.0-beta3"]
luaVersion: ["5.1", "5.2", "5.3", "5.4"]
steps:
- name: Checkout repository
+5 -5
View File
@@ -1,8 +1,8 @@
# Multi Version: 16.1.0 - The Flow State
# Multi Version: 16.3.0 -
**Key Changes**
- Updated Processors to have more controlled over scheduled processes
- Forwarding connections
- New Timeout handling
- Fixed connection multiplying
- Optimizing the library
- Updated task management support
Refer to the [Change Log](https://github.com/rayaman/multi/blob/master/docs/changes.md) for more infromation
@@ -12,7 +12,7 @@ My multitasking library for lua. It is a pure lua binding, with exceptions of th
</br>
Progress is being made in [v16.2.0](https://github.com/rayaman/multi/tree/v16.2.0)
Progress is being made in [v16.3.0](https://github.com/rayaman/multi/tree/v16.3.0)
---
</br>
+28 -5
View File
@@ -1,7 +1,8 @@
# Changelog
Table of contents
---
[Update 16.2.0 - TimeStamps and UUIDs](#update-1620---timestamps-and-uuids-and-bugfixes)</br>
[Update 16.3.0 - Proper Priorities](#update-1620---proper-priorities)</br>
[Update 16.2.0 - TimeStamps and UUIDs](#update-1620---timestamps-and-uuids)</br>
[Update 16.1.0 - The Flow State](#update-1610---the-flow-state)</br>
[Update 16.0.1 - Bug fix](#update-1601---bug-fix)</br>
[Update 16.0.0 - Connecting the dots](#update-1600---getting-the-priorities-straight)</br>
@@ -61,6 +62,30 @@ Table of contents
[Update: EventManager 1.0.0 - Error checking](#update-eventmanager-100---error-checking)</br>
[Version: EventManager 0.0.1 - In The Beginning things were very different](#version-eventmanager-001---in-the-beginning-things-were-very-different)
# Update 16.3.0 - Proper Priorities
Added
---
- New processor scoped to track connections that exist
- Better Destroy logic for connections
- Added UPTIME to all objects in the library via the create hook
- added `proc:isPaused()` to processors
- `multi.setClock(c)` allows you to set the internal clock used by the multi core. For example on linux os.clock doesn't give time elapsed since the program started like it does on windows. This method allows one to swap that out.
Changed
---
- `thread.kill(msg)` now accepts a message. If omitted will use default `thread killed!`
- Modified multiple locations to not create a function during a thread.hold() and reuse. Only functions containing an upvalue currently have functions being created during hold. This will eventually be fixed
- Updated `multi:getStats()` with more information
- `multi.forwardConnection()` now returns the link to the source connection so you can do cleanup
- When connecting to a connection you can unconnect using ref:Unconnect() it has an internal reference to the root connection
- Unnamed threads have a shorter postfix of only 4 random characters
Fixed
---
- [Issue](https://github.com/rayaman/multi/issues/77) with concat of connections where conn .. func did not work properly
- [Issue](https://github.com/rayaman/multi/issues/67) with connection multiplying
# Update 16.2.0 - TimeStamps and UUIDs and bugfixes
Added
---
@@ -629,15 +654,13 @@ Added
Output:
```
I run before all and control if execution should continue!
I run before all and control if things go!
Hi 3
Hi 1
I run before all and control if execution should continue!
Hi 3
Hi 2
Test 1
Test 2
Test 3
I run after it all!
```
**Note:** Concat of connections does modify internal events on both connections depending on the direction func .. conn or conn .. func See implemention below:
File diff suppressed because it is too large Load Diff
+237 -109
View File
@@ -92,7 +92,7 @@ function multi.getTypes()
return types
end
multi.Version = "16.2.0"
multi.Version = "16.3.0-testing"
multi.Name = "root"
multi.NIL = {Type="NIL"}
local NIL = multi.NIL
@@ -154,11 +154,18 @@ function multi:isType(type)
return self.Type == type
end
multi.connection_count = 0
multi.connection_subscriptions = 0
function multi:getStats()
local stats = {
[multi.Name] = {
threads = multi:getThreads(),
tasks = multi:getTasks()
tasks = multi.Mainloop,
name = "root",
fullName = "root",
connections = self.connection_count,
subscriptions = self.connection_subscriptions
}
}
local procs = multi:getProcessors()
@@ -166,7 +173,11 @@ function multi:getStats()
local proc = procs[i]
stats[proc:getFullName()] = {
threads = proc:getThreads(),
tasks = proc:getTasks()
tasks = proc.Mainloop,
name = proc:getName(),
fullName = proc:getFullName(),
connections = proc.connection_count,
subscriptions = proc.connection_subscriptions
}
end
return stats
@@ -202,7 +213,7 @@ end
function multi.forwardConnection(src, dest)
if multi.isMulitObj(src) and multi.isMulitObj(dest) then
src(function(...)
return src(function(...)
dest:Fire(...)
end)
else
@@ -215,23 +226,32 @@ local ignoreconn = true
local empty_func = function() end
function multi:newConnection(protect,func,kill)
self.connection_count = self.connection_count + 1
local processor = self
local c={}
local c = {}
local lock = false
local fast = {}
c.__connectionAdded = function() end
c.rawadd = false
c.Parent = self
c._child_conns = {} -- tracks connections spawned by operators
c.destroyed = false
setmetatable(c,{
__call=function(self,...)
-- Helper: register a child connection for cleanup
local function trackChild(cn)
c._child_conns[#c._child_conns + 1] = cn
return cn
end
setmetatable(c, {
__call = function(self, ...)
local t = ...
if type(t)=="table" then
for i,v in pairs(t) do
if v==self then
local ref = self:Connect(select(2,...))
if type(t) == "table" then
for i, v in pairs(t) do
if v == self then
local ref = self:Connect(select(2, ...))
if ref then
ref.root_link = select(1,...)
ref.root_link = select(1, ...)
return ref
end
return self
@@ -242,7 +262,8 @@ function multi:newConnection(protect,func,kill)
return self:Connect(...)
end
end,
__unm = function(obj) -- -obj Reverses the order of connected events
__unm = function(obj)
local conns = obj:Bind({})
for i = #conns, 1, -1 do
obj.rawadd = true
@@ -251,15 +272,16 @@ function multi:newConnection(protect,func,kill)
end
return obj
end,
__mod = function(obj1, obj2) -- %
local cn = self:newConnection()
if type(obj1) == "function" and type(obj2) == "table" then
__mod = function(obj1, obj2)
local cn = trackChild(self:newConnection())
if (type(obj1) == "function" or type(obj1) == "table" and obj1.Type == multi.registerType("function", "functions")) and type(obj2) == "table" then
obj2(function(...)
cn:Fire(obj1(...))
end)
elseif type(obj1) == "table" and type(obj2) == "function" then
elseif type(obj1) == "table" and (type(obj2) == "function" or type(obj2) == "table" and obj2.Type == multi.registerType("function", "functions")) then
local conns = obj1:Bind({})
for i = 1,#conns do
for i = 1, #conns do
obj1(function(...)
conns[i](obj2(...))
end)
@@ -274,31 +296,33 @@ function multi:newConnection(protect,func,kill)
end
return obj1
else
error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)")
multi.error("Invalid mod!", type(obj1), type(obj2), "Expected function, connection(table)")
end
return cn
end,
__div = function(obj1, obj2) -- /
local cn = self:newConnection()
local ref
__div = function(obj1, obj2)
local cn = trackChild(self:newConnection())
if type(obj1) == "function" and type(obj2) == "table" then
obj2(function(...)
local args = {obj1(...)}
local args = { obj1(...) }
if args[1] then
table.remove(args, 1)
cn:Fire(multi.unpack(args))
end
end)
else
multi.error("Invalid divide!", type(obj1), type(obj2),"Expected function/connection(table)")
multi.error("Invalid divide!", type(obj1), type(obj2), "Expected function/connection(table)")
end
return cn
end,
__concat = function(obj1, obj2) -- ..
local cn = self:newConnection()
__concat = function(obj1, obj2)
local cn = trackChild(self:newConnection())
local ref
if type(obj1) == "function" and type(obj2) == "table" then
cn(function(...)
if obj1(...) then
if obj1(...) == true then
obj2:Fire(...)
end
end)
@@ -320,26 +344,24 @@ function multi:newConnection(protect,func,kill)
end
end)
end
return obj1
return cn
elseif type(obj1) == "table" and type(obj2) == "table" then
--
-- reserved
else
error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function")
error("Invalid concat!", type(obj1), type(obj2), "Expected function/connection(table), connection(table)/function")
end
return cn
end,
__add = function(c1,c2) -- Or
local cn = self:newConnection()
c1(function(...)
cn:Fire(...)
end)
c2(function(...)
cn:Fire(...)
end)
__add = function(c1, c2) -- Or
local cn = trackChild(self:newConnection())
c1(function(...) cn:Fire(...) end)
c2(function(...) cn:Fire(...) end)
return cn
end,
__mul = function(c1,c2) -- And
local cn = self:newConnection()
__mul = function(c1, c2) -- And
local cn = trackChild(self:newConnection())
local ref1, ref2
if c1.__hasInstances == nil then
cn.__hasInstances = {2}
@@ -367,12 +389,80 @@ function multi:newConnection(protect,func,kill)
if cn.__count[1] == cn.__hasInstances[1] then
cn:Fire(...)
cn.__count[1] = 0
c1:Unlock(ref1)
c2:Unlock(ref2)
end
end)
return cn
end})
end,
})
-- ... (Type, ID, FC setup, Lock/Unlock, Fire, Connect, Bind, etc. unchanged) ...
--- Destroy this connection and all resources it owns.
-- Recursively destroys any connections created by operator overloads (+, *, %, /, ..).
-- Safe to call multiple times.
function c:Destroy()
if self.destroyed then return end
self.destroyed = true
-- Unlock first so any in-progress Fire() calls drain cleanly
lock = false
-- Destroy operator-spawned child connections recursively
for i = 1, #self._child_conns do
local child = self._child_conns[i]
if child and type(child.Destroy) == "function" and not child.destroyed then
child:Destroy()
end
end
self._child_conns = {}
-- Null out root_link back-references on all connection handles
for key, _ in pairs(fast) do
if type(key) == "string" then
local handle = fast[key]
if type(handle) == "table" and rawget(handle, "root_link") then
handle.root_link = nil
end
end
end
-- Adjust subscription count on parent before wiping fast[]
if self.Parent and self.Parent.connection_subscriptions then
self.Parent.connection_subscriptions =
math.max(0, self.Parent.connection_subscriptions - #fast)
end
-- Clear all stored callbacks
fast = {}
-- Remove from parent's object list
if self.Parent then
for i = 1, #self.Parent do
if self.Parent[i] == self then
table.remove(self.Parent, i)
break
end
end
end
-- Detach hooks so stale references can't re-fire into dead state
self.__connectionAdded = function() end
self.Connect = function() multi:warning("Connect called on destroyed connection") end
self.Fire = function() end
self.Bind = function() return {} end
self.Unconnect = function() end
self.Lock = function() return self end
self.Unlock = function() return self end
if self.Parent and self.Parent.connection_count then
self.Parent.connection_count = math.max(0, self.Parent.connection_count - 1)
end
self.Parent = nil
end
-- Alias
c.destroy = c.Destroy
c.Type=multi.registerType("connector", "connections")
c.func={}
@@ -402,7 +492,8 @@ function multi:newConnection(protect,func,kill)
function c:Unlock(conn)
if conn and conn.lock then
for i = 1, #fast do
local n = #fast
for i = 1, n do
if conn.lock == fast[i] then
fast[i] = fast[conn.ref]
return self
@@ -418,12 +509,13 @@ function multi:newConnection(protect,func,kill)
function c:Fire(...)
if lock then return end
local kills = {}
for i=1,#fast do
local n = #fast
for i=1, n do
local suc, err = pcall(fast[i], ...)
if not suc then
multi.error(err)
end
if kill then
if kill and n > 0 then
table.insert(kills,i)
processor:newTask(function()
for _, k in pairs(kills) do
@@ -448,6 +540,7 @@ function multi:newConnection(protect,func,kill)
for i = 1, #fast do
if fast[conn.ref] == fast[i] then
table.remove(self)
self.Parent.connection_subscriptions = self.Parent.connection_subscriptions - 1
return table.remove(fast, i), i
end
end
@@ -476,12 +569,18 @@ function multi:newConnection(protect,func,kill)
function c:Fire(...)
if lock then return end
for i=1,#fast do
if fast[i] then
fast[i](...)
end
end
end
end
function c:Connect(func, name)
if func == nil then
multi.error("You must provide a valid function when calling a connection")
end
self.Parent.connection_subscriptions = self.Parent.connection_subscriptions + 1
local th
if thread.getRunningThread then
th = thread.getRunningThread()
@@ -494,11 +593,12 @@ function multi:newConnection(protect,func,kill)
__CurrentConnectionThread = nil
end
end
local ref = multi.randomString(24)
table.insert(fast, func)
if name then
fast[name] = func
else
fast["Conn_"..multi.randomString(12)] = func
fast["Conn_"..ref:sub(1, 12)] = func
end
local temp = {fast = true}
setmetatable(temp,{
@@ -518,9 +618,13 @@ function multi:newConnection(protect,func,kill)
rawset(t,k,v)
end,
})
temp.ref = multi.randomString(24)
temp.ref = ref
fast[temp.ref] = func
temp.name = name
temp.link = self
function temp:Unconnect()
self.link:Unconnect(self)
end
if self.rawadd then
self.rawadd = false
else
@@ -532,7 +636,9 @@ function multi:newConnection(protect,func,kill)
function c:Bind(t)
local temp = fast
self.Parent.connection_subscriptions = self.Parent.connection_subscriptions - #fast
fast=t
self.Parent.connection_subscriptions = self.Parent.connection_subscriptions + #t
return temp
end
@@ -542,6 +648,7 @@ function multi:newConnection(protect,func,kill)
function c:Remove()
local temp = fast
self.Parent.connection_subscriptions = self.Parent.connection_subscriptions - #fast
fast={}
return temp
end
@@ -610,10 +717,10 @@ function multi:SetTime(n)
c.OnTimedOut = self:newConnection()
c.OnTimerResolved = self:newConnection()
self._timer=c.timer
function c:Act()
function c:Act(dt)
if self.timer:Get()>=self.set then
self.link:Pause()
self.OnTimedOut:Fire(self.link)
self.OnTimedOut:Fire(self.link,dt)
self:Destroy()
return true
end
@@ -698,17 +805,12 @@ function multi:isDone()
return self.Active~=true
end
local time = os.time
local ok, chronos = pcall(require, "chronos") -- hpc
if ok then
math.randomseed(chronos.nanotime()*100000000)
else
math.randomseed(time())
end
function multi:create(ref)
ref.UID = multi.generate_uuid7()
ref.UPTIME = clock()
if ref.setPriority then
ref:setPriority("normal")
end
self.OnObjectCreated:Fire(ref, self)
return self
end
@@ -813,12 +915,12 @@ end
function multi:newEvent(task, func)
local c,err=self:newBase(multi.registerType("event", "events"))
local task = task or function() end
function c:Act()
function c:Act(dt)
local t = task(self)
if t then
self:Pause()
self.returns = t
self.OnEvent:Fire(self)
self.OnEvent:Fire(self,dt)
return true
end
end
@@ -840,10 +942,10 @@ function multi:newUpdater(skip, func)
local c,err=self:newBase(multi.registerType("updater", "updaters"))
local pos = 1
local skip = skip or 1
function c:Act()
function c:Act(dt)
if pos >= skip then
pos = 0
self.OnUpdate:Fire(self)
self.OnUpdate:Fire(self,dt)
return true
end
pos = pos+1
@@ -867,11 +969,11 @@ function multi:newAlarm(set, func)
c.set=set or 0
local count = 0
local t = clock()
function c:Act()
function c:Act(dt)
if clock()-t>=self.set then
self:Pause()
self.Active=false
self.OnRing:Fire(self)
self.OnRing:Fire(self,dt)
t = clock()
return true
end
@@ -905,13 +1007,13 @@ function multi:newLoop(func, notime)
local c,err=self:newBase(multi.registerType("loop", "loops"))
local start=clock()
if notime then
function c:Act()
self.OnLoop:Fire(self)
function c:Act(dt)
self.OnLoop:Fire(self,nil,dt)
return true
end
else
function c:Act()
self.OnLoop:Fire(self,clock()-start)
function c:Act(dt)
self.OnLoop:Fire(self,clock()-start,dt)
return true
end
end
@@ -941,13 +1043,13 @@ function multi:newStep(start,reset,count,skip)
think=-1
end
end
function c:Act()
function c:Act(dt)
if self~=nil then
if self.spos==0 then
if self.pos==self.start then
self.OnStart:Fire(self)
end
self.OnStep:Fire(self,self.pos)
self.OnStep:Fire(self,self.pos,dt)
self.pos=self.pos+self.count
if self.pos-self.count==self.endAt then
self:Pause()
@@ -993,11 +1095,11 @@ function multi:newTLoop(func, set)
c.life=0
c:setPriority("Low")
function c:Act()
function c:Act(dt)
if self.timer:Get() >= self.set then
self.life=self.life+1
self.timer:Reset()
self.OnLoop:Fire(self, self.life)
self.OnLoop:Fire(self, self.life,dt)
return true
end
end
@@ -1052,13 +1154,13 @@ function multi:newTStep(start,reset,count,set)
self:Resume()
return self
end
function c:Act()
function c:Act(dt)
if clock()-self.timer>=self.set then
self:Reset()
if self.pos==self.start then
self.OnStart:Fire(self)
end
self.OnStep:Fire(self,self.pos)
self.OnStep:Fire(self,self.pos,dt)
self.pos=self.pos+self.count
if self.pos-self.count==self.endAt then
self:Pause()
@@ -1181,6 +1283,8 @@ function multi:newProcessor(name, opts, priority)
end
sandcount = sandcount + 1
c.connection_count = 0
c.connection_subscriptions = 0
c.Mainloop = {}
c.Type = multi.registerType("process", "processes")
local Active = nothread or false
@@ -1196,7 +1300,7 @@ function multi:newProcessor(name, opts, priority)
local handler
if type(opts) == "table" then
priority = opts.Priority or false
priority = opts.Priority
Active = opts.Start or false
maxThreads = opts.MaxThreads or -1
maxObjects = opts.MaxObjects or -1
@@ -1207,7 +1311,7 @@ function multi:newProcessor(name, opts, priority)
end
end
if priority then
if priority or multi.defaultSettings.priority then
handler = c:createPriorityHandler(c)
else
handler = c:createHandler(c)
@@ -1280,27 +1384,27 @@ function multi:newProcessor(name, opts, priority)
function c:boost(count)
boost = count or 1
if boost > 1 then
self.run = function()
self.run = function(dt)
if not Active then return end
for i=1,boost do
c:uManager(true)
c:uManager(dt)
handler()
end
return c
end
else
self.run = function()
self.run = function(dt)
if not Active then return end
c:uManager(true)
c:uManager(dt)
handler()
return c
end
end
end
function c.run()
function c.run(dt)
if not Active then return end
c:uManager(true)
c:uManager(dt)
handler()
return c
end
@@ -1309,6 +1413,10 @@ function multi:newProcessor(name, opts, priority)
return Active
end
function c:isPaused()
return not(Active)
end
function c.Start()
Active = true
return c
@@ -1321,8 +1429,10 @@ function multi:newProcessor(name, opts, priority)
function c:Destroy()
Active = false
if c.process then
c.process:Destroy()
end
end
function c:setTaskDelay(delay)
if type(delay) == "function" then
@@ -1517,8 +1627,9 @@ function thread.skip(n)
return yield(CMD, t_skip, n or 1)
end
function thread.kill()
multi.error("thread killed!")
function thread.kill(msg)
msg = msg or "thread killed!"
multi.error(msg)
end
function thread.yield()
@@ -1550,7 +1661,8 @@ function thread.get(name)
end
function thread.waitFor(name)
thread.hold(function() return thread.get(name)~=nil end)
local check = function() return thread.get(name)~=nil end
thread.hold(check)
return thread.get(name)
end
@@ -1578,6 +1690,7 @@ function thread:newFunctionBase(generator, holdme, TYPE)
local tfunc = {
GetCreationTimestamp = function() return multi.extract_uuid7_timestamp(UID).iso8601 end,
}
tfunc.UPTIME = clock()
tfunc.Active = true
function tfunc:Pause()
self.Active = false
@@ -1592,9 +1705,7 @@ function thread:newFunctionBase(generator, holdme, TYPE)
return nil, "Function is paused"
end
local rets, err
local function wait()
if thread.isThread() then
return thread.hold(function()
local check = function()
if err then
return multi.NIL, err
elseif rets then
@@ -1602,7 +1713,10 @@ function thread:newFunctionBase(generator, holdme, TYPE)
rets = nil
return cleanReturns((g[1] or multi.NIL),g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15],g[16])
end
end)
end
local function wait()
if thread.isThread() then
return thread.hold(check)
else
while not rets and not err do
multi:uManager()
@@ -1751,7 +1865,7 @@ function thread:newThread(name, func, ...)
multi.OnLoad:Fire() -- This was done incase a threaded function was called before mainloop/uManager was called
if type(name) == "function" then
func = name
name = "UnnamedThread_"..multi.randomString(16)
name = "UnnamedThread_"..multi.randomString(4)
end
local c={nil,nil,nil,nil,nil,nil,nil}
c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil}
@@ -2326,7 +2440,7 @@ function multi.init(settings, realsettings)
return _G["$multi"].multi,_G["$multi"].thread
end
function multi:uManager()
function multi:uManager(dt)
if self.Active then
__CurrentProcess = self
multi.OnPreLoad:Fire()
@@ -2335,7 +2449,7 @@ function multi:uManager()
end
end
function multi:uManagerRefP1()
function multi:uManagerRefP1(dt)
if self.Active then
__CurrentProcess = self
local Loop=self.Mainloop
@@ -2343,7 +2457,7 @@ function multi:uManagerRefP1()
__CurrentTask = Loop[_D]
for P=1,9 do
if PList[P]%__CurrentTask.Priority==0 then
__CurrentTask:Act()
__CurrentTask:Act(dt)
__CurrentProcess = self
end
end
@@ -2351,13 +2465,13 @@ function multi:uManagerRefP1()
end
end
function multi:uManagerRef()
function multi:uManagerRef(dt)
if self.Active then
__CurrentProcess = self
local Loop=self.Mainloop
for _D=#Loop,1,-1 do
__CurrentTask = Loop[_D]
__CurrentTask:Act()
__CurrentTask:Act(dt)
__CurrentProcess = self
end
end
@@ -2389,7 +2503,7 @@ function table.merge(t1, t2)
return t1
end
math.randomseed(os.time())
math.randomseed(math.floor(os.time()))
function multi:enableLoadDetection()
if multi.maxSpd then return end
@@ -2397,7 +2511,7 @@ function multi:enableLoadDetection()
local temp = self:newProcessor()
local t = clock()
local stop = false
temp:benchMark(.01):OnBench(function(time,steps)
temp:benchMark(.1):OnBench(function(time,steps)
stop = steps
end)
while not stop do
@@ -2410,28 +2524,39 @@ end
local lastVal = 0
local last_step = 0
function multi:getLoad()
if not multi.maxSpd then multi:enableLoadDetection() end
function multi:getLoad(loops)
local proc = proc or multi
if not proc.maxSpd then proc:enableLoadDetection() end
local val = nil
local bench
local bb
self:benchMark(.01).OnBench(function(time,steps)
local avg = 0
local loops = loops or 5
for i=1,loops do
self:benchMark(1).OnBench(function(time,steps)
bench = steps
bb = steps
avg = avg + steps
if steps > proc.maxSpd then
proc.maxSpd = steps
end
end)
_,timeout = multi.hold(function()
return bench
end,{sleep=.012})
end,{sleep=1.1})
end
avg = avg/loops
if timeout or not bench then
bench = 0
bb = 0
end
bench = bench^1.5
val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100)
val = math.ceil((1-(bench/(proc.maxSpd/2.2)))*100)
if val<0 then val = 0 end
if val > 100 then val = 100 end
lastVal = val
last_step = bb*100
print(proc.maxSpd, bench, bench/proc.maxSpd/2.2,val)
return val,last_step
end
@@ -2532,8 +2657,8 @@ function multi:benchMark(sec,p,pt)
end
function multi.Round(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
local mult = 10 ^ (numDecimalPlaces or 0)
return math.floor((num * mult) + 0.5 + 1e-10) / mult
end
function multi.AlignTable(tab)
@@ -2582,6 +2707,12 @@ end
function multi:reallocate(processor, index)
index=index or #processor.Mainloop+1
local int=self.Parent
for i = #int.Mainloop, 1, -1 do
if int.Mainloop[i] == self then
table.remove(int.Mainloop,i)
break
end
end
self.Parent=processor
if index then
table.insert(processor.Mainloop, index, self)
@@ -2679,11 +2810,8 @@ local function random_hex(len)
end
return result
end
math.randomseed(math.floor(os.time()))
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()
+10 -6
View File
@@ -58,16 +58,20 @@ function multi:newSystemThreadedQueue(name)
GLOBAL[name] = c
end
local peek = function()
return c:peek()
end
local pop = function()
return c:pop()
end
function c:Hold(opt)
local multi, thread = require("multi"):init()
if opt.peek then
return thread.hold(function()
return self:peek()
end)
return thread.hold(peek)
else
return thread.hold(function()
return self:pop()
end)
return thread.hold(pop)
end
end
-2
View File
@@ -23,9 +23,7 @@ SOFTWARE.
]]
package.path = "?/init.lua;?.lua;" .. package.path
local multi, thread = require("multi"):init()
local pseudoProcessor = multi:newProcessor()
if multi.integration then
return {
init = function()
-39
View File
@@ -1,39 +0,0 @@
function love.conf(t)
t.identity = nil -- The name of the save directory (string)
t.version = "12.0" -- The LOVE version this game was made for (string)
t.console = true -- Attach a console (boolean, Windows only)
t.window.title = "MultiThreadTest" -- The window title (string)
t.window.icon = nil -- Filepath to an image to use as the window's icon (string)
t.window.width = 1280 -- The window width (number)
t.window.height = 720 -- The window height (number)
t.window.borderless = false -- Remove all border visuals from the window (boolean)
t.window.resizable = true -- Let the window be user-resizable (boolean)
t.window.minwidth = 1 -- Minimum window width if the window is resizable (number)
t.window.minheight = 1 -- Minimum window height if the window is resizable (number)
t.window.fullscreen = false -- Enable fullscreen (boolean)
t.window.fullscreentype = "desktop" -- Standard fullscreen or desktop fullscreen mode (string)
t.window.vsync = false -- Enable vertical sync (boolean)
t.window.fsaa = 2 -- The number of samples to use with multi-sampled antialiasing (number)
t.window.display = 1 -- Index of the monitor to show the window in (number)
t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean)
t.window.srgb = false -- Enable sRGB gamma correction when drawing to the screen (boolean)
t.window.x = nil -- The x-coordinate of the window's position in the specified display (number)
t.window.y = nil -- The y-coordinate of the window's position in the specified display (number)
t.modules.audio = false -- Enable the audio module (boolean)
t.modules.event = false -- Enable the event module (boolean)
t.modules.graphics = false -- Enable the graphics module (boolean)
t.modules.image = false -- Enable the image module (boolean)
t.modules.joystick = false -- Enable the joystick module (boolean)
t.modules.keyboard = false -- Enable the keyboard module (boolean)
t.modules.math = false -- Enable the math module (boolean)
t.modules.mouse = false -- Enable the mouse module (boolean)
t.modules.physics = false -- Enable the physics module (boolean)
t.modules.sound = false -- Enable the sound module (boolean)
t.modules.system = false -- Enable the system module (boolean)
t.modules.timer = false -- Enable the timer module (boolean)
t.modules.window = false -- Enable the window module (boolean)
t.modules.thread = true -- Enable the thread module (boolean)
end
+244
View File
@@ -0,0 +1,244 @@
--[[
Test suite for multi.lua
Run with: lua multi_test.lua
Requires multi.lua to be in the same directory or on the Lua path.
Compatible with Lua 5.1, 5.2, 5.3, 5.4, and LuaJIT.
]]
package.path = "../?/init.lua;../?.lua;../init.lua;../?.lua;" .. package.path
-- ─────────────────────────────────────────────
-- Minimal test runner
-- ─────────────────────────────────────────────
local passed, failed, skipped = 0, 0, 0
local failures = {}
local function test(name, fn)
local ok, err = pcall(fn)
if ok then
passed = passed + 1
io.write("\x1b[92m ✓\x1b[0m " .. name .. "\n")
else
failed = failed + 1
table.insert(failures, {name = name, err = tostring(err)})
io.write("\x1b[91m ✗\x1b[0m " .. name .. "\n")
io.write(" " .. tostring(err) .. "\n")
end
end
local function skip(name, reason)
skipped = skipped + 1
io.write("\x1b[93m -\x1b[0m " .. name .. " [SKIPPED: " .. (reason or "") .. "]\n")
end
local function section(name)
io.write("\n\x1b[97m── " .. name .. " ──\x1b[0m\n")
end
local function assert_eq(a, b, msg)
if a ~= b then
error((msg or "assert_eq failed") .. ": expected " .. tostring(b) .. ", got " .. tostring(a), 2)
end
end
local function assert_truthy(v, msg)
if not v then
error((msg or "expected truthy value, got falsy") .. ": " .. tostring(v), 2)
end
end
local function assert_falsy(v, msg)
if v then
error((msg or "expected falsy value, got truthy") .. ": " .. tostring(v), 2)
end
end
local function assert_type(v, t, msg)
if type(v) ~= t then
error((msg or "type mismatch") .. ": expected " .. t .. ", got " .. type(v), 2)
end
end
-- ─────────────────────────────────────────────
-- Load the library
-- ─────────────────────────────────────────────
local multi, thread
local ok, err = pcall(function()
multi, thread = require("multi"):init()
end)
if not ok then
io.write("\x1b[91mFATAL: Could not load multi.lua: " .. tostring(err) .. "\x1b[0m\n")
io.write("Make sure multi.lua is in the same directory or on package.path.\n")
os.exit(1)
end
-- Helper: run the scheduler for up to `max_ticks` ticks or until `done()` returns true.
local function run_until(done, max_ticks)
max_ticks = max_ticks or 10000
for _ = 1, max_ticks do
multi:uManager()
if done and done() then return true end
end
return done == nil
end
section("Initialization")
test("lanesManager init returns GLOBAL and THREAD", function()
local GLOBAL, THREAD = require("multi.integration.lanesManager"):init()
assert_type(GLOBAL, "table")
assert_type(THREAD, "table")
end)
test("lanesManager sets integration table", function()
assert_type(multi.integration.GLOBAL, "table")
assert_type(multi.integration.THREAD, "table")
end)
-- initialize once for rest of tests
local GLOBAL, THREAD = require("multi.integration.lanesManager"):init()
-- ─────────────────────────────────────────────
section("SystemThreadedQueue")
test("queue push/pop", function()
local q = multi:newSystemThreadedQueue("testQ1")
q:push(42)
local val = q:pop()
assert_eq(val, 42)
end)
test("queue peek does not remove", function()
local q = multi:newSystemThreadedQueue("testQ2")
q:push("hello")
assert_eq(q:peek(), "hello")
assert_eq(q:pop(), "hello")
end)
test("queue empty returns nil", function()
local q = multi:newSystemThreadedQueue("testQ3")
assert_eq(q:pop(), nil)
end)
-- ─────────────────────────────────────────────
section("SystemThreadedTable")
test("table set/get", function()
local t = multi:newSystemThreadedTable("testT1")
t.foo = "bar"
assert_eq(t.foo, "bar")
end)
test("table overwrite", function()
local t = multi:newSystemThreadedTable("testT2")
t.x = 1
t.x = 2
assert_eq(t.x, 2)
end)
-- ─────────────────────────────────────────────
section("THREAD API")
test("THREAD set/get", function()
THREAD.set("abc", 123)
assert_eq(THREAD.get("abc"), 123)
end)
test("THREAD getCores > 0", function()
assert_truthy(THREAD.getCores() > 0)
end)
-- ─────────────────────────────────────────────
section("SystemThreadedConnection")
test("connection fires event", function()
local conn = multi:newSystemThreadedConnection("conn1"):init()
local received = nil
conn(function(v)
received = v
end)
conn:Fire("ping")
run_until(function()
return received ~= nil
end, 5000)
assert_eq(received, "ping")
end)
-- ─────────────────────────────────────────────
section("SystemThreadedJobQueue")
test("job queue executes function", function()
local jq = multi:newSystemThreadedJobQueue(1)
jq:registerFunction("add", function(a, b)
return a + b
end)
local result = nil
jq.OnJobCompleted(function(_, val)
result = val
end)
jq:pushJob("add", 2, 3)
run_until(function()
return result ~= nil
end, 10000)
assert_eq(result, 5)
end)
-- ─────────────────────────────────────────────
section("JobQueue newFunction")
test("newFunction returns correct result", function()
local jq = multi:newSystemThreadedJobQueue(1)
local fn = jq:newFunction(function(a, b)
return a * b
end,true)
local result = nil
result = fn(3, 4)
run_until(function()
return result ~= nil
end, 10000)
assert_eq(result, 12)
end)
-- ─────────────────────────────────────────────
-- Summary
-- ─────────────────────────────────────────────
section("Results")
io.write("\n")
io.write("Passed: " .. passed .. "\n")
io.write("Failed: " .. failed .. "\n")
io.write("Skipped: " .. skipped .. "\n")
if failed > 0 then
io.write("\nFailures:\n")
for _, f in ipairs(failures) do
io.write(" - " .. f.name .. "\n")
io.write(" " .. f.err .. "\n")
end
os.exit(1)
end
-10
View File
@@ -1,10 +0,0 @@
package.path = "../?/init.lua;../?.lua;"..package.path
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
require("lldebugger").start()
end
GLOBAL, THREAD = require("multi.integration.loveManager"):init()
require("runtests")
require("threadtests")
-1
View File
@@ -1 +0,0 @@
../
+1095
View File
File diff suppressed because it is too large Load Diff
-195
View File
@@ -1,195 +0,0 @@
package.path = "../?/init.lua;../?.lua;./init.lua;./?.lua;"..package.path
local multi, thread = require("multi"):init{print=true,warn=true,error=true}--{priority=true}
local good = false
local proc = multi:newProcessor("Test")
print("Version: "..multi.Version)
proc.Start()
proc:newAlarm(3):OnRing(function()
good = true
end)
runTest = thread:newFunction(function()
local alarms,tsteps,steps,loops,tloops,updaters,events=false,0,0,0,0,0,false
multi.print("Testing Basic Features. If this fails most other features will probably not work!")
proc:newAlarm(2):OnRing(function(a)
alarms = true
a:Destroy()
end)
proc:newTStep(1,10,1,.1):OnStep(function(t)
tsteps = tsteps + 1
end):OnEnd(function(step)
step:Destroy()
end)
proc:newStep(1,10):OnStep(function(s)
steps = steps + 1
end):OnEnd(function(step)
step:Destroy()
end)
local loop = proc:newLoop(function(l)
loops = loops + 1
end)
proc:newTLoop(function(t)
tloops = tloops + 1
end,.1)
local updater = proc:newUpdater(1):OnUpdate(function()
updaters = updaters + 1
end)
local event = proc:newEvent(function()
return alarms
end)
event.OnEvent(function(evnt)
evnt:Destroy()
events = true
multi.success("Alarms: Ok")
multi.success("Events: Ok")
if tsteps == 10 then multi.success("TSteps: Ok") else multi.error("TSteps: Bad!") end
if steps == 10 then multi.success("Steps: Ok") else multi.error("Steps: Bad!") end
if loops > 100 then multi.success("Loops: Ok") else multi.error("Loops: Bad!") end
if tloops > 10 then multi.success("TLoops: Ok") else multi.error("TLoops: Bad!") end
if updaters > 100 then multi.success("Updaters: Ok") else multi.error("Updaters: Bad!") end
end)
thread.hold(event.OnEvent)
multi.print("Starting Connection and Thread tests!")
func = thread:newFunction(function(count)
multi.print("Starting Status test: ",count)
local a = 0
while true do
a = a + 1
thread.sleep(.1)
thread.pushStatus(a,count)
if a == count then break end
end
return "Done", true, math.random(1,10000)
end)
local ret = func(10)
local ret2 = func(15)
local ret3 = func(20)
local s1,s2,s3 = 0,0,0
ret.OnError(function(...)
multi.error("Func 1:",...)
end)
ret2.OnError(function(...)
multi.error("Func 2:",...)
end)
ret3.OnError(function(...)
multi.error("Func 3:",...)
end)
ret.OnStatus(function(part,whole)
s1 = math.ceil((part/whole)*1000)/10
end)
ret2.OnStatus(function(part,whole)
s2 = math.ceil((part/whole)*1000)/10
end)
ret3.OnStatus(function(part,whole)
s3 = math.ceil((part/whole)*1000)/10
end)
ret.OnReturn(function(...)
multi.success("Done 1",...)
end)
ret2.OnReturn(function(...)
multi.success("Done 2",...)
end)
ret3.OnReturn(function(...)
multi.success("Done 3",...)
end)
local err, timeout = thread.hold(ret.OnReturn * ret2.OnReturn * ret3.OnReturn)
if s1 == 100 and s2 == 100 and s3 == 100 then
multi.success("Threads: All tests Ok")
else
if s1>0 and s2>0 and s3 > 0 then
multi.success("Thread OnStatus: Ok")
else
multi.error("Threads OnStatus or thread.hold(conn) Error!")
end
if timeout then
multi.error("Connection Error!")
else
multi.success("Connection Test 1: Ok")
end
multi.error("Connection holding Error!")
end
conn1 = proc:newConnection()
conn2 = proc:newConnection()
conn3 = proc:newConnection()
local c1,c2,c3,c4 = false,false,false,false
local a = conn1(function()
c1 = true
end)
local b = conn2(function()
c2 = true
end)
local c = conn3(function()
c3 = true
end)
local d = conn3(function()
c4 = true
end)
conn1:Fire()
conn2:Fire()
conn3:Fire()
if c1 and c2 and c3 and c4 then
multi.success("Connection Test 2: Ok")
else
multi.error("Connection Test 2: Error")
end
c3 = false
c4 = false
conn3:Unconnect(d)
conn3:Fire()
if c3 and not(c4) then
multi.success("Connection Test 3: Ok")
else
multi.error("Connection Test 3: Error removing connection")
end
if not love then
local ec = 0
multi.print("Testing pseudo threading")
capture = io.popen("lua tests/threadtests.lua p"):read("*a")
if capture:lower():match("error") then
ec = ec + 1
os.exit(1)
else
io.write(capture)
end
multi.print("Testing lanes threading")
capture = io.popen("lua tests/threadtests.lua l"):read("*a")
if capture:lower():match("error") then
ec = ec + 1
os.exit(1)
else
io.write(capture)
end
os.exit(0)
end
end)
local handle = runTest()
handle.OnError(function(...)
multi.error("Something went wrong with the test!")
print(...)
end)
if not love then
multi:mainloop()
else
local hold = thread:newFunction(function()
thread.hold(handle.OnError + handle.OnReturn)
end, true)
hold()
multi.print("Starting Threading tests!")
end
+1 -1
View File
@@ -1,4 +1,4 @@
package.path = "D:/VSCWorkspace/?/init.lua;D:/VSCWorkspace/?.lua;"..package.path
package.path = "../?/init.lua;../?.lua;../init.lua;../?.lua;" .. package.path
package.cpath = "C:/luaInstalls/lua5.4/lib/lua/5.4/?/core.dll;" .. package.cpath
multi, thread = require("multi"):init{error=true,warning=true,print=true, priority=true}
proc = multi:newProcessor("Thread Test",true)
File diff suppressed because it is too large Load Diff