net/multi/threading.lua
Ryan 7601fc636d Updated to 1.8.0
Added:
SystemThreadedBenchmark
SystemThreadedQueue
Fixed a bunch of bugs in the intergrations and regular multi objects
Fixed Error management in threads
All errors trigger the multi.OnError connection
Module creation support improved
added more examples
added Type to threaded objects
2017-06-28 22:40:04 -04:00

187 lines
5.2 KiB
Lua

--[[
MIT License
Copyright (c) 2017 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, sublicense, 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.
]]
require("multi")
thread={}
multi.GlobalVariables={}
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.sleep(n)
coroutine.yield({"_sleep_",n or 0})
end
function thread.hold(n)
coroutine.yield({"_hold_",n or function() return true end})
end
function thread.skip(n)
coroutine.yield({"_skip_",n or 0})
end
function thread.kill()
coroutine.yield({"_kill_",":)"})
end
function thread.yeild()
coroutine.yield({"_sleep_",0})
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 thread.testFor(name,val,sym)
thread.hold(function() return thread.get(name)~=nil end)
return thread.get(name)
end
function multi:newTBase(ins)
local c = {}
c.Active=true
c.func={}
c.ender={}
c.Id=0
c.PId=0
c.Parent=self
c.held=false
return c
end
function multi:newThread(name,func)
local c={}
c.ref={}
c.Name=name
c.thread=coroutine.create(func)
c.sleep=1
c.Type="thread"
c.firstRunDone=false
c.timer=multi.scheduler:newTimer()
c.ref.Globals=self:linkDomain("Globals")
function c.ref:send(name,val)
ret=coroutine.yield({Name=name,Value=val})
self:syncGlobals(ret)
end
function c.ref:get(name)
return self.Globals[name]
end
function c.ref:kill()
err=coroutine.yield({"_kill_"})
if err then
error("Failed to kill a thread! Exiting...")
end
end
function c.ref:sleep(n)
if type(n)=="function" then
ret=coroutine.yield({"_hold_",n})
self:syncGlobals(ret)
elseif type(n)=="number" then
n = tonumber(n) or 0
ret=coroutine.yield({"_sleep_",n})
self:syncGlobals(ret)
else
error("Invalid Type for sleep!")
end
end
function c.ref:syncGlobals(v)
self.Globals=v
end
table.insert(self:linkDomain("Threads"),c)
if not multi.scheduler:isActive() then
multi.scheduler:Resume()
end
end
multi:setDomainName("Threads")
multi:setDomainName("Globals")
multi.scheduler=multi:newUpdater()
multi.scheduler.Type="scheduler"
function multi.scheduler:setStep(n)
self.skip=tonumber(n) or 24
end
multi.scheduler.skip=0
multi.scheduler.counter=0
multi.scheduler.Threads=multi:linkDomain("Threads")
multi.scheduler.Globals=multi:linkDomain("Globals")
multi.scheduler:OnUpdate(function(self)
self.counter=self.counter+1
for i=#self.Threads,1,-1 do
ret={}
if coroutine.status(self.Threads[i].thread)=="dead" then
table.remove(self.Threads,i)
else
if self.Threads[i].timer:Get()>=self.Threads[i].sleep then
if self.Threads[i].firstRunDone==false then
self.Threads[i].firstRunDone=true
self.Threads[i].timer:Start()
_,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref)
else
_,ret=coroutine.resume(self.Threads[i].thread,self.Globals)
end
if _==false then
self.Parent.OnError:Fire(self.Threads[i],ret)
print("Error in thread: <"..self.Threads[i].Name.."> "..ret)
end
if ret==true or ret==false then
print("Thread Ended!!!")
ret={}
end
end
if ret then
if ret[1]=="_kill_" then
table.remove(self.Threads,i)
elseif ret[1]=="_sleep_" then
self.Threads[i].timer:Reset()
self.Threads[i].sleep=ret[2]
elseif ret[1]=="_skip_" then
self.Threads[i].timer:Reset()
self.Threads[i].sleep=math.huge
local event=multi:newEvent(function(evnt) return multi.scheduler.counter>=evnt.counter end)
event.link=self.Threads[i]
event.counter=self.counter+ret[2]
event:OnEvent(function(evnt)
evnt.link.sleep=0
end)
elseif ret[1]=="_hold_" then
self.Threads[i].timer:Reset()
self.Threads[i].sleep=math.huge
local event=multi:newEvent(ret[2])
event.link=self.Threads[i]
event:OnEvent(function(evnt)
evnt.link.sleep=0
end)
elseif ret.Name then
self.Globals[ret.Name]=ret.Value
end
end
end
end
end)
multi.scheduler:setStep()
multi.scheduler:Pause()
multi.OnError=multi:newConnection()