From 91d0b5f7be53738a0bacda4e347194035cb2c518 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 19 Dec 2021 01:05:34 -0500 Subject: [PATCH 01/80] added some spaces --- README.md | 7 +++---- multi/init.lua | 4 ++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b0848c7..fbc02f7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ -# Multi Version: 15.1.0 Hold the thread +# Multi Version: 16.0.0 Upgrade Complete **Key Changes** -- thread.hold has been updated to allow all variants to work as well as some new features. Check the changelog or documentation for more info. -- multi:newProccesor() Creates a process that acts like the multi namespace that can be managed independently from the mainloop. -- Connections can be added together +- All objects now use connections internally +- Updated getTasksDetails() to handle the new method of managing threads and processors Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! diff --git a/multi/init.lua b/multi/init.lua index d78cf6f..f5bfa3d 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -394,14 +394,18 @@ function multi:newBase(ins) _tid = _tid + 1 return c end + function multi:newConnector() local c = {Type = "connector"} return c end + local CRef = { Fire = function() end } + local ignoreconn = true + function multi:newConnection(protect,func,kill) local c={} c.callback = func From 544aa78d70ce227ae96fd01a1b389394b1aa79e5 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Fri, 24 Dec 2021 22:47:38 -0500 Subject: [PATCH 02/80] Updated code --- changes.md | 169 ++++++++++----- multi/init.lua | 541 ++++++++++++++++++++++--------------------------- 2 files changed, 355 insertions(+), 355 deletions(-) diff --git a/changes.md b/changes.md index bc444c4..0d035fe 100644 --- a/changes.md +++ b/changes.md @@ -3,6 +3,97 @@ Table of contents --- [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.0 - Upgrade Complete + +Full Update Showcase + +```lua + +``` + +Added: +--- + +- multi:newTLoop() member functions + - `Loop:Set(set)` - Sets the time to wait for the TLoop + +- multi:newStep() member functions + - `Step:Count(count)` - Sets the amount a step should count by + +- multi:newTStep() member functions + - `TStep:Set(set)` - Sets the time to wait for the TStep + + +Changed: +--- + +- Connection Objects now pass on the parent object if created on a multiobj. This was to allow chaining to work properly with the new update + + ```lua + package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path + multi,thread = require("multi"):init() + + loop = multi:newTLoop() + + function loop:testing() + print("testing haha") + end + + + loop:Set(1) + t = loop:OnLoop(function() + print("Looping...") + end):testing() + + multi:mainloop() + + --[[Returns as expected: + + testing haha + Looping... + Looping... + Looping... + ... + Looping... + Looping... + Looping... + ]] + ``` + + While chaining on the OnSomeEventMethod() wasn't really a used feature, I still wanted to keep it just incase someone was relying on this working. And it does have it uses + +- All Multi Objects now use Connection objects + + `multiobj:OnSomeEvent(func)` or `multiobj.OnSomeEvent(func)` + +- Connection Objects no longer Fire with syntax sugar when attached to an object: + + `multiobj:OnSomeEvent(arg1,arg2.arg3)` No longer triggers the Fire event. As part of the update to make all objects use connections internally this little used feature had to be scrapped! + +- multi:newTStep now derives it's functionality from multi:newStep (Cut's down on code length a bit) + +- + +### Developer Note: + + +Connections are some of the most complex objects that this library has outside of some of the system threaded stuff. I tend to add features to connection objects quite often. Just last update connections can be "added" together creating a temp connection that only triggers when all of the added connections got triggered as well. Thinking about the possibilities this could give developers using the library I had to changed the base classes to use connections. + +The best part about this is that connections allow for greater control over an object's events. You can add and remove events that have been connected to as well as a lot of other things. Reference the documentation [here](./Documentation.md#non-actor-connections) + + +Removed: +--- + +Fixed: +--- +- [Issue](https://github.com/rayaman/multi/issues/30) with Lanes crashing the lua state. Fixed it with pcall +- [Issue](https://github.com/rayaman/multi/issues/29) where System threaded functions not up to date with threaded functions + +ToDo: +--- + + # Update 15.1.0 - Hold the thread! Full Update Showcase @@ -98,9 +189,11 @@ multi:mainloop() Added: --- -## multi:newSystemThreadedJobQueue(n) isEmpty() - -- returns true if the queue is empty, false if there are items in the queue. +- multi:newSystemThreadedJobQueue(n) + + `queue:isEmpty()` + + Returns true if the queue is empty, false if there are items in the queue. **Note:** a queue might be empty, but the job may still be running and not finished yet! Also if a registered function is called directly instead of pushed, it will not reflect inside the queue until the next cycle! @@ -413,6 +506,7 @@ multi:mainloop() Note: --- This was supposed to be released over a year ago, but work and other things got in my way. Pesudo Threading now works. The goal of this is so you can write modules that can be scaled up to utilize threading features when available. + Added: --- - multi:newISOThread(name,func,env) @@ -790,15 +884,18 @@ Added: -- If the created function encounters an error, it will return nil, the error message! - special variable multi.NIL was added to allow error handling in threaded functions. -- multi.NIL can be used in to force a nil value when using thread.hold() -- All functions created in the root of a thread are now converted to threaded functions, which allow for wait and connect features. **Note:** these functions are local to the function! And are only converted if they aren't set as local! Otherwise the function +- All functions created in the root of a thread are now converted to threaded functions, which allow for wait and connect features. + + **Note:** these functions are local to the function! And are only converted if they aren't set as local! Otherwise the function is converted into a threaded function + - lanes threads can now have their priority set using: sThread.priority = --- thread.Priority_Core --- thread.Priority_High --- thread.Priority_Above_Normal --- thread.Priority_Normal --- thread.Priority_Below_Normal --- thread.Priority_Low --- thread.Priority_Idle + - thread.Priority_Core + - thread.Priority_High + - thread.Priority_Above_Normal + - thread.Priority_Normal + - thread.Priority_Below_Normal + - thread.Priority_Low + - thread.Priority_Idle - thread.hold() and multi.hold() now accept connections as an argument. See example below ```lua @@ -1203,7 +1300,7 @@ L: 6543 I: 1635 ~n=n*4 ``` -P3 Ignores using a basic funceion and instead bases its processing time on the amount of cpu time is there. If cpu-time is low and a process is set at a lower priority it will get its time reduced. There is no formula, at idle almost all process work at the same speed! +P3 Ignores using a basic function and instead bases its processing time on the amount of cpu time is there. If cpu-time is low and a process is set at a lower priority it will get its time reduced. There is no formula, at idle almost all process work at the same speed! ``` C: 2120906 H: 2120906 @@ -1218,10 +1315,10 @@ Auto Priority works by seeing what should be set high or low. Due to lua not hav **Improved:** - Performance at the base level has been doubled! On my machine benchmark went from ~9mil to ~20 mil steps/s. -Note: If you write slow code this library's improbements wont make much of a difference. +Note: If you write slow code this library's improvements wont make much of a difference. - Loops have been optimised as well! Being the most used objects I felt they needed to be made as fast as possible -I usually give an example of the changes made, but this time I have an explantion for multi.nextStep(). It's not an entirely new feature since multi:newJob() does something like this, but is completely different. nextStep addes a function that is executed first on the next step. If multiple things are added to next step, then they will be executed in the order that they were added. +I usually give an example of the changes made, but this time I have an explantion for `multi.nextStep()`. It's not an entirely new feature since multi:newJob() does something like this, but is completely different. nextStep adds a function that is executed first on the next step. If multiple things are added to next step, then they will be executed in the order that they were added. Note: The upper limit of this libraries performance on my machine is ~39mil. This is simply a while loop counting up from 0 and stops after 1 second. The 20mil that I am currently getting is probably as fast as it can get since its half of the max performance possible, and each layer I have noticed that it doubles complexity. Throughout the years with this library I have seen massive improvements in speed. In the beginning we had only ~2000 steps per second. Fast right? then after some tweaks we went to about 300000 steps per second, then 600000. Some more tweaks brought me to ~1mil steps per second, then to ~4 mil then ~9 mil and now finally ~20 mil... the doubling effect that i have now been seeing means that odds are I have reach the limit. I will aim to add more features and optimize individule objects. If its possible to make the library even faster then I will go for it. @@ -1235,7 +1332,7 @@ Fixed: Changed: --- - thread.hold() now returns the arguments that were pass by the event function -- event objexts now contain a copy of what returns were made by the function that called it in a table called returns that exist inside of the object +- event objects now contain a copy of what returns were made by the function that called it in a table called returns that exist inside of the object ```lua package.path="?/init.lua;?.lua;"..package.path @@ -1935,41 +2032,6 @@ GLOBAL,sThread=require("multi.integration.loveManager").init() -- load the love2 -- Also, each thread has a .1 second delay! This is used to generate a random value for each thread! require("core.GuiManager") gui.ff.Color=Color.Black -function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a channel on both ends - local c={} - c.name=name - if love then - if love.thread then - function c:init() - self.chan=love.thread.getChannel(self.name) - function self:push(v) - self.chan:push(v) - end - function self:pop() - return self.chan:pop() - end - GLOBAL[self.name]=self - return self - end - return c - else - error("Make sure you required the love.thread module!") - end - else - c.linda=lanes.linda() - function c:push(v) - self.linda:send("Q",v) - end - function c:pop() - return ({self.linda:receive(0,"Q")})[2] - end - function c:init() - return self - end - GLOBAL[name]=c - end - return c -end queue=multi:newSystemThreadedQueue("QUEUE"):init() queue:push("This is a test") queue:push("This is a test2") @@ -2251,7 +2313,7 @@ Change: Upcomming: --- -- Threaded objects wrapped in corutines, so you can hold/sleep without problems! +- Threaded objects wrapped in coroutines, so you can hold/sleep without problems! # Update: 1.4.0 (3/20/2017) Added: @@ -2325,8 +2387,10 @@ Added: # Update: 1.2.0 (12.31.2016) Added: --- -- connectionobj.getConnection(name) — returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) if you can orginize data before hand you can route info to certain connections thus saving a lot of cpu time. +- connectionobj.getConnection(name) — returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) if you can organize data before hand you can route info to certain connections thus saving a lot of cpu time. + **NOTE:** Only one name per each connection... you can't have 2 of the same names in a dictonary... the last one will be used + Changed: --- - Started keeping track of dates @@ -2385,6 +2449,7 @@ Changed: Changed: --- - Everything, complete restructuring of the library from function based to object based. Resembles the modern version of the library + Added: --- - Love2d support basic diff --git a/multi/init.lua b/multi/init.lua index f5bfa3d..3fc6ee8 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -198,214 +198,7 @@ function multi:getTasksDetails(t) end --Helpers - --- Used with ISO Threads -local function isolateFunction(func,env) - local dmp = string.dump(func) - local env = env or {} - if setfenv then - local f = loadstring(dmp,"IsolatedThread_PesudoThreading") - setfenv(f,env) - return f - else - return load(dmp,"IsolatedThread_PesudoThreading","bt",env) - end -end - -function multi:Break() - self:Pause() - self.Active=nil - for i=1,#self.ender do - if self.ender[i] then - self.ender[i](self) - end - end -end - -function multi:OnBreak(func) - table.insert(self.ender,func) -end - -function multi:isPaused() - return not(self.Active) -end - -function multi:isActive() - return self.Active -end - -function multi:getType() - return self.Type -end - --- Advance Timer stuff -function multi:SetTime(n) - if not n then n=3 end - local c=self:newBase() - c.Type='timemaster' - c.timer=self:newTimer() - c.timer:Start() - c.set=n - c.link=self - self._timer=c.timer - function c:Act() - if self.timer:Get()>=self.set then - self.link:Pause() - for i=1,#self.link.funcTM do - self.link.funcTM[i](self.link) - end - - self:Destroy() - end - end - return self -end - -function multi:ResolveTimer(...) - self._timer:Pause() - for i=1,#self.funcTMR do - self.funcTMR[i](self,...) - end - self:Pause() - return self -end - -function multi:OnTimedOut(func) - self.funcTM[#self.funcTM+1]=func - return self -end - -function multi:OnTimerResolved(func) - self.funcTMR[#self.funcTMR+1]=func - return self -end - --- Timer stuff done -multi.PausedObjects = {} -function multi:Pause() - if self.Type=='rootprocess' then - multi.print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") - else - self.Active=false - local loop = self.Parent.Mainloop - for i=1,#loop do - if loop[i] == self then - multi.PausedObjects[self] = true - table.remove(loop,i) - break - end - end - end - return self -end - -function multi:Resume() - if self.Type=='process' or self.Type=='rootprocess' then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self.Active==false then - table.insert(self.Parent.Mainloop,self) - multi.PausedObjects[self] = nil - self.Active=true - end - end - return self -end - -function multi:Destroy() - if self.Type=='process' or self.Type=='rootprocess' then - local c=self:getChildren() - for i=1,#c do - self.OnObjectDestroyed:Fire(c[i]) - c[i]:Destroy() - end - local new = {} - for th,proc in pairs(globalThreads) do - if proc == self then - th:Destroy() - table.remove(globalThreads,th) - else - new[th]=proc - end - end - globalThreads = new - multi.setType(self,multi.DestroyedObj) - else - for i=1,#self.Parent.Mainloop do - if self.Parent.Mainloop[i]==self then - self.Parent.OnObjectDestroyed:Fire(self) - table.remove(self.Parent.Mainloop,i) - self.Destroyed = true - break - end - end - multi.setType(self,multi.DestroyedObj) - end - return self -end - -function multi:Reset(n) - self:Resume() - return self -end - -function multi:isDone() - return self.Active~=true -end - -function multi:create(ref) - multi.OnObjectCreated:Fire(ref,self) - return self -end - -function multi:setName(name) - self.Name = name - return self -end - ---Constructors [CORE] -local _tid = 0 -function multi:newBase(ins) - if not(self.Type=='rootprocess' or self.Type=='process' or self.Type=='queue' or self.Type == 'sandbox') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='process' or self.Type=='queue' or self.Type=='sandbox' then - setmetatable(c, {__index = multi}) - else - setmetatable(c, {__index = multi}) - end - c.Active=true - c.func={} - c.funcTM={} - c.funcTMR={} - c.ender={} - c.TID = _tid - c.Act=function() end - c.Parent=self - c.creationTime = os.clock() - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - _tid = _tid + 1 - return c -end - -function multi:newConnector() - local c = {Type = "connector"} - return c -end - -local CRef = { - Fire = function() end -} - local ignoreconn = true - function multi:newConnection(protect,func,kill) local c={} c.callback = func @@ -416,7 +209,9 @@ function multi:newConnection(protect,func,kill) if type(t)=="table" then for i,v in pairs(t) do if v==self then - return self:Fire(select(2,...)) + local ref = self:connect(select(2,...)) + ref.root_link = select(1,...) + return ref end end return self:connect(...) @@ -535,9 +330,23 @@ function multi:newConnection(protect,func,kill) return self:connect(...) end } - setmetatable(temp,{__call=function(s,...) - return self:connect(...) - end}) + 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, + }) function temp:Fire(...) if self.Parent.lock then return end if self.Parent.protect then @@ -597,6 +406,190 @@ function multi:newConnection(protect,func,kill) return c end +-- Used with ISO Threads +local function isolateFunction(func,env) + local dmp = string.dump(func) + local env = env or {} + if setfenv then + local f = loadstring(dmp,"IsolatedThread_PesudoThreading") + setfenv(f,env) + return f + else + return load(dmp,"IsolatedThread_PesudoThreading","bt",env) + end +end + +function multi:Break() + self:Pause() + self.Active=nil + self.OnBreak:Fire(self) +end + +function multi:isPaused() + return not(self.Active) +end + +function multi:isActive() + return self.Active +end + +function multi:getType() + return self.Type +end + +-- Advance Timer stuff +function multi:SetTime(n) + if not n then n=3 end + local c=self:newBase() + c.Type='timemaster' + c.timer=self:newTimer() + c.timer:Start() + c.set=n + c.link=self + c.OnTimedOut = multi:newConnection() + c.OnTimerResolved = multi:newConnection() + self._timer=c.timer + function c:Act() + if self.timer:Get()>=self.set then + self.link:Pause() + self.OnTimedOut:Fire(self.link) + self:Destroy() + end + end + return self +end + +function multi:ResolveTimer(...) + self._timer:Pause() + self.OnTimerResolved:Fire(self,...) + self:Pause() + return self +end + +-- Timer stuff done +multi.PausedObjects = {} +function multi:Pause() + if self.Type=='rootprocess' then + multi.print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") + else + self.Active=false + local loop = self.Parent.Mainloop + for i=1,#loop do + if loop[i] == self then + multi.PausedObjects[self] = true + table.remove(loop,i) + break + end + end + end + return self +end + +function multi:Resume() + if self.Type=='process' or self.Type=='rootprocess' then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self.Active==false then + table.insert(self.Parent.Mainloop,self) + multi.PausedObjects[self] = nil + self.Active=true + end + end + return self +end + +function multi:Destroy() + if self.Type=='process' or self.Type=='rootprocess' then + local c=self:getChildren() + for i=1,#c do + self.OnObjectDestroyed:Fire(c[i]) + c[i]:Destroy() + end + local new = {} + for th,proc in pairs(globalThreads) do + if proc == self then + th:Destroy() + table.remove(globalThreads,th) + else + new[th]=proc + end + end + globalThreads = new + multi.setType(self,multi.DestroyedObj) + else + for i=1,#self.Parent.Mainloop do + if self.Parent.Mainloop[i]==self then + self.Parent.OnObjectDestroyed:Fire(self) + table.remove(self.Parent.Mainloop,i) + self.Destroyed = true + break + end + end + multi.setType(self,multi.DestroyedObj) + end + return self +end + +function multi:Reset(n) + self:Resume() + return self +end + +function multi:isDone() + return self.Active~=true +end + +function multi:create(ref) + self.OnObjectCreated:Fire(ref,self) + return self +end + +function multi:setName(name) + self.Name = name + return self +end + +--Constructors [CORE] +local _tid = 0 +function multi:newBase(ins) + if not(self.Type=='rootprocess' or self.Type=='process' or self.Type=='queue' or self.Type == 'sandbox') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='process' or self.Type=='queue' or self.Type=='sandbox' then + setmetatable(c, {__index = multi}) + else + setmetatable(c, {__index = multi}) + end + c.Active=true + c.func={} + c.funcTM={} + c.funcTMR={} + c.OnBreak = multi:newConnection() + c.TID = _tid + c.Act=function() end + c.Parent=self + c.creationTime = os.clock() + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + _tid = _tid + 1 + return c +end + +function multi:newConnector() + local c = {Type = "connector"} + return c +end + +local CRef = { + Fire = function() end +} + multi.OnObjectCreated=multi:newConnection() multi.OnObjectDestroyed=multi:newConnection() multi.OnLoad = multi:newConnection(nil,nil,true) @@ -643,19 +636,14 @@ function multi:newEvent(task) if t[1] then self:Pause() self.returns = t - for _E=1,#self.func do - self.func[_E](self) - end + c.OnEvent:Fire(self) end end function c:SetTask(func) self.Task=func return self end - function c:OnEvent(func) - table.insert(self.func,func) - return self - end + c.OnEvent = self:newConnection() self:setPriority("core") multi:create(c) return c @@ -668,9 +656,7 @@ function multi:newUpdater(skip) function c:Act() if self.pos>=self.skip then self.pos=0 - for i=1,#self.func do - self.func[i](self) - end + self.OnUpdate:Fire(self) end self.pos=self.pos+1 end @@ -678,7 +664,7 @@ function multi:newUpdater(skip) self.skip=n return self end - c.OnUpdate=self.OnMainConnect + c.OnUpdate=self:newConnection() multi:create(c) return c end @@ -693,9 +679,7 @@ function multi:newAlarm(set) if clock()-t>=self.set then self:Pause() self.Active=false - for i=1,#self.func do - self.func[i](self) - end + self.OnRing:Fire(self) t = clock() end end @@ -710,10 +694,7 @@ function multi:newAlarm(set) t = clock() return self end - function c:OnRing(func) - table.insert(self.func,func) - return self - end + c.OnRing = self:newConnection() function c:Pause() count = clock() self.Parent.Pause(self) @@ -726,18 +707,12 @@ function multi:newLoop(func) local c=self:newBase() c.Type='loop' local start=clock() - local funcs = {} - if func then - funcs={func} - end function c:Act() - for i=1,#funcs do - funcs[i](self,clock()-start) - end + self.OnLoop:Fire(self,clock()-start) end - function c:OnLoop(func) - table.insert(funcs,func) - return self + c.OnLoop = self:newConnection() + if func then + c.OnLoop(func) end multi:create(c) return c @@ -768,6 +743,7 @@ function multi:newFunction(func) multi:create(c) return c end + function multi:newStep(start,reset,count,skip) local c=self:newBase() think=1 @@ -789,19 +765,13 @@ function multi:newStep(start,reset,count,skip) if self~=nil then if self.spos==0 then if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self,self.pos) + self.OnStart:Fire(self) end + self.OnStep:Fire(self,self.pos) self.pos=self.pos+self.count if self.pos-self.count==self.endAt then self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end + self.OnEnd:Fire(self) self.pos=self.start end end @@ -812,22 +782,16 @@ function multi:newStep(start,reset,count,skip) end end c.Reset=c.Resume - function c:OnStart(func) - table.insert(self.funcS,func) - return self - end - function c:OnStep(func) - table.insert(self.func,1,func) - return self - end - function c:OnEnd(func) - table.insert(self.funcE,func) - return self - end + c.OnStart = self:newConnection() + c.OnStep = self:newConnection() + c.OnEnd = self:newConnection() function c:Break() self.Active=nil return self end + function c:Count(count) + self.count = count + end function c:Update(start,reset,count,skip) self.start=start or self.start self.endAt=reset or self.endAt @@ -846,19 +810,16 @@ function multi:newTLoop(func,set) c.timer=self:newTimer() c.life=0 c:setPriority("Low") - if func then - c.func={func} - end function c:Act() if self.timer:Get()>=self.set then - print("Acting...") self.life=self.life+1 self.timer:Reset() - for i=1,#self.func do - self.func[i](self,self.life) - end + self.OnLoop:Fire(self,self.life) end end + function c:Set(set) + self.set = set + end function c:Resume() self.Parent.Resume(self) self.timer:Resume() @@ -869,9 +830,9 @@ function multi:newTLoop(func,set) self.Parent.Pause(self) return self end - function c:OnLoop(func) - table.insert(self.func,func) - return self + c.OnLoop = self:newConnection() + if func then + c.OnLoop(func) end multi:create(c) return c @@ -880,20 +841,12 @@ function multi:setTimeout(func,t) multi:newThread(function() thread.sleep(t) func() end) end function multi:newTStep(start,reset,count,set) - local c=self:newBase() - think=1 + local c=self:newStep(start,reset,count) c.Type='tstep' c:setPriority("Low") - c.start=start or 1 local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} c.timer=clock() c.set=set or 1 - c.funcS={} function c:Update(start,reset,count,set) self.start=start or self.start self.pos=self.start @@ -908,38 +861,19 @@ function multi:newTStep(start,reset,count,set) if clock()-self.timer>=self.set then self:Reset() if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self,self.pos) + self.OnStart:Fire(self) end + self.OnStep:Fire(self,self.pos) self.pos=self.pos+self.count if self.pos-self.count==self.endAt then self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end + self.OnEnd:Fire(self) self.pos=self.start end end end - function c:OnStart(func) - table.insert(self.funcS,func) - return self - end - function c:OnStep(func) - table.insert(self.func,func) - return self - end - function c:OnEnd(func) - table.insert(self.funcE,func) - return self - end - function c:Break() - self.Active=nil - return self + function c:Set(set) + self.set = set end function c:Reset(n) if n then self.set=n end @@ -950,6 +884,7 @@ function multi:newTStep(start,reset,count,set) multi:create(c) return c end + local scheduledjobs = {} local sthread function multi:scheduleJob(time,func) From dff19d865f7f704c6f554da0083e246f762cffe0 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 1 Jan 2022 23:20:48 -0500 Subject: [PATCH 03/80] Updated license --- LICENSE | 2 +- changes.md | 5 +- multi/compat/love2d.lua | 2 +- multi/compat/lovr.lua | 2 +- multi/init.lua | 143 +++++++------ multi/integration/lanesManager/extensions.lua | 2 +- multi/integration/lanesManager/init.lua | 202 ++++++------------ multi/integration/lanesManager/threads.lua | 2 +- multi/integration/loveManager/extensions.lua | 2 +- multi/integration/loveManager/init.lua | 2 +- multi/integration/loveManager/threads.lua | 2 +- multi/integration/lovrManager/extensions.lua | 2 +- multi/integration/lovrManager/init.lua | 2 +- multi/integration/lovrManager/threads.lua | 2 +- multi/integration/luvitManager.lua | 2 +- multi/integration/networkManager/channel.lua | 2 +- .../integration/networkManager/childNode.lua | 2 +- .../integration/networkManager/clientSide.lua | 2 +- multi/integration/networkManager/cmds.lua | 2 +- .../integration/networkManager/extensions.lua | 2 +- multi/integration/networkManager/init.lua | 2 +- .../integration/networkManager/masterNode.lua | 2 +- multi/integration/networkManager/node.lua | 2 +- .../networkManager/nodeManager.lua | 2 +- .../integration/networkManager/serverSide.lua | 2 +- multi/integration/networkManager/threads.lua | 2 +- multi/integration/networkManager/utils.lua | 2 +- .../integration/pesudoManager/extensions.lua | 2 +- multi/integration/pesudoManager/threads.lua | 2 +- test3.lua | 17 ++ 30 files changed, 186 insertions(+), 233 deletions(-) create mode 100644 test3.lua diff --git a/LICENSE b/LICENSE index 3b661ec..f0e3a38 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Ryan Ward +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 diff --git a/changes.md b/changes.md index 0d035fe..ec270c6 100644 --- a/changes.md +++ b/changes.md @@ -1,7 +1,7 @@ # Changelog Table of contents --- -[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.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.0 - Upgrade Complete @@ -26,7 +26,6 @@ Added: Changed: --- - - Connection Objects now pass on the parent object if created on a multiobj. This was to allow chaining to work properly with the new update ```lua @@ -76,7 +75,6 @@ Changed: ### Developer Note: - Connections are some of the most complex objects that this library has outside of some of the system threaded stuff. I tend to add features to connection objects quite often. Just last update connections can be "added" together creating a temp connection that only triggers when all of the added connections got triggered as well. Thinking about the possibilities this could give developers using the library I had to changed the base classes to use connections. The best part about this is that connections allow for greater control over an object's events. You can add and remove events that have been connected to as well as a lot of other things. Reference the documentation [here](./Documentation.md#non-actor-connections) @@ -84,6 +82,7 @@ The best part about this is that connections allow for greater control over an o Removed: --- +Nothing Fixed: --- diff --git a/multi/compat/love2d.lua b/multi/compat/love2d.lua index f86191a..73110e3 100644 --- a/multi/compat/love2d.lua +++ b/multi/compat/love2d.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/compat/lovr.lua b/multi/compat/lovr.lua index c37906d..8c18b37 100644 --- a/multi/compat/lovr.lua +++ b/multi/compat/lovr.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/init.lua b/multi/init.lua index 3fc6ee8..1a93d1c 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 @@ -1148,81 +1148,88 @@ function thread.pushStatus(...) local t = thread.getRunningThread() t.statusconnector:Fire(...) end -function thread:newFunction(func,holdme) - local tfunc = {} - tfunc.Active = true - function tfunc:Pause() - self.Active = false - end - function tfunc:Resume() - self.Active = true - end - function tfunc:holdMe(b) - holdme = b - end - local function noWait() - return nil, "Function is paused" - end - local rets, err - local function wait(no) - if thread.isThread() and not (no) then - return multi.hold(function() - if err then - return multi.NIL, err - elseif rets then - return cleanReturns((rets[1] or multi.NIL),rets[2],rets[3],rets[4],rets[5],rets[6],rets[7],rets[8],rets[9],rets[10],rets[11],rets[12],rets[13],rets[14],rets[15],rets[16]) - end - end) - else - while not rets and not err do - multi.scheduler:Act() - end - if err then - return nil,err - end - return cleanReturns(rets[1],rets[2],rets[3],rets[4],rets[5],rets[6],rets[7],rets[8],rets[9],rets[10],rets[11],rets[12],rets[13],rets[14],rets[15],rets[16]) +function thread:newFunctionBase(generator,holdme) + return function() + local tfunc = {} + tfunc.Active = true + function tfunc:Pause() + self.Active = false end - end - tfunc.__call = function(t,...) - if not t.Active then - if holdme then - return nil, "Function is paused" + function tfunc:Resume() + self.Active = true + end + function tfunc:holdMe(b) + holdme = b + end + local function noWait() + return nil, "Function is paused" + end + local rets, err + local function wait(no) + if thread.isThread() and not (no) then + return multi.hold(function() + if err then + return multi.NIL, err + elseif rets then + return cleanReturns((rets[1] or multi.NIL),rets[2],rets[3],rets[4],rets[5],rets[6],rets[7],rets[8],rets[9],rets[10],rets[11],rets[12],rets[13],rets[14],rets[15],rets[16]) + end + end) + else + while not rets and not err do + multi.scheduler:Act() + end + if err then + return nil,err + end + return cleanReturns(rets[1],rets[2],rets[3],rets[4],rets[5],rets[6],rets[7],rets[8],rets[9],rets[10],rets[11],rets[12],rets[13],rets[14],rets[15],rets[16]) end - return { + end + tfunc.__call = function(t,...) + if not t.Active then + if holdme then + return nil, "Function is paused" + end + return { + isTFunc = true, + wait = noWait, + connect = function(f) + f(nil,"Function is paused") + end + } + end + local t = generator(...) --multi.getCurrentProcess():newThread("TempThread",func,...) + t.OnDeath(function(self,status,...) rets = {...} end) + t.OnError(function(self,e) err = e end) + if holdme then + return wait() + end + local temp = { + OnStatus = multi:newConnection(), + OnError = multi:newConnection(), + OnReturn = multi:newConnection(), isTFunc = true, - wait = noWait, + wait = wait, connect = function(f) - f(nil,"Function is paused") + local tempConn = multi:newConnection() + t.OnDeath(function(self,status,...) if f then f(...) else tempConn:Fire(...) end end) + t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) + return tempConn end } - end - local t = multi.getCurrentProcess():newThread("TempThread",func,...) - t.OnDeath(function(self,status,...) rets = {...} end) - t.OnError(function(self,e) err = e end) - if holdme then - return wait() + t.OnDeath(function(self,status,...) temp.OnReturn:Fire(...) end) + t.OnError(function(self,err) temp.OnError:Fire(err) end) + t.linkedFunction = temp + t.statusconnector = temp.OnStatus + return temp end - local temp = { - OnStatus = multi:newConnection(), - OnError = multi:newConnection(), - OnReturn = multi:newConnection(), - isTFunc = true, - wait = wait, - connect = function(f) - local tempConn = multi:newConnection() - t.OnDeath(function(self,status,...) if f then f(...) else tempConn:Fire(...) end end) - t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) - return tempConn - end - } - t.OnDeath(function(self,status,...) temp.OnReturn:Fire(...) end) - t.OnError(function(self,err) temp.OnError:Fire(err) end) - t.linkedFunction = temp - t.statusconnector = temp.OnStatus - return temp + setmetatable(tfunc,tfunc) + return tfunc end - setmetatable(tfunc,tfunc) - return tfunc +end +function thread:newFunction(func,holdme) + return thread:newFunctionBase(function(...) + return multi.getCurrentProcess():newThread("TempThread",func,...) + end,holdme)() end -- A cross version way to set enviroments, not the same as fenv though diff --git a/multi/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua index d4fd404..c582c17 100644 --- a/multi/integration/lanesManager/extensions.lua +++ b/multi/integration/lanesManager/extensions.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 16bdcf4..9526c4f 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 @@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] package.path = "?/init.lua;?.lua;" .. package.path -multi, thread = require("multi").init() -- get it all and have it on all lanes +multi, thread = require("multi"):init() -- get it all and have it on all lanes if multi.integration then -- This allows us to call the lanes manager from supporting modules without a hassle return { init = function() @@ -31,15 +31,18 @@ if multi.integration then -- This allows us to call the lanes manager from suppo } end -- Step 1 get lanes -lanes = require("lanes").configure() +lanes = require("lanes").configure({allocator="protected",verbose_errors=""}) multi.SystemThreads = {} multi.isMainThread = true + function multi:canSystemThread() return true end + function multi:getPlatform() return "lanes" end + -- Step 2 set up the Linda objects local __GlobalLinda = lanes.linda() -- handles global stuff local __SleepingLinda = lanes.linda() -- handles sleeping stuff @@ -50,118 +53,50 @@ multi:newLoop(function() print(unpack(data)) end end) -local GLOBAL,THREAD = require("multi.integration.lanesManager.threads").init(__GlobalLinda,__SleepingLinda) -local threads = {} +local GLOBAL,THREAD = {},{}-- require("multi.integration.lanesManager.threads").init(__GlobalLinda,__SleepingLinda) local count = 1 local started = false local livingThreads = {} +local threads = {} function THREAD:newFunction(func,holdme) - local tfunc = {} - tfunc.Active = true - function tfunc:Pause() - self.Active = false - end - function tfunc:Resume() - self.Active = true - end - function tfunc:holdMe(b) - holdme = b - end - local function noWait() - return nil, "Function is paused" - end - local rets, err - local function wait(no) - if thread.isThread() and not (no) then - return multi.hold(function() - if err then - return nil, err - elseif rets then - return unpack(rets) - end - end) - else - while not rets and not err do - multi.scheduler:Act() - end - if err then - return nil,err - end - return unpack(rets) - end - end - tfunc.__call = function(t,...) - if not t.Active then - if holdme then - return nil, "Function is paused" - end - return { - isTFunc = true, - wait = noWait, - connect = function(f) - f(nil,"Function is paused") - end - } - end - local t = multi:newSystemThread("SystemThreadedFunction",func,...) - t.OnDeath(function(self,...) rets = {...} end) - t.OnError(function(self,e) err = e end) - if holdme then - return wait() - end - local temp = { - OnStatus = multi:newConnection(), - OnError = multi:newConnection(), - OnReturn = multi:newConnection(), - isTFunc = true, - wait = wait, - connect = function(f) - local tempConn = multi:newConnection() - t.OnDeath(function(self,...) if f then f(...) else tempConn:Fire(...) end end) - t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) - return tempConn - end - } - t.OnDeath(function(self,...) temp.OnReturn:Fire(...) end) - t.OnError(function(self,err) temp.OnError:Fire(err) end) - t.linkedFunction = temp - t.statusconnector = temp.OnStatus - return temp - end - setmetatable(tfunc,tfunc) - return tfunc + return thread:newFunctionBase(function(...) + return multi:newSystemThread("TempSystemThread",func,...) + end,holdme)() end function multi:newSystemThread(name, func, ...) - multi.InitSystemThreadErrorHandler() - rand = math.random(1, 10000000) + --multi.InitSystemThreadErrorHandler() + local rand = math.random(1, 10000000) + local return_linda = lanes.linda() local c = {} - local __self = c c.name = name c.Name = name c.Id = count c.loadString = {"base","package","os","io","math","table","string","coroutine"} livingThreads[count] = {true, name} + c.returns = return_linda c.Type = "sthread" c.creationTime = os.clock() c.alive = true c.priority = THREAD.Priority_Normal - local args = {...} - multi:newThread(function() - c.thread = lanes.gen(table.concat(c.loadString,","), - { - globals={ -- Set up some globals - THREAD_NAME=name, - THREAD_ID=count, - THREAD = THREAD, - GLOBAL = GLOBAL, - _Console = __ConsoleLinda - }, - priority=c.priority - },func)(unpack(args)) - thread.kill() - end) + c.thread = lanes.gen("*", + { + globals={ -- Set up some globals + THREAD_NAME = name, + THREAD_ID = count, + THREAD = THREAD, + GLOBAL = GLOBAL, + _Console = __ConsoleLinda + }, + priority=c.priority + },function(...) + local has_error = true + return_linda:set("returns",{func(...)}) + has_error = false + --error("thread killed") + print("Thread ending") + end)(...) count = count + 1 function c:kill() self.thread:cancel() @@ -171,61 +106,56 @@ function multi:newSystemThread(name, func, ...) table.insert(multi.SystemThreads, c) c.OnDeath = multi:newConnection() c.OnError = multi:newConnection() - GLOBAL["__THREADS__"] = livingThreads + --GLOBAL["__THREADS__"] = livingThreads return c end -local function detectLuaError(str) - return type(str)=="string" and str:match("%.lua:%d*:") -end - -local function tableLen(tab) - local len = 0 - for i,v in pairs(tab) do - len = len + 1 - end - return len -end - function multi.InitSystemThreadErrorHandler() if started == true then return end started = true - multi:newThread("ThreadErrorHandler",function() + multi:newThread("SystemThreadScheduler",function() local threads = multi.SystemThreads while true do - thread.sleep(.1) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough. + thread.sleep(.01) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough. for i = #threads, 1, -1 do - local _,data = pcall(function() - return {threads[i].thread:join(1)} - end) - local v, err, t = data[1],data[2],data[3] - if detectLuaError(err) then - if err:find("Thread was killed!\1") then - livingThreads[threads[i].Id] = {false, threads[i].Name} - threads[i].alive = false - threads[i].OnDeath:Fire(threads[i],nil,"Thread was killed!") - GLOBAL["__THREADS__"] = livingThreads - table.remove(threads, i) - else - threads[i].OnError:Fire(threads[i], err, "Error in systemThread: '" .. threads[i].name .. "' <" .. err .. ">") - threads[i].alive = false - livingThreads[threads[i].Id] = {false, threads[i].Name} - GLOBAL["__THREADS__"] = livingThreads - table.remove(threads, i) - end - elseif tableLen(data)>0 then - livingThreads[threads[i].Id] = {false, threads[i].Name} - threads[i].alive = false - threads[i].OnDeath:Fire(threads[i],unpack(data)) - GLOBAL["__THREADS__"] = livingThreads + local status = threads[i].thread.status + local temp = threads[i] + if status == "done" or temp.returns:get("returns") then + livingThreads[temp.Id] = {false, temp.Name} + temp.alive = false + temp.OnDeath:Fire(temp,nil,unpack(({temp.returns:receive(0, "returns")})[2])) + --GLOBAL["__THREADS__"] = livingThreads + --print(temp.thread:cancel(10,true)) + table.remove(threads, i) + elseif status == "running" then + -- + elseif status == "waiting" then + -- + elseif status == "error" then + livingThreads[temp.Id] = {false, temp.Name} + temp.alive = false + temp.OnError:Fire(temp,nil,unpack(temp.returns:receive(0,"returns"))) + --GLOBAL["__THREADS__"] = livingThreads + table.remove(threads, i) + elseif status == "cancelled" then + livingThreads[temp.Id] = {false, temp.Name} + temp.alive = false + temp.OnError:Fire(temp,nil,"thread_cancelled") + --GLOBAL["__THREADS__"] = livingThreads + table.remove(threads, i) + elseif status == "killed" then + livingThreads[temp.Id] = {false, temp.Name} + temp.alive = false + temp.OnError:Fire(temp,nil,"thread_killed") + --GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) end end end end).OnError(function(...) - print("Error!",...) + print(...) end) end multi.print("Integrated Lanes!") diff --git a/multi/integration/lanesManager/threads.lua b/multi/integration/lanesManager/threads.lua index 6d9d13d..04277d1 100644 --- a/multi/integration/lanesManager/threads.lua +++ b/multi/integration/lanesManager/threads.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/loveManager/extensions.lua b/multi/integration/loveManager/extensions.lua index 1673611..9424902 100644 --- a/multi/integration/loveManager/extensions.lua +++ b/multi/integration/loveManager/extensions.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua index 16f9b07..5cc490d 100644 --- a/multi/integration/loveManager/init.lua +++ b/multi/integration/loveManager/init.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/loveManager/threads.lua b/multi/integration/loveManager/threads.lua index a4ee947..97280c8 100644 --- a/multi/integration/loveManager/threads.lua +++ b/multi/integration/loveManager/threads.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/lovrManager/extensions.lua b/multi/integration/lovrManager/extensions.lua index d2d5143..36747ce 100644 --- a/multi/integration/lovrManager/extensions.lua +++ b/multi/integration/lovrManager/extensions.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/lovrManager/init.lua b/multi/integration/lovrManager/init.lua index 523382a..a20e607 100644 --- a/multi/integration/lovrManager/init.lua +++ b/multi/integration/lovrManager/init.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/lovrManager/threads.lua b/multi/integration/lovrManager/threads.lua index b373136..49d5bb8 100644 --- a/multi/integration/lovrManager/threads.lua +++ b/multi/integration/lovrManager/threads.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/luvitManager.lua b/multi/integration/luvitManager.lua index 73ee666..48f6f80 100644 --- a/multi/integration/luvitManager.lua +++ b/multi/integration/luvitManager.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/channel.lua b/multi/integration/networkManager/channel.lua index 2fc61b3..3083241 100644 --- a/multi/integration/networkManager/channel.lua +++ b/multi/integration/networkManager/channel.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/childNode.lua b/multi/integration/networkManager/childNode.lua index 24945fa..43837d5 100644 --- a/multi/integration/networkManager/childNode.lua +++ b/multi/integration/networkManager/childNode.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/clientSide.lua b/multi/integration/networkManager/clientSide.lua index b640568..9210840 100644 --- a/multi/integration/networkManager/clientSide.lua +++ b/multi/integration/networkManager/clientSide.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/cmds.lua b/multi/integration/networkManager/cmds.lua index a6097ae..ccb3c2f 100644 --- a/multi/integration/networkManager/cmds.lua +++ b/multi/integration/networkManager/cmds.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/extensions.lua b/multi/integration/networkManager/extensions.lua index 0752381..c4cac79 100644 --- a/multi/integration/networkManager/extensions.lua +++ b/multi/integration/networkManager/extensions.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/init.lua b/multi/integration/networkManager/init.lua index 98f9831..23fcd7e 100644 --- a/multi/integration/networkManager/init.lua +++ b/multi/integration/networkManager/init.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/masterNode.lua b/multi/integration/networkManager/masterNode.lua index 2ea9b1e..df3eab7 100644 --- a/multi/integration/networkManager/masterNode.lua +++ b/multi/integration/networkManager/masterNode.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/node.lua b/multi/integration/networkManager/node.lua index 4723725..8151b57 100644 --- a/multi/integration/networkManager/node.lua +++ b/multi/integration/networkManager/node.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/nodeManager.lua b/multi/integration/networkManager/nodeManager.lua index 73cf06a..840bc60 100644 --- a/multi/integration/networkManager/nodeManager.lua +++ b/multi/integration/networkManager/nodeManager.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/serverSide.lua b/multi/integration/networkManager/serverSide.lua index 2c2291f..4d8b771 100644 --- a/multi/integration/networkManager/serverSide.lua +++ b/multi/integration/networkManager/serverSide.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/threads.lua b/multi/integration/networkManager/threads.lua index 0752381..c4cac79 100644 --- a/multi/integration/networkManager/threads.lua +++ b/multi/integration/networkManager/threads.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/networkManager/utils.lua b/multi/integration/networkManager/utils.lua index 0aeac3c..9ebdb1b 100644 --- a/multi/integration/networkManager/utils.lua +++ b/multi/integration/networkManager/utils.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/pesudoManager/extensions.lua b/multi/integration/pesudoManager/extensions.lua index deae13d..9942d1d 100644 --- a/multi/integration/pesudoManager/extensions.lua +++ b/multi/integration/pesudoManager/extensions.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/multi/integration/pesudoManager/threads.lua b/multi/integration/pesudoManager/threads.lua index febac47..3f97e46 100644 --- a/multi/integration/pesudoManager/threads.lua +++ b/multi/integration/pesudoManager/threads.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +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 diff --git a/test3.lua b/test3.lua new file mode 100644 index 0000000..3afc3ad --- /dev/null +++ b/test3.lua @@ -0,0 +1,17 @@ +lanes = require("lanes").configure({allocator="protected",verbose_errors=""}) +local multi,thread = require("multi"):init() +function sleep(n) + if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end +end + +lanes.gen("*",function() + print("Hello!") +end)() + +multi:newThread("Test thread",function() + while true do + thread.sleep(1) + print("...") + end +end) +multi:mainloop() \ No newline at end of file From 6842147522b0663b0c7e3cbf1bbd57468b72bc09 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 2 Jan 2022 11:25:42 -0500 Subject: [PATCH 04/80] Updated gitignore --- .gitignore | 15 +++------------ test3.lua | 1 + 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index fa5e1d5..d5aac56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,6 @@ -test2.lua -*.mp3 -*.exe -*.dll -lanestestclient.lua -lanestest.lua -sample-node.lua -sample-master.lua -Ayn Rand - The Virtue of Selfishness-Mg4QJheclsQ.m4a -Atlas Shrugged by Ayn Rand Audiobook-9s2qrEau63E.webm -test.lua -test.lua *.code-workspace *.dat +*test* +test* +*test \ No newline at end of file diff --git a/test3.lua b/test3.lua index 3afc3ad..e0bb33f 100644 --- a/test3.lua +++ b/test3.lua @@ -1,5 +1,6 @@ lanes = require("lanes").configure({allocator="protected",verbose_errors=""}) local multi,thread = require("multi"):init() + function sleep(n) if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end end From b3453d028c43626ff086fa9a0b03fbd50cb09544 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 2 Jan 2022 11:41:39 -0500 Subject: [PATCH 05/80] Fixed the lanes issue? --- .gitignore | 6 ++-- multi/integration/lanesManager/init.lua | 17 +++++----- test3.lua | 42 ++++++++++++++++++++++--- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index d5aac56..eebfe4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ *.code-workspace *.dat -*test* -test* -*test \ No newline at end of file +test.lua +test2.lua +test3.lua \ No newline at end of file diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 9526c4f..afb66ec 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] package.path = "?/init.lua;?.lua;" .. package.path -multi, thread = require("multi"):init() -- get it all and have it on all lanes +local multi, thread = require("multi"):init() -- get it all and have it on all lanes if multi.integration then -- This allows us to call the lanes manager from supporting modules without a hassle return { init = function() @@ -53,7 +53,7 @@ multi:newLoop(function() print(unpack(data)) end end) -local GLOBAL,THREAD = {},{}-- require("multi.integration.lanesManager.threads").init(__GlobalLinda,__SleepingLinda) +local GLOBAL,THREAD = require("multi.integration.lanesManager.threads").init(__GlobalLinda,__SleepingLinda) local count = 1 local started = false local livingThreads = {} @@ -66,7 +66,7 @@ function THREAD:newFunction(func,holdme) end function multi:newSystemThread(name, func, ...) - --multi.InitSystemThreadErrorHandler() + multi.InitSystemThreadErrorHandler() local rand = math.random(1, 10000000) local return_linda = lanes.linda() local c = {} @@ -94,7 +94,6 @@ function multi:newSystemThread(name, func, ...) local has_error = true return_linda:set("returns",{func(...)}) has_error = false - --error("thread killed") print("Thread ending") end)(...) count = count + 1 @@ -106,7 +105,7 @@ function multi:newSystemThread(name, func, ...) table.insert(multi.SystemThreads, c) c.OnDeath = multi:newConnection() c.OnError = multi:newConnection() - --GLOBAL["__THREADS__"] = livingThreads + GLOBAL["__THREADS__"] = livingThreads return c end @@ -126,7 +125,7 @@ function multi.InitSystemThreadErrorHandler() livingThreads[temp.Id] = {false, temp.Name} temp.alive = false temp.OnDeath:Fire(temp,nil,unpack(({temp.returns:receive(0, "returns")})[2])) - --GLOBAL["__THREADS__"] = livingThreads + GLOBAL["__THREADS__"] = livingThreads --print(temp.thread:cancel(10,true)) table.remove(threads, i) elseif status == "running" then @@ -137,19 +136,19 @@ function multi.InitSystemThreadErrorHandler() livingThreads[temp.Id] = {false, temp.Name} temp.alive = false temp.OnError:Fire(temp,nil,unpack(temp.returns:receive(0,"returns"))) - --GLOBAL["__THREADS__"] = livingThreads + GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) elseif status == "cancelled" then livingThreads[temp.Id] = {false, temp.Name} temp.alive = false temp.OnError:Fire(temp,nil,"thread_cancelled") - --GLOBAL["__THREADS__"] = livingThreads + GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) elseif status == "killed" then livingThreads[temp.Id] = {false, temp.Name} temp.alive = false temp.OnError:Fire(temp,nil,"thread_killed") - --GLOBAL["__THREADS__"] = livingThreads + GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) end end diff --git a/test3.lua b/test3.lua index e0bb33f..ac2e32f 100644 --- a/test3.lua +++ b/test3.lua @@ -1,13 +1,47 @@ -lanes = require("lanes").configure({allocator="protected",verbose_errors=""}) local multi,thread = require("multi"):init() +local GLOBAL,THREAD = require("multi.integration.threading"):init() function sleep(n) if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end end +-- local GLOBAL,THREAD = {},{}-- require("multi.integration.lanesManager.threads").init(__GlobalLinda,__SleepingLinda) +-- local count = 1 +-- local started = false +-- local livingThreads = {} +-- local threads = {} +-- multi.SystemThreads = {} +-- function multi:newSystemThread(name, func, ...) +-- --multi.InitSystemThreadErrorHandler() +-- local rand = math.random(1, 10000000) +-- local return_linda = lanes.linda() +-- local c = {} +-- c.name = name +-- c.Name = name +-- c.Id = count +-- c.loadString = {"base","package","os","io","math","table","string","coroutine"} +-- livingThreads[count] = {true, name} +-- c.returns = return_linda +-- c.Type = "sthread" +-- c.creationTime = os.clock() +-- c.alive = true +-- c.priority = THREAD.Priority_Normal +-- c.thread = lanes.gen("*",func)(...) +-- count = count + 1 +-- function c:kill() +-- self.thread:cancel() +-- multi.print("Thread: '" .. self.name .. "' has been stopped!") +-- self.alive = false +-- end +-- table.insert(multi.SystemThreads, c) +-- c.OnDeath = multi:newConnection() +-- c.OnError = multi:newConnection() +-- GLOBAL["__THREADS__"] = livingThreads +-- return c +-- end -lanes.gen("*",function() - print("Hello!") -end)() +multi:newSystemThread("test",function() + print("Hello World!") +end) multi:newThread("Test thread",function() while true do From b74a6c006e1c5f6b0e55d66496e1988ccb27d7cb Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 2 Jan 2022 12:18:35 -0500 Subject: [PATCH 06/80] Updated lanes integration --- multi/init.lua | 5 ++- multi/integration/lanesManager/init.lua | 8 ++--- test3.lua | 47 ++++--------------------- 3 files changed, 13 insertions(+), 47 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 1a93d1c..5e87891 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1197,7 +1197,7 @@ function thread:newFunctionBase(generator,holdme) end } end - local t = generator(...) --multi.getCurrentProcess():newThread("TempThread",func,...) + local t = generator(...) t.OnDeath(function(self,status,...) rets = {...} end) t.OnError(function(self,e) err = e end) if holdme then @@ -1209,6 +1209,9 @@ function thread:newFunctionBase(generator,holdme) OnReturn = multi:newConnection(), isTFunc = true, wait = wait, + getReturns = function() + return unpack(rets) + end, connect = function(f) local tempConn = multi:newConnection() t.OnDeath(function(self,status,...) if f then f(...) else tempConn:Fire(...) end end) diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index afb66ec..01afe62 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -22,7 +22,7 @@ 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() -- get it all and have it on all lanes +multi, thread = require("multi"):init() -- get it all and have it on all lanes if multi.integration then -- This allows us to call the lanes manager from supporting modules without a hassle return { init = function() @@ -57,7 +57,6 @@ local GLOBAL,THREAD = require("multi.integration.lanesManager.threads").init(__G local count = 1 local started = false local livingThreads = {} -local threads = {} function THREAD:newFunction(func,holdme) return thread:newFunctionBase(function(...) @@ -69,7 +68,7 @@ function multi:newSystemThread(name, func, ...) multi.InitSystemThreadErrorHandler() local rand = math.random(1, 10000000) local return_linda = lanes.linda() - local c = {} + c = {} c.name = name c.Name = name c.Id = count @@ -94,12 +93,10 @@ function multi:newSystemThread(name, func, ...) local has_error = true return_linda:set("returns",{func(...)}) has_error = false - print("Thread ending") end)(...) count = count + 1 function c:kill() self.thread:cancel() - multi.print("Thread: '" .. self.name .. "' has been stopped!") self.alive = false end table.insert(multi.SystemThreads, c) @@ -108,7 +105,6 @@ function multi:newSystemThread(name, func, ...) GLOBAL["__THREADS__"] = livingThreads return c end - function multi.InitSystemThreadErrorHandler() if started == true then return diff --git a/test3.lua b/test3.lua index ac2e32f..13315a6 100644 --- a/test3.lua +++ b/test3.lua @@ -1,52 +1,19 @@ +package.path = "./?/init.lua;"..package.path local multi,thread = require("multi"):init() local GLOBAL,THREAD = require("multi.integration.threading"):init() function sleep(n) if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end end --- local GLOBAL,THREAD = {},{}-- require("multi.integration.lanesManager.threads").init(__GlobalLinda,__SleepingLinda) --- local count = 1 --- local started = false --- local livingThreads = {} --- local threads = {} --- multi.SystemThreads = {} --- function multi:newSystemThread(name, func, ...) --- --multi.InitSystemThreadErrorHandler() --- local rand = math.random(1, 10000000) --- local return_linda = lanes.linda() --- local c = {} --- c.name = name --- c.Name = name --- c.Id = count --- c.loadString = {"base","package","os","io","math","table","string","coroutine"} --- livingThreads[count] = {true, name} --- c.returns = return_linda --- c.Type = "sthread" --- c.creationTime = os.clock() --- c.alive = true --- c.priority = THREAD.Priority_Normal --- c.thread = lanes.gen("*",func)(...) --- count = count + 1 --- function c:kill() --- self.thread:cancel() --- multi.print("Thread: '" .. self.name .. "' has been stopped!") --- self.alive = false --- end --- table.insert(multi.SystemThreads, c) --- c.OnDeath = multi:newConnection() --- c.OnError = multi:newConnection() --- GLOBAL["__THREADS__"] = livingThreads --- return c --- end -multi:newSystemThread("test",function() - print("Hello World!") +func = THREAD:newFunction(function(a,b,c) + print("Hello Thread!",a,b,c) + return 1,2,3 end) multi:newThread("Test thread",function() - while true do - thread.sleep(1) - print("...") - end + handler = func(4,5,6) + thread.hold(handler.OnReturn) + print("Function Done",handler.getReturns()) end) multi:mainloop() \ No newline at end of file From 537dcf0db16fa833a27fafece9b8bd2afe9fc8a8 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jan 2022 18:01:02 -0500 Subject: [PATCH 07/80] Working on performance --- changes.md | 9 +- multi/init.lua | 113 +++++++++++++----------- multi/integration/lanesManager/init.lua | 18 ++-- test3.lua | 33 ++++--- 4 files changed, 96 insertions(+), 77 deletions(-) diff --git a/changes.md b/changes.md index ec270c6..7f654b1 100644 --- a/changes.md +++ b/changes.md @@ -71,22 +71,23 @@ Changed: - multi:newTStep now derives it's functionality from multi:newStep (Cut's down on code length a bit) -- +- Fixed the getTaskDetails to handle the new format for threads ### Developer Note: -Connections are some of the most complex objects that this library has outside of some of the system threaded stuff. I tend to add features to connection objects quite often. Just last update connections can be "added" together creating a temp connection that only triggers when all of the added connections got triggered as well. Thinking about the possibilities this could give developers using the library I had to changed the base classes to use connections. +Connections are one of the most complex objects that this library has outside of some of the system threaded stuff. I tend to add features to connection objects quite often. Just last update connections can be "added" together creating a temp connection that only triggers when all of the added connections got triggered as well. Thinking about the possibilities this could give developers using the library I had to changed the base classes to use connections. The best part about this is that connections allow for greater control over an object's events. You can add and remove events that have been connected to as well as a lot of other things. Reference the documentation [here](./Documentation.md#non-actor-connections) Removed: --- -Nothing + +- Calling Fire on a connection no longer returns anything! Now that internal features use connections, I noticed how slow connections are and have increased their speed quite a bit. From 50,000 Steps per seconds to almost 7 Million. All other features should work just fine. Only returning values has been removed Fixed: --- -- [Issue](https://github.com/rayaman/multi/issues/30) with Lanes crashing the lua state. Fixed it with pcall +- [Issue](https://github.com/rayaman/multi/issues/30) with Lanes crashing the lua state. Issue seems to be related to my filesystem - [Issue](https://github.com/rayaman/multi/issues/29) where System threaded functions not up to date with threaded functions ToDo: diff --git a/multi/init.lua b/multi/init.lua index 5e87891..8065d13 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -201,6 +201,7 @@ end local ignoreconn = true function multi:newConnection(protect,func,kill) local c={} + local call_funcs = {} c.callback = func c.Parent=self c.lock = false @@ -245,8 +246,8 @@ function multi:newConnection(protect,func,kill) c.Type='connector' c.func={} c.ID=0 - c.protect=protect or true - c.connections={} + c.protect=protect or false + local connections={} c.FC=0 function c:holdUT(n) local n=n or 0 @@ -267,9 +268,9 @@ function multi:newConnection(protect,func,kill) c.HoldUT=c.holdUT function c:getConnection(name,ignore) if ignore then - return self.connections[name] or CRef + return connections[name] or CRef else - return self.connections[name] or self + return connections[name] or self end end function c:Lock() @@ -280,51 +281,58 @@ function multi:newConnection(protect,func,kill) c.lock = false return self end - function c:Fire(...) - local ret={} - if self.lock then return end - for i=#self.func,1,-1 do - if self.protect then - if not self.func[i] then return end - local temp={pcall(self.func[i][1],...)} - if temp[1] then - table.remove(temp,1) - table.insert(ret,temp) - else - multi.print(temp[2]) + if c.protect then + function c:Fire(...) + if self.lock then return end + for i=#call_funcs,1,-1 do + if not call_funcs[i] then return end + pcall(call_funcs[i],...) + if kill then + table.remove(call_funcs,i) end - else - if not self.func[i] then return end - table.insert(ret,{self.func[i][1](...)}) - end - if kill then - table.remove(self.func,i) end end - return ret + else + function c:Fire(...) + for i=#call_funcs,1,-1 do + call_funcs[i](...) + if kill then + table.remove(call_funcs,i) + end + end + end + end + local fast = {} + function c:fastMode() + function self:Fire(...) + for i=1,#fast do + fast[i](...) + end + end + function self:connect(func) + table.insert(fast,func) + end end function c:Bind(t) - local temp = self.func - self.func=t + local temp = call_funcs + call_funcs=t return temp end function c:Remove() - local temp = self.func - self.func={} + 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(self.func,num,{func,self.ID}) + table.insert(call_funcs,num,func) else - table.insert(self.func,1,{func,self.ID}) + table.insert(call_funcs,1,func) end local temp = { - Link=self.func, func=func, Type="connector_link", - ID=self.ID, Parent=self, connect = function(s,...) return self:connect(...) @@ -350,29 +358,27 @@ function multi:newConnection(protect,func,kill) function temp:Fire(...) if self.Parent.lock then return end if self.Parent.protect then - local t=pcall(self.func,...) + local t=pcall(call_funcs,...) if t then return t end else - return self.func(...) + return call_funcs(...) end end function temp:Destroy() - for i=1,#self.Link do - if self.Link[i][2]~=nil then - if self.Link[i][2]==self.ID then - table.remove(self.Link,i) + for i=1,#call_funcs do + if call_funcs[i]~=nil then + if call_funcs[i]==self.func then + table.remove(call_funcs,i) self.remove=function() end - self.Link=nil - self.ID=nil multi.setType(temp,multi.DestroyedObj) end end end end if name then - self.connections[name]=temp + connections[name]=temp end if self.callback then self.callback(temp) @@ -396,7 +402,6 @@ function multi:newConnection(protect,func,kill) else return conn_helper(self,tab[1],tab[2],tab[3]) end - end c.Connect=c.connect c.GetConnection=c.getConnection @@ -711,9 +716,14 @@ function multi:newLoop(func) self.OnLoop:Fire(self,clock()-start) end c.OnLoop = self:newConnection() + function c:fastMode() + self.OnLoop:fastMode() + end + if func then c.OnLoop(func) end + multi:create(c) return c end @@ -978,9 +988,9 @@ function thread.getRunningThread() local threads = globalThreads local t = coroutine.running() if t then - for i,v in pairs(threads) do - if t==i.thread then - return v + for th,process in pairs(threads) do + if t==th.thread then + return th end end end @@ -1204,16 +1214,16 @@ function thread:newFunctionBase(generator,holdme) return wait() end local temp = { - OnStatus = multi:newConnection(), - OnError = multi:newConnection(), - OnReturn = multi:newConnection(), + OnStatus = multi:newConnection(true), + OnError = multi:newConnection(true), + OnReturn = multi:newConnection(true), isTFunc = true, wait = wait, getReturns = function() return unpack(rets) end, connect = function(f) - local tempConn = multi:newConnection() + local tempConn = multi:newConnection(true) t.OnDeath(function(self,status,...) if f then f(...) else tempConn:Fire(...) end end) t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) return tempConn @@ -2291,17 +2301,14 @@ function multi:benchMark(sec,p,pt) if pt then multi.print(pt.." "..c.." Steps in "..sec.." second(s)!") end - self.tt(sec,c) + self.OnBench:Fire(sec,c) self:Destroy() else c=c+1 end end) + temp.OnBench = multi:newConnection() temp:setPriority(p or 1) - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end return temp end diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 01afe62..ae43fb8 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -21,6 +21,7 @@ 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 multi, thread = require("multi"):init() -- get it all and have it on all lanes if multi.integration then -- This allows us to call the lanes manager from supporting modules without a hassle @@ -31,7 +32,7 @@ if multi.integration then -- This allows us to call the lanes manager from suppo } end -- Step 1 get lanes -lanes = require("lanes").configure({allocator="protected",verbose_errors=""}) +lanes = require("lanes").configure() multi.SystemThreads = {} multi.isMainThread = true @@ -47,12 +48,6 @@ end local __GlobalLinda = lanes.linda() -- handles global stuff local __SleepingLinda = lanes.linda() -- handles sleeping stuff local __ConsoleLinda = lanes.linda() -- handles console stuff -multi:newLoop(function() - local _,data = __ConsoleLinda:receive(0, "Q") - if data then - print(unpack(data)) - end -end) local GLOBAL,THREAD = require("multi.integration.lanesManager.threads").init(__GlobalLinda,__SleepingLinda) local count = 1 local started = false @@ -65,6 +60,7 @@ function THREAD:newFunction(func,holdme) end function multi:newSystemThread(name, func, ...) + print("Creating a thread") multi.InitSystemThreadErrorHandler() local rand = math.random(1, 10000000) local return_linda = lanes.linda() @@ -106,6 +102,7 @@ function multi:newSystemThread(name, func, ...) return c end function multi.InitSystemThreadErrorHandler() + print("Thread Created!") if started == true then return end @@ -113,7 +110,11 @@ function multi.InitSystemThreadErrorHandler() multi:newThread("SystemThreadScheduler",function() local threads = multi.SystemThreads while true do - thread.sleep(.01) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough. + thread.sleep(.005) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough. + local _,data = __ConsoleLinda:receive(0, "Q") + if data then + print(unpack(data)) + end for i = #threads, 1, -1 do local status = threads[i].thread.status local temp = threads[i] @@ -153,6 +154,7 @@ function multi.InitSystemThreadErrorHandler() print(...) end) end + multi.print("Integrated Lanes!") multi.integration = {} -- for module creators multi.integration.GLOBAL = GLOBAL diff --git a/test3.lua b/test3.lua index 13315a6..4118ce6 100644 --- a/test3.lua +++ b/test3.lua @@ -1,19 +1,28 @@ package.path = "./?/init.lua;"..package.path local multi,thread = require("multi"):init() -local GLOBAL,THREAD = require("multi.integration.threading"):init() +local GLOBAL,THREAD = require("multi.integration.lanesManager"):init() -function sleep(n) - if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end -end +-- func = THREAD:newFunction(function(a,b,c) +-- print("Hello Thread!",a,b,c) +-- return 1,2,3 +-- end) -func = THREAD:newFunction(function(a,b,c) - print("Hello Thread!",a,b,c) - return 1,2,3 +-- func2 = THREAD:newFunction(function(a,b,c) +-- print("Hello Thread2!",a,b,c) +-- THREAD.sleep(1) +-- return 10,11,12 +-- end) + +-- multi:newThread("Test thread",function() +-- handler = func(4,5,6) +-- handler2 = func2(7,8,9) +-- thread.hold(handler.OnReturn + handler2.OnReturn) +-- print("Function Done",handler.getReturns()) +-- print("Function Done",handler2.getReturns()) +-- end) +multi:benchMark(1):OnBench(function(sec,steps) + print("Steps:",steps) + os.exit() end) -multi:newThread("Test thread",function() - handler = func(4,5,6) - thread.hold(handler.OnReturn) - print("Function Done",handler.getReturns()) -end) multi:mainloop() \ No newline at end of file From 997ea48b54ca52c5be17ba9da307aec5f8f82ed0 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 9 Jan 2022 23:23:54 -0500 Subject: [PATCH 08/80] Writing tests, fixed some bugs with the library, testing luajit support --- .gitignore | 1 - changes.md | 12 ++--- jitpaths.lua | 2 + luapaths.lua | 1 + multi/init.lua | 115 +++++++++++++++--------------------------- test3.lua | 14 +++-- tests/objectTests.lua | 45 +++++++++++++++-- tests/runtests.lua | 20 ++++++-- 8 files changed, 119 insertions(+), 91 deletions(-) create mode 100644 jitpaths.lua create mode 100644 luapaths.lua diff --git a/.gitignore b/.gitignore index eebfe4d..3ca13af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - *.code-workspace *.dat test.lua diff --git a/changes.md b/changes.md index 7f654b1..a1566bc 100644 --- a/changes.md +++ b/changes.md @@ -26,6 +26,7 @@ Added: Changed: --- +- `multi.hold(n,opt)` now supports an option table like thread.hold does. - Connection Objects now pass on the parent object if created on a multiobj. This was to allow chaining to work properly with the new update ```lua @@ -73,15 +74,10 @@ Changed: - Fixed the getTaskDetails to handle the new format for threads -### Developer Note: - -Connections are one of the most complex objects that this library has outside of some of the system threaded stuff. I tend to add features to connection objects quite often. Just last update connections can be "added" together creating a temp connection that only triggers when all of the added connections got triggered as well. Thinking about the possibilities this could give developers using the library I had to changed the base classes to use connections. - -The best part about this is that connections allow for greater control over an object's events. You can add and remove events that have been connected to as well as a lot of other things. Reference the documentation [here](./Documentation.md#non-actor-connections) - - Removed: --- +- `multi:newFunction(func)` + - `thread:newFunction(func)` Has many more features and replaces completely what this function did - Calling Fire on a connection no longer returns anything! Now that internal features use connections, I noticed how slow connections are and have increased their speed quite a bit. From 50,000 Steps per seconds to almost 7 Million. All other features should work just fine. Only returning values has been removed @@ -89,6 +85,8 @@ Fixed: --- - [Issue](https://github.com/rayaman/multi/issues/30) with Lanes crashing the lua state. Issue seems to be related to my filesystem - [Issue](https://github.com/rayaman/multi/issues/29) where System threaded functions not up to date with threaded functions +- Issue where gettasksdetails would try to process a destroyed object causing it to crash + ToDo: --- diff --git a/jitpaths.lua b/jitpaths.lua new file mode 100644 index 0000000..d513f01 --- /dev/null +++ b/jitpaths.lua @@ -0,0 +1,2 @@ +package.path = "./?/init.lua;C:/Luajit/lua/?.init.lua;C:/Luajit/lua/?.lua;" +package.cpath = "C:/Luajit/clib/?/core.dll;C:/Luajit/clib/?.dll;" \ No newline at end of file diff --git a/luapaths.lua b/luapaths.lua new file mode 100644 index 0000000..031ec1d --- /dev/null +++ b/luapaths.lua @@ -0,0 +1 @@ +package.path = "./?/init.lua;?.lua;".. package.path \ No newline at end of file diff --git a/multi/init.lua b/multi/init.lua index 8065d13..11753d0 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -114,7 +114,9 @@ function multi:getTasksDetails(t) name = " <"..name..">" end count = count + 1 - table.insert(str,{v.Type:sub(1,1):upper()..v.Type:sub(2,-1)..name,multi.Round(os.clock()-v.creationTime,3),self.PriorityResolve[v.Priority],v.TID}) + if not v.Type == "destroyed" then + table.insert(str,{v.Type:sub(1,1):upper()..v.Type:sub(2,-1)..name,multi.Round(os.clock()-v.creationTime,3),self.PriorityResolve[v.Priority],v.TID}) + end end if count == 0 then table.insert(str,{"Currently no processes running!","","",""}) @@ -202,9 +204,9 @@ local ignoreconn = true function multi:newConnection(protect,func,kill) local c={} local call_funcs = {} + local lock = false c.callback = func c.Parent=self - c.lock = false setmetatable(c,{__call=function(self,...) local t = ... if type(t)=="table" then @@ -246,7 +248,7 @@ function multi:newConnection(protect,func,kill) c.Type='connector' c.func={} c.ID=0 - c.protect=protect or false + local protect=protect or false local connections={} c.FC=0 function c:holdUT(n) @@ -274,16 +276,16 @@ function multi:newConnection(protect,func,kill) end end function c:Lock() - c.lock = true + lock = true return self end function c:Unlock() - c.lock = false + lock = false return self end - if c.protect then + if protect then function c:Fire(...) - if self.lock then return end + if lock then return end for i=#call_funcs,1,-1 do if not call_funcs[i] then return end pcall(call_funcs[i],...) @@ -303,6 +305,9 @@ function multi:newConnection(protect,func,kill) end end local fast = {} + function c:getConnections() + return call_funcs + end function c:fastMode() function self:Fire(...) for i=1,#fast do @@ -356,8 +361,8 @@ function multi:newConnection(protect,func,kill) end, }) function temp:Fire(...) - if self.Parent.lock then return end - if self.Parent.protect then + if lock then return end + if protect then local t=pcall(call_funcs,...) if t then return t @@ -635,17 +640,17 @@ end function multi:newEvent(task) local c=self:newBase() c.Type='event' - c.Task=task or function() end + local task = task or function() end function c:Act() - local t = {self.Task(self)} - if t[1] then + local t = task(self) + if t then self:Pause() self.returns = t c.OnEvent:Fire(self) end end function c:SetTask(func) - self.Task=func + task=func return self end c.OnEvent = self:newConnection() @@ -656,20 +661,20 @@ end function multi:newUpdater(skip) local c=self:newBase() c.Type='updater' - c.pos=1 - c.skip=skip or 1 + local pos = 1 + local skip = skip or 1 function c:Act() - if self.pos>=self.skip then - self.pos=0 + if pos >= skip then + pos = 0 self.OnUpdate:Fire(self) end - self.pos=self.pos+1 + pos = pos+1 end function c:SetSkip(n) - self.skip=n + skip=n return self end - c.OnUpdate=self:newConnection() + c.OnUpdate = self:newConnection() multi:create(c) return c end @@ -727,32 +732,6 @@ function multi:newLoop(func) multi:create(c) return c end -function multi:newFunction(func) - local c={} - c.func=func - c.Type = "mfunc" - mt={ - __index=multi, - __call=function(self,...) - if self.Active then - return self:func(...) - end - return nil,true - end - } - c.Parent=self - function c:Pause() - self.Active=false - return self - end - function c:Resume() - self.Active=true - return self - end - setmetatable(c,mt) - multi:create(c) - return c -end function multi:newStep(start,reset,count,skip) local c=self:newBase() @@ -1100,10 +1079,10 @@ function thread.waitFor(name) thread.hold(function() return thread.get(name)~=nil end) return thread.get(name) end -function multi.hold(func,no) - if thread.isThread() and not(no) then +function multi.hold(func,opt) + if thread.isThread() then if type(func) == "function" or type(func) == "table" then - return thread.hold(func) + return thread.hold(func,opt) end return thread.sleep(func) end @@ -1119,7 +1098,7 @@ function multi.hold(func,no) else local rets self:newThread("Hold_func",function() - rets = {thread.hold(func)} + rets = {thread.hold(func,opt)} death = true end) while not death do @@ -2166,31 +2145,19 @@ function multi:getLoad() if not multi.maxSpd then self:enableLoadDetection() end if busy then return lastVal,last_step end local val = nil - if thread.isThread() then - local bench - self:benchMark(.01):OnBench(function(time,steps) - bench = steps - bb = steps - end) - thread.hold(function() - return bench - end) - bench = bench^1.5 - val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) - else - busy = true - local bench - self:benchMark(.01):OnBench(function(time,steps) - bench = steps - bb = steps - end) - while not bench do - self:uManager() - end - bench = bench^1.5 - val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) - busy = false + local bench + self:benchMark(.01):OnBench(function(time,steps) + bench = steps + bb = steps + end) + _,timeout = multi.hold(function() + return bench + end,{sleep=.011}) + if timeout then + bench = 150000 end + bench = bench^1.5 + val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) if val<0 then val = 0 end if val > 100 then val = 100 end lastVal = val diff --git a/test3.lua b/test3.lua index 4118ce6..5c74bdc 100644 --- a/test3.lua +++ b/test3.lua @@ -1,6 +1,8 @@ -package.path = "./?/init.lua;"..package.path +package.path = "./?.lua" +--require("jitpaths") +require("luapaths") local multi,thread = require("multi"):init() -local GLOBAL,THREAD = require("multi.integration.lanesManager"):init() +--local GLOBAL,THREAD = require("multi.integration.lanesManager"):init() -- func = THREAD:newFunction(function(a,b,c) -- print("Hello Thread!",a,b,c) @@ -22,7 +24,13 @@ local GLOBAL,THREAD = require("multi.integration.lanesManager"):init() -- end) multi:benchMark(1):OnBench(function(sec,steps) print("Steps:",steps) - os.exit() + --os.exit() +end) + +multi:newThread(function() + print(thread.hold(function() + return false + end,{sleep=1})) end) multi:mainloop() \ No newline at end of file diff --git a/tests/objectTests.lua b/tests/objectTests.lua index 9296a48..afb090b 100644 --- a/tests/objectTests.lua +++ b/tests/objectTests.lua @@ -1,3 +1,42 @@ -return function objectTests(multi,thread) - print("Testing Alarms!") -end \ No newline at end of file +function objectTests(multi,thread) + local alarms,tsteps,steps,loops,tloops,updaters,events=false,0,0,0,0,0,false + print("Testing Basic Features. If this fails most other features will probably not work!") + multi:newAlarm(2):OnRing(function(a) + alarms = true + a:Destroy() + end) + multi:newTStep(1,10,1,.1):OnStep(function(t) + tsteps = tsteps + 1 + end):OnEnd(function(step) + step:Destroy() + end) + multi:newStep(1,10):OnStep(function(s) + steps = steps + 1 + end):OnEnd(function(step) + step:Destroy() + end) + local loop = multi:newLoop(function(l) + loops = loops + 1 + end) + multi:newTLoop(function(t) + tloops = tloops + 1 + end,.1) + local updater = multi:newUpdater(1):OnUpdate(function() + updaters = updaters + 1 + end) + local event = multi:newEvent(function() + return alarms + end) + event.OnEvent(function(evnt) + events = true + print("Alarms: Ok") + print("Events: Ok") + if tsteps == 10 then print("TSteps: Ok") else print("TSteps: Bad!") end + if steps == 10 then print("Steps: Ok") else print("Steps: Bad!") end + if loops > 100 then print("Loops: Ok") else print("Loops: Bad!") end + if tloops > 10 then print("TLoops: Ok") else print("TLoops: Bad!") end + if updaters > 100 then print("Updaters: Ok") else print("Updaters: Bad!") end + end) + return event +end +return objectTests \ No newline at end of file diff --git a/tests/runtests.lua b/tests/runtests.lua index e0faa88..d9f4d0c 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -16,6 +16,20 @@ package.path="../?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package.path This will be pushed directly to the master as tests start existing. ]] local multi, thread = require("multi"):init() -function runTest(path) - -end \ No newline at end of file + +local good = false +runTest = thread:newFunction(function() + local objects = multi:newProcessor("Basic Object Tests") + objects.Start() + otest = require("tests/objectTests")(objects,thread) + thread.hold(otest.OnEvent) + print("Timers: Ok") + print("Connections: Ok") + print("Threads: Ok") + print(objects:getTasksDetails()) + good = true + print("\nTests done") + os.exit() +end,true) +print(runTest()) +multi:mainloop() \ No newline at end of file From b16593425b0081c1d2466882b4fbce1726e09e70 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 9 Jan 2022 23:32:54 -0500 Subject: [PATCH 09/80] Fixed typo in changes.md --- changes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes.md b/changes.md index a1566bc..5a07567 100644 --- a/changes.md +++ b/changes.md @@ -77,7 +77,7 @@ Changed: Removed: --- - `multi:newFunction(func)` - - `thread:newFunction(func)` Has many more features and replaces completely what this function did + - `thread:newFunction(func)` Has many more features and replaces multi:newFunction did - Calling Fire on a connection no longer returns anything! Now that internal features use connections, I noticed how slow connections are and have increased their speed quite a bit. From 50,000 Steps per seconds to almost 7 Million. All other features should work just fine. Only returning values has been removed From 609613dbe9011683a2f84e1f86423bb3c3842240 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 11 Jan 2022 18:59:53 -0500 Subject: [PATCH 10/80] Fixed lua 5.4 issue and the taskstatus --- changes.md | 2 +- multi/init.lua | 6 +++--- tests/runtests.lua | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/changes.md b/changes.md index 5a07567..c77cc59 100644 --- a/changes.md +++ b/changes.md @@ -77,7 +77,7 @@ Changed: Removed: --- - `multi:newFunction(func)` - - `thread:newFunction(func)` Has many more features and replaces multi:newFunction did + - `thread:newFunction(func)` Has many more features and replaces what multi:newFunction did - Calling Fire on a connection no longer returns anything! Now that internal features use connections, I noticed how slow connections are and have increased their speed quite a bit. From 50,000 Steps per seconds to almost 7 Million. All other features should work just fine. Only returning values has been removed diff --git a/multi/init.lua b/multi/init.lua index 11753d0..5a099bc 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -92,7 +92,7 @@ end --Processor local priorityTable = {[0]="Round-Robin",[1]="Balanced",[2]="Top-Down",[3]="Timed-Based-Balancer"} -local ProcessName = {[true]="SubProcessor",[false]="MainProcessor"} +local ProcessName = {"SubProcessor","MainProcessor"} local globalThreads = {} function multi:getTasksDetails(t) if not(t) then @@ -153,9 +153,9 @@ function multi:getTasksDetails(t) end dat = multi.AlignTable(proc_tab).. "\n" dat = dat .. "\n" .. multi.AlignTable(th_tab) - return "Load on "..ProcessName[self.Type=="process"].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: "..thread_count.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat..dat2.."\n\n"..s + return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: "..thread_count.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat..dat2.."\n\n"..s else - return "Load on "..ProcessName[self.Type=="process"].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\n\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat2.."\n\n"..s + return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\n\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat2.."\n\n"..s end else local load,steps = self:getLoad() diff --git a/tests/runtests.lua b/tests/runtests.lua index d9f4d0c..4249110 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -1,4 +1,4 @@ -package.path="../?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package.path +package.path="./?.lua;../?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package.path --[[ This file runs all tests. Format: From af4672245e2ffdbd823d14a0631c60ea645a5070 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 11 Jan 2022 23:04:35 -0500 Subject: [PATCH 11/80] Removed some tests --- README.md | 26 ++++++++------- changes.md | 42 ++++++------------------- multi/integration/lanesManager/init.lua | 5 +-- 3 files changed, 24 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index fbc02f7..e6db14b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Multi Version: 16.0.0 Upgrade Complete +# Multi Version: 15.2.0 Upgrade Complete **Key Changes** - All objects now use connections internally - Updated getTasksDetails() to handle the new method of managing threads and processors @@ -9,16 +9,13 @@ My multitasking library for lua. It is a pure lua binding, with exceptions of th INSTALLING ---------- -Links to dependencies: +Link to dependencies: [lanes](https://github.com/LuaLanes/lanes) 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** use luarocks `luarocks install multi` -Going forward I will include a Release zip for love2d. -**The Network Manager rework is currently being worked on and the old version is not included in this version.** - Discord ------- Have a question? Or need realtime assistance? Feel free to join the discord!
@@ -26,7 +23,6 @@ https://discord.gg/U8UspuA
Planned features/TODO --------------------- -- [x] ~~Finish Documentation~~ Finished - [ ] Create test suite - [ ] Network Parallelism rework @@ -34,21 +30,27 @@ Usage: [Check out the documentation for more info](https://github.com/rayaman/mu ----- ```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path local multi, thread = require("multi"):init() -GLOBAL, THREAD = require("multi.integration.threading"):init() +GLOBAL, THREAD = require("multi.integration.lanesManager"):init() multi:newSystemThread("System Thread",function() while true do - THREAD.sleep(1) - print("World!") + THREAD.sleep(.1) + io.write(" World") + THREAD.kill() end end) multi:newThread("Coroutine Based Thread",function() while true do - print("Hello") - thread.sleep(1) + io.write("Hello") + thread.sleep(.1) + thread.kill() end end) +multi:newTLoop(function(loop) + print("!") + loop:Destroy() + os.exit() +end,.3) multi:mainloop() --[[ while true do diff --git a/changes.md b/changes.md index c77cc59..fd7c302 100644 --- a/changes.md +++ b/changes.md @@ -15,7 +15,7 @@ Added: --- - multi:newTLoop() member functions - - `Loop:Set(set)` - Sets the time to wait for the TLoop + - `TLoop:Set(set)` - Sets the time to wait for the TLoop - multi:newStep() member functions - `Step:Count(count)` - Sets the amount a step should count by @@ -30,7 +30,6 @@ Changed: - Connection Objects now pass on the parent object if created on a multiobj. This was to allow chaining to work properly with the new update ```lua - package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path multi,thread = require("multi"):init() loop = multi:newTLoop() @@ -39,7 +38,6 @@ Changed: print("testing haha") end - loop:Set(1) t = loop:OnLoop(function() print("Looping...") @@ -97,8 +95,7 @@ ToDo: Full Update Showcase ```lua -package.path = "./?/init.lua;"..package.path -multi,thread = require("multi"):init() +local multi,thread = require("multi"):init() func = thread:newFunction(function(count) local a = 0 @@ -197,9 +194,7 @@ Added: Example: ```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path -package.cpath = [[C:\Program Files (x86)\Lua\5.1\systree\lib\lua\5.1\?.dll;C:\Program Files (x86)\Lua\5.1\systree\lib\lua\5.1\?\core.dll;]] ..package.cpath -multi,thread = require("multi"):init() +local multi,thread = require("multi"):init() GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your enviroment and uses what's available jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads @@ -244,8 +239,7 @@ multi:mainloop() ## multi:newProcessor(name) ```lua -package.path = "./?/init.lua;"..package.path -multi,thread = require("multi"):init() +local multi,thread = require("multi"):init() -- Create a processor object, it works a lot like the multi object sandbox = multi:newProcessor() @@ -318,8 +312,7 @@ Can be chained as long as you want! See example below Example: ```lua -package.path = "./?/init.lua;"..package.path -multi,thread = require("multi"):init() +local multi,thread = require("multi"):init() func = thread:newFunction(function(count) local a = 0 @@ -374,8 +367,7 @@ Changed: holdMe(set) | Sets the holdme argument that existed at function creation ```lua - package.path = "./?/init.lua;"..package.path - multi, thread = require("multi"):init() + local multi, thread = require("multi"):init() test = thread:newFunction(function(a,b) thread.sleep(1) @@ -469,8 +461,7 @@ ToDo Full Update Showcase --- ```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path -multi,thread = require("multi"):init() +local multi,thread = require("multi"):init() GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your enviroment and uses what's available jq = multi:newSystemThreadedJobQueue(4) -- Job queue with 4 worker threads @@ -540,7 +531,6 @@ Todo: Full Update Showcase --- ```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path local multi,thread = require("multi"):init() -- Testing destroying and fixed connections @@ -607,7 +597,6 @@ Fixed: - Issue with connections not returning a handle for managing a specific conn object. - Issue with connections where connection chaining wasn't working properly. This has been addressed. ```lua - package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path local multi,thread = require("multi"):init() test = multi:newConnection() test(function(hmm) @@ -660,7 +649,6 @@ Full Update Showcase --- Something I plan on doing each version going forward ```lua -package.path="?.lua;?/init.lua;?.lua;"..package.path local multi, thread = require("multi"):init() GLOBAL,THREAD = require("multi.integration.lanesManager"):init() serv = multi:newService(function(self,data) @@ -897,7 +885,6 @@ Added: - thread.hold() and multi.hold() now accept connections as an argument. See example below ```lua -package.path = "./?/init.lua;"..package.path local multi, thread = require("multi"):init() conn = multi:newConnection() multi:newThread(function() @@ -928,7 +915,6 @@ end) thread newFunction using auto convert ```lua -package.path = "./?/init.lua;" .. package.path multi, thread = require("multi").init() a=5 multi:newThread("Test",function() @@ -966,7 +952,7 @@ Changed: --- - Connections connect function can now chain connections ```lua - package.path = "./?/init.lua;"..package.path + local multi, thread = require("multi").init() test = multi:newConnection() test(function(a) @@ -1164,7 +1150,6 @@ Added: - STC: FireTo(id,...) — Described above. ```lua -package.path="?/init.lua;?.lua;"..package.path local multi = require("multi") conn = multi:newConnector() conn.OnTest = multi:newConnection() @@ -1236,7 +1221,6 @@ Going forward: Example --- ```lua -package.path="?/init.lua;?.lua;"..package.path multi = require("multi") GLOBAL, THREAD = require("multi.integration.lanesManager").init() jq = multi:newSystemThreadedJobQueue() @@ -1333,7 +1317,6 @@ Changed: - event objects now contain a copy of what returns were made by the function that called it in a table called returns that exist inside of the object ```lua -package.path="?/init.lua;?.lua;"..package.path multi = require("multi") local a = 0 multi:newThread("test",function() @@ -1430,7 +1413,6 @@ Now there is a little trick you can do. If you combine both networkmanager and s **NodeManager.lua** ```lua -package.path="?/init.lua;?.lua;"..package.path multi = require("multi") local GLOBAL, THREAD = require("multi.integration.lanesManager").init() nGLOBAL = require("multi.integration.networkManager").init() @@ -1449,7 +1431,6 @@ Side note: I had a setting called cross talk that would allow nodes to talk to e **Node.lua** ```lua -package.path="?/init.lua;?.lua;"..package.path multi = require("multi") local GLOBAL, THREAD = require("multi.integration.lanesManager").init() nGLOBAL = require("multi.integration.networkManager").init() @@ -1471,10 +1452,8 @@ multi:mainloop(settings) **Master.lua** ```lua --- set up the package -package.path="?/init.lua;?.lua;"..package.path -- Import the libraries -multi = require("multi") +local multi = require("multi") local GLOBAL, THREAD = require("multi.integration.lanesManager").init() nGLOBAL = require("multi.integration.networkManager").init() -- Act as a master node @@ -1644,7 +1623,6 @@ Added: Example of threaded connections ```lua -package.path="?/init.lua;?.lua;"..package.path local GLOBAL,THREAD=require("multi.integration.lanesManager").init() multi:newSystemThread("Test_Thread_1",function() connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() @@ -1681,7 +1659,6 @@ Fixed: Example of threaded tables ```lua -package.path="?/init.lua;?.lua;"..package.path local GLOBAL,sThread=require("multi.integration.lanesManager").init() multi:newSystemThread("Test_Thread_1",function() require("multi") @@ -2007,7 +1984,6 @@ Added:
Using multi:systemThreadedBenchmark() --- ```lua -package.path="?/init.lua;"..package.path local GLOBAL,sThread=require("multi.integration.lanesManager").init() multi:systemThreadedBenchmark(3):OnBench(function(self,count) print("First Bench: "..count) diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index ae43fb8..c350343 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -60,7 +60,6 @@ function THREAD:newFunction(func,holdme) end function multi:newSystemThread(name, func, ...) - print("Creating a thread") multi.InitSystemThreadErrorHandler() local rand = math.random(1, 10000000) local return_linda = lanes.linda() @@ -102,7 +101,6 @@ function multi:newSystemThread(name, func, ...) return c end function multi.InitSystemThreadErrorHandler() - print("Thread Created!") if started == true then return end @@ -123,7 +121,6 @@ function multi.InitSystemThreadErrorHandler() temp.alive = false temp.OnDeath:Fire(temp,nil,unpack(({temp.returns:receive(0, "returns")})[2])) GLOBAL["__THREADS__"] = livingThreads - --print(temp.thread:cancel(10,true)) table.remove(threads, i) elseif status == "running" then -- @@ -132,7 +129,7 @@ function multi.InitSystemThreadErrorHandler() elseif status == "error" then livingThreads[temp.Id] = {false, temp.Name} temp.alive = false - temp.OnError:Fire(temp,nil,unpack(temp.returns:receive(0,"returns"))) + temp.OnError:Fire(temp,nil,unpack(temp.returns:receive(0,"returns") or {"Thread Killed!"})) GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) elseif status == "cancelled" then From b8b31253d44cdbf7e8f2177fdaa14cdca19a2b6c Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 11 Jan 2022 23:16:33 -0500 Subject: [PATCH 12/80] Added key feature --- README.md | 7 ++++--- test3.lua | 13 +++---------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e6db14b..6982ab4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Multi Version: 15.2.0 Upgrade Complete **Key Changes** - All objects now use connections internally +- Connections now about 23x faster! - Updated getTasksDetails() to handle the new method of managing threads and processors Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! @@ -18,15 +19,15 @@ If you want to use the system threads, then you'll need to install lanes! Discord ------- -Have a question? Or need realtime assistance? Feel free to join the discord!
-https://discord.gg/U8UspuA
+Have a question or need realtime assistance? Feel free to join the discord!
+https://discord.gg/U8UspuA Planned features/TODO --------------------- - [ ] Create test suite - [ ] Network Parallelism rework -Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/Documentation.md)
+Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/Documentation.md) ----- ```lua diff --git a/test3.lua b/test3.lua index 5c74bdc..ed8d0d6 100644 --- a/test3.lua +++ b/test3.lua @@ -1,6 +1,6 @@ package.path = "./?.lua" ---require("jitpaths") -require("luapaths") +require("jitpaths") +--require("luapaths") local multi,thread = require("multi"):init() --local GLOBAL,THREAD = require("multi.integration.lanesManager"):init() @@ -24,13 +24,6 @@ local multi,thread = require("multi"):init() -- end) multi:benchMark(1):OnBench(function(sec,steps) print("Steps:",steps) - --os.exit() + os.exit() end) - -multi:newThread(function() - print(thread.hold(function() - return false - end,{sleep=1})) -end) - multi:mainloop() \ No newline at end of file From 4877f64ca127c8ff286b9a2f0a075bb82c6c1f55 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 16 Jan 2022 16:18:17 -0500 Subject: [PATCH 13/80] Fix some bugs, added connection/thread tests --- changes.md | 8 +- multi/init.lua | 178 ++++++++++++++++++++------------------- tests/connectionTest.lua | 71 ++++++++++++++++ tests/objectTests.lua | 2 +- tests/runtests.lua | 20 ++--- 5 files changed, 176 insertions(+), 103 deletions(-) create mode 100644 tests/connectionTest.lua diff --git a/changes.md b/changes.md index fd7c302..c8d9d83 100644 --- a/changes.md +++ b/changes.md @@ -66,7 +66,7 @@ Changed: - Connection Objects no longer Fire with syntax sugar when attached to an object: - `multiobj:OnSomeEvent(arg1,arg2.arg3)` No longer triggers the Fire event. As part of the update to make all objects use connections internally this little used feature had to be scrapped! + `multiobj:OnSomeEvent(...)` No longer triggers the Fire event. As part of the update to make all objects use connections internally this little used feature had to be scrapped! - multi:newTStep now derives it's functionality from multi:newStep (Cut's down on code length a bit) @@ -76,14 +76,16 @@ Removed: --- - `multi:newFunction(func)` - `thread:newFunction(func)` Has many more features and replaces what multi:newFunction did +- `multi.holdFor()` Now that multi.hold takes the option table that thread.hold has this feature can be emulated using that. - Calling Fire on a connection no longer returns anything! Now that internal features use connections, I noticed how slow connections are and have increased their speed quite a bit. From 50,000 Steps per seconds to almost 7 Million. All other features should work just fine. Only returning values has been removed Fixed: --- - [Issue](https://github.com/rayaman/multi/issues/30) with Lanes crashing the lua state. Issue seems to be related to my filesystem -- [Issue](https://github.com/rayaman/multi/issues/29) where System threaded functions not up to date with threaded functions -- Issue where gettasksdetails would try to process a destroyed object causing it to crash +- [Issue](https://github.com/rayaman/multi/issues/29) where System threaded functions not being up to date with threaded functions +- Issue where gettasksdetails() would try to process a destroyed object causing it to crash +- Issue with multi.hold() not pumping the mainloop and only the scheduler ToDo: diff --git a/multi/init.lua b/multi/init.lua index 5a099bc..0348e22 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -137,7 +137,7 @@ function multi:getTasksDetails(t) {"Thread Name","Uptime","TID","Attached To"} } local proc_tab = { - {"Process Name", "Uptime", "PID", "Load", "Cycles per Second per task"} + {"Process Name", "Uptime", "PID", "Load", "CpS"} } for th,process in pairs(globalThreads) do if tostring(th.isProcessThread) == "destroyed" then @@ -372,7 +372,7 @@ function multi:newConnection(protect,func,kill) end end function temp:Destroy() - for i=1,#call_funcs do + for i=#call_funcs,1,-1 do if call_funcs[i]~=nil then if call_funcs[i]==self.func then table.remove(call_funcs,i) @@ -742,8 +742,6 @@ function multi:newStep(start,reset,count,skip) c.skip=skip or 0 c.spos=0 c.count=count or 1*think - c.funcE={} - c.funcS={} c.start=start or 1 if start~=nil and reset~=nil then if start>reset then @@ -905,9 +903,13 @@ function multi:scheduleJob(time,func) end local __CurrentProcess = multi +local __CurrentTask function multi.getCurrentProcess() return __CurrentProcess end +function multi.getCurrentTask() + return __CurrentTask +end local sandcount = 1 function multi:newProcessor(name) @@ -1087,14 +1089,17 @@ function multi.hold(func,opt) return thread.sleep(func) end local death = false + local proc = multi.getCurrentTask() + proc:Pause() if type(func)=="number" then self:newThread("Hold_func",function() - thread.sleep(func) + thread.hold(func) death = true end) while not death do - multi.scheduler:Act() + multi:uManager() end + proc:Resume() else local rets self:newThread("Hold_func",function() @@ -1102,25 +1107,13 @@ function multi.hold(func,opt) death = true end) while not death do - multi.scheduler:Act() + multi:uManager() end + proc:Resume() return unpack(rets) end end -function multi.holdFor(n,func) - local temp - multi.getCurrentProcess():newThread(function() - thread.sleep(n) - temp = true - end) - return multi.getCurrentProcess().hold(function() - if func() then - return func() - elseif temp then - return multi.NIL, multi.TIMEOUT - end - end) -end + local function cleanReturns(...) local returns = {...} local rets = {} @@ -1133,10 +1126,12 @@ local function cleanReturns(...) end return unpack(returns,1,ind) end + function thread.pushStatus(...) local t = thread.getRunningThread() t.statusconnector:Fire(...) end + function thread:newFunctionBase(generator,holdme) return function() local tfunc = {} @@ -1635,12 +1630,14 @@ function multi:lightloop(settings) multi.OnPreLoad:Fire() if not isRunning then local Loop=self.Mainloop + local ctask while true do for _D=#Loop,1,-1 do - if Loop[_D].Active then - self.CID=_D + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask.Active then if not protect then - Loop[_D]:Act() + ctask:Act() end end end @@ -1692,26 +1689,29 @@ function multi:mainloop(settings) local autoP = 0 local solid,sRef local cc=0 + local ctask multi.OnLoad:Fire() while mainloopActive do if priority == 1 then for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + ctask = __CurrentTask for P=1,7 do - if Loop[_D] then - if (PS.PList[P])%Loop[_D].Priority==0 then - if Loop[_D].Active then - self.CID=_D + if ctask then + if (PS.PList[P])%ctask.Priority == 0 then + if ctask.Active then + self.CID = _D if not protect then - Loop[_D]:Act() + ctask:Act() __CurrentProcess = self else - local status, err=pcall(Loop[_D].Act,Loop[_D]) + local status, err = pcall(ctask.Act,ctask) __CurrentProcess = self if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) + ctask.error=err + self.OnError:Fire(ctask,err) if stopOnError then - Loop[_D]:Destroy() + ctask:Destroy() end end end @@ -1722,21 +1722,22 @@ function multi:mainloop(settings) end elseif priority == 2 then for _D=#Loop,1,-1 do - if Loop[_D] then - if (PStep)%Loop[_D].Priority==0 then - if Loop[_D].Active then - self.CID=_D + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask then + if (PStep)%ctask.Priority==0 then + if ctask.Active then if not protect then - Loop[_D]:Act() + ctask:Act() __CurrentProcess = self else - local status, err=pcall(Loop[_D].Act,Loop[_D]) + local status, err=pcall(ctask.Act,ctask) __CurrentProcess = self if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) + ctask.error=err + self.OnError:Fire(ctask,err) if stopOnError then - Loop[_D]:Destroy() + ctask:Destroy() end end end @@ -1756,21 +1757,22 @@ function multi:mainloop(settings) cc=0 end for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Priority == p_c or (Loop[_D].Priority == p_h and tt<.5) or (Loop[_D].Priority == p_an and tt<.125) or (Loop[_D].Priority == p_n and tt<.063) or (Loop[_D].Priority == p_bn and tt<.016) or (Loop[_D].Priority == p_l and tt<.003) or (Loop[_D].Priority == p_i and tt<.001) then - if Loop[_D].Active then - self.CID=_D + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask then + if ctask.Priority == p_c or (ctask.Priority == p_h and tt<.5) or (ctask.Priority == p_an and tt<.125) or (ctask.Priority == p_n and tt<.063) or (ctask.Priority == p_bn and tt<.016) or (ctask.Priority == p_l and tt<.003) or (ctask.Priority == p_i and tt<.001) then + if ctask.Active then if not protect then - Loop[_D]:Act() + ctask:Act() __CurrentProcess = self else - local status, err=pcall(Loop[_D].Act,Loop[_D]) + local status, err=pcall(ctask.Act,ctask) __CurrentProcess = self if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) + ctask.error=err + self.OnError:Fire(ctask,err) if stopOnError then - Loop[_D]:Destroy() + ctask:Destroy() end end end @@ -1780,51 +1782,51 @@ function multi:mainloop(settings) end elseif priority == -1 then for _D=#Loop,1,-1 do - sRef = Loop[_D] - if Loop[_D] then - if (sRef.Priority == p_c) or PStep==0 then - if sRef.Active then - self.CID=_D + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask then + if (ctask.Priority == p_c) or PStep==0 then + if ctask.Active then if not protect then - if sRef.solid then - sRef:Act() + if ctask.solid then + ctask:Act() __CurrentProcess = self solid = true else - time = multi.timer(sRef.Act,sRef) - sRef.solid = true + time = multi.timer(ctask.Act,ctask) + ctask.solid = true solid = false end - if Loop[_D] and not solid then + if ctask and not solid then if time == 0 then - Loop[_D].Priority = p_c + ctask.Priority = p_c else - Loop[_D].Priority = P_LB + ctask.Priority = P_LB end end else - if Loop[_D].solid then - Loop[_D]:Act() + if ctask.solid then + ctask:Act() __CurrentProcess = self solid = true else - time, status, err=multi.timer(pcall,Loop[_D].Act,Loop[_D]) + time, status, err=multi.timer(pcall,ctask.Act,ctask) __CurrentProcess = self - Loop[_D].solid = true + ctask.solid = true solid = false end - if Loop[_D] and not solid then + if ctask and not solid then if time == 0 then - Loop[_D].Priority = p_c + ctask.Priority = p_c else - Loop[_D].Priority = P_LB + ctask.Priority = P_LB end end if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) + ctask.error=err + self.OnError:Fire(ctask,err) if stopOnError then - Loop[_D]:Destroy() + ctask:Destroy() end end end @@ -1844,20 +1846,21 @@ function multi:mainloop(settings) end else for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - self.CID=_D + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask then + if ctask.Active then if not protect then - Loop[_D]:Act() + ctask:Act() __CurrentProcess = self else - local status, err=pcall(Loop[_D].Act,Loop[_D]) + local status, err=pcall(ctask.Act,ctask) __CurrentProcess = self if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) + ctask.error=err + self.OnError:Fire(ctask,err) if stopOnError then - Loop[_D]:Destroy() + ctask:Destroy() end end end @@ -2136,25 +2139,24 @@ function multi:enableLoadDetection() multi.maxSpd = stop end -local busy = false local lastVal = 0 local last_step = 0 -local bb = 0 function multi:getLoad() - if not multi.maxSpd then self:enableLoadDetection() end - if busy then return lastVal,last_step end + if not multi.maxSpd then multi:enableLoadDetection() end local val = nil local bench - self:benchMark(.01):OnBench(function(time,steps) + local bb + self:benchMark(.01).OnBench(function(time,steps) bench = steps bb = steps end) _,timeout = multi.hold(function() return bench - end,{sleep=.011}) + end,{sleep=.012}) if timeout then - bench = 150000 + bench = 0 + bb = 0 end bench = bench^1.5 val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) diff --git a/tests/connectionTest.lua b/tests/connectionTest.lua new file mode 100644 index 0000000..98221b4 --- /dev/null +++ b/tests/connectionTest.lua @@ -0,0 +1,71 @@ +function connectionThreadTests(multi,thread) + print("Starting Connection and Thread tests!") + func = thread:newFunction(function(count) + local a = 0 + while true do + a = a + 1 + thread.sleep(.1) + thread.pushStatus(a,count) + if a == count then break end + end + return "Done" + end) + local ret = func(10) + local ret2 = func(15) + local ret3 = func(20) + local s1,s2,s3 = 0,0,0 + ret.OnStatus(function(part,whole) + s1 = math.ceil((part/whole)*1000)/10 + end) + ret2.OnStatus(function(part,whole) + s2 = math.ceil((part/whole)*1000)/10 + end) + ret3.OnStatus(function(part,whole) + s3 = math.ceil((part/whole)*1000)/10 + end) + local err, timeout = thread.hold(ret2.OnReturn + ret.OnReturn + ret3.OnReturn,{sleep=3}) + if s1 == 100 and s2 == 100 and s3 == 100 then + print("Threads: Ok") + else + print("Threads on status error") + end + if timeout then + print("Threads or Connection error!") + else + print("Connection Test 1: Ok") + end + conn1 = multi:newConnection() + conn2 = multi:newConnection() + conn3 = multi:newConnection() + local c1,c2,c3,c4 = false,false,false,false + local a = conn1(function() + c1 = true + end) + local b = conn2(function() + c2 = true + end) + local c = conn3(function() + c3 = true + end) + local d = conn3(function() + c4 = true + end) + conn1:Fire() + conn2:Fire() + conn3:Fire() + if c1 and c2 and c3 and c4 then + print("Connection Test 2: Ok") + else + print("Connection Test 2: Error") + end + c3 = false + c4 = false + d:Destroy() + conn3:Fire() + if c3 and not(c4) then + print("Connection Test 3: Ok") + else + print("Connection Test 3: Error removing connection") + end +end +return connectionThreadTests \ No newline at end of file diff --git a/tests/objectTests.lua b/tests/objectTests.lua index afb090b..c72624d 100644 --- a/tests/objectTests.lua +++ b/tests/objectTests.lua @@ -37,6 +37,6 @@ function objectTests(multi,thread) if tloops > 10 then print("TLoops: Ok") else print("TLoops: Bad!") end if updaters > 100 then print("Updaters: Ok") else print("Updaters: Bad!") end end) - return event + thread.hold(event.OnEvent) end return objectTests \ No newline at end of file diff --git a/tests/runtests.lua b/tests/runtests.lua index 4249110..b338b81 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -16,20 +16,18 @@ package.path="./?.lua;../?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package This will be pushed directly to the master as tests start existing. ]] local multi, thread = require("multi"):init() - local good = false runTest = thread:newFunction(function() local objects = multi:newProcessor("Basic Object Tests") objects.Start() - otest = require("tests/objectTests")(objects,thread) - thread.hold(otest.OnEvent) - print("Timers: Ok") - print("Connections: Ok") - print("Threads: Ok") - print(objects:getTasksDetails()) - good = true - print("\nTests done") + require("tests/objectTests")(objects,thread) + objects.Stop() + local conn_thread = multi:newProcessor("Connection/Thread Tests") + conn_thread.Start() + require("tests/connectionTest")(conn_thread,thread) + conn_thread.Stop() + print(multi:getTasksDetails()) os.exit() -end,true) -print(runTest()) +end) +runTest() multi:mainloop() \ No newline at end of file From 588923e1b76271cd71ec10eb5ca5b5e59aecc106 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 16 Jan 2022 16:26:37 -0500 Subject: [PATCH 14/80] spaces to tabs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6982ab4..5fd63a0 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,8 @@ multi:newThread("Coroutine Based Thread",function() end) multi:newTLoop(function(loop) print("!") - loop:Destroy() - os.exit() + loop:Destroy() + os.exit() end,.3) multi:mainloop() --[[ From f7452db3ec5898d9603317aa1445e30a278770c6 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 16 Jan 2022 16:30:03 -0500 Subject: [PATCH 15/80] spaces to tabs --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5fd63a0..c6294ae 100644 --- a/README.md +++ b/README.md @@ -34,28 +34,28 @@ Usage: [Check out the documentation for more info](https://github.com/rayaman/mu local multi, thread = require("multi"):init() GLOBAL, THREAD = require("multi.integration.lanesManager"):init() multi:newSystemThread("System Thread",function() - while true do - THREAD.sleep(.1) - io.write(" World") + while true do + THREAD.sleep(.1) + io.write(" World") THREAD.kill() - end + end end) multi:newThread("Coroutine Based Thread",function() - while true do - io.write("Hello") - thread.sleep(.1) + while true do + io.write("Hello") + thread.sleep(.1) thread.kill() - end + end end) multi:newTLoop(function(loop) - print("!") - loop:Destroy() - os.exit() + print("!") + loop:Destroy() + os.exit() end,.3) multi:mainloop() --[[ while true do - multi:uManager() + multi:uManager() end ]] ``` From 14c86659102856daf4becb6445ba963ea3559b67 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 16 Jan 2022 16:30:45 -0500 Subject: [PATCH 16/80] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6294ae..96fc7da 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Multi Version: 15.2.0 Upgrade Complete **Key Changes** - All objects now use connections internally -- Connections now about 23x faster! +- Connections now about 23x faster! - Updated getTasksDetails() to handle the new method of managing threads and processors Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! From d98f3539360fa7f2e1849e9dd3b65115365e248c Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 17 Jan 2022 23:23:56 -0500 Subject: [PATCH 17/80] Tweaked processor object, added lightloop and lmanager --- changes.md | 7 ++++++ multi/init.lua | 61 ++++++++++++++++++++++++++++++++++++++++++++++---- test3.lua | 23 ++++++++++++++++--- 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/changes.md b/changes.md index c8d9d83..e0a8128 100644 --- a/changes.md +++ b/changes.md @@ -13,6 +13,11 @@ Full Update Showcase Added: --- +- multi:lManager() the uManager() to mainloop() equivalent to lightloop() + - A lightweight version of uManager() Priorities are not supported! + +- multi:newProcessor(name,nothread).run() + - new function run to the processor object to - multi:newTLoop() member functions - `TLoop:Set(set)` - Sets the time to wait for the TLoop @@ -26,6 +31,8 @@ Added: Changed: --- +- `multi:newProcessor(name,nothread)` The new argument allows you to tell the system you won't be using the Start() and Stop() functions, rather you will handle the process yourself. Using the proc.run() function. This function needs to be called to pump the events. + - Processors now also use lManager instead of uManager. - `multi.hold(n,opt)` now supports an option table like thread.hold does. - Connection Objects now pass on the parent object if created on a multiobj. This was to allow chaining to work properly with the new update diff --git a/multi/init.lua b/multi/init.lua index 0348e22..02f98d6 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -912,7 +912,7 @@ function multi.getCurrentTask() end local sandcount = 1 -function multi:newProcessor(name) +function multi:newProcessor(name,nothread) local c = {} setmetatable(c,{__index = self}) local multi,thread = require("multi"):init() -- We need to capture the t in thread @@ -920,12 +920,12 @@ function multi:newProcessor(name) sandcount = sandcount + 1 c.Mainloop = {} c.Type = "process" - c.Active = false + c.Active = false or nothread c.Name = name or "" c.process = self:newThread(c.Name,function() while true do thread.hold(function() - return c.Active + return c.Active and not(nothread) end) __CurrentProcess = c c:uManager() @@ -935,6 +935,11 @@ function multi:newProcessor(name) c.process.isProcessThread = true c.process.PID = sandcount c.OnError = c.process.OnError + function c.run() + __CurrentProcess = c + c:uManager() + __CurrentProcess = self + end function c.Start() c.Active = true return self @@ -1632,6 +1637,7 @@ function multi:lightloop(settings) local Loop=self.Mainloop local ctask while true do + __CurrentProcess = self for _D=#Loop,1,-1 do __CurrentTask = Loop[_D] ctask = __CurrentTask @@ -1899,8 +1905,55 @@ function multi:uManager(settings) end multi.OnLoad:Fire() self.uManager=self.uManagerRef + self.lManager=self.lManagerRef end -function multi:uManagerRef(settings) +function multi:lManager(settings) + __CurrentProcess = self + multi.OnPreLoad:Fire() + multi.defaultSettings = settings or multi.defaultSettings + self.t,self.tt = clock(),0 + if settings then + priority = settings.priority + if settings.auto_priority then + priority = -1 + end + if settings.preLoop then + settings.preLoop(self) + end + if settings.stopOnError then + stopOnError = settings.stopOnError + end + multi.defaultSettings.p_i = self.Priority_Idle + if settings.auto_stretch then + multi.defaultSettings.p_i = settings.auto_stretch*self.Priority_Idle + end + multi.defaultSettings.delay = settings.auto_delay or 3 + multi.defaultSettings.auto_lowerbound = settings.auto_lowerbound or self.Priority_Idle + protect = settings.protect + end + multi.OnLoad:Fire() + self.uManager=self.uManagerRef + self.lManager=self.lManagerRef +end +function multi:lManagerRef() + if self.Active then + local Loop=self.Mainloop + local ctask + while true do + __CurrentProcess = self + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask.Active then + if not protect then + ctask:Act() + end + end + end + end + end +end +function multi:uManagerRef() if self.Active then local Loop=self.Mainloop local PS=self diff --git a/test3.lua b/test3.lua index ed8d0d6..94458d5 100644 --- a/test3.lua +++ b/test3.lua @@ -22,8 +22,25 @@ local multi,thread = require("multi"):init() -- print("Function Done",handler.getReturns()) -- print("Function Done",handler2.getReturns()) -- end) -multi:benchMark(1):OnBench(function(sec,steps) + + +-- multi:benchMark(1):OnBench(function(sec,steps) +-- print("Steps:",steps) +-- os.exit() +-- end) + + +local test = multi:newProcessor("test",true) +test:benchMark(1):OnBench(function(sec,steps) print("Steps:",steps) - os.exit() + --os.exit() end) -multi:mainloop() \ No newline at end of file +test:newThread(function() + while true do + thread.sleep(1) + print("hi") + end +end) +test:lightloop() + +-- multi:lightloop() \ No newline at end of file From 6c1e9f26f0f8fbf314b90440190193872e807706 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 18 Jan 2022 23:39:17 -0500 Subject: [PATCH 18/80] Reworking the loops --- multi/init.lua | 382 +++++++++++++++++++++++++++++-------------------- test3.lua | 14 +- 2 files changed, 226 insertions(+), 170 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 02f98d6..c98b2a6 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1664,8 +1664,14 @@ function multi:mainloop(settings) local delay = 3 if settings then priority = settings.priority - if settings.auto_priority then - priority = -1 + if priority == 1 then + self.uManager = self.uManagerRefP1 + elseif self.priority == 2 then + self.uManager = self.uManagerRefP2 + elseif self.priority == 3 then + self.uManager = self.uManagerRefP3 + elseif auto_priority then + self.uManager = self.uManagerRefP0 end if settings.preLoop then settings.preLoop(self) @@ -1879,15 +1885,22 @@ function multi:mainloop(settings) return "Already Running!" end end + function multi:uManager(settings) __CurrentProcess = self multi.OnPreLoad:Fire() multi.defaultSettings = settings or multi.defaultSettings self.t,self.tt = clock(),0 if settings then - priority = settings.priority - if settings.auto_priority then - priority = -1 + local priority = settings.priority + if priority == 1 then + self.uManager = self.uManagerRefP1 + elseif self.priority == 2 then + self.uManager = self.uManagerRefP2 + elseif self.priority == 3 then + self.uManager = self.uManagerRefP3 + elseif auto_priority then + self.uManager = self.uManagerRefP0 end if settings.preLoop then settings.preLoop(self) @@ -1905,8 +1918,8 @@ function multi:uManager(settings) end multi.OnLoad:Fire() self.uManager=self.uManagerRef - self.lManager=self.lManagerRef end + function multi:lManager(settings) __CurrentProcess = self multi.OnPreLoad:Fire() @@ -1935,6 +1948,7 @@ function multi:lManager(settings) self.uManager=self.uManagerRef self.lManager=self.lManagerRef end + function multi:lManagerRef() if self.Active then local Loop=self.Mainloop @@ -1953,53 +1967,32 @@ function multi:lManagerRef() end end end -function multi:uManagerRef() + +local ctask +local Loop + +function multi:uManagerRefP1() if self.Active then - local Loop=self.Mainloop - local PS=self - if multi.defaultSettings.priority==1 then - for _D=#Loop,1,-1 do - for P=1,7 do - if Loop[_D] then - if (PS.PList[P])%Loop[_D].Priority==0 then - if Loop[_D].Active then - self.CID=_D - if not multi.defaultSettings.protect then - Loop[_D]:Act() - __CurrentProcess = self - else - local status, err=pcall(Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if multi.defaultSettings.stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end - end - end - elseif multi.defaultSettings.priority==2 then - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PStep)%Loop[_D].Priority==0 then - if Loop[_D].Active then - self.CID=_D + __CurrentProcess = self + Loop=self.Mainloop + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + ctask = __CurrentTask + for P=1,7 do + if ctask then + if (PS.PList[P])%ctask.Priority==0 then + if ctask.Active then if not multi.defaultSettings.protect then - Loop[_D]:Act() + ctask:Act() __CurrentProcess = self else - local status, err=pcall(Loop[_D].Act,Loop[_D]) + local status, err=pcall(ctask.Act,ctask) __CurrentProcess = self if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) + ctask.error=err + self.OnError:Fire(ctask,err) if multi.defaultSettings.stopOnError then - Loop[_D]:Destroy() + ctask:Destroy() end end end @@ -2007,125 +2000,196 @@ function multi:uManagerRef() end end end - PS.PStep=PS.PStep+1 - if PS.PStep>self.Priority_Idle then - PS.PStep=0 - end - elseif priority == 3 then - self.tt = clock()-self.t - self.t = clock() - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Priority == self.Priority_Core or (Loop[_D].Priority == self.Priority_High and tt<.5) or (Loop[_D].Priority == self.Priority_Above_Normal and tt<.125) or (Loop[_D].Priority == self.Priority_Normal and tt<.063) or (Loop[_D].Priority == self.Priority_Below_Normal and tt<.016) or (Loop[_D].Priority == self.Priority_Low and tt<.003) or (Loop[_D].Priority == self.Priority_Idle and tt<.001) then - if Loop[_D].Active then - self.CID=_D - if not protect then - Loop[_D]:Act() - __CurrentProcess = self - else - local status, err=pcall(Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if multi.defaultSettings.stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end - end - elseif priority == -1 then - for _D=#Loop,1,-1 do - local sRef = Loop[_D] - if Loop[_D] then - if (sRef.Priority == self.Priority_Core) or PStep==0 then - if sRef.Active then - self.CID=_D - if not protect then - if sRef.solid then - sRef:Act() - __CurrentProcess = self - solid = true - else - time = multi.timer(sRef.Act,sRef) - sRef.solid = true - solid = false - end - if Loop[_D] and not solid then - if time == 0 then - Loop[_D].Priority = self.Priority_Core - else - Loop[_D].Priority = multi.defaultSettings.auto_lowerbound - end - end - else - if Loop[_D].solid then - Loop[_D]:Act() - __CurrentProcess = self - solid = true - else - time, status, err=multi.timer(pcall,Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - Loop[_D].solid = true - solid = false - end - if Loop[_D] and not solid then - if time == 0 then - Loop[_D].Priority = self.Priority_Core - else - Loop[_D].Priority = multi.defaultSettings.auto_lowerbound - end - end - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if multi.defaultSettings.stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end - end - self.PStep=self.PStep+1 - if self.PStep>multi.defaultSettings.p_i then - self.PStep=0 - if clock()-self.lastTime>multi.defaultSettings.delay then - self.lastTime = clock() - for i = 1,#Loop do - Loop[i]:ResetPriority() - end - end - end - else - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - self.CID=_D - if not multi.defaultSettings.protect then - Loop[_D]:Act() - __CurrentProcess = self - else - local status, err=pcall(Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - end - end - end - end - end end end end + +function multi:uManagerRefP2() + if self.Active then + __CurrentProcess = self + Loop=self.Mainloop + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask then + if (PS.PStep)%ctask.Priority==0 then + if ctask.Active then + self.CID=_D + if not multi.defaultSettings.protect then + ctask:Act() + __CurrentProcess = self + else + local status, err=pcall(ctask.Act,ctask) + __CurrentProcess = self + if err then + ctask.error=err + self.OnError:Fire(ctask,err) + if multi.defaultSettings.stopOnError then + ctask:Destroy() + end + end + end + end + end + end + end + PS.PStep=PS.PStep+1 + if PS.PStep>self.Priority_Idle then + PS.PStep=0 + end + end +end + +function multi:uManagerRefP3() + if self.Active then + self.tt = clock()-self.t + self.t = clock() + __CurrentProcess = self + Loop=self.Mainloop + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask then + if ctask.Priority == self.Priority_Core or (ctask.Priority == self.Priority_High and tt<.5) or (ctask.Priority == self.Priority_Above_Normal and tt<.125) or (ctask.Priority == self.Priority_Normal and tt<.063) or (ctask.Priority == self.Priority_Below_Normal and tt<.016) or (ctask.Priority == self.Priority_Low and tt<.003) or (ctask.Priority == self.Priority_Idle and tt<.001) then + if ctask.Active then + if not protect then + ctask:Act() + __CurrentProcess = self + else + local status, err=pcall(ctask.Act,ctask) + __CurrentProcess = self + if err then + ctask.error=err + self.OnError:Fire(ctask,err) + if multi.defaultSettings.stopOnError then + ctask:Destroy() + end + end + end + end + end + end + end + end +end + +function multi:uManagerRefP0() + if self.Active then + __CurrentProcess = self + Loop=self.Mainloop + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask then + if (ctask.Priority == self.Priority_Core) or PStep==0 then + if ctask.Active then + if not protect then + if ctask.solid then + ctask:Act() + __CurrentProcess = self + solid = true + else + time = multi.timer(ctask.Act,ctask) + ctask.solid = true + solid = false + end + if ctask and not solid then + if time == 0 then + ctask.Priority = self.Priority_Core + else + ctask.Priority = multi.defaultSettings.auto_lowerbound + end + end + else + if ctask.solid then + ctask:Act() + __CurrentProcess = self + solid = true + else + time, status, err=multi.timer(pcall,ctask.Act,ctask) + __CurrentProcess = self + ctask.solid = true + solid = false + end + if ctask and not solid then + if time == 0 then + ctask.Priority = self.Priority_Core + else + ctask.Priority = multi.defaultSettings.auto_lowerbound + end + end + if err then + ctask.error=err + self.OnError:Fire(ctask,err) + if multi.defaultSettings.stopOnError then + ctask:Destroy() + end + end + end + end + end + end + end + self.PStep=self.PStep+1 + if self.PStep>multi.defaultSettings.p_i then + self.PStep=0 + if clock()-self.lastTime>multi.defaultSettings.delay then + self.lastTime = clock() + for i = 1,#Loop do + Loop[i]:ResetPriority() + end + end + end + end +end + +function multi:uManagerRefLight() + if self.Active then + local Loop=self.Mainloop + local ctask + __CurrentProcess = self + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask.Active then + if not protect then + ctask:Act() + end + end + end + end +end + +function multi:uManagerRef() + if self.Active then + __CurrentProcess = self + Loop=self.Mainloop + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask then + if ctask.Active then + if multi.defaultSettings.protect then + __CurrentProcess = self + local status, err = pcall(ctask.Act,ctask) + if err then + ctask.error = err + self.OnError:Fire(ctask,err) + end + else + __CurrentProcess = self + ctask:Act() + end + end + end + end + end +end + -------- -- UTILS -------- + function table.merge(t1, t2) for k,v in pairs(t2) do if type(v) == 'table' then diff --git a/test3.lua b/test3.lua index 94458d5..1d06c2a 100644 --- a/test3.lua +++ b/test3.lua @@ -29,18 +29,10 @@ local multi,thread = require("multi"):init() -- os.exit() -- end) - -local test = multi:newProcessor("test",true) -test:benchMark(1):OnBench(function(sec,steps) +multi:benchMark(1):OnBench(function(sec,steps) print("Steps:",steps) --os.exit() end) -test:newThread(function() - while true do - thread.sleep(1) - print("hi") - end -end) -test:lightloop() +--multi:mainloop() --- multi:lightloop() \ No newline at end of file +multi:lightloop() \ No newline at end of file From f1f6e30a9833625630b84f8e6bfff179807b0102 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 19 Jan 2022 13:41:29 -0500 Subject: [PATCH 19/80] testing --- test3.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test3.lua b/test3.lua index 1d06c2a..248cf89 100644 --- a/test3.lua +++ b/test3.lua @@ -33,6 +33,6 @@ multi:benchMark(1):OnBench(function(sec,steps) print("Steps:",steps) --os.exit() end) ---multi:mainloop() +multi:mainloop() -multi:lightloop() \ No newline at end of file +--multi:lightloop() \ No newline at end of file From 8580d92c9c95d4d9728376b56897618d3ec8ba2a Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 19 Jan 2022 23:09:47 -0500 Subject: [PATCH 20/80] Fixing issues with priority --- multi/init.lua | 21 ++++++++++++--------- test3.lua | 32 +++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index c98b2a6..2a38daf 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -75,7 +75,7 @@ multi.PriorityResolve = { } multi.PStep = 1 -multi.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} +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} multi.PriorityTick=1 multi.Priority=multi.Priority_High multi.threshold=256 @@ -1630,8 +1630,10 @@ end function multi:threadloop() multi.initThreads(true) end + function multi:lightloop(settings) multi.defaultSettings = settings or multi.defaultSettings + self.uManager=self.lManager multi.OnPreLoad:Fire() if not isRunning then local Loop=self.Mainloop @@ -1650,12 +1652,13 @@ function multi:lightloop(settings) end end end + function multi:mainloop(settings) __CurrentProcess = self multi.OnPreLoad:Fire() multi.defaultSettings = settings or multi.defaultSettings self.uManager=self.uManagerRef - local p_c,p_h,p_an,p_n,p_bn,p_l,p_i = self.Priority_Core,self.Priority_High,self.Priority_Above_Normal,self.Priority_Normal,self.Priority_Below_Normal,self.Priority_Low,self.Priority_Idle + local p_c,p_vh,p_h,p_an,p_n,p_bn,p_l,p_vl,p_i = self.Priority_Core,self.Priority_Very_High,self.Priority_High,self.Priority_Above_Normal,self.Priority_Normal,self.Priority_Below_Normal,self.Priority_Low,self.Priority_Very_Low,self.Priority_Idle local P_LB = p_i if not isRunning then local protect = false @@ -1708,9 +1711,9 @@ function multi:mainloop(settings) for _D=#Loop,1,-1 do __CurrentTask = Loop[_D] ctask = __CurrentTask - for P=1,7 do + for P=1,9 do if ctask then - if (PS.PList[P])%ctask.Priority == 0 then + if (PList[P])%ctask.Priority == 0 then if ctask.Active then self.CID = _D if not protect then @@ -1772,7 +1775,7 @@ function multi:mainloop(settings) __CurrentTask = Loop[_D] ctask = __CurrentTask if ctask then - if ctask.Priority == p_c or (ctask.Priority == p_h and tt<.5) or (ctask.Priority == p_an and tt<.125) or (ctask.Priority == p_n and tt<.063) or (ctask.Priority == p_bn and tt<.016) or (ctask.Priority == p_l and tt<.003) or (ctask.Priority == p_i and tt<.001) then + if ctask.Priority == p_c or (ctask.Priority == p_vh and tt<.55) or (ctask.Priority == p_h and tt<.5) or (ctask.Priority == p_an and tt<.125) or (ctask.Priority == p_n and tt<.063) or (ctask.Priority == p_bn and tt<.016) or (ctask.Priority == p_l and tt<.003) or (ctask.Priority == p_vl and tt<.001) or (ctask.Priority == p_i and tt<.001) then if ctask.Active then if not protect then ctask:Act() @@ -1978,9 +1981,9 @@ function multi:uManagerRefP1() for _D=#Loop,1,-1 do __CurrentTask = Loop[_D] ctask = __CurrentTask - for P=1,7 do + for P=1,9 do if ctask then - if (PS.PList[P])%ctask.Priority==0 then + if (PList[P])%ctask.Priority==0 then if ctask.Active then if not multi.defaultSettings.protect then ctask:Act() @@ -2285,7 +2288,7 @@ function multi:getLoad() end function multi:setPriority(s) - if type(s)==number then + if type(s)=="number" then self.Priority=s elseif type(s)=='string' then if s:lower()=='core' or s:lower()=='c' then @@ -2516,7 +2519,7 @@ multi.OnError=multi:newConnection() multi.OnPreLoad = multi:newConnection() multi.OnExit = multi:newConnection(nil,nil,true) multi.m = {onexit = function() multi.OnExit:Fire() end} -if _VERSION >= "Lua 5.2" then +if _VERSION >= "Lua 5.2" or jit then setmetatable(multi.m, {__gc = multi.m.onexit}) else multi.m.sentinel = newproxy(true) diff --git a/test3.lua b/test3.lua index 1d06c2a..bd771f6 100644 --- a/test3.lua +++ b/test3.lua @@ -28,11 +28,29 @@ local multi,thread = require("multi"):init() -- print("Steps:",steps) -- os.exit() -- end) - -multi:benchMark(1):OnBench(function(sec,steps) - print("Steps:",steps) - --os.exit() +print("Running benchmarks! ",_VERSION) +local sleep_for = 1 +local a = 0 +local c = 1 +local function bench(t,step) + a = a + step + c = c + 1 + if c == 9 then + print("Total: "..a) + os.exit() + end +end +multi:benchMark(sleep_for,multi.Priority_Idle,"Idle:"):OnBench(bench) +multi:benchMark(sleep_for,multi.Priority_Very_Low,"Very Low:"):OnBench(bench) +multi:benchMark(sleep_for,multi.Priority_Low,"Low:"):OnBench() +multi:benchMark(sleep_for,multi.Priority_Below_Normal,"Below Normal:"):OnBench(bench) +multi:benchMark(sleep_for,multi.Priority_Normal,"Normal:"):OnBench(bench) +multi:benchMark(sleep_for,multi.Priority_Above_Normal,"Above Normal:"):OnBench(bench) +multi:benchMark(sleep_for,multi.Priority_High,"High:"):OnBench(bench) +multi:benchMark(sleep_for,multi.Priority_Very_High,"Very High:"):OnBench(bench) +multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) +multi.OnExit(function() + print("Total: ".. a) end) ---multi:mainloop() - -multi:lightloop() \ No newline at end of file +multi:mainloop{print=true,priority=3} +--multi:lightloop() \ No newline at end of file From b9b9b51d128a403daec31f772c9af924b4696e96 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 20 Jan 2022 09:49:48 -0500 Subject: [PATCH 21/80] Taking testing seriously --- .gitignore | 7 ++++++- makeENV.bat | 10 ++++++++++ makeENV.sh | 31 +++++++++++++++++++++++++++++++ test3.lua | 7 +------ 4 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 makeENV.bat create mode 100755 makeENV.sh diff --git a/.gitignore b/.gitignore index 3ca13af..2ec23ae 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,9 @@ *.dat test.lua test2.lua -test3.lua \ No newline at end of file +test3.lua +lua5.1* +luajit* +lua5.2* +lua5.3* +lua5.4* \ No newline at end of file diff --git a/makeENV.bat b/makeENV.bat new file mode 100644 index 0000000..8e1aa82 --- /dev/null +++ b/makeENV.bat @@ -0,0 +1,10 @@ +mkdir lua5.1 +mkdir lua5.2 +mkdir lua5.3 +mkdir lua5.4 +mkdir luajit +python -m hererocks -j 2.1.0-beta3 -r latest --compat all ./luajit +python -m hererocks -l 5.1 -r latest --compat all ./lua5.1 +python -m hererocks -l 5.2 -r latest --compat all ./lua5.2 +python -m hererocks -l 5.3 -r latest --compat all ./lua5.3 +python -m hererocks -l 5.4 -r latest --compat all ./lua5.4 \ No newline at end of file diff --git a/makeENV.sh b/makeENV.sh new file mode 100755 index 0000000..2840d20 --- /dev/null +++ b/makeENV.sh @@ -0,0 +1,31 @@ +#!/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/test3.lua b/test3.lua index 9cf4338..9327646 100644 --- a/test3.lua +++ b/test3.lua @@ -52,10 +52,5 @@ multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) multi.OnExit(function() print("Total: ".. a) end) -<<<<<<< HEAD -multi:mainloop{print=true,priority=3} -======= -multi:mainloop() ->>>>>>> f1f6e30a9833625630b84f8e6bfff179807b0102 ---multi:lightloop() \ No newline at end of file +multi:mainloop{print=true,priority=3} From 9e1ecb3583b2c4ebf2d0e53344f92f8fea4eabe3 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 20 Jan 2022 09:53:56 -0500 Subject: [PATCH 22/80] Testing... --- test3.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test3.lua b/test3.lua index 9327646..13ad8f9 100644 --- a/test3.lua +++ b/test3.lua @@ -53,4 +53,4 @@ multi.OnExit(function() print("Total: ".. a) end) -multi:mainloop{print=true,priority=3} +multi:mainloop{print=true,priority=1} From f7167cf97251a8417c8eb33264a6b6ba8b763a9d Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 20 Jan 2022 12:29:39 -0500 Subject: [PATCH 23/80] Current progress in v15.2.0 updated ignore file --- .gitignore | 17 ++++++----------- README.md | 9 ++++++++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index fa5e1d5..0fb4bf9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,10 @@ - +*lua5.1 +*lua5.2 +*lua5.3 +*lua5.4 +*luajit test2.lua -*.mp3 -*.exe -*.dll -lanestestclient.lua -lanestest.lua -sample-node.lua -sample-master.lua -Ayn Rand - The Virtue of Selfishness-Mg4QJheclsQ.m4a -Atlas Shrugged by Ayn Rand Audiobook-9s2qrEau63E.webm -test.lua test.lua +test3.lua *.code-workspace *.dat diff --git a/README.md b/README.md index b0848c7..8a0895d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,14 @@ Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! -My multitasking library for lua. It is a pure lua binding, with exceptions of the integrations and the love2d compat. +My multitasking library for lua. It is a pure lua binding, with exceptions of the integrations. + +
+ +Progress is being made in [v15.2.0](https://github.com/rayaman/multi/tree/v15.2.0) +--- + +
INSTALLING ---------- From 889dc6ca680996ff234d493e5e9ec243e89e27be Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 20 Jan 2022 13:06:53 -0500 Subject: [PATCH 24/80] Removed unneeded files --- jitpaths.lua | 2 -- luapaths.lua | 1 - 2 files changed, 3 deletions(-) delete mode 100644 jitpaths.lua delete mode 100644 luapaths.lua diff --git a/jitpaths.lua b/jitpaths.lua deleted file mode 100644 index d513f01..0000000 --- a/jitpaths.lua +++ /dev/null @@ -1,2 +0,0 @@ -package.path = "./?/init.lua;C:/Luajit/lua/?.init.lua;C:/Luajit/lua/?.lua;" -package.cpath = "C:/Luajit/clib/?/core.dll;C:/Luajit/clib/?.dll;" \ No newline at end of file diff --git a/luapaths.lua b/luapaths.lua deleted file mode 100644 index 031ec1d..0000000 --- a/luapaths.lua +++ /dev/null @@ -1 +0,0 @@ -package.path = "./?/init.lua;?.lua;".. package.path \ No newline at end of file From 32f7b4492b65454b4826b912faa848dace7d72be Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 22 Jan 2022 23:34:15 -0500 Subject: [PATCH 25/80] Improving performance in the mainloop --- changes.md | 6 +- makeENV.bat | 10 -- makeENV.lua | 19 ++ multi/init.lua | 470 +++---------------------------------------------- test3.lua | 8 +- 5 files changed, 48 insertions(+), 465 deletions(-) delete mode 100644 makeENV.bat create mode 100644 makeENV.lua diff --git a/changes.md b/changes.md index e0a8128..fe7c251 100644 --- a/changes.md +++ b/changes.md @@ -13,9 +13,6 @@ Full Update Showcase Added: --- -- multi:lManager() the uManager() to mainloop() equivalent to lightloop() - - A lightweight version of uManager() Priorities are not supported! - - multi:newProcessor(name,nothread).run() - new function run to the processor object to @@ -81,6 +78,9 @@ Changed: Removed: --- +- `multi setting: protect` This added extra complexity to the mainloop and not much benefit. If you feel a function will error use pcall yourself. This saves a decent amount of cycles, about 6.25% increase in performance. +- `multi:GetParentProcess()` use `multi.getCurrentProcess()` instead +- priority scheme 2, 3 and auto-priority have been removed! Only priority scheme 1 actually performed in a reasonable fashion so that one remained. - `multi:newFunction(func)` - `thread:newFunction(func)` Has many more features and replaces what multi:newFunction did - `multi.holdFor()` Now that multi.hold takes the option table that thread.hold has this feature can be emulated using that. diff --git a/makeENV.bat b/makeENV.bat deleted file mode 100644 index 8e1aa82..0000000 --- a/makeENV.bat +++ /dev/null @@ -1,10 +0,0 @@ -mkdir lua5.1 -mkdir lua5.2 -mkdir lua5.3 -mkdir lua5.4 -mkdir luajit -python -m hererocks -j 2.1.0-beta3 -r latest --compat all ./luajit -python -m hererocks -l 5.1 -r latest --compat all ./lua5.1 -python -m hererocks -l 5.2 -r latest --compat all ./lua5.2 -python -m hererocks -l 5.3 -r latest --compat all ./lua5.3 -python -m hererocks -l 5.4 -r latest --compat all ./lua5.4 \ No newline at end of file diff --git a/makeENV.lua b/makeENV.lua new file mode 100644 index 0000000..acb6fc0 --- /dev/null +++ b/makeENV.lua @@ -0,0 +1,19 @@ +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 +]] +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/multi/init.lua b/multi/init.lua index 2a38daf..ef25ee1 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -74,7 +74,6 @@ multi.PriorityResolve = { [65536]="Idle", } -multi.PStep = 1 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} multi.PriorityTick=1 multi.Priority=multi.Priority_High @@ -996,12 +995,7 @@ function thread.sleep(n) dRef[2] = n or 0 return coroutine.yield(dRef) end --- function thread.hold(n) --- thread._Requests() --- dRef[1] = "_hold_" --- dRef[2] = n or dFunc --- return coroutine.yield(dRef) --- end + function thread.hold(n,opt) thread._Requests() if opt and type(opt)=="table" then @@ -1633,7 +1627,6 @@ end function multi:lightloop(settings) multi.defaultSettings = settings or multi.defaultSettings - self.uManager=self.lManager multi.OnPreLoad:Fire() if not isRunning then local Loop=self.Mainloop @@ -1644,9 +1637,8 @@ function multi:lightloop(settings) __CurrentTask = Loop[_D] ctask = __CurrentTask if ctask.Active then - if not protect then - ctask:Act() - end + ctask:Act() + __CurrentProcess = self end end end @@ -1661,231 +1653,55 @@ function multi:mainloop(settings) local p_c,p_vh,p_h,p_an,p_n,p_bn,p_l,p_vl,p_i = self.Priority_Core,self.Priority_Very_High,self.Priority_High,self.Priority_Above_Normal,self.Priority_Normal,self.Priority_Below_Normal,self.Priority_Low,self.Priority_Very_Low,self.Priority_Idle local P_LB = p_i if not isRunning then - local protect = false local priority = false local stopOnError = true - local delay = 3 + self.uManager = self.uManagerRef if settings then priority = settings.priority - if priority == 1 then + if priority then self.uManager = self.uManagerRefP1 - elseif self.priority == 2 then - self.uManager = self.uManagerRefP2 - elseif self.priority == 3 then - self.uManager = self.uManagerRefP3 - elseif auto_priority then - self.uManager = self.uManagerRefP0 end - if settings.preLoop then + if type(settings.preLoop)=="function" then settings.preLoop(self) end if settings.stopOnError then stopOnError = settings.stopOnError end - if settings.auto_stretch then - p_i = p_i * settings.auto_stretch - end - if settings.auto_delay then - delay = settings.auto_delay - end - if settings.auto_lowerbound then - P_LB = settings.auto_lowerbound - end - protect = settings.protect end - local t,tt = clock(),0 isRunning=true local lastTime = clock() rawset(self,'Start',clock()) mainloopActive = true local Loop=self.Mainloop - local PS=self - local PStep = 1 - local autoP = 0 - local solid,sRef - local cc=0 local ctask multi.OnLoad:Fire() while mainloopActive do - if priority == 1 then - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] + if priority then + for task=#Loop,1,-1 do + __CurrentTask = Loop[task] ctask = __CurrentTask - for P=1,9 do - if ctask then - if (PList[P])%ctask.Priority == 0 then - if ctask.Active then - self.CID = _D - if not protect then - ctask:Act() - __CurrentProcess = self - else - local status, err = pcall(ctask.Act,ctask) - __CurrentProcess = self - if err then - ctask.error=err - self.OnError:Fire(ctask,err) - if stopOnError then - ctask:Destroy() - end - end - end - end + if ctask and ctask.Active then + for i=1,9 do + if PList[i]%ctask.Priority == 0 then + ctask:Act() + __CurrentProcess = self end end end end - elseif priority == 2 then - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask then - if (PStep)%ctask.Priority==0 then - if ctask.Active then - if not protect then - ctask:Act() - __CurrentProcess = self - else - local status, err=pcall(ctask.Act,ctask) - __CurrentProcess = self - if err then - ctask.error=err - self.OnError:Fire(ctask,err) - if stopOnError then - ctask:Destroy() - end - end - end - end - end - end - end - PStep=PStep+1 - if PStep==p_i then - PStep=0 - end - elseif priority == 3 then - cc=cc+1 - if cc == 1000 then - tt = clock()-t - t = clock() - cc=0 - end - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask then - if ctask.Priority == p_c or (ctask.Priority == p_vh and tt<.55) or (ctask.Priority == p_h and tt<.5) or (ctask.Priority == p_an and tt<.125) or (ctask.Priority == p_n and tt<.063) or (ctask.Priority == p_bn and tt<.016) or (ctask.Priority == p_l and tt<.003) or (ctask.Priority == p_vl and tt<.001) or (ctask.Priority == p_i and tt<.001) then - if ctask.Active then - if not protect then - ctask:Act() - __CurrentProcess = self - else - local status, err=pcall(ctask.Act,ctask) - __CurrentProcess = self - if err then - ctask.error=err - self.OnError:Fire(ctask,err) - if stopOnError then - ctask:Destroy() - end - end - end - end - end - end - end - elseif priority == -1 then - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask then - if (ctask.Priority == p_c) or PStep==0 then - if ctask.Active then - if not protect then - if ctask.solid then - ctask:Act() - __CurrentProcess = self - solid = true - else - time = multi.timer(ctask.Act,ctask) - ctask.solid = true - solid = false - end - if ctask and not solid then - if time == 0 then - ctask.Priority = p_c - else - ctask.Priority = P_LB - end - end - else - if ctask.solid then - ctask:Act() - __CurrentProcess = self - solid = true - else - time, status, err=multi.timer(pcall,ctask.Act,ctask) - __CurrentProcess = self - ctask.solid = true - solid = false - end - if ctask and not solid then - if time == 0 then - ctask.Priority = p_c - else - ctask.Priority = P_LB - end - end - if err then - ctask.error=err - self.OnError:Fire(ctask,err) - if stopOnError then - ctask:Destroy() - end - end - end - end - end - end - end - PStep=PStep+1 - if PStep>p_i then - PStep=0 - if clock()-lastTime>delay then - lastTime = clock() - for i = 1,#Loop do - Loop[i]:ResetPriority() - end - end - end else for _D=#Loop,1,-1 do __CurrentTask = Loop[_D] ctask = __CurrentTask - if ctask then - if ctask.Active then - if not protect then - ctask:Act() - __CurrentProcess = self - else - local status, err=pcall(ctask.Act,ctask) - __CurrentProcess = self - if err then - ctask.error=err - self.OnError:Fire(ctask,err) - if stopOnError then - ctask:Destroy() - end - end - end - end + if ctask and ctask.Active then + ctask:Act() + __CurrentProcess = self end end end end else - return "Already Running!" + return nil, "Already Running!" end end @@ -1893,7 +1709,7 @@ function multi:uManager(settings) __CurrentProcess = self multi.OnPreLoad:Fire() multi.defaultSettings = settings or multi.defaultSettings - self.t,self.tt = clock(),0 + self.uManager=self.uManagerRef if settings then local priority = settings.priority if priority == 1 then @@ -1911,64 +1727,8 @@ function multi:uManager(settings) if settings.stopOnError then stopOnError = settings.stopOnError end - multi.defaultSettings.p_i = self.Priority_Idle - if settings.auto_stretch then - multi.defaultSettings.p_i = settings.auto_stretch*self.Priority_Idle - end - multi.defaultSettings.delay = settings.auto_delay or 3 - multi.defaultSettings.auto_lowerbound = settings.auto_lowerbound or self.Priority_Idle - protect = settings.protect end multi.OnLoad:Fire() - self.uManager=self.uManagerRef -end - -function multi:lManager(settings) - __CurrentProcess = self - multi.OnPreLoad:Fire() - multi.defaultSettings = settings or multi.defaultSettings - self.t,self.tt = clock(),0 - if settings then - priority = settings.priority - if settings.auto_priority then - priority = -1 - end - if settings.preLoop then - settings.preLoop(self) - end - if settings.stopOnError then - stopOnError = settings.stopOnError - end - multi.defaultSettings.p_i = self.Priority_Idle - if settings.auto_stretch then - multi.defaultSettings.p_i = settings.auto_stretch*self.Priority_Idle - end - multi.defaultSettings.delay = settings.auto_delay or 3 - multi.defaultSettings.auto_lowerbound = settings.auto_lowerbound or self.Priority_Idle - protect = settings.protect - end - multi.OnLoad:Fire() - self.uManager=self.uManagerRef - self.lManager=self.lManagerRef -end - -function multi:lManagerRef() - if self.Active then - local Loop=self.Mainloop - local ctask - while true do - __CurrentProcess = self - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask.Active then - if not protect then - ctask:Act() - end - end - end - end - end end local ctask @@ -1983,183 +1743,15 @@ function multi:uManagerRefP1() ctask = __CurrentTask for P=1,9 do if ctask then - if (PList[P])%ctask.Priority==0 then + if PList[P]%ctask.Priority==0 then if ctask.Active then - if not multi.defaultSettings.protect then - ctask:Act() - __CurrentProcess = self - else - local status, err=pcall(ctask.Act,ctask) - __CurrentProcess = self - if err then - ctask.error=err - self.OnError:Fire(ctask,err) - if multi.defaultSettings.stopOnError then - ctask:Destroy() - end - end - end - end - end - end - end - end - end -end - -function multi:uManagerRefP2() - if self.Active then - __CurrentProcess = self - Loop=self.Mainloop - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask then - if (PS.PStep)%ctask.Priority==0 then - if ctask.Active then - self.CID=_D - if not multi.defaultSettings.protect then ctask:Act() __CurrentProcess = self - else - local status, err=pcall(ctask.Act,ctask) - __CurrentProcess = self - if err then - ctask.error=err - self.OnError:Fire(ctask,err) - if multi.defaultSettings.stopOnError then - ctask:Destroy() - end - end end end end end end - PS.PStep=PS.PStep+1 - if PS.PStep>self.Priority_Idle then - PS.PStep=0 - end - end -end - -function multi:uManagerRefP3() - if self.Active then - self.tt = clock()-self.t - self.t = clock() - __CurrentProcess = self - Loop=self.Mainloop - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask then - if ctask.Priority == self.Priority_Core or (ctask.Priority == self.Priority_High and tt<.5) or (ctask.Priority == self.Priority_Above_Normal and tt<.125) or (ctask.Priority == self.Priority_Normal and tt<.063) or (ctask.Priority == self.Priority_Below_Normal and tt<.016) or (ctask.Priority == self.Priority_Low and tt<.003) or (ctask.Priority == self.Priority_Idle and tt<.001) then - if ctask.Active then - if not protect then - ctask:Act() - __CurrentProcess = self - else - local status, err=pcall(ctask.Act,ctask) - __CurrentProcess = self - if err then - ctask.error=err - self.OnError:Fire(ctask,err) - if multi.defaultSettings.stopOnError then - ctask:Destroy() - end - end - end - end - end - end - end - end -end - -function multi:uManagerRefP0() - if self.Active then - __CurrentProcess = self - Loop=self.Mainloop - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask then - if (ctask.Priority == self.Priority_Core) or PStep==0 then - if ctask.Active then - if not protect then - if ctask.solid then - ctask:Act() - __CurrentProcess = self - solid = true - else - time = multi.timer(ctask.Act,ctask) - ctask.solid = true - solid = false - end - if ctask and not solid then - if time == 0 then - ctask.Priority = self.Priority_Core - else - ctask.Priority = multi.defaultSettings.auto_lowerbound - end - end - else - if ctask.solid then - ctask:Act() - __CurrentProcess = self - solid = true - else - time, status, err=multi.timer(pcall,ctask.Act,ctask) - __CurrentProcess = self - ctask.solid = true - solid = false - end - if ctask and not solid then - if time == 0 then - ctask.Priority = self.Priority_Core - else - ctask.Priority = multi.defaultSettings.auto_lowerbound - end - end - if err then - ctask.error=err - self.OnError:Fire(ctask,err) - if multi.defaultSettings.stopOnError then - ctask:Destroy() - end - end - end - end - end - end - end - self.PStep=self.PStep+1 - if self.PStep>multi.defaultSettings.p_i then - self.PStep=0 - if clock()-self.lastTime>multi.defaultSettings.delay then - self.lastTime = clock() - for i = 1,#Loop do - Loop[i]:ResetPriority() - end - end - end - end -end - -function multi:uManagerRefLight() - if self.Active then - local Loop=self.Mainloop - local ctask - __CurrentProcess = self - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask.Active then - if not protect then - ctask:Act() - end - end - end end end @@ -2172,17 +1764,8 @@ function multi:uManagerRef() ctask = __CurrentTask if ctask then if ctask.Active then - if multi.defaultSettings.protect then - __CurrentProcess = self - local status, err = pcall(ctask.Act,ctask) - if err then - ctask.error = err - self.OnError:Fire(ctask,err) - end - else - __CurrentProcess = self - ctask:Act() - end + __CurrentProcess = self + ctask:Act() end end end @@ -2238,8 +1821,7 @@ setmetatable(multi.DestroyedObj, { }) math.randomseed(os.time()) multi.defaultSettings = { - priority = 0, - protect = false, + priority = 0 } function multi:enableLoadDetection() @@ -2310,7 +1892,6 @@ function multi:setPriority(s) elseif s:lower()=='idle' or s:lower()=='i' then self.Priority=self.Priority_Idle end - self.solid = true end if not self.PrioritySet then self.defPriority = self.Priority @@ -2351,10 +1932,6 @@ function multi.randomString(n) return str end -function multi:getParentProcess() - return self.Mainloop[self.CID] -end - function multi:getChildren() return self.Mainloop end @@ -2503,7 +2080,6 @@ multi.GetType=multi.getType multi.IsPaused=multi.isPaused multi.IsActive=multi.isActive multi.Reallocate=multi.Reallocate -multi.GetParentProcess=multi.getParentProcess multi.ConnectFinal=multi.connectFinal multi.ResetTime=multi.SetTime multi.IsDone=multi.isDone diff --git a/test3.lua b/test3.lua index 13ad8f9..3de5ee1 100644 --- a/test3.lua +++ b/test3.lua @@ -1,6 +1,4 @@ -package.path = "./?.lua" -require("jitpaths") ---require("luapaths") +package.path = "./?.lua;?/init.lua;"..package.path local multi,thread = require("multi"):init() --local GLOBAL,THREAD = require("multi.integration.lanesManager"):init() @@ -29,7 +27,7 @@ local multi,thread = require("multi"):init() -- os.exit() -- end) print("Running benchmarks! ",_VERSION) -local sleep_for = 1 +local sleep_for = 3 local a = 0 local c = 1 local function bench(t,step) @@ -39,7 +37,7 @@ local function bench(t,step) print("Total: "..a) os.exit() end -end +end--p_c,p_vh,p_h,p_an,p_n,p_bn,p_l,p_vl,p_i multi:benchMark(sleep_for,multi.Priority_Idle,"Idle:"):OnBench(bench) multi:benchMark(sleep_for,multi.Priority_Very_Low,"Very Low:"):OnBench(bench) multi:benchMark(sleep_for,multi.Priority_Low,"Low:"):OnBench() From e194a06427de259aaed8483af2261925d68258e4 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 23 Jan 2022 12:14:50 -0500 Subject: [PATCH 26/80] Cleaned up the mainloop/umanager --- changes.md | 2 + multi/init.lua | 181 +++++++++++++++++-------------------------------- test3.lua | 18 ++--- 3 files changed, 74 insertions(+), 127 deletions(-) diff --git a/changes.md b/changes.md index fe7c251..125f290 100644 --- a/changes.md +++ b/changes.md @@ -78,6 +78,8 @@ Changed: Removed: --- +- `multi:lightloop()` Cleaned up the mainloop/uManager method, actually faster than lightloop (Which should have been called liteloop) +- `multi:threadloop()` See above for reasons - `multi setting: protect` This added extra complexity to the mainloop and not much benefit. If you feel a function will error use pcall yourself. This saves a decent amount of cycles, about 6.25% increase in performance. - `multi:GetParentProcess()` use `multi.getCurrentProcess()` instead - priority scheme 2, 3 and auto-priority have been removed! Only priority scheme 1 actually performed in a reasonable fashion so that one remained. diff --git a/multi/init.lua b/multi/init.lua index ef25ee1..a01cd4f 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -51,6 +51,7 @@ multi.LinkedPath = multi multi.lastTime = clock() multi.TIMEOUT = "TIMEOUT" multi.TID = 0 +multi.defaultSettings = {} multi.Priority_Core = 1 multi.Priority_Very_High = 4 @@ -79,10 +80,6 @@ multi.PriorityTick=1 multi.Priority=multi.Priority_High multi.threshold=256 multi.threstimed=.001 - -function multi.init() - return _G["$multi"].multi,_G["$multi"].thread -end -- System function multi.Stop() @@ -152,16 +149,16 @@ function multi:getTasksDetails(t) end dat = multi.AlignTable(proc_tab).. "\n" dat = dat .. "\n" .. multi.AlignTable(th_tab) - return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: "..thread_count.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat..dat2.."\n\n"..s + return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: "..thread_count.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.settings.priority or 0].."\n\n"..dat..dat2.."\n\n"..s else - return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\n\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat2.."\n\n"..s + return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\n\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.settings.priority or 0].."\n\n"..dat2.."\n\n"..s end else local load,steps = self:getLoad() str = { ProcessName = (self.Name or "Unnamed"), MemoryUsage = math.ceil(collectgarbage("count")), - PriorityScheme = priorityTable[multi.defaultSettings.priority or 0], + PriorityScheme = priorityTable[multi.settings.priority or 0], SystemLoad = multi.Round(load,2), CyclesPerSecondPerTask = steps, SystemThreadCount = multi.SystemThreads and #multi.SystemThreads or 0 @@ -261,7 +258,7 @@ function multi:newConnection(protect,func,kill) end end) repeat - self.Parent:uManager(multi.defaultSettings) + self.Parent:uManager(multi.settings) until self.waiting==false id:Destroy() return self @@ -1621,79 +1618,46 @@ function multi:newService(func) -- Priority managed threads return c end -- Multi runners -function multi:threadloop() - multi.initThreads(true) -end - -function multi:lightloop(settings) - multi.defaultSettings = settings or multi.defaultSettings - multi.OnPreLoad:Fire() - if not isRunning then - local Loop=self.Mainloop - local ctask - while true do - __CurrentProcess = self - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask.Active then - ctask:Act() - __CurrentProcess = self - end - end - end - end -end - -function multi:mainloop(settings) +local function mainloop(self) __CurrentProcess = self multi.OnPreLoad:Fire() - multi.defaultSettings = settings or multi.defaultSettings - self.uManager=self.uManagerRef - local p_c,p_vh,p_h,p_an,p_n,p_bn,p_l,p_vl,p_i = self.Priority_Core,self.Priority_Very_High,self.Priority_High,self.Priority_Above_Normal,self.Priority_Normal,self.Priority_Below_Normal,self.Priority_Low,self.Priority_Very_Low,self.Priority_Idle - local P_LB = p_i + self.uManager = self.uManagerRef if not isRunning then - local priority = false - local stopOnError = true - self.uManager = self.uManagerRef - if settings then - priority = settings.priority - if priority then - self.uManager = self.uManagerRefP1 - end - if type(settings.preLoop)=="function" then - settings.preLoop(self) - end - if settings.stopOnError then - stopOnError = settings.stopOnError - end - end isRunning=true - local lastTime = clock() - rawset(self,'Start',clock()) mainloopActive = true local Loop=self.Mainloop local ctask multi.OnLoad:Fire() while mainloopActive do - if priority then - for task=#Loop,1,-1 do - __CurrentTask = Loop[task] - ctask = __CurrentTask - if ctask and ctask.Active then - for i=1,9 do - if PList[i]%ctask.Priority == 0 then - ctask:Act() - __CurrentProcess = self - end - end - end - end - else - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask and ctask.Active then + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + ctask = __CurrentTask + ctask:Act() + __CurrentProcess = self + end + end + else + return nil, "Already Running!" + end +end +multi.mainloop = mainloop + +local function p_mainloop(self) + __CurrentProcess = self + multi.OnPreLoad:Fire() + self.uManager = self.uManagerRefP1 + if not isRunning then + isRunning=true + mainloopActive = true + local Loop = self.Mainloop + local ctask + multi.OnLoad:Fire() + while mainloopActive do + for task=#Loop,1,-1 do + __CurrentTask = Loop[task] + ctask = __CurrentTask + for i=1,9 do + if PList[i]%ctask.Priority == 0 then ctask:Act() __CurrentProcess = self end @@ -1704,51 +1668,41 @@ function multi:mainloop(settings) return nil, "Already Running!" end end - -function multi:uManager(settings) - __CurrentProcess = self - multi.OnPreLoad:Fire() - multi.defaultSettings = settings or multi.defaultSettings - self.uManager=self.uManagerRef - if settings then - local priority = settings.priority - if priority == 1 then - self.uManager = self.uManagerRefP1 - elseif self.priority == 2 then - self.uManager = self.uManagerRefP2 - elseif self.priority == 3 then - self.uManager = self.uManagerRefP3 - elseif auto_priority then - self.uManager = self.uManagerRefP0 - end - if settings.preLoop then - settings.preLoop(self) - end - if settings.stopOnError then - stopOnError = settings.stopOnError +local init = false +function multi.init(settings, realsettings) + if settings == multi then settings = realsettings end + if init then return _G["$multi"].multi,_G["$multi"].thread end + init = true + if type(settings)=="table" then + multi.defaultSettings = settings + if settings.priority then + multi.mainloop = p_mainloop + else + multi.mainloop = mainloop end end - multi.OnLoad:Fire() + return _G["$multi"].multi,_G["$multi"].thread end -local ctask -local Loop +function multi:uManager(settings) + if self.Active then + __CurrentProcess = self + multi.OnPreLoad:Fire() + self.uManager=self.uManagerRef + multi.OnLoad:Fire() + end +end function multi:uManagerRefP1() if self.Active then __CurrentProcess = self - Loop=self.Mainloop + local Loop=self.Mainloop for _D=#Loop,1,-1 do __CurrentTask = Loop[_D] - ctask = __CurrentTask for P=1,9 do - if ctask then - if PList[P]%ctask.Priority==0 then - if ctask.Active then - ctask:Act() - __CurrentProcess = self - end - end + if PList[P]%__CurrentTask.Priority==0 then + __CurrentTask:Act() + __CurrentProcess = self end end end @@ -1758,16 +1712,11 @@ end function multi:uManagerRef() if self.Active then __CurrentProcess = self - Loop=self.Mainloop + local Loop=self.Mainloop for _D=#Loop,1,-1 do __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask then - if ctask.Active then - __CurrentProcess = self - ctask:Act() - end - end + __CurrentTask:Act() + __CurrentProcess = self end end end @@ -1820,15 +1769,11 @@ setmetatable(multi.DestroyedObj, { end,__newindex = uni,__call = uni,__metatable = multi.DestroyedObj,__tostring = function() return "destroyed" end,__unm = uni,__add = uni,__sub = uni,__mul = uni,__div = uni,__mod = uni,__pow = uni,__concat = uni }) math.randomseed(os.time()) -multi.defaultSettings = { - priority = 0 -} function multi:enableLoadDetection() if multi.maxSpd then return end -- here we are going to run a quick benchMark solo local temp = self:newProcessor() - temp:Start() local t = os.clock() local stop = false temp:benchMark(.01):OnBench(function(time,steps) diff --git a/test3.lua b/test3.lua index 3de5ee1..ecb040c 100644 --- a/test3.lua +++ b/test3.lua @@ -1,5 +1,5 @@ package.path = "./?.lua;?/init.lua;"..package.path -local multi,thread = require("multi"):init() +local multi,thread = require("multi"):init{print=true,priority=true} --local GLOBAL,THREAD = require("multi.integration.lanesManager"):init() -- func = THREAD:newFunction(function(a,b,c) @@ -27,28 +27,28 @@ local multi,thread = require("multi"):init() -- os.exit() -- end) print("Running benchmarks! ",_VERSION) -local sleep_for = 3 +local sleep_for = 1 local a = 0 local c = 1 local function bench(t,step) a = a + step c = c + 1 - if c == 9 then - print("Total: "..a) + if c == 5 then + --print("Total: "..a) os.exit() end end--p_c,p_vh,p_h,p_an,p_n,p_bn,p_l,p_vl,p_i multi:benchMark(sleep_for,multi.Priority_Idle,"Idle:"):OnBench(bench) -multi:benchMark(sleep_for,multi.Priority_Very_Low,"Very Low:"):OnBench(bench) +--multi:benchMark(sleep_for,multi.Priority_Very_Low,"Very Low:"):OnBench(bench) multi:benchMark(sleep_for,multi.Priority_Low,"Low:"):OnBench() -multi:benchMark(sleep_for,multi.Priority_Below_Normal,"Below Normal:"):OnBench(bench) +--multi:benchMark(sleep_for,multi.Priority_Below_Normal,"Below Normal:"):OnBench(bench) multi:benchMark(sleep_for,multi.Priority_Normal,"Normal:"):OnBench(bench) -multi:benchMark(sleep_for,multi.Priority_Above_Normal,"Above Normal:"):OnBench(bench) +--multi:benchMark(sleep_for,multi.Priority_Above_Normal,"Above Normal:"):OnBench(bench) multi:benchMark(sleep_for,multi.Priority_High,"High:"):OnBench(bench) -multi:benchMark(sleep_for,multi.Priority_Very_High,"Very High:"):OnBench(bench) +--multi:benchMark(sleep_for,multi.Priority_Very_High,"Very High:"):OnBench(bench) multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) multi.OnExit(function() print("Total: ".. a) end) -multi:mainloop{print=true,priority=1} +multi:mainloop() From 3fcba8825bc29125f8d83d8197d4310b6969bd42 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 23 Jan 2022 17:23:32 -0500 Subject: [PATCH 27/80] Reworked priorities --- changes.md | 9 +++++ multi/init.lua | 89 +++++++++++++++++++++++++++------------------- test3.lua | 17 ++++----- tests/runtests.lua | 2 +- 4 files changed, 70 insertions(+), 47 deletions(-) diff --git a/changes.md b/changes.md index 125f290..1d4e50f 100644 --- a/changes.md +++ b/changes.md @@ -28,6 +28,11 @@ Added: Changed: --- +- multi:mainloop(~~settings~~)/multi:uManager(~~settings~~) no longer takes a settings argument, that has been moved to multi:init(settings) + | Setting | Description | + ---|--- + print | When set to true parts of the library will print out updates otherwise no internal printing will be done + priority | When set to true, the library will prioritize different objects based on their priority - `multi:newProcessor(name,nothread)` The new argument allows you to tell the system you won't be using the Start() and Stop() functions, rather you will handle the process yourself. Using the proc.run() function. This function needs to be called to pump the events. - Processors now also use lManager instead of uManager. - `multi.hold(n,opt)` now supports an option table like thread.hold does. @@ -78,6 +83,10 @@ Changed: Removed: --- +- `multi:getError()` Removed when setting protect was removed +- `multi:FreeMainEvent()` The new changes with connections make's this function unnecessary +- `multi:OnMainConnect(func)` See above +- `multi:connectFinal(func)` See above - `multi:lightloop()` Cleaned up the mainloop/uManager method, actually faster than lightloop (Which should have been called liteloop) - `multi:threadloop()` See above for reasons - `multi setting: protect` This added extra complexity to the mainloop and not much benefit. If you feel a function will error use pcall yourself. This saves a decent amount of cycles, about 6.25% increase in performance. diff --git a/multi/init.lua b/multi/init.lua index a01cd4f..08350fa 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -149,16 +149,16 @@ function multi:getTasksDetails(t) end dat = multi.AlignTable(proc_tab).. "\n" dat = dat .. "\n" .. multi.AlignTable(th_tab) - return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: "..thread_count.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.settings.priority or 0].."\n\n"..dat..dat2.."\n\n"..s + return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: "..thread_count.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat..dat2.."\n\n"..s else - return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\n\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.settings.priority or 0].."\n\n"..dat2.."\n\n"..s + return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\n\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat2.."\n\n"..s end else local load,steps = self:getLoad() str = { ProcessName = (self.Name or "Unnamed"), MemoryUsage = math.ceil(collectgarbage("count")), - PriorityScheme = priorityTable[multi.settings.priority or 0], + PriorityScheme = priorityTable[multi.defaultSettings.priority or 0], SystemLoad = multi.Round(load,2), CyclesPerSecondPerTask = steps, SystemThreadCount = multi.SystemThreads and #multi.SystemThreads or 0 @@ -258,7 +258,7 @@ function multi:newConnection(protect,func,kill) end end) repeat - self.Parent:uManager(multi.settings) + self.Parent:uManager() until self.waiting==false id:Destroy() return self @@ -654,6 +654,7 @@ function multi:newEvent(task) multi:create(c) return c end + function multi:newUpdater(skip) local c=self:newBase() c.Type='updater' @@ -674,6 +675,7 @@ function multi:newUpdater(skip) multi:create(c) return c end + function multi:newAlarm(set) local c=self:newBase() c.Type='alarm' @@ -709,6 +711,7 @@ function multi:newAlarm(set) multi:create(c) return c end + function multi:newLoop(func) local c=self:newBase() c.Type='loop' @@ -786,6 +789,7 @@ function multi:newStep(start,reset,count,skip) multi:create(c) return c end + function multi:newTLoop(func,set) local c=self:newBase() c.Type='tloop' @@ -820,9 +824,11 @@ function multi:newTLoop(func,set) multi:create(c) return c end + function multi:setTimeout(func,t) multi:newThread(function() thread.sleep(t) func() end) end + function multi:newTStep(start,reset,count,set) local c=self:newStep(start,reset,count) c.Type='tstep' @@ -903,6 +909,7 @@ local __CurrentTask function multi.getCurrentProcess() return __CurrentProcess end + function multi.getCurrentTask() return __CurrentTask end @@ -963,9 +970,11 @@ multi.GlobalVariables={} local dFunc = function() return true end local dRef = {nil,nil,nil,nil,nil} thread.requests = {} + function thread.request(t,cmd,...) thread.requests[t.thread] = {cmd,{...}} end + function thread.getRunningThread() local threads = globalThreads local t = coroutine.running() @@ -977,6 +986,7 @@ function thread.getRunningThread() end end end + function thread._Requests() local t = thread.requests[coroutine.running()] if t then @@ -985,6 +995,7 @@ function thread._Requests() thread[cmd](unpack(args)) end end + function thread.sleep(n) thread._Requests() thread.getRunningThread().lastSleep = clock() @@ -1026,6 +1037,7 @@ function thread.hold(n,opt) return coroutine.yield(dRef) end end + function thread.holdFor(sec,n) thread._Requests() dRef[1] = "_holdF_" @@ -1033,6 +1045,7 @@ function thread.holdFor(sec,n) dRef[3] = n or dFunc return coroutine.yield(dRef) end + function thread.holdWithin(skip,n) thread._Requests() dRef[1] = "_holdW_" @@ -1040,21 +1053,25 @@ function thread.holdWithin(skip,n) dRef[3] = n or dFunc return coroutine.yield(dRef) end + function thread.skip(n) thread._Requests() dRef[1] = "_skip_" dRef[2] = n or 1 return coroutine.yield(dRef) end + function thread.kill() dRef[1] = "_kill_" dRef[2] = "T_T" return coroutine.yield(dRef) end + function thread.yield() thread._Requests() return thread.sleep(0) end + function thread.isThread() if _VERSION~="Lua 5.1" then local a,b = coroutine.running() @@ -1063,20 +1080,25 @@ function thread.isThread() return coroutine.running()~=nil end end + function thread.getCores() return thread.__CORES end + function thread.set(name,val) multi.GlobalVariables[name]=val return true end + function thread.get(name) return multi.GlobalVariables[name] end + function thread.waitFor(name) thread.hold(function() return thread.get(name)~=nil end) return thread.get(name) end + function multi.hold(func,opt) if thread.isThread() then if type(func) == "function" or type(func) == "table" then @@ -1209,6 +1231,7 @@ function thread:newFunctionBase(generator,holdme) return tfunc end end + function thread:newFunction(func,holdme) return thread:newFunctionBase(function(...) return multi.getCurrentProcess():newThread("TempThread",func,...) @@ -1225,6 +1248,7 @@ end function multi:attachScheduler() local threads = {} self.threadsRef = threads + function self:newThread(name,func,...) self.OnLoad:Fire() local func = func or name @@ -1248,9 +1272,11 @@ function multi:attachScheduler() c.isError = false c.OnError = self:newConnection(true,nil,true) c.OnDeath = self:newConnection(true,nil,true) + function c:isPaused() return self._isPaused end + local resumed = false function c:Pause() if not self._isPaused then @@ -1265,22 +1291,27 @@ function multi:attachScheduler() end return self end + function c:Resume() resumed = true return self end + function c:Kill() thread.request(self,"kill") return self end c.Destroy = c.Kill c.kill = c.Kill + function c.ref:send(name,val) ret=coroutine.yield({Name=name,Value=val}) end + function c.ref:get(name) return self.Globals[name] end + function c.ref:kill() dRef[1] = "_kill_" dRef[2] = "I Was killed by You!" @@ -1289,6 +1320,7 @@ function multi:attachScheduler() error("Failed to kill a thread! Exiting...") end end + function c.ref:sleep(n) if type(n)=="function" then ret=thread.hold(n) @@ -1298,9 +1330,11 @@ function multi:attachScheduler() error("Invalid Type for sleep!") end end + function c.ref:syncGlobals(v) self.Globals=v end + table.insert(threads,c) globalThreads[c] = self if initT==false then @@ -1311,6 +1345,7 @@ function multi:attachScheduler() multi:create(c) return c end + function self:newISOThread(name,func,_env,...) self.OnLoad:Fire() local func = func or name @@ -1327,6 +1362,7 @@ function multi:attachScheduler() local func = isolateFunction(func,env) return self:newThread(name,func) end + function self.initThreads(justThreads) initT = true self.scheduler=self:newLoop():setName("multi.thread") @@ -1524,6 +1560,7 @@ function multi:attachScheduler() end end end + function multi:newService(func) -- Priority managed threads local c = {} c.Type = "service" @@ -1617,6 +1654,7 @@ function multi:newService(func) -- Priority managed threads multi.create(multi,c) return c end + -- Multi runners local function mainloop(self) __CurrentProcess = self @@ -1640,6 +1678,7 @@ local function mainloop(self) return nil, "Already Running!" end end + multi.mainloop = mainloop local function p_mainloop(self) @@ -1668,6 +1707,7 @@ local function p_mainloop(self) return nil, "Already Running!" end end + local init = false function multi.init(settings, realsettings) if settings == multi then settings = realsettings end @@ -1684,7 +1724,7 @@ function multi.init(settings, realsettings) return _G["$multi"].multi,_G["$multi"].thread end -function multi:uManager(settings) +function multi:uManager() if self.Active then __CurrentProcess = self multi.OnPreLoad:Fire() @@ -1739,9 +1779,11 @@ function table.merge(t1, t2) end return t1 end + if table.unpack and not unpack then unpack=table.unpack end + multi.DestroyedObj = { Type = "destroyed", } @@ -1899,12 +1941,6 @@ function multi:canSystemThread() return false end -function multi:getError() - if self.error then - return self.error - end -end - function multi:benchMark(sec,p,pt) local c = 0 local temp=self:newLoop(function(self,t) @@ -1958,7 +1994,12 @@ function multi.AlignTable(tab) end function multi:endTask(TID) - self.Mainloop[TID]:Destroy() + for i=#self.Mainloop,1,-1 do + if self.Mainloop[i].TID == TID then + self.Mainloop[TID]:Destroy() + return self + end + end return self end @@ -1985,30 +2026,6 @@ function multi.timer(func,...) return t,unpack(args) end -function multi:OnMainConnect(func) - table.insert(self.func,func) - return self -end - -function multi:FreeMainEvent() - self.func={} - return self -end - -function multi:connectFinal(func) - if self.Type=='event' then - self:OnEvent(func) - elseif self.Type=='alarm' then - self:OnRing(func) - elseif self.Type=='step' or self.Type=='tstep' then - self:OnEnd(func) - else - multi.print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(func) to trigger it's final event!") - self:OnBreak(func) - end - return self -end - if os.getOS()=="windows" then thread.__CORES=tonumber(os.getenv("NUMBER_OF_PROCESSORS")) else diff --git a/test3.lua b/test3.lua index ecb040c..c91b67b 100644 --- a/test3.lua +++ b/test3.lua @@ -1,5 +1,5 @@ package.path = "./?.lua;?/init.lua;"..package.path -local multi,thread = require("multi"):init{print=true,priority=true} +local multi,thread = require("multi"):init{print=true} --local GLOBAL,THREAD = require("multi.integration.lanesManager"):init() -- func = THREAD:newFunction(function(a,b,c) @@ -33,18 +33,15 @@ local c = 1 local function bench(t,step) a = a + step c = c + 1 - if c == 5 then - --print("Total: "..a) - os.exit() - end -end--p_c,p_vh,p_h,p_an,p_n,p_bn,p_l,p_vl,p_i -multi:benchMark(sleep_for,multi.Priority_Idle,"Idle:"):OnBench(bench) + os.exit() +end +--multi:benchMark(sleep_for,multi.Priority_Idle,"Idle:"):OnBench(bench) --multi:benchMark(sleep_for,multi.Priority_Very_Low,"Very Low:"):OnBench(bench) -multi:benchMark(sleep_for,multi.Priority_Low,"Low:"):OnBench() +--multi:benchMark(sleep_for,multi.Priority_Low,"Low:"):OnBench() --multi:benchMark(sleep_for,multi.Priority_Below_Normal,"Below Normal:"):OnBench(bench) -multi:benchMark(sleep_for,multi.Priority_Normal,"Normal:"):OnBench(bench) +--multi:benchMark(sleep_for,multi.Priority_Normal,"Normal:"):OnBench(bench) --multi:benchMark(sleep_for,multi.Priority_Above_Normal,"Above Normal:"):OnBench(bench) -multi:benchMark(sleep_for,multi.Priority_High,"High:"):OnBench(bench) +--multi:benchMark(sleep_for,multi.Priority_High,"High:"):OnBench(bench) --multi:benchMark(sleep_for,multi.Priority_Very_High,"Very High:"):OnBench(bench) multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) multi.OnExit(function() diff --git a/tests/runtests.lua b/tests/runtests.lua index b338b81..d650347 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -29,5 +29,5 @@ runTest = thread:newFunction(function() print(multi:getTasksDetails()) os.exit() end) -runTest() +runTest().OnError(print) multi:mainloop() \ No newline at end of file From 47178dd3b385c162490171529a7776f8e9b48451 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 24 Jan 2022 09:00:51 -0500 Subject: [PATCH 28/80] Started to work on the scheduler rework --- .gitignore | 3 - multi/init.lua | 549 +++++++++++++++++++++++++-------------------- test4.lua | 30 +++ tests/runtests.lua | 6 +- 4 files changed, 339 insertions(+), 249 deletions(-) create mode 100644 test4.lua diff --git a/.gitignore b/.gitignore index 73824b6..c9636a2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,5 @@ *lua5.3 *lua5.4 *luajit -test2.lua -test.lua -test3.lua *.code-workspace *.dat \ No newline at end of file diff --git a/multi/init.lua b/multi/init.lua index 08350fa..9b07bff 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -32,23 +32,14 @@ if not _G["$multi"] then _G["$multi"] = {multi=multi,thread=thread} end -multi.Version = "15.1.0" -multi.stage = "stable" +multi.Version = "15.2.0" multi.Name = "multi.root" multi.NIL = {Type="NIL"} multi.Mainloop = {} -multi.Garbage = {} -multi.ender = {} multi.Children = {} multi.Active = true multi.Type = "rootprocess" -multi.Rest = 0 -multi._type = type -multi.queue = {} -multi.clock = os.clock -multi.time = os.time multi.LinkedPath = multi -multi.lastTime = clock() multi.TIMEOUT = "TIMEOUT" multi.TID = 0 multi.defaultSettings = {} @@ -83,13 +74,15 @@ multi.threstimed=.001 -- System function multi.Stop() - mainloopActive=false + isRunning = false + mainloopActive = false end --Processor local priorityTable = {[0]="Round-Robin",[1]="Balanced",[2]="Top-Down",[3]="Timed-Based-Balancer"} local ProcessName = {"SubProcessor","MainProcessor"} local globalThreads = {} + function multi:getTasksDetails(t) if not(t) then str = { @@ -562,9 +555,9 @@ end --Constructors [CORE] local _tid = 0 function multi:newBase(ins) - if not(self.Type=='rootprocess' or self.Type=='process' or self.Type=='queue' or self.Type == 'sandbox') then error('Can only create an object on multi or an interface obj') return false end + if not(self.Type=='rootprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end local c = {} - if self.Type=='process' or self.Type=='queue' or self.Type=='sandbox' then + if self.Type=='process' then setmetatable(c, {__index = multi}) else setmetatable(c, {__index = multi}) @@ -876,6 +869,7 @@ end local scheduledjobs = {} local sthread + function multi:scheduleJob(time,func) if not sthread then sthread = multi:newThread("JobScheduler",function() @@ -906,6 +900,7 @@ end local __CurrentProcess = multi local __CurrentTask + function multi.getCurrentProcess() return __CurrentProcess end @@ -959,146 +954,6 @@ function multi:newProcessor(name,nothread) return c end --- Threading stuff -local initT = false -local threadCount = 0 -local threadid = 0 -thread.__threads = {} -local threads = thread.__threads -local Gref = _G -multi.GlobalVariables={} -local dFunc = function() return true end -local dRef = {nil,nil,nil,nil,nil} -thread.requests = {} - -function thread.request(t,cmd,...) - thread.requests[t.thread] = {cmd,{...}} -end - -function thread.getRunningThread() - local threads = globalThreads - local t = coroutine.running() - if t then - for th,process in pairs(threads) do - if t==th.thread then - return th - end - end - end -end - -function thread._Requests() - local t = thread.requests[coroutine.running()] - if t then - thread.requests[coroutine.running()] = nil - local cmd,args = t[1],t[2] - thread[cmd](unpack(args)) - end -end - -function thread.sleep(n) - thread._Requests() - thread.getRunningThread().lastSleep = clock() - dRef[1] = "_sleep_" - dRef[2] = n or 0 - return coroutine.yield(dRef) -end - -function thread.hold(n,opt) - thread._Requests() - if opt and type(opt)=="table" then - if opt.interval then - dRef[4] = opt.interval - end - if opt.cycles then - dRef[1] = "_holdW_" - dRef[2] = opt.cycles or 1 - dRef[3] = n or dFunc - return coroutine.yield(dRef) - elseif opt.sleep then - dRef[1] = "_holdF_" - dRef[2] = opt.sleep - dRef[3] = n or dFunc - return coroutine.yield(dRef) - elseif opt.skip then - dRef[1] = "_skip_" - dRef[2] = opt.skip or 1 - return coroutine.yield(dRef) - end - end - if type(n) == "number" then - thread.getRunningThread().lastSleep = clock() - dRef[1] = "_sleep_" - dRef[2] = n or 0 - return coroutine.yield(dRef) - else - dRef[1] = "_hold_" - dRef[2] = n or dFunc - return coroutine.yield(dRef) - end -end - -function thread.holdFor(sec,n) - thread._Requests() - dRef[1] = "_holdF_" - dRef[2] = sec - dRef[3] = n or dFunc - return coroutine.yield(dRef) -end - -function thread.holdWithin(skip,n) - thread._Requests() - dRef[1] = "_holdW_" - dRef[2] = skip or 1 - dRef[3] = n or dFunc - return coroutine.yield(dRef) -end - -function thread.skip(n) - thread._Requests() - dRef[1] = "_skip_" - dRef[2] = n or 1 - return coroutine.yield(dRef) -end - -function thread.kill() - dRef[1] = "_kill_" - dRef[2] = "T_T" - return coroutine.yield(dRef) -end - -function thread.yield() - thread._Requests() - return thread.sleep(0) -end - -function thread.isThread() - if _VERSION~="Lua 5.1" then - local a,b = coroutine.running() - return not(b) - else - return coroutine.running()~=nil - end -end - -function thread.getCores() - return thread.__CORES -end - -function thread.set(name,val) - multi.GlobalVariables[name]=val - return true -end - -function thread.get(name) - return multi.GlobalVariables[name] -end - -function thread.waitFor(name) - thread.hold(function() return thread.get(name)~=nil end) - return thread.get(name) -end - function multi.hold(func,opt) if thread.isThread() then if type(func) == "function" or type(func) == "table" then @@ -1132,6 +987,122 @@ function multi.hold(func,opt) end end +-- Threading stuff +local initT = false +local threadCount = 0 +local threadid = 0 +thread.__threads = {} +local threads = thread.__threads +multi.GlobalVariables={} +local dFunc = function() return true end +thread.requests = {} + +function thread.request(t,cmd,...) + thread.requests[t.thread] = {cmd,{...}} +end + +function thread.getRunningThread() + local threads = globalThreads + local t = coroutine.running() + if t then + for th,process in pairs(threads) do + if t==th.thread then + return th + end + end + end +end + +function thread._Requests() + local t = thread.requests[coroutine.running()] + if t then + thread.requests[coroutine.running()] = nil + local cmd,args = t[1],t[2] + thread[cmd](unpack(args)) + end +end + +function thread.sleep(n) + thread._Requests() + thread.getRunningThread().lastSleep = clock() + return coroutine.yield(CMD, t_sleep, n or 1) +end + +local CMD = {} -- We will compare this special local +local interval +local t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none = 1, 2, 3, 4, 5, 6, 7 + +function thread.hold(n,opt) + thread._Requests() + if type(opt)=="table" then + interval = opt.interval + if opt.cycles then + return coroutine.yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) + elseif opt.sleep then + return coroutine.yield(CMD, t_holdF, opt.sleep, n or dFunc, interval) + elseif opt.skip then + return coroutine.yield(CMD, t_skip, opt.skip or 1, nil, interval) + end + end + if type(n) == "number" then + thread.getRunningThread().lastSleep = clock() + return coroutine.yield(CMD, t_sleep, n or 0) + else + return coroutine.yield(CMD, t_hold, n or dFunc) + end +end + +function thread.holdFor(sec,n) + thread._Requests() + return coroutine.yield(CMD, t_holdF, sec, n or dFunc) +end + +function thread.holdWithin(skip,n) + thread._Requests() + return coroutine.yield(CMD, t_holdW, skip or 1, n or dFunc) +end + +function thread.skip(n) + thread._Requests() + return coroutine.yield(CMD, t_skip, n or 1) +end + +function thread.kill() + error("thread killed!") +end + +function thread.yield() + thread._Requests() + return coroutine.yield(CMD, t_yield) +end + +function thread.isThread() + if _VERSION~="Lua 5.1" then + local a,b = coroutine.running() + return not(b) + else + return coroutine.running()~=nil + end +end + +function thread.getCores() + return thread.__CORES +end + +function thread.set(name,val) + multi.GlobalVariables[name]=val + return true +end + +function thread.get(name) + return multi.GlobalVariables[name] +end + +function thread.waitFor(name) + thread.hold(function() return thread.get(name)~=nil end) + return thread.get(name) +end + local function cleanReturns(...) local returns = {...} local rets = {} @@ -1250,7 +1221,7 @@ function multi:attachScheduler() self.threadsRef = threads function self:newThread(name,func,...) - self.OnLoad:Fire() + self.OnLoad:Fire() -- This was made incase a threaded function was called before mainloop/uManager was called local func = func or name if type(name) == "function" then name = "Thread#"..threadCount @@ -1301,48 +1272,33 @@ function multi:attachScheduler() thread.request(self,"kill") return self end + + function c:Sleep(n) + thread.request(self,"exec",function() + thread.sleep(n) + resumed = false + end) + return self + end + + function c:Hold(n,opt) + thread.request(self,"exec",function() + thread.hold(n,opt) + resumed = false + end) + return self + end + c.Destroy = c.Kill - c.kill = c.Kill - - function c.ref:send(name,val) - ret=coroutine.yield({Name=name,Value=val}) - end - - function c.ref:get(name) - return self.Globals[name] - end - - function c.ref:kill() - dRef[1] = "_kill_" - dRef[2] = "I Was killed by You!" - err = coroutine.yield(dRef) - if err then - error("Failed to kill a thread! Exiting...") - end - end - - function c.ref:sleep(n) - if type(n)=="function" then - ret=thread.hold(n) - elseif type(n)=="number" then - ret=thread.sleep(tonumber(n) or 0) - else - error("Invalid Type for sleep!") - end - end - - function c.ref:syncGlobals(v) - self.Globals=v - end table.insert(threads,c) globalThreads[c] = self if initT==false then self.initThreads() end - c.creationTime = os.clock() threadid = threadid + 1 multi:create(c) + c.creationTime = os.clock() return c end @@ -1372,7 +1328,7 @@ function multi:attachScheduler() end self.scheduler.skip=0 local t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 - local r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 + local r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 local ret,_ local function CheckRets(i) if threads[i] and not(threads[i].isError) then @@ -1413,7 +1369,7 @@ function multi:attachScheduler() end end end - local function helper(i) + local function helper(CMD,arg1,arg2,arg3,arg4) if type(ret)=="table" then if ret[1]=="_kill_" then threads[i].OnDeath:Fire(threads[i],"killed",ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) @@ -1465,8 +1421,160 @@ function multi:attachScheduler() end CheckRets(i) end + local task, thd, ref + --[[ + if coroutine.running() ~= threads[i].thread then + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=coroutine.resume(threads[i].thread,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + CheckRets(i) + end + ]] + -- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order + local switch = { + function(th,co,ind,arg1,arg2,arg3,arg4)--hold + if clock() - th.intervalR>=th.interval then + t0,t1,t2,t3,t4,t5,t6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = th.func() + if t0 then + if t0==self.NIL then t0 = nil end + th.task = t_none + th.__ready = true + end + th.intervalR = clock() + end + end, + function(th,co,ind,arg1,arg2,arg3,arg4)--sleep + if clock() - th.time>=th.sec then + th.task = t_none + th.__ready = true + end + end, + function(th,co,ind,arg1,arg2,arg3,arg4)--holdf + if clock() - th.intervalR>=th.interval then + t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() + if t0 then + if t0 then + if t0==self.NIL then t0 = nil end + th.task = t_none + th.__ready = true + end + th.task = t_none + th.__ready = true + elseif clock() - th.time>=th.sec then + th.task = t_none + th.__ready = true + t0 = nil + t1 = multi.TIMEOUT + end + th.intervalR = clock() + end + end, + function(th,co,ind,arg1,arg2,arg3,arg4)--skip + th.pos = th.pos + 1 + if th.count==th.pos then + th.task = t_none + th.__ready = true + end + end, + function(th,co,ind,arg1,arg2,arg3,arg4)--holdw + if clock() - th.intervalR>=th.interval then + th.pos = th.pos + 1 + t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() + if t0 then + if t0 then + if t0==self.NIL then t0 = nil end + th.task = t_none + th.__ready = true + end + th.task = "" + th.__ready = true + elseif th.count==th.pos then + th.task = "" + th.__ready = true + t0 = nil + t1 = multi.TIMEOUT + end + th.intervalR = clock() + end + end, + function(th,co,ind,arg1,arg2,arg3,arg4)--yield + -- + end, + function() end--none + } + setmetatable(switch,{__index=function() return function() end end}) + local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order + function(th,arg1,arg2,arg3,arg4) + threads[i].OnDeath:Fire(threads[i],"killed",ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) + self.setType(threads[i],self.DestroyedObj) + table.remove(threads,i) + ret = nil + end, + function(th,arg1,arg2,arg3,arg4) + threads[i].sec = ret[2] + threads[i].time = clock() + threads[i].task = "sleep" + threads[i].__ready = false + ret = nil + end, + function(th,arg1,arg2,arg3,arg4) + threads[i].count = ret[2] + threads[i].pos = 0 + threads[i].task = "skip" + threads[i].__ready = false + ret = nil + end, + function(th,arg1,arg2,arg3,arg4) + holdconn(2) + threads[i].func = ret[2] + threads[i].task = "hold" + threads[i].__ready = false + threads[i].interval = ret[4] or 0 + threads[i].intervalR = clock() + ret = nil + end, + function(th,arg1,arg2,arg3,arg4) + holdconn(3) + threads[i].sec = ret[2] + threads[i].func = ret[3] + threads[i].task = "holdF" + threads[i].time = clock() + threads[i].__ready = false + threads[i].interval = ret[4] or 0 + threads[i].intervalR = clock() + ret = nil + end, + function(th,arg1,arg2,arg3,arg4) + holdconn(3) + threads[i].count = ret[2] + threads[i].pos = 0 + threads[i].func = ret[3] + threads[i].task = "holdW" + threads[i].time = clock() + threads[i].__ready = false + threads[i].interval = ret[4] or 0 + threads[i].intervalR = clock() + ret = nil + end, + function() end + } + local status = { + ["suspended"] = function(thd,ref) + if not ref.started then + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=coroutine.resume(thd,unpack(ref.startArgs)) + cmds[_](ret,r1,r2,r3) + return + end + end, + ["normal"] = true, + ["running"] = true, + ["dead"] = true + } + self.scheduler:OnLoop(function(self) for i=#threads,1,-1 do + ref = threads[i] + task = ref.task + thd = ref.thread + if threads[i].isError then if coroutine.status(threads[i].thread)=="dead" then threads[i].OnError:Fire(threads[i],unpack(threads[i].TempRets)) @@ -1474,74 +1582,27 @@ function multi:attachScheduler() table.remove(threads,i) end end - if threads[i] and not threads[i].__started then - if coroutine.running() ~= threads[i].thread then - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=coroutine.resume(threads[i].thread,unpack(threads[i].startArgs)) - end - threads[i].__started = true - helper(i) - end + status[coroutine.status(thd)](thd,ref) if threads[i] and not _ then threads[i].OnError:Fire(threads[i],unpack(threads[i].TempRets)) threads[i].isError = true end + switch(task)(ref,thd,i) if threads[i] and coroutine.status(threads[i].thread)=="dead" then local t = threads[i].TempRets or {} threads[i].OnDeath:Fire(threads[i],"ended",t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8],t[9],t[10],t[11],t[12],t[13],t[14],t[15],t[16]) self.setType(threads[i],self.DestroyedObj) table.remove(threads,i) elseif threads[i] and threads[i].task == "skip" then - threads[i].pos = threads[i].pos + 1 - if threads[i].count==threads[i].pos then - threads[i].task = "" - threads[i].__ready = true - end + elseif threads[i] and threads[i].task == "hold" then - if clock() - threads[i].intervalR>=threads[i].interval then - t0,t1,t2,t3,t4,t5,t6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = threads[i].func() - if t0 then - if t0==self.NIL then - t0 = nil - end - threads[i].task = "" - threads[i].__ready = true - end - threads[i].intervalR = clock() - end + elseif threads[i] and threads[i].task == "sleep" then - if clock() - threads[i].time>=threads[i].sec then - threads[i].task = "" - threads[i].__ready = true - end + elseif threads[i] and threads[i].task == "holdF" then - if clock() - threads[i].intervalR>=threads[i].interval then - t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = threads[i].func() - if t0 then - threads[i].task = "" - threads[i].__ready = true - elseif clock() - threads[i].time>=threads[i].sec then - threads[i].task = "" - threads[i].__ready = true - t0 = nil - t1 = multi.TIMEOUT - end - threads[i].intervalR = clock() - end + elseif threads[i] and threads[i].task == "holdW" then - if clock() - threads[i].intervalR>=threads[i].interval then - threads[i].pos = threads[i].pos + 1 - t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = threads[i].func() - if t0 then - threads[i].task = "" - threads[i].__ready = true - elseif threads[i].count==threads[i].pos then - threads[i].task = "" - threads[i].__ready = true - t0 = nil - t1 = multi.TIMEOUT - end - threads[i].intervalR = clock() - end + end if threads[i] and threads[i].__ready then threads[i].__ready = false @@ -1661,7 +1722,7 @@ local function mainloop(self) multi.OnPreLoad:Fire() self.uManager = self.uManagerRef if not isRunning then - isRunning=true + isRunning = true mainloopActive = true local Loop=self.Mainloop local ctask diff --git a/test4.lua b/test4.lua new file mode 100644 index 0000000..8ebd3be --- /dev/null +++ b/test4.lua @@ -0,0 +1,30 @@ +package.path = "./?.lua;?/init.lua;"..package.path +local multi,thread = require("multi"):init() +--[[ Testing... +Before AVG: 522386 +Test 1 AVG: +]] +local sleep_for = 1 + +local function bench(_,steps) + print("Steps/5s: "..steps) + os.exit() +end +multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) +multi:newThread(function() + while true do + thread.sleep(1) -- We just need to run things + end +end) + +multi:newThread(function() + while true do + thread.sleep(1) -- We just need to run things + end +end) + +-- multi.OnExit(function() +-- print("Total: ".. a) +-- end) + +multi:mainloop() \ No newline at end of file diff --git a/tests/runtests.lua b/tests/runtests.lua index d650347..52f27d6 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -15,7 +15,7 @@ package.path="./?.lua;../?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package The expected and actual should "match" (Might be impossible when playing with threads) This will be pushed directly to the master as tests start existing. ]] -local multi, thread = require("multi"):init() +local multi, thread = require("multi"):init{priority=true} local good = false runTest = thread:newFunction(function() local objects = multi:newProcessor("Basic Object Tests") @@ -29,5 +29,7 @@ runTest = thread:newFunction(function() print(multi:getTasksDetails()) os.exit() end) -runTest().OnError(print) +runTest().OnError(function(...) + print("Error:",...) +end) multi:mainloop() \ No newline at end of file From 472d1748ee6bf858d6eb27aff48357a8a7e256c7 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 24 Jan 2022 14:25:23 -0500 Subject: [PATCH 29/80] Working on thread scheduler rework --- multi/init.lua | 141 +++++++++++++++++++++++-------------------------- test4.lua | 16 ++++-- 2 files changed, 76 insertions(+), 81 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 9b07bff..f4c476c 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -996,6 +996,9 @@ local threads = thread.__threads multi.GlobalVariables={} local dFunc = function() return true end thread.requests = {} +local CMD = {} -- We will compare this special local +local interval +local t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none = 1, 2, 3, 4, 5, 6, 7 function thread.request(t,cmd,...) thread.requests[t.thread] = {cmd,{...}} @@ -1028,10 +1031,6 @@ function thread.sleep(n) return coroutine.yield(CMD, t_sleep, n or 1) end -local CMD = {} -- We will compare this special local -local interval -local t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none = 1, 2, 3, 4, 5, 6, 7 - function thread.hold(n,opt) thread._Requests() if type(opt)=="table" then @@ -1215,13 +1214,14 @@ function multi.setEnv(func,env) local chunk = load(f,"env","bt",env) return chunk end - +local resume, status, create = coroutine.resume, coroutine.status, coroutine.create function multi:attachScheduler() local threads = {} self.threadsRef = threads - + local startme = {} + local startme_len = 0 function self:newThread(name,func,...) - self.OnLoad:Fire() -- This was made incase a threaded function was called before mainloop/uManager was called + self.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 @@ -1232,7 +1232,7 @@ function multi:attachScheduler() c.startArgs = {...} c.ref={} c.Name=name - c.thread=coroutine.create(func) + c.thread=create(func) c.sleep=1 c.Type="thread" c.TID = threadid @@ -1292,6 +1292,8 @@ function multi:attachScheduler() c.Destroy = c.Kill table.insert(threads,c) + table.insert(startme,c) + startme_len = #startme globalThreads[c] = self if initT==false then self.initThreads() @@ -1421,60 +1423,59 @@ function multi:attachScheduler() end CheckRets(i) end - local task, thd, ref + local task, thd, ref, ready --[[ if coroutine.running() ~= threads[i].thread then - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=coroutine.resume(threads[i].thread,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(coreads[i].thread,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) CheckRets(i) end ]] -- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order local switch = { - function(th,co,ind,arg1,arg2,arg3,arg4)--hold + function(th,co,arg1,arg2,arg3,arg4)--hold if clock() - th.intervalR>=th.interval then t0,t1,t2,t3,t4,t5,t6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = th.func() if t0 then if t0==self.NIL then t0 = nil end th.task = t_none - th.__ready = true + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) end th.intervalR = clock() end end, - function(th,co,ind,arg1,arg2,arg3,arg4)--sleep + function(th,co,arg1,arg2,arg3,arg4)--sleep if clock() - th.time>=th.sec then th.task = t_none - th.__ready = true + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) end end, - function(th,co,ind,arg1,arg2,arg3,arg4)--holdf + function(th,co,arg1,arg2,arg3,arg4)--holdf if clock() - th.intervalR>=th.interval then t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() if t0 then if t0 then if t0==self.NIL then t0 = nil end th.task = t_none - th.__ready = true end th.task = t_none - th.__ready = true + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) elseif clock() - th.time>=th.sec then th.task = t_none - th.__ready = true t0 = nil t1 = multi.TIMEOUT + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) end th.intervalR = clock() end end, - function(th,co,ind,arg1,arg2,arg3,arg4)--skip + function(th,co,arg1,arg2,arg3,arg4)--skip th.pos = th.pos + 1 if th.count==th.pos then th.task = t_none - th.__ready = true + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) end end, - function(th,co,ind,arg1,arg2,arg3,arg4)--holdw + function(th,co,arg1,arg2,arg3,arg4)--holdw if clock() - th.intervalR>=th.interval then th.pos = th.pos + 1 t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() @@ -1482,21 +1483,20 @@ function multi:attachScheduler() if t0 then if t0==self.NIL then t0 = nil end th.task = t_none - th.__ready = true end th.task = "" - th.__ready = true + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) elseif th.count==th.pos then - th.task = "" - th.__ready = true + th.task = t_none t0 = nil t1 = multi.TIMEOUT + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) end th.intervalR = clock() end end, - function(th,co,ind,arg1,arg2,arg3,arg4)--yield - -- + function(th,co,arg1,arg2,arg3,arg4)--yield + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) end, function() end--none } @@ -1509,23 +1509,23 @@ function multi:attachScheduler() ret = nil end, function(th,arg1,arg2,arg3,arg4) - threads[i].sec = ret[2] - threads[i].time = clock() - threads[i].task = "sleep" - threads[i].__ready = false + print("SLEEP:",th,arg1,arg2,arg3,arg4) + th.sec = arg1 + th.time = clock() + th.task = t_sleep ret = nil end, function(th,arg1,arg2,arg3,arg4) threads[i].count = ret[2] threads[i].pos = 0 - threads[i].task = "skip" + threads[i].task = t_skip threads[i].__ready = false ret = nil end, function(th,arg1,arg2,arg3,arg4) holdconn(2) threads[i].func = ret[2] - threads[i].task = "hold" + threads[i].task = t_hold threads[i].__ready = false threads[i].interval = ret[4] or 0 threads[i].intervalR = clock() @@ -1535,7 +1535,7 @@ function multi:attachScheduler() holdconn(3) threads[i].sec = ret[2] threads[i].func = ret[3] - threads[i].task = "holdF" + threads[i].task = t_holdF threads[i].time = clock() threads[i].__ready = false threads[i].interval = ret[4] or 0 @@ -1547,7 +1547,7 @@ function multi:attachScheduler() threads[i].count = ret[2] threads[i].pos = 0 threads[i].func = ret[3] - threads[i].task = "holdW" + threads[i].task = t_holdW threads[i].time = clock() threads[i].__ready = false threads[i].interval = ret[4] or 0 @@ -1556,58 +1556,47 @@ function multi:attachScheduler() end, function() end } - local status = { - ["suspended"] = function(thd,ref) - if not ref.started then - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=coroutine.resume(thd,unpack(ref.startArgs)) - cmds[_](ret,r1,r2,r3) - return - end + setmetatable(cmds,{__index=function() return function() end end}) + local co_status = { + ["suspended"] = function(thd,ref,task) + print(ref,r2,r3,r4,r5) + switch[task](ref,thd) + --cmds[r1](ref,r2,r3,r4,r5) + end, + ["normal"] = function(thd,ref) end, -- Not sure if I will handle this + ["running"] = function(thd,ref) end, + ["dead"] = function(thd,ref) + local t = ref.TempRets or {} + ref.OnDeath:Fire(ref,"ended",t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8],t[9],t[10],t[11],t[12],t[13],t[14],t[15],t[16]) + self.setType(ref,self.DestroyedObj) + table.remove(threads,i) end, - ["normal"] = true, - ["running"] = true, - ["dead"] = true } - self.scheduler:OnLoop(function(self) for i=#threads,1,-1 do ref = threads[i] task = ref.task thd = ref.thread - - if threads[i].isError then - if coroutine.status(threads[i].thread)=="dead" then - threads[i].OnError:Fire(threads[i],unpack(threads[i].TempRets)) - self.setType(threads[i],self.DestroyedObj) - table.remove(threads,i) - end - end - status[coroutine.status(thd)](thd,ref) - if threads[i] and not _ then - threads[i].OnError:Fire(threads[i],unpack(threads[i].TempRets)) - threads[i].isError = true - end - switch(task)(ref,thd,i) - if threads[i] and coroutine.status(threads[i].thread)=="dead" then - local t = threads[i].TempRets or {} - threads[i].OnDeath:Fire(threads[i],"ended",t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8],t[9],t[10],t[11],t[12],t[13],t[14],t[15],t[16]) - self.setType(threads[i],self.DestroyedObj) - table.remove(threads,i) - elseif threads[i] and threads[i].task == "skip" then - - elseif threads[i] and threads[i].task == "hold" then - - elseif threads[i] and threads[i].task == "sleep" then - - elseif threads[i] and threads[i].task == "holdF" then - - elseif threads[i] and threads[i].task == "holdW" then - + ready = ref.__ready + + for start = 1, startme_len do + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(startme[start].thread,unpack(startme[start].startArgs)) + cmds[r1](ref,r2,r3,r4,r5) + startme_len = startme_len - 1 end + + -- if threads[i].isError then + -- if status(threads[i].thread)=="dead" then + -- threads[i].OnError:Fire(threads[i],unpack(threads[i].TempRets)) + -- self.setType(threads[i],self.DestroyedObj) + -- table.remove(threads,i) + -- end + -- end + co_status[status(thd)](thd,ref,task) if threads[i] and threads[i].__ready then threads[i].__ready = false if coroutine.running() ~= threads[i].thread then - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=coroutine.resume(threads[i].thread,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + CheckRets(i) end end diff --git a/test4.lua b/test4.lua index 8ebd3be..5fbcd8f 100644 --- a/test4.lua +++ b/test4.lua @@ -4,24 +4,30 @@ local multi,thread = require("multi"):init() Before AVG: 522386 Test 1 AVG: ]] -local sleep_for = 1 +local sleep_for = 5 local function bench(_,steps) print("Steps/5s: "..steps) os.exit() end multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) -multi:newThread(function() +multi:newThread("Thread 1",function(a,b,c) + print(a,b,c) while true do + print(1) thread.sleep(1) -- We just need to run things + print("1 ...") end -end) +end,1,2,3) -multi:newThread(function() +multi:newThread("Thread 2",function(a,b,c) + print(a,b,c) while true do + print(2) thread.sleep(1) -- We just need to run things + print("2 ...") end -end) +end,4,5,6) -- multi.OnExit(function() -- print("Total: ".. a) From 2b122f5c77f74f67062eae0d1bda661dcfcd6410 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 26 Jan 2022 00:03:32 -0500 Subject: [PATCH 30/80] scheduler is much faster, missing connection holding and error handling --- multi/init.lua | 186 ++++++++++++------------------------------------- test4.lua | 30 ++++---- 2 files changed, 60 insertions(+), 156 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index f4c476c..47f65f8 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1033,8 +1033,8 @@ end function thread.hold(n,opt) thread._Requests() + interval = opt.interval if type(opt)=="table" then - interval = opt.interval if opt.cycles then return coroutine.yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) elseif opt.sleep then @@ -1045,9 +1045,9 @@ function thread.hold(n,opt) end if type(n) == "number" then thread.getRunningThread().lastSleep = clock() - return coroutine.yield(CMD, t_sleep, n or 0) + return coroutine.yield(CMD, t_sleep, n or 0, nil, interval) else - return coroutine.yield(CMD, t_hold, n or dFunc) + return coroutine.yield(CMD, t_hold, n or dFunc, nil, interval) end end @@ -1226,7 +1226,7 @@ function multi:attachScheduler() if type(name) == "function" then name = "Thread#"..threadCount end - local c={} + local c={nil,nil,nil,nil,nil,nil,nil} local env = {self=c} c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} c.startArgs = {...} @@ -1332,34 +1332,6 @@ function multi:attachScheduler() local t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 local r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 local ret,_ - local function CheckRets(i) - if threads[i] and not(threads[i].isError) then - if not _ then - threads[i].isError = true - threads[i].TempRets[1] = ret - return - end - if ret or r1 or r2 or r3 or r4 or r5 or r6 or r7 or r8 or r9 or r10 or r11 or r12 or r13 or r14 or r15 or r16 then - threads[i].TempRets[1] = ret - threads[i].TempRets[2] = r1 - threads[i].TempRets[3] = r2 - threads[i].TempRets[4] = r3 - threads[i].TempRets[5] = r4 - threads[i].TempRets[6] = r5 - threads[i].TempRets[7] = r6 - threads[i].TempRets[8] = r7 - threads[i].TempRets[9] = r8 - threads[i].TempRets[10] = r9 - threads[i].TempRets[11] = r10 - threads[i].TempRets[12] = r11 - threads[i].TempRets[13] = r12 - threads[i].TempRets[14] = r13 - threads[i].TempRets[15] = r14 - threads[i].TempRets[16] = r15 - threads[i].TempRets[17] = r16 - end - end - end local function holdconn(n) if type(ret[n])=="table" and ret[n].Type=='connector' then local letsgo @@ -1371,58 +1343,6 @@ function multi:attachScheduler() end end end - local function helper(CMD,arg1,arg2,arg3,arg4) - if type(ret)=="table" then - if ret[1]=="_kill_" then - threads[i].OnDeath:Fire(threads[i],"killed",ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) - self.setType(threads[i],self.DestroyedObj) - table.remove(threads,i) - ret = nil - elseif ret[1]=="_sleep_" then - threads[i].sec = ret[2] - threads[i].time = clock() - threads[i].task = "sleep" - threads[i].__ready = false - ret = nil - elseif ret[1]=="_skip_" then - threads[i].count = ret[2] - threads[i].pos = 0 - threads[i].task = "skip" - threads[i].__ready = false - ret = nil - elseif ret[1]=="_hold_" then - holdconn(2) - threads[i].func = ret[2] - threads[i].task = "hold" - threads[i].__ready = false - threads[i].interval = ret[4] or 0 - threads[i].intervalR = clock() - ret = nil - elseif ret[1]=="_holdF_" then - holdconn(3) - threads[i].sec = ret[2] - threads[i].func = ret[3] - threads[i].task = "holdF" - threads[i].time = clock() - threads[i].__ready = false - threads[i].interval = ret[4] or 0 - threads[i].intervalR = clock() - ret = nil - elseif ret[1]=="_holdW_" then - holdconn(3) - threads[i].count = ret[2] - threads[i].pos = 0 - threads[i].func = ret[3] - threads[i].task = "holdW" - threads[i].time = clock() - threads[i].__ready = false - threads[i].interval = ret[4] or 0 - threads[i].intervalR = clock() - ret = nil - end - end - CheckRets(i) - end local task, thd, ref, ready --[[ if coroutine.running() ~= threads[i].thread then @@ -1502,74 +1422,67 @@ function multi:attachScheduler() } setmetatable(switch,{__index=function() return function() end end}) local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order + -- function(th,arg1,arg2,arg3,arg4) + -- threads[i].OnDeath:Fire(threads[i],"killed",ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) + -- self.setType(threads[i],self.DestroyedObj) + -- table.remove(threads,i) + -- ret = nil + -- end, function(th,arg1,arg2,arg3,arg4) - threads[i].OnDeath:Fire(threads[i],"killed",ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) - self.setType(threads[i],self.DestroyedObj) - table.remove(threads,i) - ret = nil + th.func = arg1 + th.task = t_hold + th.interval = arg4 or 0 + th.intervalR = clock() end, function(th,arg1,arg2,arg3,arg4) - print("SLEEP:",th,arg1,arg2,arg3,arg4) th.sec = arg1 th.time = clock() th.task = t_sleep - ret = nil end, function(th,arg1,arg2,arg3,arg4) - threads[i].count = ret[2] - threads[i].pos = 0 - threads[i].task = t_skip - threads[i].__ready = false - ret = nil + th.sec = arg1 + th.func = arg2 + th.task = t_holdF + th.time = clock() + th.interval = arg4 or 0 + th.intervalR = clock() end, function(th,arg1,arg2,arg3,arg4) - holdconn(2) - threads[i].func = ret[2] - threads[i].task = t_hold - threads[i].__ready = false - threads[i].interval = ret[4] or 0 - threads[i].intervalR = clock() - ret = nil + th.count = arg1 + th.pos = 0 + th.task = t_skip end, function(th,arg1,arg2,arg3,arg4) - holdconn(3) - threads[i].sec = ret[2] - threads[i].func = ret[3] - threads[i].task = t_holdF - threads[i].time = clock() - threads[i].__ready = false - threads[i].interval = ret[4] or 0 - threads[i].intervalR = clock() - ret = nil - end, - function(th,arg1,arg2,arg3,arg4) - holdconn(3) - threads[i].count = ret[2] - threads[i].pos = 0 - threads[i].func = ret[3] - threads[i].task = t_holdW - threads[i].time = clock() - threads[i].__ready = false - threads[i].interval = ret[4] or 0 - threads[i].intervalR = clock() - ret = nil + th.count = arg1 + th.pos = 0 + th.func = arg2 + th.task = t_holdW + th.time = clock() + th.interval = arg4 or 0 + th.intervalR = clock() end, function() end } setmetatable(cmds,{__index=function() return function() end end}) local co_status = { ["suspended"] = function(thd,ref,task) - print(ref,r2,r3,r4,r5) switch[task](ref,thd) - --cmds[r1](ref,r2,r3,r4,r5) + cmds[r1](ref,r2,r3,r4,r5) + if not _ then + print(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) + io.read() -- This is an error spot too + end + r1=nil r2=nil r3=nil r4=nil r5=nil end, ["normal"] = function(thd,ref) end, -- Not sure if I will handle this ["running"] = function(thd,ref) end, - ["dead"] = function(thd,ref) + ["dead"] = function(thd,ref,task,i) local t = ref.TempRets or {} + print(_,ref.Name,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) + print("ended",t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8],t[9],t[10],t[11],t[12],t[13],t[14],t[15],t[16]) ref.OnDeath:Fire(ref,"ended",t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8],t[9],t[10],t[11],t[12],t[13],t[14],t[15],t[16]) - self.setType(ref,self.DestroyedObj) table.remove(threads,i) + self.setType(ref,self.DestroyedObj) end, } self.scheduler:OnLoop(function(self) @@ -1581,10 +1494,10 @@ function multi:attachScheduler() for start = 1, startme_len do _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(startme[start].thread,unpack(startme[start].startArgs)) - cmds[r1](ref,r2,r3,r4,r5) + -- An error can happen here + cmds[r1](startme[start],r2,r3,r4,r5) startme_len = startme_len - 1 end - -- if threads[i].isError then -- if status(threads[i].thread)=="dead" then -- threads[i].OnError:Fire(threads[i],unpack(threads[i].TempRets)) @@ -1592,22 +1505,9 @@ function multi:attachScheduler() -- table.remove(threads,i) -- end -- end - co_status[status(thd)](thd,ref,task) - if threads[i] and threads[i].__ready then - threads[i].__ready = false - if coroutine.running() ~= threads[i].thread then - - CheckRets(i) - end - end - helper(i) + co_status[status(thd)](thd,ref,task,i) end end) - if justThreads then - while true do - self.scheduler:Act() - end - end end end diff --git a/test4.lua b/test4.lua index 5fbcd8f..73003d1 100644 --- a/test4.lua +++ b/test4.lua @@ -4,30 +4,34 @@ local multi,thread = require("multi"):init() Before AVG: 522386 Test 1 AVG: ]] -local sleep_for = 5 - +local sleep_for = 100000 +local conn = multi:newConnection() local function bench(_,steps) print("Steps/5s: "..steps) os.exit() end +local ready = false +multi:newAlarm(3):OnRing(function() + conn:Fire() +end) multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) -multi:newThread("Thread 1",function(a,b,c) - print(a,b,c) +multi:newThread("Thread 1",function() while true do - print(1) - thread.sleep(1) -- We just need to run things - print("1 ...") + thread.hold(conn) -- We just need to run things end -end,1,2,3) +end) -multi:newThread("Thread 2",function(a,b,c) - print(a,b,c) +multi:newThread("Thread 2",function() + thread.sleep(1) + error("Hi") +end) + +multi:newThread("Thread 3",function() while true do - print(2) thread.sleep(1) -- We just need to run things - print("2 ...") + print("3 ...") end -end,4,5,6) +end) -- multi.OnExit(function() -- print("Total: ".. a) From 3fbead60d9bfe65ca081c731fd6a81f535f38e35 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 26 Jan 2022 16:52:17 -0500 Subject: [PATCH 31/80] connections working, todo: error catching and return catching --- multi/init.lua | 85 +++++++++++++++++++++++++------------------------- test4.lua | 18 +++++------ 2 files changed, 49 insertions(+), 54 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 47f65f8..17748fe 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1033,8 +1033,9 @@ end function thread.hold(n,opt) thread._Requests() - interval = opt.interval + local opt = opt or {} if type(opt)=="table" then + interval = opt.interval if opt.cycles then return coroutine.yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) elseif opt.sleep then @@ -1046,6 +1047,14 @@ function thread.hold(n,opt) if type(n) == "number" then thread.getRunningThread().lastSleep = clock() return coroutine.yield(CMD, t_sleep, n or 0, nil, interval) + elseif type(n) == "table" and n.Type == "connector" then + local ready = false + n(function() + ready = true + end) + return coroutine.yield(CMD, t_hold, function() + return ready + end) else return coroutine.yield(CMD, t_hold, n or dFunc, nil, interval) end @@ -1332,17 +1341,6 @@ function multi:attachScheduler() local t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 local r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 local ret,_ - local function holdconn(n) - if type(ret[n])=="table" and ret[n].Type=='connector' then - local letsgo - ret[n](function(...) letsgo = {...} end) - ret[n] = function() - if letsgo then - return unpack(letsgo) - end - end - end - end local task, thd, ref, ready --[[ if coroutine.running() ~= threads[i].thread then @@ -1350,9 +1348,9 @@ function multi:attachScheduler() CheckRets(i) end ]] - -- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order + -- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_conn, t_none <-- Order local switch = { - function(th,co,arg1,arg2,arg3,arg4)--hold + function(th,co)--hold if clock() - th.intervalR>=th.interval then t0,t1,t2,t3,t4,t5,t6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = th.func() if t0 then @@ -1363,13 +1361,13 @@ function multi:attachScheduler() th.intervalR = clock() end end, - function(th,co,arg1,arg2,arg3,arg4)--sleep + function(th,co)--sleep if clock() - th.time>=th.sec then th.task = t_none _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) end end, - function(th,co,arg1,arg2,arg3,arg4)--holdf + function(th,co)--holdf if clock() - th.intervalR>=th.interval then t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() if t0 then @@ -1388,14 +1386,14 @@ function multi:attachScheduler() th.intervalR = clock() end end, - function(th,co,arg1,arg2,arg3,arg4)--skip + function(th,co)--skip th.pos = th.pos + 1 if th.count==th.pos then th.task = t_none _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) end end, - function(th,co,arg1,arg2,arg3,arg4)--holdw + function(th,co)--holdw if clock() - th.intervalR>=th.interval then th.pos = th.pos + 1 t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() @@ -1415,50 +1413,55 @@ function multi:attachScheduler() th.intervalR = clock() end end, - function(th,co,arg1,arg2,arg3,arg4)--yield + function(th,co)--yield _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) end, function() end--none } setmetatable(switch,{__index=function() return function() end end}) local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order - -- function(th,arg1,arg2,arg3,arg4) + -- function(th,arg1,arg2,arg3) -- threads[i].OnDeath:Fire(threads[i],"killed",ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) -- self.setType(threads[i],self.DestroyedObj) -- table.remove(threads,i) -- ret = nil -- end, - function(th,arg1,arg2,arg3,arg4) + function(th,arg1,arg2,arg3) + print("hold",arg1,arg2,arg3) th.func = arg1 th.task = t_hold - th.interval = arg4 or 0 + th.interval = arg3 or 0 th.intervalR = clock() end, - function(th,arg1,arg2,arg3,arg4) + function(th,arg1,arg2,arg3) + print("sleep",arg1,arg2,arg3) th.sec = arg1 th.time = clock() th.task = t_sleep end, - function(th,arg1,arg2,arg3,arg4) + function(th,arg1,arg2,arg3) + print("holdf",arg1,arg2,arg3) th.sec = arg1 th.func = arg2 th.task = t_holdF th.time = clock() - th.interval = arg4 or 0 + th.interval = arg3 or 0 th.intervalR = clock() end, - function(th,arg1,arg2,arg3,arg4) + function(th,arg1,arg2,arg3) + print("skip",arg1,arg2,arg3) th.count = arg1 th.pos = 0 th.task = t_skip end, - function(th,arg1,arg2,arg3,arg4) + function(th,arg1,arg2,arg3) + print("holdw",arg1,arg2,arg3) th.count = arg1 th.pos = 0 th.func = arg2 th.task = t_holdW th.time = clock() - th.interval = arg4 or 0 + th.interval = arg3 or 0 th.intervalR = clock() end, function() end @@ -1468,21 +1471,18 @@ function multi:attachScheduler() ["suspended"] = function(thd,ref,task) switch[task](ref,thd) cmds[r1](ref,r2,r3,r4,r5) - if not _ then - print(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) - io.read() -- This is an error spot too - end r1=nil r2=nil r3=nil r4=nil r5=nil end, ["normal"] = function(thd,ref) end, -- Not sure if I will handle this ["running"] = function(thd,ref) end, ["dead"] = function(thd,ref,task,i) + print("Ended:",ref.Name,_,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) local t = ref.TempRets or {} - print(_,ref.Name,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) print("ended",t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8],t[9],t[10],t[11],t[12],t[13],t[14],t[15],t[16]) ref.OnDeath:Fire(ref,"ended",t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8],t[9],t[10],t[11],t[12],t[13],t[14],t[15],t[16]) table.remove(threads,i) - self.setType(ref,self.DestroyedObj) + _=nil r1=nil r2=nil r3=nil r4=nil r5=nil + --self.setType(ref,self.DestroyedObj) end, } self.scheduler:OnLoop(function(self) @@ -1492,20 +1492,19 @@ function multi:attachScheduler() thd = ref.thread ready = ref.__ready - for start = 1, startme_len do + for start = startme_len,1,-1 do _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(startme[start].thread,unpack(startme[start].startArgs)) -- An error can happen here cmds[r1](startme[start],r2,r3,r4,r5) startme_len = startme_len - 1 + if not _ then + co_status["dead"](startme[start].thread,startme[start],task,i) + table.remove(startme,startme_len) + break + end + table.remove(startme,startme_len) end - -- if threads[i].isError then - -- if status(threads[i].thread)=="dead" then - -- threads[i].OnError:Fire(threads[i],unpack(threads[i].TempRets)) - -- self.setType(threads[i],self.DestroyedObj) - -- table.remove(threads,i) - -- end - -- end - co_status[status(thd)](thd,ref,task,i) + co_status[status(thd)](thd,ref,task,i) end end) end diff --git a/test4.lua b/test4.lua index 73003d1..74feb57 100644 --- a/test4.lua +++ b/test4.lua @@ -4,7 +4,7 @@ local multi,thread = require("multi"):init() Before AVG: 522386 Test 1 AVG: ]] -local sleep_for = 100000 +local sleep_for = 5 local conn = multi:newConnection() local function bench(_,steps) print("Steps/5s: "..steps) @@ -13,24 +13,20 @@ end local ready = false multi:newAlarm(3):OnRing(function() conn:Fire() + ready = true end) multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) multi:newThread("Thread 1",function() - while true do - thread.hold(conn) -- We just need to run things - end + error("Testing 1") end) multi:newThread("Thread 2",function() - thread.sleep(1) - error("Hi") + thread.sleep(2) + error("Testing 2") end) - multi:newThread("Thread 3",function() - while true do - thread.sleep(1) -- We just need to run things - print("3 ...") - end + thread.sleep(1) + return "Complete 3" end) -- multi.OnExit(function() From cdb4bfda1183f2ed793f3012964eddb1eb4066e2 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Fri, 28 Jan 2022 17:50:19 -0500 Subject: [PATCH 32/80] Error handling not working if its on the first step --- multi/init.lua | 52 ++++++++++++++++++++++++-------------------------- test4.lua | 25 ++++++++++++++++-------- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 17748fe..2119993 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1420,27 +1420,21 @@ function multi:attachScheduler() } setmetatable(switch,{__index=function() return function() end end}) local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order - -- function(th,arg1,arg2,arg3) - -- threads[i].OnDeath:Fire(threads[i],"killed",ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) - -- self.setType(threads[i],self.DestroyedObj) - -- table.remove(threads,i) - -- ret = nil - -- end, function(th,arg1,arg2,arg3) - print("hold",arg1,arg2,arg3) + --print("hold",arg1,arg2,arg3) th.func = arg1 th.task = t_hold th.interval = arg3 or 0 th.intervalR = clock() end, function(th,arg1,arg2,arg3) - print("sleep",arg1,arg2,arg3) + --print("sleep",arg1,arg2,arg3) th.sec = arg1 th.time = clock() th.task = t_sleep end, function(th,arg1,arg2,arg3) - print("holdf",arg1,arg2,arg3) + --print("holdf",arg1,arg2,arg3) th.sec = arg1 th.func = arg2 th.task = t_holdF @@ -1449,13 +1443,13 @@ function multi:attachScheduler() th.intervalR = clock() end, function(th,arg1,arg2,arg3) - print("skip",arg1,arg2,arg3) + --print("skip",arg1,arg2,arg3) th.count = arg1 th.pos = 0 th.task = t_skip end, function(th,arg1,arg2,arg3) - print("holdw",arg1,arg2,arg3) + --print("holdw",arg1,arg2,arg3) th.count = arg1 th.pos = 0 th.func = arg2 @@ -1476,13 +1470,23 @@ function multi:attachScheduler() ["normal"] = function(thd,ref) end, -- Not sure if I will handle this ["running"] = function(thd,ref) end, ["dead"] = function(thd,ref,task,i) - print("Ended:",ref.Name,_,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) - local t = ref.TempRets or {} - print("ended",t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8],t[9],t[10],t[11],t[12],t[13],t[14],t[15],t[16]) - ref.OnDeath:Fire(ref,"ended",t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8],t[9],t[10],t[11],t[12],t[13],t[14],t[15],t[16]) - table.remove(threads,i) + if _ then + ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) + else + ref.OnError:Fire(ref,ret,r1) + end + if i then + table.remove(threads,i) + else + for i,v in pairs(threads) do + if v.thread==thd then + table.remove(threads,i) + break + end + end + end _=nil r1=nil r2=nil r3=nil r4=nil r5=nil - --self.setType(ref,self.DestroyedObj) + self.setType(ref,self.DestroyedObj) end, } self.scheduler:OnLoop(function(self) @@ -1491,18 +1495,12 @@ function multi:attachScheduler() task = ref.task thd = ref.thread ready = ref.__ready - + -- First time setup for threads for start = startme_len,1,-1 do - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(startme[start].thread,unpack(startme[start].startArgs)) - -- An error can happen here - cmds[r1](startme[start],r2,r3,r4,r5) + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(startme[start].thread,unpack(startme[start].startArgs)) + co_status[status(startme[startme_len].thread)](startme[startme_len].thread,startme[startme_len],t_none) --cmds[r1](startme[start],r2,r3,r4,r5) + startme[startme_len] = nil startme_len = startme_len - 1 - if not _ then - co_status["dead"](startme[start].thread,startme[start],task,i) - table.remove(startme,startme_len) - break - end - table.remove(startme,startme_len) end co_status[status(thd)](thd,ref,task,i) end diff --git a/test4.lua b/test4.lua index 74feb57..1150f14 100644 --- a/test4.lua +++ b/test4.lua @@ -17,17 +17,26 @@ multi:newAlarm(3):OnRing(function() end) multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) multi:newThread("Thread 1",function() - error("Testing 1") -end) + while true do + thread.sleep(1) + error("hi") + print("Test 1") + thread.hold(conn) + print("Conn sleep test") + end +end).OnError(print) multi:newThread("Thread 2",function() - thread.sleep(2) - error("Testing 2") -end) + print("Thread 2") + return "it worked" +end):OnDeath(print):OnError(error) multi:newThread("Thread 3",function() - thread.sleep(1) - return "Complete 3" -end) + thread.hold(function() + return ready + end) + print("Function test") + return "Yay we did it" +end).OnDeath(print) -- multi.OnExit(function() -- print("Total: ".. a) From 49c0bd393052ac2942307473448d54c271329ffb Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 31 Jan 2022 08:31:38 -0500 Subject: [PATCH 33/80] Fixing issues with the new thread scheduler, nested yields need handling --- multi/init.lua | 61 +++++++++++++++++++++++++--------------- test4.lua | 2 +- tests/connectionTest.lua | 21 ++++++++++++-- tests/objectTests.lua | 7 +++-- tests/runtests.lua | 17 +++++++---- 5 files changed, 73 insertions(+), 35 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 2119993..3a6dc71 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -79,7 +79,7 @@ function multi.Stop() end --Processor -local priorityTable = {[0]="Round-Robin",[1]="Balanced",[2]="Top-Down",[3]="Timed-Based-Balancer"} +local priorityTable = {[false]="Disabled",[true]="Enabled"} local ProcessName = {"SubProcessor","MainProcessor"} local globalThreads = {} @@ -132,7 +132,7 @@ function multi:getTasksDetails(t) if tostring(th.isProcessThread) == "destroyed" then globalThreads[th] = nil elseif th.isProcessThread then - local load, steps = process:getLoad() + load, steps = process:getLoad() process_count = process_count + 1 table.insert(proc_tab,{th.Name,os.clock()-th.creationTime,(th.PID or "-1"),load,steps}) else @@ -142,16 +142,16 @@ function multi:getTasksDetails(t) end dat = multi.AlignTable(proc_tab).. "\n" dat = dat .. "\n" .. multi.AlignTable(th_tab) - return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: "..thread_count.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat..dat2.."\n\n"..s + return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: "..thread_count.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or false].."\n\n"..dat..dat2.."\n\n"..s else - return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\n\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat2.."\n\n"..s + return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\n\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or false].."\n\n"..dat2.."\n\n"..s end else local load,steps = self:getLoad() str = { ProcessName = (self.Name or "Unnamed"), MemoryUsage = math.ceil(collectgarbage("count")), - PriorityScheme = priorityTable[multi.defaultSettings.priority or 0], + PriorityScheme = priorityTable[multi.defaultSettings.priority or false], SystemLoad = multi.Round(load,2), CyclesPerSecondPerTask = steps, SystemThreadCount = multi.SystemThreads and #multi.SystemThreads or 0 @@ -286,6 +286,9 @@ function multi:newConnection(protect,func,kill) else function c:Fire(...) for i=#call_funcs,1,-1 do + if type(call_funcs[i])=="table" then + print(call_funcs[i].Parent.Type) + end call_funcs[i](...) if kill then table.remove(call_funcs,i) @@ -304,6 +307,9 @@ function multi:newConnection(protect,func,kill) end end function self:connect(func) + if type(func) == "table" then + print(debug.traceback()) + end table.insert(fast,func) end end @@ -382,6 +388,9 @@ function multi:newConnection(protect,func,kill) function c:connect(...)--func,name,num local tab = {...} local funcs={} + if type(tab[1])=="table" then + print(debug.traceback()) + end for i=1,#tab do if type(tab[i])=="function" then funcs[#funcs+1] = tab[i] @@ -520,7 +529,7 @@ function multi:Destroy() globalThreads = new multi.setType(self,multi.DestroyedObj) else - for i=1,#self.Parent.Mainloop do + for i=#self.Parent.Mainloop,1,-1 do if self.Parent.Mainloop[i]==self then self.Parent.OnObjectDestroyed:Fire(self) table.remove(self.Parent.Mainloop,i) @@ -528,7 +537,7 @@ function multi:Destroy() break end end - multi.setType(self,multi.DestroyedObj) + self.Act = function() end end return self end @@ -726,6 +735,7 @@ function multi:newLoop(func) end function multi:newStep(start,reset,count,skip) + print(self.Type) local c=self:newBase() think=1 c.Type='step' @@ -920,11 +930,9 @@ function multi:newProcessor(name,nothread) c.Type = "process" c.Active = false or nothread c.Name = name or "" - c.process = self:newThread(c.Name,function() + c.process = self:newThread(function() while true do - thread.hold(function() - return c.Active and not(nothread) - end) + thread.hold(function() return c.Active end) __CurrentProcess = c c:uManager() __CurrentProcess = self @@ -1044,6 +1052,7 @@ function thread.hold(n,opt) return coroutine.yield(CMD, t_skip, opt.skip or 1, nil, interval) end end + if type(n) == "number" then thread.getRunningThread().lastSleep = clock() return coroutine.yield(CMD, t_sleep, n or 0, nil, interval) @@ -1055,8 +1064,10 @@ function thread.hold(n,opt) return coroutine.yield(CMD, t_hold, function() return ready end) - else + elseif type(n) == "function" then return coroutine.yield(CMD, t_hold, n or dFunc, nil, interval) + else + error("Invalid argument passed to thread.hold(...)!") end end @@ -1126,6 +1137,7 @@ end function thread.pushStatus(...) local t = thread.getRunningThread() + print("Pushing",t) t.statusconnector:Fire(...) end @@ -1223,10 +1235,10 @@ function multi.setEnv(func,env) local chunk = load(f,"env","bt",env) return chunk end -local resume, status, create = coroutine.resume, coroutine.status, coroutine.create + function multi:attachScheduler() + local resume, status, create = coroutine.resume, coroutine.status, coroutine.create local threads = {} - self.threadsRef = threads local startme = {} local startme_len = 0 function self:newThread(name,func,...) @@ -1246,7 +1258,6 @@ function multi:attachScheduler() c.Type="thread" c.TID = threadid c.firstRunDone=false - c.timer=self:newTimer() c._isPaused = false c.returns = {} c.isError = false @@ -1471,9 +1482,11 @@ function multi:attachScheduler() ["running"] = function(thd,ref) end, ["dead"] = function(thd,ref,task,i) if _ then + print("Death") ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) else - ref.OnError:Fire(ref,ret,r1) + print("Error",ref,ret) + ref.OnError:Fire(ref,ret) end if i then table.remove(threads,i) @@ -1486,15 +1499,11 @@ function multi:attachScheduler() end end _=nil r1=nil r2=nil r3=nil r4=nil r5=nil - self.setType(ref,self.DestroyedObj) + --self.setType(ref,self.DestroyedObj) end, } self.scheduler:OnLoop(function(self) for i=#threads,1,-1 do - ref = threads[i] - task = ref.task - thd = ref.thread - ready = ref.__ready -- First time setup for threads for start = startme_len,1,-1 do _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(startme[start].thread,unpack(startme[start].startArgs)) @@ -1502,7 +1511,13 @@ function multi:attachScheduler() startme[startme_len] = nil startme_len = startme_len - 1 end - co_status[status(thd)](thd,ref,task,i) + if threads[i] then + ref = threads[i] + task = ref.task + thd = ref.thread + ready = ref.__ready + co_status[status(thd)](thd,ref,task,i) + end end end) end @@ -1790,7 +1805,7 @@ function multi:getLoad() _,timeout = multi.hold(function() return bench end,{sleep=.012}) - if timeout then + if timeout or not bench then bench = 0 bb = 0 end diff --git a/test4.lua b/test4.lua index 1150f14..2fcffe3 100644 --- a/test4.lua +++ b/test4.lua @@ -19,10 +19,10 @@ multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) multi:newThread("Thread 1",function() while true do thread.sleep(1) - error("hi") print("Test 1") thread.hold(conn) print("Conn sleep test") + error("hi") end end).OnError(print) diff --git a/tests/connectionTest.lua b/tests/connectionTest.lua index 98221b4..06f3dc8 100644 --- a/tests/connectionTest.lua +++ b/tests/connectionTest.lua @@ -1,6 +1,9 @@ function connectionThreadTests(multi,thread) print("Starting Connection and Thread tests!") + print("Current Thread:",thread.getRunningThread()) func = thread:newFunction(function(count) + print("Starting Status test: ",count) + print("Current Thread:",thread.getRunningThread().thread) local a = 0 while true do a = a + 1 @@ -14,8 +17,12 @@ function connectionThreadTests(multi,thread) local ret2 = func(15) local ret3 = func(20) local s1,s2,s3 = 0,0,0 + ret.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 @@ -23,14 +30,22 @@ function connectionThreadTests(multi,thread) ret3.OnStatus(function(part,whole) s3 = math.ceil((part/whole)*1000)/10 end) - local err, timeout = thread.hold(ret2.OnReturn + ret.OnReturn + ret3.OnReturn,{sleep=3}) + ret.OnReturn(function() + print("Done") + end) + local err, timeout = thread.hold(ret.OnReturn + ret2.OnReturn + ret3.OnReturn,{sleep=3}) + print(err,timeout) + for i,v in pairs(err) do + print(i,v) + end + os.exit() if s1 == 100 and s2 == 100 and s3 == 100 then print("Threads: Ok") else - print("Threads on status error") + print("Threads OnStatus or thread.hold(conn) Error!") end if timeout then - print("Threads or Connection error!") + print("Threads or Connection Error!") else print("Connection Test 1: Ok") end diff --git a/tests/objectTests.lua b/tests/objectTests.lua index c72624d..b94cdb9 100644 --- a/tests/objectTests.lua +++ b/tests/objectTests.lua @@ -7,12 +7,12 @@ function objectTests(multi,thread) end) multi:newTStep(1,10,1,.1):OnStep(function(t) tsteps = tsteps + 1 - end):OnEnd(function(step) + end).OnEnd(function(step) step:Destroy() end) multi:newStep(1,10):OnStep(function(s) steps = steps + 1 - end):OnEnd(function(step) + end).OnEnd(function(step) step:Destroy() end) local loop = multi:newLoop(function(l) @@ -28,6 +28,7 @@ function objectTests(multi,thread) return alarms end) event.OnEvent(function(evnt) + evnt:Destroy() events = true print("Alarms: Ok") print("Events: Ok") @@ -37,6 +38,6 @@ function objectTests(multi,thread) if tloops > 10 then print("TLoops: Ok") else print("TLoops: Bad!") end if updaters > 100 then print("Updaters: Ok") else print("Updaters: Bad!") end end) - thread.hold(event.OnEvent) + thread.hold(event.OnEvent) end return objectTests \ No newline at end of file diff --git a/tests/runtests.lua b/tests/runtests.lua index 52f27d6..d0a6807 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -18,15 +18,22 @@ package.path="./?.lua;../?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package local multi, thread = require("multi"):init{priority=true} local good = false runTest = thread:newFunction(function() - local objects = multi:newProcessor("Basic Object Tests") - objects.Start() - require("tests/objectTests")(objects,thread) - objects.Stop() + -- thread.sleep(1) + -- local objects = multi:newProcessor("Basic Object Tests") + -- objects.OnError(function(...) + -- print("Error: ",...) + -- end) + -- objects.Start() + -- require("tests/objectTests")(objects,thread) + -- objects.Stop() local conn_thread = multi:newProcessor("Connection/Thread Tests") + conn_thread.OnError(function(...) + print("Error: ",...) + end) conn_thread.Start() require("tests/connectionTest")(conn_thread,thread) conn_thread.Stop() - print(multi:getTasksDetails()) + --print(multi:getTasksDetails()) os.exit() end) runTest().OnError(function(...) From b572bf218d78a548bae50b65aa5984c80ac54c79 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 31 Jan 2022 09:47:37 -0500 Subject: [PATCH 34/80] Still debugging, not sure what is causing the thread to not yield properly --- multi/init.lua | 6 ++--- rockspecs/multi-15.2-0.rockspec | 42 +++++++++++++++++++++++++++++++++ tests/connectionTest.lua | 9 +------ tests/runtests.lua | 6 ++++- 4 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 rockspecs/multi-15.2-0.rockspec diff --git a/multi/init.lua b/multi/init.lua index 3a6dc71..9d3f3f3 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1137,7 +1137,6 @@ end function thread.pushStatus(...) local t = thread.getRunningThread() - print("Pushing",t) t.statusconnector:Fire(...) end @@ -1433,6 +1432,7 @@ function multi:attachScheduler() local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order function(th,arg1,arg2,arg3) --print("hold",arg1,arg2,arg3) + --print(_,ret,r1,r2,r3,r4,r5) th.func = arg1 th.task = t_hold th.interval = arg3 or 0 @@ -1478,8 +1478,8 @@ function multi:attachScheduler() cmds[r1](ref,r2,r3,r4,r5) r1=nil r2=nil r3=nil r4=nil r5=nil end, - ["normal"] = function(thd,ref) end, -- Not sure if I will handle this - ["running"] = function(thd,ref) end, + ["normal"] = function(thd,ref) print("Normal Status") io.read() end, -- Not sure if I will handle this + ["running"] = function(thd,ref) print("Running Status") io.read() end, ["dead"] = function(thd,ref,task,i) if _ then print("Death") diff --git a/rockspecs/multi-15.2-0.rockspec b/rockspecs/multi-15.2-0.rockspec new file mode 100644 index 0000000..f0aaaf5 --- /dev/null +++ b/rockspecs/multi-15.2-0.rockspec @@ -0,0 +1,42 @@ +package = "multi" +version = "15.2-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v15.2.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. Features non coroutine based multi-tasking, coroutine based multi-tasking, and system threading (Requires use of an integration). + Check github for documentation. + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1", + "lanes", +} +build = { + type = "builtin", + modules = { + ["multi"] = "multi/init.lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.compat.lovr"] = "multi/compat/lovr.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/tests/connectionTest.lua b/tests/connectionTest.lua index 06f3dc8..babf1f2 100644 --- a/tests/connectionTest.lua +++ b/tests/connectionTest.lua @@ -1,9 +1,7 @@ function connectionThreadTests(multi,thread) print("Starting Connection and Thread tests!") - print("Current Thread:",thread.getRunningThread()) func = thread:newFunction(function(count) print("Starting Status test: ",count) - print("Current Thread:",thread.getRunningThread().thread) local a = 0 while true do a = a + 1 @@ -33,12 +31,7 @@ function connectionThreadTests(multi,thread) ret.OnReturn(function() print("Done") end) - local err, timeout = thread.hold(ret.OnReturn + ret2.OnReturn + ret3.OnReturn,{sleep=3}) - print(err,timeout) - for i,v in pairs(err) do - print(i,v) - end - os.exit() + local err, timeout = thread.hold(ret.OnReturn + ret2.OnReturn + ret3.OnReturn) if s1 == 100 and s2 == 100 and s3 == 100 then print("Threads: Ok") else diff --git a/tests/runtests.lua b/tests/runtests.lua index d0a6807..020b4c9 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -1,4 +1,6 @@ -package.path="./?.lua;../?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package.path +package.path="multi/?.lua;multi/?/init.lua;multi/?.lua;multi/?/?/init.lua;"..package.path +require("lldebugger").start() + --[[ This file runs all tests. Format: @@ -15,6 +17,8 @@ package.path="./?.lua;../?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package The expected and actual should "match" (Might be impossible when playing with threads) This will be pushed directly to the master as tests start existing. ]] +os.execute("cd multi") +os.execute("pwd") local multi, thread = require("multi"):init{priority=true} local good = false runTest = thread:newFunction(function() From a9111f2fa3f84fddf7a86630628dc02ac7239bd1 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 31 Jan 2022 09:49:12 -0500 Subject: [PATCH 35/80] test --- test4.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test4.lua b/test4.lua index 2fcffe3..fa7758e 100644 --- a/test4.lua +++ b/test4.lua @@ -22,7 +22,7 @@ multi:newThread("Thread 1",function() print("Test 1") thread.hold(conn) print("Conn sleep test") - error("hi") + error("hi") end end).OnError(print) From 207c5b8d692878be9ea6d646d6b6cc5b6d3fadeb Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 31 Jan 2022 09:52:43 -0500 Subject: [PATCH 36/80] test2 --- test4.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test4.lua b/test4.lua index fa7758e..2f79735 100644 --- a/test4.lua +++ b/test4.lua @@ -27,7 +27,7 @@ multi:newThread("Thread 1",function() end).OnError(print) multi:newThread("Thread 2",function() - print("Thread 2") + print("Thread 2") return "it worked" end):OnDeath(print):OnError(error) multi:newThread("Thread 3",function() From a60aae02c69a99c30a79cd39294a54cdec1b659c Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 31 Jan 2022 10:45:26 -0500 Subject: [PATCH 37/80] Cleanup spaces --- test4.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test4.lua b/test4.lua index 2f79735..2fcffe3 100644 --- a/test4.lua +++ b/test4.lua @@ -22,12 +22,12 @@ multi:newThread("Thread 1",function() print("Test 1") thread.hold(conn) print("Conn sleep test") - error("hi") + error("hi") end end).OnError(print) multi:newThread("Thread 2",function() - print("Thread 2") + print("Thread 2") return "it worked" end):OnDeath(print):OnError(error) multi:newThread("Thread 3",function() From e05f2ea400990752fe87a95fd3495a35441f68ca Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 31 Jan 2022 10:50:45 -0500 Subject: [PATCH 38/80] last test --- test4.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test4.lua b/test4.lua index 2fcffe3..4127ec2 100644 --- a/test4.lua +++ b/test4.lua @@ -24,7 +24,7 @@ multi:newThread("Thread 1",function() print("Conn sleep test") error("hi") end -end).OnError(print) +end).OnError(print) multi:newThread("Thread 2",function() print("Thread 2") From c3a9ddfdbd47851aee689cdccf068c9855d02a10 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 31 Jan 2022 10:51:44 -0500 Subject: [PATCH 39/80] Reverted the extra space --- test4.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test4.lua b/test4.lua index 4127ec2..2fcffe3 100644 --- a/test4.lua +++ b/test4.lua @@ -24,7 +24,7 @@ multi:newThread("Thread 1",function() print("Conn sleep test") error("hi") end -end).OnError(print) +end).OnError(print) multi:newThread("Thread 2",function() print("Thread 2") From 6c73220a52af21466f0956c8e897159bf20496bc Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 31 Jan 2022 17:11:14 -0500 Subject: [PATCH 40/80] working on scheduler, nothing fix yet --- multi/init.lua | 3 ++- tests/runtests.lua | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 9d3f3f3..781f4f2 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1313,6 +1313,7 @@ function multi:attachScheduler() table.insert(threads,c) table.insert(startme,c) startme_len = #startme + print("startme:",startme_len) globalThreads[c] = self if initT==false then self.initThreads() @@ -1509,7 +1510,7 @@ function multi:attachScheduler() _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(startme[start].thread,unpack(startme[start].startArgs)) co_status[status(startme[startme_len].thread)](startme[startme_len].thread,startme[startme_len],t_none) --cmds[r1](startme[start],r2,r3,r4,r5) startme[startme_len] = nil - startme_len = startme_len - 1 + startme_len = #startme end if threads[i] then ref = threads[i] diff --git a/tests/runtests.lua b/tests/runtests.lua index 020b4c9..38c8860 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -1,6 +1,9 @@ -package.path="multi/?.lua;multi/?/init.lua;multi/?.lua;multi/?/?/init.lua;"..package.path -require("lldebugger").start() - +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 +end --[[ This file runs all tests. Format: @@ -17,8 +20,6 @@ require("lldebugger").start() The expected and actual should "match" (Might be impossible when playing with threads) This will be pushed directly to the master as tests start existing. ]] -os.execute("cd multi") -os.execute("pwd") local multi, thread = require("multi"):init{priority=true} local good = false runTest = thread:newFunction(function() @@ -38,7 +39,6 @@ runTest = thread:newFunction(function() require("tests/connectionTest")(conn_thread,thread) conn_thread.Stop() --print(multi:getTasksDetails()) - os.exit() end) runTest().OnError(function(...) print("Error:",...) From bf60a354d431faedbb85adb77fc2fe381b18880a Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Fri, 4 Feb 2022 17:27:13 -0500 Subject: [PATCH 41/80] So the bug wasn't a bug all along --- tests/connectionTest.lua | 79 ----------------------- tests/objectTests.lua | 43 ------------- tests/runtests.lua | 133 ++++++++++++++++++++++++++++++++++----- 3 files changed, 118 insertions(+), 137 deletions(-) delete mode 100644 tests/connectionTest.lua delete mode 100644 tests/objectTests.lua diff --git a/tests/connectionTest.lua b/tests/connectionTest.lua deleted file mode 100644 index babf1f2..0000000 --- a/tests/connectionTest.lua +++ /dev/null @@ -1,79 +0,0 @@ -function connectionThreadTests(multi,thread) - print("Starting Connection and Thread tests!") - func = thread:newFunction(function(count) - print("Starting Status test: ",count) - local a = 0 - while true do - a = a + 1 - thread.sleep(.1) - thread.pushStatus(a,count) - if a == count then break end - end - return "Done" - 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) - 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 - end) - ret3.OnStatus(function(part,whole) - s3 = math.ceil((part/whole)*1000)/10 - end) - ret.OnReturn(function() - print("Done") - end) - local err, timeout = thread.hold(ret.OnReturn + ret2.OnReturn + ret3.OnReturn) - if s1 == 100 and s2 == 100 and s3 == 100 then - print("Threads: 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") - end - conn1 = multi:newConnection() - conn2 = multi:newConnection() - conn3 = multi:newConnection() - local c1,c2,c3,c4 = false,false,false,false - local a = conn1(function() - c1 = true - end) - local b = conn2(function() - c2 = true - end) - local c = conn3(function() - c3 = true - end) - local d = conn3(function() - c4 = true - end) - conn1:Fire() - conn2:Fire() - conn3:Fire() - if c1 and c2 and c3 and c4 then - print("Connection Test 2: Ok") - else - print("Connection Test 2: Error") - end - c3 = false - c4 = false - d:Destroy() - conn3:Fire() - if c3 and not(c4) then - print("Connection Test 3: Ok") - else - print("Connection Test 3: Error removing connection") - end -end -return connectionThreadTests \ No newline at end of file diff --git a/tests/objectTests.lua b/tests/objectTests.lua deleted file mode 100644 index b94cdb9..0000000 --- a/tests/objectTests.lua +++ /dev/null @@ -1,43 +0,0 @@ -function objectTests(multi,thread) - local alarms,tsteps,steps,loops,tloops,updaters,events=false,0,0,0,0,0,false - print("Testing Basic Features. If this fails most other features will probably not work!") - multi:newAlarm(2):OnRing(function(a) - alarms = true - a:Destroy() - end) - multi:newTStep(1,10,1,.1):OnStep(function(t) - tsteps = tsteps + 1 - end).OnEnd(function(step) - step:Destroy() - end) - multi:newStep(1,10):OnStep(function(s) - steps = steps + 1 - end).OnEnd(function(step) - step:Destroy() - end) - local loop = multi:newLoop(function(l) - loops = loops + 1 - end) - multi:newTLoop(function(t) - tloops = tloops + 1 - end,.1) - local updater = multi:newUpdater(1):OnUpdate(function() - updaters = updaters + 1 - end) - local event = multi:newEvent(function() - return alarms - end) - event.OnEvent(function(evnt) - evnt:Destroy() - events = true - print("Alarms: Ok") - print("Events: Ok") - if tsteps == 10 then print("TSteps: Ok") else print("TSteps: Bad!") end - if steps == 10 then print("Steps: Ok") else print("Steps: Bad!") end - if loops > 100 then print("Loops: Ok") else print("Loops: Bad!") end - if tloops > 10 then print("TLoops: Ok") else print("TLoops: Bad!") end - if updaters > 100 then print("Updaters: Ok") else print("Updaters: Bad!") end - end) - thread.hold(event.OnEvent) -end -return objectTests \ No newline at end of file diff --git a/tests/runtests.lua b/tests/runtests.lua index 38c8860..fdedeb9 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -22,23 +22,126 @@ end ]] local multi, thread = require("multi"):init{priority=true} local good = false +multi:newAlarm(3):OnRing(function() + good = true +end) runTest = thread:newFunction(function() - -- thread.sleep(1) - -- local objects = multi:newProcessor("Basic Object Tests") - -- objects.OnError(function(...) - -- print("Error: ",...) - -- end) - -- objects.Start() - -- require("tests/objectTests")(objects,thread) - -- objects.Stop() - local conn_thread = multi:newProcessor("Connection/Thread Tests") - conn_thread.OnError(function(...) - print("Error: ",...) + local alarms,tsteps,steps,loops,tloops,updaters,events=false,0,0,0,0,0,false + print("Testing Basic Features. If this fails most other features will probably not work!") + multi:newAlarm(2):OnRing(function(a) + alarms = true + a:Destroy() + end) + multi:newTStep(1,10,1,.1):OnStep(function(t) + tsteps = tsteps + 1 + end).OnEnd(function(step) + step:Destroy() + end) + multi:newStep(1,10):OnStep(function(s) + steps = steps + 1 + end).OnEnd(function(step) + step:Destroy() + end) + local loop = multi:newLoop(function(l) + loops = loops + 1 + end) + multi:newTLoop(function(t) + tloops = tloops + 1 + end,.1) + local updater = multi:newUpdater(1):OnUpdate(function() + updaters = updaters + 1 + end) + local event = multi:newEvent(function() + return alarms + end) + event.OnEvent(function(evnt) + evnt:Destroy() + events = true + print("Alarms: Ok") + print("Events: Ok") + if tsteps == 10 then print("TSteps: Ok") else print("TSteps: Bad!") end + if steps == 10 then print("Steps: Ok") else print("Steps: Bad!") end + if loops > 100 then print("Loops: Ok") else print("Loops: Bad!") end + if tloops > 10 then print("TLoops: Ok") else print("TLoops: Bad!") end + if updaters > 100 then print("Updaters: Ok") else print("Updaters: Bad!") end + end) + thread.hold(event.OnEvent) + print("Starting Connection and Thread tests!") + func = thread:newFunction(function(count) + print("Starting Status test: ",count) + local a = 0 + while true do + a = a + 1 + thread.sleep(.1) + thread.pushStatus(a,count) + if a == count then break end + end + return "Done" end) - conn_thread.Start() - require("tests/connectionTest")(conn_thread,thread) - conn_thread.Stop() - --print(multi:getTasksDetails()) + 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) + ret.OnStatus(function(part,whole) + s1 = math.ceil((part/whole)*1000)/10 + end) + ret2.OnStatus(function(part,whole) + s2 = math.ceil((part/whole)*1000)/10 + end) + ret3.OnStatus(function(part,whole) + s3 = math.ceil((part/whole)*1000)/10 + end) + ret.OnReturn(function() + print("Done") + end) + local err, timeout = thread.hold(ret.OnReturn + ret2.OnReturn + ret3.OnReturn) + if s1 == 100 and s2 == 100 and s3 == 100 then + print("Threads: 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") + end + conn1 = multi:newConnection() + conn2 = multi:newConnection() + conn3 = multi:newConnection() + local c1,c2,c3,c4 = false,false,false,false + local a = conn1(function() + c1 = true + end) + local b = conn2(function() + c2 = true + end) + local c = conn3(function() + c3 = true + end) + local d = conn3(function() + c4 = true + end) + conn1:Fire() + conn2:Fire() + conn3:Fire() + if c1 and c2 and c3 and c4 then + print("Connection Test 2: Ok") + else + print("Connection Test 2: Error") + end + c3 = false + c4 = false + d:Destroy() + conn3:Fire() + if c3 and not(c4) then + print("Connection Test 3: Ok") + else + print("Connection Test 3: Error removing connection") + end + os.exit() -- End of tests end) runTest().OnError(function(...) print("Error:",...) From 593bfd0d8ccf02d69531226d2d1a38f081b178f6 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 5 Feb 2022 00:55:11 -0500 Subject: [PATCH 42/80] Threads now working, can create many more threads without a performance hit --- multi/init.lua | 603 +++++++++--------- multi/integration/lanesManager/extensions.lua | 8 +- multi/integration/lanesManager/init.lua | 7 +- multi/integration/loveManager/extensions.lua | 8 +- multi/integration/loveManager/init.lua | 2 +- multi/integration/loveManager/threads.lua | 2 +- multi/integration/lovrManager/extensions.lua | 8 +- multi/integration/lovrManager/threads.lua | 2 +- .../integration/networkManager/masterNode.lua | 4 +- .../integration/pesudoManager/extensions.lua | 2 +- multi/integration/pesudoManager/init.lua | 2 +- test4.lua | 42 +- 12 files changed, 319 insertions(+), 371 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 781f4f2..b3c7b30 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -35,6 +35,7 @@ end multi.Version = "15.2.0" multi.Name = "multi.root" multi.NIL = {Type="NIL"} +local NIL = multi.NIL multi.Mainloop = {} multi.Children = {} multi.Active = true @@ -286,9 +287,6 @@ function multi:newConnection(protect,func,kill) else function c:Fire(...) for i=#call_funcs,1,-1 do - if type(call_funcs[i])=="table" then - print(call_funcs[i].Parent.Type) - end call_funcs[i](...) if kill then table.remove(call_funcs,i) @@ -307,9 +305,6 @@ function multi:newConnection(protect,func,kill) end end function self:connect(func) - if type(func) == "table" then - print(debug.traceback()) - end table.insert(fast,func) end end @@ -388,9 +383,6 @@ function multi:newConnection(protect,func,kill) function c:connect(...)--func,name,num local tab = {...} local funcs={} - if type(tab[1])=="table" then - print(debug.traceback()) - end for i=1,#tab do if type(tab[i])=="function" then funcs[#funcs+1] = tab[i] @@ -714,12 +706,18 @@ function multi:newAlarm(set) return c end -function multi:newLoop(func) +function multi:newLoop(func,notime) local c=self:newBase() c.Type='loop' local start=clock() - function c:Act() - self.OnLoop:Fire(self,clock()-start) + if notime then + function c:Act() + self.OnLoop:Fire(self) + end + else + function c:Act() + self.OnLoop:Fire(self,clock()-start) + end end c.OnLoop = self:newConnection() function c:fastMode() @@ -735,7 +733,6 @@ function multi:newLoop(func) end function multi:newStep(start,reset,count,skip) - print(self.Type) local c=self:newBase() think=1 c.Type='step' @@ -829,7 +826,7 @@ function multi:newTLoop(func,set) end function multi:setTimeout(func,t) - multi:newThread(function() thread.sleep(t) func() end) + thread:newThread(function() thread.sleep(t) func() end) end function multi:newTStep(start,reset,count,set) @@ -882,7 +879,7 @@ local sthread function multi:scheduleJob(time,func) if not sthread then - sthread = multi:newThread("JobScheduler",function() + sthread = thread:newThread("JobScheduler",function() local time = os.date("*t", os.time()) local ready = false while true do @@ -930,7 +927,7 @@ function multi:newProcessor(name,nothread) c.Type = "process" c.Active = false or nothread c.Name = name or "" - c.process = self:newThread(function() + c.process = thread:newThread(function() while true do thread.hold(function() return c.Active end) __CurrentProcess = c @@ -957,8 +954,6 @@ function multi:newProcessor(name,nothread) function c:Destroy() self.OnObjectDestroyed:Fire(c) end - c:attachScheduler() - c.initThreads() return c end @@ -973,7 +968,7 @@ function multi.hold(func,opt) local proc = multi.getCurrentTask() proc:Pause() if type(func)=="number" then - self:newThread("Hold_func",function() + thread:newThread("Hold_func",function() thread.hold(func) death = true end) @@ -983,7 +978,7 @@ function multi.hold(func,opt) proc:Resume() else local rets - self:newThread("Hold_func",function() + thread:newThread("Hold_func",function() rets = {thread.hold(func,opt)} death = true end) @@ -996,7 +991,6 @@ function multi.hold(func,opt) end -- Threading stuff -local initT = false local threadCount = 0 local threadid = 0 thread.__threads = {} @@ -1006,6 +1000,7 @@ local dFunc = function() return true end thread.requests = {} local CMD = {} -- We will compare this special local local interval +local resume, status, create, yield, running = coroutine.resume, coroutine.status, coroutine.create, coroutine.yield, coroutine.running local t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none = 1, 2, 3, 4, 5, 6, 7 function thread.request(t,cmd,...) @@ -1025,9 +1020,9 @@ function thread.getRunningThread() end function thread._Requests() - local t = thread.requests[coroutine.running()] + local t = thread.requests[running()] if t then - thread.requests[coroutine.running()] = nil + thread.requests[running()] = nil local cmd,args = t[1],t[2] thread[cmd](unpack(args)) end @@ -1036,7 +1031,7 @@ end function thread.sleep(n) thread._Requests() thread.getRunningThread().lastSleep = clock() - return coroutine.yield(CMD, t_sleep, n or 1) + return yield(CMD, t_sleep, n or 1) end function thread.hold(n,opt) @@ -1045,27 +1040,27 @@ function thread.hold(n,opt) if type(opt)=="table" then interval = opt.interval if opt.cycles then - return coroutine.yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) + return yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) elseif opt.sleep then - return coroutine.yield(CMD, t_holdF, opt.sleep, n or dFunc, interval) + return yield(CMD, t_holdF, opt.sleep, n or dFunc, interval) elseif opt.skip then - return coroutine.yield(CMD, t_skip, opt.skip or 1, nil, interval) + return yield(CMD, t_skip, opt.skip or 1, nil, interval) end end if type(n) == "number" then thread.getRunningThread().lastSleep = clock() - return coroutine.yield(CMD, t_sleep, n or 0, nil, interval) + return yield(CMD, t_sleep, n or 0, nil, interval) elseif type(n) == "table" and n.Type == "connector" then local ready = false n(function() ready = true end) - return coroutine.yield(CMD, t_hold, function() + return yield(CMD, t_hold, function() return ready end) elseif type(n) == "function" then - return coroutine.yield(CMD, t_hold, n or dFunc, nil, interval) + return yield(CMD, t_hold, n or dFunc, nil, interval) else error("Invalid argument passed to thread.hold(...)!") end @@ -1073,17 +1068,17 @@ end function thread.holdFor(sec,n) thread._Requests() - return coroutine.yield(CMD, t_holdF, sec, n or dFunc) + return yield(CMD, t_holdF, sec, n or dFunc) end function thread.holdWithin(skip,n) thread._Requests() - return coroutine.yield(CMD, t_holdW, skip or 1, n or dFunc) + return yield(CMD, t_holdW, skip or 1, n or dFunc) end function thread.skip(n) thread._Requests() - return coroutine.yield(CMD, t_skip, n or 1) + return yield(CMD, t_skip, n or 1) end function thread.kill() @@ -1092,15 +1087,15 @@ end function thread.yield() thread._Requests() - return coroutine.yield(CMD, t_yield) + return yield(CMD, t_yield) end function thread.isThread() if _VERSION~="Lua 5.1" then - local a,b = coroutine.running() + local a,b = running() return not(b) else - return coroutine.running()~=nil + return running()~=nil end end @@ -1224,7 +1219,7 @@ end function thread:newFunction(func,holdme) return thread:newFunctionBase(function(...) - return multi.getCurrentProcess():newThread("TempThread",func,...) + return thread:newThread("TempThread",func,...) end,holdme)() end @@ -1235,295 +1230,271 @@ function multi.setEnv(func,env) return chunk end -function multi:attachScheduler() - local resume, status, create = coroutine.resume, coroutine.status, coroutine.create - local threads = {} - local startme = {} - local startme_len = 0 - function self:newThread(name,func,...) - self.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 - end - local c={nil,nil,nil,nil,nil,nil,nil} - local env = {self=c} - c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} - c.startArgs = {...} - c.ref={} - c.Name=name - c.thread=create(func) - c.sleep=1 - c.Type="thread" - c.TID = threadid - c.firstRunDone=false - c._isPaused = false - c.returns = {} - c.isError = false - c.OnError = self:newConnection(true,nil,true) - c.OnDeath = self:newConnection(true,nil,true) +local threads = {} +local startme = {} +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 + end + local c={nil,nil,nil,nil,nil,nil,nil} + local env = {self=c} + c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} + c.startArgs = {...} + c.ref={} + c.Name=name + c.thread=create(func) + c.sleep=1 + c.Type="thread" + c.TID = threadid + c.firstRunDone=false + c._isPaused = false + c.returns = {} + c.isError = false + c.OnError = multi:newConnection(true,nil,true) + c.OnDeath = multi:newConnection(true,nil,true) - function c:isPaused() - return self._isPaused - end + function c:isPaused() + return self._isPaused + end - local resumed = false - function c:Pause() - if not self._isPaused then - thread.request(self,"exec",function() - thread.hold(function() - return resumed - end) - resumed = false - self._isPaused = false + local resumed = false + function c:Pause() + if not self._isPaused then + thread.request(self,"exec",function() + thread.hold(function() + return resumed end) - self._isPaused = true - end - return self - end - - function c:Resume() - resumed = true - return self - end - - function c:Kill() - thread.request(self,"kill") - return self - end - - function c:Sleep(n) - thread.request(self,"exec",function() - thread.sleep(n) resumed = false + self._isPaused = false end) - return self + self._isPaused = true end - - function c:Hold(n,opt) - thread.request(self,"exec",function() - thread.hold(n,opt) - resumed = false - end) - return self - end - - c.Destroy = c.Kill - - table.insert(threads,c) - table.insert(startme,c) - startme_len = #startme - print("startme:",startme_len) - globalThreads[c] = self - if initT==false then - self.initThreads() - end - threadid = threadid + 1 - multi:create(c) - c.creationTime = os.clock() - return c + return self end - function self:newISOThread(name,func,_env,...) - self.OnLoad:Fire() - local func = func or name - local env = _env or {} - if not env.thread then - env.thread = thread - end - if not env.multi then - env.multi = self - end - if type(name) == "function" then - name = "Thread#"..threadCount - end - local func = isolateFunction(func,env) - return self:newThread(name,func) + function c:Resume() + resumed = true + return self end - function self.initThreads(justThreads) - initT = true - self.scheduler=self:newLoop():setName("multi.thread") - self.scheduler.Type="scheduler" - function self.scheduler:setStep(n) - self.skip=tonumber(n) or 24 - end - self.scheduler.skip=0 - local t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 - local r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 - local ret,_ - local task, thd, ref, ready - --[[ - if coroutine.running() ~= threads[i].thread then - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(coreads[i].thread,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - CheckRets(i) - end - ]] - -- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_conn, t_none <-- Order - local switch = { - function(th,co)--hold - if clock() - th.intervalR>=th.interval then - t0,t1,t2,t3,t4,t5,t6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = th.func() - if t0 then - if t0==self.NIL then t0 = nil end - th.task = t_none - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end - th.intervalR = clock() - end - end, - function(th,co)--sleep - if clock() - th.time>=th.sec then - th.task = t_none - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end - end, - function(th,co)--holdf - if clock() - th.intervalR>=th.interval then - t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() - if t0 then - if t0 then - if t0==self.NIL then t0 = nil end - th.task = t_none - end - th.task = t_none - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - elseif clock() - th.time>=th.sec then - th.task = t_none - t0 = nil - t1 = multi.TIMEOUT - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end - th.intervalR = clock() - end - end, - function(th,co)--skip - th.pos = th.pos + 1 - if th.count==th.pos then - th.task = t_none - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end - end, - function(th,co)--holdw - if clock() - th.intervalR>=th.interval then - th.pos = th.pos + 1 - t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() - if t0 then - if t0 then - if t0==self.NIL then t0 = nil end - th.task = t_none - end - th.task = "" - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - elseif th.count==th.pos then - th.task = t_none - t0 = nil - t1 = multi.TIMEOUT - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end - th.intervalR = clock() - end - end, - function(th,co)--yield - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end, - function() end--none - } - setmetatable(switch,{__index=function() return function() end end}) - local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order - function(th,arg1,arg2,arg3) - --print("hold",arg1,arg2,arg3) - --print(_,ret,r1,r2,r3,r4,r5) - th.func = arg1 - th.task = t_hold - th.interval = arg3 or 0 - th.intervalR = clock() - end, - function(th,arg1,arg2,arg3) - --print("sleep",arg1,arg2,arg3) - th.sec = arg1 - th.time = clock() - th.task = t_sleep - end, - function(th,arg1,arg2,arg3) - --print("holdf",arg1,arg2,arg3) - th.sec = arg1 - th.func = arg2 - th.task = t_holdF - th.time = clock() - th.interval = arg3 or 0 - th.intervalR = clock() - end, - function(th,arg1,arg2,arg3) - --print("skip",arg1,arg2,arg3) - th.count = arg1 - th.pos = 0 - th.task = t_skip - end, - function(th,arg1,arg2,arg3) - --print("holdw",arg1,arg2,arg3) - th.count = arg1 - th.pos = 0 - th.func = arg2 - th.task = t_holdW - th.time = clock() - th.interval = arg3 or 0 - th.intervalR = clock() - end, - function() end - } - setmetatable(cmds,{__index=function() return function() end end}) - local co_status = { - ["suspended"] = function(thd,ref,task) - switch[task](ref,thd) - cmds[r1](ref,r2,r3,r4,r5) - r1=nil r2=nil r3=nil r4=nil r5=nil - end, - ["normal"] = function(thd,ref) print("Normal Status") io.read() end, -- Not sure if I will handle this - ["running"] = function(thd,ref) print("Running Status") io.read() end, - ["dead"] = function(thd,ref,task,i) - if _ then - print("Death") - ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) - else - print("Error",ref,ret) - ref.OnError:Fire(ref,ret) - end - if i then - table.remove(threads,i) - else - for i,v in pairs(threads) do - if v.thread==thd then - table.remove(threads,i) - break - end - end - end - _=nil r1=nil r2=nil r3=nil r4=nil r5=nil - --self.setType(ref,self.DestroyedObj) - end, - } - self.scheduler:OnLoop(function(self) - for i=#threads,1,-1 do - -- First time setup for threads - for start = startme_len,1,-1 do - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(startme[start].thread,unpack(startme[start].startArgs)) - co_status[status(startme[startme_len].thread)](startme[startme_len].thread,startme[startme_len],t_none) --cmds[r1](startme[start],r2,r3,r4,r5) - startme[startme_len] = nil - startme_len = #startme - end - if threads[i] then - ref = threads[i] - task = ref.task - thd = ref.thread - ready = ref.__ready - co_status[status(thd)](thd,ref,task,i) - end - end + function c:Kill() + thread.request(self,"kill") + return self + end + + function c:Sleep(n) + thread.request(self,"exec",function() + thread.sleep(n) + resumed = false end) + return self end + + function c:Hold(n,opt) + thread.request(self,"exec",function() + thread.hold(n,opt) + resumed = false + end) + return self + end + + c.Destroy = c.Kill + + table.insert(threads,c) + table.insert(startme,c) + startme_len = #startme + globalThreads[c] = multi + threadid = threadid + 1 + multi:create(c) + c.creationTime = os.clock() + return c end +function thread:newISOThread(name,func,_env,...) + local func = func or name + local env = _env or {} + if not env.thread then + env.thread = thread + end + if not env.multi then + env.multi = multi + end + if type(name) == "function" then + name = "Thread#"..threadCount + end + local func = isolateFunction(func,env) + return thread:newThread(name,func) +end + +multi.newThread = thread.newThread +multi.newISOThread = thread.newISOThread + +local t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 +local r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 +local ret,_ +local task, thd, ref, ready +local switch = { + function(th,co)--hold + if clock() - th.intervalR>=th.interval then + t0,t1,t2,t3,t4,t5,t6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = th.func() + if t0 then + if t0==NIL then t0 = nil end + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + th.intervalR = clock() + end + end, + function(th,co)--sleep + if clock() - th.time>=th.sec then + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + end, + function(th,co)--holdf + if clock() - th.intervalR>=th.interval then + t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() + if t0 then + if t0 then + if t0==NIL then t0 = nil end + th.task = t_none + end + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + elseif clock() - th.time>=th.sec then + th.task = t_none + t0 = nil + t1 = multi.TIMEOUT + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + th.intervalR = clock() + end + end, + function(th,co)--skip + th.pos = th.pos + 1 + if th.count==th.pos then + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + end, + function(th,co)--holdw + if clock() - th.intervalR>=th.interval then + th.pos = th.pos + 1 + t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() + if t0 then + if t0 then + if t0==NIL then t0 = nil end + th.task = t_none + end + th.task = "" + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + elseif th.count==th.pos then + th.task = t_none + t0 = nil + t1 = multi.TIMEOUT + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + th.intervalR = clock() + end + end, + function(th,co)--yield + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end, + function() end--none +} +setmetatable(switch,{__index=function() return function() end end}) +local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order + function(th,arg1,arg2,arg3) + th.func = arg1 + th.task = t_hold + th.interval = arg3 or 0 + th.intervalR = clock() + end, + function(th,arg1,arg2,arg3) + th.sec = arg1 + th.time = clock() + th.task = t_sleep + end, + function(th,arg1,arg2,arg3) + th.sec = arg1 + th.func = arg2 + th.task = t_holdF + th.time = clock() + th.interval = arg3 or 0 + th.intervalR = clock() + end, + function(th,arg1,arg2,arg3) + th.count = arg1 + th.pos = 0 + th.task = t_skip + end, + function(th,arg1,arg2,arg3) + th.count = arg1 + th.pos = 0 + th.func = arg2 + th.task = t_holdW + th.time = clock() + th.interval = arg3 or 0 + th.intervalR = clock() + end, + function() end +} +setmetatable(cmds,{__index=function() return function() end end}) +local co_status = { + ["suspended"] = function(thd,ref,task) + switch[task](ref,thd) + cmds[r1](ref,r2,r3,r4,r5) + r1=nil r2=nil r3=nil r4=nil r5=nil + end, + ["normal"] = function(thd,ref) print("Normal Status") io.read() end, -- Not sure if I will handle this + ["running"] = function(thd,ref) print("Running Status") io.read() end, + ["dead"] = function(thd,ref,task,i) + if _ then + ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) + else + ref.OnError:Fire(ref,ret) + end + if i then + table.remove(threads,i) + else + for i,v in pairs(threads) do + if v.thread==thd then + table.remove(threads,i) + break + end + end + end + _=nil r1=nil r2=nil r3=nil r4=nil r5=nil + --self.setType(ref,self.DestroyedObj) + end, +} +local count = 0 +local handler = coroutine.wrap(function(self) + while true do + for start = startme_len,1,-1 do + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(startme[start].thread,unpack(startme[start].startArgs)) + co_status[status(startme[startme_len].thread)](startme[startme_len].thread,startme[startme_len],t_none) -- Make sure there was no error + startme[startme_len] = nil + startme_len = #startme + yield() + end + for i=#threads,1,-1 do + if threads[i] then + ref = threads[i] + task = ref.task + thd = ref.thread + ready = ref.__ready + co_status[status(thd)](thd,ref,task,i) + end + yield() + end + end +end) + + function multi:newService(func) -- Priority managed threads local c = {} c.Type = "service" @@ -1553,7 +1524,7 @@ function multi:newService(func) -- Priority managed threads task(ap) return c end - local th = self:newThread(function() + local th = thread:newThread(function() while true do process() end @@ -1636,6 +1607,7 @@ local function mainloop(self) ctask:Act() __CurrentProcess = self end + handler() end else return nil, "Already Running!" @@ -1665,6 +1637,7 @@ local function p_mainloop(self) end end end + handler() end else return nil, "Already Running!" @@ -1693,6 +1666,7 @@ function multi:uManager() multi.OnPreLoad:Fire() self.uManager=self.uManagerRef multi.OnLoad:Fire() + handler() end end @@ -1709,6 +1683,7 @@ function multi:uManagerRefP1() end end end + handler() end end @@ -1721,6 +1696,7 @@ function multi:uManagerRef() __CurrentTask:Act() __CurrentProcess = self end + handler() end end @@ -2026,5 +2002,4 @@ else multi.m.sentinel = newproxy(true) getmetatable(multi.m.sentinel).__gc = multi.m.onexit end -multi:attachScheduler() return multi diff --git a/multi/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua index c582c17..b8cdc70 100644 --- a/multi/integration/lanesManager/extensions.lua +++ b/multi/integration/lanesManager/extensions.lua @@ -113,7 +113,7 @@ function multi:newSystemThreadedJobQueue(n) end) end,holup),name end - multi:newThread("JobQueueManager",function() + thread:newthread("JobQueueManager",function() while true do local job = thread.hold(function() return queueReturn:pop() @@ -129,7 +129,7 @@ function multi:newSystemThreadedJobQueue(n) local clock = os.clock local ref = 0 setmetatable(_G,{__index = funcs}) - multi:newThread("JobHandler",function() + thread:newthread("JobHandler",function() while true do local dat = thread.hold(function() return queueJob:pop() @@ -141,7 +141,7 @@ function multi:newSystemThreadedJobQueue(n) queueReturn:push{jid, funcs[name](unpack(args)),queue} end end) - multi:newThread("DoAllHandler",function() + thread:newthread("DoAllHandler",function() while true do local dat = thread.hold(function() return doAll:peek() @@ -156,7 +156,7 @@ function multi:newSystemThreadedJobQueue(n) end end end) - multi:newThread("IdleHandler",function() + thread:newthread("IdleHandler",function() while true do thread.hold(function() return clock()-idle>3 diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index c350343..719379a 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -105,14 +105,11 @@ function multi.InitSystemThreadErrorHandler() return end started = true - multi:newThread("SystemThreadScheduler",function() + thread:newthread("SystemThreadScheduler",function() local threads = multi.SystemThreads while true do thread.sleep(.005) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough. local _,data = __ConsoleLinda:receive(0, "Q") - if data then - print(unpack(data)) - end for i = #threads, 1, -1 do local status = threads[i].thread.status local temp = threads[i] @@ -147,8 +144,6 @@ function multi.InitSystemThreadErrorHandler() end end end - end).OnError(function(...) - print(...) end) end diff --git a/multi/integration/loveManager/extensions.lua b/multi/integration/loveManager/extensions.lua index 9424902..24c7887 100644 --- a/multi/integration/loveManager/extensions.lua +++ b/multi/integration/loveManager/extensions.lua @@ -131,7 +131,7 @@ function multi:newSystemThreadedJobQueue(n) end) end,holup),name end - multi:newThread("jobManager",function() + thread:newthread("jobManager",function() while true do thread.yield() local dat = c.queueReturn:pop() @@ -155,7 +155,7 @@ function multi:newSystemThreadedJobQueue(n) local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") local registry = {} setmetatable(_G,{__index = funcs}) - multi:newThread("startUp",function() + thread:newthread("startUp",function() while true do thread.yield() local all = queueAll:peek() @@ -165,7 +165,7 @@ function multi:newSystemThreadedJobQueue(n) end end end) - multi:newThread("runner",function() + thread:newthread("runner",function() thread.sleep(.1) while true do thread.yield() @@ -187,7 +187,7 @@ function multi:newSystemThreadedJobQueue(n) end):OnError(function(...) error(...) end) - multi:newThread("Idler",function() + thread:newthread("Idler",function() while true do thread.yield() if clock()-lastProc> 2 then diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua index 5cc490d..583cdb5 100644 --- a/multi/integration/loveManager/init.lua +++ b/multi/integration/loveManager/init.lua @@ -122,7 +122,7 @@ function multi:newSystemThread(name,func,...) GLOBAL["__THREAD_"..c.ID] = {ID=c.ID,Name=c.name,Thread=c.thread} GLOBAL["__THREAD_COUNT"] = THREAD_ID THREAD_ID=THREAD_ID+1 - multi:newThread(function() + thread:newthread(function() while true do thread.yield() if c.stab["returns"] then diff --git a/multi/integration/loveManager/threads.lua b/multi/integration/loveManager/threads.lua index 97280c8..5d7a19e 100644 --- a/multi/integration/loveManager/threads.lua +++ b/multi/integration/loveManager/threads.lua @@ -167,7 +167,7 @@ if not ISTHREAD then local clock = os.clock local lastproc = clock() local queue = love.thread.getChannel("__CONSOLE__") - multi:newThread("consoleManager",function() + thread:newthread("consoleManager",function() while true do thread.yield() dat = queue:pop() diff --git a/multi/integration/lovrManager/extensions.lua b/multi/integration/lovrManager/extensions.lua index 36747ce..96b9557 100644 --- a/multi/integration/lovrManager/extensions.lua +++ b/multi/integration/lovrManager/extensions.lua @@ -129,7 +129,7 @@ function multi:newSystemThreadedJobQueue(n) end) end,holup),name end - multi:newThread("jobManager",function() + thread:newthread("jobManager",function() while true do thread.yield() local dat = c.queueReturn:pop() @@ -153,7 +153,7 @@ function multi:newSystemThreadedJobQueue(n) local queueAll = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueAll") local registry = {} setmetatable(_G,{__index = funcs}) - multi:newThread("startUp",function() + thread:newthread("startUp",function() while true do thread.yield() local all = queueAll:peek() @@ -163,7 +163,7 @@ function multi:newSystemThreadedJobQueue(n) end end end) - multi:newThread("runner",function() + thread:newthread("runner",function() thread.sleep(.1) while true do thread.yield() @@ -185,7 +185,7 @@ function multi:newSystemThreadedJobQueue(n) end):OnError(function(...) error(...) end) - multi:newThread("Idler",function() + thread:newthread("Idler",function() while true do thread.yield() if clock()-lastProc> 2 then diff --git a/multi/integration/lovrManager/threads.lua b/multi/integration/lovrManager/threads.lua index 49d5bb8..cab81d7 100644 --- a/multi/integration/lovrManager/threads.lua +++ b/multi/integration/lovrManager/threads.lua @@ -168,7 +168,7 @@ if not ISTHREAD then local clock = os.clock local lastproc = clock() local queue = lovr.thread.getChannel("__CONSOLE__") - multi:newThread("consoleManager",function() + thread:newthread("consoleManager",function() while true do thread.yield() dat = queue:pop() diff --git a/multi/integration/networkManager/masterNode.lua b/multi/integration/networkManager/masterNode.lua index df3eab7..25c2b07 100644 --- a/multi/integration/networkManager/masterNode.lua +++ b/multi/integration/networkManager/masterNode.lua @@ -72,7 +72,7 @@ function master:newNetworkThread(nodeName,func,...) local ret local nID = netID local conn = multi:newConnection() - multi:newThread(function() + thread:newthread(function() dat:addBlock{ args = args, func = func, @@ -143,7 +143,7 @@ function multi:newMasterNode(cd) else c:getNodesFromBroadcast() end - multi:newThread("CMDQueueProcessor",function() + thread:newthread("CMDQueueProcessor",function() while true do thread.skip(128) local data = table.remove(c._queue,1) diff --git a/multi/integration/pesudoManager/extensions.lua b/multi/integration/pesudoManager/extensions.lua index 9942d1d..3881f3f 100644 --- a/multi/integration/pesudoManager/extensions.lua +++ b/multi/integration/pesudoManager/extensions.lua @@ -125,7 +125,7 @@ function multi:newSystemThreadedJobQueue(n) end,holup),name end for i=1,c.cores do - multi:newThread("PesudoThreadedJobQueue_"..i,function() + thread:newthread("PesudoThreadedJobQueue_"..i,function() while true do thread.yield() if #jobs>0 then diff --git a/multi/integration/pesudoManager/init.lua b/multi/integration/pesudoManager/init.lua index 0cafe79..c93b049 100644 --- a/multi/integration/pesudoManager/init.lua +++ b/multi/integration/pesudoManager/init.lua @@ -70,7 +70,7 @@ function multi:newSystemThread(name,func,...) env[tab[i]] = _G[tab[i]] end --setmetatable(env,{__index=env}) - multi:newISOThread(name,func,env,...).OnError(function(self,msg) + thread:newISOThread(name,func,env,...).OnError(function(self,msg) print("ERROR:",msg) end) id = id + 1 diff --git a/test4.lua b/test4.lua index 2fcffe3..6c0b527 100644 --- a/test4.lua +++ b/test4.lua @@ -4,42 +4,20 @@ local multi,thread = require("multi"):init() Before AVG: 522386 Test 1 AVG: ]] -local sleep_for = 5 +local sleep_for = 1 local conn = multi:newConnection() +local test = {} local function bench(_,steps) - print("Steps/5s: "..steps) + print("Steps/1s: "..steps) os.exit() end -local ready = false -multi:newAlarm(3):OnRing(function() - conn:Fire() - ready = true -end) -multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) -multi:newThread("Thread 1",function() - while true do - thread.sleep(1) - print("Test 1") - thread.hold(conn) - print("Conn sleep test") - error("hi") - end -end).OnError(print) - -multi:newThread("Thread 2",function() - print("Thread 2") - return "it worked" -end):OnDeath(print):OnError(error) -multi:newThread("Thread 3",function() - thread.hold(function() - return ready +for i = 1,400 do + thread:newThread(function() + while true do + thread.sleep(.1) + end end) - print("Function test") - return "Yay we did it" -end).OnDeath(print) - --- multi.OnExit(function() --- print("Total: ".. a) --- end) +end +multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) multi:mainloop() \ No newline at end of file From 03cea2d71a9a4cf3c935481ba497feea5da36d98 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 5 Feb 2022 10:56:03 -0500 Subject: [PATCH 43/80] Processors are working nicely, mostly done with the library --- multi/init.lua | 10 ++++------ test4.lua | 9 ++++++--- tests/runtests.lua | 28 ++++++++++++++++------------ 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index b3c7b30..6b9d36f 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -919,8 +919,7 @@ end local sandcount = 1 function multi:newProcessor(name,nothread) local c = {} - setmetatable(c,{__index = self}) - local multi,thread = require("multi"):init() -- We need to capture the t in thread + setmetatable(c,{__index = multi}) local name = name or "Processor_"..sandcount sandcount = sandcount + 1 c.Mainloop = {} @@ -930,18 +929,17 @@ function multi:newProcessor(name,nothread) c.process = thread:newThread(function() while true do thread.hold(function() return c.Active end) - __CurrentProcess = c c:uManager() - __CurrentProcess = self end end) c.process.isProcessThread = true c.process.PID = sandcount c.OnError = c.process.OnError function c.run() - __CurrentProcess = c c:uManager() - __CurrentProcess = self + end + function c:AttachThread(t) + -- end function c.Start() c.Active = true diff --git a/test4.lua b/test4.lua index 6c0b527..fbf7b18 100644 --- a/test4.lua +++ b/test4.lua @@ -11,13 +11,16 @@ local function bench(_,steps) print("Steps/1s: "..steps) os.exit() end +proc = multi:newProcessor("Test") for i = 1,400 do - thread:newThread(function() + thread:newThread("Thread: "..i,function() while true do thread.sleep(.1) end end) end -multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) +proc:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) -multi:mainloop() \ No newline at end of file +while true do + proc.run() +end \ No newline at end of file diff --git a/tests/runtests.lua b/tests/runtests.lua index fdedeb9..2c45222 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -22,36 +22,38 @@ end ]] local multi, thread = require("multi"):init{priority=true} local good = false -multi:newAlarm(3):OnRing(function() +local proc = multi:newProcessor("Test") +proc:newAlarm(3):OnRing(function() good = true end) + runTest = thread:newFunction(function() local alarms,tsteps,steps,loops,tloops,updaters,events=false,0,0,0,0,0,false print("Testing Basic Features. If this fails most other features will probably not work!") - multi:newAlarm(2):OnRing(function(a) + proc:newAlarm(2):OnRing(function(a) alarms = true a:Destroy() end) - multi:newTStep(1,10,1,.1):OnStep(function(t) + proc:newTStep(1,10,1,.1):OnStep(function(t) tsteps = tsteps + 1 end).OnEnd(function(step) step:Destroy() end) - multi:newStep(1,10):OnStep(function(s) + proc:newStep(1,10):OnStep(function(s) steps = steps + 1 end).OnEnd(function(step) step:Destroy() end) - local loop = multi:newLoop(function(l) + local loop = proc:newLoop(function(l) loops = loops + 1 end) - multi:newTLoop(function(t) + proc:newTLoop(function(t) tloops = tloops + 1 end,.1) - local updater = multi:newUpdater(1):OnUpdate(function() + local updater = proc:newUpdater(1):OnUpdate(function() updaters = updaters + 1 end) - local event = multi:newEvent(function() + local event = proc:newEvent(function() return alarms end) event.OnEvent(function(evnt) @@ -108,9 +110,9 @@ runTest = thread:newFunction(function() else print("Connection Test 1: Ok") end - conn1 = multi:newConnection() - conn2 = multi:newConnection() - conn3 = multi:newConnection() + conn1 = proc:newConnection() + conn2 = proc:newConnection() + conn3 = proc:newConnection() local c1,c2,c3,c4 = false,false,false,false local a = conn1(function() c1 = true @@ -146,4 +148,6 @@ end) runTest().OnError(function(...) print("Error:",...) end) -multi:mainloop() \ No newline at end of file +while true do + proc.run() +end \ No newline at end of file From c14a4690694d11c03dab4e3b6274e282c88c7ee4 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 8 Feb 2022 22:40:38 -0500 Subject: [PATCH 44/80] Each processor has it's own thread handler, all processes now trigger the default thread handler. --- changes.md | 4 ++- multi/init.lua | 70 ++++++++++++++++++++++++++++++++++++++++---------- test4.lua | 37 +++++++++++++++++--------- 3 files changed, 84 insertions(+), 27 deletions(-) diff --git a/changes.md b/changes.md index 1d4e50f..e360ee8 100644 --- a/changes.md +++ b/changes.md @@ -28,6 +28,7 @@ Added: Changed: --- +- Moved `multi:newThread(...)` into the thread interface (`thread:newThread(...)`), code using `multi:newThread(...)` will still work. Also using `process:newThread(...)` binds the thread to the process, meaning if the process the thread is bound to is paused so is the thread. - multi:mainloop(~~settings~~)/multi:uManager(~~settings~~) no longer takes a settings argument, that has been moved to multi:init(settings) | Setting | Description | ---|--- @@ -105,10 +106,11 @@ Fixed: - Issue where gettasksdetails() would try to process a destroyed object causing it to crash - Issue with multi.hold() not pumping the mainloop and only the scheduler - ToDo: --- +- Work on network parallelism + # Update 15.1.0 - Hold the thread! diff --git a/multi/init.lua b/multi/init.lua index 6b9d36f..0a48e25 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -27,6 +27,7 @@ local mainloopActive = false local isRunning = false local clock = os.clock local thread = {} +local in_proc = false if not _G["$multi"] then _G["$multi"] = {multi=multi,thread=thread} @@ -926,31 +927,46 @@ function multi:newProcessor(name,nothread) c.Type = "process" c.Active = false or nothread c.Name = name or "" - c.process = thread:newThread(function() - while true do - thread.hold(function() return c.Active end) + c.pump = false + c.threads = {} + c.startme = {} + local handler = c:createHandler(c.threads,c.startme) + c.process = multi:newLoop(function() + if c.Active then + c.pump = true c:uManager() + handler() + c.pump = false end end) c.process.isProcessThread = true c.process.PID = sandcount c.OnError = c.process.OnError - function c.run() - c:uManager() + function c:newThread(name,func,...) + in_proc = c + local t = thread.newThread(c,name,func,...) + in_proc = false + return t end - function c:AttachThread(t) - -- + function c.run() + c.pump = true + c:uManager() + handler() + c.pump = false end function c.Start() + if nothread then return self end c.Active = true return self end function c.Stop() + if nothread then return self end c.Active = false return self end function c:Destroy() - self.OnObjectDestroyed:Fire(c) + c.Active = false + c.process:Destroy() end return c end @@ -1237,6 +1253,7 @@ function thread:newThread(name,func,...) if type(name) == "function" then name = "Thread#"..threadCount end + local c={nil,nil,nil,nil,nil,nil,nil} local env = {self=c} c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} @@ -1300,9 +1317,13 @@ function thread:newThread(name,func,...) end c.Destroy = c.Kill - - table.insert(threads,c) - table.insert(startme,c) + if self.Type=="process" then + table.insert(self.threads,c) + table.insert(self.startme,c) + else + table.insert(threads,c) + table.insert(startme,c) + end startme_len = #startme globalThreads[c] = multi threadid = threadid + 1 @@ -1469,7 +1490,6 @@ local co_status = { --self.setType(ref,self.DestroyedObj) end, } -local count = 0 local handler = coroutine.wrap(function(self) while true do for start = startme_len,1,-1 do @@ -1480,8 +1500,8 @@ local handler = coroutine.wrap(function(self) yield() end for i=#threads,1,-1 do - if threads[i] then - ref = threads[i] + ref = threads[i] + if ref then task = ref.task thd = ref.thread ready = ref.__ready @@ -1492,6 +1512,28 @@ local handler = coroutine.wrap(function(self) end end) +function multi:createHandler(threads,startme) + return coroutine.wrap(function(self) + while true do + for start = #startme,1,-1 do + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(startme[start].thread,unpack(startme[start].startArgs)) + co_status[status(startme[start].thread)](startme[start].thread,startme[start],t_none) -- Make sure there was no error + table.remove(startme) + yield() + end + for i=#threads,1,-1 do + ref = threads[i] + if ref then + task = ref.task + thd = ref.thread + ready = ref.__ready + co_status[status(thd)](thd,ref,task,i) + end + yield() + end + end + end) +end function multi:newService(func) -- Priority managed threads local c = {} diff --git a/test4.lua b/test4.lua index fbf7b18..a902fe2 100644 --- a/test4.lua +++ b/test4.lua @@ -9,18 +9,31 @@ local conn = multi:newConnection() local test = {} local function bench(_,steps) print("Steps/1s: "..steps) - os.exit() + --os.exit() end proc = multi:newProcessor("Test") -for i = 1,400 do - thread:newThread("Thread: "..i,function() - while true do - thread.sleep(.1) - end - end) -end -proc:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) -while true do - proc.run() -end \ No newline at end of file +proc.Start() + +thread:newThread(function() + thread.sleep(5) + proc.Stop() + thread.sleep(5) + proc.Start() +end) + +thread:newThread(function() + while true do + thread.sleep(1) + print("...") + end +end) + +proc:newThread(function() + while true do + thread.sleep(1) + print("Testing...") + end +end) + +multi:mainloop() \ No newline at end of file From 19ac257204c74066987af95fcc680a0798f48bf4 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 8 Feb 2022 22:47:16 -0500 Subject: [PATCH 45/80] Testing actions --- .github/workflows/run-tests.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/run-tests.yml diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..cf5ea94 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,21 @@ +name: Test code +on: [push] +jobs: + Explore-GitHub-Actions: + runs-on: ubuntu-latest + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" + - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." + - name: Check out repository code + uses: actions/checkout@v2 + - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + - name: List files in the repository + run: | + ls ${{ github.workspace }} + - run: echo "🍏 This job's status is ${{ job.status }}." + - name: Lua Action + uses: MilesChou/lua-action@v0.0.1 + run: echo "lua -v" + run: lua tests/runtests.lua From 3401a8ac610bf55ef34872a05dd0d353df763ac0 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 8 Feb 2022 22:48:27 -0500 Subject: [PATCH 46/80] fixing issue with actions --- .github/workflows/run-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index cf5ea94..02fa95d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -17,5 +17,5 @@ jobs: - run: echo "🍏 This job's status is ${{ job.status }}." - name: Lua Action uses: MilesChou/lua-action@v0.0.1 - run: echo "lua -v" - run: lua tests/runtests.lua + - run: echo "lua -v" + - run: lua tests/runtests.lua From 78cd15681a14c35a4bc4e4996c87f72799d10f62 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 8 Feb 2022 22:51:46 -0500 Subject: [PATCH 47/80] Removed actions for now --- .github/workflows/run-tests.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .github/workflows/run-tests.yml diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml deleted file mode 100644 index 02fa95d..0000000 --- a/.github/workflows/run-tests.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Test code -on: [push] -jobs: - Explore-GitHub-Actions: - runs-on: ubuntu-latest - steps: - - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" - - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - - name: Check out repository code - uses: actions/checkout@v2 - - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - - run: echo "🖥️ The workflow is now ready to test your code on the runner." - - name: List files in the repository - run: | - ls ${{ github.workspace }} - - run: echo "🍏 This job's status is ${{ job.status }}." - - name: Lua Action - uses: MilesChou/lua-action@v0.0.1 - - run: echo "lua -v" - - run: lua tests/runtests.lua From 2acce5001dc133182b33a066ae75b764beec892b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 9 Feb 2022 16:30:19 -0500 Subject: [PATCH 48/80] Small issue with functions attached to processes --- changes.md | 5 +++++ multi/init.lua | 5 +++++ test4.lua | 27 ++++++++++++--------------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/changes.md b/changes.md index e360ee8..ff8c81f 100644 --- a/changes.md +++ b/changes.md @@ -16,6 +16,9 @@ Added: - multi:newProcessor(name,nothread).run() - new function run to the processor object to +- multi:newProcessor(name,nothread):newFunction(func,holdme) + - Acts like thread:newFunction(), but binds the execution of that threaded function to the processor + - multi:newTLoop() member functions - `TLoop:Set(set)` - Sets the time to wait for the TLoop @@ -28,7 +31,9 @@ Added: Changed: --- +- Modified how threads are handled internally. This changes makes it so threads "regardless of amount" should not impact performance. What you do in the threads might. This change was made by internally only processing one thread per step per processor. If you have 10 processors that are all active expect one step to process 10 threads. However if one processor has 10 threads each step will only process one thread. Simply put each addition of a thread shouldn't impact performance as it did before. - Moved `multi:newThread(...)` into the thread interface (`thread:newThread(...)`), code using `multi:newThread(...)` will still work. Also using `process:newThread(...)` binds the thread to the process, meaning if the process the thread is bound to is paused so is the thread. + - multi:mainloop(~~settings~~)/multi:uManager(~~settings~~) no longer takes a settings argument, that has been moved to multi:init(settings) | Setting | Description | ---|--- diff --git a/multi/init.lua b/multi/init.lua index 0a48e25..e66ba71 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -948,6 +948,11 @@ function multi:newProcessor(name,nothread) in_proc = false return t end + function c:newFunction(func,holdme) + return thread:newFunctionBase(function(...) + return c:newThread("TempThread",func,...) + end,holdme)() + end function c.run() c.pump = true c:uManager() diff --git a/test4.lua b/test4.lua index a902fe2..6912c9c 100644 --- a/test4.lua +++ b/test4.lua @@ -15,25 +15,22 @@ proc = multi:newProcessor("Test") proc.Start() +local func = proc:newFunction(function(a,b,c) + print("Testing proc functions!",a,b,c) + for i=1,10 do + thread.sleep(1) + print("h1") + end + return true,"Smile" +end) + thread:newThread(function() - thread.sleep(5) + thread.sleep(3.1) proc.Stop() - thread.sleep(5) + thread.sleep(3) proc.Start() end) -thread:newThread(function() - while true do - thread.sleep(1) - print("...") - end -end) - -proc:newThread(function() - while true do - thread.sleep(1) - print("Testing...") - end -end) +func("Some","tests","needed") multi:mainloop() \ No newline at end of file From 3f046afaa12469ccb51d4113d1f202e476285309 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 9 Feb 2022 17:05:30 -0500 Subject: [PATCH 49/80] Fixed: missing a yield --- multi/init.lua | 27 ++++++++++++++++----------- test4.lua | 15 ++++++++++++++- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index e66ba71..520c2b0 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -925,18 +925,16 @@ function multi:newProcessor(name,nothread) sandcount = sandcount + 1 c.Mainloop = {} c.Type = "process" - c.Active = false or nothread + local Active = nothread or false c.Name = name or "" c.pump = false c.threads = {} c.startme = {} local handler = c:createHandler(c.threads,c.startme) c.process = multi:newLoop(function() - if c.Active then - c.pump = true + if Active then c:uManager() handler() - c.pump = false end end) c.process.isProcessThread = true @@ -954,23 +952,28 @@ function multi:newProcessor(name,nothread) end,holdme)() end function c.run() + if not Active then return end c.pump = true c:uManager() handler() c.pump = false + return c + end + function c.isActive() + return Active end function c.Start() - if nothread then return self end - c.Active = true - return self + print("Proc Start",mainloopActive) + Active = true + return c end function c.Stop() - if nothread then return self end - c.Active = false - return self + print("Proc Stop") + Active = false + return c end function c:Destroy() - c.Active = false + Active = false c.process:Destroy() end return c @@ -1514,6 +1517,7 @@ local handler = coroutine.wrap(function(self) end yield() end + yield() end end) @@ -1536,6 +1540,7 @@ function multi:createHandler(threads,startme) end yield() end + yield() end end) end diff --git a/test4.lua b/test4.lua index 6912c9c..1cfe637 100644 --- a/test4.lua +++ b/test4.lua @@ -15,6 +15,17 @@ proc = multi:newProcessor("Test") proc.Start() +multi:newTLoop(function() + +end,1) + +-- thread:newThread(function() +-- while true do +-- thread.sleep(1) +-- print("Proc: ".. tostring(proc.isActive())) +-- end +-- end) + local func = proc:newFunction(function(a,b,c) print("Testing proc functions!",a,b,c) for i=1,10 do @@ -31,6 +42,8 @@ thread:newThread(function() proc.Start() end) -func("Some","tests","needed") + + +func("Some","tests","needed").connect(print) multi:mainloop() \ No newline at end of file From 264867a0da565d1d7ab7769b5b1b2610742eb22e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 9 Feb 2022 19:29:06 -0500 Subject: [PATCH 50/80] Fixed issue with threads not returning values properly --- multi/init.lua | 27 ++++++++++++++------------- test4.lua | 20 +++++--------------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 520c2b0..43407a2 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1207,7 +1207,7 @@ function thread:newFunctionBase(generator,holdme) } end local t = generator(...) - t.OnDeath(function(self,status,...) rets = {...} end) + t.OnDeath(function(...) rets = {...} end) t.OnError(function(self,e) err = e end) if holdme then return wait() @@ -1223,7 +1223,7 @@ function thread:newFunctionBase(generator,holdme) end, connect = function(f) local tempConn = multi:newConnection(true) - t.OnDeath(function(self,status,...) if f then f(...) else tempConn:Fire(...) end end) + t.OnDeath(function(...) if f then f(...) else tempConn:Fire(...) end end) t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) return tempConn end @@ -1325,6 +1325,7 @@ function thread:newThread(name,func,...) end c.Destroy = c.Kill + if self.Type=="process" then table.insert(self.threads,c) table.insert(self.startme,c) @@ -1332,6 +1333,7 @@ function thread:newThread(name,func,...) table.insert(threads,c) table.insert(startme,c) end + startme_len = #startme globalThreads[c] = multi threadid = threadid + 1 @@ -1476,33 +1478,32 @@ local co_status = { cmds[r1](ref,r2,r3,r4,r5) r1=nil r2=nil r3=nil r4=nil r5=nil end, - ["normal"] = function(thd,ref) print("Normal Status") io.read() end, -- Not sure if I will handle this - ["running"] = function(thd,ref) print("Running Status") io.read() end, - ["dead"] = function(thd,ref,task,i) + ["normal"] = function(thd,ref) end, + ["running"] = function(thd,ref) end, + ["dead"] = function(thd,ref,task,i,th) if _ then ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) else ref.OnError:Fire(ref,ret) end if i then - table.remove(threads,i) + table.remove(th,i) else - for i,v in pairs(threads) do + for i,v in pairs(th) do if v.thread==thd then - table.remove(threads,i) + table.remove(th,i) break end end end _=nil r1=nil r2=nil r3=nil r4=nil r5=nil - --self.setType(ref,self.DestroyedObj) end, } local handler = coroutine.wrap(function(self) while true do for start = startme_len,1,-1 do _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(startme[start].thread,unpack(startme[start].startArgs)) - co_status[status(startme[startme_len].thread)](startme[startme_len].thread,startme[startme_len],t_none) -- Make sure there was no error + co_status[status(startme[startme_len].thread)](startme[startme_len].thread,startme[startme_len],t_none,nil,threads) -- Make sure there was no error startme[startme_len] = nil startme_len = #startme yield() @@ -1513,7 +1514,7 @@ local handler = coroutine.wrap(function(self) task = ref.task thd = ref.thread ready = ref.__ready - co_status[status(thd)](thd,ref,task,i) + co_status[status(thd)](thd,ref,task,i,threads) end yield() end @@ -1526,7 +1527,7 @@ function multi:createHandler(threads,startme) while true do for start = #startme,1,-1 do _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(startme[start].thread,unpack(startme[start].startArgs)) - co_status[status(startme[start].thread)](startme[start].thread,startme[start],t_none) -- Make sure there was no error + co_status[status(startme[start].thread)](startme[start].thread,startme[start],t_none,nil,threads) -- Make sure there was no error table.remove(startme) yield() end @@ -1536,7 +1537,7 @@ function multi:createHandler(threads,startme) task = ref.task thd = ref.thread ready = ref.__ready - co_status[status(thd)](thd,ref,task,i) + co_status[status(thd)](thd,ref,task,i,threads) end yield() end diff --git a/test4.lua b/test4.lua index 1cfe637..54c6ebe 100644 --- a/test4.lua +++ b/test4.lua @@ -27,23 +27,13 @@ end,1) -- end) local func = proc:newFunction(function(a,b,c) - print("Testing proc functions!",a,b,c) - for i=1,10 do - thread.sleep(1) - print("h1") - end - return true,"Smile" + print("Testing proc functions!") + error("Testing") + return "Please", "Smile", 123 end) -thread:newThread(function() - thread.sleep(3.1) - proc.Stop() - thread.sleep(3) - proc.Start() +func("Some","tests","needed").connect(function(a,b,c) + print("Return",a,b,c) end) - - -func("Some","tests","needed").connect(print) - multi:mainloop() \ No newline at end of file From e9a0e7bbf78649cbbb39719b1e6a42e42e60d380 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 9 Feb 2022 22:19:27 -0500 Subject: [PATCH 51/80] Fixed an issue with the new thread namespace --- multi/compat/love2d.lua | 273 ----------------- multi/compat/lovr.lua | 281 ------------------ multi/integration/lanesManager/extensions.lua | 8 +- multi/integration/lanesManager/init.lua | 2 +- multi/integration/loveManager/extensions.lua | 8 +- multi/integration/loveManager/init.lua | 4 +- multi/integration/loveManager/threads.lua | 2 +- multi/integration/lovrManager/extensions.lua | 8 +- multi/integration/lovrManager/threads.lua | 2 +- test4.lua | 2 +- 10 files changed, 18 insertions(+), 572 deletions(-) delete mode 100644 multi/compat/love2d.lua delete mode 100644 multi/compat/lovr.lua diff --git a/multi/compat/love2d.lua b/multi/compat/love2d.lua deleted file mode 100644 index 73110e3..0000000 --- a/multi/compat/love2d.lua +++ /dev/null @@ -1,273 +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. -]] -if table.unpack then - unpack=table.unpack -end -function table.val_to_str ( v ) - if "string" == type( v ) then - v = string.gsub( v, "\n", "\\n" ) - if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then - return "'" .. v .. "'" - end - return '"' .. string.gsub(v,'"', '\\"' ) .. '"' - else - return "table" == type( v ) and table.tostring( v ) or - tostring( v ) - end -end - -function table.key_to_str ( k ) - if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then - return k - else - return "[" .. table.val_to_str( k ) .. "]" - end -end - -function table.tostring( tbl ) - local result, done = {}, {} - for k, v in ipairs( tbl ) do - table.insert( result, table.val_to_str( v ) ) - done[ k ] = true - end - for k, v in pairs( tbl ) do - if not done[ k ] then - table.insert( result, - table.key_to_str( k ) .. "=" .. table.val_to_str( v ) ) - end - end - return "{" .. table.concat( result, "," ) .. "}" -end -function table.merge(t1, t2) - t1,t2= t1 or {},t2 or {} - for k,v in pairs(t2) do - if type(v) == "table" then - if type(t1[k] or false) == "table" then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end -Library={} -function Library.inject(lib,dat,arg) - if type(lib)=="table" then - if type(dat)=="table" then - table.merge(lib,dat) - elseif type(dat)=="string" then - if lib.Version and dat:match("(%d-)%.(%d-)%.(%d-)") then - lib.Version={dat:match("(%d+)%.(%d+)%.(%d+)")} - elseif dat=="meta" and type(arg)=="table" then - local _mt=getmetatable(lib) or {} - local mt={} - table.merge(mt,arg) - table.merge(_mt,mt) - setmetatable(lib,_mt) - elseif dat=="compat" then - lib["getVersion"]=function(self) return self.Version[1].."."..self.Version[2].."."..self.Version[3] end - if not lib.Version then - lib.Version={1,0,0} - end - elseif dat=="inhert" then - if not(lib["!%"..arg.."%!"]) then print("Wrong Password!!") return end - lib["!%"..arg.."%!"].__index=lib["!!%"..arg.."%!!"] - end - elseif type(dat)=="function" then - for i,v in pairs(lib) do - dat(lib,i,v) - end - end - elseif type(lib)=="function" or type(lib)=="userdata" then - if lib==unpack then - print("function unpack cannot yet be injected!") - return unpack - elseif lib==pairs then - print("function pairs cannot yet be injected!") - return lib - elseif lib==ipairs then - print("function ipairs cannot yet be injected!") - return lib - elseif lib==type then - print("function type cannot yet be injected!") - return lib - end - temp={} - local mt={ - __call=function(t,...) - local consume,MainRet,init={},{},{...} - local tt={} - for i=1,#t.__Link do - tt={} - if t.__Link[i]==t.__Main then - if #consume~=0 then - MainRet={t.__Link[i](unpack(consume))} - else - MainRet={t.__Link[i](unpack(init))} - end - else - if i==1 then - consume=(t.__Link[i](unpack(init))) - else - if type(MainRet)=="table" then - table.merge(tt,MainRet) - end - if type(consume)=="table" then - table.merge(tt,consume) - end - consume={t.__Link[i](unpack(tt))} - end - if i==#t.__Link then - return unpack(consume) - end - if consume then if consume[0]=="\1\7\6\3\2\99\125" then consume[0]=nil return unpack(consume) end end - end - end - if type(MainRet)=="table" then - table.merge(tt,MainRet) - end - if type(consume)=="table" then - table.merge(tt,consume) - end - return unpack(tt) - end, - } - temp.__Link={lib} - temp.__Main=lib - temp.__self=temp - function temp:inject(func,i) - if i then - table.insert(self.__Link,i,func) - else - table.insert(self.__Link,func) - end - end - function temp:consume(func) - for i=1,#self.__Link do - if self.__Link[i]==self.__Main then - self.__Link[i]=func - self.__self.__Main=func - return true - end - end - return false - end - setmetatable(temp,mt) - return temp - else - return "arg1 must be a table or a function" - end -end -function Library.convert(...) - local temp,rets={...},{} - for i=1,#temp do - if type(temp[i])=="function" then - table.insert(rets,Library.inject(temp[i])) - else - error("Takes only functions and returns in order from functions given. arg # "..i.." is not a function!!! It is a "..type(temp[i])) - end - end - return unpack(rets) -end - -local link={MainLibrary=Library} -Library.inject(Library,"meta",{ - __Link=link, - __call=function(self,func) func(link) end, -}) -local multi, thread = require("multi").init() -os.sleep = love.timer.sleep -multi.drawF = {} -function multi:onDraw(func, i) - i = i or 1 - table.insert(self.drawF, i, func) -end -multi.OnKeyPressed = multi:newConnection() -multi.OnKeyReleased = multi:newConnection() -multi.OnMousePressed = multi:newConnection() -multi.OnMouseReleased = multi:newConnection() -multi.OnMouseWheelMoved = multi:newConnection() -multi.OnMouseMoved = multi:newConnection() -multi.OnDraw = multi:newConnection() -multi.OnTextInput = multi:newConnection() -multi.OnUpdate = multi:newConnection() -multi.OnQuit = multi:newConnection() -multi.OnPreLoad(function() - local function Hook(func, conn) - if love[func] ~= nil then - love[func] = Library.convert(love[func]) - love[func]:inject(function(...) - conn:Fire(...) - return {...} - end,1) - elseif love[func] == nil then - love[func] = function(...) - conn:Fire(...) - end - end - end - Hook("quit", multi.OnQuit) - Hook("keypressed", multi.OnKeyPressed) - Hook("keyreleased", multi.OnKeyReleased) - Hook("mousepressed", multi.OnMousePressed) - Hook("mousereleased", multi.OnMouseReleased) - Hook("wheelmoved", multi.OnMouseWheelMoved) - Hook("mousemoved", multi.OnMouseMoved) - Hook("draw", multi.OnDraw) - Hook("textinput", multi.OnTextInput) - Hook("update", multi.OnUpdate) - multi.OnDraw(function() - for i = 1, #multi.drawF do - love.graphics.setColor(255, 255, 255, 255) - multi.drawF[i]() - end - end) -end) - -function multi:loveloop(light) - local link - link = multi:newThread(function() - local mainloop = love.run() - while true do - thread.yield() - pcall(mainloop) - end - end).OnError(function(...) - print(...) - end) - if light==false then - multi:mainloop() - else - multi:lightloop() - end -end - -multi.OnQuit(function() - multi.Stop() - love.event.quit() -end) -return multi diff --git a/multi/compat/lovr.lua b/multi/compat/lovr.lua deleted file mode 100644 index 8c18b37..0000000 --- a/multi/compat/lovr.lua +++ /dev/null @@ -1,281 +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. -]] -if table.unpack then - unpack=table.unpack -end -function table.val_to_str ( v ) - if "string" == type( v ) then - v = string.gsub( v, "\n", "\\n" ) - if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then - return "'" .. v .. "'" - end - return '"' .. string.gsub(v,'"', '\\"' ) .. '"' - else - return "table" == type( v ) and table.tostring( v ) or - tostring( v ) - end -end - -function table.key_to_str ( k ) - if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then - return k - else - return "[" .. table.val_to_str( k ) .. "]" - end -end - -function table.tostring( tbl ) - local result, done = {}, {} - for k, v in ipairs( tbl ) do - table.insert( result, table.val_to_str( v ) ) - done[ k ] = true - end - for k, v in pairs( tbl ) do - if not done[ k ] then - table.insert( result, - table.key_to_str( k ) .. "=" .. table.val_to_str( v ) ) - end - end - return "{" .. table.concat( result, "," ) .. "}" -end -function table.merge(t1, t2) - t1,t2= t1 or {},t2 or {} - for k,v in pairs(t2) do - if type(v) == "table" then - if type(t1[k] or false) == "table" then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end -Library={} -function Library.inject(lib,dat,arg) - if type(lib)=="table" then - if type(dat)=="table" then - table.merge(lib,dat) - elseif type(dat)=="string" then - if lib.Version and dat:match("(%d-)%.(%d-)%.(%d-)") then - lib.Version={dat:match("(%d+)%.(%d+)%.(%d+)")} - elseif dat=="meta" and type(arg)=="table" then - local _mt=getmetatable(lib) or {} - local mt={} - table.merge(mt,arg) - table.merge(_mt,mt) - setmetatable(lib,_mt) - elseif dat=="compat" then - lib["getVersion"]=function(self) return self.Version[1].."."..self.Version[2].."."..self.Version[3] end - if not lib.Version then - lib.Version={1,0,0} - end - elseif dat=="inhert" then - if not(lib["!%"..arg.."%!"]) then print("Wrong Password!!") return end - lib["!%"..arg.."%!"].__index=lib["!!%"..arg.."%!!"] - end - elseif type(dat)=="function" then - for i,v in pairs(lib) do - dat(lib,i,v) - end - end - elseif type(lib)=="function" or type(lib)=="userdata" then - if lib==unpack then - print("function unpack cannot yet be injected!") - return unpack - elseif lib==pairs then - print("function pairs cannot yet be injected!") - return lib - elseif lib==ipairs then - print("function ipairs cannot yet be injected!") - return lib - elseif lib==type then - print("function type cannot yet be injected!") - return lib - end - temp={} - local mt={ - __call=function(t,...) - local consume,MainRet,init={},{},{...} - local tt={} - for i=1,#t.__Link do - tt={} - if t.__Link[i]==t.__Main then - if #consume~=0 then - MainRet={t.__Link[i](unpack(consume))} - else - MainRet={t.__Link[i](unpack(init))} - end - else - if i==1 then - consume=(t.__Link[i](unpack(init))) - else - if type(MainRet)=="table" then - table.merge(tt,MainRet) - end - if type(consume)=="table" then - table.merge(tt,consume) - end - consume={t.__Link[i](unpack(tt))} - end - if i==#t.__Link then - return unpack(consume) - end - if consume then if consume[0]=="\1\7\6\3\2\99\125" then consume[0]=nil return unpack(consume) end end - end - end - if type(MainRet)=="table" then - table.merge(tt,MainRet) - end - if type(consume)=="table" then - table.merge(tt,consume) - end - return unpack(tt) - end, - } - temp.__Link={lib} - temp.__Main=lib - temp.__self=temp - function temp:inject(func,i) - if i then - table.insert(self.__Link,i,func) - else - table.insert(self.__Link,func) - end - end - function temp:consume(func) - for i=1,#self.__Link do - if self.__Link[i]==self.__Main then - self.__Link[i]=func - self.__self.__Main=func - return true - end - end - return false - end - setmetatable(temp,mt) - return temp - else - return "arg1 must be a table or a function" - end -end -function Library.convert(...) - local temp,rets={...},{} - for i=1,#temp do - if type(temp[i])=="function" then - table.insert(rets,Library.inject(temp[i])) - else - error("Takes only functions and returns in order from functions given. arg # "..i.." is not a function!!! It is a "..type(temp[i])) - end - end - return unpack(rets) -end - -local link={MainLibrary=Library} -Library.inject(Library,"meta",{ - __Link=link, - __call=function(self,func) func(link) end, -}) -local multi, thread = require("multi").init() -os.sleep = lovr.timer.sleep -multi.drawF = {} -function multi:onDraw(func, i) - i = i or 1 - table.insert(self.drawF, i, func) -end -multi.OnKeyPressed = multi:newConnection() -multi.OnKeyReleased = multi:newConnection() -multi.OnErrHand = multi:newConnection() -multi.OnFocus = multi:newConnection() -multi.OnLoad = multi:newConnection() -multi.OnLog = multi:newConnection() -multi.OnPermission = multi:newConnection() -multi.OnResize = multi:newConnection() -multi.OnRestart = multi:newConnection() -multi.OnThreadError = multi:newConnection() -multi.OnDraw = multi:newConnection() -multi.OnTextInput = multi:newConnection() -multi.OnUpdate = multi:newConnection() -multi.OnQuit = multi:newConnection() -multi.OnPreLoad(function() - local function Hook(func, conn) - if lovr[func] ~= nil then - lovr[func] = Library.convert(lovr[func]) - lovr[func]:inject(function(...) - conn:Fire(...) - return {...} - end,1) - elseif lovr[func] == nil then - lovr[func] = function(...) - conn:Fire(...) - end - end - end - Hook("quit", multi.OnQuit) - Hook("keypressed", multi.OnKeyPressed) - Hook("keyreleased", multi.OnKeyReleased) - Hook("focus", multi.OnFocus) - Hook("log", multi.OnLog) - Hook("errhand", multi.OnErrHand) - Hook("load", multi.OnLoad) - Hook("draw", multi.OnDraw) - Hook("textinput", multi.OnTextInput) - Hook("update", multi.OnUpdate) - Hook("permission", multi.OnPermission) - Hook("resize", multi.OnResize) - Hook("restart", multi.OnRestart) - Hook("threaderror", multi.OnThreadError) - multi.OnDraw(function() - for i = 1, #multi.drawF do - lovr.graphics.setColor(255, 255, 255, 255) - multi.drawF[i]() - end - end) -end) - -function multi:lovrloop(light) - local link - link = multi:newThread(function() - local mainloop = lovr.run() - while true do - thread.yield() - pcall(mainloop) - end - end).OnError(function(...) - print(...) - end) - if light==false then - multi:mainloop() - else - multi:lightloop() - end -end - -multi.OnQuit(function() - multi.Stop() - lovr.event.quit() -end) -return multi diff --git a/multi/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua index b8cdc70..198e551 100644 --- a/multi/integration/lanesManager/extensions.lua +++ b/multi/integration/lanesManager/extensions.lua @@ -113,7 +113,7 @@ function multi:newSystemThreadedJobQueue(n) end) end,holup),name end - thread:newthread("JobQueueManager",function() + thread:newThread("JobQueueManager",function() while true do local job = thread.hold(function() return queueReturn:pop() @@ -129,7 +129,7 @@ function multi:newSystemThreadedJobQueue(n) local clock = os.clock local ref = 0 setmetatable(_G,{__index = funcs}) - thread:newthread("JobHandler",function() + thread:newThread("JobHandler",function() while true do local dat = thread.hold(function() return queueJob:pop() @@ -141,7 +141,7 @@ function multi:newSystemThreadedJobQueue(n) queueReturn:push{jid, funcs[name](unpack(args)),queue} end end) - thread:newthread("DoAllHandler",function() + thread:newThread("DoAllHandler",function() while true do local dat = thread.hold(function() return doAll:peek() @@ -156,7 +156,7 @@ function multi:newSystemThreadedJobQueue(n) end end end) - thread:newthread("IdleHandler",function() + thread:newThread("IdleHandler",function() while true do thread.hold(function() return clock()-idle>3 diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 719379a..9c21d5a 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -105,7 +105,7 @@ function multi.InitSystemThreadErrorHandler() return end started = true - thread:newthread("SystemThreadScheduler",function() + thread:newThread("SystemThreadScheduler",function() local threads = multi.SystemThreads while true do thread.sleep(.005) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough. diff --git a/multi/integration/loveManager/extensions.lua b/multi/integration/loveManager/extensions.lua index 24c7887..cf6beb2 100644 --- a/multi/integration/loveManager/extensions.lua +++ b/multi/integration/loveManager/extensions.lua @@ -131,7 +131,7 @@ function multi:newSystemThreadedJobQueue(n) end) end,holup),name end - thread:newthread("jobManager",function() + thread:newThread("jobManager",function() while true do thread.yield() local dat = c.queueReturn:pop() @@ -155,7 +155,7 @@ function multi:newSystemThreadedJobQueue(n) local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") local registry = {} setmetatable(_G,{__index = funcs}) - thread:newthread("startUp",function() + thread:newThread("startUp",function() while true do thread.yield() local all = queueAll:peek() @@ -165,7 +165,7 @@ function multi:newSystemThreadedJobQueue(n) end end end) - thread:newthread("runner",function() + thread:newThread("runner",function() thread.sleep(.1) while true do thread.yield() @@ -187,7 +187,7 @@ function multi:newSystemThreadedJobQueue(n) end):OnError(function(...) error(...) end) - thread:newthread("Idler",function() + thread:newThread("Idler",function() while true do thread.yield() if clock()-lastProc> 2 then diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua index 583cdb5..6c94531 100644 --- a/multi/integration/loveManager/init.lua +++ b/multi/integration/loveManager/init.lua @@ -37,7 +37,7 @@ GLOBAL = THREAD.getGlobal() multi, thread = require("multi").init() stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} ]] -local multi, thread = require("multi.compat.love2d"):init() +local multi, thread = require("multi"):init() local THREAD = {} __THREADID__ = 0 __THREADNAME__ = "MainThread" @@ -122,7 +122,7 @@ function multi:newSystemThread(name,func,...) GLOBAL["__THREAD_"..c.ID] = {ID=c.ID,Name=c.name,Thread=c.thread} GLOBAL["__THREAD_COUNT"] = THREAD_ID THREAD_ID=THREAD_ID+1 - thread:newthread(function() + thread:newThread(function() while true do thread.yield() if c.stab["returns"] then diff --git a/multi/integration/loveManager/threads.lua b/multi/integration/loveManager/threads.lua index 5d7a19e..8886be7 100644 --- a/multi/integration/loveManager/threads.lua +++ b/multi/integration/loveManager/threads.lua @@ -167,7 +167,7 @@ if not ISTHREAD then local clock = os.clock local lastproc = clock() local queue = love.thread.getChannel("__CONSOLE__") - thread:newthread("consoleManager",function() + thread:newThread("consoleManager",function() while true do thread.yield() dat = queue:pop() diff --git a/multi/integration/lovrManager/extensions.lua b/multi/integration/lovrManager/extensions.lua index 96b9557..7032b1d 100644 --- a/multi/integration/lovrManager/extensions.lua +++ b/multi/integration/lovrManager/extensions.lua @@ -129,7 +129,7 @@ function multi:newSystemThreadedJobQueue(n) end) end,holup),name end - thread:newthread("jobManager",function() + thread:newThread("jobManager",function() while true do thread.yield() local dat = c.queueReturn:pop() @@ -153,7 +153,7 @@ function multi:newSystemThreadedJobQueue(n) local queueAll = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueAll") local registry = {} setmetatable(_G,{__index = funcs}) - thread:newthread("startUp",function() + thread:newThread("startUp",function() while true do thread.yield() local all = queueAll:peek() @@ -163,7 +163,7 @@ function multi:newSystemThreadedJobQueue(n) end end end) - thread:newthread("runner",function() + thread:newThread("runner",function() thread.sleep(.1) while true do thread.yield() @@ -185,7 +185,7 @@ function multi:newSystemThreadedJobQueue(n) end):OnError(function(...) error(...) end) - thread:newthread("Idler",function() + thread:newThread("Idler",function() while true do thread.yield() if clock()-lastProc> 2 then diff --git a/multi/integration/lovrManager/threads.lua b/multi/integration/lovrManager/threads.lua index cab81d7..6a95a1e 100644 --- a/multi/integration/lovrManager/threads.lua +++ b/multi/integration/lovrManager/threads.lua @@ -168,7 +168,7 @@ if not ISTHREAD then local clock = os.clock local lastproc = clock() local queue = lovr.thread.getChannel("__CONSOLE__") - thread:newthread("consoleManager",function() + thread:newThread("consoleManager",function() while true do thread.yield() dat = queue:pop() diff --git a/test4.lua b/test4.lua index 54c6ebe..21dba20 100644 --- a/test4.lua +++ b/test4.lua @@ -36,4 +36,4 @@ func("Some","tests","needed").connect(function(a,b,c) print("Return",a,b,c) end) -multi:mainloop() \ No newline at end of file +multi:mainloop() From 48bba84c084d14b5a961a4160baa3884c3167849 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 13 Feb 2022 00:16:00 -0500 Subject: [PATCH 52/80] Fix issue where I forgot to implement the handler for thread.yield --- multi/init.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/multi/init.lua b/multi/init.lua index 43407a2..b7e6ae8 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1469,6 +1469,9 @@ local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_no th.interval = arg3 or 0 th.intervalR = clock() end, + function(th,arg1,arg2,arg3) + th.task = t_yield + end, function() end } setmetatable(cmds,{__index=function() return function() end end}) From 72e24e8a9bcab4547bcb8bbe177ce41a36ad6d45 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 14 Feb 2022 18:00:02 -0500 Subject: [PATCH 53/80] Added connection:hasConnections() --- multi/init.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/multi/init.lua b/multi/init.lua index b7e6ae8..d234e52 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -242,6 +242,9 @@ function multi:newConnection(protect,func,kill) 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 From 04d5500374fed673ce4392753a3d30d61faaaa72 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 15 Feb 2022 15:11:28 -0500 Subject: [PATCH 54/80] Fixed issue with the new thread scheduler(again) connections and hold get a buff --- changes.md | 44 ++++++++++++++++++++++ multi/init.lua | 56 ++++++++++++++++----------- test.lua | 100 ++++--------------------------------------------- 3 files changed, 85 insertions(+), 115 deletions(-) diff --git a/changes.md b/changes.md index ff8c81f..4ed1957 100644 --- a/changes.md +++ b/changes.md @@ -31,6 +31,50 @@ Added: Changed: --- +- `thread.hold(connectionObj)` now passes the returns of that connection to `thread.hold()`! See Exampe below: + ```lua + multi, thread = require("multi"):init() + + func = thread:newFunction(function(count) + local a = 0 + while true do + a = a + 1 + thread.sleep(.1) + thread.pushStatus(a,count) + if a == count then break end + end + return "Done", 1, 2, 3 + end) + + thread:newThread("test",function() + local ret = func(10) + ret.OnStatus(function(part,whole) + print("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") + end) + print("Status:",thread.hold(ret.OnReturn)) + print("Function Done!") + os.exit() + end).OnError(function(...) + print("Error:",...) + end) + + multi:mainloop() + ``` + Output: + ``` + Ret1: 10% + Ret1: 20% + Ret1: 30% + Ret1: 40% + Ret1: 50% + Ret1: 60% + Ret1: 70% + Ret1: 80% + Ret1: 90% + Ret1: 100% + Status: Done 1 2 3 nil nil nil nil nil nil nil nil nil nil nil nil + Function Done! + ``` - Modified how threads are handled internally. This changes makes it so threads "regardless of amount" should not impact performance. What you do in the threads might. This change was made by internally only processing one thread per step per processor. If you have 10 processors that are all active expect one step to process 10 threads. However if one processor has 10 threads each step will only process one thread. Simply put each addition of a thread shouldn't impact performance as it did before. - Moved `multi:newThread(...)` into the thread interface (`thread:newThread(...)`), code using `multi:newThread(...)` will still work. Also using `process:newThread(...)` binds the thread to the process, meaning if the process the thread is bound to is paused so is the thread. diff --git a/multi/init.lua b/multi/init.lua index d234e52..d6c301e 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -966,12 +966,10 @@ function multi:newProcessor(name,nothread) return Active end function c.Start() - print("Proc Start",mainloopActive) Active = true return c end function c.Stop() - print("Proc Stop") Active = false return c end @@ -1026,6 +1024,7 @@ thread.requests = {} local CMD = {} -- We will compare this special local local interval local resume, status, create, yield, running = coroutine.resume, coroutine.status, coroutine.create, coroutine.yield, coroutine.running + local t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none = 1, 2, 3, 4, 5, 6, 7 function thread.request(t,cmd,...) @@ -1077,13 +1076,20 @@ 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 ready = false - n(function() - ready = true + 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 ready - end) + return rdy() + end, nil, interval) elseif type(n) == "function" then return yield(CMD, t_hold, n or dFunc, nil, interval) else @@ -1231,7 +1237,7 @@ function thread:newFunctionBase(generator,holdme) return tempConn end } - t.OnDeath(function(self,status,...) temp.OnReturn:Fire(...) end) + t.OnDeath(function(...) temp.OnReturn:Fire(...) end) t.OnError(function(self,err) temp.OnError:Fire(err) end) t.linkedFunction = temp t.statusconnector = temp.OnStatus @@ -1261,10 +1267,10 @@ 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 end - local c={nil,nil,nil,nil,nil,nil,nil} local env = {self=c} c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} @@ -1330,10 +1336,8 @@ function thread:newThread(name,func,...) c.Destroy = c.Kill if self.Type=="process" then - table.insert(self.threads,c) table.insert(self.startme,c) else - table.insert(threads,c) table.insert(startme,c) end @@ -1478,10 +1482,14 @@ local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_no function() end } setmetatable(cmds,{__index=function() return function() end end}) -local co_status = { - ["suspended"] = function(thd,ref,task) +local co_status +co_status = { + ["suspended"] = function(thd,ref,task,i,th) switch[task](ref,thd) cmds[r1](ref,r2,r3,r4,r5) + if ret~=CMD then -- The rework makes this necessary + co_status["dead"](thd,ref,task,i,th) + end r1=nil r2=nil r3=nil r4=nil r5=nil end, ["normal"] = function(thd,ref) end, @@ -1506,12 +1514,14 @@ local co_status = { end, } local handler = coroutine.wrap(function(self) + local temp_start while true do - for start = startme_len,1,-1 do - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(startme[start].thread,unpack(startme[start].startArgs)) - co_status[status(startme[startme_len].thread)](startme[startme_len].thread,startme[startme_len],t_none,nil,threads) -- Make sure there was no error - startme[startme_len] = nil - startme_len = #startme + for start = 1, #startme do + temp_start = startme[start] + table.remove(startme) + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(temp_start.thread,unpack(temp_start.startArgs)) + co_status[status(temp_start.thread)](temp_start.thread,temp_start,t_none,nil,threads) -- Make sure there was no error + table.insert(threads,temp_start) yield() end for i=#threads,1,-1 do @@ -1531,10 +1541,12 @@ end) function multi:createHandler(threads,startme) return coroutine.wrap(function(self) while true do - for start = #startme,1,-1 do - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(startme[start].thread,unpack(startme[start].startArgs)) - co_status[status(startme[start].thread)](startme[start].thread,startme[start],t_none,nil,threads) -- Make sure there was no error - table.remove(startme) + for start = #startme, 1, -1 do + temp_start = startme[start] + table.remove(startme[start]) + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(temp_start.thread,unpack(temp_start.startArgs)) + co_status[status(temp_start.thread)](temp_start.thread,temp_start,t_none,nil,threads) -- Make sure there was no error + table.insert(threads,temp_start) yield() end for i=#threads,1,-1 do diff --git a/test.lua b/test.lua index 21fd9e2..5ed2e58 100644 --- a/test.lua +++ b/test.lua @@ -1,113 +1,27 @@ ---package.path = "./?/init.lua;"..package.path -multi,thread = require("multi"):init() +package.path = "./?/init.lua;"..package.path +multi, thread = require("multi"):init() func = thread:newFunction(function(count) local a = 0 while true do a = a + 1 - thread.sleep(.5) + thread.sleep(.1) thread.pushStatus(a,count) if a == count then break end end - return "Done" + return "Done", 1, 2, 3 end) -multi:newThread("test",function() +thread:newThread("test",function() local ret = func(10) - local ret2 = func(15) - local ret3 = func(20) ret.OnStatus(function(part,whole) print("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") end) - ret2.OnStatus(function(part,whole) - print("Ret2: ",math.ceil((part/whole)*1000)/10 .."%") - end) - ret3.OnStatus(function(part,whole) - print("Ret3: ",math.ceil((part/whole)*1000)/10 .."%") - end) - thread.hold(ret2.OnReturn + ret.OnReturn + ret3.OnReturn) + print("Status:",thread.hold(ret.OnReturn)) print("Function Done!") os.exit() -end) - ---GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your environment and uses what's available - -func2 = thread:newFunction(function() - thread.sleep(3) - print("Hello World!") - return true -end,true) -- set holdme to true - -func2:holdMe(false) -- reset holdme to false -print("Calling func...") -print(func2()) - -test = thread:newFunction(function(a,b) - thread.sleep(1) - return a,b -end) -print(test(1,2).connect(function(...) - print(...) -end)) -test:Pause() -print(test(1,2).connect(function(...) - print(...) -end)) -test:Resume() -print(test(1,2).connect(function(...) - print(...) -end)) - -test = thread:newFunction(function() - return 1,2,nil,3,4,5,6,7,8,9 -end,true) -print(test()) -multi:newThread("testing",function() - print("#Test = ",test()) - print(thread.hold(function() - print("Hello!") - return false - end,{ - interval = 2, - cycles = 3 - })) -- End result, 3 attempts within 6 seconds. If still false then timeout - print("held") end).OnError(function(...) - print(...) + print("Error:",...) end) -sandbox = multi:newProcessor("Test Processor") -sandbox:newTLoop(function() - print("testing...") -end,1) - -test2 = multi:newTLoop(function() - print("testing2...") -end,1) - -sandbox:newThread("Test Thread",function() - local a = 0 - while true do - thread.sleep(1) - a = a + 1 - print("Thread Test: ".. multi.getCurrentProcess().Name) - if a == 10 then - sandbox.Stop() - end - end -end).OnError(function(...) - print(...) -end) - -multi:newThread("Test Thread",function() - while true do - thread.sleep(1) - print("Thread Test: ".. multi.getCurrentProcess().Name) - end -end).OnError(function(...) - print(...) -end) - -sandbox.Start() - multi:mainloop() \ No newline at end of file From fc18a303dd0269069dc46b480c3fc0b3292333e5 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 15 Feb 2022 21:47:07 -0500 Subject: [PATCH 55/80] Fixed issue with how the thread scheduler spawned threads --- multi/init.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index d6c301e..06699fd 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1516,7 +1516,7 @@ co_status = { local handler = coroutine.wrap(function(self) local temp_start while true do - for start = 1, #startme do + for start = #startme, 1, -1 do temp_start = startme[start] table.remove(startme) _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(temp_start.thread,unpack(temp_start.startArgs)) @@ -1540,10 +1540,11 @@ end) function multi:createHandler(threads,startme) return coroutine.wrap(function(self) + local temp_start while true do for start = #startme, 1, -1 do temp_start = startme[start] - table.remove(startme[start]) + table.remove(startme) _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(temp_start.thread,unpack(temp_start.startArgs)) co_status[status(temp_start.thread)](temp_start.thread,temp_start,t_none,nil,threads) -- Make sure there was no error table.insert(threads,temp_start) From 5172dcdf01bde0e80983fdc4a14d9216ae069bbf Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 17 Feb 2022 10:00:55 -0500 Subject: [PATCH 56/80] Reworking the taskdetails method --- changes.md | 2 ++ multi/init.lua | 7 +++++++ test.lua | 25 +++---------------------- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/changes.md b/changes.md index 4ed1957..a32b0d9 100644 --- a/changes.md +++ b/changes.md @@ -13,6 +13,8 @@ Full Update Showcase Added: --- +- multi:getThreads() + - Returns a list of all threads on a process - multi:newProcessor(name,nothread).run() - new function run to the processor object to diff --git a/multi/init.lua b/multi/init.lua index 06699fd..a59587b 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -943,6 +943,9 @@ function multi:newProcessor(name,nothread) c.process.isProcessThread = true c.process.PID = sandcount c.OnError = c.process.OnError + function c:getThreads() + return self.threads + end function c:newThread(name,func,...) in_proc = c local t = thread.newThread(c,name,func,...) @@ -1027,6 +1030,10 @@ local resume, status, create, yield, running = coroutine.resume, coroutine.statu local t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none = 1, 2, 3, 4, 5, 6, 7 +function multi:getThreads() + return threads +end + function thread.request(t,cmd,...) thread.requests[t.thread] = {cmd,{...}} end diff --git a/test.lua b/test.lua index 5ed2e58..4662f1d 100644 --- a/test.lua +++ b/test.lua @@ -1,27 +1,8 @@ package.path = "./?/init.lua;"..package.path multi, thread = require("multi"):init() -func = thread:newFunction(function(count) - local a = 0 - while true do - a = a + 1 - thread.sleep(.1) - thread.pushStatus(a,count) - if a == count then break end - end - return "Done", 1, 2, 3 -end) - -thread:newThread("test",function() - local ret = func(10) - ret.OnStatus(function(part,whole) - print("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") - end) - print("Status:",thread.hold(ret.OnReturn)) - print("Function Done!") - os.exit() -end).OnError(function(...) - print("Error:",...) -end) +function multi:getTaskStats() + local stats = {} +end multi:mainloop() \ No newline at end of file From 4240737e00643da0287a24c504359da427d9555f Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 20 Feb 2022 21:07:04 -0500 Subject: [PATCH 57/80] Working on taskmanager features --- changes.md | 44 +++++++++++++++++++++++++++++-------- multi/init.lua | 59 ++++++++++++++++++++++++++++++++++++++++++++++++-- test.lua | 28 ++++++++++++++++++++++-- 3 files changed, 118 insertions(+), 13 deletions(-) diff --git a/changes.md b/changes.md index a32b0d9..09be44f 100644 --- a/changes.md +++ b/changes.md @@ -13,21 +13,42 @@ Full Update Showcase Added: --- -- multi:getThreads() - - Returns a list of all threads on a process -- multi:newProcessor(name,nothread).run() - - new function run to the processor object to +- `multi:lock()` + - Locks a multi object which prevents, Destroy(), Pause(), and Resume() being processed. -- multi:newProcessor(name,nothread):newFunction(func,holdme) +- `multi:unlock()` + - Undoes the lock + +- `multi:getProcessors()` + - Returns a list of all processors + +- `multi:getName()` + - Returns the name of a processor + +- `multi:getFullName()` + - Returns the fullname/entire process tree of a process + +- Processors can be attached to processors + +- `multi:getTasks()` + - Returns a list of all non thread based objects (loops, alarms, steps, etc) + +- `multi:getThreads()` + - Returns a list of all threads on a process + +- `multi:newProcessor(name,nothread).run()` + - New function run to the processor object to + +- `multi:newProcessor(name,nothread):newFunction(func,holdme)` - Acts like thread:newFunction(), but binds the execution of that threaded function to the processor -- multi:newTLoop() member functions +- `multi:newTLoop()` member functions - `TLoop:Set(set)` - Sets the time to wait for the TLoop -- multi:newStep() member functions +- `multi:newStep()` member functions - `Step:Count(count)` - Sets the amount a step should count by -- multi:newTStep() member functions +- `multi:newTStep()` member functions - `TStep:Set(set)` - Sets the time to wait for the TStep @@ -77,6 +98,7 @@ Changed: Status: Done 1 2 3 nil nil nil nil nil nil nil nil nil nil nil nil Function Done! ``` + - Modified how threads are handled internally. This changes makes it so threads "regardless of amount" should not impact performance. What you do in the threads might. This change was made by internally only processing one thread per step per processor. If you have 10 processors that are all active expect one step to process 10 threads. However if one processor has 10 threads each step will only process one thread. Simply put each addition of a thread shouldn't impact performance as it did before. - Moved `multi:newThread(...)` into the thread interface (`thread:newThread(...)`), code using `multi:newThread(...)` will still work. Also using `process:newThread(...)` binds the thread to the process, meaning if the process the thread is bound to is paused so is the thread. @@ -152,9 +174,13 @@ Removed: Fixed: --- -- [Issue](https://github.com/rayaman/multi/issues/30) with Lanes crashing the lua state. Issue seems to be related to my filesystem + +- [Issue](https://github.com/rayaman/multi/issues/30) with Lanes crashing the lua state. Issue seemed to be related to my filesystem, since remounting the drive caused the issue to stop. (Windows) + - [Issue](https://github.com/rayaman/multi/issues/29) where System threaded functions not being up to date with threaded functions + - Issue where gettasksdetails() would try to process a destroyed object causing it to crash + - Issue with multi.hold() not pumping the mainloop and only the scheduler ToDo: diff --git a/multi/init.lua b/multi/init.lua index a59587b..60e026e 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -28,13 +28,14 @@ local isRunning = false local clock = os.clock local thread = {} local in_proc = false +local processes = {} if not _G["$multi"] then _G["$multi"] = {multi=multi,thread=thread} end multi.Version = "15.2.0" -multi.Name = "multi.root" +multi.Name = "root" multi.NIL = {Type="NIL"} local NIL = multi.NIL multi.Mainloop = {} @@ -85,6 +86,10 @@ local priorityTable = {[false]="Disabled",[true]="Enabled"} local ProcessName = {"SubProcessor","MainProcessor"} local globalThreads = {} +function multi:getProcessors() + return processes +end + function multi:getTasksDetails(t) if not(t) then str = { @@ -441,6 +446,14 @@ function multi:getType() return self.Type end +function multi:lock() + self.__locked = true +end + +function multi:unlock() + self.__locked = false +end + -- Advance Timer stuff function multi:SetTime(n) if not n then n=3 end @@ -473,6 +486,7 @@ end -- Timer stuff done multi.PausedObjects = {} function multi:Pause() + if self.__locked then multi.print("Cannot perform action on a locked object!") return end if self.Type=='rootprocess' then multi.print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") else @@ -490,6 +504,7 @@ function multi:Pause() end function multi:Resume() + if self.__locked then multi.print("Cannot perform action on a locked object!") return end if self.Type=='process' or self.Type=='rootprocess' then self.Active=true local c=self:getChildren() @@ -507,6 +522,7 @@ function multi:Resume() end function multi:Destroy() + if self.__locked then multi.print("Cannot perform action on a locked object!") return end if self.Type=='process' or self.Type=='rootprocess' then local c=self:getChildren() for i=1,#c do @@ -576,6 +592,7 @@ function multi:newBase(ins) c.Act=function() end c.Parent=self c.creationTime = os.clock() + c.__locked = false if ins then table.insert(self.Mainloop,ins,c) else @@ -690,6 +707,7 @@ function multi:newAlarm(set) end end function c:Resume() + if self.__locked then multi.print("Cannot perform action on a locked object!") return end self.Parent.Resume(self) t = count + t return self @@ -702,6 +720,7 @@ function multi:newAlarm(set) end c.OnRing = self:newConnection() function c:Pause() + if self.__locked then multi.print("Cannot perform action on a locked object!") return end count = clock() self.Parent.Pause(self) return self @@ -812,11 +831,13 @@ function multi:newTLoop(func,set) self.set = set end function c:Resume() + if self.__locked then multi.print("Cannot perform action on a locked object!") return end self.Parent.Resume(self) self.timer:Resume() return self end function c:Pause() + if self.__locked then multi.print("Cannot perform action on a locked object!") return end self.timer:Pause() self.Parent.Pause(self) return self @@ -920,7 +941,16 @@ function multi.getCurrentTask() return __CurrentTask end +function multi:getName() + return self.Name +end + +function multi:getFullName() + return self.Name +end + local sandcount = 1 + function multi:newProcessor(name,nothread) local c = {} setmetatable(c,{__index = multi}) @@ -933,30 +963,45 @@ function multi:newProcessor(name,nothread) c.pump = false c.threads = {} c.startme = {} + c.parent = self local handler = c:createHandler(c.threads,c.startme) - c.process = multi:newLoop(function() + + c.process = self:newLoop(function() if Active then c:uManager() handler() end end) + c.process.isProcessThread = true c.process.PID = sandcount c.OnError = c.process.OnError + function c:getThreads() return self.threads end + + function c:getFullName() + return self.parent:getFullName() .. "." .. self.Name + end + + function c:getName() + return self.Name + end + function c:newThread(name,func,...) in_proc = c local t = thread.newThread(c,name,func,...) in_proc = false return t end + function c:newFunction(func,holdme) return thread:newFunctionBase(function(...) return c:newThread("TempThread",func,...) end,holdme)() end + function c.run() if not Active then return end c.pump = true @@ -965,21 +1010,27 @@ function multi:newProcessor(name,nothread) c.pump = false return c end + function c.isActive() return Active end + function c.Start() Active = true return c end + function c.Stop() Active = false return c end + function c:Destroy() Active = false c.process:Destroy() end + + table.insert(processes,c) return c end @@ -1034,6 +1085,10 @@ function multi:getThreads() return threads end +function multi:getTasks() + return self.Mainloop +end + function thread.request(t,cmd,...) thread.requests[t.thread] = {cmd,{...}} end diff --git a/test.lua b/test.lua index 4662f1d..b29e322 100644 --- a/test.lua +++ b/test.lua @@ -1,8 +1,32 @@ package.path = "./?/init.lua;"..package.path multi, thread = require("multi"):init() +local proc = multi:newProcessor("Test") +local proc2 = multi:newProcessor("Test2") +local proc3 = proc2:newProcessor("Test3") + function multi:getTaskStats() - local stats = {} + local stats = { + [multi.Name] = { + threads = multi:getThreads(), + tasks = multi:getTasks() + } + } + local procs = multi:getProcessors() + for i = 1, #procs do + local proc = procs[i] + stats[proc:getFullName()] = { + threads = proc:getThreads(), + tasks = proc:getTasks() + } + end + return stats end -multi:mainloop() \ No newline at end of file +local tasks = multi:getTaskStats() + +for i,v in pairs(tasks) do + print("Process: "..i) +end + +--multi:mainloop() \ No newline at end of file From 03ffb6bc0a71a7c7057d835c730e9e4170b964bf Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Fri, 25 Feb 2022 14:36:21 -0500 Subject: [PATCH 58/80] Removed locking --- changes.md | 6 ------ multi/init.lua | 16 ---------------- 2 files changed, 22 deletions(-) diff --git a/changes.md b/changes.md index 09be44f..9a49b12 100644 --- a/changes.md +++ b/changes.md @@ -13,12 +13,6 @@ Full Update Showcase Added: --- -- `multi:lock()` - - Locks a multi object which prevents, Destroy(), Pause(), and Resume() being processed. - -- `multi:unlock()` - - Undoes the lock - - `multi:getProcessors()` - Returns a list of all processors diff --git a/multi/init.lua b/multi/init.lua index 60e026e..509e006 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -446,14 +446,6 @@ function multi:getType() return self.Type end -function multi:lock() - self.__locked = true -end - -function multi:unlock() - self.__locked = false -end - -- Advance Timer stuff function multi:SetTime(n) if not n then n=3 end @@ -486,7 +478,6 @@ end -- Timer stuff done multi.PausedObjects = {} function multi:Pause() - if self.__locked then multi.print("Cannot perform action on a locked object!") return end if self.Type=='rootprocess' then multi.print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") else @@ -504,7 +495,6 @@ function multi:Pause() end function multi:Resume() - if self.__locked then multi.print("Cannot perform action on a locked object!") return end if self.Type=='process' or self.Type=='rootprocess' then self.Active=true local c=self:getChildren() @@ -522,7 +512,6 @@ function multi:Resume() end function multi:Destroy() - if self.__locked then multi.print("Cannot perform action on a locked object!") return end if self.Type=='process' or self.Type=='rootprocess' then local c=self:getChildren() for i=1,#c do @@ -592,7 +581,6 @@ function multi:newBase(ins) c.Act=function() end c.Parent=self c.creationTime = os.clock() - c.__locked = false if ins then table.insert(self.Mainloop,ins,c) else @@ -707,7 +695,6 @@ function multi:newAlarm(set) end end function c:Resume() - if self.__locked then multi.print("Cannot perform action on a locked object!") return end self.Parent.Resume(self) t = count + t return self @@ -720,7 +707,6 @@ function multi:newAlarm(set) end c.OnRing = self:newConnection() function c:Pause() - if self.__locked then multi.print("Cannot perform action on a locked object!") return end count = clock() self.Parent.Pause(self) return self @@ -831,13 +817,11 @@ function multi:newTLoop(func,set) self.set = set end function c:Resume() - if self.__locked then multi.print("Cannot perform action on a locked object!") return end self.Parent.Resume(self) self.timer:Resume() return self end function c:Pause() - if self.__locked then multi.print("Cannot perform action on a locked object!") return end self.timer:Pause() self.Parent.Pause(self) return self From e8a3cd731d2d13b017bf0d6f5dcf808c3c4c8cf9 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 2 Apr 2022 00:07:06 -0400 Subject: [PATCH 59/80] Added ST to THD namespace, old way still works --- multi/integration/lanesManager/init.lua | 3 ++ multi/integration/loveManager/init.lua | 1 + multi/integration/lovrManager/init.lua | 1 + multi/integration/luvitManager.lua | 1 + test.lua | 63 +++++++++++++++---------- 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 9c21d5a..80ccbd8 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -100,6 +100,9 @@ function multi:newSystemThread(name, func, ...) GLOBAL["__THREADS__"] = livingThreads return c end + +THREAD.newSystemThread = multi.newSystemThread + function multi.InitSystemThreadErrorHandler() if started == true then return diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua index 6c94531..8eecd37 100644 --- a/multi/integration/loveManager/init.lua +++ b/multi/integration/loveManager/init.lua @@ -144,6 +144,7 @@ function multi:newSystemThread(name,func,...) end) return c end +THREAD.newSystemThread = multi.newSystemThread function love.threaderror(thread, errorstr) print("Thread error!\n"..errorstr) end diff --git a/multi/integration/lovrManager/init.lua b/multi/integration/lovrManager/init.lua index a20e607..53cafa3 100644 --- a/multi/integration/lovrManager/init.lua +++ b/multi/integration/lovrManager/init.lua @@ -76,6 +76,7 @@ function multi:newSystemThread(name,func,...) THREAD_ID=THREAD_ID+1 return c end +THREAD.newSystemThread = multi.newSystemThread function lovr.threaderror(thread, errorstr) print("Thread error!\n"..errorstr) end diff --git a/multi/integration/luvitManager.lua b/multi/integration/luvitManager.lua index 48f6f80..0f53cb9 100644 --- a/multi/integration/luvitManager.lua +++ b/multi/integration/luvitManager.lua @@ -116,6 +116,7 @@ local function _INIT(luvitThread, timer) 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 diff --git a/test.lua b/test.lua index b29e322..f81fd25 100644 --- a/test.lua +++ b/test.lua @@ -1,32 +1,45 @@ package.path = "./?/init.lua;"..package.path multi, thread = require("multi"):init() +GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -local proc = multi:newProcessor("Test") -local proc2 = multi:newProcessor("Test2") -local proc3 = proc2:newProcessor("Test3") - -function multi:getTaskStats() - local stats = { - [multi.Name] = { - threads = multi:getThreads(), - tasks = multi:getTasks() - } - } - local procs = multi:getProcessors() - for i = 1, #procs do - local proc = procs[i] - stats[proc:getFullName()] = { - threads = proc:getThreads(), - tasks = proc:getTasks() - } +THREAD:newSystemThread("Test",function() + print("In a thread...") + while true do + THREAD.sleep(1) + print("In thread") end - return stats -end +end) -local tasks = multi:getTaskStats() +multi:newTLoop(function() + print("...") +end,1) -for i,v in pairs(tasks) do - print("Process: "..i) -end +-- local proc = multi:newProcessor("Test") +-- local proc2 = multi:newProcessor("Test2") +-- local proc3 = proc2:newProcessor("Test3") ---multi:mainloop() \ No newline at end of file +-- function multi:getTaskStats() +-- local stats = { +-- [multi.Name] = { +-- threads = multi:getThreads(), +-- tasks = multi:getTasks() +-- } +-- } +-- local procs = multi:getProcessors() +-- for i = 1, #procs do +-- local proc = procs[i] +-- stats[proc:getFullName()] = { +-- threads = proc:getThreads(), +-- tasks = proc:getTasks() +-- } +-- end +-- return stats +-- end + +-- local tasks = multi:getTaskStats() + +-- for i,v in pairs(tasks) do +-- print("Process: "..i) +-- end + +multi:mainloop() \ No newline at end of file From c0fb94ddbb8be36bb536fc9f22c9987509e4383e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 2 Apr 2022 00:22:34 -0400 Subject: [PATCH 60/80] Fixed and documented the newProcessor changes. Rockspec fix --- changes.md | 7 +++++-- rockspecs/multi-15.2-0.rockspec | 4 ++-- tests/runtests.lua | 7 ++++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/changes.md b/changes.md index 9a49b12..e4c591a 100644 --- a/changes.md +++ b/changes.md @@ -13,6 +13,9 @@ Full Update Showcase Added: --- +- `multi:newProcessor(name, nothread)` + - If no thread is true auto sets the processor as Active, so proc.run() will start without the need for proc.Start() + - `multi:getProcessors()` - Returns a list of all processors @@ -30,10 +33,10 @@ Added: - `multi:getThreads()` - Returns a list of all threads on a process -- `multi:newProcessor(name,nothread).run()` +- `multi:newProcessor(name, nothread).run()` - New function run to the processor object to -- `multi:newProcessor(name,nothread):newFunction(func,holdme)` +- `multi:newProcessor(name, nothread):newFunction(func, holdme)` - Acts like thread:newFunction(), but binds the execution of that threaded function to the processor - `multi:newTLoop()` member functions diff --git a/rockspecs/multi-15.2-0.rockspec b/rockspecs/multi-15.2-0.rockspec index f0aaaf5..be799ec 100644 --- a/rockspecs/multi-15.2-0.rockspec +++ b/rockspecs/multi-15.2-0.rockspec @@ -21,8 +21,8 @@ build = { type = "builtin", modules = { ["multi"] = "multi/init.lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.compat.lovr"] = "multi/compat/lovr.lua", + --["multi.compat.love2d"] = "multi/compat/love2d.lua", + --["multi.compat.lovr"] = "multi/compat/lovr.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", diff --git a/tests/runtests.lua b/tests/runtests.lua index 2c45222..d0224be 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="./?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package.path end --[[ This file runs all tests. @@ -20,9 +20,9 @@ end The expected and actual should "match" (Might be impossible when playing with threads) This will be pushed directly to the master as tests start existing. ]] -local multi, thread = require("multi"):init{priority=true} +local multi, thread = require("multi"):init()--{priority=true} local good = false -local proc = multi:newProcessor("Test") +local proc = multi:newProcessor("Test",true) proc:newAlarm(3):OnRing(function() good = true end) @@ -148,6 +148,7 @@ end) runTest().OnError(function(...) print("Error:",...) end) +print("Pumping proc") while true do proc.run() end \ No newline at end of file From d8aeefd2027a4f240fd9d569889410aa4daf0e2b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 2 Apr 2022 23:06:06 -0400 Subject: [PATCH 61/80] Working on love2d threaded functions --- multi/integration/loveManager/init.lua | 82 +++++--------------------- 1 file changed, 14 insertions(+), 68 deletions(-) diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua index 8eecd37..4a4c279 100644 --- a/multi/integration/loveManager/init.lua +++ b/multi/integration/loveManager/init.lua @@ -48,70 +48,7 @@ local GLOBAL = THREAD.getGlobal() local THREAD_ID = 1 local OBJECT_ID = 0 local stf = 0 -function THREAD:newFunction(func,holup) - local tfunc = {} - tfunc.Active = true - function tfunc:Pause() - self.Active = false - end - function tfunc:Resume() - self.Active = true - end - function tfunc:holdMe(b) - holdme = b - end - local function noWait() - return nil, "Function is paused" - end - local rets, err - local function wait(no) - if thread.isThread() and not (no) then - -- In a thread - else - -- Not in a thread - end - end - tfunc.__call = function(t,...) - if not t.Active then - if holdme then - return nil, "Function is paused" - end - return { - isTFunc = true, - wait = noWait, - connect = function(f) - f(nil,"Function is paused") - end - } - end - local t = multi:newSystemThread("SystemThreadedFunction",func,...) - t.OnDeath(function(self,...) rets = {...} end) - t.OnError(function(self,e) err = e end) - if holdme then - return wait() - end - local temp = { - OnStatus = multi:newConnection(), - OnError = multi:newConnection(), - OnReturn = multi:newConnection(), - isTFunc = true, - wait = wait, - connect = function(f) - local tempConn = multi:newConnection() - t.OnDeath(function(self,...) if f then f(...) else tempConn:Fire(...) end end) - t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) - return tempConn - end - } - t.OnDeath(function(self,...) temp.OnReturn:Fire(...) end) - t.OnError(function(self,err) temp.OnError:Fire(err) end) - t.linkedFunction = temp - t.statusconnector = temp.OnStatus - return temp - end - setmetatable(tfunc,tfunc) - return tfunc -end + function multi:newSystemThread(name,func,...) local c = {} c.name = name @@ -119,9 +56,11 @@ function multi:newSystemThread(name,func,...) c.thread=love.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} + c.OnDeath = multi:newConnection() + c.OnError = multi:newConnection() + GLOBAL["__THREAD_"..c.ID] = {ID=c.ID, Name=c.name, Thread=c.thread} GLOBAL["__THREAD_COUNT"] = THREAD_ID - THREAD_ID=THREAD_ID+1 + THREAD_ID=THREAD_ID + 1 thread:newThread(function() while true do thread.yield() @@ -133,10 +72,10 @@ function multi:newSystemThread(name,func,...) local error = c.thread:getError() if error then if error:find("Thread Killed!\1") then - c.OnDeath:Fire(c,"Thread Killed!") + c.OnDeath:Fire(c, "Thread Killed!") thread.kill() else - c.OnError:Fire(c,error) + c.OnError:Fire(c, error) thread.kill() end end @@ -144,6 +83,13 @@ function multi:newSystemThread(name,func,...) end) return c end + +function THREAD:newFunction(func) + return thread:newFunctionBase(function(...) + return multi:newSystemThread("TempSystemThread",func,...) + end)() +end + THREAD.newSystemThread = multi.newSystemThread function love.threaderror(thread, errorstr) print("Thread error!\n"..errorstr) From 79f58a79f9100d77e3f0660b6cc8c76352c783d0 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 10 Apr 2022 20:00:20 -0400 Subject: [PATCH 62/80] Love2d newFunction working --- multi/integration/loveManager/init.lua | 33 +++++++++++++------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua index 4a4c279..a8508a9 100644 --- a/multi/integration/loveManager/init.lua +++ b/multi/integration/loveManager/init.lua @@ -62,23 +62,22 @@ function multi:newSystemThread(name,func,...) GLOBAL["__THREAD_COUNT"] = THREAD_ID THREAD_ID=THREAD_ID + 1 thread:newThread(function() - while true do - thread.yield() - if c.stab["returns"] then - c.OnDeath:Fire(c,unpack(t.stab.returns)) - t.stab.returns = nil - thread.kill() - end - local error = c.thread:getError() - if error then - if error:find("Thread Killed!\1") then - c.OnDeath:Fire(c, "Thread Killed!") - thread.kill() - else - c.OnError:Fire(c, error) - thread.kill() - end - end + thread.hold(function() + return not c.thread:isRunning() + end) + print("Thread: "..name.." finished executing...") + -- If the thread is not running let's handle that. + local thread_err = c.thread:getError() + if thread_err == "Thread Killed!\1" then + print("Killed...") + c.OnDeath:Fire(c,"Thread Killed!") + elseif thread_err then + print("Error...",thread_err) + c.OnError:Fire(c,thread_err) + elseif c.stab.returns then + print("Returns",unpack(c.stab.returns)) + c.OnDeath:Fire(c,unpack(c.stab.returns)) + c.stab.returns = nil end end) return c From d30ee3788ecc53a97d8893caf58960a512541533 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 11 Apr 2022 23:48:45 -0400 Subject: [PATCH 63/80] THREAD.pushStatus for lanes works, todo love --- multi/integration/lanesManager/init.lua | 17 +++++--- multi/integration/lanesManager/threads.lua | 10 +++-- test.lua | 49 +++++++++++++++++----- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 80ccbd8..4b8ac89 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -48,7 +48,9 @@ end local __GlobalLinda = lanes.linda() -- handles global stuff local __SleepingLinda = lanes.linda() -- handles sleeping stuff local __ConsoleLinda = lanes.linda() -- handles console stuff -local GLOBAL,THREAD = require("multi.integration.lanesManager.threads").init(__GlobalLinda,__SleepingLinda) +local __StatusLinda = lanes.linda() -- handles pushstatus for stfunctions + +local GLOBAL,THREAD = require("multi.integration.lanesManager.threads").init(__GlobalLinda, __SleepingLinda, __StatusLinda) local count = 1 local started = false local livingThreads = {} @@ -110,12 +112,17 @@ function multi.InitSystemThreadErrorHandler() started = true thread:newThread("SystemThreadScheduler",function() local threads = multi.SystemThreads + local _,data,status,push,temp while true do - thread.sleep(.005) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough. - local _,data = __ConsoleLinda:receive(0, "Q") + thread.yield() + _,data = __ConsoleLinda:receive(0, "Q") for i = #threads, 1, -1 do - local status = threads[i].thread.status - local temp = threads[i] + temp = threads[i] + status = temp.thread.status + push = __StatusLinda:get(temp.Id) + if push then + temp.statusconnector:Fire(unpack(({__StatusLinda:receive(nil, temp.Id)})[2])) + end if status == "done" or temp.returns:get("returns") then livingThreads[temp.Id] = {false, temp.Name} temp.alive = false diff --git a/multi/integration/lanesManager/threads.lua b/multi/integration/lanesManager/threads.lua index 04277d1..b8e2c57 100644 --- a/multi/integration/lanesManager/threads.lua +++ b/multi/integration/lanesManager/threads.lua @@ -28,7 +28,7 @@ local function getOS() return "unix" end end -local function INIT(__GlobalLinda,__SleepingLinda) +local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda) local THREAD = {} THREAD.Priority_Core = 3 THREAD.Priority_High = 2 @@ -90,6 +90,10 @@ local function INIT(__GlobalLinda,__SleepingLinda) function THREAD.getID() return THREAD_ID end + function THREAD.pushStatus(...) + local args = {...} + __StatusLinda:send(nil,THREAD_ID, args) + end _G.THREAD_ID = 0 function THREAD.sleep(n) math.randomseed(os.time()) @@ -115,6 +119,6 @@ local function INIT(__GlobalLinda,__SleepingLinda) }) return GLOBAL, THREAD end -return {init = function(g,s) - return INIT(g,s) +return {init = function(g,s,st) + return INIT(g,s,st) end} \ No newline at end of file diff --git a/test.lua b/test.lua index f81fd25..5668fd6 100644 --- a/test.lua +++ b/test.lua @@ -2,18 +2,47 @@ package.path = "./?/init.lua;"..package.path multi, thread = require("multi"):init() GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -THREAD:newSystemThread("Test",function() - print("In a thread...") - while true do - THREAD.sleep(1) - print("In thread") - end +thread:newThread(function() + func = THREAD:newFunction(function(count) + print("Starting Status test: ",count) + local a = 0 + while true do + a = a + 1 + THREAD.sleep(.1) + THREAD.pushStatus(a,count) + if a == count then break end + end + return "Done" + 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) + local err, timeout = thread.hold(ret.OnReturn + ret2.OnReturn + ret3.OnReturn) + print("Done") end) -multi:newTLoop(function() - print("...") -end,1) - -- local proc = multi:newProcessor("Test") -- local proc2 = multi:newProcessor("Test2") -- local proc3 = proc2:newProcessor("Test3") From 568c95fa7364a0921732ad67e668d3bc122ef888 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 16 Apr 2022 00:02:54 -0400 Subject: [PATCH 64/80] Issue with love2d system threaded functions pushstatus fixed --- multi/init.lua | 5 +- multi/integration/loveManager/init.lua | 30 ++++++--- multi/integration/loveManager/threads.lua | 6 ++ multi/integration/pesudoManager/init.lua | 3 +- multi/integration/pesudoManager/threads.lua | 9 +-- test.lua | 74 ++++++++++----------- 6 files changed, 71 insertions(+), 56 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index 509e006..cf0d71f 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -948,6 +948,7 @@ function multi:newProcessor(name,nothread) c.threads = {} c.startme = {} c.parent = self + local handler = c:createHandler(c.threads,c.startme) c.process = self:newLoop(function() @@ -1533,7 +1534,7 @@ co_status = { ["suspended"] = function(thd,ref,task,i,th) switch[task](ref,thd) cmds[r1](ref,r2,r3,r4,r5) - if ret~=CMD then -- The rework makes this necessary + if ret ~= CMD and _ ~= nil then -- The rework makes this necessary co_status["dead"](thd,ref,task,i,th) end r1=nil r2=nil r3=nil r4=nil r5=nil @@ -2118,4 +2119,4 @@ else multi.m.sentinel = newproxy(true) getmetatable(multi.m.sentinel).__gc = multi.m.onexit end -return multi +return multi \ No newline at end of file diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua index a8508a9..1be7d0d 100644 --- a/multi/integration/loveManager/init.lua +++ b/multi/integration/loveManager/init.lua @@ -62,20 +62,28 @@ function multi:newSystemThread(name,func,...) GLOBAL["__THREAD_COUNT"] = THREAD_ID THREAD_ID=THREAD_ID + 1 thread:newThread(function() - thread.hold(function() - return not c.thread:isRunning() - end) - print("Thread: "..name.." finished executing...") + if name:find("TempSystemThread") then + local status_channel = love.thread.getChannel("__"..c.ID.."__MULTI__STATUS_CHANNEL__") + thread.hold(function() + -- While the thread is running we might as well do something in the loop + local status = status_channel + if status:peek()~=nil then + c.statusconnector:Fire(unpack(status:pop())) + end + return not c.thread:isRunning() + end) + else + thread.hold(function() + return not c.thread:isRunning() + end) + end -- If the thread is not running let's handle that. local thread_err = c.thread:getError() if thread_err == "Thread Killed!\1" then - print("Killed...") c.OnDeath:Fire(c,"Thread Killed!") elseif thread_err then - print("Error...",thread_err) c.OnError:Fire(c,thread_err) elseif c.stab.returns then - print("Returns",unpack(c.stab.returns)) c.OnDeath:Fire(c,unpack(c.stab.returns)) c.stab.returns = nil end @@ -85,18 +93,20 @@ end function THREAD:newFunction(func) return thread:newFunctionBase(function(...) - return multi:newSystemThread("TempSystemThread",func,...) + return multi:newSystemThread("TempSystemThread"..THREAD_ID,func,...) end)() end THREAD.newSystemThread = multi.newSystemThread + function love.threaderror(thread, errorstr) - print("Thread error!\n"..errorstr) + mulit.print("Thread error!\n"..errorstr) end + multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD require("multi.integration.loveManager.extensions") -print("Integrated Love Threading!") +mulit.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 8886be7..afcb951 100644 --- a/multi/integration/loveManager/threads.lua +++ b/multi/integration/loveManager/threads.lua @@ -24,6 +24,7 @@ SOFTWARE. require("love.timer") require("love.system") require("love.data") +require("love.thread") local socket = require("socket") local multi, thread = require("multi").init() local threads = {} @@ -94,6 +95,11 @@ end function threads.kill() error("Thread Killed!\1") end +function THREAD.pushStatus(...) + local status_channel = love.thread.getChannel("__"..__THREADID__.."__MULTI__STATUS_CHANNEL__") + local args = {...} + status_channel:push(__THREADID__, args) +end function threads.getThreads() local t = {} for i=1,GLOBAL["__THREAD_COUNT"] do diff --git a/multi/integration/pesudoManager/init.lua b/multi/integration/pesudoManager/init.lua index c93b049..6c19d28 100644 --- a/multi/integration/pesudoManager/init.lua +++ b/multi/integration/pesudoManager/init.lua @@ -32,7 +32,7 @@ if multi.integration then } end -local GLOBAL, THREAD = require("multi.integration.pesudoManager.threads"):init() +local GLOBAL, THREAD = require("multi.integration.pesudoManager.threads"):init(thread) function multi:canSystemThread() -- We are emulating system threading return true @@ -75,6 +75,7 @@ function multi:newSystemThread(name,func,...) end) id = id + 1 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 diff --git a/multi/integration/pesudoManager/threads.lua b/multi/integration/pesudoManager/threads.lua index 3f97e46..901bef9 100644 --- a/multi/integration/pesudoManager/threads.lua +++ b/multi/integration/pesudoManager/threads.lua @@ -28,7 +28,7 @@ local function getOS() return "unix" end end -local function INIT(env) +local function INIT(env,thread) local THREAD = {} local GLOBAL = {} THREAD.Priority_Core = 3 @@ -45,7 +45,6 @@ local function INIT(env) return GLOBAL[name] end function THREAD.waitFor(name) - print("Waiting",thread) return thread.hold(function() return GLOBAL[name] end) end if getOS() == "windows" then @@ -69,6 +68,7 @@ local function INIT(env) 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 @@ -83,6 +83,7 @@ local function INIT(env) function THREAD.getID() return GLOBAL["$THREAD_ID"] end + THREAD.pushstatus function THREAD.sleep(n) thread.sleep(n) end @@ -91,6 +92,6 @@ local function INIT(env) end return GLOBAL, THREAD end -return {init = function() - return INIT() +return {init = function(thread) + return INIT(nil,thread) end} \ No newline at end of file diff --git a/test.lua b/test.lua index 5668fd6..d17f6cc 100644 --- a/test.lua +++ b/test.lua @@ -2,45 +2,41 @@ package.path = "./?/init.lua;"..package.path multi, thread = require("multi"):init() GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -thread:newThread(function() - func = THREAD:newFunction(function(count) - print("Starting Status test: ",count) - local a = 0 - while true do - a = a + 1 - THREAD.sleep(.1) - THREAD.pushStatus(a,count) - if a == count then break end - end - return "Done" - 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) - local err, timeout = thread.hold(ret.OnReturn + ret2.OnReturn + ret3.OnReturn) - print("Done") +func = THREAD:newFunction(function(count) + print("Starting Status test: ",count) + local a = 0 + while true do + a = a + 1 + THREAD.sleep(.1) + THREAD.pushStatus(a,count) + if a == count then break end + end + return "Done" +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) -- local proc = multi:newProcessor("Test") From 40dd293bf8b2b1b5edef39d0a7d416bb7bcd3ce7 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 18 Apr 2022 21:03:14 -0400 Subject: [PATCH 65/80] Fixed issue with pesudo threading not working properly --- multi/integration/lanesManager/threads.lua | 18 +++++++++++ multi/integration/loveManager/threads.lua | 23 ++++++++++++++ multi/integration/pesudoManager/init.lua | 10 +++++-- multi/integration/pesudoManager/threads.lua | 33 ++++++++++++++------- test.lua | 3 +- 5 files changed, 73 insertions(+), 14 deletions(-) diff --git a/multi/integration/lanesManager/threads.lua b/multi/integration/lanesManager/threads.lua index b8e2c57..68cb733 100644 --- a/multi/integration/lanesManager/threads.lua +++ b/multi/integration/lanesManager/threads.lua @@ -28,6 +28,7 @@ local function getOS() return "unix" end end + local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda) local THREAD = {} THREAD.Priority_Core = 3 @@ -37,12 +38,15 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda) THREAD.Priority_Below_Normal = -1 THREAD.Priority_Low = -2 THREAD.Priority_Idle = -3 + function THREAD.set(name, val) __GlobalLinda:set(name, val) end + function THREAD.get(name) return __GlobalLinda:get(name) end + function THREAD.waitFor(name) local function wait() math.randomseed(os.time()) @@ -53,14 +57,17 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda) until __GlobalLinda:get(name) return __GlobalLinda:get(name) 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 = {} c.queue = _Console @@ -73,32 +80,41 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda) end return c end + function THREAD.getThreads() return GLOBAL.__THREADS__ 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 lane destruction error("Thread was killed!\1") end + function THREAD.getName() return THREAD_NAME end + function THREAD.getID() return THREAD_ID end + function THREAD.pushStatus(...) local args = {...} __StatusLinda:send(nil,THREAD_ID, args) end + _G.THREAD_ID = 0 + function THREAD.sleep(n) math.randomseed(os.time()) __SleepingLinda:receive(n, "__non_existing_variable") end + function THREAD.hold(n) local function wait() math.randomseed(os.time()) @@ -108,6 +124,7 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda) wait() until n() end + local GLOBAL = {} setmetatable(GLOBAL, { __index = function(t, k) @@ -119,6 +136,7 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda) }) return GLOBAL, THREAD end + return {init = function(g,s,st) return INIT(g,s,st) end} \ No newline at end of file diff --git a/multi/integration/loveManager/threads.lua b/multi/integration/loveManager/threads.lua index afcb951..c66e7dc 100644 --- a/multi/integration/loveManager/threads.lua +++ b/multi/integration/loveManager/threads.lua @@ -28,12 +28,15 @@ require("love.thread") 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 love.data.newByteData(string.dump(func)) end + local fRef = {"func",nil} local function manage(channel, value) channel:clear() @@ -45,6 +48,7 @@ local function manage(channel, value) channel:push(value) end end + local function RandomVariable(length) local res = {} math.randomseed(socket.gettime()*10000) @@ -53,12 +57,14 @@ local function RandomVariable(length) end return table.concat(res) end + local GNAME = "__GLOBAL_" local proxy = {} function threads.set(name,val) if not proxy[name] then proxy[name] = love.thread.getChannel(GNAME..name) end proxy[name]:performAtomic(manage, val) end + function threads.get(name) if not proxy[name] then proxy[name] = love.thread.getChannel(GNAME..name) end local dat = proxy[name]:peek() @@ -68,6 +74,7 @@ function threads.get(name) return dat end end + function threads.waitFor(name) if thread.isThread() then return thread.hold(function() @@ -83,23 +90,28 @@ function threads.waitFor(name) 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 love.system.getProcessorCount() end + function threads.kill() error("Thread Killed!\1") end + function THREAD.pushStatus(...) local status_channel = love.thread.getChannel("__"..__THREADID__.."__MULTI__STATUS_CHANNEL__") local args = {...} status_channel:push(__THREADID__, args) end + function threads.getThreads() local t = {} for i=1,GLOBAL["__THREAD_COUNT"] do @@ -107,18 +119,23 @@ function threads.getThreads() 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) love.timer.sleep(n) end + function threads.getGlobal() return setmetatable({}, { @@ -131,6 +148,7 @@ function threads.getGlobal() } ) end + function threads.createTable(n) local _proxy = {} local function set(name,val) @@ -157,6 +175,7 @@ function threads.createTable(n) } ) end + function threads.getConsole() local c = {} c.queue = love.thread.getChannel("__CONSOLE__") @@ -169,6 +188,7 @@ function threads.getConsole() end return c end + if not ISTHREAD then local clock = os.clock local lastproc = clock() @@ -187,6 +207,7 @@ if not ISTHREAD then end end) end + function threads.createStaticTable(n) local __proxy = {} local function set(name,val) @@ -218,10 +239,12 @@ function threads.createStaticTable(n) } ) 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/pesudoManager/init.lua b/multi/integration/pesudoManager/init.lua index 6c19d28..6297dc5 100644 --- a/multi/integration/pesudoManager/init.lua +++ b/multi/integration/pesudoManager/init.lua @@ -22,7 +22,7 @@ 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() +local multi, thread = require("multi"):init() if multi.integration then return { @@ -32,7 +32,7 @@ if multi.integration then } end -local GLOBAL, THREAD = require("multi.integration.pesudoManager.threads"):init(thread) +local GLOBAL, THREAD = require("multi.integration.pesudoManager.threads").init(thread) function multi:canSystemThread() -- We are emulating system threading return true @@ -41,6 +41,7 @@ end function multi:getPlatform() return "pesudo" end + local function split(str) local tab = {} for word in string.gmatch(str, '([^,]+)') do @@ -48,13 +49,15 @@ local function split(str) end return tab end + THREAD.newFunction=thread.newFunction + local id = 0 function multi:newSystemThread(name,func,...) GLOBAL["$THREAD_NAME"] = name GLOBAL["$__THREADNAME__"] = name GLOBAL["$THREAD_ID"] = id - --GLOBAL["$thread"] = thread + GLOBAL["$thread"] = thread local env = { GLOBAL = GLOBAL, THREAD = THREAD, @@ -75,6 +78,7 @@ function multi:newSystemThread(name,func,...) end) id = id + 1 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 diff --git a/multi/integration/pesudoManager/threads.lua b/multi/integration/pesudoManager/threads.lua index 901bef9..0cbae07 100644 --- a/multi/integration/pesudoManager/threads.lua +++ b/multi/integration/pesudoManager/threads.lua @@ -28,7 +28,9 @@ local function getOS() return "unix" end end -local function INIT(env,thread) + +local function INIT(thread) + print("T",thread.sleep) local THREAD = {} local GLOBAL = {} THREAD.Priority_Core = 3 @@ -38,23 +40,29 @@ local function INIT(env,thread) 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(...) @@ -65,33 +73,38 @@ local function INIT(env,thread) end return c end + function THREAD.getThreads() return {}--GLOBAL.__THREADS__ end - THREAD.pushstatus = thread.pushstatus + + 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.pushstatus - function THREAD.sleep(n) - thread.sleep(n) - end - function THREAD.hold(n) - return thread.hold(n) - end + + THREAD.sleep = thread.sleep + + THREAD.hold = thread.hold + return GLOBAL, THREAD end + return {init = function(thread) - return INIT(nil,thread) + return INIT(thread) end} \ No newline at end of file diff --git a/test.lua b/test.lua index d17f6cc..78b7dcf 100644 --- a/test.lua +++ b/test.lua @@ -1,6 +1,6 @@ package.path = "./?/init.lua;"..package.path multi, thread = require("multi"):init() -GLOBAL, THREAD = require("multi.integration.lanesManager"):init() +GLOBAL, THREAD = require("multi.integration.pesudoManager"):init() func = THREAD:newFunction(function(count) print("Starting Status test: ",count) @@ -13,6 +13,7 @@ func = THREAD:newFunction(function(count) end return "Done" end) + local ret = func(10) local ret2 = func(15) local ret3 = func(20) From bd49805c251fe404d500e814974d242baa885b9d Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 18 Apr 2022 21:04:44 -0400 Subject: [PATCH 66/80] testing... --- test.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test.lua b/test.lua index 78b7dcf..8493d69 100644 --- a/test.lua +++ b/test.lua @@ -1,6 +1,6 @@ package.path = "./?/init.lua;"..package.path -multi, thread = require("multi"):init() -GLOBAL, THREAD = require("multi.integration.pesudoManager"):init() +multi, thread = require("multi"):init{print=true} +GLOBAL, THREAD = require("multi.integration.threading"):init() func = THREAD:newFunction(function(count) print("Starting Status test: ",count) From 6527dc1aaac9a27d5c35ae41fef2669b7a3dc04b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 18 Apr 2022 21:25:36 -0400 Subject: [PATCH 67/80] Fixed test script to not use globally installed version and the dev version --- multi/init.lua | 2 +- tests/runtests.lua | 32 +++++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index cf0d71f..80e0b99 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1545,7 +1545,7 @@ co_status = { if _ then ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) else - ref.OnError:Fire(ref,ret) + ref.OnError:Fire(ref,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) end if i then table.remove(th,i) diff --git a/tests/runtests.lua b/tests/runtests.lua index d0224be..07d6e85 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="./?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package.path end --[[ This file runs all tests. @@ -20,7 +20,7 @@ end The expected and actual should "match" (Might be impossible when playing with threads) This will be pushed directly to the master as tests start existing. ]] -local multi, thread = require("multi"):init()--{priority=true} +local multi, thread = require("multi"):init{print=true}--{priority=true} local good = false local proc = multi:newProcessor("Test",true) proc:newAlarm(3):OnRing(function() @@ -85,7 +85,13 @@ runTest = thread:newFunction(function() local ret3 = func(20) local s1,s2,s3 = 0,0,0 ret.OnError(function(...) - print("Error:",...) + print("Func 1:",...) + end) + ret2.OnError(function(...) + print("Func 2:",...) + end) + ret3.OnError(function(...) + print("Func 3:",...) end) ret.OnStatus(function(part,whole) s1 = math.ceil((part/whole)*1000)/10 @@ -97,7 +103,13 @@ runTest = thread:newFunction(function() s3 = math.ceil((part/whole)*1000)/10 end) ret.OnReturn(function() - print("Done") + print("Done 1") + end) + ret2.OnReturn(function() + print("Done 2") + end) + ret3.OnReturn(function() + print("Done 3") end) local err, timeout = thread.hold(ret.OnReturn + ret2.OnReturn + ret3.OnReturn) if s1 == 100 and s2 == 100 and s3 == 100 then @@ -114,21 +126,27 @@ runTest = thread:newFunction(function() conn2 = proc:newConnection() conn3 = proc:newConnection() local c1,c2,c3,c4 = false,false,false,false + local a = conn1(function() c1 = true end) + local b = conn2(function() c2 = true end) + local c = conn3(function() c3 = true end) + local d = conn3(function() c4 = true end) + conn1:Fire() conn2:Fire() conn3:Fire() + if c1 and c2 and c3 and c4 then print("Connection Test 2: Ok") else @@ -145,9 +163,13 @@ runTest = thread:newFunction(function() end os.exit() -- End of tests end) + runTest().OnError(function(...) - print("Error:",...) + print("Error: Something went wrong with the test!") + print(...) + os.exit(1) end) + print("Pumping proc") while true do proc.run() From 2cc2a57a46188fa872f92701595ccddaead17757 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 18 Apr 2022 22:44:08 -0400 Subject: [PATCH 68/80] Fixed system threads calling OnDeath incorrectly, finished full update showcase --- README.md | 2 +- changes.md | 130 +++++++++++++++++++- multi/init.lua | 151 +++++++----------------- multi/integration/lanesManager/init.lua | 2 +- multi/integration/loveManager/init.lua | 4 +- test.lua | 88 ++++++++++---- 6 files changed, 239 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index 502ca86..01549c0 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ My multitasking library for lua. It is a pure lua binding, with exceptions of th
-Progress is being made in [v15.2.0](https://github.com/rayaman/multi/tree/v15.2.0) +Progress is being made in [v15.3.0](https://github.com/rayaman/multi/tree/v15.3.0) ---
diff --git a/changes.md b/changes.md index e4c591a..77368e5 100644 --- a/changes.md +++ b/changes.md @@ -8,11 +8,138 @@ Table of contents Full Update Showcase ```lua +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") +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() ``` Added: --- +- `multi:getStats()` + - Returns a structured table where you can access data on processors there tasks and threads: + ```lua + -- Upon calling multi:getStats() the table below is returned + get_Stats_Table { + proc_1 -- table + proc_2 -- table + ... + proc_n -- table + } + proc_Table { + tasks = {alarms,steps,loops,etc} -- All multi objects + threads = {thread_1,thread_2,thread_3,etc} -- Thread objects + } + -- Refer to the objects documentation to see how you can interact with them + ``` + - Reference the Full update showcase for the method in action - `multi:newProcessor(name, nothread)` - If no thread is true auto sets the processor as Active, so proc.run() will start without the need for proc.Start() @@ -150,10 +277,9 @@ Changed: - multi:newTStep now derives it's functionality from multi:newStep (Cut's down on code length a bit) -- Fixed the getTaskDetails to handle the new format for threads - Removed: --- +- `multi:getTasksDetails()` Remade completely and now called `multi:getStats()` - `multi:getError()` Removed when setting protect was removed - `multi:FreeMainEvent()` The new changes with connections make's this function unnecessary - `multi:OnMainConnect(func)` See above diff --git a/multi/init.lua b/multi/init.lua index 80e0b99..adca53d 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -90,109 +90,22 @@ function multi:getProcessors() return processes end -function multi:getTasksDetails(t) - if not(t) then - str = { - {"Type ","Uptime","Priority","TID"} +function multi:getStats() + local stats = { + [multi.Name] = { + threads = multi:getThreads(), + tasks = multi:getTasks() } - local count = 0 - for i,v in pairs(self.Mainloop) do - local name = v.Name or "" - if name~="" then - name = " <"..name..">" - end - count = count + 1 - table.insert(str,{v.Type:sub(1,1):upper()..v.Type:sub(2,-1)..name,multi.Round(os.clock()-v.creationTime,3),self.PriorityResolve[v.Priority],v.TID}) - end - for v,i in pairs(self.PausedObjects) do - local name = v.Name or "" - if name~="" then - name = " <"..name..">" - end - count = count + 1 - if not v.Type == "destroyed" then - table.insert(str,{v.Type:sub(1,1):upper()..v.Type:sub(2,-1)..name,multi.Round(os.clock()-v.creationTime,3),self.PriorityResolve[v.Priority],v.TID}) - end - end - if count == 0 then - table.insert(str,{"Currently no processes running!","","",""}) - end - local s = multi.AlignTable(str) - dat = "" - dat2 = "" - if multi.SystemThreads then - for i = 1,#multi.SystemThreads do - dat2 = dat2.."\n" - end - end - local load, steps = self:getLoad() - local thread_count = 0 - local process_count = 0 - if globalThreads then - local th_tab = { - {"Thread Name","Uptime","TID","Attached To"} - } - local proc_tab = { - {"Process Name", "Uptime", "PID", "Load", "CpS"} - } - for th,process in pairs(globalThreads) do - if tostring(th.isProcessThread) == "destroyed" then - globalThreads[th] = nil - elseif th.isProcessThread then - load, steps = process:getLoad() - process_count = process_count + 1 - table.insert(proc_tab,{th.Name,os.clock()-th.creationTime,(th.PID or "-1"),load,steps}) - else - thread_count = thread_count + 1 - table.insert(th_tab,{th.Name,os.clock()-th.creationTime,(th.TID or "-1"),process.Name}) - end - end - dat = multi.AlignTable(proc_tab).. "\n" - dat = dat .. "\n" .. multi.AlignTable(th_tab) - return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: "..thread_count.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or false].."\n\n"..dat..dat2.."\n\n"..s - else - return "Load on "..ProcessName[(self.Type=="process" and 1 or 2)].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\n\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or false].."\n\n"..dat2.."\n\n"..s - end - else - local load,steps = self:getLoad() - str = { - ProcessName = (self.Name or "Unnamed"), - MemoryUsage = math.ceil(collectgarbage("count")), - PriorityScheme = priorityTable[multi.defaultSettings.priority or false], - SystemLoad = multi.Round(load,2), - CyclesPerSecondPerTask = steps, - SystemThreadCount = multi.SystemThreads and #multi.SystemThreads or 0 + } + local procs = multi:getProcessors() + for i = 1, #procs do + local proc = procs[i] + stats[proc:getFullName()] = { + threads = proc:getThreads(), + tasks = proc:getTasks() } - str.Tasks = {} - str.PausedTasks = {} - str.Threads = {} - str.Processes = {} - str.Systemthreads = {} - for i,v in pairs(self.Mainloop) do - table.insert(str.Tasks,{Link = v, Type=v.Type,Name=v.Name,Uptime=os.clock()-v.creationTime,Priority=self.PriorityResolve[v.Priority],TID = v.TID}) - end - for v,i in pairs(multi.PausedObjects) do - table.insert(str.Tasks,{Link = v, Type=v.Type,Name=v.Name,Uptime=os.clock()-v.creationTime,Priority=self.PriorityResolve[v.Priority],TID = v.TID}) - end - for th,process in pairs(globalThreads) do - if tostring(th.isProcessThread) == "destroyed" then - globalThreads[th] = nil - elseif th.isProcessThread then - local load, steps = process:getLoad() - table.insert(str.Processes,{Uptime = os.clock()-th.creationTime, Name = th.Name, Link = th, TID = th.TID,Load = load,Steps = steps}) - else - table.insert(str.Threads,{Uptime = os.clock()-th.creationTime,Name = th.Name,Link = th,TID = th.TID,Attached_to = process}) - end - end - str.ThreadCount = #str.Threads - str.ProcessCount = #str.Processes - if multi.SystemThreads then - for i=1,#multi.SystemThreads do - table.insert(str.Systemthreads,{Uptime = os.clock()-multi.SystemThreads[i].creationTime,Name = multi.SystemThreads[i].Name,Link = multi.SystemThreads[i],TID = multi.SystemThreads[i].count}) - end - end - return str end + return stats end --Helpers @@ -654,6 +567,7 @@ function multi:newEvent(task) end c.OnEvent = self:newConnection() self:setPriority("core") + c:SetName(c.Type) multi:create(c) return c end @@ -675,6 +589,7 @@ function multi:newUpdater(skip) return self end c.OnUpdate = self:newConnection() + c:SetName(c.Type) multi:create(c) return c end @@ -711,6 +626,7 @@ function multi:newAlarm(set) self.Parent.Pause(self) return self end + c:SetName(c.Type) multi:create(c) return c end @@ -738,6 +654,7 @@ function multi:newLoop(func,notime) end multi:create(c) + c:SetName(c.Type) return c end @@ -795,6 +712,7 @@ function multi:newStep(start,reset,count,skip) self:Resume() return self end + c:SetName(c.Type) multi:create(c) return c end @@ -830,6 +748,7 @@ function multi:newTLoop(func,set) if func then c.OnLoop(func) end + c:SetName(c.Type) multi:create(c) return c end @@ -879,6 +798,7 @@ function multi:newTStep(start,reset,count,set) self:Resume() return self end + c:SetName(c.Type) multi:create(c) return c end @@ -957,17 +877,19 @@ function multi:newProcessor(name,nothread) handler() end end) + + c.process.__ignore = true c.process.isProcessThread = true c.process.PID = sandcount c.OnError = c.process.OnError function c:getThreads() - return self.threads + return c.threads end function c:getFullName() - return self.parent:getFullName() .. "." .. self.Name + return c.parent:getFullName() .. "." .. c.Name end function c:getName() @@ -1071,7 +993,13 @@ function multi:getThreads() end function multi:getTasks() - return self.Mainloop + local tasks = {} + for i,v in pairs(self.Mainloop) do + if not v.__ignore then + tasks[#tasks+1] = v + end + end + return tasks end function thread.request(t,cmd,...) @@ -1213,6 +1141,8 @@ function thread.pushStatus(...) t.statusconnector:Fire(...) end +local handler + function thread:newFunctionBase(generator,holdme) return function() local tfunc = {} @@ -1230,10 +1160,11 @@ function thread:newFunctionBase(generator,holdme) return nil, "Function is paused" end local rets, err - local function wait(no) - if thread.isThread() and not (no) then - return multi.hold(function() + local function wait() + if thread.isThread() then + return thread.hold(function() if err then + print("ERROR",err) return multi.NIL, err elseif rets then return cleanReturns((rets[1] or multi.NIL),rets[2],rets[3],rets[4],rets[5],rets[6],rets[7],rets[8],rets[9],rets[10],rets[11],rets[12],rets[13],rets[14],rets[15],rets[16]) @@ -1241,7 +1172,7 @@ function thread:newFunctionBase(generator,holdme) end) else while not rets and not err do - multi.scheduler:Act() + handler() end if err then return nil,err @@ -1335,6 +1266,10 @@ function thread:newThread(name,func,...) c.OnError = multi:newConnection(true,nil,true) c.OnDeath = multi:newConnection(true,nil,true) + function c:getName() + return c.Name + end + function c:isPaused() return self._isPaused end @@ -1560,7 +1495,7 @@ co_status = { _=nil r1=nil r2=nil r3=nil r4=nil r5=nil end, } -local handler = coroutine.wrap(function(self) +handler = coroutine.wrap(function(self) local temp_start while true do for start = #startme, 1, -1 do diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 4b8ac89..043c1ad 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -126,7 +126,7 @@ function multi.InitSystemThreadErrorHandler() if status == "done" or temp.returns:get("returns") then livingThreads[temp.Id] = {false, temp.Name} temp.alive = false - temp.OnDeath:Fire(temp,nil,unpack(({temp.returns:receive(0, "returns")})[2])) + temp.OnDeath:Fire(unpack(({temp.returns:receive(0, "returns")})[2])) GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) elseif status == "running" then diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua index 1be7d0d..f0af116 100644 --- a/multi/integration/loveManager/init.lua +++ b/multi/integration/loveManager/init.lua @@ -80,11 +80,11 @@ function multi:newSystemThread(name,func,...) -- If the thread is not running let's handle that. local thread_err = c.thread:getError() if thread_err == "Thread Killed!\1" then - c.OnDeath:Fire(c,"Thread Killed!") + c.OnDeath:Fire("Thread Killed!") elseif thread_err then c.OnError:Fire(c,thread_err) elseif c.stab.returns then - c.OnDeath:Fire(c,unpack(c.stab.returns)) + c.OnDeath:Fire(unpack(c.stab.returns)) c.stab.returns = nil end end) diff --git a/test.lua b/test.lua index 8493d69..af0c2cb 100644 --- a/test.lua +++ b/test.lua @@ -2,18 +2,34 @@ 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) @@ -40,32 +56,56 @@ ret3.OnStatus(function(part,whole) print(s3) end) --- local proc = multi:newProcessor("Test") --- local proc2 = multi:newProcessor("Test2") --- local proc3 = proc2:newProcessor("Test3") +loop = multi:newTLoop() --- function multi:getTaskStats() --- local stats = { --- [multi.Name] = { --- threads = multi:getThreads(), --- tasks = multi:getTasks() --- } --- } --- local procs = multi:getProcessors() --- for i = 1, #procs do --- local proc = procs[i] --- stats[proc:getFullName()] = { --- threads = proc:getThreads(), --- tasks = proc:getTasks() --- } --- end --- return stats --- end +function loop:testing() + print("testing haha") +end --- local tasks = multi:getTaskStats() +loop:Set(1) +t = loop:OnLoop(function() + print("Looping...") +end):testing() --- for i,v in pairs(tasks) do --- print("Process: "..i) --- end +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 8da4c4c1ded4428ee08e6e3350a2fb5e7033f133 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 18 Apr 2022 22:51:38 -0400 Subject: [PATCH 69/80] made lanes optional(Removed it from the dependency list) --- README.md | 3 ++- rockspecs/multi-15.2-0.rockspec | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 01549c0..537a0cd 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ - All objects now use connections internally - Connections now about 23x faster! - Updated getTasksDetails() to handle the new method of managing threads and processors +- Made lanes optional, install separately if needed Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! @@ -21,7 +22,7 @@ Link to dependencies: [lanes](https://github.com/LuaLanes/lanes) 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! +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` Discord diff --git a/rockspecs/multi-15.2-0.rockspec b/rockspecs/multi-15.2-0.rockspec index be799ec..6f7aa82 100644 --- a/rockspecs/multi-15.2-0.rockspec +++ b/rockspecs/multi-15.2-0.rockspec @@ -14,8 +14,7 @@ description = { license = "MIT" } dependencies = { - "lua >= 5.1", - "lanes", + "lua >= 5.1" } build = { type = "builtin", From 440995b1c868c851306bbe84750c3172454e2367 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 18 Apr 2022 22:56:55 -0400 Subject: [PATCH 70/80] Cleaned up rockspec file --- rockspecs/multi-15.2-0.rockspec | 2 -- 1 file changed, 2 deletions(-) diff --git a/rockspecs/multi-15.2-0.rockspec b/rockspecs/multi-15.2-0.rockspec index 6f7aa82..ac62aa9 100644 --- a/rockspecs/multi-15.2-0.rockspec +++ b/rockspecs/multi-15.2-0.rockspec @@ -20,8 +20,6 @@ build = { type = "builtin", modules = { ["multi"] = "multi/init.lua", - --["multi.compat.love2d"] = "multi/compat/love2d.lua", - --["multi.compat.lovr"] = "multi/compat/lovr.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", From b0ab40d41003fcf830533ee3f721aa2d36284ac4 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 19 Apr 2022 18:39:30 -0400 Subject: [PATCH 71/80] Fixed some bugs, a little more testing needed --- README.md | 12 +++++-- multi/init.lua | 3 +- multi/integration/lanesManager/init.lua | 3 ++ multi/integration/loveManager/init.lua | 3 ++ multi/integration/pesudoManager/init.lua | 22 +++++++----- multi/integration/pesudoManager/threads.lua | 2 +- test4.lua | 39 --------------------- 7 files changed, 31 insertions(+), 53 deletions(-) delete mode 100644 test4.lua diff --git a/README.md b/README.md index 537a0cd..e7390f2 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,9 @@ Progress is being made in [v15.3.0](https://github.com/rayaman/multi/tree/v15.3. INSTALLING ---------- -Link to dependencies: +Link to optional dependencies: [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! @@ -32,7 +33,7 @@ https://discord.gg/U8UspuA Planned features/TODO --------------------- -- [ ] Create test suite +- [ ] Create test suite (In progress, mostly done) - [ ] Network Parallelism rework Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/Documentation.md) @@ -40,7 +41,8 @@ Usage: [Check out the documentation for more info](https://github.com/rayaman/mu ```lua local multi, thread = require("multi"):init() -GLOBAL, THREAD = require("multi.integration.lanesManager"):init() +GLOBAL, THREAD = require("multi.integration.threading"):init() + multi:newSystemThread("System Thread",function() while true do THREAD.sleep(.1) @@ -48,6 +50,7 @@ multi:newSystemThread("System Thread",function() THREAD.kill() end end) + multi:newThread("Coroutine Based Thread",function() while true do io.write("Hello") @@ -55,12 +58,15 @@ multi:newThread("Coroutine Based Thread",function() thread.kill() end end) + multi:newTLoop(function(loop) print("!") loop:Destroy() os.exit() end,.3) + multi:mainloop() + --[[ while true do multi:uManager() diff --git a/multi/init.lua b/multi/init.lua index adca53d..c06c6c8 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1164,7 +1164,6 @@ function thread:newFunctionBase(generator,holdme) if thread.isThread() then return thread.hold(function() if err then - print("ERROR",err) return multi.NIL, err elseif rets then return cleanReturns((rets[1] or multi.NIL),rets[2],rets[3],rets[4],rets[5],rets[6],rets[7],rets[8],rets[9],rets[10],rets[11],rets[12],rets[13],rets[14],rets[15],rets[16]) @@ -1344,7 +1343,7 @@ function thread:newISOThread(name,func,_env,...) name = "Thread#"..threadCount end local func = isolateFunction(func,env) - return thread:newThread(name,func) + return thread:newThread(name,func,...) end multi.newThread = thread.newThread diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 043c1ad..316c24d 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -92,6 +92,9 @@ function multi:newSystemThread(name, func, ...) has_error = false end)(...) count = count + 1 + function c:getName() + return c.Name + end function c:kill() self.thread:cancel() self.alive = false diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua index f0af116..93a5a2c 100644 --- a/multi/integration/loveManager/init.lua +++ b/multi/integration/loveManager/init.lua @@ -61,6 +61,9 @@ function multi:newSystemThread(name,func,...) GLOBAL["__THREAD_"..c.ID] = {ID=c.ID, Name=c.name, Thread=c.thread} GLOBAL["__THREAD_COUNT"] = THREAD_ID THREAD_ID=THREAD_ID + 1 + function c:getName() + return c.name + end thread:newThread(function() if name:find("TempSystemThread") then local status_channel = love.thread.getChannel("__"..c.ID.."__MULTI__STATUS_CHANNEL__") diff --git a/multi/integration/pesudoManager/init.lua b/multi/integration/pesudoManager/init.lua index 6297dc5..84bdca3 100644 --- a/multi/integration/pesudoManager/init.lua +++ b/multi/integration/pesudoManager/init.lua @@ -50,7 +50,8 @@ local function split(str) return tab end -THREAD.newFunction=thread.newFunction +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,...) @@ -67,23 +68,28 @@ function multi:newSystemThread(name,func,...) thread = thread } - 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) for i = 1,#tab do env[tab[i]] = _G[tab[i]] end - --setmetatable(env,{__index=env}) - thread:newISOThread(name,func,env,...).OnError(function(self,msg) - print("ERROR:",msg) - 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 -print("Integrated Pesudo Threading!") +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 diff --git a/multi/integration/pesudoManager/threads.lua b/multi/integration/pesudoManager/threads.lua index 0cbae07..b8ee6aa 100644 --- a/multi/integration/pesudoManager/threads.lua +++ b/multi/integration/pesudoManager/threads.lua @@ -21,6 +21,7 @@ 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" @@ -30,7 +31,6 @@ local function getOS() end local function INIT(thread) - print("T",thread.sleep) local THREAD = {} local GLOBAL = {} THREAD.Priority_Core = 3 diff --git a/test4.lua b/test4.lua deleted file mode 100644 index 21dba20..0000000 --- a/test4.lua +++ /dev/null @@ -1,39 +0,0 @@ -package.path = "./?.lua;?/init.lua;"..package.path -local multi,thread = require("multi"):init() ---[[ Testing... -Before AVG: 522386 -Test 1 AVG: -]] -local sleep_for = 1 -local conn = multi:newConnection() -local test = {} -local function bench(_,steps) - print("Steps/1s: "..steps) - --os.exit() -end -proc = multi:newProcessor("Test") - -proc.Start() - -multi:newTLoop(function() - -end,1) - --- thread:newThread(function() --- while true do --- thread.sleep(1) --- print("Proc: ".. tostring(proc.isActive())) --- end --- end) - -local func = proc:newFunction(function(a,b,c) - print("Testing proc functions!") - error("Testing") - return "Please", "Smile", 123 -end) - -func("Some","tests","needed").connect(function(a,b,c) - print("Return",a,b,c) -end) - -multi:mainloop() From 3646a1a07428ea06662d8ec922462b37d9c42002 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 19 Apr 2022 18:45:14 -0400 Subject: [PATCH 72/80] Syntax changes --- multi/init.lua | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/multi/init.lua b/multi/init.lua index c06c6c8..cd91633 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1422,7 +1422,9 @@ local switch = { end, function() end--none } + setmetatable(switch,{__index=function() return function() end end}) + local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order function(th,arg1,arg2,arg3) th.func = arg1 @@ -1558,6 +1560,7 @@ function multi:newService(func) -- Priority managed threads local ap local task = thread.sleep local scheme = 1 + function c.Start() if not active then time = self:newTimer() @@ -1566,7 +1569,8 @@ function multi:newService(func) -- Priority managed threads c:OnStarted(c,Service_Data) end return c - end + end + local function process() thread.hold(function() return active @@ -1575,18 +1579,22 @@ function multi:newService(func) -- Priority managed threads task(ap) return c end + local th = thread:newThread(function() while true do process() end end) + th.OnError = c.OnError -- use the threads onerror as our own + function c.Destroy() th:kill() c.Stop() multi.setType(c,multi.DestroyedObj) return c end + function c:SetScheme(n) if type(self)=="number" then n = self end scheme = n @@ -1602,6 +1610,7 @@ function multi:newService(func) -- Priority managed threads end return c end + function c.Stop() if active then c:OnStopped(c) @@ -1613,6 +1622,7 @@ function multi:newService(func) -- Priority managed threads end return c end + function c.Pause() if active then time:Pause() @@ -1620,6 +1630,7 @@ function multi:newService(func) -- Priority managed threads end return c end + function c.Resume() if not active then time:Resume() @@ -1627,16 +1638,20 @@ function multi:newService(func) -- Priority managed threads end return c end + function c.GetUpTime() return time:Get() end + function c:SetPriority(pri) if type(self)=="number" then pri = self end p = pri c.SetScheme(scheme) return c end + multi.create(multi,c) + return c end @@ -2039,18 +2054,22 @@ multi.SetName = multi.setName -- Special Events local _os = os.exit + function os.exit(n) multi.OnExit:Fire(n or 0) _os(n) end + multi.OnError=multi:newConnection() multi.OnPreLoad = multi:newConnection() multi.OnExit = multi:newConnection(nil,nil,true) multi.m = {onexit = function() multi.OnExit:Fire() end} + if _VERSION >= "Lua 5.2" or jit then setmetatable(multi.m, {__gc = multi.m.onexit}) else multi.m.sentinel = newproxy(true) getmetatable(multi.m.sentinel).__gc = multi.m.onexit end + return multi \ No newline at end of file From fcc07171155b7dfe5e04456489844ebd60b147ce Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 19 Apr 2022 18:46:38 -0400 Subject: [PATCH 73/80] Update README.md --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e7390f2..b1abc29 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ -# Multi Version: 15.2.0 Upgrade Complete +# Multi Version: 15.3.0 **Key Changes** -- All objects now use connections internally -- Connections now about 23x faster! -- Updated getTasksDetails() to handle the new method of managing threads and processors -- Made lanes optional, install separately if needed +- Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! From f74f85759b034d581daa4e8fb063c095b70da197 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 28 May 2022 22:52:20 -0400 Subject: [PATCH 74/80] Cleaned up test files --- test.lua | 104 ------------------------------------------------------ test3.lua | 51 -------------------------- 2 files changed, 155 deletions(-) delete mode 100644 test3.lua diff --git a/test.lua b/test.lua index af0c2cb..a426b01 100644 --- a/test.lua +++ b/test.lua @@ -2,110 +2,6 @@ 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") -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 diff --git a/test3.lua b/test3.lua deleted file mode 100644 index c91b67b..0000000 --- a/test3.lua +++ /dev/null @@ -1,51 +0,0 @@ -package.path = "./?.lua;?/init.lua;"..package.path -local multi,thread = require("multi"):init{print=true} ---local GLOBAL,THREAD = require("multi.integration.lanesManager"):init() - --- func = THREAD:newFunction(function(a,b,c) --- print("Hello Thread!",a,b,c) --- return 1,2,3 --- end) - --- func2 = THREAD:newFunction(function(a,b,c) --- print("Hello Thread2!",a,b,c) --- THREAD.sleep(1) --- return 10,11,12 --- end) - --- multi:newThread("Test thread",function() --- handler = func(4,5,6) --- handler2 = func2(7,8,9) --- thread.hold(handler.OnReturn + handler2.OnReturn) --- print("Function Done",handler.getReturns()) --- print("Function Done",handler2.getReturns()) --- end) - - --- multi:benchMark(1):OnBench(function(sec,steps) --- print("Steps:",steps) --- os.exit() --- end) -print("Running benchmarks! ",_VERSION) -local sleep_for = 1 -local a = 0 -local c = 1 -local function bench(t,step) - a = a + step - c = c + 1 - os.exit() -end ---multi:benchMark(sleep_for,multi.Priority_Idle,"Idle:"):OnBench(bench) ---multi:benchMark(sleep_for,multi.Priority_Very_Low,"Very Low:"):OnBench(bench) ---multi:benchMark(sleep_for,multi.Priority_Low,"Low:"):OnBench() ---multi:benchMark(sleep_for,multi.Priority_Below_Normal,"Below Normal:"):OnBench(bench) ---multi:benchMark(sleep_for,multi.Priority_Normal,"Normal:"):OnBench(bench) ---multi:benchMark(sleep_for,multi.Priority_Above_Normal,"Above Normal:"):OnBench(bench) ---multi:benchMark(sleep_for,multi.Priority_High,"High:"):OnBench(bench) ---multi:benchMark(sleep_for,multi.Priority_Very_High,"Very High:"):OnBench(bench) -multi:benchMark(sleep_for,multi.Priority_Core,"Core:"):OnBench(bench) -multi.OnExit(function() - print("Total: ".. a) -end) - -multi:mainloop() From 831abeaccffa065c9e6fa4a4923c45bb9dfb0410 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 28 May 2022 23:04:51 -0400 Subject: [PATCH 75/80] Testing functions --- test.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test.lua b/test.lua index a426b01..91d4732 100644 --- a/test.lua +++ b/test.lua @@ -2,6 +2,17 @@ package.path = "./?/init.lua;"..package.path multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.threading"):init() +test = thread:newFunction(function() + return 1,2 +end) +ref = test() +ref.OnError(function(...) + print("Got Error",...) +end) + +ref.OnReturn(function(...) + print("Got Returns",...) +end) multi:mainloop() \ No newline at end of file From 89e76ce31dcc44dc08e4ddd876cab825ac262be4 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 6 Jun 2022 19:19:35 -0400 Subject: [PATCH 76/80] Fixed issue where threaded functions were exiting improperly --- multi/init.lua | 4 ++++ multi/integration/lanesManager/init.lua | 6 +++--- test.lua | 26 +++++++++++++------------ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/multi/init.lua b/multi/init.lua index cd91633..f74e3bc 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1,3 +1,4 @@ +print("Dev") --[[ MIT License @@ -1471,6 +1472,7 @@ 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 @@ -1478,6 +1480,7 @@ co_status = { ["normal"] = function(thd,ref) end, ["running"] = function(thd,ref) end, ["dead"] = function(thd,ref,task,i,th) + if ref.__processed then return end if _ then ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) else @@ -1494,6 +1497,7 @@ co_status = { end end _=nil r1=nil r2=nil r3=nil r4=nil r5=nil + ref.__processed = true end, } handler = coroutine.wrap(function(self) diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 316c24d..4bada5a 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -139,19 +139,19 @@ function multi.InitSystemThreadErrorHandler() elseif status == "error" then livingThreads[temp.Id] = {false, temp.Name} temp.alive = false - temp.OnError:Fire(temp,nil,unpack(temp.returns:receive(0,"returns") or {"Thread Killed!"})) + temp.OnError:Fire(temp,unpack(temp.returns:receive(0,"returns") or {"Thread Killed!"})) GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) elseif status == "cancelled" then livingThreads[temp.Id] = {false, temp.Name} temp.alive = false - temp.OnError:Fire(temp,nil,"thread_cancelled") + temp.OnError:Fire(temp,"thread_cancelled") GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) elseif status == "killed" then livingThreads[temp.Id] = {false, temp.Name} temp.alive = false - temp.OnError:Fire(temp,nil,"thread_killed") + temp.OnError:Fire(temp,"thread_killed") GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) end diff --git a/test.lua b/test.lua index 91d4732..fc484f9 100644 --- a/test.lua +++ b/test.lua @@ -1,18 +1,20 @@ -package.path = "./?/init.lua;"..package.path +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 multi, thread = require("multi"):init{print=true} -GLOBAL, THREAD = require("multi.integration.threading"):init() +GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -test = thread:newFunction(function() +test = THREAD:newFunction(function() + PNT() return 1,2 +end,true) +multi:newThread(function() + while true do + print("...") + thread.sleep(1) + end end) - -ref = test() -ref.OnError(function(...) - print("Got Error",...) -end) - -ref.OnReturn(function(...) - print("Got Returns",...) -end) +multi:newAlarm(.1):OnRing(function() os.exit() end) +print(test()) +print("Hi!") 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 77/80] 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 78/80] 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 79/80] 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 80/80] 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