From a708fb3f83a8eb0ea1b69fe68dc98c9834679e7e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 19 Jan 2022 23:13:58 -0500 Subject: [PATCH 01/40] testing stuff --- multi/integration/networkManager/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multi/integration/networkManager/init.lua b/multi/integration/networkManager/init.lua index 98f9831..f6778d8 100644 --- a/multi/integration/networkManager/init.lua +++ b/multi/integration/networkManager/init.lua @@ -23,7 +23,7 @@ SOFTWARE. ]] local multi, thread = require("multi"):init() local net = require("net") -local bin = require("bin") +--local bin = require("bin") local char = string.char local byte = string.byte bin.setBitsInterface(infinabits) From 7b70838567b45ad4628fb8a8e6a08efb4ca85272 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 23 Apr 2022 21:07:56 -0400 Subject: [PATCH 02/40] Planning stuff --- test.lua | 106 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 104 deletions(-) diff --git a/test.lua b/test.lua index af0c2cb..1616eb4 100644 --- a/test.lua +++ b/test.lua @@ -2,110 +2,8 @@ package.path = "./?/init.lua;"..package.path multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.threading"):init() --- Using a system thread, but both system and local threads support this! --- Don't worry if you don't have lanes or love2d. PesudoThreading will kick in to emulate the threading features if you do not have access to system threading. -func = THREAD:newFunction(function(count) - print("Starting Status test: ",count) - local a = 0 - while true do - a = a + 1 - THREAD.sleep(.1) - -- Push the status from the currently running threaded function to the main thread - THREAD.pushStatus(a,count) - if a == count then break end - end - return "Done" -end) - -thread:newThread("test",function() - local ret = func(10) - ret.OnStatus(function(part,whole) - print("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") - end) - print("TEST",func(5).wait()) - -- The results from the OnReturn connection is passed by thread.hold - print("Status:",thread.hold(ret.OnReturn)) - print("Function Done!") -end).OnError(function(...) - print("Error:",...) -end) - -local ret = func(10) -local ret2 = func(15) -local ret3 = func(20) -local s1,s2,s3 = 0,0,0 -ret.OnError(function(...) - print("Error:",...) -end) -ret2.OnError(function(...) - print("Error:",...) -end) -ret3.OnError(function(...) - print("Error:",...) -end) -ret.OnStatus(function(part,whole) - s1 = math.ceil((part/whole)*1000)/10 - print(s1) -end) -ret2.OnStatus(function(part,whole) - s2 = math.ceil((part/whole)*1000)/10 - print(s2) -end) -ret3.OnStatus(function(part,whole) - s3 = math.ceil((part/whole)*1000)/10 - print(s3) -end) - -loop = multi:newTLoop() - -function loop:testing() - print("testing haha") +function multi:newSystemThreadedConnection() + -- end -loop:Set(1) -t = loop:OnLoop(function() - print("Looping...") -end):testing() - -local proc = multi:newProcessor("Test") -local proc2 = multi:newProcessor("Test2") -local proc3 = proc2:newProcessor("Test3") -proc.Start() -proc2.Start() -proc3.Start() -proc:newThread("TestThread_1",function() - while true do - thread.sleep(1) - end -end) -proc:newThread("TestThread_2",function() - while true do - thread.sleep(1) - end -end) -proc2:newThread("TestThread_3",function() - while true do - thread.sleep(1) - end -end) - -thread:newThread(function() - thread.sleep(1) - local tasks = multi:getStats() - - for i,v in pairs(tasks) do - print("Process: " ..i.. "\n\tTasks:") - for ii,vv in pairs(v.tasks) do - print("\t\t"..vv:getName()) - end - print("\tThreads:") - for ii,vv in pairs(v.threads) do - print("\t\t"..vv:getName()) - end - end - - thread.sleep(10) -- Wait 10 seconds then kill the process! - os.exit() -end) - multi:mainloop() \ No newline at end of file From bac91574a21faac4b572c70b35922ae43dd573e1 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 6 Jun 2022 22:42:30 -0400 Subject: [PATCH 03/40] Changed readme, added rockspec for 15.2.1 and 15.3.0 --- README.md | 5 +++- multi.zip | Bin 0 -> 52689 bytes rockspecs/multi-15.2-1.rockspec | 39 ++++++++++++++++++++++++++++++++ rockspecs/multi-15.3-0.rockspec | 39 ++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 multi.zip create mode 100644 rockspecs/multi-15.2-1.rockspec create mode 100644 rockspecs/multi-15.3-0.rockspec diff --git a/README.md b/README.md index b1abc29..c1599be 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,10 @@ Link to optional dependencies: To install copy the multi folder into your environment and you are good to go
If you want to use the system threads, then you'll need to install lanes or love2d game engine! -**or** use luarocks `luarocks install multi` + +``` +luarocks install multi +``` Discord ------- diff --git a/multi.zip b/multi.zip new file mode 100644 index 0000000000000000000000000000000000000000..45aa8564f52240ca64b3ee9aa7b4d42fc09cf5b2 GIT binary patch literal 52689 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 JeJwwq{y&vlYVQC5 literal 0 HcmV?d00001 diff --git a/rockspecs/multi-15.2-1.rockspec b/rockspecs/multi-15.2-1.rockspec new file mode 100644 index 0000000..12c0c0c --- /dev/null +++ b/rockspecs/multi-15.2-1.rockspec @@ -0,0 +1,39 @@ +package = "multi" +version = "15.2-1" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v15.2.1", +} +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"] = "multi/init.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua", + ["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua", + ["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua", + ["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua", + ["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua", + --["multi.integration.lovrManager"] = "multi/integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "multi/integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "multi/integration/lovrManager/threads.lua", + ["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua", + ["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua", + ["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.threading"] = "multi/integration/threading.lua", + --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + } +} \ No newline at end of file diff --git a/rockspecs/multi-15.3-0.rockspec b/rockspecs/multi-15.3-0.rockspec new file mode 100644 index 0000000..d96f6f4 --- /dev/null +++ b/rockspecs/multi-15.3-0.rockspec @@ -0,0 +1,39 @@ +package = "multi" +version = "15.3-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v15.3.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"] = "multi/init.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua", + ["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua", + ["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua", + ["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua", + ["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua", + --["multi.integration.lovrManager"] = "multi/integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "multi/integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "multi/integration/lovrManager/threads.lua", + ["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua", + ["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua", + ["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.threading"] = "multi/integration/threading.lua", + --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + } +} \ No newline at end of file From 7dfbf16bc17136c11a5b4093b2190d18ca1c3169 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 6 Jun 2022 22:42:53 -0400 Subject: [PATCH 04/40] Removed multi.zip --- multi.zip | Bin 52689 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 multi.zip diff --git a/multi.zip b/multi.zip deleted file mode 100644 index 45aa8564f52240ca64b3ee9aa7b4d42fc09cf5b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52689 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 JeJwwq{y&vlYVQC5 From 0e2119a87d292fbbd991ce56160674333a5eec86 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 6 Jun 2022 22:43:36 -0400 Subject: [PATCH 05/40] Added .zip to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c9636a2..8538ab3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ *lua5.4 *luajit *.code-workspace -*.dat \ No newline at end of file +*.dat +*.zip \ No newline at end of file From e2bb9644235d9a268157213b86edce57787012b3 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 11 Jun 2022 23:24:28 -0400 Subject: [PATCH 06/40] Removed print statement --- multi/init.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index f74e3bc..9625e4c 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1,4 +1,3 @@ -print("Dev") --[[ MIT License @@ -35,7 +34,7 @@ if not _G["$multi"] then _G["$multi"] = {multi=multi,thread=thread} end -multi.Version = "15.2.0" +multi.Version = "15.3.0" multi.Name = "root" multi.NIL = {Type="NIL"} local NIL = multi.NIL @@ -1472,7 +1471,6 @@ co_status = { switch[task](ref,thd) cmds[r1](ref,r2,r3,r4,r5) if ret ~= CMD and _ ~= nil then -- The rework makes this necessary - print("Hello") co_status["dead"](thd,ref,task,i,th) end r1=nil r2=nil r3=nil r4=nil r5=nil From 9050e65d93fcee4691a5ae8fb6e9d1ed63d490da Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 18 Sep 2022 09:33:13 -0400 Subject: [PATCH 07/40] Back and making progress --- README.md | 7 +- changes.md | 31 +++++++ multi/init.lua | 91 ++++++++++--------- multi/integration/lanesManager/extensions.lua | 2 + test.lua | 26 +++++- 5 files changed, 111 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index b1abc29..131db6a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Multi Version: 15.3.0 +# Multi Version: 15.3.0 Connecting the dots **Key Changes** - @@ -16,8 +16,9 @@ Progress is being made in [v15.3.0](https://github.com/rayaman/multi/tree/v15.3. INSTALLING ---------- Link to optional dependencies: -[lanes](https://github.com/LuaLanes/lanes) -[love2d](https://love2d.org/) +- [lanes](https://github.com/LuaLanes/lanes) + +- [love2d](https://love2d.org/) To install copy the multi folder into your environment and you are good to go
If you want to use the system threads, then you'll need to install lanes or love2d game engine! diff --git a/changes.md b/changes.md index 77368e5..7457dfe 100644 --- a/changes.md +++ b/changes.md @@ -3,6 +3,37 @@ Table of contents --- [Update 15.2.0 - Upgrade Complete](#update-1520---upgrade-complete)
[Update 15.1.0 - Hold the thread!](#update-1510---hold-the-thread)
[Update 15.0.0 - The art of faking it](#update-1500---the-art-of-faking-it)
[Update 14.2.0 - Bloatware Removed](#update-1420---bloatware-removed)
[Update 14.1.0 - A whole new world of possibilities](#update-1410---a-whole-new-world-of-possibilities)
[Update 14.0.0 - Consistency, Additions and Stability](#update-1400---consistency-additions-and-stability)
[Update 13.1.0 - Bug fixes and features added](#update-1310---bug-fixes-and-features-added)
[Update 13.0.0 - Added some documentation, and some new features too check it out!](#update-1300---added-some-documentation-and-some-new-features-too-check-it-out)
[Update 12.2.2 - Time for some more bug fixes!](#update-1222---time-for-some-more-bug-fixes)
[Update 12.2.1 - Time for some bug fixes!](#update-1221---time-for-some-bug-fixes)
[Update 12.2.0 - The chains of binding](#update-1220---the-chains-of-binding)
[Update 12.1.0 - Threads just can't hold on anymore](#update-1210---threads-just-cant-hold-on-anymore)
[Update: 12.0.0 - Big update (Lots of additions some changes)](#update-1200---big-update-lots-of-additions-some-changes)
[Update: 1.11.1 - Small Clarification on Love](#update-1111---small-clarification-on-love)
[Update: 1.11.0](#update-1110)
[Update: 1.10.0](#update-1100)
[Update: 1.9.2](#update-192)
[Update: 1.9.1 - Threads can now argue](#update-191---threads-can-now-argue)
[Update: 1.9.0](#update-190)
[Update: 1.8.7](#update-187)
[Update: 1.8.6](#update-186)
[Update: 1.8.5](#update-185)
[Update: 1.8.4](#update-184)
[Update: 1.8.3 - Mainloop recieves some needed overhauling](#update-183---mainloop-recieves-some-needed-overhauling)
[Update: 1.8.2](#update-182)
[Update: 1.8.1](#update-181)
[Update: 1.7.6](#update-176)
[Update: 1.7.5](#update-175)
[Update: 1.7.4](#update-174)
[Update: 1.7.3](#update-173)
[Update: 1.7.2](#update-172)
[Update: 1.7.1 - Bug Fixes Only](#update-171---bug-fixes-only)
[Update: 1.7.0 - Threading the systems](#update-170---threading-the-systems)
[Update: 1.6.0](#update-160)
[Update: 1.5.0](#update-150)
[Update: 1.4.1 (4/10/2017) - First Public release of the library](#update-141-4102017---first-public-release-of-the-library)
[Update: 1.4.0 (3/20/2017)](#update-140-3202017)
[Update: 1.3.0 (1/29/2017)](#update-130-1292017)
[Update: 1.2.0 (12.31.2016)](#update-120-12312016)
[Update: 1.1.0](#update-110)
[Update: 1.0.0](#update-100)
[Update: 0.6.3](#update-063)
[Update: 0.6.2](#update-062)
[Update: 0.6.1-6](#update-061-6)
[Update: 0.5.1-6](#update-051-6)
[Update: 0.4.1](#update-041)
[Update: 0.3.0 - The update that started it all](#update-030---the-update-that-started-it-all)
[Update: EventManager 2.0.0](#update-eventmanager-200)
[Update: EventManager 1.2.0](#update-eventmanager-120)
[Update: EventManager 1.1.0](#update-eventmanager-110)
[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 15.3.0 - A world of connection + +Full Update Showcase + +Added +--- +- `multi:newSystemThreadedConnection()` + + Allows one to trigger connection events across threads. +- `multi:newConnection():SetHelper(func)` + + Sets the helper function that the connection object uses when creating connection links. + +Changed +--- +- `Connection:[connect, hasConnections, getConnection]` changed to be `Connection:[Connect, HasConnections, getConnections]`. This was done in an attempt to follow a consistent naming scheme. The old methods still will work to prevent old code breaking. + +Removed +--- +- Connection objects methods removed: + - holdUT(), HoldUT() -- With the way `thread.hold(conn)` interacts with connections this method was no longer needed. To emulate this use `multi.hold(conn)`. `multi.hold()` is able to emulate what `thread.hold()` outside of a thread, albeit with some drawbacks. + +Fixed +--- +- + +ToDo +--- + +- Work on network parallelism (I am really excited to start working on this. Not because it will have much use, but because it seems like a cool addition/project to work on. I just need time to actually do work on stuff) + # Update 15.2.0 - Upgrade Complete Full Update Showcase diff --git a/multi/init.lua b/multi/init.lua index cd91633..080c5d9 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -58,15 +58,15 @@ multi.Priority_Very_Low = 16384 multi.Priority_Idle = 65536 multi.PriorityResolve = { - [1]="Core", - [4]="Very High", - [16]="High", - [64]="Above Normal", - [256]="Normal", - [1024]="Below Normal", - [4096]="Low", - [16384]="Very Low", - [65536]="Idle", + [1] = "Core", + [4] = "Very High", + [16] = "High", + [64] = "Above Normal", + [256] = "Normal", + [1024] = "Below Normal", + [4096] = "Low", + [16384] = "Very Low", + [65536] = "Idle", } local PList = {multi.Priority_Core,multi.Priority_Very_High,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Very_Low,multi.Priority_Idle} @@ -116,19 +116,20 @@ function multi:newConnection(protect,func,kill) local lock = false c.callback = func c.Parent=self + 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,...)) + local ref = self:Connect(select(2,...)) ref.root_link = select(1,...) return ref end end - return self:connect(...) + return self:Connect(...) else - return self:connect(...) + return self:Connect(...) end end, __add = function(c1,c2) @@ -154,32 +155,18 @@ function multi:newConnection(protect,func,kill) end) return cn end}) + c.Type='connector' c.func={} c.ID=0 local protect=protect or false local connections={} c.FC=0 + function c:hasConnections() return #call_funcs~=0 end - function c:holdUT(n) - local n=n or 0 - self.waiting=true - local count=0 - local id=self:connect(function() - count = count + 1 - if n<=count then - self.waiting=false - end - end) - repeat - self.Parent:uManager() - until self.waiting==false - id:Destroy() - return self - end - c.HoldUT=c.holdUT + function c:getConnection(name,ignore) if ignore then return connections[name] or CRef @@ -187,14 +174,17 @@ function multi:newConnection(protect,func,kill) return connections[name] or self end end + function c:Lock() lock = true return self end + function c:Unlock() lock = false return self end + if protect then function c:Fire(...) if lock then return end @@ -216,48 +206,56 @@ function multi:newConnection(protect,func,kill) end end end + local fast = {} function c:getConnections() return call_funcs end + function c:fastMode() function self:Fire(...) for i=1,#fast do fast[i](...) end end - function self:connect(func) + function self:Connect(func) table.insert(fast,func) end end + function c:Bind(t) local temp = call_funcs call_funcs=t return temp end + function c:Remove() local temp = call_funcs call_funcs={} return temp end + local function conn_helper(self,func,name,num) self.ID=self.ID+1 + if num then table.insert(call_funcs,num,func) else table.insert(call_funcs,1,func) end + local temp = { func=func, Type="connector_link", Parent=self, connect = function(s,...) - return self:connect(...) + return self:Connect(...) end } + setmetatable(temp,{ __call=function(s,...) - return self:connect(...) + return self:Connect(...) end, __index = function(t,k) if rawget(t,"root_link") then @@ -272,6 +270,7 @@ function multi:newConnection(protect,func,kill) rawset(t,k,v) end, }) + function temp:Fire(...) if lock then return end if protect then @@ -283,6 +282,7 @@ function multi:newConnection(protect,func,kill) return call_funcs(...) end end + function temp:Destroy() for i=#call_funcs,1,-1 do if call_funcs[i]~=nil then @@ -294,6 +294,7 @@ function multi:newConnection(protect,func,kill) end end end + if name then connections[name]=temp end @@ -302,7 +303,8 @@ function multi:newConnection(protect,func,kill) end return temp end - function c:connect(...)--func,name,num + + function c:Connect(...)--func,name,num local tab = {...} local funcs={} for i=1,#tab do @@ -320,8 +322,15 @@ function multi:newConnection(protect,func,kill) return conn_helper(self,tab[1],tab[2],tab[3]) end end + + function c:SetHelper(func) + conn_helper = func + end + c.Connect=c.connect c.GetConnection=c.getConnection + c.HasConnections = c.hasConnections + c.GetConnection = c.getConnection if not(ignoreconn) then multi:create(c) end @@ -2043,14 +2052,14 @@ function multi.print(...) end end -multi.GetType=multi.getType -multi.IsPaused=multi.isPaused -multi.IsActive=multi.isActive -multi.Reallocate=multi.Reallocate -multi.ConnectFinal=multi.connectFinal -multi.ResetTime=multi.SetTime -multi.IsDone=multi.isDone -multi.SetName = multi.setName +multi.GetType = multi.getType +multi.IsPaused = multi.isPaused +multi.IsActive = multi.isActive +multi.Reallocate = multi.Reallocate +multi.ConnectFinal = multi.connectFinal +multi.ResetTime = multi.SetTime +multi.IsDone = multi.isDone +multi.SetName = multi.setName -- Special Events local _os = os.exit diff --git a/multi/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua index 198e551..bc37455 100644 --- a/multi/integration/lanesManager/extensions.lua +++ b/multi/integration/lanesManager/extensions.lua @@ -41,6 +41,7 @@ function multi:newSystemThreadedQueue(name) GLOBAL[name or "_"] = c return c end + function multi:newSystemThreadedTable(name) local c = {} c.link = lanes.linda() @@ -58,6 +59,7 @@ function multi:newSystemThreadedTable(name) GLOBAL[name or "_"] = c return c end + function multi:newSystemThreadedJobQueue(n) local c = {} c.cores = n or THREAD.getCores()*2 diff --git a/test.lua b/test.lua index 1616eb4..dc3361e 100644 --- a/test.lua +++ b/test.lua @@ -2,8 +2,30 @@ package.path = "./?/init.lua;"..package.path multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.threading"):init() -function multi:newSystemThreadedConnection() - -- +function multi:newSystemThreadedConnection(name,...) + local master_conn = multi:newConnection(...) + local c = {} + local name = name or multi.randomString(16) + local connections = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. + setmetatable(c,master_conn) -- A different approach will be taken for the non main connection objects + c.subscribe = multi:newSystemThreadedQueue("Subscribe_"..name) + multi:newThread("STC_"..name,function() + while true do + thread.yield() + local item = c.subscribe:pop() + if item ~= nil then + connections[#connections+1] = item + thread.skip(multi.Priority_Normal) -- Usually a bunch of threads subscribe close to the same time. Process those by ensuring that they come alive around the same time + else -- I'm using these "Constant" values since they may change with other releases and this should allow these functions to adjust with them. + thread.skip(multi.Priority_Idle) + end + end + end) + function c:init() + return self + end + GLOBAL[name or "_"] = c + return c end multi:mainloop() \ No newline at end of file From 8f3eb6d9a35a2266c3d3917394a613e6654c45d9 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 18 Sep 2022 09:36:47 -0400 Subject: [PATCH 08/40] Fixed changelog order --- changes.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/changes.md b/changes.md index 3509c65..8b235e7 100644 --- a/changes.md +++ b/changes.md @@ -4,9 +4,6 @@ Table of contents [Update 15.2.1 - Bug fix](#update-1521---bug-fix)
[Update 15.2.0 - Upgrade Complete](#update-1520---upgrade-complete)
[Update 15.1.0 - Hold the thread!](#update-1510---hold-the-thread)
[Update 15.0.0 - The art of faking it](#update-1500---the-art-of-faking-it)
[Update 14.2.0 - Bloatware Removed](#update-1420---bloatware-removed)
[Update 14.1.0 - A whole new world of possibilities](#update-1410---a-whole-new-world-of-possibilities)
[Update 14.0.0 - Consistency, Additions and Stability](#update-1400---consistency-additions-and-stability)
[Update 13.1.0 - Bug fixes and features added](#update-1310---bug-fixes-and-features-added)
[Update 13.0.0 - Added some documentation, and some new features too check it out!](#update-1300---added-some-documentation-and-some-new-features-too-check-it-out)
[Update 12.2.2 - Time for some more bug fixes!](#update-1222---time-for-some-more-bug-fixes)
[Update 12.2.1 - Time for some bug fixes!](#update-1221---time-for-some-bug-fixes)
[Update 12.2.0 - The chains of binding](#update-1220---the-chains-of-binding)
[Update 12.1.0 - Threads just can't hold on anymore](#update-1210---threads-just-cant-hold-on-anymore)
[Update: 12.0.0 - Big update (Lots of additions some changes)](#update-1200---big-update-lots-of-additions-some-changes)
[Update: 1.11.1 - Small Clarification on Love](#update-1111---small-clarification-on-love)
[Update: 1.11.0](#update-1110)
[Update: 1.10.0](#update-1100)
[Update: 1.9.2](#update-192)
[Update: 1.9.1 - Threads can now argue](#update-191---threads-can-now-argue)
[Update: 1.9.0](#update-190)
[Update: 1.8.7](#update-187)
[Update: 1.8.6](#update-186)
[Update: 1.8.5](#update-185)
[Update: 1.8.4](#update-184)
[Update: 1.8.3 - Mainloop recieves some needed overhauling](#update-183---mainloop-recieves-some-needed-overhauling)
[Update: 1.8.2](#update-182)
[Update: 1.8.1](#update-181)
[Update: 1.7.6](#update-176)
[Update: 1.7.5](#update-175)
[Update: 1.7.4](#update-174)
[Update: 1.7.3](#update-173)
[Update: 1.7.2](#update-172)
[Update: 1.7.1 - Bug Fixes Only](#update-171---bug-fixes-only)
[Update: 1.7.0 - Threading the systems](#update-170---threading-the-systems)
[Update: 1.6.0](#update-160)
[Update: 1.5.0](#update-150)
[Update: 1.4.1 (4/10/2017) - First Public release of the library](#update-141-4102017---first-public-release-of-the-library)
[Update: 1.4.0 (3/20/2017)](#update-140-3202017)
[Update: 1.3.0 (1/29/2017)](#update-130-1292017)
[Update: 1.2.0 (12.31.2016)](#update-120-12312016)
[Update: 1.1.0](#update-110)
[Update: 1.0.0](#update-100)
[Update: 0.6.3](#update-063)
[Update: 0.6.2](#update-062)
[Update: 0.6.1-6](#update-061-6)
[Update: 0.5.1-6](#update-051-6)
[Update: 0.4.1](#update-041)
[Update: 0.3.0 - The update that started it all](#update-030---the-update-that-started-it-all)
[Update: EventManager 2.0.0](#update-eventmanager-200)
[Update: EventManager 1.2.0](#update-eventmanager-120)
[Update: EventManager 1.1.0](#update-eventmanager-110)
[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 15.2.1 - Bug fix -Fixed issue #41 - # Update 15.3.0 - A world of connection Full Update Showcase @@ -38,6 +35,10 @@ ToDo - Work on network parallelism (I am really excited to start working on this. Not because it will have much use, but because it seems like a cool addition/project to work on. I just need time to actually do work on stuff) +# Update 15.2.1 - Bug fix +Fixed issue #41 +--- + # Update 15.2.0 - Upgrade Complete Full Update Showcase From 1546076c80298ee27964baf7bc1a836f6dd62e62 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 18 Sep 2022 12:22:34 -0400 Subject: [PATCH 09/40] Protection is handled by Fire Funcction --- multi/init.lua | 11 +++-------- multi/integration/lanesManager/extensions.lua | 8 ++++---- test.lua | 19 +++++++++++++++---- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index c850a53..2cd2953 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -273,14 +273,7 @@ function multi:newConnection(protect,func,kill) function temp:Fire(...) if lock then return end - if protect then - local t=pcall(call_funcs,...) - if t then - return t - end - else - return call_funcs(...) - end + return call_funcs(...) end function temp:Destroy() @@ -298,9 +291,11 @@ function multi:newConnection(protect,func,kill) if name then connections[name]=temp end + if self.callback then self.callback(temp) end + return temp end diff --git a/multi/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua index bc37455..15f5bb1 100644 --- a/multi/integration/lanesManager/extensions.lua +++ b/multi/integration/lanesManager/extensions.lua @@ -64,10 +64,10 @@ function multi:newSystemThreadedJobQueue(n) local c = {} c.cores = n or THREAD.getCores()*2 c.OnJobCompleted = multi:newConnection() - local funcs = multi:newSystemThreadedTable() - local queueJob = multi:newSystemThreadedQueue() - local queueReturn = multi:newSystemThreadedQueue() - local doAll = multi:newSystemThreadedQueue() + local funcs = multi:newSystemThreadedTable():init() + local queueJob = multi:newSystemThreadedQueue():init() + local queueReturn = multi:newSystemThreadedQueue():init() + local doAll = multi:newSystemThreadedQueue():init() local ID=1 local jid = 1 function c:isEmpty() diff --git a/test.lua b/test.lua index b5483b9..f92ec6d 100644 --- a/test.lua +++ b/test.lua @@ -4,24 +4,35 @@ multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.lanesManager"):init() function multi:newSystemThreadedConnection(name,...) - local master_conn = multi:newConnection(...) local c = {} + local proxy_conn = multi:newConnection(...) local name = name or multi.randomString(16) local connections = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. setmetatable(c,master_conn) -- A different approach will be taken for the non main connection objects - c.subscribe = multi:newSystemThreadedQueue("Subscribe_"..name) + c.subscribe = multi:newSystemThreadedQueue("Subscribe_"..name):init() -- Incoming subscriptions multi:newThread("STC_"..name,function() while true do - thread.yield() local item = c.subscribe:pop() + -- We need to check on broken connections + -- c:Ping() + -- if item ~= nil then connections[#connections+1] = item thread.skip(multi.Priority_Normal) -- Usually a bunch of threads subscribe close to the same time. Process those by ensuring that they come alive around the same time - else -- I'm using these "Constant" values since they may change with other releases and this should allow these functions to adjust with them. + else -- I'm using these "Constants" since they may change with other releases and this should allow these functions to adjust with them. thread.skip(multi.Priority_Idle) end end end) + function c:Ping() -- Threaded Function call, can use thread.* + -- + end + function c:Fire(...) + -- + end + function c:Connect(...) + -- + end function c:init() return self end From bb7fab0857ebe7a9a6bed60c1e2d9b9619c4e1a8 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 18 Sep 2022 13:56:00 -0400 Subject: [PATCH 10/40] working on systemthreadedconnections --- changes.md | 4 ++++ multi/init.lua | 8 +++++++- test.lua | 25 ++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/changes.md b/changes.md index 8b235e7..bd5bcae 100644 --- a/changes.md +++ b/changes.md @@ -17,6 +17,10 @@ Added Sets the helper function that the connection object uses when creating connection links. +- `multi.ForEach(table, callback_function)` + + Loops through the table and calls callback_function with each element of the array. + Changed --- - `Connection:[connect, hasConnections, getConnection]` changed to be `Connection:[Connect, HasConnections, getConnections]`. This was done in an attempt to follow a consistent naming scheme. The old methods still will work to prevent old code breaking. diff --git a/multi/init.lua b/multi/init.lua index 2cd2953..fe2f078 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -109,6 +109,12 @@ function multi:getStats() end --Helpers +function multi.ForEach(tab,func) + for i=1,#tab do + func(tab[i]) + end +end + local ignoreconn = true function multi:newConnection(protect,func,kill) local c={} @@ -198,6 +204,7 @@ function multi:newConnection(protect,func,kill) end else function c:Fire(...) + if lock then return end for i=#call_funcs,1,-1 do call_funcs[i](...) if kill then @@ -272,7 +279,6 @@ function multi:newConnection(protect,func,kill) }) function temp:Fire(...) - if lock then return end return call_funcs(...) end diff --git a/test.lua b/test.lua index f92ec6d..bba1341 100644 --- a/test.lua +++ b/test.lua @@ -3,24 +3,34 @@ package.cpath = "lua5.4/lib/lua/?/core.dll;"..package.cpath multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.lanesManager"):init() + + function multi:newSystemThreadedConnection(name,...) local c = {} + c.CONN = 0x00 + c.TRIG = 0x01 + c.PING = 0x02 + c.PONG = 0x03 local proxy_conn = multi:newConnection(...) local name = name or multi.randomString(16) local connections = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. + local funcs = {} setmetatable(c,master_conn) -- A different approach will be taken for the non main connection objects c.subscribe = multi:newSystemThreadedQueue("Subscribe_"..name):init() -- Incoming subscriptions multi:newThread("STC_"..name,function() while true do + thread.yield() local item = c.subscribe:pop() -- We need to check on broken connections -- c:Ping() -- if item ~= nil then connections[#connections+1] = item + multi.ForEach(funcs, function(link) -- Sync new connections + item:push{c.CONN, link} + end) thread.skip(multi.Priority_Normal) -- Usually a bunch of threads subscribe close to the same time. Process those by ensuring that they come alive around the same time - else -- I'm using these "Constants" since they may change with other releases and this should allow these functions to adjust with them. - thread.skip(multi.Priority_Idle) + -- I'm using these "Constants" since they may change with other releases and this should allow these functions to adjust with them. end end end) @@ -30,9 +40,18 @@ function multi:newSystemThreadedConnection(name,...) function c:Fire(...) -- end - function c:Connect(...) + function c:Sync(link) -- end + function c:Connect(func) + local conn_func = func + proxy_conn(function() + funcs[#funcs+1] = func -- Used for syncing new connections to this connection later on + multi.ForEach(c.connections, function(link) + link:push{CONN, func} + end) + end) + end function c:init() return self end From 772095431f1fc3e8709557a06c80b462f6cb2e08 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 20 Sep 2022 16:52:51 -0400 Subject: [PATCH 11/40] Some changes to extensions, working on STC --- changes.md | 4 +- multi/init.lua | 5 +- multi/integration/lanesManager/extensions.lua | 4 + multi/integration/lanesManager/init.lua | 1 + multi/integration/loveManager/extensions.lua | 6 +- multi/integration/lovrManager/extensions.lua | 6 +- test.lua | 74 +++++++++++++------ test2.lua | 33 +++++++++ 8 files changed, 103 insertions(+), 30 deletions(-) create mode 100644 test2.lua diff --git a/changes.md b/changes.md index bd5bcae..5c5621b 100644 --- a/changes.md +++ b/changes.md @@ -21,6 +21,8 @@ Added Loops through the table and calls callback_function with each element of the array. +- If a name is not supplied when creating threads; a name is randomly generated. Unless sending through an established channel/queue you might not be able to easily init the object. + Changed --- - `Connection:[connect, hasConnections, getConnection]` changed to be `Connection:[Connect, HasConnections, getConnections]`. This was done in an attempt to follow a consistent naming scheme. The old methods still will work to prevent old code breaking. @@ -32,7 +34,7 @@ Removed Fixed --- -- +- SystemThreaded Objects variables weren't consistent. ToDo --- diff --git a/multi/init.lua b/multi/init.lua index fe2f078..e6caf78 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1254,9 +1254,8 @@ local startme_len = 0 function thread:newThread(name,func,...) multi.OnLoad:Fire() -- This was done incase a threaded function was called before mainloop/uManager was called local func = func or name - - if type(name) == "function" then - name = "Thread#"..threadCount + if func == name then + name = name or multi.randomString(16) end local c={nil,nil,nil,nil,nil,nil,nil} local env = {self=c} diff --git a/multi/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua index 15f5bb1..54b121a 100644 --- a/multi/integration/lanesManager/extensions.lua +++ b/multi/integration/lanesManager/extensions.lua @@ -24,7 +24,9 @@ SOFTWARE. local multi, thread = require("multi"):init() local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD function multi:newSystemThreadedQueue(name) + local name = name or multi.randomString(16) local c = {} + c.Name = name c.linda = lanes.linda() function c:push(v) self.linda:send("Q", v) @@ -43,8 +45,10 @@ function multi:newSystemThreadedQueue(name) end function multi:newSystemThreadedTable(name) + local name = name or multi.randomString(16) local c = {} c.link = lanes.linda() + c.Name = name setmetatable(c,{ __index = function(t,k) return c.link:get(k) diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 4bada5a..b016699 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -62,6 +62,7 @@ function THREAD:newFunction(func,holdme) end function multi:newSystemThread(name, func, ...) + local name = name or multi.randomString(16) multi.InitSystemThreadErrorHandler() local rand = math.random(1, 10000000) local return_linda = lanes.linda() diff --git a/multi/integration/loveManager/extensions.lua b/multi/integration/loveManager/extensions.lua index cf6beb2..9d6fa7f 100644 --- a/multi/integration/loveManager/extensions.lua +++ b/multi/integration/loveManager/extensions.lua @@ -27,6 +27,7 @@ local multi, thread = require("multi").init() GLOBAL = multi.integration.GLOBAL THREAD = multi.integration.THREAD function multi:newSystemThreadedQueue(name) + local name = name or multi.randomString(16) local c = {} c.Name = name local fRef = {"func",nil} @@ -64,10 +65,11 @@ function multi:newSystemThreadedQueue(name) return c end function multi:newSystemThreadedTable(name) + local name = name or multi.randomString(16) local c = {} - c.name = name + c.Name = name function c:init() - return THREAD.createTable(self.name) + return THREAD.createTable(self.Name) end THREAD.package(name,c) return c diff --git a/multi/integration/lovrManager/extensions.lua b/multi/integration/lovrManager/extensions.lua index 7032b1d..b2082da 100644 --- a/multi/integration/lovrManager/extensions.lua +++ b/multi/integration/lovrManager/extensions.lua @@ -25,6 +25,7 @@ local multi, thread = require("multi").init() GLOBAL = multi.integration.GLOBAL THREAD = multi.integration.THREAD function multi:newSystemThreadedQueue(name) + local name = name or multi.randomString(16) local c = {} c.Name = name local fRef = {"func",nil} @@ -62,10 +63,11 @@ function multi:newSystemThreadedQueue(name) return c end function multi:newSystemThreadedTable(name) + local name = name or multi.randomString(16) local c = {} - c.name = name + c.Name = name function c:init() - return THREAD.createTable(self.name) + return THREAD.createTable(self.Name) end THREAD.package(name,c) return c diff --git a/test.lua b/test.lua index bba1341..87c2e00 100644 --- a/test.lua +++ b/test.lua @@ -3,59 +3,89 @@ package.cpath = "lua5.4/lib/lua/?/core.dll;"..package.cpath multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.lanesManager"):init() - - function multi:newSystemThreadedConnection(name,...) local c = {} c.CONN = 0x00 c.TRIG = 0x01 c.PING = 0x02 c.PONG = 0x03 - local proxy_conn = multi:newConnection(...) + c.proxy_conn = multi:newConnection(...) + local function remove(a, b) + local ai = {} + local r = {} + for k,v in pairs(a) do ai[v]=true end + for k,v in pairs(b) do + if ai[v]==nil then table.insert(r,a[k]) end + end + return r + end local name = name or multi.randomString(16) + c.Name = name local connections = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. local funcs = {} setmetatable(c,master_conn) -- A different approach will be taken for the non main connection objects - c.subscribe = multi:newSystemThreadedQueue("Subscribe_"..name):init() -- Incoming subscriptions - multi:newThread("STC_"..name,function() + c.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..name):init() -- Incoming subscriptions + multi:newThread("STC_SUB_MAN"..name,function() + local item while true do thread.yield() - local item = c.subscribe:pop() -- We need to check on broken connections - -- c:Ping() - -- - if item ~= nil then - connections[#connections+1] = item - multi.ForEach(funcs, function(link) -- Sync new connections - item:push{c.CONN, link} - end) - thread.skip(multi.Priority_Normal) -- Usually a bunch of threads subscribe close to the same time. Process those by ensuring that they come alive around the same time - -- I'm using these "Constants" since they may change with other releases and this should allow these functions to adjust with them. - end + c:Ping() -- Should return instantlly and process this in another thread + thread.hold(function() + item = c.subscribe:peek() + if item ~= nil and item[1] == c.CONN then + c.subscribe:pop() + connections[#connections+1] = item + multi.ForEach(funcs, function(link) -- Sync new connections + item:push{c.CONN, link} + end) + end + -- Nil return keeps this hold going until timeout + end,{cycles=multi.Priority_Normal}) + -- Usually a bunch of threads subscribe close to the same time. + -- Give those threads some time to ready up. end end) - function c:Ping() -- Threaded Function call, can use thread.* - -- - end + c.Ping = thread:newFunction(function(self) + c.Ping:Pause() -- Don't allow this function to be called more than once + local pings = {} + multi.ForEach(funcs, function(link) -- Sync new connections + link:push{self.PING} + end) + thread.hold(function() + item = self.subscribe:peek() + if item ~= nil and item[1] == self.PONG then + table.insert(pings,item[2]) + end + end,{sleep=3}) -- Give all threads time to respond to the ping + -- We waited long enough for a response, anything that did not respond gets removed + remove(funcs, pings) + c.Ping:Resume() + end,false) function c:Fire(...) - -- + master_conn:Fire(...) + multi.ForEach(funcs, function(link) -- Sync new connections + link:push{self.TRIG,{...}} + end) end function c:Sync(link) -- end function c:Connect(func) local conn_func = func + self.proxy_conn(func) proxy_conn(function() funcs[#funcs+1] = func -- Used for syncing new connections to this connection later on - multi.ForEach(c.connections, function(link) + multi.ForEach(self.connections, function(link) link:push{CONN, func} end) end) end function c:init() + self.proxy_conn return self end - GLOBAL[name or "_"] = c + GLOBAL[name] = c return c end diff --git a/test2.lua b/test2.lua new file mode 100644 index 0000000..5603708 --- /dev/null +++ b/test2.lua @@ -0,0 +1,33 @@ +function difference(a, b) + local ai = {} + local r = {} + local rr = {} + for k,v in pairs(a) do r[k] = v; ai[v]=true end + for k,v in pairs(b) do + if ai[v]==nil then table.insert(rr,r[k]) end + end + return rr +end +function remove(a, b) + local ai = {} + local r = {} + for k,v in pairs(a) do ai[v]=true end + for k,v in pairs(b) do + if ai[v]==nil then table.insert(r,a[k]) end + end + return r +end + +function printtab(tab,msg) + print(msg or "TABLE") + for i,v in pairs(tab) do + print(i, v) + end + print("") +end + +local tab1 = {1,2,3,4,5} +local tab2 = {3,4,5,6,7} +tab1 = remove(tab1,tab2) +printtab(tab1, "Table 1") +printtab(tab2, "Table 2") From 62e05788d2a3d3bba600b973b659b706d7affbdb Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 21 Sep 2022 00:06:48 -0400 Subject: [PATCH 12/40] Working on STC --- makeENV.lua | 10 ++-- multi/init.lua | 2 +- test.lua | 142 +++++++++++++++++++++++++++++++------------------ 3 files changed, 96 insertions(+), 58 deletions(-) diff --git a/makeENV.lua b/makeENV.lua index acb6fc0..d312349 100644 --- a/makeENV.lua +++ b/makeENV.lua @@ -1,9 +1,9 @@ commands = [[ -mkdir luajit && python -m hererocks -j 2.1.0-beta3 -r latest --patch --compat all ./luajit && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi -mkdir lua5.1 && python -m hererocks -l 5.1 -r latest --patch --compat all ./lua5.1 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi -mkdir lua5.2 && python -m hererocks -l 5.2 -r latest --patch --compat all ./lua5.2 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi -mkdir lua5.3 && python -m hererocks -l 5.3 -r latest --patch --compat all ./lua5.3 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi -mkdir lua5.4 && python -m hererocks -l 5.4 -r latest --patch --compat all ./lua5.4 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi +mkdir luajit && python -m hererocks -j 2.1.0-beta3 -r latest --patch --compat all ./luajit && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes +mkdir lua5.1 && python -m hererocks -l 5.1 -r latest --patch --compat all ./lua5.1 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes +mkdir lua5.2 && python -m hererocks -l 5.2 -r latest --patch --compat all ./lua5.2 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes +mkdir lua5.3 && python -m hererocks -l 5.3 -r latest --patch --compat all ./lua5.3 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes +mkdir lua5.4 && python -m hererocks -l 5.4 -r latest --patch --compat all ./lua5.4 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes ]] function string.split (inputstr, sep) local sep = sep or "\n" diff --git a/multi/init.lua b/multi/init.lua index e6caf78..63e3bbc 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -328,7 +328,7 @@ function multi:newConnection(protect,func,kill) conn_helper = func end - c.Connect=c.connect + c.connect=c.Connect c.GetConnection=c.getConnection c.HasConnections = c.hasConnections c.GetConnection = c.getConnection diff --git a/test.lua b/test.lua index 87c2e00..d7bcd66 100644 --- a/test.lua +++ b/test.lua @@ -1,15 +1,15 @@ -package.path = "./?/init.lua;?.lua;lua5.4/share/lua/?/init.lua;lua5.4/share/lua/?.lua;"..package.path -package.cpath = "lua5.4/lib/lua/?/core.dll;"..package.cpath +package.path = "./?/init.lua;?.lua;lua5.2/share/lua/5.2/?/init.lua;lua5.2/share/lua/5.2/?.lua;" +package.cpath = "lua5.2/lib/lua/5.2/?/core.dll;" multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -function multi:newSystemThreadedConnection(name,...) +function multi:newSystemThreadedConnection(name) + local name = name or multi.randomString(16) local c = {} c.CONN = 0x00 c.TRIG = 0x01 c.PING = 0x02 c.PONG = 0x03 - c.proxy_conn = multi:newConnection(...) local function remove(a, b) local ai = {} local r = {} @@ -19,37 +19,14 @@ function multi:newSystemThreadedConnection(name,...) end return r end - local name = name or multi.randomString(16) c.Name = name - local connections = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. - local funcs = {} - setmetatable(c,master_conn) -- A different approach will be taken for the non main connection objects - c.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..name):init() -- Incoming subscriptions - multi:newThread("STC_SUB_MAN"..name,function() - local item - while true do - thread.yield() - -- We need to check on broken connections - c:Ping() -- Should return instantlly and process this in another thread - thread.hold(function() - item = c.subscribe:peek() - if item ~= nil and item[1] == c.CONN then - c.subscribe:pop() - connections[#connections+1] = item - multi.ForEach(funcs, function(link) -- Sync new connections - item:push{c.CONN, link} - end) - end - -- Nil return keeps this hold going until timeout - end,{cycles=multi.Priority_Normal}) - -- Usually a bunch of threads subscribe close to the same time. - -- Give those threads some time to ready up. - end - end) - c.Ping = thread:newFunction(function(self) - c.Ping:Pause() -- Don't allow this function to be called more than once + c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. + local ping + ping = thread:newFunction(function(self) + ping:Pause() -- Don't allow this function to be called until the first instance is done local pings = {} - multi.ForEach(funcs, function(link) -- Sync new connections + local count = #self.links + multi.ForEach(self.links, function(link) -- Sync new connections link:push{self.PING} end) thread.hold(function() @@ -57,36 +34,97 @@ function multi:newSystemThreadedConnection(name,...) if item ~= nil and item[1] == self.PONG then table.insert(pings,item[2]) end + if #pings==count then + return true -- If we get all pings give control back + end end,{sleep=3}) -- Give all threads time to respond to the ping -- We waited long enough for a response, anything that did not respond gets removed - remove(funcs, pings) - c.Ping:Resume() + self.links = remove(self.links, pings) + ping:Resume() end,false) + + thread:newThread("STC_SUB_MAN"..name,function() + local item + while true do + thread.yield() + -- We need to check on broken connections + ping() -- Should return instantlly and process this in another thread + thread.hold(function() + item = c.subscribe:peek() + if item ~= nil and item[1] == c.CONN then + c.subscribe:pop() + multi.ForEach(c.links, function(link) -- Sync new connections + item[2]:push{c.CONN, link} + end) + c.links[#c.links+1] = item + end + -- Nil return keeps this hold going until timeout + end,{cycles=multi.Priority_Normal}) + -- Usually a bunch of threads subscribe close to the same time. + end + end) + function c:Fire(...) - master_conn:Fire(...) - multi.ForEach(funcs, function(link) -- Sync new connections - link:push{self.TRIG,{...}} - end) - end - function c:Sync(link) - -- - end - function c:Connect(func) - local conn_func = func - self.proxy_conn(func) - proxy_conn(function() - funcs[#funcs+1] = func -- Used for syncing new connections to this connection later on - multi.ForEach(self.connections, function(link) - link:push{CONN, func} - end) + self.proxy_conn:Fire(...) + local args = {...} + multi.ForEach(self.links, function(link) -- Sync new connections + print(link[2]) -- Bugs everywhere + for i,v in pairs(link[2]) do print(i,v) end + link[2]:push{self.TRIG, args} end) end + function c:init() - self.proxy_conn + self.links = {} + self.proxy_conn = multi:newConnection() + setmetatable(self, {__index = self.proxy_conn}) + self.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..self.Name):init() -- Incoming subscriptions + thread:newThread("STC_CONN_MAN"..name,function() + local item + local link_self_ref = multi:newSystemThreadedQueue() + self.subscribe:push{self.CONN,link_self_ref} + while true do + item = thread.hold(function() + return self.subscribe:peek() + end) + if item[1] == self.PING then + self.subscribe:push{self.PONG, link_self_ref} + elseif item[1] == self.CONN then + if item[2] ~= link_self_ref then + table.insert(self.links, item[2]) + end + elseif item[1] == self.TRIG then + self.proxy_conn:Fire(unpack(item[2])) + else + -- This shouldn't be the case + end + end + end) return self end + GLOBAL[name] = c + return c end +local conn = multi:newSystemThreadedConnection("conn"):init() +multi:newSystemThread("Test",function() + local multi, thread = require("multi"):init() + local conn = THREAD.waitFor("conn"):init() + conn(function() + print("Thread was triggered!") + end) + multi:mainloop() +end) +-- conn(function() +-- print("Mainloop conn got triggered!") +-- end) + +alarm = multi:newAlarm(1) +alarm:OnRing(function() + print("Ring") + conn:Fire() +end) + multi:mainloop() \ No newline at end of file From 7bbb217018d6f2fd2ef5d93116df4b0dee41191c Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 21 Sep 2022 23:16:38 -0400 Subject: [PATCH 13/40] Working with lanes, todo implement for love2d and test. 'Should work' the way it is with love2d --- changes.md | 74 +++++++- multi/integration/lanesManager/extensions.lua | 125 ++++++++++++- multi/integration/lanesManager/init.lua | 1 + test.lua | 170 ++++++------------ 4 files changed, 253 insertions(+), 117 deletions(-) diff --git a/changes.md b/changes.md index 5c5621b..d6cef4f 100644 --- a/changes.md +++ b/changes.md @@ -7,12 +7,84 @@ Table of contents # Update 15.3.0 - A world of connection Full Update Showcase +```lua +multi, thread = require("multi"):init{print=true} +GLOBAL, THREAD = require("multi.integration.lanesManager"):init() + +local conn = multi:newSystemThreadedConnection("conn"):init() + +multi:newSystemThread("Thread_Test_1",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + conn(function() + print(THREAD:getName().." was triggered!") + end) + multi:mainloop() +end) + +multi:newSystemThread("Thread_Test_2",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + conn(function(a,b,c) + print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(2):OnRing(function() + print("Fire 2!!!") + conn:Fire(4,5,6) + THREAD.kill() + end) + + multi:mainloop() +end) + +conn(function(a,b,c) + print("Mainloop conn got triggered!",a,b,c) +end) + +alarm = multi:newAlarm(1) +alarm:OnRing(function() + print("Fire 1!!!") + conn:Fire(1,2,3) +end) + +alarm = multi:newAlarm(3):OnRing(function() + multi:newSystemThread("Thread_Test_3",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + conn(function(a,b,c) + print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(2):OnRing(function() + print("Fire 3!!!") + conn:Fire(7,8,9) + end) + multi:mainloop() + end) +end) + +multi:newSystemThread("Thread_Test_4",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local conn2 = multi:newConnection() + multi:newAlarm(2):OnRing(function() + conn2:Fire() + end) + multi:newThread(function() + print("Conn Test!") + thread.hold(conn + conn2) + print("It held!") + end) + multi:mainloop() +end) + +multi:mainloop() +``` Added --- - `multi:newSystemThreadedConnection()` - Allows one to trigger connection events across threads. + Allows one to trigger connection events across threads. Works like how any connection would work. Supports all of the features, can even be `added` with non SystemThreadedConnections as demonstrated in the full showcase. - `multi:newConnection():SetHelper(func)` Sets the helper function that the connection object uses when creating connection links. diff --git a/multi/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua index 54b121a..e0add79 100644 --- a/multi/integration/lanesManager/extensions.lua +++ b/multi/integration/lanesManager/extensions.lua @@ -22,7 +22,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] local multi, thread = require("multi"):init() -local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD +if not (GLOBAL and THREAD) then + local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD +else + lanes = require("lanes") +end function multi:newSystemThreadedQueue(name) local name = name or multi.randomString(16) local c = {} @@ -174,4 +178,121 @@ function multi:newSystemThreadedJobQueue(n) end,i).priority = thread.Priority_Core end return c -end \ No newline at end of file +end + +function multi:newSystemThreadedConnection(name) + local name = name or multi.randomString(16) + local c = {} + c.CONN = 0x00 + c.TRIG = 0x01 + c.PING = 0x02 + c.PONG = 0x03 + local function remove(a, b) + local ai = {} + local r = {} + for k,v in pairs(a) do ai[v]=true end + for k,v in pairs(b) do + if ai[v]==nil then table.insert(r,a[k]) end + end + return r + end + c.CID = THREAD.getID() + c.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..self.Name):init() + c.Name = name + c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. + -- Locals will only live in the thread that creates the original object + local ping + local pong = function(link, links) + local res = thread.hold(function() + return link:peek()[1] == c.PONG + end,{sleep=3}) + + if not res then + self.links = remove(self.links, pings) + else + link:pop() + end + end + + ping = thread:newFunction(function(self) + ping:Pause() + multi.ForEach(self.links, function(link) -- Sync new connections + link:push{self.PING} + multi:newThread("pong Thread", pong, link, links) + end) + + thread.sleep(3) + + ping:Resume() + end,false) + + thread:newThread("STC_SUB_MAN"..name,function() + local item + while true do + thread.yield() + -- We need to check on broken connections + ping(c) -- Should return instantlly and process this in another thread + item = thread.hold(function() -- This will keep things held up until there is something to process + return c.subscribe:pop() + end) + if item[1] == c.CONN then + multi.ForEach(c.links, function(link) -- Sync new connections + item[2]:push{c.CONN, link} + end) + c.links[#c.links+1] = item[2] + elseif item[1] == c.TRIG then + c:Fire(unpack(item[2])) + c.proxy_conn:Fire(unpack(item[2])) + end + end + end) + --- ^^^ This will only exist in the init thread + + function c:Fire(...) + local args = {...} + if self.CID == THREAD.getID() then -- Host Call + for _, link in pairs(self.links) do + link:push {self.TRIG, args} + end + else + self.subscribe:push {self.TRIG, args} + end + end + + function c:init() + local multi, thread = require("multi"):init() + self.links = {} + self.proxy_conn = multi:newConnection() + local mt = getmetatable(self.proxy_conn) + setmetatable(self, {__index = self.proxy_conn, __call = function(t,func) self.proxy_conn(func) end, __add = mt.__add}) + thread:newThread("STC_CONN_MAN"..name,function() + local item + local link_self_ref = multi:newSystemThreadedQueue() + self.subscribe:push{self.CONN, link_self_ref} + while true do + item = thread.hold(function() + return link_self_ref:peek() + end) + if item[1] == self.PING then + link_self_ref:push{self.PONG} + link_self_ref:pop() + elseif item[1] == self.CONN then + if item[2].Name ~= link_self_ref.Name then + table.insert(self.links, item[2]) + end + link_self_ref:pop() + elseif item[1] == self.TRIG then + self.proxy_conn:Fire(unpack(item[2])) + link_self_ref:pop() + else + -- This shouldn't be the case + end + end + end) + return self + end + + GLOBAL[name] = c + + return c +end \ No newline at end of file diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index b016699..3653b55 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -88,6 +88,7 @@ function multi:newSystemThread(name, func, ...) }, priority=c.priority },function(...) + require("multi.integration.lanesManager.extensions") local has_error = true return_linda:set("returns",{func(...)}) has_error = false diff --git a/test.lua b/test.lua index d7bcd66..5fdd687 100644 --- a/test.lua +++ b/test.lua @@ -3,128 +3,70 @@ package.cpath = "lua5.2/lib/lua/5.2/?/core.dll;" multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -function multi:newSystemThreadedConnection(name) - local name = name or multi.randomString(16) - local c = {} - c.CONN = 0x00 - c.TRIG = 0x01 - c.PING = 0x02 - c.PONG = 0x03 - local function remove(a, b) - local ai = {} - local r = {} - for k,v in pairs(a) do ai[v]=true end - for k,v in pairs(b) do - if ai[v]==nil then table.insert(r,a[k]) end - end - return r - end - c.Name = name - c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. - local ping - ping = thread:newFunction(function(self) - ping:Pause() -- Don't allow this function to be called until the first instance is done - local pings = {} - local count = #self.links - multi.ForEach(self.links, function(link) -- Sync new connections - link:push{self.PING} - end) - thread.hold(function() - item = self.subscribe:peek() - if item ~= nil and item[1] == self.PONG then - table.insert(pings,item[2]) - end - if #pings==count then - return true -- If we get all pings give control back - end - end,{sleep=3}) -- Give all threads time to respond to the ping - -- We waited long enough for a response, anything that did not respond gets removed - self.links = remove(self.links, pings) - ping:Resume() - end,false) - - thread:newThread("STC_SUB_MAN"..name,function() - local item - while true do - thread.yield() - -- We need to check on broken connections - ping() -- Should return instantlly and process this in another thread - thread.hold(function() - item = c.subscribe:peek() - if item ~= nil and item[1] == c.CONN then - c.subscribe:pop() - multi.ForEach(c.links, function(link) -- Sync new connections - item[2]:push{c.CONN, link} - end) - c.links[#c.links+1] = item - end - -- Nil return keeps this hold going until timeout - end,{cycles=multi.Priority_Normal}) - -- Usually a bunch of threads subscribe close to the same time. - end - end) - - function c:Fire(...) - self.proxy_conn:Fire(...) - local args = {...} - multi.ForEach(self.links, function(link) -- Sync new connections - print(link[2]) -- Bugs everywhere - for i,v in pairs(link[2]) do print(i,v) end - link[2]:push{self.TRIG, args} - end) - end - - function c:init() - self.links = {} - self.proxy_conn = multi:newConnection() - setmetatable(self, {__index = self.proxy_conn}) - self.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..self.Name):init() -- Incoming subscriptions - thread:newThread("STC_CONN_MAN"..name,function() - local item - local link_self_ref = multi:newSystemThreadedQueue() - self.subscribe:push{self.CONN,link_self_ref} - while true do - item = thread.hold(function() - return self.subscribe:peek() - end) - if item[1] == self.PING then - self.subscribe:push{self.PONG, link_self_ref} - elseif item[1] == self.CONN then - if item[2] ~= link_self_ref then - table.insert(self.links, item[2]) - end - elseif item[1] == self.TRIG then - self.proxy_conn:Fire(unpack(item[2])) - else - -- This shouldn't be the case - end - end - end) - return self - end - - GLOBAL[name] = c - - return c -end - local conn = multi:newSystemThreadedConnection("conn"):init() -multi:newSystemThread("Test",function() + +multi:newSystemThread("Thread_Test_1",function() local multi, thread = require("multi"):init() - local conn = THREAD.waitFor("conn"):init() + local conn = GLOBAL["conn"]:init() conn(function() - print("Thread was triggered!") + print(THREAD:getName().." was triggered!") end) multi:mainloop() end) --- conn(function() --- print("Mainloop conn got triggered!") --- end) + +multi:newSystemThread("Thread_Test_2",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + conn(function(a,b,c) + print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(2):OnRing(function() + print("Fire 2!!!") + conn:Fire(4,5,6) + THREAD.kill() + end) + + multi:mainloop() +end) + +conn(function(a,b,c) + print("Mainloop conn got triggered!",a,b,c) +end) alarm = multi:newAlarm(1) -alarm:OnRing(function() - print("Ring") - conn:Fire() +alarm:OnRing(function() + print("Fire 1!!!") + conn:Fire(1,2,3) +end) + +alarm = multi:newAlarm(3):OnRing(function() + multi:newSystemThread("Thread_Test_3",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + conn(function(a,b,c) + print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(2):OnRing(function() + print("Fire 3!!!") + conn:Fire(7,8,9) + end) + multi:mainloop() + end) +end) + +multi:newSystemThread("Thread_Test_4",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local conn2 = multi:newConnection() + multi:newAlarm(2):OnRing(function() + conn2:Fire() + end) + multi:newThread(function() + print("Conn Test!") + thread.hold(conn + conn2) + print("It held!") + end) + multi:mainloop() end) multi:mainloop() \ No newline at end of file From a14cbb45d3971a42ea734c16bed82b67cf263434 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 22 Sep 2022 10:29:11 -0400 Subject: [PATCH 14/40] working on STC --- multi/init.lua | 5 + multi/integration/lanesManager/extensions.lua | 146 +++++++- multi/integration/lanesManager/init.lua | 2 + multi/integration/lanesManager/threads.lua | 2 +- multi/integration/loveManager/extensions.lua | 325 ++++++++++++++++++ test.lua | 84 ++++- 6 files changed, 543 insertions(+), 21 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 9625e4c..976170a 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -109,6 +109,11 @@ function multi:getStats() end --Helpers + +function multi.ForEach(tab,func) + for i=1,#tab do func(tab[i]) end +end + local ignoreconn = true function multi:newConnection(protect,func,kill) local c={} diff --git a/multi/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua index 198e551..ad6c5cf 100644 --- a/multi/integration/lanesManager/extensions.lua +++ b/multi/integration/lanesManager/extensions.lua @@ -22,9 +22,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] local multi, thread = require("multi"):init() -local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD +if not (GLOBAL and THREAD) then + local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD +else + lanes = require("lanes") +end function multi:newSystemThreadedQueue(name) + local name = name or multi.randomString(16) local c = {} + c.Name = name c.linda = lanes.linda() function c:push(v) self.linda:send("Q", v) @@ -41,9 +47,12 @@ function multi:newSystemThreadedQueue(name) GLOBAL[name or "_"] = c return c end + function multi:newSystemThreadedTable(name) + local name = name or multi.randomString(16) local c = {} c.link = lanes.linda() + c.Name = name setmetatable(c,{ __index = function(t,k) return c.link:get(k) @@ -58,14 +67,15 @@ function multi:newSystemThreadedTable(name) GLOBAL[name or "_"] = c return c end + function multi:newSystemThreadedJobQueue(n) local c = {} c.cores = n or THREAD.getCores()*2 c.OnJobCompleted = multi:newConnection() - local funcs = multi:newSystemThreadedTable() - local queueJob = multi:newSystemThreadedQueue() - local queueReturn = multi:newSystemThreadedQueue() - local doAll = multi:newSystemThreadedQueue() + local funcs = multi:newSystemThreadedTable():init() + local queueJob = multi:newSystemThreadedQueue():init() + local queueReturn = multi:newSystemThreadedQueue():init() + local doAll = multi:newSystemThreadedQueue():init() local ID=1 local jid = 1 function c:isEmpty() @@ -168,4 +178,128 @@ function multi:newSystemThreadedJobQueue(n) end,i).priority = thread.Priority_Core end return c -end \ No newline at end of file +end + +function multi:newSystemThreadedConnection(name) + local name = name or multi.randomString(16) + local c = {} + c.CONN = 0x00 + c.TRIG = 0x01 + c.PING = 0x02 + c.PONG = 0x03 + local function remove(a, b) + local ai = {} + local r = {} + for k,v in pairs(a) do ai[v]=true end + for k,v in pairs(b) do + if ai[v]==nil then table.insert(r,a[k]) end + end + return r + end + c.CID = THREAD.getID() + c.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..self.Name):init() + c.Name = name + c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. + -- Locals will only live in the thread that creates the original object + local ping + local pong = function(link, links) + local res = thread.hold(function() + return link:peek()[1] == c.PONG + end,{sleep=3}) + + if not res then + for i=1,#links do + if links[i] == link then + table.remove(links,i,link) + break + end + end + else + link:pop() + end + end + + ping = thread:newFunction(function(self) + ping:Pause() + multi.ForEach(self.links, function(link) -- Sync new connections + link:push{self.PING} + multi:newThread("pong Thread", pong, link, links) + end) + + thread.sleep(3) + + ping:Resume() + end,false) + + thread:newThread("STC_SUB_MAN"..name,function() + local item + while true do + thread.yield() + -- We need to check on broken connections + ping(c) -- Should return instantlly and process this in another thread + item = thread.hold(function() -- This will keep things held up until there is something to process + return c.subscribe:pop() + end) + if item[1] == c.CONN then + multi.ForEach(c.links, function(link) -- Sync new connections + item[2]:push{c.CONN, link} + end) + c.links[#c.links+1] = item[2] + elseif item[1] == c.TRIG then + c:Fire(unpack(item[2])) + c.proxy_conn:Fire(unpack(item[2])) + end + end + end).OnError(print) + --- ^^^ This will only exist in the init thread + + function c:Fire(...) + local args = {...} + if self.CID == THREAD.getID() then -- Host Call + for _, link in pairs(self.links) do + link:push {self.TRIG, args} + end + --c.proxy_conn:Fire(...) + else + self.subscribe:push {self.TRIG, args} + end + end + + function c:init() + local multi, thread = require("multi"):init() + self.links = {} + self.proxy_conn = multi:newConnection() + local mt = getmetatable(self.proxy_conn) + setmetatable(self, {__index = self.proxy_conn, __call = function(t,func) self.proxy_conn(func) end, __add = mt.__add}) + if self.CID == THREAD.getID() then return self end + thread:newThread("STC_CONN_MAN"..name,function() + local item + local link_self_ref = multi:newSystemThreadedQueue() + self.subscribe:push{self.CONN, link_self_ref} + while true do + item = thread.hold(function() + return link_self_ref:peek() + end) + if item[1] == self.PING then + link_self_ref:push{self.PONG} + link_self_ref:pop() + elseif item[1] == self.CONN then + if item[2].Name ~= link_self_ref.Name then + table.insert(self.links, item[2]) + end + link_self_ref:pop() + elseif item[1] == self.TRIG then + self.proxy_conn:Fire(unpack(item[2])) + link_self_ref:pop() + else + -- This shouldn't be the case + end + end + end).OnError(print) + return self + end + + GLOBAL[name] = c + + return c +end \ No newline at end of file diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 4bada5a..57f507e 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -87,6 +87,7 @@ function multi:newSystemThread(name, func, ...) }, priority=c.priority },function(...) + require("multi.integration.lanesManager.extensions") local has_error = true return_linda:set("returns",{func(...)}) has_error = false @@ -119,6 +120,7 @@ function multi.InitSystemThreadErrorHandler() while true do thread.yield() _,data = __ConsoleLinda:receive(0, "Q") + if data then print(unpack(data)) end for i = #threads, 1, -1 do temp = threads[i] status = temp.thread.status diff --git a/multi/integration/lanesManager/threads.lua b/multi/integration/lanesManager/threads.lua index 68cb733..56b2340 100644 --- a/multi/integration/lanesManager/threads.lua +++ b/multi/integration/lanesManager/threads.lua @@ -75,7 +75,7 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda) c.queue:send("Q", {...}) end function c.error(err) - c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} + c.queue:push("Q",{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__}) error(err) end return c diff --git a/multi/integration/loveManager/extensions.lua b/multi/integration/loveManager/extensions.lua index cf6beb2..ad8339e 100644 --- a/multi/integration/loveManager/extensions.lua +++ b/multi/integration/loveManager/extensions.lua @@ -201,5 +201,330 @@ function multi:newSystemThreadedJobQueue(n) end,jqc) end jqc = jqc + 1 + return c +end + +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] + +-- TODO make compatible with lovr +local multi, thread = require("multi").init() +GLOBAL = multi.integration.GLOBAL +THREAD = multi.integration.THREAD +function multi:newSystemThreadedQueue(name) + local name = name or multi.randomString(16) + local c = {} + c.Name = name + local fRef = {"func",nil} + function c:init() + local q = {} + q.chan = love.thread.getChannel(self.Name) + function q:push(dat) + if type(dat) == "function" then + fRef[2] = THREAD.dump(dat) + self.chan:push(fRef) + return + else + self.chan:push(dat) + end + end + function q:pop() + local dat = self.chan:pop() + if type(dat)=="table" and dat[1]=="func" then + return THREAD.loadDump(dat[2]) + else + return dat + end + end + function q:peek() + local dat = self.chan:peek() + if type(dat)=="table" and dat[1]=="func" then + return THREAD.loadDump(dat[2]) + else + return dat + end + end + return q + end + THREAD.package(name,c) + return c +end +function multi:newSystemThreadedTable(name) + local name = name or multi.randomString(16) + local c = {} + c.Name = name + function c:init() + return THREAD.createTable(self.Name) + end + THREAD.package(name,c) + return c +end +local jqc = 1 +function multi:newSystemThreadedJobQueue(n) + local c = {} + c.cores = n or THREAD.getCores() + c.registerQueue = {} + c.funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") + c.queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") + c.queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") + c.queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") + c.id = 0 + c.OnJobCompleted = multi:newConnection() + local allfunc = 0 + function c:doToAll(func) + local f = THREAD.dump(func) + for i = 1, self.cores do + self.queueAll:push({allfunc,f}) + end + allfunc = allfunc + 1 + end + function c:registerFunction(name,func) + if self.funcs[name] then + error("A function by the name "..name.." has already been registered!") + end + self.funcs[name] = func + end + function c:pushJob(name,...) + self.id = self.id + 1 + self.queue:push{name,self.id,...} + return self.id + end + function c:isEmpty() + return queueJob:peek()==nil + end + local nFunc = 0 + function c:newFunction(name,func,holup) -- This registers with the queue + if type(name)=="function" then + holup = func + func = name + name = "JQ_Function_"..nFunc + end + nFunc = nFunc + 1 + c:registerFunction(name,func) + return thread:newFunction(function(...) + local id = c:pushJob(name,...) + local link + local rets + link = c.OnJobCompleted(function(jid,...) + if id==jid then + rets = {...} + link:Destroy() + end + end) + return thread.hold(function() + if rets then + return unpack(rets) or multi.NIL + end + end) + end,holup),name + end + thread:newThread("jobManager",function() + while true do + thread.yield() + local dat = c.queueReturn:pop() + if dat then + c.OnJobCompleted:Fire(unpack(dat)) + end + end + end) + for i=1,c.cores do + multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) + local multi, thread = require("multi"):init() + require("love.timer") + local function atomic(channel) + return channel:pop() + end + local clock = os.clock + local funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") + local queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") + local queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") + local lastProc = clock() + local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") + local registry = {} + setmetatable(_G,{__index = funcs}) + thread:newThread("startUp",function() + while true do + thread.yield() + local all = queueAll:peek() + if all and not registry[all[1]] then + lastProc = os.clock() + THREAD.loadDump(queueAll:pop()[2])() + end + end + end) + thread:newThread("runner",function() + thread.sleep(.1) + while true do + thread.yield() + local all = queueAll:peek() + if all and not registry[all[1]] then + lastProc = os.clock() + THREAD.loadDump(queueAll:pop()[2])() + end + local dat = queue:performAtomic(atomic) + if dat then + lastProc = os.clock() + local name = table.remove(dat,1) + local id = table.remove(dat,1) + local tab = {funcs[name](unpack(dat))} + table.insert(tab,1,id) + queueReturn:push(tab) + end + end + end):OnError(function(...) + error(...) + end) + thread:newThread("Idler",function() + while true do + thread.yield() + if clock()-lastProc> 2 then + THREAD.sleep(.05) + else + THREAD.sleep(.001) + end + end + end) + multi:mainloop() + end,jqc) + end + jqc = jqc + 1 + return c +end + +function multi:newSystemThreadedConnection(name) + local name = name or multi.randomString(16) + local c = {} + c.CONN = 0x00 + c.TRIG = 0x01 + c.PING = 0x02 + c.PONG = 0x03 + local function remove(a, b) + local ai = {} + local r = {} + for k,v in pairs(a) do ai[v]=true end + for k,v in pairs(b) do + if ai[v]==nil then table.insert(r,a[k]) end + end + return r + end + c.CID = THREAD.getID() + c.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..self.Name):init() + c.Name = name + c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. + -- Locals will only live in the thread that creates the original object + local ping + local pong = function(link, links) + local res = thread.hold(function() + return link:peek()[1] == c.PONG + end,{sleep=3}) + + if not res then + self.links = remove(self.links, pings) + else + link:pop() + end + end + + ping = thread:newFunction(function(self) + ping:Pause() + multi.ForEach(self.links, function(link) -- Sync new connections + link:push{self.PING} + multi:newThread("pong Thread", pong, link, links) + end) + + thread.sleep(3) + + ping:Resume() + end,false) + + thread:newThread("STC_SUB_MAN"..name,function() + local item + while true do + thread.yield() + -- We need to check on broken connections + ping(c) -- Should return instantlly and process this in another thread + item = thread.hold(function() -- This will keep things held up until there is something to process + return c.subscribe:pop() + end) + if item[1] == c.CONN then + multi.ForEach(c.links, function(link) -- Sync new connections + item[2]:push{c.CONN, link} + end) + c.links[#c.links+1] = item[2] + elseif item[1] == c.TRIG then + c:Fire(unpack(item[2])) + c.proxy_conn:Fire(unpack(item[2])) + end + end + end) + --- ^^^ This will only exist in the init thread + + function c:Fire(...) + local args = {...} + if self.CID == THREAD.getID() then -- Host Call + for _, link in pairs(self.links) do + link:push {self.TRIG, args} + end + else + self.subscribe:push {self.TRIG, args} + end + end + + function c:init() + local multi, thread = require("multi"):init() + self.links = {} + self.proxy_conn = multi:newConnection() + local mt = getmetatable(self.proxy_conn) + setmetatable(self, {__index = self.proxy_conn, __call = function(t,func) self.proxy_conn(func) end, __add = mt.__add}) + thread:newThread("STC_CONN_MAN"..name,function() + local item + local link_self_ref = multi:newSystemThreadedQueue() + self.subscribe:push{self.CONN, link_self_ref} + while true do + item = thread.hold(function() + return link_self_ref:peek() + end) + if item[1] == self.PING then + link_self_ref:push{self.PONG} + link_self_ref:pop() + elseif item[1] == self.CONN then + if item[2].Name ~= link_self_ref.Name then + table.insert(self.links, item[2]) + end + link_self_ref:pop() + elseif item[1] == self.TRIG then + self.proxy_conn:Fire(unpack(item[2])) + link_self_ref:pop() + else + -- This shouldn't be the case + end + end + end) + return self + end + + GLOBAL[name] = c + return c end \ No newline at end of file diff --git a/test.lua b/test.lua index fc484f9..b77857a 100644 --- a/test.lua +++ b/test.lua @@ -1,20 +1,76 @@ -package.path = "./?/init.lua;?.lua;lua5.4/share/lua/?/init.lua;lua5.4/share/lua/?.lua;"..package.path -package.cpath = "lua5.4/lib/lua/?/core.dll;"..package.cpath +package.path = "./?/init.lua;?.lua;lua5.4/share/lua/5.4/?/init.lua;lua5.4/share/lua/5.4/?.lua;"--..package.path +package.cpath = "lua5.4/lib/lua/5.4/?/core.dll;"--..package.cpath multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -test = THREAD:newFunction(function() - PNT() - return 1,2 -end,true) -multi:newThread(function() - while true do - print("...") - thread.sleep(1) - end +local conn = multi:newSystemThreadedConnection("conn"):init() + +multi:newSystemThread("Thread_Test_1",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local console = THREAD.getConsole() + conn(function() + console.print(THREAD:getName().." was triggered!") + end) + multi:mainloop() +end) + +multi:newSystemThread("Thread_Test_2",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local console = THREAD.getConsole() + conn(function(a,b,c) + console.print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(3):OnRing(function() + console.print("Fire 2!!!") + conn:Fire(4,5,6) + --THREAD.kill() + end) + + multi:mainloop() +end) + +conn(function(a,b,c) + print("Mainloop conn got triggered!",a,b,c) +end) + +alarm = multi:newAlarm(1) +alarm:OnRing(function() + print("Fire 1!!!") + conn:Fire(1,2,3) +end) + +alarm = multi:newAlarm(3):OnRing(function() + multi:newSystemThread("Thread_Test_3",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local console = THREAD.getConsole() + conn(function(a,b,c) + console.print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(4):OnRing(function() + console.print("Fire 3!!!") + conn:Fire(7,8,9) + end) + multi:mainloop() + end) +end) + +multi:newSystemThread("Thread_Test_4",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local conn2 = multi:newConnection() + local console = THREAD.getConsole() + multi:newAlarm(2):OnRing(function() + conn2:Fire() + end) + multi:newThread(function() + console.print("Conn Test!") + thread.hold(conn + conn2) + console.print("It held!") + end) + multi:mainloop() end) -multi:newAlarm(.1):OnRing(function() os.exit() end) -print(test()) -print("Hi!") multi:mainloop() \ No newline at end of file From 0d3a7b9393f1fe1379fac2b6a2c40b6f78c5b934 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 29 Sep 2022 11:57:37 -0400 Subject: [PATCH 15/40] Fixed some buges with love2d support --- multi/init.lua | 2 +- multi/integration/loveManager/init.lua | 2 +- multi/integration/loveManager/threads.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 3d1d006..2853319 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1189,7 +1189,7 @@ function thread:newFunctionBase(generator,holdme) end end tfunc.__call = function(t,...) - if not t.Active then + if t.Active == false then if holdme then return nil, "Function is paused" end diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua index 93a5a2c..f002d86 100644 --- a/multi/integration/loveManager/init.lua +++ b/multi/integration/loveManager/init.lua @@ -109,7 +109,7 @@ end multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD require("multi.integration.loveManager.extensions") -mulit.print("Integrated Love Threading!") +multi.print("Integrated Love Threading!") return {init=function() return GLOBAL,THREAD end} \ No newline at end of file diff --git a/multi/integration/loveManager/threads.lua b/multi/integration/loveManager/threads.lua index c66e7dc..d867d6d 100644 --- a/multi/integration/loveManager/threads.lua +++ b/multi/integration/loveManager/threads.lua @@ -106,7 +106,7 @@ function threads.kill() error("Thread Killed!\1") end -function THREAD.pushStatus(...) +function threads.pushStatus(...) local status_channel = love.thread.getChannel("__"..__THREADID__.."__MULTI__STATUS_CHANNEL__") local args = {...} status_channel:push(__THREADID__, args) From df429b617efc56b3d94f8837bf654a93039f2386 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 29 Sep 2022 12:12:24 -0400 Subject: [PATCH 16/40] Changed the structure of the code --- multi/init.lua => init.lua | 0 .../lanesManager/extensions.lua | 0 .../lanesManager/init.lua | 0 .../lanesManager/threads.lua | 0 .../loveManager/extensions.lua | 0 .../loveManager/init.lua | 0 .../loveManager/threads.lua | 0 .../lovrManager/extensions.lua | 0 .../networkManager/init.lua | 0 multi/integration/lovrManager/init.lua | 89 ------- multi/integration/lovrManager/threads.lua | 222 ------------------ multi/integration/luvitManager.lua | 138 ----------- multi/integration/networkManager/channel.lua | 34 --- .../integration/networkManager/childNode.lua | 46 ---- .../integration/networkManager/clientSide.lua | 35 --- multi/integration/networkManager/cmds.lua | 42 ---- .../integration/networkManager/extensions.lua | 23 -- .../integration/networkManager/masterNode.lua | 163 ------------- multi/integration/networkManager/node.lua | 127 ---------- .../networkManager/nodeManager.lua | 48 ---- .../integration/networkManager/serverSide.lua | 47 ---- multi/integration/networkManager/threads.lua | 23 -- multi/integration/networkManager/utils.lua | 27 --- .../integration/pesudoManager/extensions.lua | 141 ----------- multi/integration/pesudoManager/init.lua | 101 -------- multi/integration/pesudoManager/threads.lua | 110 --------- multi/integration/threading.lua | 13 - rockspecs/multi-15.3-0.rockspec | 32 +-- 28 files changed, 16 insertions(+), 1445 deletions(-) rename multi/init.lua => init.lua (100%) rename {multi/integration => integration}/lanesManager/extensions.lua (100%) rename {multi/integration => integration}/lanesManager/init.lua (100%) rename {multi/integration => integration}/lanesManager/threads.lua (100%) rename {multi/integration => integration}/loveManager/extensions.lua (100%) rename {multi/integration => integration}/loveManager/init.lua (100%) rename {multi/integration => integration}/loveManager/threads.lua (100%) rename {multi/integration => integration}/lovrManager/extensions.lua (100%) rename {multi/integration => integration}/networkManager/init.lua (100%) delete mode 100644 multi/integration/lovrManager/init.lua delete mode 100644 multi/integration/lovrManager/threads.lua delete mode 100644 multi/integration/luvitManager.lua delete mode 100644 multi/integration/networkManager/channel.lua delete mode 100644 multi/integration/networkManager/childNode.lua delete mode 100644 multi/integration/networkManager/clientSide.lua delete mode 100644 multi/integration/networkManager/cmds.lua delete mode 100644 multi/integration/networkManager/extensions.lua delete mode 100644 multi/integration/networkManager/masterNode.lua delete mode 100644 multi/integration/networkManager/node.lua delete mode 100644 multi/integration/networkManager/nodeManager.lua delete mode 100644 multi/integration/networkManager/serverSide.lua delete mode 100644 multi/integration/networkManager/threads.lua delete mode 100644 multi/integration/networkManager/utils.lua delete mode 100644 multi/integration/pesudoManager/extensions.lua delete mode 100644 multi/integration/pesudoManager/init.lua delete mode 100644 multi/integration/pesudoManager/threads.lua delete mode 100644 multi/integration/threading.lua diff --git a/multi/init.lua b/init.lua similarity index 100% rename from multi/init.lua rename to init.lua diff --git a/multi/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua similarity index 100% rename from multi/integration/lanesManager/extensions.lua rename to integration/lanesManager/extensions.lua diff --git a/multi/integration/lanesManager/init.lua b/integration/lanesManager/init.lua similarity index 100% rename from multi/integration/lanesManager/init.lua rename to integration/lanesManager/init.lua diff --git a/multi/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua similarity index 100% rename from multi/integration/lanesManager/threads.lua rename to integration/lanesManager/threads.lua diff --git a/multi/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua similarity index 100% rename from multi/integration/loveManager/extensions.lua rename to integration/loveManager/extensions.lua diff --git a/multi/integration/loveManager/init.lua b/integration/loveManager/init.lua similarity index 100% rename from multi/integration/loveManager/init.lua rename to integration/loveManager/init.lua diff --git a/multi/integration/loveManager/threads.lua b/integration/loveManager/threads.lua similarity index 100% rename from multi/integration/loveManager/threads.lua rename to integration/loveManager/threads.lua diff --git a/multi/integration/lovrManager/extensions.lua b/integration/lovrManager/extensions.lua similarity index 100% rename from multi/integration/lovrManager/extensions.lua rename to integration/lovrManager/extensions.lua diff --git a/multi/integration/networkManager/init.lua b/integration/networkManager/init.lua similarity index 100% rename from multi/integration/networkManager/init.lua rename to integration/networkManager/init.lua diff --git a/multi/integration/lovrManager/init.lua b/multi/integration/lovrManager/init.lua deleted file mode 100644 index 53cafa3..0000000 --- a/multi/integration/lovrManager/init.lua +++ /dev/null @@ -1,89 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] --- TODO make compatible with lovr -if ISTHREAD then - error("You cannot require the lovrManager from within a thread!") -end -local ThreadFileData = [[ -ISTHREAD = true -THREAD = require("multi.integration.lovrManager.threads") -- order is important! -sThread = THREAD -__IMPORTS = {...} -__FUNC__=table.remove(__IMPORTS,1) -__THREADID__=table.remove(__IMPORTS,1) -__THREADNAME__=table.remove(__IMPORTS,1) -stab = THREAD.createStaticTable(__THREADNAME__) -GLOBAL = THREAD.getGlobal() -multi, thread = require("multi").init() -stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} -]] -local multi, thread = require("multi.compat.lovr2d"):init() -local THREAD = {} -__THREADID__ = 0 -__THREADNAME__ = "MainThread" -multi.integration={} -multi.integration.lovr2d={} -local THREAD = require("multi.integration.lovrManager.threads") -local GLOBAL = THREAD.getGlobal() -local THREAD_ID = 1 -local OBJECT_ID = 0 -local stf = 0 -function THREAD:newFunction(func,holup) - stf = stf + 1 - return function(...) - local t = multi:newSystemThread("STF"..stf,func,...) - return thread:newFunction(function() - return thread.hold(function() - if t.stab["returns"] then - local dat = t.stab.returns - t.stab.returns = nil - return unpack(dat) - end - end) - end,holup)() - end -end -function multi:newSystemThread(name,func,...) - local c = {} - c.name = name - c.ID=THREAD_ID - c.thread=lovr.thread.newThread(ThreadFileData) - c.thread:start(THREAD.dump(func),c.ID,c.name,...) - c.stab = THREAD.createStaticTable(name) - GLOBAL["__THREAD_"..c.ID] = {ID=c.ID,Name=c.name,Thread=c.thread} - GLOBAL["__THREAD_COUNT"] = THREAD_ID - THREAD_ID=THREAD_ID+1 - return c -end -THREAD.newSystemThread = multi.newSystemThread -function lovr.threaderror(thread, errorstr) - print("Thread error!\n"..errorstr) -end -multi.integration.GLOBAL = GLOBAL -multi.integration.THREAD = THREAD -require("multi.integration.lovrManager.extensions") -print("Integrated lovr Threading!") -return {init=function() - return GLOBAL,THREAD -end} \ No newline at end of file diff --git a/multi/integration/lovrManager/threads.lua b/multi/integration/lovrManager/threads.lua deleted file mode 100644 index 6a95a1e..0000000 --- a/multi/integration/lovrManager/threads.lua +++ /dev/null @@ -1,222 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] --- TODO make compatible with lovr -require("lovr.timer") -require("lovr.system") -require("lovr.data") -local socket = require("socket") -local multi, thread = require("multi").init() -local threads = {} -function threads.loadDump(d) - return loadstring(d:getString()) -end -function threads.dump(func) - return lovr.data.newByteData(string.dump(func)) -end -local fRef = {"func",nil} -local function manage(channel, value) - channel:clear() - if type(value) == "function" then - fRef[2] = THREAD.dump(value) - channel:push(fRef) - return - else - channel:push(value) - end -end -local function RandomVariable(length) - local res = {} - math.randomseed(socket.gettime()*10000) - for i = 1, length do - res[#res+1] = string.char(math.random(97, 122)) - end - return table.concat(res) -end -local GNAME = "__GLOBAL_" -local proxy = {} -function threads.set(name,val) - if not proxy[name] then proxy[name] = lovr.thread.getChannel(GNAME..name) end - proxy[name]:performAtomic(manage, val) -end -function threads.get(name) - if not proxy[name] then proxy[name] = lovr.thread.getChannel(GNAME..name) end - local dat = proxy[name]:peek() - if type(dat)=="table" and dat[1]=="func" then - return THREAD.loadDump(dat[2]) - else - return dat - end -end -function threads.waitFor(name) - if thread.isThread() then - return thread.hold(function() - return threads.get(name) - end) - end - while threads.get(name)==nil do - lovr.timer.sleep(.001) - end - local dat = threads.get(name) - if type(dat) == "table" and dat.init then - dat.init = threads.loadDump(dat.init) - end - return dat -end -function threads.package(name,val) - local init = val.init - val.init=threads.dump(val.init) - GLOBAL[name]=val - val.init=init -end -function threads.getCores() - return lovr.system.getProcessorCount() -end -function threads.kill() - error("Thread Killed!") -end -function threads.getThreads() - local t = {} - for i=1,GLOBAL["__THREAD_COUNT"] do - t[#t+1]=GLOBAL["__THREAD_"..i] - end - return t -end -function threads.getThread(n) - return GLOBAL["__THREAD_"..n] -end -function threads.getName() - return __THREADNAME__ -end -function threads.getID() - return __THREADID__ -end -function threads.sleep(n) - lovr.timer.sleep(n) -end -function threads.getGlobal() - return setmetatable({}, - { - __index = function(t, k) - return THREAD.get(k) - end, - __newindex = function(t, k, v) - THREAD.set(k,v) - end - } - ) -end -function threads.createTable(n) - local _proxy = {} - local function set(name,val) - if not _proxy[name] then _proxy[name] = lovr.thread.getChannel(n..name) end - _proxy[name]:performAtomic(manage, val) - end - local function get(name) - if not _proxy[name] then _proxy[name] = lovr.thread.getChannel(n..name) end - local dat = _proxy[name]:peek() - if type(dat)=="table" and dat[1]=="func" then - return THREAD.loadDump(dat[2]) - else - return dat - end - end - return setmetatable({}, - { - __index = function(t, k) - return get(k) - end, - __newindex = function(t, k, v) - set(k,v) - end - } - ) -end -function threads.getConsole() - local c = {} - c.queue = lovr.thread.getChannel("__CONSOLE__") - function c.print(...) - c.queue:push{...} - end - function c.error(err) - c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} - error(err) - end - return c -end -if not ISTHREAD then - local clock = os.clock - local lastproc = clock() - local queue = lovr.thread.getChannel("__CONSOLE__") - thread:newThread("consoleManager",function() - while true do - thread.yield() - dat = queue:pop() - if dat then - lastproc = clock() - print(unpack(dat)) - end - if clock()-lastproc>2 then - thread.sleep(.1) - end - end - end) -end -function threads.createStaticTable(n) - local __proxy = {} - local function set(name,val) - if __proxy[name] then return end - local chan = lovr.thread.getChannel(n..name) - if chan:getCount()>0 then return end - chan:performAtomic(manage, val) - __proxy[name] = val - end - local function get(name) - if __proxy[name] then return __proxy[name] end - local dat = lovr.thread.getChannel(n..name):peek() - if type(dat)=="table" and dat[1]=="func" then - __proxy[name] = THREAD.loadDump(dat[2]) - return __proxy[name] - else - __proxy[name] = dat - return __proxy[name] - end - end - return setmetatable({}, - { - __index = function(t, k) - return get(k) - end, - __newindex = function(t, k, v) - set(k,v) - end - } - ) -end -function threads.hold(n) - local dat - while not(dat) do - dat = n() - end -end -return threads \ No newline at end of file diff --git a/multi/integration/luvitManager.lua b/multi/integration/luvitManager.lua deleted file mode 100644 index 0f53cb9..0000000 --- a/multi/integration/luvitManager.lua +++ /dev/null @@ -1,138 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] - --- This module probably will not be maintained any longer! -package.path = "?/init.lua;?.lua;" .. package.path -local function _INIT(luvitThread, timer) - -- lots of this stuff should be able to stay the same - function os.getOS() - if package.config:sub(1, 1) == "\\" then - return "windows" - else - return "unix" - end - end - -- Step 1 get setup threads on luvit... Sigh how do i even... - local multi, thread = require("multi").init() - isMainThread = true - function multi:canSystemThread() - return true - end - function multi:getPlatform() - return "luvit" - end - local multi = multi - -- Step 2 set up the Global table... is this possible? - local GLOBAL = {} - setmetatable( - GLOBAL, - { - __index = function(t, k) - --print("No Global table when using luvit integration!") - return nil - end, - __newindex = function(t, k, v) - --print("No Global table when using luvit integration!") - end - } - ) - local THREAD = {} - function THREAD.set(name, val) - --print("No Global table when using luvit integration!") - end - function THREAD.get(name) - --print("No Global table when using luvit integration!") - end - local function randomString(n) - local str = "" - local strings = {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","1","2","3","4","5","6","7","8","9","0","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"} - for i = 1, n do - str = str .. "" .. strings[math.random(1, #strings)] - end - return str - end - function THREAD.waitFor(name) - --print("No Global table when using luvit integration!") - end - function THREAD.testFor(name, val, sym) - --print("No Global table when using luvit integration!") - end - function THREAD.getCores() - return THREAD.__CORES - end - if os.getOS() == "windows" then - THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS")) - else - THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n")) - end - function THREAD.kill() -- trigger the thread destruction - error("Thread was Killed!") - end - -- hmmm if im cleaver I can get this to work... but since data passing isn't going to be a thing its probably not important - function THREAD.sleep(n) - --print("No Global table when using luvit integration!") - end - function THREAD.hold(n) - --print("No Global table when using luvit integration!") - end - -- Step 5 Basic Threads! - local function entry(path, name, func, ...) - local timer = require "timer" - local luvitThread = require "thread" - package.path = path - loadstring(func)(...) - end - function multi:newSystemThread(name, func, ...) - local c = {} - local __self = c - c.name = name - c.Type = "sthread" - c.thread = {} - c.func = string.dump(func) - function c:kill() - -- print("No Global table when using luvit integration!") - end - luvitThread.start(entry, package.path, name, c.func, ...) - return c - end - THREAD.newSystemThread = multi.newSystemThread - multi.print("Integrated Luvit!") - multi.integration = {} -- for module creators - multi.integration.GLOBAL = GLOBAL - multi.integration.THREAD = THREAD - require("multi.integration.shared") - -- Start the main mainloop... This allows you to process your multi objects, but the engine on the main thread will be limited to .001 or 1 millisecond sigh... - local interval = - timer.setInterval( - 1, - function() - multi:uManager() - end - ) - return multi -end -return {init = function(threadHandle, timerHandle) - local multi = _INIT(threadHandle, timerHandle) - return GLOBAL, THREAD -end} diff --git a/multi/integration/networkManager/channel.lua b/multi/integration/networkManager/channel.lua deleted file mode 100644 index 3083241..0000000 --- a/multi/integration/networkManager/channel.lua +++ /dev/null @@ -1,34 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -_G["__CHANNEL__"] = {} -local channel = {} -channel.__index = channel - --- Creates/Gets a channel of name -function channel:newChannel(name) - local chan = _G["__CHANNEL__"] - if chan then - - end -end \ No newline at end of file diff --git a/multi/integration/networkManager/childNode.lua b/multi/integration/networkManager/childNode.lua deleted file mode 100644 index 43837d5..0000000 --- a/multi/integration/networkManager/childNode.lua +++ /dev/null @@ -1,46 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -local multi, thread = require("multi"):init() -local cmd = require("multi.integration.networkManager.cmds") -local node = require("multi.integration.networkManager.node") -local net = require("net") -local bin = require("bin") -local child = {} -child.__index = child -function multi:newChildNode(cd) - local c = {} - setmetatable(c,child) - local name - if cd then - if cd.name then - name = cd.name - end - c.node = node:new(cd.nodePort or cmd.defaultPort,nil,name) - if cd.managerHost then - cd.managerPort = cd.managerPort or cmd.defaultManagerPort - c.node:registerWithManager(cd.managerHost,cd.managerPort) - end - end - return c -end \ No newline at end of file diff --git a/multi/integration/networkManager/clientSide.lua b/multi/integration/networkManager/clientSide.lua deleted file mode 100644 index 9210840..0000000 --- a/multi/integration/networkManager/clientSide.lua +++ /dev/null @@ -1,35 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -return function(self,data) - local cmd,data = data:match("!(.-)!(.*)") - --print(">",cmd,data) - if cmd == "PONG" then - self:send("!PONG!") - elseif cmd == "CHANNEL" then - -- - elseif cmd == "RETURNS" then - local rets = bin.new(data):getBlock("t") - self.node.master.OnDataReturned:Fire(rets) - end -end \ No newline at end of file diff --git a/multi/integration/networkManager/cmds.lua b/multi/integration/networkManager/cmds.lua deleted file mode 100644 index ccb3c2f..0000000 --- a/multi/integration/networkManager/cmds.lua +++ /dev/null @@ -1,42 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -local cmds = { - defaultManagerPort = 0XDE2, - defaultWait = 0X002, - defaultPort = 0X000, -- We will let the OS assign us one - standardSkip = 0X018, - ERROR = 0X000, - PING = 0X001, - PONG = 0X002, - QUEUE = 0X003, - TASK = 0X004, - INITNODE = 0X005, - INITMASTER = 0X006, - GLOBAL = 0X007, - LOAD = 0X008, - CALL = 0X009, - REG = 0X00A, - CONSOLE = 0X00B, -} -return cmds \ No newline at end of file diff --git a/multi/integration/networkManager/extensions.lua b/multi/integration/networkManager/extensions.lua deleted file mode 100644 index c4cac79..0000000 --- a/multi/integration/networkManager/extensions.lua +++ /dev/null @@ -1,23 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] \ No newline at end of file diff --git a/multi/integration/networkManager/masterNode.lua b/multi/integration/networkManager/masterNode.lua deleted file mode 100644 index 25c2b07..0000000 --- a/multi/integration/networkManager/masterNode.lua +++ /dev/null @@ -1,163 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -local multi, thread = require("multi"):init() -local cmd = require("multi.integration.networkManager.cmds") -local node = require("multi.integration.networkManager.node") -local net = require("net") -local bin = require("bin") -local master = {} -master.__index = master -function master:addNode(ip,port) - return node:new(ip,port) -end -function master:getNodesFromBroadcast() - net:newCastedClients("NODE_.+") - net.OnCastedClientInfo(function(client, n, ip, port) - self.nodes[n] = node:new(client) - end) -end -function master:getNodesFromManager(ip,port) - local mn = self.nodes - if not self.manager then - self.manager = net:newTCPClient(ip,port) - if not self.manager then - error("Unable to connect to the node Manager! Is it running? Perhaps the hostname or port is incorrect!") - end - end - self.manager.OnDataRecieved(function(self,data,client) - local cmd = data:match("!(.+)!") - data = data:gsub("!"..cmd.."!","") - if cmd == "NODE" then - local n,h,p = data:match("(.-)|(.-)|(.+)") - mn[n] = node:new(h,tonumber(p)) - end - end) - self.manager:send("!NODES!") -end -function master:setDefaultNode(nodeName) - if self:nodeExists(nodeName) then - self.defaultNode = nodeName - end -end -function master:getRandomNode() - local t = {} - for i,v in pairs(self.nodes) do t[#t+1] = i end - return t[math.random(1,#t)] -end -local netID = 0 -function master:newNetworkThread(nodeName,func,...) - local args = {...} - local dat = bin.new() - local ret - local nID = netID - local conn = multi:newConnection() - thread:newthread(function() - dat:addBlock{ - args = args, - func = func, - id = netID - } - netID = netID + 1 - if type(nodeName) == "function" then - func = nodeName - nodeName = self.defaultNode or self:getRandomNode() - if not func then - error("You must provide a function!") - end - end - self:sendTo(nodeName,"!N_THREAD!"..dat.data) - self.OnDataReturned(function(rets) - if rets.ID == nID then - conn:Fire(unpack(rets.rets)) - end - end) - end) - return conn -end -function master:newNetworkChannel(nodeName) - -- -end -function master:sendTo(nodeName,data) - self:queue("send",nodeName,data) -end -function master:demandNodeExistance(nodeName) - if self.nodes[nodeName] then - return multi.hold(self.nodes[nodeName]:ping().pong) - else - return false - end -end -function master:queue(c,...) - table.insert(self._queue,{c,{...}}) -end -function multi:newMasterNode(cd) - local c = {} - setmetatable(c, master) - c.OnNodeDiscovered = multi:newConnection() - c.OnNodeRemoved = multi:newConnection() - c.OnDataRecieved = multi:newConnection() - c.OnDataReturned = multi:newConnection() - c.defaultNode = "" - c.nodes = {} - setmetatable(c.nodes, - {__newindex = function(t,k,v) - rawset(t,k,v) - v.master = c - c.OnNodeDiscovered:Fire(k,v) - end}) - c._queue = {} - if cd then - if cd.nodeHost then - cd.nodePort = cd.nodePort or cmd.defaultPort - local n,no = c:addNode(cd.nodeHost,cd.nodePort) - if n then - c.nodes[n] = no - end - elseif cd.managerHost then - cd.managerPort = cd.managerPort or cmd.defaultManagerPort - c:getNodesFromManager(cd.managerHost,cd.managerPort) - else - c:getNodesFromBroadcast() - end - else - c:getNodesFromBroadcast() - end - thread:newthread("CMDQueueProcessor",function() - while true do - thread.skip(128) - local data = table.remove(c._queue,1) - if data then - local cmd = data[1] - if cmd == "send" then - local nodeName = data[2][1] - local dat = data[2][2] - c.nodes[nodeName]:send(dat) - end - end - end - end):OnError(function(...) - print(...) - end) - return c -end \ No newline at end of file diff --git a/multi/integration/networkManager/node.lua b/multi/integration/networkManager/node.lua deleted file mode 100644 index 8151b57..0000000 --- a/multi/integration/networkManager/node.lua +++ /dev/null @@ -1,127 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -local net = require("net") -local cmd = require("multi.integration.networkManager.cmds") -local multi,thread = require("multi"):init() -local node = {} -node.__index = node -local rand = {} -for i = 65,90 do - rand[#rand+1] = string.char(i) -end -local function randName(n) - local str = {} - for i=1,(n or 10) do - str[#str+1] = rand[math.random(1,#rand)] - end - return table.concat(str) -end -local getNames = thread:newFunction(function(names) - local listen = socket.udp() -- make a new socket - listen:setsockname(net.getLocalIP(), 11111) - listen:settimeout(0) - local data, ip, port = listen:receivefrom() - thread.holdWithin(1,function() - if data then - local n, tp, ip, port = data:match("(%S-)|(%S-)|(%S-):(%d+)") - if n then - names[n]=true - end - end - end) - return multi.NIL -end) -local function setName(ref,name) - if name then - ref.name = "NODE_"..name - ref.connection:broadcast(name) - return - end - local names = {} - getNames(names).wait() -- Prevents duplicate names from spawning! - local name = randName() - while names["NODE_"..name] do - name = randName() - end - ref.name = "NODE_"..name - ref.connection:broadcast(ref.name) -end -node.ServerCode = require("multi.integration.networkManager.serverSide") -node.ClientCode = require("multi.integration.networkManager.clientSide") -function node.random() - return randName(12) -end -function node:registerWithManager(ip,port) - if self.type ~= "server" then return end - if not self.manager then - self.manager = net:newTCPClient(ip,port) - if not self.manager then - error("Unable to connect to the node Manager! Is it running? Perhaps the hostname or port is incorrect!") - end - end - thread:newFunction(function() - thread.hold(function() return self.name end) - self.manager:send("!REG_NODE!"..self.name.."|"..net.getLocalIP().."|"..self.connection.port) - end)() -end -function node:new(host,port,name) - local c = {} - c.links = {} - setmetatable(c,node) - if type(host)=="number" or type(host)=="nil" then - c.connection = net:newTCPServer(host or cmd.defaultPort) - c.connection:enableBinaryMode() - c.type = "server" - c.connection.node = c - c.connection.OnDataRecieved(self.ServerCode) - setName(c) - elseif type(host)=="table" and host.Type == "tcp" then - c.connection = host - c.connection:enableBinaryMode() - c.type = "client" - c.connection.node = c - c.connection.OnDataRecieved(self.ClientCode) - c.name = "MASTER_NODE" - elseif type(host) == "string" and type(port)=="number" then - c.connection = net:newTCPClient(host, port) - c.connection:enableBinaryMode() - c.type = "client" - c.connection.node = c - c.connection.OnDataRecieved(self.ClientCode) - c.name = "MASTER_NODE" - else - error("Invalid arguments!") - end - return c -end -function node:ping() - if self.type ~= "client" then return end - self:send("!PING!") - return {pong=self.connection.OnDataRecieved} -end -function node:send(data) - if self.type ~= "client" then return end - self.connection:send(data) -end -return node \ No newline at end of file diff --git a/multi/integration/networkManager/nodeManager.lua b/multi/integration/networkManager/nodeManager.lua deleted file mode 100644 index 840bc60..0000000 --- a/multi/integration/networkManager/nodeManager.lua +++ /dev/null @@ -1,48 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -local multi, thread = require("multi"):init() -local cmd = require("multi.integration.networkManager.cmds") -local net = require("net") -local bin = require("bin") -local nodes = { -- Testing stuff - -} -function multi:newNodeManager(port) - print("Running node manager on port: "..(port or cmd.defaultManagerPort)) - local server = net:newTCPServer(port or cmd.defaultManagerPort) - server.OnDataRecieved(function(serv, data, client) - local cmd = data:match("!(.+)!") - data = data:gsub("!"..cmd.."!","") - if cmd == "NODES" then - for i,v in ipairs(nodes) do - -- Sample data - serv:send(client, "!NODE!".. v[1].."|"..v[2].."|"..v[3]) - end - elseif cmd == "REG_NODE" then - local name, ip, port = data:match("(.-)|(.-)|(.+)") - table.insert(nodes,{name,ip,port}) - print("Registering Node:",name, ip, port) - end - end) -end \ No newline at end of file diff --git a/multi/integration/networkManager/serverSide.lua b/multi/integration/networkManager/serverSide.lua deleted file mode 100644 index 4d8b771..0000000 --- a/multi/integration/networkManager/serverSide.lua +++ /dev/null @@ -1,47 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -local bin, bits = require("bin").init() -return function(self,data,client) - local cmd,data = data:match("!(.-)!(.*)") - --print("SERVER",cmd,data) - if cmd == "PING" then - self:send(client,"!PONG!") - elseif cmd == "N_THREAD" then - print(1) - local dat = bin.new(data) - print(2) - local t = dat:getBlock("t") - print(3) - local ret = bin.new() - print(4) - ret:addBlock{ID = t.id,rets = {t.func(unpack(t.args))}} - print(5) - print(client,"!RETURNS!"..ret:getData()) - self:send(client,"!RETURNS!"..ret:getData()) - print(6) - elseif cmd == "CHANNEL" then - local dat = bin.new(data):getBlock("t") - - end -end \ No newline at end of file diff --git a/multi/integration/networkManager/threads.lua b/multi/integration/networkManager/threads.lua deleted file mode 100644 index c4cac79..0000000 --- a/multi/integration/networkManager/threads.lua +++ /dev/null @@ -1,23 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] \ No newline at end of file diff --git a/multi/integration/networkManager/utils.lua b/multi/integration/networkManager/utils.lua deleted file mode 100644 index 9ebdb1b..0000000 --- a/multi/integration/networkManager/utils.lua +++ /dev/null @@ -1,27 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -local bin = require("bin") -local utils = {} --- Will contain data that handles sterilizing and managing data -return utils \ No newline at end of file diff --git a/multi/integration/pesudoManager/extensions.lua b/multi/integration/pesudoManager/extensions.lua deleted file mode 100644 index 3881f3f..0000000 --- a/multi/integration/pesudoManager/extensions.lua +++ /dev/null @@ -1,141 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -local multi, thread = require("multi"):init() -local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD - -local function stripUpValues(func) - local dmp = string.dump(func) - if setfenv then - return loadstring(dmp,"IsolatedThread_PesudoThreading") - else - return load(dmp,"IsolatedThread_PesudoThreading","bt") - end -end - -function multi:newSystemThreadedQueue(name) - local c = {} - function c:push(v) - table.insert(self,v) - end - function c:pop() - return table.remove(self,1) - end - function c:peek() - return self[1] - end - function c:init() - return self - end - GLOBAL[name or "_"] = c - return c -end -function multi:newSystemThreadedTable(name) - local c = {} - function c:init() - return self - end - GLOBAL[name or "_"] = c - return c -end -local setfenv = setfenv -if not setfenv then - if not debug then - multi.print("Unable to implement setfenv in lua 5.2+ the debug module is not available!") - else - setfenv = function(f, env) - return load(string.dump(f), nil, nil, env) - end - end -end -function multi:newSystemThreadedJobQueue(n) - local c = {} - c.cores = n or THREAD.getCores()*2 - c.OnJobCompleted = multi:newConnection() - local jobs = {} - local ID=1 - local jid = 1 - local env = {} - setmetatable(env,{ - __index = _G - }) - local funcs = {} - function c:doToAll(func) - setfenv(func,env)() - return self - end - function c:registerFunction(name,func) - funcs[name] = setfenv(func,env) - return self - end - function c:pushJob(name,...) - table.insert(jobs,{name,jid,{...}}) - jid = jid + 1 - return jid-1 - end - function c:isEmpty() - print(#jobs) - return #jobs == 0 - end - local nFunc = 0 - function c:newFunction(name,func,holup) -- This registers with the queue - local func = stripUpValues(func) - if type(name)=="function" then - holup = func - func = name - name = "JQ_Function_"..nFunc - end - nFunc = nFunc + 1 - c:registerFunction(name,func) - return thread:newFunction(function(...) - local id = c:pushJob(name,...) - local link - local rets - link = c.OnJobCompleted(function(jid,...) - if id==jid then - rets = {...} - link:Destroy() - end - end) - return thread.hold(function() - if rets then - return unpack(rets) or multi.NIL - end - end) - end,holup),name - end - for i=1,c.cores do - thread:newthread("PesudoThreadedJobQueue_"..i,function() - while true do - thread.yield() - if #jobs>0 then - local j = table.remove(jobs,1) - c.OnJobCompleted:Fire(j[2],funcs[j[1]](unpack(j[3]))) - else - thread.sleep(.05) - end - end - end) - end - return c -end \ No newline at end of file diff --git a/multi/integration/pesudoManager/init.lua b/multi/integration/pesudoManager/init.lua deleted file mode 100644 index 84bdca3..0000000 --- a/multi/integration/pesudoManager/init.lua +++ /dev/null @@ -1,101 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2020 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -package.path = "?/init.lua;?.lua;" .. package.path -local multi, thread = require("multi"):init() - -if multi.integration then - return { - init = function() - return multi.integration.GLOBAL, multi.integration.THREAD - end - } -end - -local GLOBAL, THREAD = require("multi.integration.pesudoManager.threads").init(thread) - -function multi:canSystemThread() -- We are emulating system threading - return true -end - -function multi:getPlatform() - return "pesudo" -end - -local function split(str) - local tab = {} - for word in string.gmatch(str, '([^,]+)') do - table.insert(tab,word) - end - return tab -end - -local tab = [[_VERSION,io,os,require,load,debug,assert,collectgarbage,error,getfenv,getmetatable,ipairs,loadstring,module,next,pairs,pcall,print,rawequal,rawget,rawset,select,setfenv,setmetatable,tonumber,tostring,type,unpack,xpcall,math,coroutine,string,table]] -tab = split(tab) - -local id = 0 -function multi:newSystemThread(name,func,...) - GLOBAL["$THREAD_NAME"] = name - GLOBAL["$__THREADNAME__"] = name - GLOBAL["$THREAD_ID"] = id - GLOBAL["$thread"] = thread - local env = { - GLOBAL = GLOBAL, - THREAD = THREAD, - THREAD_NAME = name, - __THREADNAME__ = name, - THREAD_ID = id, - thread = thread - } - - for i = 1,#tab do - env[tab[i]] = _G[tab[i]] - end - - local th = thread:newISOThread(name,func,env,...) - - id = id + 1 - - return th -end - -THREAD.newSystemThread = multi.newSystemThread --- System threads as implemented here cannot share memory, but use a message passing system. --- An isolated thread allows us to mimic that behavior so if access data from the "main" thread happens things will not work. This behavior is in line with how the system threading works - -function THREAD:newFunction(func,holdme) - return thread:newFunctionBase(function(...) - return multi:newSystemThread("TempSystemThread",func,...) - end,holdme)() -end - -multi.print("Integrated Pesudo Threading!") -multi.integration = {} -- for module creators -multi.integration.GLOBAL = GLOBAL -multi.integration.THREAD = THREAD -require("multi.integration.pesudoManager.extensions") -return { - init = function() - return GLOBAL, THREAD - end -} diff --git a/multi/integration/pesudoManager/threads.lua b/multi/integration/pesudoManager/threads.lua deleted file mode 100644 index b8ee6aa..0000000 --- a/multi/integration/pesudoManager/threads.lua +++ /dev/null @@ -1,110 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] - -local function getOS() - if package.config:sub(1, 1) == "\\" then - return "windows" - else - return "unix" - end -end - -local function INIT(thread) - local THREAD = {} - local GLOBAL = {} - THREAD.Priority_Core = 3 - THREAD.Priority_High = 2 - THREAD.Priority_Above_Normal = 1 - THREAD.Priority_Normal = 0 - THREAD.Priority_Below_Normal = -1 - THREAD.Priority_Low = -2 - THREAD.Priority_Idle = -3 - - function THREAD.set(name, val) - GLOBAL[name] = val - end - - function THREAD.get(name) - return GLOBAL[name] - end - - function THREAD.waitFor(name) - return thread.hold(function() return GLOBAL[name] end) - end - - if getOS() == "windows" then - THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS")) - else - THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n")) - end - - function THREAD.getCores() - return THREAD.__CORES - end - - function THREAD.getConsole() - local c = {} - function c.print(...) - print(...) - end - function c.error(err) - error("ERROR in <"..GLOBAL["$__THREADNAME__"]..">: "..err) - end - return c - end - - function THREAD.getThreads() - return {}--GLOBAL.__THREADS__ - end - - THREAD.pushStatus = thread.pushStatus - - if os.getOS() == "windows" then - THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS")) - else - THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n")) - end - - function THREAD.kill() - error("Thread was killed!") - end - - function THREAD.getName() - return GLOBAL["$THREAD_NAME"] - end - - function THREAD.getID() - return GLOBAL["$THREAD_ID"] - end - - THREAD.sleep = thread.sleep - - THREAD.hold = thread.hold - - return GLOBAL, THREAD -end - -return {init = function(thread) - return INIT(thread) -end} \ No newline at end of file diff --git a/multi/integration/threading.lua b/multi/integration/threading.lua deleted file mode 100644 index b75dbc6..0000000 --- a/multi/integration/threading.lua +++ /dev/null @@ -1,13 +0,0 @@ --- We need to detect what enviroment we are running our code in. -return { - init = function() - if love then - return require("multi.integration.loveManager"):init() - else - if pcall(require,"lanes") then - return require("multi.integration.lanesManager"):init() - end - return require("multi.integration.pesudoManager"):init() - end - end -} \ No newline at end of file diff --git a/rockspecs/multi-15.3-0.rockspec b/rockspecs/multi-15.3-0.rockspec index d96f6f4..2cc71ba 100644 --- a/rockspecs/multi-15.3-0.rockspec +++ b/rockspecs/multi-15.3-0.rockspec @@ -19,21 +19,21 @@ dependencies = { build = { type = "builtin", modules = { - ["multi"] = "multi/init.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua", - ["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua", - ["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua", - ["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua", - ["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua", - --["multi.integration.lovrManager"] = "multi/integration/lovrManager/init.lua", - --["multi.integration.lovrManager.extensions"] = "multi/integration/lovrManager/extensions.lua", - --["multi.integration.lovrManager.threads"] = "multi/integration/lovrManager/threads.lua", - ["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua", - ["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua", - ["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.threading"] = "multi/integration/threading.lua", - --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + ["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.lovrManager"] = "integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "integration/lovrManager/threads.lua", + ["multi.integration.pesudoManager"] = "integration/pesudoManager/init.lua", + ["multi.integration.pesudoManager.extensions"] = "integration/pesudoManager/extensions.lua", + ["multi.integration.pesudoManager.threads"] = "integration/pesudoManager/threads.lua", + ["multi.integration.luvitManager"] = "integration/luvitManager.lua", + ["multi.integration.threading"] = "integration/threading.lua", + --["multi.integration.networkManager"] = "integration/networkManager.lua", } } \ No newline at end of file From 3ae2acbd78830f3d35dda44da2f8d17c75b7d4ab Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 29 Sep 2022 12:13:33 -0400 Subject: [PATCH 17/40] how did pseudothreading go away --- integration/pesudoManager/extensions.lua | 141 +++++++++++++++++++++++ integration/pesudoManager/init.lua | 101 ++++++++++++++++ integration/pesudoManager/threads.lua | 110 ++++++++++++++++++ 3 files changed, 352 insertions(+) create mode 100644 integration/pesudoManager/extensions.lua create mode 100644 integration/pesudoManager/init.lua create mode 100644 integration/pesudoManager/threads.lua diff --git a/integration/pesudoManager/extensions.lua b/integration/pesudoManager/extensions.lua new file mode 100644 index 0000000..3881f3f --- /dev/null +++ b/integration/pesudoManager/extensions.lua @@ -0,0 +1,141 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +local multi, thread = require("multi"):init() +local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD + +local function stripUpValues(func) + local dmp = string.dump(func) + if setfenv then + return loadstring(dmp,"IsolatedThread_PesudoThreading") + else + return load(dmp,"IsolatedThread_PesudoThreading","bt") + end +end + +function multi:newSystemThreadedQueue(name) + local c = {} + function c:push(v) + table.insert(self,v) + end + function c:pop() + return table.remove(self,1) + end + function c:peek() + return self[1] + end + function c:init() + return self + end + GLOBAL[name or "_"] = c + return c +end +function multi:newSystemThreadedTable(name) + local c = {} + function c:init() + return self + end + GLOBAL[name or "_"] = c + return c +end +local setfenv = setfenv +if not setfenv then + if not debug then + multi.print("Unable to implement setfenv in lua 5.2+ the debug module is not available!") + else + setfenv = function(f, env) + return load(string.dump(f), nil, nil, env) + end + end +end +function multi:newSystemThreadedJobQueue(n) + local c = {} + c.cores = n or THREAD.getCores()*2 + c.OnJobCompleted = multi:newConnection() + local jobs = {} + local ID=1 + local jid = 1 + local env = {} + setmetatable(env,{ + __index = _G + }) + local funcs = {} + function c:doToAll(func) + setfenv(func,env)() + return self + end + function c:registerFunction(name,func) + funcs[name] = setfenv(func,env) + return self + end + function c:pushJob(name,...) + table.insert(jobs,{name,jid,{...}}) + jid = jid + 1 + return jid-1 + end + function c:isEmpty() + print(#jobs) + return #jobs == 0 + end + local nFunc = 0 + function c:newFunction(name,func,holup) -- This registers with the queue + local func = stripUpValues(func) + if type(name)=="function" then + holup = func + func = name + name = "JQ_Function_"..nFunc + end + nFunc = nFunc + 1 + c:registerFunction(name,func) + return thread:newFunction(function(...) + local id = c:pushJob(name,...) + local link + local rets + link = c.OnJobCompleted(function(jid,...) + if id==jid then + rets = {...} + link:Destroy() + end + end) + return thread.hold(function() + if rets then + return unpack(rets) or multi.NIL + end + end) + end,holup),name + end + for i=1,c.cores do + thread:newthread("PesudoThreadedJobQueue_"..i,function() + while true do + thread.yield() + if #jobs>0 then + local j = table.remove(jobs,1) + c.OnJobCompleted:Fire(j[2],funcs[j[1]](unpack(j[3]))) + else + thread.sleep(.05) + end + end + end) + end + return c +end \ No newline at end of file diff --git a/integration/pesudoManager/init.lua b/integration/pesudoManager/init.lua new file mode 100644 index 0000000..84bdca3 --- /dev/null +++ b/integration/pesudoManager/init.lua @@ -0,0 +1,101 @@ +--[[ +MIT License + +Copyright (c) 2020 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +package.path = "?/init.lua;?.lua;" .. package.path +local multi, thread = require("multi"):init() + +if multi.integration then + return { + init = function() + return multi.integration.GLOBAL, multi.integration.THREAD + end + } +end + +local GLOBAL, THREAD = require("multi.integration.pesudoManager.threads").init(thread) + +function multi:canSystemThread() -- We are emulating system threading + return true +end + +function multi:getPlatform() + return "pesudo" +end + +local function split(str) + local tab = {} + for word in string.gmatch(str, '([^,]+)') do + table.insert(tab,word) + end + return tab +end + +local tab = [[_VERSION,io,os,require,load,debug,assert,collectgarbage,error,getfenv,getmetatable,ipairs,loadstring,module,next,pairs,pcall,print,rawequal,rawget,rawset,select,setfenv,setmetatable,tonumber,tostring,type,unpack,xpcall,math,coroutine,string,table]] +tab = split(tab) + +local id = 0 +function multi:newSystemThread(name,func,...) + GLOBAL["$THREAD_NAME"] = name + GLOBAL["$__THREADNAME__"] = name + GLOBAL["$THREAD_ID"] = id + GLOBAL["$thread"] = thread + local env = { + GLOBAL = GLOBAL, + THREAD = THREAD, + THREAD_NAME = name, + __THREADNAME__ = name, + THREAD_ID = id, + thread = thread + } + + for i = 1,#tab do + env[tab[i]] = _G[tab[i]] + end + + local th = thread:newISOThread(name,func,env,...) + + id = id + 1 + + return th +end + +THREAD.newSystemThread = multi.newSystemThread +-- System threads as implemented here cannot share memory, but use a message passing system. +-- An isolated thread allows us to mimic that behavior so if access data from the "main" thread happens things will not work. This behavior is in line with how the system threading works + +function THREAD:newFunction(func,holdme) + return thread:newFunctionBase(function(...) + return multi:newSystemThread("TempSystemThread",func,...) + end,holdme)() +end + +multi.print("Integrated Pesudo Threading!") +multi.integration = {} -- for module creators +multi.integration.GLOBAL = GLOBAL +multi.integration.THREAD = THREAD +require("multi.integration.pesudoManager.extensions") +return { + init = function() + return GLOBAL, THREAD + end +} diff --git a/integration/pesudoManager/threads.lua b/integration/pesudoManager/threads.lua new file mode 100644 index 0000000..b8ee6aa --- /dev/null +++ b/integration/pesudoManager/threads.lua @@ -0,0 +1,110 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] + +local function getOS() + if package.config:sub(1, 1) == "\\" then + return "windows" + else + return "unix" + end +end + +local function INIT(thread) + local THREAD = {} + local GLOBAL = {} + THREAD.Priority_Core = 3 + THREAD.Priority_High = 2 + THREAD.Priority_Above_Normal = 1 + THREAD.Priority_Normal = 0 + THREAD.Priority_Below_Normal = -1 + THREAD.Priority_Low = -2 + THREAD.Priority_Idle = -3 + + function THREAD.set(name, val) + GLOBAL[name] = val + end + + function THREAD.get(name) + return GLOBAL[name] + end + + function THREAD.waitFor(name) + return thread.hold(function() return GLOBAL[name] end) + end + + if getOS() == "windows" then + THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS")) + else + THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n")) + end + + function THREAD.getCores() + return THREAD.__CORES + end + + function THREAD.getConsole() + local c = {} + function c.print(...) + print(...) + end + function c.error(err) + error("ERROR in <"..GLOBAL["$__THREADNAME__"]..">: "..err) + end + return c + end + + function THREAD.getThreads() + return {}--GLOBAL.__THREADS__ + end + + THREAD.pushStatus = thread.pushStatus + + if os.getOS() == "windows" then + THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS")) + else + THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n")) + end + + function THREAD.kill() + error("Thread was killed!") + end + + function THREAD.getName() + return GLOBAL["$THREAD_NAME"] + end + + function THREAD.getID() + return GLOBAL["$THREAD_ID"] + end + + THREAD.sleep = thread.sleep + + THREAD.hold = thread.hold + + return GLOBAL, THREAD +end + +return {init = function(thread) + return INIT(thread) +end} \ No newline at end of file From 57b4871a85751fce2a8c4ddd3675487b3a0bf762 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 29 Sep 2022 12:14:38 -0400 Subject: [PATCH 18/40] added missing files --- integration/luvitManager.lua | 138 +++++++++++++++++++++++++++++++++++ integration/threading.lua | 13 ++++ 2 files changed, 151 insertions(+) create mode 100644 integration/luvitManager.lua create mode 100644 integration/threading.lua diff --git a/integration/luvitManager.lua b/integration/luvitManager.lua new file mode 100644 index 0000000..0f53cb9 --- /dev/null +++ b/integration/luvitManager.lua @@ -0,0 +1,138 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] + +-- This module probably will not be maintained any longer! +package.path = "?/init.lua;?.lua;" .. package.path +local function _INIT(luvitThread, timer) + -- lots of this stuff should be able to stay the same + function os.getOS() + if package.config:sub(1, 1) == "\\" then + return "windows" + else + return "unix" + end + end + -- Step 1 get setup threads on luvit... Sigh how do i even... + local multi, thread = require("multi").init() + isMainThread = true + function multi:canSystemThread() + return true + end + function multi:getPlatform() + return "luvit" + end + local multi = multi + -- Step 2 set up the Global table... is this possible? + local GLOBAL = {} + setmetatable( + GLOBAL, + { + __index = function(t, k) + --print("No Global table when using luvit integration!") + return nil + end, + __newindex = function(t, k, v) + --print("No Global table when using luvit integration!") + end + } + ) + local THREAD = {} + function THREAD.set(name, val) + --print("No Global table when using luvit integration!") + end + function THREAD.get(name) + --print("No Global table when using luvit integration!") + end + local function randomString(n) + local str = "" + local strings = {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","1","2","3","4","5","6","7","8","9","0","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"} + for i = 1, n do + str = str .. "" .. strings[math.random(1, #strings)] + end + return str + end + function THREAD.waitFor(name) + --print("No Global table when using luvit integration!") + end + function THREAD.testFor(name, val, sym) + --print("No Global table when using luvit integration!") + end + function THREAD.getCores() + return THREAD.__CORES + end + if os.getOS() == "windows" then + THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS")) + else + THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n")) + end + function THREAD.kill() -- trigger the thread destruction + error("Thread was Killed!") + end + -- hmmm if im cleaver I can get this to work... but since data passing isn't going to be a thing its probably not important + function THREAD.sleep(n) + --print("No Global table when using luvit integration!") + end + function THREAD.hold(n) + --print("No Global table when using luvit integration!") + end + -- Step 5 Basic Threads! + local function entry(path, name, func, ...) + local timer = require "timer" + local luvitThread = require "thread" + package.path = path + loadstring(func)(...) + end + function multi:newSystemThread(name, func, ...) + local c = {} + local __self = c + c.name = name + c.Type = "sthread" + c.thread = {} + c.func = string.dump(func) + function c:kill() + -- print("No Global table when using luvit integration!") + end + luvitThread.start(entry, package.path, name, c.func, ...) + return c + end + THREAD.newSystemThread = multi.newSystemThread + multi.print("Integrated Luvit!") + multi.integration = {} -- for module creators + multi.integration.GLOBAL = GLOBAL + multi.integration.THREAD = THREAD + require("multi.integration.shared") + -- Start the main mainloop... This allows you to process your multi objects, but the engine on the main thread will be limited to .001 or 1 millisecond sigh... + local interval = + timer.setInterval( + 1, + function() + multi:uManager() + end + ) + return multi +end +return {init = function(threadHandle, timerHandle) + local multi = _INIT(threadHandle, timerHandle) + return GLOBAL, THREAD +end} diff --git a/integration/threading.lua b/integration/threading.lua new file mode 100644 index 0000000..b75dbc6 --- /dev/null +++ b/integration/threading.lua @@ -0,0 +1,13 @@ +-- We need to detect what enviroment we are running our code in. +return { + init = function() + if love then + return require("multi.integration.loveManager"):init() + else + if pcall(require,"lanes") then + return require("multi.integration.lanesManager"):init() + end + return require("multi.integration.pesudoManager"):init() + end + end +} \ No newline at end of file From b727fb15b5e01e04d186ba04c34a89d6adcc41ba Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 29 Sep 2022 12:29:05 -0400 Subject: [PATCH 19/40] Reverted changes with code structure --- init.lua => multi/init.lua | 0 .../integration}/lanesManager/extensions.lua | 0 .../integration}/lanesManager/init.lua | 0 .../integration}/lanesManager/threads.lua | 0 .../integration}/loveManager/extensions.lua | 0 .../integration}/loveManager/init.lua | 0 .../integration}/loveManager/threads.lua | 0 .../integration}/lovrManager/extensions.lua | 0 .../integration}/luvitManager.lua | 0 .../integration}/networkManager/init.lua | 0 .../integration}/pesudoManager/extensions.lua | 0 .../integration}/pesudoManager/init.lua | 0 .../integration}/pesudoManager/threads.lua | 0 .../integration}/threading.lua | 0 rockspecs/multi-15.3-0.rockspec | 32 +++++++++---------- 15 files changed, 16 insertions(+), 16 deletions(-) rename init.lua => multi/init.lua (100%) rename {integration => multi/integration}/lanesManager/extensions.lua (100%) rename {integration => multi/integration}/lanesManager/init.lua (100%) rename {integration => multi/integration}/lanesManager/threads.lua (100%) rename {integration => multi/integration}/loveManager/extensions.lua (100%) rename {integration => multi/integration}/loveManager/init.lua (100%) rename {integration => multi/integration}/loveManager/threads.lua (100%) rename {integration => multi/integration}/lovrManager/extensions.lua (100%) rename {integration => multi/integration}/luvitManager.lua (100%) rename {integration => multi/integration}/networkManager/init.lua (100%) rename {integration => multi/integration}/pesudoManager/extensions.lua (100%) rename {integration => multi/integration}/pesudoManager/init.lua (100%) rename {integration => multi/integration}/pesudoManager/threads.lua (100%) rename {integration => multi/integration}/threading.lua (100%) diff --git a/init.lua b/multi/init.lua similarity index 100% rename from init.lua rename to multi/init.lua diff --git a/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua similarity index 100% rename from integration/lanesManager/extensions.lua rename to multi/integration/lanesManager/extensions.lua diff --git a/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua similarity index 100% rename from integration/lanesManager/init.lua rename to multi/integration/lanesManager/init.lua diff --git a/integration/lanesManager/threads.lua b/multi/integration/lanesManager/threads.lua similarity index 100% rename from integration/lanesManager/threads.lua rename to multi/integration/lanesManager/threads.lua diff --git a/integration/loveManager/extensions.lua b/multi/integration/loveManager/extensions.lua similarity index 100% rename from integration/loveManager/extensions.lua rename to multi/integration/loveManager/extensions.lua diff --git a/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua similarity index 100% rename from integration/loveManager/init.lua rename to multi/integration/loveManager/init.lua diff --git a/integration/loveManager/threads.lua b/multi/integration/loveManager/threads.lua similarity index 100% rename from integration/loveManager/threads.lua rename to multi/integration/loveManager/threads.lua diff --git a/integration/lovrManager/extensions.lua b/multi/integration/lovrManager/extensions.lua similarity index 100% rename from integration/lovrManager/extensions.lua rename to multi/integration/lovrManager/extensions.lua diff --git a/integration/luvitManager.lua b/multi/integration/luvitManager.lua similarity index 100% rename from integration/luvitManager.lua rename to multi/integration/luvitManager.lua diff --git a/integration/networkManager/init.lua b/multi/integration/networkManager/init.lua similarity index 100% rename from integration/networkManager/init.lua rename to multi/integration/networkManager/init.lua diff --git a/integration/pesudoManager/extensions.lua b/multi/integration/pesudoManager/extensions.lua similarity index 100% rename from integration/pesudoManager/extensions.lua rename to multi/integration/pesudoManager/extensions.lua diff --git a/integration/pesudoManager/init.lua b/multi/integration/pesudoManager/init.lua similarity index 100% rename from integration/pesudoManager/init.lua rename to multi/integration/pesudoManager/init.lua diff --git a/integration/pesudoManager/threads.lua b/multi/integration/pesudoManager/threads.lua similarity index 100% rename from integration/pesudoManager/threads.lua rename to multi/integration/pesudoManager/threads.lua diff --git a/integration/threading.lua b/multi/integration/threading.lua similarity index 100% rename from integration/threading.lua rename to multi/integration/threading.lua diff --git a/rockspecs/multi-15.3-0.rockspec b/rockspecs/multi-15.3-0.rockspec index 2cc71ba..d96f6f4 100644 --- a/rockspecs/multi-15.3-0.rockspec +++ b/rockspecs/multi-15.3-0.rockspec @@ -19,21 +19,21 @@ dependencies = { 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.lovrManager"] = "integration/lovrManager/init.lua", - --["multi.integration.lovrManager.extensions"] = "integration/lovrManager/extensions.lua", - --["multi.integration.lovrManager.threads"] = "integration/lovrManager/threads.lua", - ["multi.integration.pesudoManager"] = "integration/pesudoManager/init.lua", - ["multi.integration.pesudoManager.extensions"] = "integration/pesudoManager/extensions.lua", - ["multi.integration.pesudoManager.threads"] = "integration/pesudoManager/threads.lua", - ["multi.integration.luvitManager"] = "integration/luvitManager.lua", - ["multi.integration.threading"] = "integration/threading.lua", - --["multi.integration.networkManager"] = "integration/networkManager.lua", + ["multi"] = "multi/init.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua", + ["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua", + ["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua", + ["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua", + ["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua", + --["multi.integration.lovrManager"] = "multi/integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "multi/integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "multi/integration/lovrManager/threads.lua", + ["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua", + ["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua", + ["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.threading"] = "multi/integration/threading.lua", + --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", } } \ No newline at end of file From 4c088b90803e118d42e12c6702d9055d1e4bda3f Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 29 Sep 2022 23:10:39 -0400 Subject: [PATCH 20/40] Working on new features --- changes.md | 32 +++ multi/init.lua | 14 +- multi/integration/lovrManager/extensions.lua | 6 +- multi/integration/lovrManager/init.lua | 89 +++++++ multi/integration/lovrManager/threads.lua | 222 ++++++++++++++++++ multi/integration/networkManager/channel.lua | 34 +++ .../integration/networkManager/childNode.lua | 46 ++++ .../integration/networkManager/clientSide.lua | 35 +++ multi/integration/networkManager/cmds.lua | 42 ++++ .../integration/networkManager/extensions.lua | 23 ++ .../integration/networkManager/masterNode.lua | 163 +++++++++++++ multi/integration/networkManager/node.lua | 127 ++++++++++ .../networkManager/nodeManager.lua | 48 ++++ .../integration/networkManager/serverSide.lua | 47 ++++ multi/integration/networkManager/threads.lua | 23 ++ multi/integration/networkManager/utils.lua | 27 +++ test.lua | 147 +++++++----- 17 files changed, 1056 insertions(+), 69 deletions(-) create mode 100644 multi/integration/lovrManager/init.lua create mode 100644 multi/integration/lovrManager/threads.lua create mode 100644 multi/integration/networkManager/channel.lua create mode 100644 multi/integration/networkManager/childNode.lua create mode 100644 multi/integration/networkManager/clientSide.lua create mode 100644 multi/integration/networkManager/cmds.lua create mode 100644 multi/integration/networkManager/extensions.lua create mode 100644 multi/integration/networkManager/masterNode.lua create mode 100644 multi/integration/networkManager/node.lua create mode 100644 multi/integration/networkManager/nodeManager.lua create mode 100644 multi/integration/networkManager/serverSide.lua create mode 100644 multi/integration/networkManager/threads.lua create mode 100644 multi/integration/networkManager/utils.lua diff --git a/changes.md b/changes.md index d6cef4f..fa53381 100644 --- a/changes.md +++ b/changes.md @@ -99,6 +99,38 @@ Changed --- - `Connection:[connect, hasConnections, getConnection]` changed to be `Connection:[Connect, HasConnections, getConnections]`. This was done in an attempt to follow a consistent naming scheme. The old methods still will work to prevent old code breaking. +- `Connections when added(+) together now act like 'or', to get the 'and' feature multiply(*) them together.` + + **Note:** This is a potentially breaking change for using connections. + + ```lua + multi, thread = require("multi"):init{print=true} + -- GLOBAL, THREAD = require("multi.integration.lanesManager"):init() + + local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection(), multi:newConnection() + + thread:newThread(function() + print("Awaiting status") + thread.hold(conn1 + (conn2 * conn3)) + print("Conn or Conn2 and Conn3") + end) + + multi:newAlarm(1):OnRing(function() + print("Conn") + conn1:Fire() + end) + + multi:newAlarm(2):OnRing(function() + print("Conn2") + conn2:Fire() + end) + + multi:newAlarm(3):OnRing(function() + print("Conn3") + conn3:Fire() + end) + ``` + Removed --- - Connection objects methods removed: diff --git a/multi/init.lua b/multi/init.lua index 2853319..fe73b0e 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -137,8 +137,18 @@ function multi:newConnection(protect,func,kill) return self:Connect(...) end end, - __add = function(c1,c2) - cn = multi:newConnection() + __add = function(c1,c2) -- Or + local cn = multi:newConnection() + c1(function(...) + cn:Fire(...) + end) + c2(function(...) + cn:Fire(...) + end) + return cn + end, + __mul = function(c1,c2) -- And + local cn = multi:newConnection() if not c1.__hasInstances then cn.__hasInstances = 2 cn.__count = 0 diff --git a/multi/integration/lovrManager/extensions.lua b/multi/integration/lovrManager/extensions.lua index b2082da..7032b1d 100644 --- a/multi/integration/lovrManager/extensions.lua +++ b/multi/integration/lovrManager/extensions.lua @@ -25,7 +25,6 @@ local multi, thread = require("multi").init() GLOBAL = multi.integration.GLOBAL THREAD = multi.integration.THREAD function multi:newSystemThreadedQueue(name) - local name = name or multi.randomString(16) local c = {} c.Name = name local fRef = {"func",nil} @@ -63,11 +62,10 @@ function multi:newSystemThreadedQueue(name) return c end function multi:newSystemThreadedTable(name) - local name = name or multi.randomString(16) local c = {} - c.Name = name + c.name = name function c:init() - return THREAD.createTable(self.Name) + return THREAD.createTable(self.name) end THREAD.package(name,c) return c diff --git a/multi/integration/lovrManager/init.lua b/multi/integration/lovrManager/init.lua new file mode 100644 index 0000000..53cafa3 --- /dev/null +++ b/multi/integration/lovrManager/init.lua @@ -0,0 +1,89 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +-- TODO make compatible with lovr +if ISTHREAD then + error("You cannot require the lovrManager from within a thread!") +end +local ThreadFileData = [[ +ISTHREAD = true +THREAD = require("multi.integration.lovrManager.threads") -- order is important! +sThread = THREAD +__IMPORTS = {...} +__FUNC__=table.remove(__IMPORTS,1) +__THREADID__=table.remove(__IMPORTS,1) +__THREADNAME__=table.remove(__IMPORTS,1) +stab = THREAD.createStaticTable(__THREADNAME__) +GLOBAL = THREAD.getGlobal() +multi, thread = require("multi").init() +stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} +]] +local multi, thread = require("multi.compat.lovr2d"):init() +local THREAD = {} +__THREADID__ = 0 +__THREADNAME__ = "MainThread" +multi.integration={} +multi.integration.lovr2d={} +local THREAD = require("multi.integration.lovrManager.threads") +local GLOBAL = THREAD.getGlobal() +local THREAD_ID = 1 +local OBJECT_ID = 0 +local stf = 0 +function THREAD:newFunction(func,holup) + stf = stf + 1 + return function(...) + local t = multi:newSystemThread("STF"..stf,func,...) + return thread:newFunction(function() + return thread.hold(function() + if t.stab["returns"] then + local dat = t.stab.returns + t.stab.returns = nil + return unpack(dat) + end + end) + end,holup)() + end +end +function multi:newSystemThread(name,func,...) + local c = {} + c.name = name + c.ID=THREAD_ID + c.thread=lovr.thread.newThread(ThreadFileData) + c.thread:start(THREAD.dump(func),c.ID,c.name,...) + c.stab = THREAD.createStaticTable(name) + GLOBAL["__THREAD_"..c.ID] = {ID=c.ID,Name=c.name,Thread=c.thread} + GLOBAL["__THREAD_COUNT"] = THREAD_ID + THREAD_ID=THREAD_ID+1 + return c +end +THREAD.newSystemThread = multi.newSystemThread +function lovr.threaderror(thread, errorstr) + print("Thread error!\n"..errorstr) +end +multi.integration.GLOBAL = GLOBAL +multi.integration.THREAD = THREAD +require("multi.integration.lovrManager.extensions") +print("Integrated lovr Threading!") +return {init=function() + return GLOBAL,THREAD +end} \ No newline at end of file diff --git a/multi/integration/lovrManager/threads.lua b/multi/integration/lovrManager/threads.lua new file mode 100644 index 0000000..6a95a1e --- /dev/null +++ b/multi/integration/lovrManager/threads.lua @@ -0,0 +1,222 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +-- TODO make compatible with lovr +require("lovr.timer") +require("lovr.system") +require("lovr.data") +local socket = require("socket") +local multi, thread = require("multi").init() +local threads = {} +function threads.loadDump(d) + return loadstring(d:getString()) +end +function threads.dump(func) + return lovr.data.newByteData(string.dump(func)) +end +local fRef = {"func",nil} +local function manage(channel, value) + channel:clear() + if type(value) == "function" then + fRef[2] = THREAD.dump(value) + channel:push(fRef) + return + else + channel:push(value) + end +end +local function RandomVariable(length) + local res = {} + math.randomseed(socket.gettime()*10000) + for i = 1, length do + res[#res+1] = string.char(math.random(97, 122)) + end + return table.concat(res) +end +local GNAME = "__GLOBAL_" +local proxy = {} +function threads.set(name,val) + if not proxy[name] then proxy[name] = lovr.thread.getChannel(GNAME..name) end + proxy[name]:performAtomic(manage, val) +end +function threads.get(name) + if not proxy[name] then proxy[name] = lovr.thread.getChannel(GNAME..name) end + local dat = proxy[name]:peek() + if type(dat)=="table" and dat[1]=="func" then + return THREAD.loadDump(dat[2]) + else + return dat + end +end +function threads.waitFor(name) + if thread.isThread() then + return thread.hold(function() + return threads.get(name) + end) + end + while threads.get(name)==nil do + lovr.timer.sleep(.001) + end + local dat = threads.get(name) + if type(dat) == "table" and dat.init then + dat.init = threads.loadDump(dat.init) + end + return dat +end +function threads.package(name,val) + local init = val.init + val.init=threads.dump(val.init) + GLOBAL[name]=val + val.init=init +end +function threads.getCores() + return lovr.system.getProcessorCount() +end +function threads.kill() + error("Thread Killed!") +end +function threads.getThreads() + local t = {} + for i=1,GLOBAL["__THREAD_COUNT"] do + t[#t+1]=GLOBAL["__THREAD_"..i] + end + return t +end +function threads.getThread(n) + return GLOBAL["__THREAD_"..n] +end +function threads.getName() + return __THREADNAME__ +end +function threads.getID() + return __THREADID__ +end +function threads.sleep(n) + lovr.timer.sleep(n) +end +function threads.getGlobal() + return setmetatable({}, + { + __index = function(t, k) + return THREAD.get(k) + end, + __newindex = function(t, k, v) + THREAD.set(k,v) + end + } + ) +end +function threads.createTable(n) + local _proxy = {} + local function set(name,val) + if not _proxy[name] then _proxy[name] = lovr.thread.getChannel(n..name) end + _proxy[name]:performAtomic(manage, val) + end + local function get(name) + if not _proxy[name] then _proxy[name] = lovr.thread.getChannel(n..name) end + local dat = _proxy[name]:peek() + if type(dat)=="table" and dat[1]=="func" then + return THREAD.loadDump(dat[2]) + else + return dat + end + end + return setmetatable({}, + { + __index = function(t, k) + return get(k) + end, + __newindex = function(t, k, v) + set(k,v) + end + } + ) +end +function threads.getConsole() + local c = {} + c.queue = lovr.thread.getChannel("__CONSOLE__") + function c.print(...) + c.queue:push{...} + end + function c.error(err) + c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} + error(err) + end + return c +end +if not ISTHREAD then + local clock = os.clock + local lastproc = clock() + local queue = lovr.thread.getChannel("__CONSOLE__") + thread:newThread("consoleManager",function() + while true do + thread.yield() + dat = queue:pop() + if dat then + lastproc = clock() + print(unpack(dat)) + end + if clock()-lastproc>2 then + thread.sleep(.1) + end + end + end) +end +function threads.createStaticTable(n) + local __proxy = {} + local function set(name,val) + if __proxy[name] then return end + local chan = lovr.thread.getChannel(n..name) + if chan:getCount()>0 then return end + chan:performAtomic(manage, val) + __proxy[name] = val + end + local function get(name) + if __proxy[name] then return __proxy[name] end + local dat = lovr.thread.getChannel(n..name):peek() + if type(dat)=="table" and dat[1]=="func" then + __proxy[name] = THREAD.loadDump(dat[2]) + return __proxy[name] + else + __proxy[name] = dat + return __proxy[name] + end + end + return setmetatable({}, + { + __index = function(t, k) + return get(k) + end, + __newindex = function(t, k, v) + set(k,v) + end + } + ) +end +function threads.hold(n) + local dat + while not(dat) do + dat = n() + end +end +return threads \ No newline at end of file diff --git a/multi/integration/networkManager/channel.lua b/multi/integration/networkManager/channel.lua new file mode 100644 index 0000000..3083241 --- /dev/null +++ b/multi/integration/networkManager/channel.lua @@ -0,0 +1,34 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +_G["__CHANNEL__"] = {} +local channel = {} +channel.__index = channel + +-- Creates/Gets a channel of name +function channel:newChannel(name) + local chan = _G["__CHANNEL__"] + if chan then + + end +end \ No newline at end of file diff --git a/multi/integration/networkManager/childNode.lua b/multi/integration/networkManager/childNode.lua new file mode 100644 index 0000000..43837d5 --- /dev/null +++ b/multi/integration/networkManager/childNode.lua @@ -0,0 +1,46 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +local multi, thread = require("multi"):init() +local cmd = require("multi.integration.networkManager.cmds") +local node = require("multi.integration.networkManager.node") +local net = require("net") +local bin = require("bin") +local child = {} +child.__index = child +function multi:newChildNode(cd) + local c = {} + setmetatable(c,child) + local name + if cd then + if cd.name then + name = cd.name + end + c.node = node:new(cd.nodePort or cmd.defaultPort,nil,name) + if cd.managerHost then + cd.managerPort = cd.managerPort or cmd.defaultManagerPort + c.node:registerWithManager(cd.managerHost,cd.managerPort) + end + end + return c +end \ No newline at end of file diff --git a/multi/integration/networkManager/clientSide.lua b/multi/integration/networkManager/clientSide.lua new file mode 100644 index 0000000..9210840 --- /dev/null +++ b/multi/integration/networkManager/clientSide.lua @@ -0,0 +1,35 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +return function(self,data) + local cmd,data = data:match("!(.-)!(.*)") + --print(">",cmd,data) + if cmd == "PONG" then + self:send("!PONG!") + elseif cmd == "CHANNEL" then + -- + elseif cmd == "RETURNS" then + local rets = bin.new(data):getBlock("t") + self.node.master.OnDataReturned:Fire(rets) + end +end \ No newline at end of file diff --git a/multi/integration/networkManager/cmds.lua b/multi/integration/networkManager/cmds.lua new file mode 100644 index 0000000..ccb3c2f --- /dev/null +++ b/multi/integration/networkManager/cmds.lua @@ -0,0 +1,42 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +local cmds = { + defaultManagerPort = 0XDE2, + defaultWait = 0X002, + defaultPort = 0X000, -- We will let the OS assign us one + standardSkip = 0X018, + ERROR = 0X000, + PING = 0X001, + PONG = 0X002, + QUEUE = 0X003, + TASK = 0X004, + INITNODE = 0X005, + INITMASTER = 0X006, + GLOBAL = 0X007, + LOAD = 0X008, + CALL = 0X009, + REG = 0X00A, + CONSOLE = 0X00B, +} +return cmds \ No newline at end of file diff --git a/multi/integration/networkManager/extensions.lua b/multi/integration/networkManager/extensions.lua new file mode 100644 index 0000000..c4cac79 --- /dev/null +++ b/multi/integration/networkManager/extensions.lua @@ -0,0 +1,23 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] \ No newline at end of file diff --git a/multi/integration/networkManager/masterNode.lua b/multi/integration/networkManager/masterNode.lua new file mode 100644 index 0000000..25c2b07 --- /dev/null +++ b/multi/integration/networkManager/masterNode.lua @@ -0,0 +1,163 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +local multi, thread = require("multi"):init() +local cmd = require("multi.integration.networkManager.cmds") +local node = require("multi.integration.networkManager.node") +local net = require("net") +local bin = require("bin") +local master = {} +master.__index = master +function master:addNode(ip,port) + return node:new(ip,port) +end +function master:getNodesFromBroadcast() + net:newCastedClients("NODE_.+") + net.OnCastedClientInfo(function(client, n, ip, port) + self.nodes[n] = node:new(client) + end) +end +function master:getNodesFromManager(ip,port) + local mn = self.nodes + if not self.manager then + self.manager = net:newTCPClient(ip,port) + if not self.manager then + error("Unable to connect to the node Manager! Is it running? Perhaps the hostname or port is incorrect!") + end + end + self.manager.OnDataRecieved(function(self,data,client) + local cmd = data:match("!(.+)!") + data = data:gsub("!"..cmd.."!","") + if cmd == "NODE" then + local n,h,p = data:match("(.-)|(.-)|(.+)") + mn[n] = node:new(h,tonumber(p)) + end + end) + self.manager:send("!NODES!") +end +function master:setDefaultNode(nodeName) + if self:nodeExists(nodeName) then + self.defaultNode = nodeName + end +end +function master:getRandomNode() + local t = {} + for i,v in pairs(self.nodes) do t[#t+1] = i end + return t[math.random(1,#t)] +end +local netID = 0 +function master:newNetworkThread(nodeName,func,...) + local args = {...} + local dat = bin.new() + local ret + local nID = netID + local conn = multi:newConnection() + thread:newthread(function() + dat:addBlock{ + args = args, + func = func, + id = netID + } + netID = netID + 1 + if type(nodeName) == "function" then + func = nodeName + nodeName = self.defaultNode or self:getRandomNode() + if not func then + error("You must provide a function!") + end + end + self:sendTo(nodeName,"!N_THREAD!"..dat.data) + self.OnDataReturned(function(rets) + if rets.ID == nID then + conn:Fire(unpack(rets.rets)) + end + end) + end) + return conn +end +function master:newNetworkChannel(nodeName) + -- +end +function master:sendTo(nodeName,data) + self:queue("send",nodeName,data) +end +function master:demandNodeExistance(nodeName) + if self.nodes[nodeName] then + return multi.hold(self.nodes[nodeName]:ping().pong) + else + return false + end +end +function master:queue(c,...) + table.insert(self._queue,{c,{...}}) +end +function multi:newMasterNode(cd) + local c = {} + setmetatable(c, master) + c.OnNodeDiscovered = multi:newConnection() + c.OnNodeRemoved = multi:newConnection() + c.OnDataRecieved = multi:newConnection() + c.OnDataReturned = multi:newConnection() + c.defaultNode = "" + c.nodes = {} + setmetatable(c.nodes, + {__newindex = function(t,k,v) + rawset(t,k,v) + v.master = c + c.OnNodeDiscovered:Fire(k,v) + end}) + c._queue = {} + if cd then + if cd.nodeHost then + cd.nodePort = cd.nodePort or cmd.defaultPort + local n,no = c:addNode(cd.nodeHost,cd.nodePort) + if n then + c.nodes[n] = no + end + elseif cd.managerHost then + cd.managerPort = cd.managerPort or cmd.defaultManagerPort + c:getNodesFromManager(cd.managerHost,cd.managerPort) + else + c:getNodesFromBroadcast() + end + else + c:getNodesFromBroadcast() + end + thread:newthread("CMDQueueProcessor",function() + while true do + thread.skip(128) + local data = table.remove(c._queue,1) + if data then + local cmd = data[1] + if cmd == "send" then + local nodeName = data[2][1] + local dat = data[2][2] + c.nodes[nodeName]:send(dat) + end + end + end + end):OnError(function(...) + print(...) + end) + return c +end \ No newline at end of file diff --git a/multi/integration/networkManager/node.lua b/multi/integration/networkManager/node.lua new file mode 100644 index 0000000..8151b57 --- /dev/null +++ b/multi/integration/networkManager/node.lua @@ -0,0 +1,127 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +local net = require("net") +local cmd = require("multi.integration.networkManager.cmds") +local multi,thread = require("multi"):init() +local node = {} +node.__index = node +local rand = {} +for i = 65,90 do + rand[#rand+1] = string.char(i) +end +local function randName(n) + local str = {} + for i=1,(n or 10) do + str[#str+1] = rand[math.random(1,#rand)] + end + return table.concat(str) +end +local getNames = thread:newFunction(function(names) + local listen = socket.udp() -- make a new socket + listen:setsockname(net.getLocalIP(), 11111) + listen:settimeout(0) + local data, ip, port = listen:receivefrom() + thread.holdWithin(1,function() + if data then + local n, tp, ip, port = data:match("(%S-)|(%S-)|(%S-):(%d+)") + if n then + names[n]=true + end + end + end) + return multi.NIL +end) +local function setName(ref,name) + if name then + ref.name = "NODE_"..name + ref.connection:broadcast(name) + return + end + local names = {} + getNames(names).wait() -- Prevents duplicate names from spawning! + local name = randName() + while names["NODE_"..name] do + name = randName() + end + ref.name = "NODE_"..name + ref.connection:broadcast(ref.name) +end +node.ServerCode = require("multi.integration.networkManager.serverSide") +node.ClientCode = require("multi.integration.networkManager.clientSide") +function node.random() + return randName(12) +end +function node:registerWithManager(ip,port) + if self.type ~= "server" then return end + if not self.manager then + self.manager = net:newTCPClient(ip,port) + if not self.manager then + error("Unable to connect to the node Manager! Is it running? Perhaps the hostname or port is incorrect!") + end + end + thread:newFunction(function() + thread.hold(function() return self.name end) + self.manager:send("!REG_NODE!"..self.name.."|"..net.getLocalIP().."|"..self.connection.port) + end)() +end +function node:new(host,port,name) + local c = {} + c.links = {} + setmetatable(c,node) + if type(host)=="number" or type(host)=="nil" then + c.connection = net:newTCPServer(host or cmd.defaultPort) + c.connection:enableBinaryMode() + c.type = "server" + c.connection.node = c + c.connection.OnDataRecieved(self.ServerCode) + setName(c) + elseif type(host)=="table" and host.Type == "tcp" then + c.connection = host + c.connection:enableBinaryMode() + c.type = "client" + c.connection.node = c + c.connection.OnDataRecieved(self.ClientCode) + c.name = "MASTER_NODE" + elseif type(host) == "string" and type(port)=="number" then + c.connection = net:newTCPClient(host, port) + c.connection:enableBinaryMode() + c.type = "client" + c.connection.node = c + c.connection.OnDataRecieved(self.ClientCode) + c.name = "MASTER_NODE" + else + error("Invalid arguments!") + end + return c +end +function node:ping() + if self.type ~= "client" then return end + self:send("!PING!") + return {pong=self.connection.OnDataRecieved} +end +function node:send(data) + if self.type ~= "client" then return end + self.connection:send(data) +end +return node \ No newline at end of file diff --git a/multi/integration/networkManager/nodeManager.lua b/multi/integration/networkManager/nodeManager.lua new file mode 100644 index 0000000..840bc60 --- /dev/null +++ b/multi/integration/networkManager/nodeManager.lua @@ -0,0 +1,48 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +local multi, thread = require("multi"):init() +local cmd = require("multi.integration.networkManager.cmds") +local net = require("net") +local bin = require("bin") +local nodes = { -- Testing stuff + +} +function multi:newNodeManager(port) + print("Running node manager on port: "..(port or cmd.defaultManagerPort)) + local server = net:newTCPServer(port or cmd.defaultManagerPort) + server.OnDataRecieved(function(serv, data, client) + local cmd = data:match("!(.+)!") + data = data:gsub("!"..cmd.."!","") + if cmd == "NODES" then + for i,v in ipairs(nodes) do + -- Sample data + serv:send(client, "!NODE!".. v[1].."|"..v[2].."|"..v[3]) + end + elseif cmd == "REG_NODE" then + local name, ip, port = data:match("(.-)|(.-)|(.+)") + table.insert(nodes,{name,ip,port}) + print("Registering Node:",name, ip, port) + end + end) +end \ No newline at end of file diff --git a/multi/integration/networkManager/serverSide.lua b/multi/integration/networkManager/serverSide.lua new file mode 100644 index 0000000..4d8b771 --- /dev/null +++ b/multi/integration/networkManager/serverSide.lua @@ -0,0 +1,47 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +local bin, bits = require("bin").init() +return function(self,data,client) + local cmd,data = data:match("!(.-)!(.*)") + --print("SERVER",cmd,data) + if cmd == "PING" then + self:send(client,"!PONG!") + elseif cmd == "N_THREAD" then + print(1) + local dat = bin.new(data) + print(2) + local t = dat:getBlock("t") + print(3) + local ret = bin.new() + print(4) + ret:addBlock{ID = t.id,rets = {t.func(unpack(t.args))}} + print(5) + print(client,"!RETURNS!"..ret:getData()) + self:send(client,"!RETURNS!"..ret:getData()) + print(6) + elseif cmd == "CHANNEL" then + local dat = bin.new(data):getBlock("t") + + end +end \ No newline at end of file diff --git a/multi/integration/networkManager/threads.lua b/multi/integration/networkManager/threads.lua new file mode 100644 index 0000000..c4cac79 --- /dev/null +++ b/multi/integration/networkManager/threads.lua @@ -0,0 +1,23 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] \ No newline at end of file diff --git a/multi/integration/networkManager/utils.lua b/multi/integration/networkManager/utils.lua new file mode 100644 index 0000000..9ebdb1b --- /dev/null +++ b/multi/integration/networkManager/utils.lua @@ -0,0 +1,27 @@ +--[[ +MIT License + +Copyright (c) 2022 Ryan Ward + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +local bin = require("bin") +local utils = {} +-- Will contain data that handles sterilizing and managing data +return utils \ No newline at end of file diff --git a/test.lua b/test.lua index aacb05f..e4bebde 100644 --- a/test.lua +++ b/test.lua @@ -1,76 +1,97 @@ package.path = "./?/init.lua;?.lua;lua5.4/share/lua/5.4/?/init.lua;lua5.4/share/lua/5.4/?.lua;"--..package.path package.cpath = "lua5.4/lib/lua/5.4/?/core.dll;"--..package.cpath multi, thread = require("multi"):init{print=true} -GLOBAL, THREAD = require("multi.integration.lanesManager"):init() +-- GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -local conn = multi:newSystemThreadedConnection("conn"):init() - -multi:newSystemThread("Thread_Test_1",function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local console = THREAD.getConsole() - conn(function(a,b,c) - console.print(THREAD:getName().." was triggered!",a,b,c) - end) - multi:mainloop() +local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection(), multi:newConnection() +thread:newThread(function() + print("Awaiting status") + thread.hold(conn1 + (conn2 * conn3)) + print("Conn or Conn2 and Conn3") end) -multi:newSystemThread("Thread_Test_2",function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local console = THREAD.getConsole() - conn(function(a,b,c) - console.print(THREAD:getName().." was triggered!",a,b,c) - end) - multi:newAlarm(2):OnRing(function() - console.print("Fire 2!!!") - conn:Fire(4,5,6) - THREAD.kill() - end) - - multi:mainloop() +multi:newAlarm(1):OnRing(function() + print("Conn") + conn1:Fire() end) -local console = THREAD.getConsole() -conn(function(a,b,c) - console.print("Mainloop conn got triggered!",a,b,c) +multi:newAlarm(2):OnRing(function() + print("Conn2") + conn2:Fire() +end) +multi:newAlarm(3):OnRing(function() + print("Conn3") + conn3:Fire() end) -alarm = multi:newAlarm(1) -alarm:OnRing(function() - console.print("Fire 1!!!") - conn:Fire(1,2,3) -end) -alarm = multi:newAlarm(3):OnRing(function() - multi:newSystemThread("Thread_Test_3",function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local console = THREAD.getConsole() - conn(function(a,b,c) - console.print(THREAD:getName().." was triggered!",a,b,c) - end) - multi:newAlarm(4):OnRing(function() - console.print("Fire 3!!!") - conn:Fire(7,8,9) - end) - multi:mainloop() - end) -end) +-- local conn = multi:newSystemThreadedConnection("conn"):init() -multi:newSystemThread("Thread_Test_4",function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local conn2 = multi:newConnection() - local console = THREAD.getConsole() - multi:newAlarm(2):OnRing(function() - conn2:Fire() - end) - multi:newThread(function() - console.print("Conn Test!") - thread.hold(conn + conn2) - console.print("It held!") - end) - multi:mainloop() -end) +-- multi:newSystemThread("Thread_Test_1",function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:mainloop() +-- end) + +-- multi:newSystemThread("Thread_Test_2",function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:newAlarm(2):OnRing(function() +-- console.print("Fire 2!!!") +-- conn:Fire(4,5,6) +-- THREAD.kill() +-- end) + +-- multi:mainloop() +-- end) +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print("Mainloop conn got triggered!",a,b,c) +-- end) + +-- alarm = multi:newAlarm(1) +-- alarm:OnRing(function() +-- console.print("Fire 1!!!") +-- conn:Fire(1,2,3) +-- end) + +-- alarm = multi:newAlarm(3):OnRing(function() +-- multi:newSystemThread("Thread_Test_3",function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:newAlarm(4):OnRing(function() +-- console.print("Fire 3!!!") +-- conn:Fire(7,8,9) +-- end) +-- multi:mainloop() +-- end) +-- end) + +-- multi:newSystemThread("Thread_Test_4",function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local conn2 = multi:newConnection() +-- local console = THREAD.getConsole() +-- multi:newAlarm(2):OnRing(function() +-- conn2:Fire() +-- end) +-- multi:newThread(function() +-- console.print("Conn Test!") +-- thread.hold(conn + conn2) +-- console.print("It held!") +-- end) +-- multi:mainloop() +-- end) multi:mainloop() \ No newline at end of file From 7353fde7998b3620de0ff4c8373bc2a19af34e69 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 6 Oct 2022 18:36:45 -0400 Subject: [PATCH 21/40] Working on some nice features --- changes.md | 5 ++++- multi/init.lua | 54 ++++++++++++++++++++++++++------------------------ test.lua | 38 ++++++++++++++++++----------------- 3 files changed, 52 insertions(+), 45 deletions(-) diff --git a/changes.md b/changes.md index fa53381..edcf885 100644 --- a/changes.md +++ b/changes.md @@ -97,8 +97,10 @@ Added Changed --- +- All actors now use fastmode on connections +- Performance enhancement with processes that are pumped instead of automatically running, by suppressing the creation of an internal loop object that would manage the process +- `Connection:fastMode() or Connection:SetHelper()` now returns a reference to itself - `Connection:[connect, hasConnections, getConnection]` changed to be `Connection:[Connect, HasConnections, getConnections]`. This was done in an attempt to follow a consistent naming scheme. The old methods still will work to prevent old code breaking. - - `Connections when added(+) together now act like 'or', to get the 'and' feature multiply(*) them together.` **Note:** This is a potentially breaking change for using connections. @@ -139,6 +141,7 @@ Removed Fixed --- - SystemThreaded Objects variables weren't consistent. +- Issue with connections being multiplied only being able to have a combined fire once ToDo --- diff --git a/multi/init.lua b/multi/init.lua index fe73b0e..4b7aab6 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -160,12 +160,14 @@ function multi:newConnection(protect,func,kill) cn.__count = cn.__count + 1 if cn.__count == cn.__hasInstances then cn:Fire(...) + cn.__count = 0 end end) c2(function(...) cn.__count = cn.__count + 1 if cn.__count == cn.__hasInstances then cn:Fire(...) + cn.__count = 0 end end) return cn @@ -237,6 +239,7 @@ function multi:newConnection(protect,func,kill) function self:Connect(func) table.insert(fast,func) end + return self end function c:Bind(t) @@ -335,6 +338,7 @@ function multi:newConnection(protect,func,kill) function c:SetHelper(func) conn_helper = func + return self end c.connect=c.Connect @@ -584,7 +588,7 @@ function multi:newEvent(task) task=func return self end - c.OnEvent = self:newConnection() + c.OnEvent = self:newConnection():fastMode() self:setPriority("core") c:SetName(c.Type) multi:create(c) @@ -607,7 +611,7 @@ function multi:newUpdater(skip) skip=n return self end - c.OnUpdate = self:newConnection() + c.OnUpdate = self:newConnection():fastMode() c:SetName(c.Type) multi:create(c) return c @@ -639,7 +643,7 @@ function multi:newAlarm(set) t = clock() return self end - c.OnRing = self:newConnection() + c.OnRing = self:newConnection():fastMode() function c:Pause() count = clock() self.Parent.Pause(self) @@ -663,15 +667,12 @@ function multi:newLoop(func,notime) self.OnLoop:Fire(self,clock()-start) end end - c.OnLoop = self:newConnection() - function c:fastMode() - self.OnLoop:fastMode() - end + c.OnLoop = self:newConnection():fastMode() if func then c.OnLoop(func) end - + multi:create(c) c:SetName(c.Type) return c @@ -713,9 +714,9 @@ function multi:newStep(start,reset,count,skip) end end c.Reset=c.Resume - c.OnStart = self:newConnection() - c.OnStep = self:newConnection() - c.OnEnd = self:newConnection() + c.OnStart = self:newConnection():fastMode() + c.OnStep = self:newConnection():fastMode() + c.OnEnd = self:newConnection():fastMode() function c:Break() self.Active=nil return self @@ -763,7 +764,7 @@ function multi:newTLoop(func,set) self.Parent.Pause(self) return self end - c.OnLoop = self:newConnection() + c.OnLoop = self:newConnection():fastMode() if func then c.OnLoop(func) end @@ -883,25 +884,28 @@ function multi:newProcessor(name,nothread) c.Type = "process" local Active = nothread or false c.Name = name or "" - c.pump = false c.threads = {} c.startme = {} c.parent = self local handler = c:createHandler(c.threads,c.startme) - c.process = self:newLoop(function() - if Active then - c:uManager() - handler() - end - end) + if not nothread then -- Don't create a loop if we are triggering this manually + c.process = self:newLoop(function() + if Active then + c:uManager() + handler() + end + end) + c.process.__ignore = true + c.process.isProcessThread = true + c.process.PID = sandcount + c.OnError = c.process.OnError + else + c.OnError = multi:newConnection() + end + - c.process.__ignore = true - - c.process.isProcessThread = true - c.process.PID = sandcount - c.OnError = c.process.OnError function c:getThreads() return c.threads @@ -930,10 +934,8 @@ function multi:newProcessor(name,nothread) function c.run() if not Active then return end - c.pump = true c:uManager() handler() - c.pump = false return c end diff --git a/test.lua b/test.lua index e4bebde..f9e93e6 100644 --- a/test.lua +++ b/test.lua @@ -3,25 +3,27 @@ package.cpath = "lua5.4/lib/lua/5.4/?/core.dll;"--..package.cpath multi, thread = require("multi"):init{print=true} -- GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection(), multi:newConnection() -thread:newThread(function() - print("Awaiting status") - thread.hold(conn1 + (conn2 * conn3)) - print("Conn or Conn2 and Conn3") -end) +-- local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection(), multi:newConnection() +-- thread:newThread(function() +-- print("Awaiting status") +-- thread.hold(conn1 + (conn2 * conn3)) +-- print("Conn or Conn2 and Conn3") +-- end) + +-- multi:newAlarm(1):OnRing(function() +-- print("Conn") +-- conn1:Fire() +-- end) +-- multi:newAlarm(2):OnRing(function() +-- print("Conn2") +-- conn2:Fire() +-- end) +-- multi:newAlarm(3):OnRing(function() +-- print("Conn3") +-- conn3:Fire() +-- end) + -multi:newAlarm(1):OnRing(function() - print("Conn") - conn1:Fire() -end) -multi:newAlarm(2):OnRing(function() - print("Conn2") - conn2:Fire() -end) -multi:newAlarm(3):OnRing(function() - print("Conn3") - conn3:Fire() -end) -- local conn = multi:newSystemThreadedConnection("conn"):init() From e5ac4d3d9ef37440d563b6f80610eafa157462d3 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 24 Oct 2022 23:24:24 -0400 Subject: [PATCH 22/40] Testing new optimization features --- multi/init.lua | 164 +++++++++++++++++- multi/integration/lanesManager/extensions.lua | 7 +- multi/integration/lanesManager/init.lua | 2 + test.lua | 135 +++++++------- 4 files changed, 230 insertions(+), 78 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 4b7aab6..bab2b39 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -29,6 +29,7 @@ local clock = os.clock local thread = {} local in_proc = false local processes = {} +local find_optimization = false if not _G["$multi"] then _G["$multi"] = {multi=multi,thread=thread} @@ -113,7 +114,11 @@ end function multi.ForEach(tab,func) for i=1,#tab do func(tab[i]) end end +local CRef = { + Fire = function() end +} +local optimization_stats = {} local ignoreconn = true function multi:newConnection(protect,func,kill) local c={} @@ -128,8 +133,11 @@ function multi:newConnection(protect,func,kill) for i,v in pairs(t) do if v==self then local ref = self:Connect(select(2,...)) - ref.root_link = select(1,...) - return ref + if ref then + ref.root_link = select(1,...) + return ref + end + return self end end return self:Connect(...) @@ -207,7 +215,10 @@ function multi:newConnection(protect,func,kill) if lock then return end for i=#call_funcs,1,-1 do if not call_funcs[i] then return end - pcall(call_funcs[i],...) + local suc, err = pcall(call_funcs[i],...) + if not suc then + print(err) + end if kill then table.remove(call_funcs,i) end @@ -231,6 +242,7 @@ function multi:newConnection(protect,func,kill) end function c:fastMode() + if find_optimization then return self end function self:Fire(...) for i=1,#fast do fast[i](...) @@ -341,16 +353,39 @@ function multi:newConnection(protect,func,kill) return self end + if find_optimization then + -- + end + c.connect=c.Connect c.GetConnection=c.getConnection c.HasConnections = c.hasConnections c.GetConnection = c.getConnection + if not(ignoreconn) then multi:create(c) end return c end +multi.enableOptimization = multi:newConnection() +multi.optConn = multi:newConnection(true) +multi.optConn(function(msg) + table.insert(optimization_stats, msg) +end) + +function multi:getOptimizationConnection() + return multi.optConn +end + +function multi:getOptimizationStats() + return optimization_stats +end + +function multi:isFindingOptimizing() + return find_optimization +end + -- Used with ISO Threads local function isolateFunction(func,env) local dmp = string.dump(func) @@ -531,10 +566,6 @@ function multi:newConnector() return c end -local CRef = { - Fire = function() end -} - multi.OnObjectCreated=multi:newConnection() multi.OnObjectDestroyed=multi:newConnection() multi.OnLoad = multi:newConnection(nil,nil,true) @@ -1093,6 +1124,62 @@ function thread.hold(n,opt) end end +function thread.hold(n,opt) + thread._Requests() + local opt = opt or {} + if type(opt)=="table" then + interval = opt.interval + if opt.cycles then + return yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) + elseif opt.sleep then + return yield(CMD, t_holdF, opt.sleep, n or dFunc, interval) + elseif opt.skip then + return yield(CMD, t_skip, opt.skip or 1, nil, interval) + end + end + + if type(n) == "number" then + thread.getRunningThread().lastSleep = clock() + return yield(CMD, t_sleep, n or 0, nil, interval) + elseif type(n) == "table" and n.Type == "connector" then + local rdy = function() + return false + end + n(function(a1,a2,a3,a4,a5,a6) + rdy = function() + if a1==nil then + return NIL,a2,a3,a4,a5,a6 + end + return a1,a2,a3,a4,a5,a6 + end + end) + return yield(CMD, t_hold, function() + return rdy() + end, nil, interval) + elseif type(n) == "function" then + if find_optimization then + local cache = string.dump(n) + local f_str = tostring(n) + local good = true + for i=1,#func_cache do + if func_cache[i][1] == cache and func_cache[i][2] ~= f_str then + if not func_cache[i][3] then + multi.optConn:Fire("It's better to store a function to a variable than to use an anonymous function within the hold method!\n" .. debug.traceback()) + func_cache[i][3] = true + end + good = false + end + end + if good then + table.insert(func_cache, {cache, f_str}) + end + end + return yield(CMD, t_hold, n or dFunc, nil, interval) + else + error("Invalid argument passed to thread.hold(...)!") + end +end + function thread.holdFor(sec,n) thread._Requests() return yield(CMD, t_holdF, sec, n or dFunc) @@ -1744,6 +1831,10 @@ function multi.init(settings, realsettings) else multi.mainloop = mainloop end + if settings.findopt then + find_optimization = true + multi.enableOptimization:Fire(multi, thread) + end end return _G["$multi"].multi,_G["$multi"].thread end @@ -2093,5 +2184,64 @@ else multi.m.sentinel = newproxy(true) getmetatable(multi.m.sentinel).__gc = multi.m.onexit end +local func_cache = {} +multi:newThread(function() + thread.skip() + if find_optimization then + + function thread.hold(n,opt) + thread._Requests() + local opt = opt or {} + if type(opt)=="table" then + interval = opt.interval + if opt.cycles then + return yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) + elseif opt.sleep then + return yield(CMD, t_holdF, opt.sleep, n or dFunc, interval) + elseif opt.skip then + return yield(CMD, t_skip, opt.skip or 1, nil, interval) + end + end + + if type(n) == "number" then + thread.getRunningThread().lastSleep = clock() + return yield(CMD, t_sleep, n or 0, nil, interval) + elseif type(n) == "table" and n.Type == "connector" then + local rdy = function() + return false + end + n(function(a1,a2,a3,a4,a5,a6) + rdy = function() + if a1==nil then + return NIL,a2,a3,a4,a5,a6 + end + return a1,a2,a3,a4,a5,a6 + end + end) + return yield(CMD, t_hold, function() + return rdy() + end, nil, interval) + elseif type(n) == "function" then + local cache = string.dump(n) + local f_str = tostring(n) + local good = true + for i=1,#func_cache do + if func_cache[i][1] == cache and func_cache[i][2] ~= f_str and not func_cache[i][3] then + multi:getOptimizationConnection():Fire("It's better to store a function to a variable than to use an anonymous function within the hold method!\n" .. debug.traceback()) + func_cache[i][3] = true + good = false + end + end + if good then + table.insert(func_cache, {cache, f_str}) + end + return yield(CMD, t_hold, n or dFunc, nil, interval) + else + error("Invalid argument passed to thread.hold(...)!") + end + end + -- Add more Overrides + end +end).OnError(print) return multi \ No newline at end of file diff --git a/multi/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua index fdf9f9a..4fc7b93 100644 --- a/multi/integration/lanesManager/extensions.lua +++ b/multi/integration/lanesManager/extensions.lua @@ -239,13 +239,14 @@ function multi:newSystemThreadedConnection(name) thread:newThread("STC_SUB_MAN"..name,function() local item + local sub_func = function() -- This will keep things held up until there is something to process + return c.subscribe:pop() + end while true do thread.yield() -- We need to check on broken connections ping(c) -- Should return instantlly and process this in another thread - item = thread.hold(function() -- This will keep things held up until there is something to process - return c.subscribe:pop() - end) + item = thread.hold(sub_func) if item[1] == c.CONN then multi.ForEach(c.links, function(link) -- Sync new connections item[2]:push{c.CONN, link} diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 26ac65c..b5a772b 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -77,6 +77,7 @@ function multi:newSystemThread(name, func, ...) c.creationTime = os.clock() c.alive = true c.priority = THREAD.Priority_Normal + local multi_settings = multi.defaultSettings c.thread = lanes.gen("*", { globals={ -- Set up some globals @@ -88,6 +89,7 @@ function multi:newSystemThread(name, func, ...) }, priority=c.priority },function(...) + require("multi"):init(multi_settings) require("multi.integration.lanesManager.extensions") local has_error = true return_linda:set("returns",{func(...)}) diff --git a/test.lua b/test.lua index f9e93e6..663a4c8 100644 --- a/test.lua +++ b/test.lua @@ -1,8 +1,10 @@ package.path = "./?/init.lua;?.lua;lua5.4/share/lua/5.4/?/init.lua;lua5.4/share/lua/5.4/?.lua;"--..package.path package.cpath = "lua5.4/lib/lua/5.4/?/core.dll;"--..package.cpath multi, thread = require("multi"):init{print=true} --- GLOBAL, THREAD = require("multi.integration.lanesManager"):init() - +GLOBAL, THREAD = require("multi.integration.lanesManager"):init() +multi:getOptimizationConnection()(function(msg) + print(msg) +end) -- local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection(), multi:newConnection() -- thread:newThread(function() -- print("Awaiting status") @@ -23,77 +25,74 @@ multi, thread = require("multi"):init{print=true} -- conn3:Fire() -- end) +local conn = multi:newSystemThreadedConnection("conn"):init() +multi:newSystemThread("Thread_Test_1", function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local console = THREAD.getConsole() + conn(function(a,b,c) + console.print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:mainloop() +end) +multi:newSystemThread("Thread_Test_2", function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local console = THREAD.getConsole() + conn(function(a,b,c) + console.print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(2):OnRing(function() + console.print("Fire 2!!!") + conn:Fire(4,5,6) + THREAD.kill() + end) --- local conn = multi:newSystemThreadedConnection("conn"):init() + multi:mainloop() +end) +local console = THREAD.getConsole() +conn(function(a,b,c) + console.print("Mainloop conn got triggered!",a,b,c) +end) --- multi:newSystemThread("Thread_Test_1",function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print(THREAD:getName().." was triggered!",a,b,c) --- end) --- multi:mainloop() --- end) +alarm = multi:newAlarm(1) +alarm:OnRing(function() + console.print("Fire 1!!!") + conn:Fire(1,2,3) +end) --- multi:newSystemThread("Thread_Test_2",function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print(THREAD:getName().." was triggered!",a,b,c) --- end) --- multi:newAlarm(2):OnRing(function() --- console.print("Fire 2!!!") --- conn:Fire(4,5,6) --- THREAD.kill() --- end) +alarm = multi:newAlarm(3):OnRing(function() + multi:newSystemThread("Thread_Test_3",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local console = THREAD.getConsole() + conn(function(a,b,c) + console.print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(4):OnRing(function() + console.print("Fire 3!!!") + conn:Fire(7,8,9) + end) + multi:mainloop() + end) +end) --- multi:mainloop() --- end) --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print("Mainloop conn got triggered!",a,b,c) --- end) - --- alarm = multi:newAlarm(1) --- alarm:OnRing(function() --- console.print("Fire 1!!!") --- conn:Fire(1,2,3) --- end) - --- alarm = multi:newAlarm(3):OnRing(function() --- multi:newSystemThread("Thread_Test_3",function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print(THREAD:getName().." was triggered!",a,b,c) --- end) --- multi:newAlarm(4):OnRing(function() --- console.print("Fire 3!!!") --- conn:Fire(7,8,9) --- end) --- multi:mainloop() --- end) --- end) - --- multi:newSystemThread("Thread_Test_4",function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local conn2 = multi:newConnection() --- local console = THREAD.getConsole() --- multi:newAlarm(2):OnRing(function() --- conn2:Fire() --- end) --- multi:newThread(function() --- console.print("Conn Test!") --- thread.hold(conn + conn2) --- console.print("It held!") --- end) --- multi:mainloop() --- end) +multi:newSystemThread("Thread_Test_4",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local conn2 = multi:newConnection() + local console = THREAD.getConsole() + multi:newAlarm(2):OnRing(function() + conn2:Fire() + end) + multi:newThread(function() + console.print("Conn Test!") + thread.hold(conn + conn2) + console.print("It held!") + end) + multi:mainloop() +end) multi:mainloop() \ No newline at end of file From 6abfb9c3d9a7214ebcc8836cb3b2389334fe4fbd Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 30 Oct 2022 23:54:56 -0400 Subject: [PATCH 23/40] Fixed issue with opt_finding thread.hold --- makeENV.lua | 2 +- multi/init.lua | 22 +++---------------- multi/integration/lanesManager/extensions.lua | 2 +- multi/integration/lanesManager/init.lua | 3 +++ test.lua | 2 +- 5 files changed, 9 insertions(+), 22 deletions(-) diff --git a/makeENV.lua b/makeENV.lua index d312349..0b38933 100644 --- a/makeENV.lua +++ b/makeENV.lua @@ -1,4 +1,4 @@ -commands = [[ +lcommands = [[ mkdir luajit && python -m hererocks -j 2.1.0-beta3 -r latest --patch --compat all ./luajit && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes mkdir lua5.1 && python -m hererocks -l 5.1 -r latest --patch --compat all ./lua5.1 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes mkdir lua5.2 && python -m hererocks -l 5.2 -r latest --patch --compat all ./lua5.2 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes diff --git a/multi/init.lua b/multi/init.lua index bab2b39..4750fbd 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -935,7 +935,7 @@ function multi:newProcessor(name,nothread) else c.OnError = multi:newConnection() end - + c.OnError(multi.print) function c:getThreads() @@ -1157,23 +1157,6 @@ function thread.hold(n,opt) return rdy() end, nil, interval) elseif type(n) == "function" then - if find_optimization then - local cache = string.dump(n) - local f_str = tostring(n) - local good = true - for i=1,#func_cache do - if func_cache[i][1] == cache and func_cache[i][2] ~= f_str then - if not func_cache[i][3] then - multi.optConn:Fire("It's better to store a function to a variable than to use an anonymous function within the hold method!\n" .. debug.traceback()) - func_cache[i][3] = true - end - good = false - end - end - if good then - table.insert(func_cache, {cache, f_str}) - end - end return yield(CMD, t_hold, n or dFunc, nil, interval) else error("Invalid argument passed to thread.hold(...)!") @@ -1371,6 +1354,7 @@ function thread:newThread(name,func,...) c.isError = false c.OnError = multi:newConnection(true,nil,true) c.OnDeath = multi:newConnection(true,nil,true) + c.OnError(multi.print) function c:getName() return c.Name @@ -2242,6 +2226,6 @@ multi:newThread(function() end -- Add more Overrides end -end).OnError(print) +end) return multi \ No newline at end of file diff --git a/multi/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua index 4fc7b93..9600dc6 100644 --- a/multi/integration/lanesManager/extensions.lua +++ b/multi/integration/lanesManager/extensions.lua @@ -223,7 +223,7 @@ function multi:newSystemThreadedConnection(name) ping:Pause() multi.ForEach(self.links, function(link) -- Sync new connections link:push{self.PING} - multi:newThread("pong Thread", pong, link, links) + multi:newThread("pong Thread", pong, link, self.links) end) thread.sleep(3) diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index b5a772b..d53e12f 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -78,6 +78,9 @@ function multi:newSystemThread(name, func, ...) c.alive = true c.priority = THREAD.Priority_Normal local multi_settings = multi.defaultSettings + for i,v in pairs(multi_settings) do + print(i,v) + end c.thread = lanes.gen("*", { globals={ -- Set up some globals diff --git a/test.lua b/test.lua index 663a4c8..bf5a843 100644 --- a/test.lua +++ b/test.lua @@ -1,6 +1,6 @@ package.path = "./?/init.lua;?.lua;lua5.4/share/lua/5.4/?/init.lua;lua5.4/share/lua/5.4/?.lua;"--..package.path package.cpath = "lua5.4/lib/lua/5.4/?/core.dll;"--..package.cpath -multi, thread = require("multi"):init{print=true} +multi, thread = require("multi"):init{print=true,findopt=true} GLOBAL, THREAD = require("multi.integration.lanesManager"):init() multi:getOptimizationConnection()(function(msg) print(msg) From f240588eddd4685569e8916cd63e29e0d609d441 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 31 Oct 2022 00:09:18 -0400 Subject: [PATCH 24/40] Added changes to changelog --- changes.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/changes.md b/changes.md index edcf885..d9e6631 100644 --- a/changes.md +++ b/changes.md @@ -1,10 +1,11 @@ # Changelog Table of contents --- +[Update 15.3.0 - A world of connections](#update-1530---a-world-of-connections)
[Update 15.2.1 - Bug fix](#update-1521---bug-fix)
[Update 15.2.0 - Upgrade Complete](#update-1520---upgrade-complete)
[Update 15.1.0 - Hold the thread!](#update-1510---hold-the-thread)
[Update 15.0.0 - The art of faking it](#update-1500---the-art-of-faking-it)
[Update 14.2.0 - Bloatware Removed](#update-1420---bloatware-removed)
[Update 14.1.0 - A whole new world of possibilities](#update-1410---a-whole-new-world-of-possibilities)
[Update 14.0.0 - Consistency, Additions and Stability](#update-1400---consistency-additions-and-stability)
[Update 13.1.0 - Bug fixes and features added](#update-1310---bug-fixes-and-features-added)
[Update 13.0.0 - Added some documentation, and some new features too check it out!](#update-1300---added-some-documentation-and-some-new-features-too-check-it-out)
[Update 12.2.2 - Time for some more bug fixes!](#update-1222---time-for-some-more-bug-fixes)
[Update 12.2.1 - Time for some bug fixes!](#update-1221---time-for-some-bug-fixes)
[Update 12.2.0 - The chains of binding](#update-1220---the-chains-of-binding)
[Update 12.1.0 - Threads just can't hold on anymore](#update-1210---threads-just-cant-hold-on-anymore)
[Update: 12.0.0 - Big update (Lots of additions some changes)](#update-1200---big-update-lots-of-additions-some-changes)
[Update: 1.11.1 - Small Clarification on Love](#update-1111---small-clarification-on-love)
[Update: 1.11.0](#update-1110)
[Update: 1.10.0](#update-1100)
[Update: 1.9.2](#update-192)
[Update: 1.9.1 - Threads can now argue](#update-191---threads-can-now-argue)
[Update: 1.9.0](#update-190)
[Update: 1.8.7](#update-187)
[Update: 1.8.6](#update-186)
[Update: 1.8.5](#update-185)
[Update: 1.8.4](#update-184)
[Update: 1.8.3 - Mainloop recieves some needed overhauling](#update-183---mainloop-recieves-some-needed-overhauling)
[Update: 1.8.2](#update-182)
[Update: 1.8.1](#update-181)
[Update: 1.7.6](#update-176)
[Update: 1.7.5](#update-175)
[Update: 1.7.4](#update-174)
[Update: 1.7.3](#update-173)
[Update: 1.7.2](#update-172)
[Update: 1.7.1 - Bug Fixes Only](#update-171---bug-fixes-only)
[Update: 1.7.0 - Threading the systems](#update-170---threading-the-systems)
[Update: 1.6.0](#update-160)
[Update: 1.5.0](#update-150)
[Update: 1.4.1 (4/10/2017) - First Public release of the library](#update-141-4102017---first-public-release-of-the-library)
[Update: 1.4.0 (3/20/2017)](#update-140-3202017)
[Update: 1.3.0 (1/29/2017)](#update-130-1292017)
[Update: 1.2.0 (12.31.2016)](#update-120-12312016)
[Update: 1.1.0](#update-110)
[Update: 1.0.0](#update-100)
[Update: 0.6.3](#update-063)
[Update: 0.6.2](#update-062)
[Update: 0.6.1-6](#update-061-6)
[Update: 0.5.1-6](#update-051-6)
[Update: 0.4.1](#update-041)
[Update: 0.3.0 - The update that started it all](#update-030---the-update-that-started-it-all)
[Update: EventManager 2.0.0](#update-eventmanager-200)
[Update: EventManager 1.2.0](#update-eventmanager-120)
[Update: EventManager 1.1.0](#update-eventmanager-110)
[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 15.3.0 - A world of connection +# Update 15.3.0 - A world of Connections Full Update Showcase ```lua @@ -82,6 +83,7 @@ multi:mainloop() Added --- +- Experimental option to multi settings: `findopt`. When set to `true` it will print out a message when certain pattern are used with this library. For example if an anonymous function is used in thread.hold() within a loop. The library will trigger a message alerting you that this isn't the most performant way to use thread.hold(). - `multi:newSystemThreadedConnection()` Allows one to trigger connection events across threads. Works like how any connection would work. Supports all of the features, can even be `added` with non SystemThreadedConnections as demonstrated in the full showcase. @@ -97,6 +99,7 @@ Added Changed --- +- Internally all `OnError` events are now connected to with multi.print, you must pass `print=true` to the init settings when initializing the multi object. `require("multi"):init{print=true}` - All actors now use fastmode on connections - Performance enhancement with processes that are pumped instead of automatically running, by suppressing the creation of an internal loop object that would manage the process - `Connection:fastMode() or Connection:SetHelper()` now returns a reference to itself From a5b59ee601cdf73200f17ae32e64c37ff8b75509 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 5 Nov 2022 22:21:19 -0400 Subject: [PATCH 25/40] testing --- Documentation.md => docs/Documentation.md | 0 changes.md => docs/changes.md | 0 multi/init.lua => init.lua | 0 .../lanesManager/extensions.lua | 0 .../lanesManager/init.lua | 0 .../lanesManager/threads.lua | 0 .../loveManager/extensions.lua | 0 .../loveManager/init.lua | 0 .../loveManager/threads.lua | 0 .../lovrManager/extensions.lua | 0 .../lovrManager/init.lua | 0 .../lovrManager/threads.lua | 0 .../luvitManager.lua | 0 .../networkManager/channel.lua | 0 .../networkManager/childNode.lua | 0 .../networkManager/clientSide.lua | 0 .../networkManager/cmds.lua | 0 .../networkManager/extensions.lua | 0 .../networkManager/init.lua | 0 .../networkManager/masterNode.lua | 0 .../networkManager/node.lua | 0 .../networkManager/nodeManager.lua | 0 .../networkManager/serverSide.lua | 0 .../networkManager/threads.lua | 0 .../networkManager/utils.lua | 0 .../pesudoManager/extensions.lua | 0 .../pesudoManager/init.lua | 0 .../pesudoManager/threads.lua | 0 .../integration => integration}/threading.lua | 0 makeENV.lua | 19 ------------ makeENV.sh | 31 ------------------- test.lua => tests/test.lua | 1 + test2.lua => tests/test2.lua | 0 33 files changed, 1 insertion(+), 50 deletions(-) rename Documentation.md => docs/Documentation.md (100%) rename changes.md => docs/changes.md (100%) rename multi/init.lua => init.lua (100%) rename {multi/integration => integration}/lanesManager/extensions.lua (100%) rename {multi/integration => integration}/lanesManager/init.lua (100%) rename {multi/integration => integration}/lanesManager/threads.lua (100%) rename {multi/integration => integration}/loveManager/extensions.lua (100%) rename {multi/integration => integration}/loveManager/init.lua (100%) rename {multi/integration => integration}/loveManager/threads.lua (100%) rename {multi/integration => integration}/lovrManager/extensions.lua (100%) rename {multi/integration => integration}/lovrManager/init.lua (100%) rename {multi/integration => integration}/lovrManager/threads.lua (100%) rename {multi/integration => integration}/luvitManager.lua (100%) rename {multi/integration => integration}/networkManager/channel.lua (100%) rename {multi/integration => integration}/networkManager/childNode.lua (100%) rename {multi/integration => integration}/networkManager/clientSide.lua (100%) rename {multi/integration => integration}/networkManager/cmds.lua (100%) rename {multi/integration => integration}/networkManager/extensions.lua (100%) rename {multi/integration => integration}/networkManager/init.lua (100%) rename {multi/integration => integration}/networkManager/masterNode.lua (100%) rename {multi/integration => integration}/networkManager/node.lua (100%) rename {multi/integration => integration}/networkManager/nodeManager.lua (100%) rename {multi/integration => integration}/networkManager/serverSide.lua (100%) rename {multi/integration => integration}/networkManager/threads.lua (100%) rename {multi/integration => integration}/networkManager/utils.lua (100%) rename {multi/integration => integration}/pesudoManager/extensions.lua (100%) rename {multi/integration => integration}/pesudoManager/init.lua (100%) rename {multi/integration => integration}/pesudoManager/threads.lua (100%) rename {multi/integration => integration}/threading.lua (100%) delete mode 100644 makeENV.lua delete mode 100755 makeENV.sh rename test.lua => tests/test.lua (99%) rename test2.lua => tests/test2.lua (100%) diff --git a/Documentation.md b/docs/Documentation.md similarity index 100% rename from Documentation.md rename to docs/Documentation.md diff --git a/changes.md b/docs/changes.md similarity index 100% rename from changes.md rename to docs/changes.md diff --git a/multi/init.lua b/init.lua similarity index 100% rename from multi/init.lua rename to init.lua diff --git a/multi/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua similarity index 100% rename from multi/integration/lanesManager/extensions.lua rename to integration/lanesManager/extensions.lua diff --git a/multi/integration/lanesManager/init.lua b/integration/lanesManager/init.lua similarity index 100% rename from multi/integration/lanesManager/init.lua rename to integration/lanesManager/init.lua diff --git a/multi/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua similarity index 100% rename from multi/integration/lanesManager/threads.lua rename to integration/lanesManager/threads.lua diff --git a/multi/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua similarity index 100% rename from multi/integration/loveManager/extensions.lua rename to integration/loveManager/extensions.lua diff --git a/multi/integration/loveManager/init.lua b/integration/loveManager/init.lua similarity index 100% rename from multi/integration/loveManager/init.lua rename to integration/loveManager/init.lua diff --git a/multi/integration/loveManager/threads.lua b/integration/loveManager/threads.lua similarity index 100% rename from multi/integration/loveManager/threads.lua rename to integration/loveManager/threads.lua diff --git a/multi/integration/lovrManager/extensions.lua b/integration/lovrManager/extensions.lua similarity index 100% rename from multi/integration/lovrManager/extensions.lua rename to integration/lovrManager/extensions.lua diff --git a/multi/integration/lovrManager/init.lua b/integration/lovrManager/init.lua similarity index 100% rename from multi/integration/lovrManager/init.lua rename to integration/lovrManager/init.lua diff --git a/multi/integration/lovrManager/threads.lua b/integration/lovrManager/threads.lua similarity index 100% rename from multi/integration/lovrManager/threads.lua rename to integration/lovrManager/threads.lua diff --git a/multi/integration/luvitManager.lua b/integration/luvitManager.lua similarity index 100% rename from multi/integration/luvitManager.lua rename to integration/luvitManager.lua diff --git a/multi/integration/networkManager/channel.lua b/integration/networkManager/channel.lua similarity index 100% rename from multi/integration/networkManager/channel.lua rename to integration/networkManager/channel.lua diff --git a/multi/integration/networkManager/childNode.lua b/integration/networkManager/childNode.lua similarity index 100% rename from multi/integration/networkManager/childNode.lua rename to integration/networkManager/childNode.lua diff --git a/multi/integration/networkManager/clientSide.lua b/integration/networkManager/clientSide.lua similarity index 100% rename from multi/integration/networkManager/clientSide.lua rename to integration/networkManager/clientSide.lua diff --git a/multi/integration/networkManager/cmds.lua b/integration/networkManager/cmds.lua similarity index 100% rename from multi/integration/networkManager/cmds.lua rename to integration/networkManager/cmds.lua diff --git a/multi/integration/networkManager/extensions.lua b/integration/networkManager/extensions.lua similarity index 100% rename from multi/integration/networkManager/extensions.lua rename to integration/networkManager/extensions.lua diff --git a/multi/integration/networkManager/init.lua b/integration/networkManager/init.lua similarity index 100% rename from multi/integration/networkManager/init.lua rename to integration/networkManager/init.lua diff --git a/multi/integration/networkManager/masterNode.lua b/integration/networkManager/masterNode.lua similarity index 100% rename from multi/integration/networkManager/masterNode.lua rename to integration/networkManager/masterNode.lua diff --git a/multi/integration/networkManager/node.lua b/integration/networkManager/node.lua similarity index 100% rename from multi/integration/networkManager/node.lua rename to integration/networkManager/node.lua diff --git a/multi/integration/networkManager/nodeManager.lua b/integration/networkManager/nodeManager.lua similarity index 100% rename from multi/integration/networkManager/nodeManager.lua rename to integration/networkManager/nodeManager.lua diff --git a/multi/integration/networkManager/serverSide.lua b/integration/networkManager/serverSide.lua similarity index 100% rename from multi/integration/networkManager/serverSide.lua rename to integration/networkManager/serverSide.lua diff --git a/multi/integration/networkManager/threads.lua b/integration/networkManager/threads.lua similarity index 100% rename from multi/integration/networkManager/threads.lua rename to integration/networkManager/threads.lua diff --git a/multi/integration/networkManager/utils.lua b/integration/networkManager/utils.lua similarity index 100% rename from multi/integration/networkManager/utils.lua rename to integration/networkManager/utils.lua diff --git a/multi/integration/pesudoManager/extensions.lua b/integration/pesudoManager/extensions.lua similarity index 100% rename from multi/integration/pesudoManager/extensions.lua rename to integration/pesudoManager/extensions.lua diff --git a/multi/integration/pesudoManager/init.lua b/integration/pesudoManager/init.lua similarity index 100% rename from multi/integration/pesudoManager/init.lua rename to integration/pesudoManager/init.lua diff --git a/multi/integration/pesudoManager/threads.lua b/integration/pesudoManager/threads.lua similarity index 100% rename from multi/integration/pesudoManager/threads.lua rename to integration/pesudoManager/threads.lua diff --git a/multi/integration/threading.lua b/integration/threading.lua similarity index 100% rename from multi/integration/threading.lua rename to integration/threading.lua diff --git a/makeENV.lua b/makeENV.lua deleted file mode 100644 index 0b38933..0000000 --- a/makeENV.lua +++ /dev/null @@ -1,19 +0,0 @@ -lcommands = [[ -mkdir luajit && python -m hererocks -j 2.1.0-beta3 -r latest --patch --compat all ./luajit && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes -mkdir lua5.1 && python -m hererocks -l 5.1 -r latest --patch --compat all ./lua5.1 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes -mkdir lua5.2 && python -m hererocks -l 5.2 -r latest --patch --compat all ./lua5.2 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes -mkdir lua5.3 && python -m hererocks -l 5.3 -r latest --patch --compat all ./lua5.3 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes -mkdir lua5.4 && python -m hererocks -l 5.4 -r latest --patch --compat all ./lua5.4 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes -]] -function string.split (inputstr, sep) - local sep = sep or "\n" - local t={} - for str in string.gmatch(inputstr, "([^"..sep.."]+)") do - table.insert(t, str) - end - return t -end -local run = commands:split() -for i=1,#run do - os.execute(run[i]) -end \ No newline at end of file diff --git a/makeENV.sh b/makeENV.sh deleted file mode 100755 index 2840d20..0000000 --- a/makeENV.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -mkdir luajit -hererocks -j 2.1.0-beta3 -r latest --compat all ./luajit -. luajit/bin/activate -echo | lua -v -luarocks install multi -deactivate-lua -mkdir lua5.1 -hererocks -l 5.1 -r latest --patch --compat all ./lua5.1 -. lua5.1/bin/activate -echo | lua -v -luarocks install multi -deactivate-lua -mkdir lua5.2 -hererocks -l 5.2 -r latest --patch --compat all ./lua5.2 -. lua5.2/bin/activate -echo | lua -v -luarocks install multi -deactivate-lua -mkdir lua5.3 -hererocks -l 5.3 -r latest --patch --compat all ./lua5.3 -. lua5.3/bin/activate -echo | lua -v -luarocks install multi -deactivate-lua -mkdir lua5.4 -hererocks -l 5.4 -r latest --patch --compat all ./lua5.4 -. lua5.4/bin/activate -echo | lua -v -luarocks install multi -deactivate-lua \ No newline at end of file diff --git a/test.lua b/tests/test.lua similarity index 99% rename from test.lua rename to tests/test.lua index bf5a843..a1f7ef9 100644 --- a/test.lua +++ b/tests/test.lua @@ -5,6 +5,7 @@ GLOBAL, THREAD = require("multi.integration.lanesManager"):init() multi:getOptimizationConnection()(function(msg) print(msg) end) + -- local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection(), multi:newConnection() -- thread:newThread(function() -- print("Awaiting status") diff --git a/test2.lua b/tests/test2.lua similarity index 100% rename from test2.lua rename to tests/test2.lua From 5cfd15524124a054ba83e441209516d648eef23d Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 5 Nov 2022 23:19:20 -0400 Subject: [PATCH 26/40] Updated rockspec --- rockspecs/multi-15.3-0.rockspec | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/rockspecs/multi-15.3-0.rockspec b/rockspecs/multi-15.3-0.rockspec index d96f6f4..2cc71ba 100644 --- a/rockspecs/multi-15.3-0.rockspec +++ b/rockspecs/multi-15.3-0.rockspec @@ -19,21 +19,21 @@ dependencies = { build = { type = "builtin", modules = { - ["multi"] = "multi/init.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua", - ["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua", - ["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua", - ["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua", - ["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua", - --["multi.integration.lovrManager"] = "multi/integration/lovrManager/init.lua", - --["multi.integration.lovrManager.extensions"] = "multi/integration/lovrManager/extensions.lua", - --["multi.integration.lovrManager.threads"] = "multi/integration/lovrManager/threads.lua", - ["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua", - ["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua", - ["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.threading"] = "multi/integration/threading.lua", - --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + ["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.lovrManager"] = "integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "integration/lovrManager/threads.lua", + ["multi.integration.pesudoManager"] = "integration/pesudoManager/init.lua", + ["multi.integration.pesudoManager.extensions"] = "integration/pesudoManager/extensions.lua", + ["multi.integration.pesudoManager.threads"] = "integration/pesudoManager/threads.lua", + ["multi.integration.luvitManager"] = "integration/luvitManager.lua", + ["multi.integration.threading"] = "integration/threading.lua", + --["multi.integration.networkManager"] = "integration/networkManager.lua", } } \ No newline at end of file From a90b1d09b1d5fe7bee0d169128d8e2afca878bd2 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 5 Nov 2022 23:20:44 -0400 Subject: [PATCH 27/40] testing --- makeENV.lua | 19 ------------------- makeENV.sh | 31 ------------------------------- test.lua | 1 + 3 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 makeENV.lua delete mode 100755 makeENV.sh diff --git a/makeENV.lua b/makeENV.lua deleted file mode 100644 index 0b38933..0000000 --- a/makeENV.lua +++ /dev/null @@ -1,19 +0,0 @@ -lcommands = [[ -mkdir luajit && python -m hererocks -j 2.1.0-beta3 -r latest --patch --compat all ./luajit && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes -mkdir lua5.1 && python -m hererocks -l 5.1 -r latest --patch --compat all ./lua5.1 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes -mkdir lua5.2 && python -m hererocks -l 5.2 -r latest --patch --compat all ./lua5.2 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes -mkdir lua5.3 && python -m hererocks -l 5.3 -r latest --patch --compat all ./lua5.3 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes -mkdir lua5.4 && python -m hererocks -l 5.4 -r latest --patch --compat all ./lua5.4 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi && luarocks install lanes -]] -function string.split (inputstr, sep) - local sep = sep or "\n" - local t={} - for str in string.gmatch(inputstr, "([^"..sep.."]+)") do - table.insert(t, str) - end - return t -end -local run = commands:split() -for i=1,#run do - os.execute(run[i]) -end \ No newline at end of file diff --git a/makeENV.sh b/makeENV.sh deleted file mode 100755 index 2840d20..0000000 --- a/makeENV.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -mkdir luajit -hererocks -j 2.1.0-beta3 -r latest --compat all ./luajit -. luajit/bin/activate -echo | lua -v -luarocks install multi -deactivate-lua -mkdir lua5.1 -hererocks -l 5.1 -r latest --patch --compat all ./lua5.1 -. lua5.1/bin/activate -echo | lua -v -luarocks install multi -deactivate-lua -mkdir lua5.2 -hererocks -l 5.2 -r latest --patch --compat all ./lua5.2 -. lua5.2/bin/activate -echo | lua -v -luarocks install multi -deactivate-lua -mkdir lua5.3 -hererocks -l 5.3 -r latest --patch --compat all ./lua5.3 -. lua5.3/bin/activate -echo | lua -v -luarocks install multi -deactivate-lua -mkdir lua5.4 -hererocks -l 5.4 -r latest --patch --compat all ./lua5.4 -. lua5.4/bin/activate -echo | lua -v -luarocks install multi -deactivate-lua \ No newline at end of file diff --git a/test.lua b/test.lua index bf5a843..a1f7ef9 100644 --- a/test.lua +++ b/test.lua @@ -5,6 +5,7 @@ GLOBAL, THREAD = require("multi.integration.lanesManager"):init() multi:getOptimizationConnection()(function(msg) print(msg) end) + -- local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection(), multi:newConnection() -- thread:newThread(function() -- print("Awaiting status") From d6d4934a7e379071fddb84375409546baf18e8bd Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 6 Nov 2022 01:00:04 -0400 Subject: [PATCH 28/40] Tests failing with connections * connections, not counting triggers properly --- .gitignore | 7 ---- init.lua | 96 ++++++++++++++++++++-------------------------- tests/runtests.lua | 15 +++++--- tests/test.lua | 3 +- tests/test2.lua | 33 ---------------- 5 files changed, 51 insertions(+), 103 deletions(-) delete mode 100644 tests/test2.lua diff --git a/.gitignore b/.gitignore index 8538ab3..4d67604 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1 @@ -*lua5.1 -*lua5.2 -*lua5.3 -*lua5.4 -*luajit *.code-workspace -*.dat -*.zip \ No newline at end of file diff --git a/init.lua b/init.lua index 4750fbd..7d43893 100644 --- a/init.lua +++ b/init.lua @@ -159,23 +159,27 @@ function multi:newConnection(protect,func,kill) local cn = multi:newConnection() if not c1.__hasInstances then cn.__hasInstances = 2 - cn.__count = 0 + cn.__count = {0} else cn.__hasInstances = c1.__hasInstances + 1 cn.__count = c1.__count end + c1(function(...) - cn.__count = cn.__count + 1 - if cn.__count == cn.__hasInstances then + cn.__count[1] = cn.__count[1] + 1 + print(cn.__count[1], cn.__hasInstances) + if cn.__count[1] == cn.__hasInstances then cn:Fire(...) - cn.__count = 0 + cn.__count[1] = 0 end end) + c2(function(...) - cn.__count = cn.__count + 1 - if cn.__count == cn.__hasInstances then + cn.__count[1] = cn.__count[1] + 1 + print(cn.__count[1], cn.__hasInstances) + if cn.__count[1] == cn.__hasInstances then cn:Fire(...) - cn.__count = 0 + cn.__count[1] = 0 end end) return cn @@ -250,6 +254,25 @@ function multi:newConnection(protect,func,kill) end function self:Connect(func) table.insert(fast,func) + local temp = {} + setmetatable(temp,{ + __call=function(s,...) + return self:Connect(...) + end, + __index = function(t,k) + if rawget(t,"root_link") then + return t["root_link"][k] + end + return nil + end, + __newindex = function(t,k,v) + if rawget(t,"root_link") then + t["root_link"][k] = v + end + rawset(t,k,v) + end, + }) + return temp end return self end @@ -1085,42 +1108,18 @@ function thread.sleep(n) return yield(CMD, t_sleep, n or 1) end -function thread.hold(n,opt) - thread._Requests() - local opt = opt or {} - if type(opt)=="table" then - interval = opt.interval - if opt.cycles then - return yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) - elseif opt.sleep then - return yield(CMD, t_holdF, opt.sleep, n or dFunc, interval) - elseif opt.skip then - return yield(CMD, t_skip, opt.skip or 1, nil, interval) - end +local function conn_test(conn) + local ready = false + local args + local func = function(...) + ready = true + args = {...} end - - if type(n) == "number" then - thread.getRunningThread().lastSleep = clock() - return yield(CMD, t_sleep, n or 0, nil, interval) - elseif type(n) == "table" and n.Type == "connector" then - local rdy = function() - return false + conn(func) + return function() + if ready then + return unpack(args) or multi.NIL end - n(function(a1,a2,a3,a4,a5,a6) - rdy = function() - if a1==nil then - return NIL,a2,a3,a4,a5,a6 - end - return a1,a2,a3,a4,a5,a6 - end - end) - return yield(CMD, t_hold, function() - return rdy() - end, nil, interval) - elseif type(n) == "function" then - return yield(CMD, t_hold, n or dFunc, nil, interval) - else - error("Invalid argument passed to thread.hold(...)!") end end @@ -1142,20 +1141,7 @@ function thread.hold(n,opt) thread.getRunningThread().lastSleep = clock() return yield(CMD, t_sleep, n or 0, nil, interval) elseif type(n) == "table" and n.Type == "connector" then - local rdy = function() - return false - end - n(function(a1,a2,a3,a4,a5,a6) - rdy = function() - if a1==nil then - return NIL,a2,a3,a4,a5,a6 - end - return a1,a2,a3,a4,a5,a6 - end - end) - return yield(CMD, t_hold, function() - return rdy() - end, nil, interval) + return yield(CMD, t_hold, conn_test(n), nil, interval) elseif type(n) == "function" then return yield(CMD, t_hold, n or dFunc, nil, interval) else diff --git a/tests/runtests.lua b/tests/runtests.lua index 07d6e85..fdf63ce 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -2,7 +2,7 @@ if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then package.path="multi/?.lua;multi/?/init.lua;multi/?.lua;multi/?/?/init.lua;"..package.path require("lldebugger").start() else - package.path="./?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package.path + package.path = "../?/init.lua;../?.lua;"..package.path end --[[ This file runs all tests. @@ -36,12 +36,12 @@ runTest = thread:newFunction(function() end) proc:newTStep(1,10,1,.1):OnStep(function(t) tsteps = tsteps + 1 - end).OnEnd(function(step) + end):OnEnd(function(step) step:Destroy() end) proc:newStep(1,10):OnStep(function(s) steps = steps + 1 - end).OnEnd(function(step) + end):OnEnd(function(step) step:Destroy() end) local loop = proc:newLoop(function(l) @@ -102,6 +102,7 @@ runTest = thread:newFunction(function() ret3.OnStatus(function(part,whole) s3 = math.ceil((part/whole)*1000)/10 end) + ret.OnReturn(function() print("Done 1") end) @@ -111,7 +112,9 @@ runTest = thread:newFunction(function() ret3.OnReturn(function() print("Done 3") end) - local err, timeout = thread.hold(ret.OnReturn + ret2.OnReturn + ret3.OnReturn) + + local err, timeout = thread.hold(ret.OnReturn * ret2.OnReturn * ret3.OnReturn) + print("Working!",s1,s2,s3) if s1 == 100 and s2 == 100 and s3 == 100 then print("Threads: Ok") else @@ -164,11 +167,11 @@ runTest = thread:newFunction(function() os.exit() -- End of tests end) -runTest().OnError(function(...) +print(runTest().OnError(function(...) print("Error: Something went wrong with the test!") print(...) os.exit(1) -end) +end)) print("Pumping proc") while true do diff --git a/tests/test.lua b/tests/test.lua index a1f7ef9..f64791b 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,5 +1,4 @@ -package.path = "./?/init.lua;?.lua;lua5.4/share/lua/5.4/?/init.lua;lua5.4/share/lua/5.4/?.lua;"--..package.path -package.cpath = "lua5.4/lib/lua/5.4/?/core.dll;"--..package.cpath +package.path = "../?/init.lua;../?.lua;"..package.path multi, thread = require("multi"):init{print=true,findopt=true} GLOBAL, THREAD = require("multi.integration.lanesManager"):init() multi:getOptimizationConnection()(function(msg) diff --git a/tests/test2.lua b/tests/test2.lua deleted file mode 100644 index 5603708..0000000 --- a/tests/test2.lua +++ /dev/null @@ -1,33 +0,0 @@ -function difference(a, b) - local ai = {} - local r = {} - local rr = {} - for k,v in pairs(a) do r[k] = v; ai[v]=true end - for k,v in pairs(b) do - if ai[v]==nil then table.insert(rr,r[k]) end - end - return rr -end -function remove(a, b) - local ai = {} - local r = {} - for k,v in pairs(a) do ai[v]=true end - for k,v in pairs(b) do - if ai[v]==nil then table.insert(r,a[k]) end - end - return r -end - -function printtab(tab,msg) - print(msg or "TABLE") - for i,v in pairs(tab) do - print(i, v) - end - print("") -end - -local tab1 = {1,2,3,4,5} -local tab2 = {3,4,5,6,7} -tab1 = remove(tab1,tab2) -printtab(tab1, "Table 1") -printtab(tab2, "Table 2") From 0d3de6e31d68f8f3fe10a08fd1e1af889873fde9 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 7 Nov 2022 10:01:22 -0500 Subject: [PATCH 29/40] Fixed multiplying connections together --- init.lua | 13 ++++++------- tests/runtests.lua | 22 ++++++++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/init.lua b/init.lua index 7d43893..33e77c3 100644 --- a/init.lua +++ b/init.lua @@ -157,18 +157,18 @@ function multi:newConnection(protect,func,kill) end, __mul = function(c1,c2) -- And local cn = multi:newConnection() - if not c1.__hasInstances then - cn.__hasInstances = 2 + if c1.__hasInstances == nil then + cn.__hasInstances = {2} cn.__count = {0} else - cn.__hasInstances = c1.__hasInstances + 1 + cn.__hasInstances = c1.__hasInstances + cn.__hasInstances[1] = cn.__hasInstances[1] + 1 cn.__count = c1.__count end c1(function(...) cn.__count[1] = cn.__count[1] + 1 - print(cn.__count[1], cn.__hasInstances) - if cn.__count[1] == cn.__hasInstances then + if cn.__count[1] == cn.__hasInstances[1] then cn:Fire(...) cn.__count[1] = 0 end @@ -176,8 +176,7 @@ function multi:newConnection(protect,func,kill) c2(function(...) cn.__count[1] = cn.__count[1] + 1 - print(cn.__count[1], cn.__hasInstances) - if cn.__count[1] == cn.__hasInstances then + if cn.__count[1] == cn.__hasInstances[1] then cn:Fire(...) cn.__count[1] = 0 end diff --git a/tests/runtests.lua b/tests/runtests.lua index fdf63ce..1640e05 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -114,17 +114,23 @@ runTest = thread:newFunction(function() end) local err, timeout = thread.hold(ret.OnReturn * ret2.OnReturn * ret3.OnReturn) - print("Working!",s1,s2,s3) + if s1 == 100 and s2 == 100 and s3 == 100 then - print("Threads: Ok") + print("Threads: All tests Ok") else - print("Threads OnStatus or thread.hold(conn) Error!") - end - if timeout then - print("Threads or Connection Error!") - else - print("Connection Test 1: Ok") + if s1>0 and s2>0 and s3 > 0 then + print("Thread OnStatus: Ok") + else + print("Threads OnStatus or thread.hold(conn) Error!") + end + if timeout then + print("Connection Error!") + else + print("Connection Test 1: Ok") + end + print("Connection holding Error!") end + conn1 = proc:newConnection() conn2 = proc:newConnection() conn3 = proc:newConnection() From 2a47de9fd849ba6e03b14255a3ccc3c8d90cef50 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 20 Nov 2022 12:17:55 -0500 Subject: [PATCH 30/40] Added thread.chain(...) --- README.md | 8 +++++--- docs/changes.md | 9 ++++++++- init.lua | 7 +++++++ tests/runtests.lua | 14 +++++++------- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d53e19c..753e2d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# Multi Version: 15.3.0 Connecting the dots +# Multi Version: 15.3.0 A world of Connections **Key Changes** -- Bug fix +- SystemThreadedConnections +- Restructured the directory structure of the repo (Allows for keeping multi as a submodule and being able to require it as is) +- Bug fixes Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! @@ -8,7 +10,7 @@ My multitasking library for lua. It is a pure lua binding, with exceptions of th
-Progress is being made in [v15.3.0](https://github.com/rayaman/multi/tree/v15.3.0) +Progress is being made in [v15.4.0](https://github.com/rayaman/multi/tree/v15.4.0) ---
diff --git a/docs/changes.md b/docs/changes.md index d9e6631..01f3019 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -83,6 +83,13 @@ multi:mainloop() Added --- +- `thread.chain(...)` allows you to chain `thread.hold(FUNCTIONs)` together + ``` + while true do + thread.chain(hold_function_1, hold_function_2) + end + ``` + If the first function returns true, it moves on to the next one - Experimental option to multi settings: `findopt`. When set to `true` it will print out a message when certain pattern are used with this library. For example if an anonymous function is used in thread.hold() within a loop. The library will trigger a message alerting you that this isn't the most performant way to use thread.hold(). - `multi:newSystemThreadedConnection()` @@ -101,7 +108,7 @@ Changed --- - Internally all `OnError` events are now connected to with multi.print, you must pass `print=true` to the init settings when initializing the multi object. `require("multi"):init{print=true}` - All actors now use fastmode on connections -- Performance enhancement with processes that are pumped instead of automatically running, by suppressing the creation of an internal loop object that would manage the process +- Performance enhancement with processes that are pumped. Instead of automatically running, by suppressing the creation of an internal loop object that would manage the process, we bypass that freeing up memory and adding a bit more speed. - `Connection:fastMode() or Connection:SetHelper()` now returns a reference to itself - `Connection:[connect, hasConnections, getConnection]` changed to be `Connection:[Connect, HasConnections, getConnections]`. This was done in an attempt to follow a consistent naming scheme. The old methods still will work to prevent old code breaking. - `Connections when added(+) together now act like 'or', to get the 'and' feature multiply(*) them together.` diff --git a/init.lua b/init.lua index 33e77c3..88c2ba9 100644 --- a/init.lua +++ b/init.lua @@ -1122,6 +1122,13 @@ local function conn_test(conn) end end +function thread.chain(...) + local args = select("#") + for i=1,#args do + thread.hold(select(i,...)) + end +end + function thread.hold(n,opt) thread._Requests() local opt = opt or {} diff --git a/tests/runtests.lua b/tests/runtests.lua index 1640e05..ae8b098 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -78,7 +78,7 @@ runTest = thread:newFunction(function() thread.pushStatus(a,count) if a == count then break end end - return "Done" + return "Done", true, math.random(1,10000) end) local ret = func(10) local ret2 = func(15) @@ -103,14 +103,14 @@ runTest = thread:newFunction(function() s3 = math.ceil((part/whole)*1000)/10 end) - ret.OnReturn(function() - print("Done 1") + ret.OnReturn(function(...) + print("Done 1",...) end) - ret2.OnReturn(function() - print("Done 2") + ret2.OnReturn(function(...) + print("Done 2",...) end) - ret3.OnReturn(function() - print("Done 3") + ret3.OnReturn(function(...) + print("Done 3",...) end) local err, timeout = thread.hold(ret.OnReturn * ret2.OnReturn * ret3.OnReturn) From 019855d81f250ca4a194100819b45fc61889c5ad Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 20 Nov 2022 12:31:37 -0500 Subject: [PATCH 31/40] Fixed #args when args was a number --- init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 88c2ba9..0239efb 100644 --- a/init.lua +++ b/init.lua @@ -1124,7 +1124,7 @@ end function thread.chain(...) local args = select("#") - for i=1,#args do + for i=1,args do thread.hold(select(i,...)) end end From 35a86a6cdacf660cd52433415e749135d4ea37b2 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 20 Nov 2022 13:19:52 -0500 Subject: [PATCH 32/40] Fixed code hanging due to thread.chain not processing varargs properly --- init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 0239efb..a3c3f54 100644 --- a/init.lua +++ b/init.lua @@ -1123,7 +1123,7 @@ local function conn_test(conn) end function thread.chain(...) - local args = select("#") + local args = select("#",...) for i=1,args do thread.hold(select(i,...)) end From 4cf80517ebef3f50b1cabb0f5870dcc2e9eac6e6 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 6 Dec 2022 00:03:27 -0500 Subject: [PATCH 33/40] Updated docs --- .gitignore | 1 + docs/changes.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4d67604..f3ede8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.code-workspace +lua5.4/* \ No newline at end of file diff --git a/docs/changes.md b/docs/changes.md index 01f3019..8ebf6b9 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -102,7 +102,7 @@ Added Loops through the table and calls callback_function with each element of the array. -- If a name is not supplied when creating threads; a name is randomly generated. Unless sending through an established channel/queue you might not be able to easily init the object. +- If a name is not supplied when creating threads and threaded objects; a name is randomly generated. Unless sending through an established channel/queue you might not be able to easily init the object. Changed --- From 0d6a9f92ada2e61c64f0f972f06ce075d0c01649 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 6 Dec 2022 00:53:03 -0500 Subject: [PATCH 34/40] Working on getting STC working in love2d --- integration/loveManager/extensions.lua | 324 +++++-------------------- integration/loveManager/init.lua | 3 +- 2 files changed, 66 insertions(+), 261 deletions(-) diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 3eb1a7a..965915a 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -22,218 +22,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] --- TODO make compatible with lovr -local multi, thread = require("multi").init() -GLOBAL = multi.integration.GLOBAL -THREAD = multi.integration.THREAD -function multi:newSystemThreadedQueue(name) - local name = name or multi.randomString(16) - local c = {} - c.Name = name - local fRef = {"func",nil} - function c:init() - local q = {} - q.chan = love.thread.getChannel(self.Name) - function q:push(dat) - if type(dat) == "function" then - fRef[2] = THREAD.dump(dat) - self.chan:push(fRef) - return - else - self.chan:push(dat) - end - end - function q:pop() - local dat = self.chan:pop() - if type(dat)=="table" and dat[1]=="func" then - return THREAD.loadDump(dat[2]) - else - return dat - end - end - function q:peek() - local dat = self.chan:peek() - if type(dat)=="table" and dat[1]=="func" then - return THREAD.loadDump(dat[2]) - else - return dat - end - end - return q - end - THREAD.package(name,c) - return c -end -function multi:newSystemThreadedTable(name) - local name = name or multi.randomString(16) - local c = {} - c.Name = name - function c:init() - return THREAD.createTable(self.Name) - end - THREAD.package(name,c) - return c -end -local jqc = 1 -function multi:newSystemThreadedJobQueue(n) - local c = {} - c.cores = n or THREAD.getCores() - c.registerQueue = {} - c.funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") - c.queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") - c.queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") - c.queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") - c.id = 0 - c.OnJobCompleted = multi:newConnection() - local allfunc = 0 - function c:doToAll(func) - local f = THREAD.dump(func) - for i = 1, self.cores do - self.queueAll:push({allfunc,f}) - end - allfunc = allfunc + 1 - end - function c:registerFunction(name,func) - if self.funcs[name] then - error("A function by the name "..name.." has already been registered!") - end - self.funcs[name] = func - end - function c:pushJob(name,...) - self.id = self.id + 1 - self.queue:push{name,self.id,...} - return self.id - end - function c:isEmpty() - return queueJob:peek()==nil - end - local nFunc = 0 - function c:newFunction(name,func,holup) -- This registers with the queue - if type(name)=="function" then - holup = func - func = name - name = "JQ_Function_"..nFunc - end - nFunc = nFunc + 1 - c:registerFunction(name,func) - return thread:newFunction(function(...) - local id = c:pushJob(name,...) - local link - local rets - link = c.OnJobCompleted(function(jid,...) - if id==jid then - rets = {...} - link:Destroy() - end - end) - return thread.hold(function() - if rets then - return unpack(rets) or multi.NIL - end - end) - end,holup),name - end - thread:newThread("jobManager",function() - while true do - thread.yield() - local dat = c.queueReturn:pop() - if dat then - c.OnJobCompleted:Fire(unpack(dat)) - end - end - end) - for i=1,c.cores do - multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) - local multi, thread = require("multi"):init() - require("love.timer") - local function atomic(channel) - return channel:pop() - end - local clock = os.clock - local funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") - local queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") - local queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") - local lastProc = clock() - local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") - local registry = {} - setmetatable(_G,{__index = funcs}) - thread:newThread("startUp",function() - while true do - thread.yield() - local all = queueAll:peek() - if all and not registry[all[1]] then - lastProc = os.clock() - THREAD.loadDump(queueAll:pop()[2])() - end - end - end) - thread:newThread("runner",function() - thread.sleep(.1) - while true do - thread.yield() - local all = queueAll:peek() - if all and not registry[all[1]] then - lastProc = os.clock() - THREAD.loadDump(queueAll:pop()[2])() - end - local dat = queue:performAtomic(atomic) - if dat then - lastProc = os.clock() - local name = table.remove(dat,1) - local id = table.remove(dat,1) - local tab = {funcs[name](unpack(dat))} - table.insert(tab,1,id) - queueReturn:push(tab) - end - end - end):OnError(function(...) - error(...) - end) - thread:newThread("Idler",function() - while true do - thread.yield() - if clock()-lastProc> 2 then - THREAD.sleep(.05) - else - THREAD.sleep(.001) - end - end - end) - multi:mainloop() - end,jqc) - end - jqc = jqc + 1 - return c +if not ISTHREAD then + multi, thread = require("multi").init() + GLOBAL = multi.integration.GLOBAL + THREAD = multi.integration.THREAD +else + end ---[[ -MIT License - -Copyright (c) 2022 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] - --- TODO make compatible with lovr -local multi, thread = require("multi").init() -GLOBAL = multi.integration.GLOBAL -THREAD = multi.integration.THREAD function multi:newSystemThreadedQueue(name) local name = name or multi.randomString(16) local c = {} @@ -421,6 +217,59 @@ function multi:newSystemThreadedConnection(name) c.TRIG = 0x01 c.PING = 0x02 c.PONG = 0x03 + + local subscribe = multi:newSystemThreadedQueue("SUB_STC_" .. name):init() + + function c:init() + + self.subscribe = THREAD.waitFor("SUB_STC_" .. self.Name):init() + + function self:Fire(...) + local args = {...} + if self.CID == THREAD.getID() then -- Host Call + for _, link in pairs(self.links) do + link:push{self.TRIG, args} + end + self.proxy_conn:Fire(...) + else + self.subscribe:push{self.TRIG, args} + end + end + + local multi, thread = require("multi"):init() + self.links = {} + self.proxy_conn = multi:newConnection() + local mt = getmetatable(self.proxy_conn) + setmetatable(self, {__index = self.proxy_conn, __call = function(t,func) self.proxy_conn(func) end, __add = mt.__add}) + if self.CID == THREAD.getID() then return self end + thread:newThread("STC_CONN_MAN" .. self.Name,function() + local item + local string_self_ref = "LSF_" .. multi.randomString(16) + local link_self_ref = multi:newSystemThreadedQueue():init() + self.subscribe:push{self.CONN, string_self_ref} + while true do + item = thread.hold(function() + return link_self_ref:peek() + end) + if item[1] == self.PING then + link_self_ref:push{self.PONG} + link_self_ref:pop() + elseif item[1] == self.CONN then + if string_self_ref ~= item[2] then + table.insert(self.links, THREAD.waitFor(item[2]):init()) + end + link_self_ref:pop() + elseif item[1] == self.TRIG then + self.proxy_conn:Fire(unpack(item[2])) + link_self_ref:pop() + else + -- This shouldn't be the case + end + end + end).OnError(print) + return self + end + local function remove(a, b) local ai = {} local r = {} @@ -431,7 +280,6 @@ function multi:newSystemThreadedConnection(name) return r end c.CID = THREAD.getID() - c.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..self.Name):init() c.Name = name c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. -- Locals will only live in the thread that creates the original object @@ -455,6 +303,7 @@ function multi:newSystemThreadedConnection(name) ping = thread:newFunction(function(self) ping:Pause() + multi.ForEach(self.links, function(link) -- Sync new connections link:push{self.PING} multi:newThread("pong Thread", pong, link, links) @@ -482,63 +331,18 @@ function multi:newSystemThreadedConnection(name) end) if item[1] == c.CONN then multi.ForEach(c.links, function(link) -- Sync new connections - item[2]:push{c.CONN, link} + THREAD.waitFor(item[2]):init():push{c.CONN, link.Name} end) - c.links[#c.links+1] = item[2] + print("Adding link") + c.links[#c.links+1] = THREAD.waitFor(item[2]):init() elseif item[1] == c.TRIG then fire(unpack(item[2])) c.proxy_conn:Fire(unpack(item[2])) end end - end) + end).OnError(print) --- ^^^ This will only exist in the init thread - function c:Fire(...) - local args = {...} - if self.CID == THREAD.getID() then -- Host Call - for _, link in pairs(self.links) do - link:push {self.TRIG, args} - end - self.proxy_conn:Fire(...) - else - self.subscribe:push {self.TRIG, args} - end - end - - function c:init() - local multi, thread = require("multi"):init() - self.links = {} - self.proxy_conn = multi:newConnection() - local mt = getmetatable(self.proxy_conn) - setmetatable(self, {__index = self.proxy_conn, __call = function(t,func) self.proxy_conn(func) end, __add = mt.__add}) - if self.CID == THREAD.getID() then return self end - thread:newThread("STC_CONN_MAN"..name,function() - local item - local link_self_ref = multi:newSystemThreadedQueue() - self.subscribe:push{self.CONN, link_self_ref} - while true do - item = thread.hold(function() - return link_self_ref:peek() - end) - if item[1] == self.PING then - link_self_ref:push{self.PONG} - link_self_ref:pop() - elseif item[1] == self.CONN then - if item[2].Name ~= link_self_ref.Name then - table.insert(self.links, item[2]) - end - link_self_ref:pop() - elseif item[1] == self.TRIG then - self.proxy_conn:Fire(unpack(item[2])) - link_self_ref:pop() - else - -- This shouldn't be the case - end - end - end) - return self - end - THREAD.package(name,c) return c diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index f002d86..dd2d402 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -35,6 +35,7 @@ __THREADNAME__=table.remove(__IMPORTS,1) stab = THREAD.createStaticTable(__THREADNAME__) GLOBAL = THREAD.getGlobal() multi, thread = require("multi").init() +print(pcall(require,"multi.integration.loveManager.extensions")) stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} ]] local multi, thread = require("multi"):init() @@ -103,7 +104,7 @@ end THREAD.newSystemThread = multi.newSystemThread function love.threaderror(thread, errorstr) - mulit.print("Thread error!\n"..errorstr) + multi.print("Thread error!\n"..errorstr) end multi.integration.GLOBAL = GLOBAL From bbc83acc0f819e56415d0c2154837a84d7b877cd Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 10 Dec 2022 22:48:26 -0500 Subject: [PATCH 35/40] Modified changes --- docs/changes.md | 10 ++++++++-- init.lua | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 01f3019..a0a82d1 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -84,12 +84,18 @@ multi:mainloop() Added --- - `thread.chain(...)` allows you to chain `thread.hold(FUNCTIONs)` together - ``` + ```lua while true do thread.chain(hold_function_1, hold_function_2) end ``` - If the first function returns true, it moves on to the next one + If the first function returns true, it moves on to the next one. if expanded it follows: + ```lua + while true do + thread.hold(hold_function_1) + thread.hold(hold_function_2) + end + ``` - Experimental option to multi settings: `findopt`. When set to `true` it will print out a message when certain pattern are used with this library. For example if an anonymous function is used in thread.hold() within a loop. The library will trigger a message alerting you that this isn't the most performant way to use thread.hold(). - `multi:newSystemThreadedConnection()` diff --git a/init.lua b/init.lua index a3c3f54..74ef7f4 100644 --- a/init.lua +++ b/init.lua @@ -876,6 +876,32 @@ function multi:newTStep(start,reset,count,set) return c end +local tasks = {} +local _tasks = 0 + +local function _task_handler() + tasks[#tasks + 1] = func + _tasks = _tasks + 1 +end + +function multi:newTask(func) + multi:newThread("Task Handler",function() + while true do + thread.hold(function() + return _tasks > 1 + end) + for i=1,_tasks do + tasks[i]() + end + _tasks = 0 + end + end) + -- Re bind this method to use the one that doesn't init a thread! + multi.newTask = _task_handler + tasks[#tasks + 1] = func + _tasks = _tasks + 1 +end + local scheduledjobs = {} local sthread From 2f11a80a288dcb93518fb6d5e75c947a58829676 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 20 Dec 2022 14:35:13 -0500 Subject: [PATCH 36/40] Fixed > 1 which prevented tasks from being processed if only on task was in queue --- init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 74ef7f4..d4f67bb 100644 --- a/init.lua +++ b/init.lua @@ -888,9 +888,10 @@ function multi:newTask(func) multi:newThread("Task Handler",function() while true do thread.hold(function() - return _tasks > 1 + return _tasks > 0 end) for i=1,_tasks do + print("Doing tasks") tasks[i]() end _tasks = 0 From f5e95b8c9f2eeae180a033e8853797dbcb13f22e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 20 Dec 2022 15:11:00 -0500 Subject: [PATCH 37/40] Removed print statement --- init.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/init.lua b/init.lua index d4f67bb..5a57539 100644 --- a/init.lua +++ b/init.lua @@ -891,7 +891,6 @@ function multi:newTask(func) return _tasks > 0 end) for i=1,_tasks do - print("Doing tasks") tasks[i]() end _tasks = 0 From 8c3d66a7b1d9a741f98d0165da2c4ce3e6d91e14 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 25 Dec 2022 21:14:34 -0500 Subject: [PATCH 38/40] Added Unconnect to connections. Allows fastmode connections to be removed --- docs/changes.md | 1 + init.lua | 12 +++- tests/test.lua | 165 +++++++++++++++++++++++++++++------------------- 3 files changed, 111 insertions(+), 67 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 0b23bd5..314a545 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -83,6 +83,7 @@ multi:mainloop() Added --- +- `multi:newConnection():Unconnect(conn_link)` Fastmode previously didn't have the ability to be unconnected to. This method works with both fastmode and non fastmode. `fastMode` will be made the default in v16.0.0 (This is a breaking change for those using the Destroy method, use this time to migrate to using `Unconnect()`) - `thread.chain(...)` allows you to chain `thread.hold(FUNCTIONs)` together ```lua while true do diff --git a/init.lua b/init.lua index 5a57539..643cac3 100644 --- a/init.lua +++ b/init.lua @@ -244,6 +244,14 @@ function multi:newConnection(protect,func,kill) return call_funcs end + function c:Unconnect(conn) + if conn.fast then + table.remove(fast,conn.ind) + elseif conn.Destroy then + conn:Destroy() + end + end + function c:fastMode() if find_optimization then return self end function self:Fire(...) @@ -252,8 +260,8 @@ function multi:newConnection(protect,func,kill) end end function self:Connect(func) - table.insert(fast,func) - local temp = {} + table.insert(fast, func) + local temp = {fast = true, ind = #fast} setmetatable(temp,{ __call=function(s,...) return self:Connect(...) diff --git a/tests/test.lua b/tests/test.lua index f64791b..b4dddf4 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -5,7 +5,42 @@ multi:getOptimizationConnection()(function(msg) print(msg) end) --- local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection(), multi:newConnection() +local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection():fastMode(), multi:newConnection() + +local link = conn1(function() + print("Conn1, first") +end) + +local link2 = conn1(function() + print("Conn1, second") +end) + +local link3 = conn1(function() + print("Conn1, third") +end) + +local link4 = conn2(function() + print("Conn2, first") +end) + +local link5 = conn2(function() + print("Conn2, second") +end) + +local link6 = conn2(function() + print("Conn2, third") +end) + +print("All conns\n-------------") +conn1:Fire() +conn2:Fire() + +conn1:Unconnect(link3) +conn2:Unconnect(link6) +print("All conns Edit\n---------------------") +conn1:Fire() +conn2:Fire() + -- thread:newThread(function() -- print("Awaiting status") -- thread.hold(conn1 + (conn2 * conn3)) @@ -25,74 +60,74 @@ end) -- conn3:Fire() -- end) -local conn = multi:newSystemThreadedConnection("conn"):init() +-- local conn = multi:newSystemThreadedConnection("conn"):init() -multi:newSystemThread("Thread_Test_1", function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local console = THREAD.getConsole() - conn(function(a,b,c) - console.print(THREAD:getName().." was triggered!",a,b,c) - end) - multi:mainloop() -end) +-- multi:newSystemThread("Thread_Test_1", function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:mainloop() +-- end) -multi:newSystemThread("Thread_Test_2", function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local console = THREAD.getConsole() - conn(function(a,b,c) - console.print(THREAD:getName().." was triggered!",a,b,c) - end) - multi:newAlarm(2):OnRing(function() - console.print("Fire 2!!!") - conn:Fire(4,5,6) - THREAD.kill() - end) +-- multi:newSystemThread("Thread_Test_2", function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:newAlarm(2):OnRing(function() +-- console.print("Fire 2!!!") +-- conn:Fire(4,5,6) +-- THREAD.kill() +-- end) - multi:mainloop() -end) -local console = THREAD.getConsole() -conn(function(a,b,c) - console.print("Mainloop conn got triggered!",a,b,c) -end) +-- multi:mainloop() +-- end) +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print("Mainloop conn got triggered!",a,b,c) +-- end) -alarm = multi:newAlarm(1) -alarm:OnRing(function() - console.print("Fire 1!!!") - conn:Fire(1,2,3) -end) +-- alarm = multi:newAlarm(1) +-- alarm:OnRing(function() +-- console.print("Fire 1!!!") +-- conn:Fire(1,2,3) +-- end) -alarm = multi:newAlarm(3):OnRing(function() - multi:newSystemThread("Thread_Test_3",function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local console = THREAD.getConsole() - conn(function(a,b,c) - console.print(THREAD:getName().." was triggered!",a,b,c) - end) - multi:newAlarm(4):OnRing(function() - console.print("Fire 3!!!") - conn:Fire(7,8,9) - end) - multi:mainloop() - end) -end) +-- alarm = multi:newAlarm(3):OnRing(function() +-- multi:newSystemThread("Thread_Test_3",function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:newAlarm(4):OnRing(function() +-- console.print("Fire 3!!!") +-- conn:Fire(7,8,9) +-- end) +-- multi:mainloop() +-- end) +-- end) -multi:newSystemThread("Thread_Test_4",function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local conn2 = multi:newConnection() - local console = THREAD.getConsole() - multi:newAlarm(2):OnRing(function() - conn2:Fire() - end) - multi:newThread(function() - console.print("Conn Test!") - thread.hold(conn + conn2) - console.print("It held!") - end) - multi:mainloop() -end) +-- multi:newSystemThread("Thread_Test_4",function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local conn2 = multi:newConnection() +-- local console = THREAD.getConsole() +-- multi:newAlarm(2):OnRing(function() +-- conn2:Fire() +-- end) +-- multi:newThread(function() +-- console.print("Conn Test!") +-- thread.hold(conn + conn2) +-- console.print("It held!") +-- end) +-- multi:mainloop() +-- end) -multi:mainloop() \ No newline at end of file +-- multi:mainloop() \ No newline at end of file From 7ea6873c1f74a87b130268bf245bba9e5c89fd87 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Fri, 30 Dec 2022 15:22:37 -0500 Subject: [PATCH 39/40] Fixed issue where fastmode connections wouldn't properly be removed --- init.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/init.lua b/init.lua index 643cac3..bf65b70 100644 --- a/init.lua +++ b/init.lua @@ -246,7 +246,11 @@ function multi:newConnection(protect,func,kill) function c:Unconnect(conn) if conn.fast then - table.remove(fast,conn.ind) + for i = 1, #fast do + if conn.ref == fast[i] then + table.remove(fast, i) + end + end elseif conn.Destroy then conn:Destroy() end @@ -261,7 +265,7 @@ function multi:newConnection(protect,func,kill) end function self:Connect(func) table.insert(fast, func) - local temp = {fast = true, ind = #fast} + local temp = {fast = true} setmetatable(temp,{ __call=function(s,...) return self:Connect(...) @@ -279,6 +283,7 @@ function multi:newConnection(protect,func,kill) rawset(t,k,v) end, }) + temp.ref = func return temp end return self @@ -337,6 +342,7 @@ function multi:newConnection(protect,func,kill) end function temp:Destroy() + multi.print("Calling Destroy on a connection link is deprecated and will be removed in v16.0.0") for i=#call_funcs,1,-1 do if call_funcs[i]~=nil then if call_funcs[i]==self.func then From ec5bf74009f269fc1f9ffaa37a33f00ad5a1b4c3 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 31 Dec 2022 02:21:01 -0500 Subject: [PATCH 40/40] Fixed love2d STCs! Actually before new years :P --- integration/loveManager/extensions.lua | 30 ++-- integration/loveManager/init.lua | 12 +- tests/test.lua | 182 ++++++++++++------------- 3 files changed, 116 insertions(+), 108 deletions(-) diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 965915a..da5b560 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -27,7 +27,8 @@ if not ISTHREAD then GLOBAL = multi.integration.GLOBAL THREAD = multi.integration.THREAD else - + GLOBAL = multi.integration.GLOBAL + THREAD = multi.integration.THREAD end function multi:newSystemThreadedQueue(name) @@ -218,17 +219,17 @@ function multi:newSystemThreadedConnection(name) c.PING = 0x02 c.PONG = 0x03 - local subscribe = multi:newSystemThreadedQueue("SUB_STC_" .. name):init() + local subscribe = love.thread.getChannel("SUB_STC_" .. name) function c:init() - self.subscribe = THREAD.waitFor("SUB_STC_" .. self.Name):init() + self.subscribe = love.thread.getChannel("SUB_STC_" .. self.Name) function self:Fire(...) local args = {...} if self.CID == THREAD.getID() then -- Host Call for _, link in pairs(self.links) do - link:push{self.TRIG, args} + love.thread.getChannel(link):push{self.TRIG, args} end self.proxy_conn:Fire(...) else @@ -245,7 +246,7 @@ function multi:newSystemThreadedConnection(name) thread:newThread("STC_CONN_MAN" .. self.Name,function() local item local string_self_ref = "LSF_" .. multi.randomString(16) - local link_self_ref = multi:newSystemThreadedQueue():init() + local link_self_ref = love.thread.getChannel(string_self_ref) self.subscribe:push{self.CONN, string_self_ref} while true do item = thread.hold(function() @@ -256,7 +257,7 @@ function multi:newSystemThreadedConnection(name) link_self_ref:pop() elseif item[1] == self.CONN then if string_self_ref ~= item[2] then - table.insert(self.links, THREAD.waitFor(item[2]):init()) + table.insert(self.links, love.thread.getChannel(item[2])) end link_self_ref:pop() elseif item[1] == self.TRIG then @@ -286,7 +287,7 @@ function multi:newSystemThreadedConnection(name) local ping local pong = function(link, links) local res = thread.hold(function() - return link:peek()[1] == c.PONG + return love.thread.getChannel(link):peek()[1] == c.PONG end,{sleep=3}) if not res then @@ -297,7 +298,7 @@ function multi:newSystemThreadedConnection(name) end end else - link:pop() + love.thread.getChannel(link):pop() end end @@ -305,8 +306,8 @@ function multi:newSystemThreadedConnection(name) ping:Pause() multi.ForEach(self.links, function(link) -- Sync new connections - link:push{self.PING} - multi:newThread("pong Thread", pong, link, links) + love.thread.getChannel(link):push{self.PING} + multi:newThread("pong Thread", pong, link, self.links) end) thread.sleep(3) @@ -316,7 +317,7 @@ function multi:newSystemThreadedConnection(name) local function fire(...) for _, link in pairs(c.links) do - link:push {c.TRIG, {...}} + love.thread.getChannel(link):push {c.TRIG, {...}} end end @@ -330,11 +331,12 @@ function multi:newSystemThreadedConnection(name) return c.subscribe:pop() end) if item[1] == c.CONN then + multi.ForEach(c.links, function(link) -- Sync new connections - THREAD.waitFor(item[2]):init():push{c.CONN, link.Name} + love.thread.getChannel(item[2]):push{c.CONN, link} end) - print("Adding link") - c.links[#c.links+1] = THREAD.waitFor(item[2]):init() + c.links[#c.links+1] = item[2] + elseif item[1] == c.TRIG then fire(unpack(item[2])) c.proxy_conn:Fire(unpack(item[2])) diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index dd2d402..dd0e929 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -26,16 +26,23 @@ if ISTHREAD then end local ThreadFileData = [[ ISTHREAD = true -THREAD = require("multi.integration.loveManager.threads") -- order is important! +THREAD = require("multi.integration.loveManager.threads") sThread = THREAD __IMPORTS = {...} __FUNC__=table.remove(__IMPORTS,1) __THREADID__=table.remove(__IMPORTS,1) __THREADNAME__=table.remove(__IMPORTS,1) +math.randomseed(__THREADID__) +math.random() +math.random() +math.random() stab = THREAD.createStaticTable(__THREADNAME__) GLOBAL = THREAD.getGlobal() multi, thread = require("multi").init() -print(pcall(require,"multi.integration.loveManager.extensions")) +multi.integration={} +multi.integration.GLOBAL = GLOBAL +multi.integration.THREAD = THREAD +pcall(require,"multi.integration.loveManager.extensions") stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} ]] local multi, thread = require("multi"):init() @@ -43,7 +50,6 @@ local THREAD = {} __THREADID__ = 0 __THREADNAME__ = "MainThread" multi.integration={} -multi.integration.love2d={} local THREAD = require("multi.integration.loveManager.threads") local GLOBAL = THREAD.getGlobal() local THREAD_ID = 1 diff --git a/tests/test.lua b/tests/test.lua index b4dddf4..4387b11 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -5,41 +5,41 @@ multi:getOptimizationConnection()(function(msg) print(msg) end) -local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection():fastMode(), multi:newConnection() +-- local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection():fastMode(), multi:newConnection() -local link = conn1(function() - print("Conn1, first") -end) +-- local link = conn1(function() +-- print("Conn1, first") +-- end) -local link2 = conn1(function() - print("Conn1, second") -end) +-- local link2 = conn1(function() +-- print("Conn1, second") +-- end) -local link3 = conn1(function() - print("Conn1, third") -end) +-- local link3 = conn1(function() +-- print("Conn1, third") +-- end) -local link4 = conn2(function() - print("Conn2, first") -end) +-- local link4 = conn2(function() +-- print("Conn2, first") +-- end) -local link5 = conn2(function() - print("Conn2, second") -end) +-- local link5 = conn2(function() +-- print("Conn2, second") +-- end) -local link6 = conn2(function() - print("Conn2, third") -end) +-- local link6 = conn2(function() +-- print("Conn2, third") +-- end) -print("All conns\n-------------") -conn1:Fire() -conn2:Fire() +-- print("All conns\n-------------") +-- conn1:Fire() +-- conn2:Fire() -conn1:Unconnect(link3) -conn2:Unconnect(link6) -print("All conns Edit\n---------------------") -conn1:Fire() -conn2:Fire() +-- conn1:Unconnect(link3) +-- conn2:Unconnect(link6) +-- print("All conns Edit\n---------------------") +-- conn1:Fire() +-- conn2:Fire() -- thread:newThread(function() -- print("Awaiting status") @@ -60,74 +60,74 @@ conn2:Fire() -- conn3:Fire() -- end) --- local conn = multi:newSystemThreadedConnection("conn"):init() +local conn = multi:newSystemThreadedConnection("conn"):init() --- multi:newSystemThread("Thread_Test_1", function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print(THREAD:getName().." was triggered!",a,b,c) --- end) --- multi:mainloop() --- end) +multi:newSystemThread("Thread_Test_1", function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local console = THREAD.getConsole() + conn(function(a,b,c) + console.print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:mainloop() +end) --- multi:newSystemThread("Thread_Test_2", function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print(THREAD:getName().." was triggered!",a,b,c) --- end) --- multi:newAlarm(2):OnRing(function() --- console.print("Fire 2!!!") --- conn:Fire(4,5,6) --- THREAD.kill() --- end) +multi:newSystemThread("Thread_Test_2", function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local console = THREAD.getConsole() + conn(function(a,b,c) + console.print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(2):OnRing(function() + console.print("Fire 2!!!") + conn:Fire(4,5,6) + THREAD.kill() + end) --- multi:mainloop() --- end) --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print("Mainloop conn got triggered!",a,b,c) --- end) + multi:mainloop() +end) +local console = THREAD.getConsole() +conn(function(a,b,c) + console.print("Mainloop conn got triggered!",a,b,c) +end) --- alarm = multi:newAlarm(1) --- alarm:OnRing(function() --- console.print("Fire 1!!!") --- conn:Fire(1,2,3) --- end) +alarm = multi:newAlarm(1) +alarm:OnRing(function() + console.print("Fire 1!!!") + conn:Fire(1,2,3) +end) --- alarm = multi:newAlarm(3):OnRing(function() --- multi:newSystemThread("Thread_Test_3",function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print(THREAD:getName().." was triggered!",a,b,c) --- end) --- multi:newAlarm(4):OnRing(function() --- console.print("Fire 3!!!") --- conn:Fire(7,8,9) --- end) --- multi:mainloop() --- end) --- end) +alarm = multi:newAlarm(3):OnRing(function() + multi:newSystemThread("Thread_Test_3",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local console = THREAD.getConsole() + conn(function(a,b,c) + console.print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(4):OnRing(function() + console.print("Fire 3!!!") + conn:Fire(7,8,9) + end) + multi:mainloop() + end) +end) --- multi:newSystemThread("Thread_Test_4",function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local conn2 = multi:newConnection() --- local console = THREAD.getConsole() --- multi:newAlarm(2):OnRing(function() --- conn2:Fire() --- end) --- multi:newThread(function() --- console.print("Conn Test!") --- thread.hold(conn + conn2) --- console.print("It held!") --- end) --- multi:mainloop() --- end) +multi:newSystemThread("Thread_Test_4",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + local conn2 = multi:newConnection() + local console = THREAD.getConsole() + multi:newAlarm(2):OnRing(function() + conn2:Fire() + end) + multi:newThread(function() + console.print("Conn Test!") + thread.hold(conn + conn2) + console.print("It held!") + end) + multi:mainloop() +end) --- multi:mainloop() \ No newline at end of file +multi:mainloop() \ No newline at end of file