--[[ MIT License Copyright (c) 2020 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sub-license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] local multi = {} local mainloopActive = false local isRunning = false local clock = os.clock local thread = {} if not _G["$multi"] then _G["$multi"] = {multi=multi,thread=thread} end multi.Version = "14.2.0" multi.stage = "stable" multi.Name = "multi.root" multi.Mainloop = {} multi.Garbage = {} multi.ender = {} multi.Children = {} multi.Active = true multi.Type = "mainprocess" multi.Rest = 0 multi._type = type multi.queue = {} multi.clock = os.clock multi.time = os.time multi.LinkedPath = multi multi.lastTime = clock() multi.Priority_Core = 1 multi.Priority_Very_High = 4 multi.Priority_High = 16 multi.Priority_Above_Normal = 64 multi.Priority_Normal = 256 multi.Priority_Below_Normal = 1024 multi.Priority_Low = 4096 multi.Priority_Very_Low = 16384 multi.Priority_Idle = 65536 multi.PriorityResolve = { [1]="Core", [4]="High", [16]="Above Normal", [64]="Normal", [256]="Below Normal", [1024]="Low", [4096]="Idle", } multi.PStep = 1 multi.PList = {multi.Priority_Core,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Idle} multi.PriorityTick=1 multi.Priority=multi.Priority_High multi.threshold=256 multi.threstimed=.001 function multi.init() multi.NIL = {Type="NIL"} return _G["$multi"].multi,_G["$multi"].thread end -- System function multi.Stop() mainloopActive=false end --Processor local priorityTable = {[0]="Round-Robin",[1]="Just-Right",[2]="Top-heavy",[3]="Timed-Based-Balancer"} local ProcessName = {[true]="SubProcessor",[false]="MainProcessor"} function multi:getTasksDetails(t) if t == "string" or not t then str = { {"Type ","Uptime","Priority","TID"} } 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(multi.PausedObjects) 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 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 = multi:getLoad() if thread.__threads then for i=1,#thread.__threads do dat = dat .. "\n" end 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\nThreads Running: "..#thread.__threads.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..s.."\n\n"..dat..dat2 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\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..s..dat2 end elseif t == "t" or t == "table" then local load,steps = multi:getLoad() str = { ProcessName = (self.Name or "Unnamed"), ThreadCount = #thread.__threads, MemoryUsage = math.ceil(collectgarbage("count")), PriorityScheme = priorityTable[multi.defaultSettings.priority or 0], SystemLoad = multi.Round(load,2), CyclesPerSecondPerTask = steps, SystemThreadCount = multi.SystemThreads and #multi.SystemThreads or 0 } str.Tasks = {} str.PausedTasks = {} str.Threads = {} 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 i=1,#thread.__threads do table.insert(str.Threads,{Uptime = os.clock()-thread.__threads[i].creationTime,Name = thread.__threads[i].Name,Link = thread.__threads[i],TID = thread.__threads[i].TID}) end 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 end --Helpers 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=multi:newBase() c.Type='timemaster' c.timer=multi: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=='mainprocess' 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=='mainprocess' 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=='mainprocess' then local c=self:getChildren() for i=1,#c do self.OnObjectDestroyed:Fire(c[i]) c[i]:Destroy() end 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) end function multi:setName(name) self.Name = name return self end --Constructors [CORE] local _tid = 0 function multi:newBase(ins) if not(self.Type=='mainprocess' or self.Type=='process' or self.Type=='queue') 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' 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 c.Parent=self c.lock = false setmetatable(c,{__call=function(self,...) local t = ... if type(t)=="table" then for i,v in pairs(t) do if v==self then return self:Fire(select(2,...)) end end return self:connect(...) else return self:connect(...) end end}) c.Type='connector' c.func={} c.ID=0 c.protect=protect or true c.connections={} c.FC=0 function c:holdUT(n) local n=n or 0 self.waiting=true local count=0 local id=self:connect(function() count = count + 1 if n<=count then self.waiting=false end end) repeat self.Parent:uManager(multi.defaultSettings) until self.waiting==false id:Destroy() return self end c.HoldUT=c.holdUT function c:getConnection(name,ignore) if ignore then return self.connections[name] or CRef else return self.connections[name] or self end end function c:Lock() c.lock = true end function c:Unlock() c.lock = false 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]) 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 end function c:Bind(t) local temp = self.func self.func=t return temp end function c:Remove() local temp = self.func self.func={} 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}) else table.insert(self.func,1,{func,self.ID}) end local temp = { Link=self.func, func=func, Type="connector_link", ID=self.ID, Parent=self, connect = function(s,...) return self:connect(...) end } setmetatable(temp,{__call=function(s,...) return self:connect(...) end}) function temp:Fire(...) if self.Parent.lock then return end if self.Parent.protect then local t=pcall(self.func,...) if t then return t end else return self.func(...) 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) 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 end if self.callback then self.callback(temp) end return temp end function c:connect(...)--func,name,num local tab = {...} local funcs={} for i=1,#tab do if type(tab[i])=="function" then funcs[#funcs+1] = tab[i] end end if #funcs>1 then local ret = {} for i=1,#funcs do table.insert(ret,conn_helper(self,funcs[i])) end return ret else return conn_helper(self,tab[1],tab[2],tab[3]) end end c.Connect=c.connect c.GetConnection=c.getConnection if not(ignoreconn) then multi:create(c) end return c end multi.OnObjectCreated=multi:newConnection() multi.OnObjectDestroyed=multi:newConnection() multi.OnLoad = multi:newConnection(nil,nil,true) ignoreconn = false function multi:newProcessor(file) if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end local c = {} setmetatable(c, {__index = multi}) c.Parent=self c.Active=true c.func={} c.Type='process' c.Mainloop={} c.Garbage={} c.Children={} c.Active=false c.Rest=0 c.queue={} c.l=self:newLoop(function(self,dt) if self.link.Active then c:uManager() end end) c.l.link = c c.l.Type = "processor" function c:getController() return c.l end function c:Start() self.Active = true return self end function c:Resume() self.Active = false return self end function c:setName(name) c.l.Name = name return self end function c:Pause() if self.l then self.l:Pause() end return self end function c:Remove() if self.Type == "process" then self:__Destroy() self.l:Destroy() else self:__Destroy() end end function c:Destroy() if self == c then self.l:Destroy() else for i = #c.Mainloop,1,-1 do if c.Mainloop[i] == self then table.remove(c.Mainloop,i) break end end end end if file then self.Cself=c loadstring('local process=multi.Cself '..io.open(file,'rb'):read('*all'))() end self:create(c) return c end function multi:newTimer() local c={} c.Type='timer' local time=0 local count=0 local paused=false function c:Start() time=os.clock() return self end function c:Get() if self:isPaused() then return time end return (clock()-time)+count end function c:isPaused() return paused end c.Reset=c.Start function c:Pause() time=self:Get() paused=true return self end function c:Resume() paused=false time=os.clock()-time return self end self:create(c) return c end --Core Actors function multi:newEvent(task) local c=self:newBase() c.Type='event' c.Task=task or function() end function c:Act() local t = {self.Task(self)} if t[1] then self:Pause() self.returns = t for _E=1,#self.func do self.func[_E](self) end end end function c:SetTask(func) self.Task=func return self end function c:OnEvent(func) table.insert(self.func,func) return self end self:setPriority("core") self:create(c) return c end function multi:newUpdater(skip) local c=self:newBase() c.Type='updater' c.pos=1 c.skip=skip or 1 function c:Act() if self.pos>=self.skip then self.pos=0 for i=1,#self.func do self.func[i](self) end end self.pos=self.pos+1 end function c:SetSkip(n) self.skip=n return self end c.OnUpdate=self.OnMainConnect self:create(c) return c end function multi:newAlarm(set) local c=self:newBase() c.Type='alarm' c:setPriority("Low") c.set=set or 0 local count = 0 local t = clock() function c:Act() if clock()-t>=self.set then self:Pause() self.Active=false for i=1,#self.func do self.func[i](self) end t = clock() end end function c:Resume() self.Parent.Resume(self) t = count + t return self end function c:Reset(n) if n then self.set=n end self:Resume() t = clock() return self end function c:OnRing(func) table.insert(self.func,func) return self end function c:Pause() count = clock() self.Parent.Pause(self) return self end self:create(c) return c end 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 end function c:OnLoop(func) table.insert(funcs,func) return self end self: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) self:create(c) return c end function multi:newStep(start,reset,count,skip) local c=self:newBase() think=1 c.Type='step' c.pos=start or 1 c.endAt=reset or math.huge 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 think=-1 end end function c:Act() 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) end 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.pos=self.start end end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 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 function c:Break() self.Active=nil return self end function c:Update(start,reset,count,skip) self.start=start or self.start self.endAt=reset or self.endAt self.skip=skip or self.skip self.count=count or self.count self:Resume() return self end self:create(c) return c end function multi:newTLoop(func,set) local c=self:newBase() c.Type='tloop' c.set=set or 0 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 self.life=self.life+1 for i=1,#self.func do self.func[i](self,self.life) end self.timer:Reset() end end function c:Resume() self.Parent.Resume(self) self.timer:Resume() return self end function c:Pause() self.timer:Pause() self.Parent.Pause(self) return self end function c:OnLoop(func) table.insert(self.func,func) return self end self: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:newBase() think=1 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 self.endAt=reset or self.endAt self.set=set or self.set self.count=count or self.count or 1 self.timer=clock() self:Resume() return self end function c:Act() 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) end 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.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 end function c:Reset(n) if n then self.set=n end self.timer=clock() self:Resume() return self end self:create(c) return c end local scheduledjobs = {} local sthread function multi:scheduleJob(time,func) if not sthread then sthread = multi:newThread("JobScheduler",function() local time = os.date("*t", os.time()) local ready = false while true do thread.sleep(1) -- Every second we do some tests time = os.date("*t", os.time()) for j,job in pairs(scheduledjobs) do ready = true for k,v in pairs(job[1]) do if not (v == time[k]) then ready = false end end if ready and not job[3] then job[2]() job[3] = true elseif not ready and job[3] then job[3] = false end end end end) end table.insert(scheduledjobs,{time, func,false}) 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} thread.requests = {} function thread.request(t,cmd,...) thread.requests[t.thread] = {cmd,{...}} end function thread.getRunningThread() local t = coroutine.running() if t then for i,v in pairs(threads) do if t==v.thread then return v 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) thread._Requests() dRef[1] = "_hold_" dRef[2] = n or dFunc return coroutine.yield(dRef) 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() return coroutine.running()~=nil 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,no) if thread.isThread() and not(no) then if type(func) == "function" or type(func) == "table" then return thread.hold(func) end return thread.sleep(func) end local death = false if type(func)=="number" then multi:newThread("Hold_func",function() thread.sleep(func) death = true end) while not death do multi.scheduler:Act() end else local rets multi:newThread("Hold_func",function() rets = {thread.hold(func)} death = true end) while not death do multi.scheduler:Act() end return unpack(rets) end end function multi.holdFor(n,func) local temp multi:newThread(function() thread.sleep(n) temp = true end) return multi.hold(function() if func() then return func() elseif temp then return multi.NIL, "TIMEOUT" end end) end function thread:newFunction(func,holdme) return function(...) 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 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 local t = multi: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 = { isTFunc = true, wait = wait, connect = function(f) t.OnDeath(function(self,status,...) f(...) end) t.OnError(function(self,err) f(err) end) end } return temp end end function multi:newThread(name,func,...) multi.OnLoad:Fire() local func = func or name if type(name) == "function" then name = "Thread#"..threadCount end local env = {} setmetatable(env,{ __index = Gref, __newindex = function(t,k,v) if type(v)=="function" then rawset(t,k,thread:newFunction(v)) else Gref[k]=v end end }) setfenv(func,env) local c={} c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} c.startArgs = {...} c.ref={} c.Name=name c.thread=coroutine.create(func) c.sleep=1 c.Type="thread" c.TID = threadid c.firstRunDone=false c.timer=multi:newTimer() c._isPaused = false c.returns = {} c.OnError = multi:newConnection(true,nil,true) c.OnDeath = multi:newConnection(true,nil,true) 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 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 c.Destroy = 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) if initT==false then multi.initThreads() end c.creationTime = os.clock() threadid = threadid + 1 self:create(c) return c end function multi.initThreads(justThreads) initT = true multi.scheduler=multi:newLoop():setName("multi.thread") multi.scheduler.Type="scheduler" function multi.scheduler:setStep(n) self.skip=tonumber(n) or 24 end multi.scheduler.skip=0 local t0,t1,t2,t3,t4,t5,t6 local r1,r2,r3,r4,r5,r6 local ret,_ local function CheckRets(i) if ret~=nil and not(threads[i].isError) then if not threads[i] then return end if not _ then threads[i].isError = true threads[i].TempRets[1] = ret return end 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 end end local function helper(i) if type(ret)=="table" then if ret[1]=="_kill_" then threads[i].OnDeath:Fire(threads[i],"killed",ret,r1,r2,r3,r4,r5,r6) multi.setType(threads[i],multi.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 if type(ret[2])=="table" and ret[2].Type=='connector' then local letsgo ret[2](function(...) letsgo = {...} end) ret[2] = function() if letsgo then return unpack(letsgo) end end end threads[i].func = ret[2] threads[i].task = "hold" threads[i].__ready = false ret = nil elseif ret[1]=="_holdF_" then threads[i].sec = ret[2] threads[i].func = ret[3] threads[i].task = "holdF" threads[i].time = clock() threads[i].__ready = false ret = nil elseif ret[1]=="_holdW_" then 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 ret = nil end CheckRets(i) end end multi.scheduler:OnLoop(function(self) for i=#threads,1,-1 do if threads[i].isError then threads[i].OnError:Fire(threads[i],unpack(threads[i].TempRets)) multi.setType(threads[i],multi.DestroyedObj) table.remove(threads,i) end if threads[i] and not threads[i].__started then if coroutine.running() ~= threads[i].thread then _,ret,r1,r2,r3,r4,r5,r6=coroutine.resume(threads[i].thread,unpack(threads[i].startArgs)) end threads[i].__started = true helper(i) end if threads[i] and not _ then print("TESTING",threads[i]) threads[i].OnError:Fire(threads[i],unpack(threads[i].TempRets)) threads[i].isError = true end if threads[i] and coroutine.status(threads[i].thread)=="dead" then threads[i].OnDeath:Fire(threads[i],"ended",unpack(threads[i].TempRets or {})) multi.setType(threads[i],multi.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 --GOHERE t0,t1,t2,t3,t4,t5,t6 = threads[i].func() if t0~=nil then if t0==multi.NIL then t0 = nil end threads[i].task = "" threads[i].__ready = true 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 t0,t1,t2,t3,t4,t5,t6 = threads[i].func() if t0~=nil 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 = "TIMEOUT" end elseif threads[i] and threads[i].task == "holdW" then threads[i].pos = threads[i].pos + 1 t0,t1,t2,t3,t4,t5,t6 = threads[i].func() if t0~=nil 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 = "TIMEOUT" end end 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=coroutine.resume(threads[i].thread,t0,t1,t2,t3,t4,t5,t6) CheckRets(i) end end helper(i) end end) if justThreads then while true do multi.scheduler:Act() end end end function multi:threadloop() multi.initThreads(true) end function multi:newService(func) -- Priority managed threads local c = {} c.Type = "service" c.OnStopped = multi:newConnection() c.OnStarted = multi:newConnection() local Service_Data = {} local active local time local p = multi.Priority_Normal local ap local task = thread.sleep local scheme = 1 function c.Start() if not active then time = multi:newTimer() time:Start() active = true c:OnStarted(c,Service_Data) end return c end local function process() thread.hold(function() return active end) func(c,Service_Data) task(ap) return c end local th = multi: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) end function c:SetScheme(n) if type(self)=="number" then n = self end scheme = n if math.abs(n)==1 then ap = (p^(1/3))/10 if ap==.1 then task = thread.yield end task = thread.sleep elseif math.abs(n)==2 then ap = math.abs(p-1)*32+1 task = thread.skip elseif math.abs(n)==3 then -- This is a time based pirority manager. Things that take long to run get end return c end function c.Stop() if active then c:OnStopped(c) Service_Data = {} time:Reset() time:Pause() time = nil active = false end return c end function c.Pause() if active then time:Pause() active = false end return c end function c.Resume() if not active then time:Resume() active = true 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) end multi.create(multi,c) return c end multi.Jobs = multi:newService(function(self,jobs) local job = table.remove(jobs,1) if job and job.removed==nil then job.func() end end) multi.Jobs.OnStarted(function(self,jobs) function self:newJob(func,name) table.insert(jobs,{ func = func, name = name, removeJob = function(self) self.removed = true end }) end function self:getJobs(name) local tab = {} if not name then return jobs end for i=1,#jobs do if name == jobs[i].name then table.insert(tab,jobs[i]) end end return tab end function self:removeJobs(name) for i=1,#jobs do if name ~= nil and name == jobs[i].name then jobs[i]:removeJob() elseif name == nil then jobs[i]:removeJob() end end end end) multi.Jobs.SetPriority(multi.Priority_Normal) multi.Jobs.Start() -- Multi runners function multi:lightloop() if not isRunning then local Loop=self.Mainloop while true do for _D=#Loop,1,-1 do if Loop[_D].Active then self.CID=_D if not protect then Loop[_D]:Act() end end end end end end function multi:mainloop(settings) 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_LB = p_i if not isRunning then local protect = false local priority = false local stopOnError = true local delay = 3 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 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 multi.OnLoad:Fire() while mainloopActive do if 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 protect then Loop[_D]:Act() else local status, err=pcall(Loop[_D].Act,Loop[_D]) if err then Loop[_D].error=err self.OnError:Fire(Loop[_D],err) if stopOnError then Loop[_D]:Destroy() end end end end end end end 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 if not protect then Loop[_D]:Act() else local status, err=pcall(Loop[_D].Act,Loop[_D]) if err then Loop[_D].error=err self.OnError:Fire(Loop[_D],err) if stopOnError then Loop[_D]: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 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 if not protect then Loop[_D]:Act() else local status, err=pcall(Loop[_D].Act,Loop[_D]) if err then Loop[_D].error=err self.OnError:Fire(Loop[_D],err) if stopOnError then Loop[_D]:Destroy() end end end end end end 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 if not protect then if sRef.solid then sRef:Act() 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 = p_c else Loop[_D].Priority = P_LB end end else if Loop[_D].solid then Loop[_D]:Act() solid = true else time, status, err=multi.timer(pcall,Loop[_D].Act,Loop[_D]) Loop[_D].solid = true solid = false end if Loop[_D] and not solid then if time == 0 then Loop[_D].Priority = p_c else Loop[_D].Priority = P_LB end end if err then Loop[_D].error=err self.OnError:Fire(Loop[_D],err) if stopOnError then Loop[_D]: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 if Loop[_D] then if Loop[_D].Active then self.CID=_D if not protect then Loop[_D]:Act() else local status, err=pcall(Loop[_D].Act,Loop[_D]) if err then Loop[_D].error=err self.OnError:Fire(Loop[_D],err) if stopOnError then Loop[_D]:Destroy() end end end end end end end end else return "Already Running!" end end function multi:uManager(settings) 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 end function multi:uManagerRef(settings) 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() else local status, err=pcall(Loop[_D].Act,Loop[_D]) 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 if not multi.defaultSettings.protect then Loop[_D]:Act() else local status, err=pcall(Loop[_D].Act,Loop[_D]) 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 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() else local status, err=pcall(Loop[_D].Act,Loop[_D]) 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() 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() solid = true else time, status, err=multi.timer(pcall,Loop[_D].Act,Loop[_D]) 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() else local status, err=pcall(Loop[_D].Act,Loop[_D]) if err then Loop[_D].error=err self.OnError:Fire(Loop[_D],err) end end end end end end end end -------- -- UTILS -------- function table.merge(t1, t2) 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 if table.unpack and not unpack then unpack=table.unpack end multi.DestroyedObj = { Type = "destroyed", } local function uni() return multi.DestroyedObj end local function uniN() end function multi.setType(obj,t) if t == multi.DestroyedObj then for i,v in pairs(obj) do obj[i] = nil end setmetatable(obj, { __index = function(t,k) return setmetatable({},{__index = uni,__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}) 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 }) end end setmetatable(multi.DestroyedObj, { __index = function(t,k) return setmetatable({},{__index = uni,__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}) 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, protect = false, } function multi:enableLoadDetection() if multi.maxSpd then return end -- here we are going to run a quick benchMark solo local temp = multi:newProcessor() temp:Start() local t = os.clock() local stop = false temp:benchMark(.01):OnBench(function(time,steps) stop = steps end) while not stop do temp:uManager() end temp:Destroy() multi.maxSpd = stop end local busy = false local lastVal = 0 local bb = 0 function multi:getLoad() if not multi.maxSpd then multi:enableLoadDetection() end if busy then return lastVal end local val = nil if thread.isThread() then local bench multi: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 multi:benchMark(.01):OnBench(function(time,steps) bench = steps bb = steps end) while not bench do multi:uManager() end bench = bench^1.5 val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) busy = false end if val<0 then val = 0 end if val > 100 then val = 100 end lastVal = val return val,bb*100 end function multi:setPriority(s) if type(s)==number then self.Priority=s elseif type(s)=='string' then if s:lower()=='core' or s:lower()=='c' then self.Priority=self.Priority_Core elseif s:lower()=="very high" or s:lower()=="vh" then self.Priority=self.Priority_Very_High elseif s:lower()=='high' or s:lower()=='h' then self.Priority=self.Priority_High elseif s:lower()=='above' or s:lower()=='a' then self.Priority=self.Priority_Above_Normal elseif s:lower()=='normal' or s:lower()=='n' then self.Priority=self.Priority_Normal elseif s:lower()=='below' or s:lower()=='b' then self.Priority=self.Priority_Below_Normal elseif s:lower()=='low' or s:lower()=='l' then self.Priority=self.Priority_Low elseif s:lower()=="very low" or s:lower()=="vl" then self.Priority=self.Priority_Very_Low 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 self.PrioritySet = true end end function multi:ResetPriority() self.Priority = self.defPriority end function os.getOS() if package.config:sub(1,1)=='\\' then return 'windows' else return 'unix' end end if os.getOS()=='windows' then function os.sleep(n) if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end end else function os.sleep(n) os.execute('sleep ' .. tonumber(n)) end end function multi.randomString(n) local str = '' local strings = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'} for i=1,n do str = str..''..strings[math.random(1,#strings)] end return str end function multi:getParentProcess() return self.Mainloop[self.CID] end function multi:getChildren() return self.Mainloop end function multi:getVersion() return multi.Version end function multi:getPlatform() if love then if love.thread then return "love2d" end else return "lanes" end end 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) if t>sec then if pt then multi.print(pt.." "..c.." Steps in "..sec.." second(s)!") end self.tt(sec,c) self:Destroy() else c=c+1 end end) temp:setPriority(p or 1) function temp:OnBench(func) self.tt=func end self.tt=function() end return temp end function multi.Round(num, numDecimalPlaces) local mult = 10^(numDecimalPlaces or 0) return math.floor(num * mult + 0.5) / mult end function multi.AlignTable(tab) local longest = {} local columns = #tab[1] local rows = #tab for i=1, columns do longest[i] = -math.huge end for i = 1,rows do for j = 1,columns do tab[i][j] = tostring(tab[i][j]) if #tab[i][j]>longest[j] then longest[j] = #tab[i][j] end end end for i = 1,rows do for j = 1,columns do if tab[i][j]~=nil and #tab[i][j]= "Lua 5.2" 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