multi/oldversions/MultiManager(1.2.0).lua
Ryan eaaf5d80a7 Fixed System Threaded Queues!
Faster
Added older versions
Other bug fixes
More examples
2017-07-07 23:38:54 -04:00

1435 lines
32 KiB
Lua

if not bin then
print('Warning the \'bin\' library wasn\'t required! multi:tofile(path) and the multi:fromfile(path,int) features will not work!')
end
if table.unpack then
unpack=table.unpack
end
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
multi = {}
multi.Version={'A',2,0}-- History: EventManager,EventManager+,MultiManager <-- Current After 6.3.0 Versioning scheme was altered. A.0.0
multi.help=[[
For a list of features do print(multi.Features)
For a list of changes do print(multi.changelog)
For current version do print(multi.Version)
For current stage do print(multi.stage)
For help do print(multi.help) :D
]]
multi.stage='stable'
multi.Features='Current Version: '..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[
MultiManager has 19 Objects: # indicates most commonly used 1-19 1 being the most used by me
+Events #7
+Alarms #2
+Loops #3
+Steps #4
+TSteps #6
+Triggers #16
+Tasks #12
+Connections #1 -- This is a rather new feature of this library, but has become the most useful for async handling. Knowing this is already 50% of this library
+Timers #14 -- this was tricky because these make up both Alarms and TSteps, but in purly using this standalone is almost non existent
+Jobs #11
+Process #10
+Conditions #15
+Ranges #8
+Threads #13
+Functions #5
+Queuers #17
+Updaters #9
+Watchers #18
+CustomObjects #19
Constructors [Runners]
---------------------- Note: multi is the main Processor Obj It cannot be paused or destroyed (kinda)
intObj=multi:newProcess([string: FILE defualt: nil])
intObj=multi:newQueuer([string: FILE defualt: nil])
Constructors [ACTORS]
--------------------- Note: everything is a multiObj!
eventObj=multi:newEvent([function: TASK defualt: function() end])
alarmObj=multi:newAlarm([number: SET defualt: 0])
loopObj=multi:newLoop([function: FUNC])
stepObj=multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0])
tstepObj=multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1])
updaterObj=multi:newUpdater([number: SKIP defualt: 0])
watcherObj=multi:newWatcher(table: NAMESPACE,string: NAME)
multiObj=multi:newCustomObject([table: OBJREF],[string: T='process'])
void=multi:newThread(string: name,function: func)
Constructors [Semi-ACTORS]
--------------------------
multi:newJob(function: func,[string: name])
multi:newRange(number: a,number: b,[number: c])
multi:newCondition(func)
Constructors [NON-ACTORS]
-------------------------
multi:newTrigger(function: func)
multi:newTask(function: func)
multi:newConnection()
multi:newTimer()
multi:newFunction(function: func)
]]
multi.changelog=[[Changelog starts at Version A.0.0
New in A.0.0
Nothing really however a changelog will now be recorded! Feel free to remove this extra strings if space is a requriment
version.major.minor
New in A.1.0
Changed: multi:newConnection(protect) method
Changed the way you are able to interact with it by adding the __call metamethod
Old usage:
OnUpdate=multi:newConnection()
OnUpdate:connect(function(...)
print("Updating",...)
end)
OnUpdate:Fire(1,2,3)
New usage: notice that connect is no longer needed! Both ways still work! and always will work :)
OnUpdate=multi:newConnection()
OnUpdate(function(...)
print("Updating",...)
end)
OnUpdate:Fire(1,2,3)
New in A.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. 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: obj=multi:newConnection()
obj:connect(func,name) and obj(func,name)
Added the name argument to allow indexing specific connection objects... Useful when creating an async library
]]
multi.__index = multi
multi.Mainloop={}
multi.Tasks={}
multi.Tasks2={}
multi.Garbage={}
multi.ender={}
multi.Children={}
multi.Paused={}
multi.Active=true
multi.fps=60
multi.Id=-1
multi.Type='mainprocess'
multi.Rest=0
multi._type=type
multi.Jobs={}
multi.queue={}
multi.jobUS=2
multi.clock=os.clock
multi.time=os.time
multi.LinkedPath=multi
multi.queuefinal=function(self)
self:Destroy()
if self.Parent.Mainloop[#self.Parent.Mainloop] then
self.Parent.Mainloop[#self.Parent.Mainloop]:Resume()
else
for i=1,#self.Parent.funcE do
self.Parent.funcE[i](self)
end
self.Parent:Remove()
end
end
--Do not change these ever...Any other number will not work (Unless you are using enablePriority2() then change can be made. Just ensure that Priority_Idle is the greatest and Priority_Core is 1!)
multi.Priority_Core=1
multi.Priority_High=4
multi.Priority_Above_Normal=16
multi.Priority_Normal=64
multi.Priority_Below_Normal=256
multi.Priority_Low=1024
multi.Priority_Idle=4096
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.PStep=1
--^^^^
multi.PriorityTick=1 -- Between 1 and 4 any greater and problems arise
multi.Priority=multi.Priority_Core
function multi:setDomainName(name)
self[name]={}
end
function multi:linkDomain(name)
return self[name]
end
function multi:_Pause()
self.Active=false
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()=='high' or s:lower()=='h' then
self.Priority=self.Priority_High
elseif s:lower()=='above' or s:lower()=='an' 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()=='bn' then
self.Priority=self.Priority_Below_Normal
elseif s:lower()=='low' or s:lower()=='l' then
self.Priority=self.Priority_Low
elseif s:lower()=='idle' or s:lower()=='i' then
self.Priority=self.Priority_Idle
end
end
end
-- System
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:getParentProcess()
return self.Mainloop[self.CID]
end
function multi:Stop()
self.Active=false
end
function multi:condition(cond)
if not self.CD then
self:Pause()
self.held=true
self.CD=cond.condition
elseif not(cond.condition()) then
self.held=false
self:Resume()
self.CD=nil
return false
end
self.Parent:Do_Order()
return true
end
function multi:isHeld()
return self.held
end
function multi.executeFunction(name,...)
if type(_G[name])=='function' then
_G[name](...)
else
print('Error: Not a function')
end
end
function multi:waitFor(obj)
self:hold(function() return obj:isActive() end)
end
function multi:reboot(r)
local before=collectgarbage('count')
self.Mainloop={}
self.Tasks={}
self.Tasks2={}
self.Garbage={}
self.Children={}
self.Paused={}
self.Active=true
self.Id=-1
if r then
for i,v in pairs(_G) do
if type(i)=='table' then
if i.Parent and i.Id and i.Act then
i={}
end
end
end
end
collectgarbage()
local after=collectgarbage('count')
print([[Before rebooting total Ram used was ]]..before..[[Kb
After rebooting total Ram used is ]]..after..[[ Kb
A total of ]]..(before-after)..[[Kb was cleaned up]])
end
function multi:getChildren()
return self.Mainloop
end
--Processor
function multi:getError()
if self.error then
return self.error
end
end
function multi:Do_Order()
local Loop=self.Mainloop
_G.ID=0
for _D=#Loop,1,-1 do
if Loop[_D] then
if Loop[_D].Active then
Loop[_D].Id=_D
self.CID=_D
Loop[_D]:Act()
end
end
end
end
function multi:enablePriority()
function self:Do_Order()
local Loop=self.Mainloop
_G.ID=0
local PS=self
for _D=#Loop,1,-1 do
if Loop[_D] then
if (PS.PList[PS.PStep])%Loop[_D].Priority==0 then
if Loop[_D].Active then
Loop[_D].Id=_D
self.CID=_D
Loop[_D]:Act()
end
end
end
end
PS.PStep=PS.PStep+1
if PS.PStep>7 then
PS.PStep=1
end
end
end
function multi:enablePriority2()
function self:Do_Order()
local Loop=self.Mainloop
_G.ID=0
local PS=self
for _D=#Loop,1,-1 do
if Loop[_D] then
if (PS.PStep)%Loop[_D].Priority==0 then
if Loop[_D].Active then
Loop[_D].Id=_D
self.CID=_D
Loop[_D]:Act()
end
end
end
end
PS.PStep=PS.PStep+1
if PS.PStep>self.Priority_Idle then
PS.PStep=1
end
end
end
multi.disablePriority=multi.unProtect
function multi:fromfile(path,int)
int=int or self
local test2={}
local test=bin.load(path)
local tp=test:getBlock('s')
if tp=='event' then
test2=int:newEvent(test:getBlock('f'))
local t=test:getBlock('t')
for i=1,#t do
test2:OnEvent(t[i])
end
elseif tp=='alarm' then
test2=int:newAlarm(test:getBlock('n'))
elseif tp=='loop' then
test2=int:newLoop(test:getBlock('t')[1])
elseif tp=='step' or tp=='tstep' then
local func=test:getBlock('t')
local funcE=test:getBlock('t')
local funcS=test:getBlock('t')
local tab=test:getBlock('t')
test2=int:newStep()
table.merge(test2,tab)
test2.funcE=funcE
test2.funcS=funcS
test2.func=func
elseif tp=='trigger' then
test2=int:newTrigger(test:getBlock('f'))
elseif tp=='connector' then
test2=int:newConnection()
test2.func=test:getBlock('t')
elseif tp=='timer' then
test2=int:newTimer()
test2.count=tonumber(test:getBlock('n'))
else
print('Error: The file you selected is not a valid multi file object!')
return false
end
return test2
end
function multi:benchMark(sec,p)
local temp=self:newLoop(function(t,self)
if self.clock()-self.init>self.sec then
print(self.c..' steps in '..self.sec..' second(s)')
self.tt(self.sec)
self:Destroy()
else
self.c=self.c+1
end
end)
temp.Priority=p or 1
function temp:OnBench(func)
self.tt=func
end
self.tt=function() end
temp.sec=sec
temp.init=self.clock()
temp.c=0
return temp
end
function multi:tofile(path)
local items=self:getChildren()
io.mkDir(io.getName(path))
for i=1,#items do
items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat')
end
local int=bin.new()
int:addBlock('process')
int:addBlock(io.getName(path))
int:addBlock(#self.Mainloop)
int:addBlock(self.Active)
int:addBlock(self.Rest)
int:addBlock(self.Jobs)
int:tofile()
end
function multi.startFPSMonitior()
if not multi.runFPS then
multi.doFPS(s)
multi.runFPS=true
end
end
function multi.doFPS(s)
multi:benchMark(1):OnBench(doFPS)
if s then
multi.fps=s
end
end
--Helpers
function multi:OnMainConnect(func)
table.insert(self.func,func)
end
function multi:protect()
function self:Do_Order()
local Loop=self.Mainloop
for _D=#Loop,1,-1 do
if Loop[_D]~=nil then
Loop[_D].Id=_D
self.CID=_D
local status, err=pcall(Loop[_D].Act,Loop[_D])
if err and not(Loop[_D].error) then
Loop[_D].error=err
self.OnError:Fire(err,Loop[_D])
end
end
end
end
end
function multi:unProtect()
local Loop=self.Mainloop
_G.ID=0
for _D=#Loop,1,-1 do
if Loop[_D] then
if Loop[_D].Active then
Loop[_D].Id=_D
self.CID=_D
Loop[_D]:Act()
end
end
end
end
function multi:setJobSpeed(n)
self.jobUS=n
end
function multi:hasJobs()
return #self.Jobs>0,#self.Jobs
end
function multi:getJobs()
return #self.Jobs
end
function multi:removeJob(name)
for i=#self.Jobs,1,-1 do
if self.Jobs[i][2]==name then
table.remove(self.Jobs,i)
end
end
end
function multi:FreeMainEvent()
self.func={}
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
print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(function) to trigger it's final event!")
self:OnBreak(func)
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
function multi:Sleep(n)
self:hold(n)
end
function multi:Pause()
if self.Type=='mainprocess' then
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
if self.Parent.Mainloop[self.Id]~=nil then
table.remove(self.Parent.Mainloop,self.Id)
table.insert(self.Parent.Paused,self)
self.PId=#self.Parent.Paused
end
end
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:isPaused() then
table.remove(self.Parent.Paused,self.PId)
table.insert(self.Parent.Mainloop,self)
self.Id=#self.Parent.Mainloop
self.Active=true
end
end
end
function multi:resurrect()
table.insert(self.Parent.Mainloop,self)
self.Active=true
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)
break
end
end
self.Active=false
end
end
function multi:hold(task)
self:Pause()
self.held=true
if type(task)=='number' then
local alarm=self.Parent:newAlarm(task)
while alarm.Active==true do
if love then
self.Parent:lManager()
else
self.Parent:Do_Order()
end
end
alarm:Destroy()
self:Resume()
self.held=false
elseif type(task)=='function' then
local env=self.Parent:newEvent(task)
env:OnEvent(function(envt) envt:Pause() envt.Active=false end)
while env.Active do
if love then
self.Parent:lManager()
else
self.Parent:Do_Order()
end
end
env:Destroy()
self:Resume()
self.held=false
else
print('Error Data Type!!!')
end
end
function multi:oneTime(func,...)
if not(self.Type=='mainprocess' or self.Type=='process') then
for _k=1,#self.Parent.Tasks2 do
if self.Parent.Tasks2[_k]==func then
return false
end
end
table.insert(self.Parent.Tasks2,func)
func(...)
return true
else
for _k=1,#self.Tasks2 do
if self.Tasks2[_k]==func then
return false
end
end
table.insert(self.Tasks2,func)
func(...)
return true
end
end
function multi:Reset(n)
self:Resume()
end
function multi:isDone()
return self.Active~=true
end
function multi:create(ref)
multi.OnObjectCreated:Fire(ref)
end
--Constructors [CORE]
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, self.Parent)
else
setmetatable(c, self)
end
c.Active=true
c.func={}
c.ender={}
c.Id=0
c.PId=0
c.Act=function() end
c.Parent=self
c.held=false
if ins then
table.insert(self.Mainloop,ins,c)
else
table.insert(self.Mainloop,c)
end
return c
end
function multi:newProcess(file)
if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end
local c = {}
setmetatable(c, self)
c.Parent=self
c.Active=true
c.func={}
c.Id=0
c.Type='process'
c.Mainloop={}
c.Tasks={}
c.Tasks2={}
c.Garbage={}
c.Children={}
c.Paused={}
c.Active=true
c.Id=-1
c.Rest=0
c.Jobs={}
c.queue={}
c.jobUS=2
function c:Start()
if self.l then
self.l:Resume()
else
self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end)
end
end
function c:Pause()
if self.l then
self.l:Pause()
end
end
function c:Remove()
self:Destroy()
self.l:Destroy()
end
if file then
self.Cself=c
loadstring('local interface=multi.Cself '..io.open(file,'rb'):read('*all'))()
end
self:create(c)
return c
end
function multi:newQueuer(file)
local c=self:newProcess()
c.Type='queue'
c.last={}
c.funcE={}
function c:OnQueueCompleted(func)
table.insert(self.funcE,func)
end
if file then
self.Cself=c
loadstring('local queue=multi.Cself '..io.open(file,'rb'):read('*all'))()
end
self:create(c)
return c
end
--Constructors [ACTORS]
function multi:newCustomObject(objRef,t)
local c={}
if t=='process' then
if self.Type=='queue' then
c=self:newBase(1)
self.last=c
print("This Custom Object was created on a queue! Ensure that it has a way to end! All objects have a obj:Break() method!")
else
c=self:newBase()
end
if type(objRef)=='table' then
table.merge(c,objRef)
end
if not c.Act then
function c:Act()
-- Empty function
end
end
else
c=objRef or {}
end
if not c.Type then
c.Type='coustomObject'
end
if self.Type=='queue' then
if #self.Mainloop>1 then
c:Pause()
end
c:connectFinal(multi.queuefinal)
end
self:create(c)
return c
end
function multi:newEvent(task)
local c={}
if self.Type=='queue' then
c=self:newBase(1)
self.last=c
else
c=self:newBase()
end
c.Type='event'
c.Task=task or function() end
function c:Act()
if self.Task(self) then
self:Pause()
for _E=1,#self.func do
self.func[_E](self)
end
end
end
function c:OnEvent(func)
table.insert(self.func,func)
end
function c:tofile(path)
local m=bin.new()
m:addBlock(self.Type)
m:addBlock(self.Task)
m:addBlock(self.func)
m:addBlock(self.Active)
m:tofile(path)
end
if self.Type=='queue' then
if #self.Mainloop>1 then
c:Pause()
end
c:connectFinal(multi.queuefinal)
end
self:create(c)
return c
end
function multi:newAlarm(set)
local c={}
if self.Type=='queue' then
c=self:newBase(1)
self.last=c
else
c=self:newBase()
end
c.Type='alarm'
c.Priority=self.Priority_Low
c.timer=self:newTimer()
c.set=set or 0
function c:tofile(path)
local m=bin.new()
m:addBlock(self.Type)
m:addBlock(self.set)
m:addBlock(self.Active)
m:tofile(path)
end
function c:Act()
if self.timer:Get()>=self.set then
self:Pause()
self.Active=false
for i=1,#self.func do
self.func[i](self)
end
end
end
function c:Resume()
self.Parent.Resume(self)
self.timer:Resume()
end
function c:Reset(n)
if n then self.set=n end
self:Resume()
self.timer:Reset()
end
function c:OnRing(func)
table.insert(self.func,func)
end
function c:Pause()
self.timer:Pause()
self.Parent.Pause(self)
end
if self.Type=='queue' then
c:Pause()
c:connectFinal(multi.queuefinal)
else
c.timer:Start()
end
self:create(c)
return c
end
function multi:newLoop(func)
local c={}
if self.Type=='queue' then
c=self:newBase(1)
self.last=c
else
c=self:newBase()
end
c.Type='loop'
c.Start=self.clock()
if func then
c.func={func}
end
function c:tofile(path)
local m=bin.new()
m:addBlock(self.Type)
m:addBlock(self.func)
m:addBlock(self.Active)
m:tofile(path)
end
function c:Act()
for i=1,#self.func do
self.func[i](self.Parent.clock()-self.Start,self)
end
end
function c:OnLoop(func)
table.insert(self.func,func)
end
if self.Type=='queue' then
if #self.Mainloop>1 then
c:Pause()
end
c:connectFinal(multi.queuefinal)
end
self:create(c)
return c
end
function multi:newUpdater(skip)
local c={}
if self.Type=='queue' then
c=self:newBase(1)
self.last=c
else
c=self:newBase()
end
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
end
c.OnUpdate=self.OnMainConnect
if self.Type=='queue' then
if #self.Mainloop>1 then
c:Pause()
end
c:connectFinal(multi.queuefinal)
end
self:create(c)
return c
end
function multi:newStep(start,reset,count,skip)
local c={}
if self.Type=='queue' then
c=self:newBase(1)
self.last=c
else
c=self:newBase()
end
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:tofile(path)
local m=bin.new()
m:addBlock(self.Type)
m:addBlock(self.func)
m:addBlock(self.funcE)
m:addBlock(self.funcS)
m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start})
m:addBlock(self.Active)
m:tofile(path)
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.pos,self)
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)
end
function c:OnStep(func)
table.insert(self.func,1,func)
end
function c:OnEnd(func)
table.insert(self.funcE,func)
end
function c:Break()
self.Active=nil
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()
end
if self.Type=='queue' then
if #self.Mainloop>1 then
c:Pause()
end
c:connectFinal(multi.queuefinal)
end
self:create(c)
return c
end
function multi:newTStep(start,reset,count,set)
local c={}
if self.Type=='queue' then
c=self:newBase(1)
self.last=c
else
c=self:newBase()
end
think=1
c.Type='tstep'
c.Priority=self.Priority_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=self.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=self.clock()
self:Resume()
end
function c:tofile(path)
local m=bin.new()
m:addBlock(self.Type)
m:addBlock(self.func)
m:addBlock(self.funcE)
m:addBlock(self.funcS)
m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set})
m:addBlock(self.Active)
m:tofile(path)
end
function c:Act()
if self.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.pos,self)
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)
end
function c:OnStep(func)
table.insert(self.func,func)
end
function c:OnEnd(func)
table.insert(self.funcE,func)
end
function c:Break()
self.Active=nil
end
function c:Reset(n)
if n then self.set=n end
self.timer=self.clock()
self:Resume()
end
if self.Type=='queue' then
if #self.Mainloop>1 then
c:Pause()
end
c:connectFinal(multi.queuefinal)
end
self:create(c)
return c
end
function multi:newWatcher(namespace,name)
local function WatcherObj(ns,n)
if self.Type=='queue' then
print("Cannot create a watcher on a queue! Creating on 'multi' instead!")
self=multi
end
local c=self:newBase()
c.Type='watcher'
c.ns=ns
c.n=n
c.cv=ns[n]
function c:OnValueChanged(func)
table.insert(self.func,func)
end
function c:Act()
if self.cv~=self.ns[self.n] then
for i=1,#self.func do
self.func[i](self,self.cv,self.ns[self.n])
end
self.cv=self.ns[self.n]
end
end
self:create(c)
return c
end
if type(namespace)~='table' and type(namespace)=='string' then
return WatcherObj(_G,namespace)
elseif type(namespace)=='table' and (type(name)=='string' or 'number') then
return WatcherObj(namespace,name)
else
print('Warning, invalid arguments! Nothing returned!')
end
end
function multi:newThread(name,func)
local c={}
c.ref={}
c.Name=name
c.thread=coroutine.create(func)
c.sleep=1
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)
n = tonumber(n) or 0
ret=coroutine.yield({"_sleep_",n})
self:syncGlobals(ret)
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
-- Constructors [SEMI-ACTORS]
function multi:newJob(func,name)
if not(self.Type=='mainprocess' 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' then
setmetatable(c, self.Parent)
else
setmetatable(c, self)
end
c.Active=true
c.func={}
c.Id=0
c.PId=0
c.Parent=self
c.Type='job'
c.trigfunc=func or function() end
function c:Act()
self:trigfunc(self)
end
table.insert(self.Jobs,{c,name})
if self.JobRunner==nil then
self.JobRunner=self:newAlarm(self.jobUS)
self.JobRunner:OnRing(function(self)
if #self.Parent.Jobs>0 then
if self.Parent.Jobs[1] then
self.Parent.Jobs[1][1]:Act()
table.remove(self.Parent.Jobs,1)
end
end
self:Reset(self.Parent.jobUS)
end)
end
end
function multi:newRange()
local selflink=self
local temp={
getN = function(self) selflink:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link.held=false self.Link:Resume() return nil end return self.n end,
}
setmetatable(temp,{
__call=function(self,a,b,c)
self.c=c or 1
self.n=a-self.c
self.a=a
self.b=b
self.Link=selflink--.Parent.Mainloop[selflink.CID] or
self.Link:Pause()
self.Link.held=true
return function() return self:getN() end
end
})
self:create(temp)
return temp
end
function multi:newCondition(func)
local c={['condition']=func}
self:create(c)
return c
end
-- Constructors [NON-ACTORS]
function multi:newFunction(func)
local c={}
c.func=func
mt={
__index=multi,
__call=function(self,...) if self.Active then return self:func(...) end local t={...} return "PAUSED" end
}
c.Parent=self
function c:Pause()
self.Active=false
end
function c:Resume()
self.Active=true
end
setmetatable(c,mt)
self:create(c)
return c
end
function multi:newTimer()
local c={}
c.Type='timer'
c.time=0
c.count=0
function c:Start()
self.time=os.clock()
end
function c:Get()
return (os.clock()-self.time)+self.count
end
c.Reset=c.Start
function c:Pause()
self.time=self:Get()
end
function c:Resume()
self.time=os.clock()-self.time
end
function c:tofile(path)
local m=bin.new()
self.count=self.count+self:Get()
m:addBlock(self.Type)
m:addBlock(self.count)
m:tofile(path)
end
self:create(c)
return c
end
function multi:newTask(func)
table.insert(self.Tasks,func)
end
function multi:newTrigger(func)
local c={}
c.Type='trigger'
c.trigfunc=func or function() end
function c:Fire(...)
self:trigfunc(self,...)
end
function c:tofile(path)
local m=bin.new()
m:addBlock(self.Type)
m:addBlock(self.trigfunc)
m:tofile(path)
end
self:create(c)
return c
end
function multi:newConnection(protect)
local c={}
setmetatable(c,{__call=function(self,...) self:connect(...) end})
c.Type='connector'
c.func={}
c.ID=0
c.protect=protect or true
c.connections={}
c.fconnections={}
c.FC=0
function c:fConnect(func)
local temp=self:connect(func)
table.insert(self.fconnections,temp)
self.FC=self.FC+1
end
function c:getConnection(name,ingore)
if ingore then
return self.connections[name] or {
Fire=function() end -- if the connection doesn't exist lets call all of them or silently ingore
}
else
return self.connections[name] or self
end
end
function c:Fire(...)
local ret={}
for i=#self.func,1,-1 do
if self.protect then
local temp={pcall(self.func[i][1],...)}
if temp[1] then
table.remove(temp,1)
table.insert(ret,temp)
else
print(temp[2])
end
else
table.insert(ret,{self.func[i][1](...)})
end
end
return ret
end
function c:bind(t)
self.func=t
end
function c:remove()
self.func={}
end
function c:connect(func,name)
self.ID=self.ID+1
table.insert(self.func,1,{func,self.ID})
local temp = {
Link=self.func,
func=func,
ID=self.ID,
Parent=self,
Fire=function(self,...)
if self.Parent.FC>0 then
for i=1,#self.Parent.FC do
self.Parent.FC[i]:Fire(...)
end
end
if self.Parent.protect then
local t=pcall(self.func,...)
if t then
return t
end
else
return self.func(...)
end
end,
remove=function(self)
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
return true
end
end
end
end
}
if name then
self.connections[name]=temp
end
return temp
end
function c:tofile(path)
local m=bin.new()
m:addBlock(self.Type)
m:addBlock(self.func)
m:tofile(path)
end
return c
end
multi.OnObjectCreated=multi:newConnection()
multi.OnObjectDestroyed=multi:newConnection()
--Managers
function multi:mainloop()
for i=1,#self.Tasks do
self.Tasks[i](self)
end
rawset(self,'Start',self.clock())
while self.Active do
self:Do_Order()
end
print("Did you call multi:Stop()? This method should not be used when using multi:mainloop()! You now need to restart the multiManager, by using multi:reboot() and calling multi:mainloop() again or by using multi:uManager()")
end
function multi._tFunc(self,dt)
for i=1,#self.Tasks do
self.Tasks[i](self)
end
if dt then
self.pump=true
end
self.pumpvar=dt
rawset(self,'Start',self.clock())
end
function multi:uManager(dt)
if self.Active then
self:oneTime(self._tFunc,self,dt)
function self:uManager(dt)
self:Do_Order()
end
self:Do_Order()
end
end
--Thread Setup Stuff
multi:setDomainName("Threads")
multi:setDomainName("Globals")
-- Scheduler
multi.scheduler=multi:newUpdater()
multi.scheduler.Type="scheduler"
function multi.scheduler:setStep(n)
self.skip=tonumber(n) or 24
end
multi.scheduler.Threads=multi:linkDomain("Threads")
multi.scheduler.Globals=multi:linkDomain("Globals")
multi.scheduler:OnUpdate(function(self)
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 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.Name then
self.Globals[ret.Name]=ret.Value
end
end
end
end
end)
multi.scheduler:setStep()
multi.scheduler:Pause()
multi.OnError=multi:newConnection()