diff --git a/.gitignore b/.gitignore index 080d185..0aff785 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,7 @@ 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 -*.m4a +test.lua +test.lua diff --git a/Documentation.html b/Documentation.html new file mode 100644 index 0000000..c44921d --- /dev/null +++ b/Documentation.html @@ -0,0 +1,1328 @@ + + +
+ +Current Multi Version: 13.0.0
multi.Version — The current version of the librarymulti.Priority_Core — Highest level of pirority that can be given to a processmulti.Priority_Highmulti.Priority_Above_Normalmulti.Priority_Normal — The default level of pirority that is given to a processmulti.Priority_Below_Normalmulti.Priority_Lowmulti.Priority_Idle — Lowest level of pirority that can be given to a process
multi:mainloop([TABLE settings]) — This runs the mainloop by having its own internal while loop runningmulti:threadloop([TABLE settings]) — This runs the mainloop by having its own internal while loop running, but prioritizes threads over multi-objectsmulti:uManager([TABLE settings]) — This runs the mainloop, but does not have its own while loop and thus needs to be within a loop of some kind.
Note: Most settings have been fined tuned to be at the peak of performance already, however preLoop, protect (Which drastically lowers preformance), and stopOnError should be used freely to fit your needs.
| Setting | +Type: default | +Purpose | +
|---|---|---|
| preLoop | +function: nil | +This is a function that is called after all the important components of the library are loaded. This is called once only. The first and only argument passed is a reference to itself. | +
| protect | +boolean: false | +This runs code within a protected call. To catch when errors happen see built in connections | +
| stopOnError | +boolean: false | +This setting is used with protect. If an object crashes due to some error should it be paused? | +
| priority | +number: 0 | +This sets the priority scheme. Look at the P-Charts below for examples. | +
| auto_priority | +boolean: false | +Note: This overrides any value set for priority! If auto priority is enabled then priority scheme 3 is used and processes are considered for “recheck” after a certain amount of time. If a process isn’t taking too long to complete anymore then it will be reset to core, if it starts to take a lot of time all of a sudden it will be set to idle. | +
| auto_stretch | +number: 1 | +For use with auto_priority. Modifies the internal reperesentation of idle time by multiplying multi.Priority_Idle by the value given | +
| auto_delay | +number: 3 | +For use with auto_priority. This changes the time in seconds that process are “rechecked” | +
| auto_lowerbound | +number: multi.Priority_Idle | +For use with auto_priority. The lowerbound is what is considered to be idle time. A higher value combined with auto_stretch allows one to fine tune how pirority is managed. | +
P1 follows a forumla that resembles this: ~n=I*PRank where n is the amount of steps given to an object with PRank and where I is the idle time see chart below. The aim of this priority scheme was to make core objects run fastest while letting idle processes get decent time as well.
| Priority: n | +PRank | +Formula | +
|---|---|---|
| Core: 3322269 | +7 | +n = ~I*7 | +
| High: 2847660 | +6 | +n = ~I*6 | +
| Above_Normal: 2373050 | +5 | +n = ~I*5 | +
| Normal: 1898440 | +4 | +n = ~I*4 | +
| Below_Normal: 1423830 | +3 | +n = ~I*3 | +
| Low: 949220 | +2 | +n = ~I*2 | +
| Idle: 474610 | +1 | +n = ~I*1 | +
General Rule: ~n=I*PRank
P2 follows a formula that resembles this: ~n=n*4 where n starts as the initial idle time, see chart below. The goal of this one was to make core process’ higher while keeping idle process’ low.
| Priority: n | +
|---|
| Core: 6700821 | +
| High: 1675205 | +
| Above_Normal: 418801 | +
| Normal: 104700 | +
| Below_Normal: 26175 | +
| Low: 6543 | +
| Idle: 1635 | +
General Rule: ~n=n*4 Where the inital n = I
P3 Ignores using a basic formula 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!
There are 2 settings for this: Core and Idle. If a process takes too long then it is set to idle. Otherwise it will stay core.
Example of settings:
settings = {
+ preLoop = function(m)
+ print("All settings have been loaded!")
+ end,
+ protect = false,
+ stopOnError = false,
+ priority = 0,
+ auto_priority = false,
+ auto_stretch = 1,
+ auto_delay = 3,
+ auto_lowerbound = multi.Priority_Idle
+}
+
+-- Below are how the runners work
+
+multi:mainloop(settings)
+
+-- or
+
+multi:threadloop(settings)
+
+-- or
+
+while true do
+ multi:uManager(settings)
+end
+Processorsproc = multi:newProcessor([STRING: file nil])
Non-Actorstimer = multi:newTimer()conn = multi:newConnection([BOOLEAN protect true])nil = multi:newJob(FUNCTION func, STRING name)func = multi:newFunction(FUNCTION func)trigger = multi:newTrigger(FUNCTION: func)
Actorsevent = multi:newEvent(FUNCTION task)updater = multi:newUpdater([NUMBER skip 1])alarm = multi:newAlarm([NUMBER 0])loop = multi:newLoop(FUNCTION func)tloop = multi:newTLoop(FUNCTION func ,NUMBER: [set 1])step = multi:newStep(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER skip 0])tstep = multi:newStep(NUMBER start, NUMBER reset, [NUMBER count 1], [NUMBER set 1])trigger = multi:newTrigger(FUNCTION: func)stamper = multi:newTimeStamper()watcher = multi:newWatcher(STRING name)watcher = multi:newWatcher(TABLE namespace, STRING name)cobj = multi:newCustomObject(TABLE objRef, BOOLEAN isActor)
Note: A lot of methods will return self as a return. This is due to the ability to chain that was added in 12.x.x
proc = multi:newProcessor([STRING file nil])
Creates a processor runner that acts like the multi runner. Actors and Non-Actors can be created on these objects. Pausing a process pauses all objects that are running on that process.
An optional argument file is used if you want to load a file containing the processor data.
Note: This isn’t portable on all areas where lua is used. Some interperters disable loadstring so it is not encouraged to use the file method for creating processors
loop = Processor:getController() — returns the loop that runs the “runner” that drives this processorself = Processor:Start() — Starts the processorself = Processor:Pause() — Pauses the processorself = Processor:Resume() — Resumes a paused processornil = Processor:Destroy() — Destroys the processor and all of the Actors running on it
Example
multi = require("multi")
+proc = multi:newProcessor()
+proc:newTLoop(function() -- create a t loop that runs every second
+ print("Hi!")
+end,1) -- where we set the 1 second
+proc:Start() -- let's start the processor
+multi:mainloop() -- the main runner that drives everything
+timer = multi:newTimer()
Creates a timer object that can keep track of time
self = timer:Start() — Starts the timer
time_elapsed = timer:Get() — Returns the time elapsed since timer:Start() was called
boolean = timer:isPaused() — Returns if the timer is paused or not
self = timer:Pause() — Pauses the timer, it skips time that would be counted during the time that it is paused
self = timer:Resume() — Resumes a paused timer. See note below
self = timer:tofile(STRING path) — Saves the object to a file at location path
Note: If a timer was paused after 1 second then resumed a second later and Get() was called a second later, timer would have 2 seconds counted though 3 really have passed.
Arguable my favorite object in this library, next to threads
conn = multi:newConnection([BOOLEAN protect true])
Creates a connection object and defaults to a protective state. All calls will run within pcall()
self = conn:HoldUT([NUMBER n 0]) — Will hold futhur execution of the thread until the connection was triggered. If n is supplied the connection must be triggered n times before it will allow ececution to continue.self = conn:FConnect(FUNCTION func) — Creates a connection that is forced to execute when Fire() is called. returns or nil = conn:Fire(…) — Triggers the connection with arguments …, “returns” if non-nil is a table containing return values from the triggered connections. [Deprecated: Planned removal in 14.x.x]self = conn:Bind(TABLE t) — sets the table to hold the connections. Leaving it alone is best unless you know what you are doingself = conn:Remove() — removes the bind that was put in place. This will also destroy all connections that existed before.link = conn:connect(FUNCTION func, [STRING name nil], [NUMBER num #conns+1]) — Connects to the object using function func which will recieve the arguments passed by Fire(…). You can name a connection, which allows you to use conn:getConnection(name). Names must be unique! num is simple the position in the order in which connections are triggered. The return Link is the link to the connected event that was made. You can remove this event or even trigger it specifically if need be.link:Fire(...) — Fires the created eventbool = link:Destroy() — returns true if success.subConn = conn:getConnection(STRING name, BOOLEAN ingore) — returns the sub connection which matches name.
returns or nil subConn:Fire() — “returns” if non-nil is a table containing return values from the triggered connections.self = conn:tofile(STRING path) — Saves the object to a file at location path
The connect feature has some syntax sugar to it as seen belowlink = conn(FUNCTION func, [STRING name nil], [NUMBER #conns+1])
Example:
local multi = require("multi")
+-- Let’s create the events
+yawn={}
+OnCustomSafeEvent=multi:newConnection(true) -- lets pcall the calls in case something goes wrong default
+OnCustomEvent=multi:newConnection(false) -- let’s not pcall the calls and let errors happen.
+OnCustomEvent:Bind(yawn) -- create the connection lookup data in yawn
+
+-- Let’s connect to them, a recent update adds a nice syntax to connect to these
+cd1=OnCustomSafeEvent:Connect(function(arg1,arg2,...)
+ print("CSE1",arg1,arg2,...)
+end,"bob") -- let’s give this connection a name
+cd2=OnCustomSafeEvent:Connect(function(arg1,arg2,...)
+ print("CSE2",arg1,arg2,...)
+end,"joe") -- let’s give this connection a name
+cd3=OnCustomSafeEvent:Connect(function(arg1,arg2,...)
+ print("CSE3",arg1,arg2,...)
+end) -- let’s not give this connection a name
+
+-- Using syntax sugar
+OnCustomEvent(function(arg1,arg2,...)
+ print(arg1,arg2,...)
+end)
+
+-- Now within some loop/other object you trigger the connection like
+OnCustomEvent:Fire(1,2,"Hello!!!") -- fire all connections
+
+-- You may have noticed that some events have names! See the following example!
+OnCustomSafeEvent:getConnection("bob"):Fire(1,100,"Bye!") -- fire only bob!
+OnCustomSafeEvent:getConnection("joe"):Fire(1,100,"Hello!") -- fire only joe!!
+OnCustomSafeEvent:Fire(1,100,"Hi Ya Folks!!!") -- fire them all!!!
+
+-- Connections have more to them than that though!
+-- As seen above cd1-cd3 these are hooks to the connection object. This allows you to remove a connection
+-- For Example:
+cd1:Remove() -- remove this connection from the master connection object
+print("------")
+OnCustomSafeEvent:Fire(1,100,"Hi Ya Folks!!!") -- fire them all again!!!
+-- To remove all connections use:
+OnCustomSafeEvent:Remove()
+print("------")
+OnCustomSafeEvent:Fire(1,100,"Hi Ya Folks!!!") -- fire them all again!!!
+nil = multi:newJob(FUNCTION func, STRING name) — Adds a job to a queue of jobs that get executed after some time. func is the job that is being ran, name is the name of the job.nil = multi:setJobSpeed(NUMBER n) — seconds between when each job should be done.bool, number = multi:hasJobs() — returns true if there are jobs to be processed. And the number of jobs to be processednum = multi:getJobs() — returns the number of jobs left to be processed.number = multi:removeJob(STRING name) — removes all jobs of name, name. Returns the number of jobs removed
Note: Jobs may be turned into actual objects in the future.
Example:
local multi = require("multi")
+print(multi:hasJobs())
+multi:setJobSpeed(1) -- set job speed to 1 second
+multi:newJob(function()
+ print("A job!")
+end,"test")
+
+multi:newJob(function()
+ print("Another job!")
+ multi:removeJob("test") -- removes all jobs with name "test"
+end,"test")
+
+multi:newJob(function()
+ print("Almost done!")
+end,"test")
+
+multi:newJob(function()
+ print("Final job!")
+end,"test")
+print(multi:hasJobs())
+print("There are "..multi:getJobs().." jobs in the queue!")
+multi:mainloop()
+func = multi:newFunction(FUNCTION func)
These objects used to have more of a function before corutine based threads came around, but the main purpose now is the ablity to have pausable function calls
... = func(...) — This is how you call your function. The first argument passed is itself when your function is triggered. See example.self = func:Pause()self = func:Resume()
Note: A paused function will return: nil, true
Example:
local multi = require("multi")
+printOnce = multi:newFunction(function(self,msg)
+ print(msg)
+ self:Pause()
+ return "I won't work anymore"
+end)
+a=printOnce("Hello World!")
+b,c=printOnce("Hello World!")
+print(a,b,c)
+trigger = multi:newTrigger(FUNCTION: func(...)) — A trigger is the precursor of connection objects. The main difference is that only one function can be binded to the trigger.self = trigger:Fire(...) — Fires the function that was connected to the trigger and passes the arguments supplied in Fire to the function given.
All of these functions are found on actorsself = multiObj:Pause() — Pauses the actor from runningself = multiObj:Resume() — Resumes the actor that was pausednil = multiObj:Destroy() — Removes the object from the mainloopbool = multiObj:isPaused() — Returns true if the object is paused, false otherwisestring = multiObj:getType() — Returns the type of the objectself = multiObj:SetTime(n) — Sets a timer, and creates a special “timemaster” actor, which will timeout unless ResolveTimer is calledself = multiObj:ResolveTimer(...) — Stops the timer that was put onto the multiObj from timing outself = multiObj:OnTimedOut(func) — If ResolveTimer was not called in time this event will be triggered. The function connected to it get a refrence of the original object that the timer was created on as the first argument.self = multiObj:OnTimerResolved(func) — This event is triggered when the timer gets resolved. Same argument as above is passed, but the variable arguments that are accepted in resolvetimer are also passed as well.self = multiObj:Reset(n) — In the cases where it isn’t obvious what it does, it acts as Resume()self = multiObj:SetName(STRING name)
event = multi:newEvent(FUNCTION task)
The object that started it all. These are simply actors that wait for a condition to take place, then auto triggers an event. The event when triggered once isn’t triggered again unless you Reset() it.
self = SetTask(FUNCTION func) — This function is not needed if you supplied task at construction timeself = OnEvent(FUNCTION func) — Connects to the OnEvent event passes argument self to the connectee
Example:
local multi = require("multi")
+count=0
+-- A loop object is used to demostrate how one could use an event object.
+loop=multi:newLoop(function(self,dt)
+ count=count+1
+end)
+event=multi:newEvent(function() return count==100 end) -- set the event
+event:OnEvent(function(self) -- connect to the event object
+ loop:Destroy() -- destroys the loop from running!
+ print("Stopped that loop!",count)
+end) -- events like alarms need to be reset the Reset() command works here as well
+multi:mainloop()
+updater = multi:newUpdater([NUMBER skip 1]) — set the amount of steps that are skipped
Updaters are a mix between both loops and steps. They were a way to add basic priority management to loops (until a better way was added). Now they aren’t as useful, but if you do not want the performance hit of turning on priority then they are useful to auro skip some loops. Note: The performance hit due to priority management is not as bas as it used to be.
self = updater:SetSkip(NUMBER n) — sets the amount of steps that are skippedself = OnUpdate(FUNCTION func) — connects to the main trigger of the updater which is called every nth step
Example:
local multi = require("multi")
+updater=multi:newUpdater(5000) -- simple, think of a loop with the skip feature of a step
+updater:OnUpdate(function(self)
+ print("updating...")
+end)
+multi:mainloop()
+alarm = multi:newAlarm([NUMBER 0]) — creates an alarm which waits n seconds
Alarms ring after a certain amount of time, but you need to reset the alarm every time it rings! Use a TLoop if you do not want to have to reset.
self = alarm:Reset([NUMBER sec current_time_set]) — Allows one to reset an alarm, optional argument to change the time until the next ring.self = alarm:OnRing(FUNCTION func — Allows one to connect to the alarm event which is triggerd after a certain amount of time has passed.
Example:
local multi = require("multi")
+alarm=multi:newAlarm(3) -- in seconds can go to .001 uses the built in os.clock()
+alarm:OnRing(function(a)
+ print("3 Seconds have passed!")
+ a:Reset(n) -- if n were nil it will reset back to 3, or it would reset to n seconds
+end)
+multi:mainloop()
+loop = multi:newLoop(FUNCTION func) — func the main connection that you can connect to. Is optional, but you can also use OnLoop(func) to connect as well.
Loops are events that happen over and over until paused. They act like a while loop.
self = OnLoop(FUNCTION func) — func the main connection that you can connect to. Alllows multiple connections to one loop if need be.
Example:
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+local a = 0
+loop = multi:newLoop(function()
+ a = a + 1
+ if a == 1000 then
+ print("a = 1000")
+ loop:Pause()
+ end
+end)
+multi:mainloop()
+tloop = multi:newTLoop(FUNCTION func ,NUMBER: [set 1]) — TLoops are pretty much the same as loops. The only difference is that they take set which is how long it waits, in seconds, before triggering function func.
self = OnLoop(FUNCTION func) — func the main connection that you can connect to. Alllows multiple connections to one TLoop if need be.
Example:
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+local a = 0
+loop = multi:newTLoop(function()
+ a = a + 1
+ if a == 10 then
+ print("a = 10")
+ loop:Pause()
+ end
+end,1)
+multi:mainloop()
+step = multi:newStep(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER skip 0]) — Steps were originally introduced to bs used as for loops that can run parallel with other code. When using steps think of it like this: for i=start,reset,count do When the skip argument is given, each time the step object is given cpu cycles it will be skipped by n cycles. So if skip is 1 every other cpu cycle will be alloted to the step object.
self = step:OnStart(FUNCTION func(self)) — This connects a function to an event that is triggered everytime a step starts.self = step:OnStep(FUNCTION func(self,i)) — This connects a function to an event that is triggered every step or cycle that is alloted to the step objectself = step:OnEnd(FUNCTION func(self)) — This connects a function to an event that is triggered when a step reaches its goalself = step:Update(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER skip 0]) — Update can be used to change the goals of the step. You should call step:Reset() after using Update to restart the step.
Example:
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+multi:newStep(1,10,1,0):OnStep(function(step,pos)
+ print(step,pos)
+end):OnEnd(fucntion(step)
+ step:Destroy()
+end)
+multi:mainloop()
+tstep = multi:newStep(NUMBER start, NUMBER reset, [NUMBER count 1], [NUMBER set 1]) — TSteps work just like steps, the only difference is that instead of skip, we have set which is how long in seconds it should wait before triggering the OnStep() event.
self = tstep:OnStart(FUNCTION func(self)) — This connects a function to an event that is triggered everytime a step starts.self = tstep:OnStep(FUNCTION func(self,i)) — This connects a function to an event that is triggered every step or cycle that is alloted to the step objectself = tstep:OnEnd(FUNCTION func(self)) — This connects a function to an event that is triggered when a step reaches its goalself = tstep:Update(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER set 1]) — Update can be used to change the goals of the step. You should call step:Reset() after using Update to restart the step.self = tstep:Reset([NUMBER n set]) — Allows you to reset a tstep that has ended, but also can change the time between each trigger.
Example:
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+multi:newTStep(1,10,1,1):OnStep(function(step,pos)
+ print(step,pos)
+end):OnEnd(fucntion(step)
+ step:Destroy()
+end)
+multi:mainloop()
+stamper = multi:newTimeStamper() — This allows for long time spans as well as short time spans.stamper = stamper:OhSecond(NUMBER second, FUNCTION func) — This takes a value between 0 and 59. This event is called once every second! Not once every second! If you want seconds then use alarms*! 0 is the start of every minute and 59 is the end of every minute.stamper = stamper:OhMinute(NUMBER minute, FUNCTION func) — This takes a value between 0 and 59. This event is called once every hour*! Same concept as OnSecond()stamper = stamper:OhHour(NUMBER hour, FUNCTION func) — This takes a value between 0 and 23. This event is called once every day*! 0 is midnight and 23 is 11pm if you use 12 hour based time.stamper = stamper:OnDay(STRING/NUMBER day, FUNCTION func) — So the days work like this ‘Sun’, ‘Mon’, ‘Tue’, ‘Wed’, ‘Thu’, ‘Fri’, ‘Sat’. When in string form this is called every week. When in number form this is called every month*!
There is a gotcha though with this. Months can have 28,29,30, and 31 days to it, which means that something needs to be done when dealing with the last few days of a month. I am aware of this issue and am looking into a solution that is simple and readable. I thought about allowing negitive numbers to allow one to eaisly use the last day of a month. -1 is the last day of the month where -2 is the second to last day of the month. You can go as low as -28 if you want, but this provides a nice way to do something near the end of the month that is lua like.stamper = stamper:OnMonth(NUMBER month,FUNCTION func) — This takes a value between 1 and 12. 1 being January and 12 being December. Called once per year*.stamper = stamper:OnYear(NUMBER year,FUNCTION func) — This takes a number yy. for example 18 do not use yyyy format! Odds are you will not see this method triggered more than once, unless science figures out the whole life extension thing. But every century this event is triggered*! I am going to be honest though, the odds of a system never reseting for 100 years is very unlikely, so if I used 18 (every 18th year in each century every time i load my program this event will be triggered). Does it actually work? I have no idea tbh it should, but can i prove that without actually testing it? Yes by using fake data thats how.stamper = stamper:OnTime(NUMBER hour,NUMBER minute,NUMBER second,FUNCTION func) — This takes in a time to trigger, hour, minute, second. This triggeres once a day at a certain time! Sort of like setting an alarm! You can combine events to get other effects like this!stamper = stamper:OnTime(STRING time,FUNCTION func) — This takes a string time that should be formatted like this: “hh:mm:ss” hours minutes and seconds must be given as parameters! Otherwise functions as above!
*If your program crashes or is rebooted than the data in RAM letting the code know that the function was already called will be reset! This means that if an event set to be triggered on Monday then you reboot the code it will retrigger that event on the same day if the code restarts. In a future update I am planning of writing to the disk for OnHour/Day/Week/Year events. This will be an option that can be set on the object.
Examples:
OnSecond
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+ts = multi:newTimeStamper()
+local a = 0
+ts:OnSecond(0,function()
+ a=a+1
+ print("New Minute: "..a.." <"..os.date("%M")..">")
+end)
+multi:mainloop()
+OnMinute
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+ts = multi:newTimeStamper()
+local a = 0
+ts:OnSecond(0,function()
+ a=a+1
+ print("New Hour: "..a.." <"..os.date("%I")..">")
+end)
+multi:mainloop()
+OnHour
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+ts = multi:newTimeStamper()
+ts:OnHour(0,function()
+ print("New Day")
+end)
+multi:mainloop()
+OnDay
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+ts = multi:newTimeStamper()
+ts:OnDay("Thu",function()
+ print("It's thursday!")
+end)
+multi:mainloop()
+package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+ts = multi:newTimeStamper()
+ts:OnDay(2,function()
+ print("Second day of the month!")
+end)
+multi:mainloop()
+package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+ts = multi:newTimeStamper()
+ts:OnDay(-1,function()
+ print("Last day of the month!")
+end)
+multi:mainloop()
+OnYear
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+ts = multi:newTimeStamper()
+ts:OnYear(19,function() -- They gonna wonder if they run this in 2018 why it no work :P
+ print("We did it!")
+end)
+multi:mainloop()
+OnTime
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+ts = multi:newTimeStamper()
+ts:OnTime(12,1,0,function()
+ print("Whooooo")
+end)
+multi:mainloop()
+package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+ts = multi:newTimeStamper()
+ts:OnTime("12:04:00",function()
+ print("Whooooo")
+end)
+multi:mainloop()
+Deprecated: This object was removed due to its uselessness. Metatables will work much better for what is being done. Perhaps in the future i will remake this method to use metamethods instead of basic watching every step. This will most likely be removed in the next version of the library or changed to use metatables and metamethods.watcher = multi:newWatcher(STRING name) — Watches a variable on the global namespacewatcher = multi:newWatcher(TABLE namespace, STRING name) — Watches a variable inside of a tablewatcher = watcher::OnValueChanged(Function func(self, old_value, current_value))
Example
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+test = {a=0}
+watcher = multi:newWatcher(test,"a")
+watcher:OnValueChanged(function(self, old_value, current_value)
+ print(old_value,current_value)
+end)
+multi:newTLoop(function()
+ test.a=test.a + 1
+end,.5)
+multi:mainloop()
+cobj = multi:newCustomObject(TABLE objRef, BOOLEAN isActor [false]) — Allows you to create your own multiobject that runs each allotted step. This allows you to create your own object that works with all the features that each built in multi object does. If isActor is set to true you must have an Act method in your table. See example below. If an object is not an actor than the Act method will not be automatically called for you.
Example:
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+local work = false
+ticktock = multi:newCustomObject({
+ timer = multi:newTimer(),
+ Act = function(self)
+ if self.timer:Get()>=1 then
+ work = not work
+ if work then
+ self.OnTick:Fire()
+ else
+ self.OnTock:Fire()
+ end
+ self.timer:Reset()
+ end
+ end,
+ OnTick = multi:newConnection(),
+ OnTock = multi:newConnection(),
+},true)
+ticktock.OnTick(function()
+ print("Tick")
+end)
+ticktock.OnTock(function()
+ print("Tock")
+end)
+multi:mainloop()
+This was made due to the limitations of multiObj:hold(), which no longer exists. When this library was in its infancy and before I knew about coroutines, I actually tried to emulate what coroutines did in pure lua.
The threaded bariants of the non threaded objects do exist, but there isn’t too much of a need to use them.
The main benefits of using the coroutine based threads is the thread.* namespace which gives you the ability to easily run code side by side.
A quick note on how threads are managed in the library. The library contains a scheduler which keeps track of coroutines and manages them. Coroutines take some time then give off processing to another coroutine. Which means there are some methods that you need to use in order to hand off cpu time to other coroutines or the main thread. You must hand off cpu time when inside of a non ending loop or your code will hang. Threads also have a slight delay before starting, about 3 seconds.
thread.sleep(NUMBER n) — Holds execution of the thread until a certain amount of time has passedthread.hold(FUNCTION func) — Hold execttion until the function returns truethread.skip(NUMBER n) — How many cycles should be skipped until I execute againthread.kill() — Kills the threadthread.yeild() — Is the same as using thread.skip(0) or thread.sleep(0), hands off control until the next cyclethread.isThread() — Returns true if the current running code is inside of a coroutine based threadthread.getCores() — Returns the number of cores that the current system has. (used for system threads)thread.set(STRING name, VARIABLE val) — A global interface where threads can talk with eachother. sets a variable with name and its valuethread.get(STRING name) — Gets the data stored in namethread.waitFor(STRING name) — Holds executon of a thread until variable name existsthread.testFor(STRING name,VARIABLE val,STRING sym) — holds execution untile variable name exists and is compared to val
sym can be equal to: “=”, “==”, “<”, “>”, “<=”, or “>=” the way comparisan works is: “return val sym valTested“
multi:newThread(STRING name,FUNCTION func) — Creates a new thread with name and function.
Note: newThread() returns nothing. Threads are opperated hands off everything that happens, does so inside of its functions.
Threads simplify many things that you would use non CBT objects for. I almost solely use CBT for my current programming. I will slso show the above custom object using threads instead. Yes its cool and can be done.
Examples:
package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+multi:newThread("Example of basic usage",function()
+ while true do
+ thread.sleep(1)
+ print("We just made an alarm!")
+ end
+end)
+multi:mainloop()
+package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+
+function multi:newTickTock()
+ local work = false
+ local _alive = true
+ local OnTick = multi:newConnection()
+ local OnTock = multi:newConnection()
+ local c =multi:newCustomObject{
+ OnTick = OnTick,
+ OnTock = OnTock,
+ Destroy = function()
+ _alive = false -- Threads at least how they work here now need a bit of data management for cleaning up objects. When a thread either finishes its execution of thread.kill() is called everything is removed from the scheduler letting lua know that it can garbage collect
+ end
+ }
+ multi:newThread("TickTocker",function()
+ while _alive do
+ thread.sleep(1)
+ work = not work
+ if work then
+ OnTick:Fire()
+ else
+ OnTock:Fire()
+ end
+ end
+ thread.kill() -- When a thread gets to the end of it's ececution it will automatically be ended, but having this method is good to show what is going on with your code.
+ end)
+ return c
+end
+ticktock = multi:newTickTock()
+ticktock.OnTick(function()
+ print("Tick")
+ -- The thread.* namespace works in all events that
+end)
+ticktock.OnTock(function()
+ print("Tock")
+end)
+multi:mainloop()
+package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+
+multi:newThread("TickTocker",function()
+ print("Waiting for variable a to exist...")
+ ret,ret2 = thread.hold(function()
+ return a~=nil, "test!"
+ end)
+ print(ret,ret2) -- The hold method returns the arguments when the first argument is true. This methods return feature is rather new and took more work then you think to get working. Since threads
+end)
+multi:newAlarm(3):OnRing(function() a = true end) -- allows a to exist
+
+multi:mainloop()
+process = multi:newThreadedProcess(STRING name) — Creates a process object that is able allows all processes created on it to use the thread.* namespace
nil = process:getController() — Returns nothing there is no “controller” when using threaded processesself = process:Start() — Starts the processorself = process:Pause() — Pauses the processorself = process:Resume() — Resumes a paused processorself = process:Kill() — Kills/Destroys the process threadself = process:Remove() — Destroys/Kills the processor and all of the Actors running on itself = process:Sleep(NUMBER n) — Forces a process to sleep for n amount of timeself = process:Hold(FUNCTION/NUMBER n) — Forces a process to either test a condition or sleep.
Everything eles works as if you were using the multi. interface. You can create multi objects on the process and the objects are able to use the thread. interface.
Note: When using Hold/Sleep/Skip on an object created inside of a threaded process, you actually hold the entire process! Which means all objects on that process will be stopping until the conditions are met!
Example:
test = multi:newThreadedProcess("test")
+test:newLoop(function()
+ print("HI!")
+end)
+test:newLoop(function()
+ print("HI2!")
+ thread.sleep(.5)
+end)
+multi:newAlarm(3):OnRing(function()
+ test:Sleep(10)
+end)
+test:Start()
+multi:mainloop()
+process = multi:newHyperThreadedProcess(STRING name) — Creates a process object that is able allows all processes created on it to use the thread.* namespace. Hold/Sleep/Skip can be used in each multi obj created without stopping each other object that is running, but allows for one to pause/halt a process and stop all objects running in that process.
nil = process:getController() — Returns nothing there is no “controller” when using threaded processesself = process:Start() — Starts the processorself = process:Pause() — Pauses the processorself = process:Resume() — Resumes a paused processorself = process:Kill() — Kills/Destroys the process threadself = process:Remove() — Destroys/Kills the processor and all of the Actors running on itself = process:Sleep(NUMBER n) — Forces a process to sleep for n amount of timeself = process:Hold(FUNCTION/NUMBER n) — Forces a process to either test a condition or sleep.
Example:
test = multi:newHyperThreadedProcess("test")
+test:newLoop(function()
+ print("HI!")
+end)
+test:newLoop(function()
+ print("HI2!")
+ thread.sleep(.5)
+end)
+multi:newAlarm(3):OnRing(function()
+ test:Sleep(10)
+end)
+test:Start()
+multi:mainloop()
+Same example as above, but notice how this works opposed to the non hyper version
The system threads need to be required seperatly.
local GLOBAL, THREAD = require("multi.integration.lanesManager").init()# -- We will talk about the global and thread interface that is returned
+GLOBAL, THREAD = require("multi.integration.loveManager").init()
+GLOBAL, THREAD = require("luvitManager")-- There is a catch to this*
+Using this integration modifies some methods that the multi library has.multi:canSystemThread() — Returns true is system threading is possiblemulti:getPlatform() — Returns (for now) either “lanes”, “love2d” and “luvit”
This variable is created on the main thread only inside of the multi namespace: multi.isMainThread = true
This is used to know which thread is the main thread. When network threads are being discussed there is a gotcha that needs to be addressed.
* GLOBAL and THREAD do not work currently when using the luvit integration#So you may have noticed that when using the lanes manager you need to make the global and thread local, this is due to how lanes copies local variables between states. Also love2d does not require this, actually things will break if this is done! Keep these non local since the way threading is handled at the lower level is much different anyway so GLOBAL and THREAD is automatically set up for use within a spawned thread!
THREAD.set(STRING name, VALUE val) — Sets a value in GLOBALTHREAD.get(STRING name) — Gets a value in GLOBALTHREAD.waitFor(STRING name) — Waits for a value in GLOBAL to existTHREAD.testFor(STRING name, VALUE val, STRING sym) — NOT YET IMPLEMENTED but plannedTHREAD.getCores() — Returns the number of actual system threads/coresTHREAD.kill() — Kills the threadTHREAD.getName() — Returns the name of the working threadTHREAD.sleep(NUMBER n) — Sleeps for an amount of time stopping the current threadTHREAD.hold(FUNCTION func) — Holds the current thread until a condition is metTHREAD.getID() — returns a unique ID for the current thread. This varaiable is visible to the main thread as well by accessing it through the returned thread object. OBJ.Id
Treat global like a table.
GLOBAL["name"] = "Ryan"
+print(GLOBAL["name"])
+Removes the need to use THREAD.set() and THREAD.get()
systemThread = multi:newSystemThread(STRING thread_name,FUNCTION spawned_function,ARGUMENTS ...) — Spawns a thread with a certain name.systemThread:kill() — kills a thread; can only be called in the main thread!systemThread.OnError(FUNCTION(systemthread,errMsg,errorMsgWithThreadName))
System Threads are the feature that allows a user to interact with systen threads. It differs from regular coroutine based thread in how it can interact with variables. When using system threads the GLOBAL table is the “only way”* to send data. Spawning a System thread is really simple once all the required libraries are in place. See example below:
local multi = require("multi") -- keep this global when using lanes or implicitly define multi within the spawned thread
+local GLOBAL, THREAD = require("multi.integration.lanesManager").init()
+multi:newSystemThread("Example thread",function()
+ local multi = require("multi") -- we are in a thread so lets not refer to that upvalue!
+ print("We have spawned a thread!")
+ -- we could do work but theres no need to we can save that for other examples
+ print("Lets have a non ending loop!")
+ while true do
+ -- If this was not in a thread execution would halt for the entire process
+ end
+end,"A message that we are passing") -- There are restrictions on what can be passed!
+
+tloop = multi:newTLoop(function()
+ print("I'm still kicking!")
+end,1)
+multi:mainloop()
+*This isn’t entirely true, as of right now the compatiablity with the lanes library and love2d engine have their own methods to share data, but if you would like to have your code work in both enviroments then using the GLOBAL table and the data structures provided by the multi library will ensure this happens. If you do not plan on having support for both platforms then feel free to use linda’s in lanes and channels in love2d.
Note: luvit currently has very basic support, it only allows the spawning of system threads, but no way to send data back and forth as of yet. I do not know if this is doable or not, but I will keep looking into it. If I can somehow emulate System Threaded Queues and the GLOBAL tabke then all other datastructures will work!
Great we are able to spawn threads, but unless your working with a process that works on passed data and then uses a socket or writes to the disk I can’t do to much with out being able to pass data between threads. This section we will look at how we can share objects between threads. In order to keep the compatibility between both love2d and lanes I had to format the system threaded objects in a strange way, but they are consistant and should work on both enviroments.
When creating objects with a name they are automatically exposed to the GLOBAL table. Which means you can retrieve them from a spawned thread. For example we have a queue object, which will be discussed in more detail next.
-- Exposing a queue
+multi = require("multi")
+local GLOBAL, THREAD = require("multi.integration.lanesManager").init() -- The standard setup above
+queue = multi:newSystemThreadedQueue("myQueue"):init() -- We create and initiate the queue for the main thread
+queue:push("This is a test!") -- We push some data onto the queue that other threads can consume and do stuff with
+multi:newSystemThread("Example thread",function() -- Create a system thread
+ queue = THREAD.waitFor("myQueue"):init() -- Get the queue. It is good pratice to use the waitFor command when getting objects. If it doesn't exist yet we wait for it, preventing future errors. It is possible for the data to not ve present when a thread is looking for it! Especally when using the love2d module, my fault needs some rewriting data passing on the GLOBAL is quite slow, but the queue internally uses channels so after it is exposed you should have good speeds!
+ local data = queue:pop() -- Get the data
+ print(data) -- print the data
+end)
+multi:mainloop()
+queue(nonInit) = multi:newSystemThreadedQueue(STRING name) — You must enter a name!queue = queue:init() — initiates the queue, without doing this it will not workvoid = queue:push(DATA data) — Pushes data into a queue that all threads that have been shared have access todata = queue:pop() — pops data from the queue removing it from all threadsdata = queue:peek() — looks at data that is on the queue, but dont remove it from the queue
This object the System Threaded Queue is the basis for all other data structures that a user has access to within the “shared” objects.
General tips when using a queue. You can always pop from a queue without worrying if another thread poped that same data, BUT if you are peeking at a queue there is the possibility that another thread popped the data while you are peeking and this could cause an issue, depends on what you are doing though. It’s important to keep this in mind when using queues.
Let’s get into some examples:
multi = require("multi")
+thread_names = {"Thread_A","Thread_B","Thread_C","Thread_D"}
+local GLOBAL, THREAD = require("multi.integration.lanesManager").init()
+queue = multi:newSystemThreadedQueue("myQueue"):init()
+for _,n in pairs(thread_names) do
+ multi:newSystemThread(n,function()
+ queue = THREAD.waitFor("myQueue"):init()
+ local name = THREAD.getName()
+ local data = queue:pop()
+ while data do
+ print(name.." "..data)
+ data = queue:pop()
+ end
+ end)
+end
+for i=1,100 do
+ queue:push(math.random(1,1000))
+end
+multi:newEvent(function() -- Felt like using the event object, I hardly use them for anything non internal
+ return not queue:peek()
+end):OnEvent(function()
+ print("No more data within the queue!")
+ os.exit()
+end)
+multi:mainloop()
+You have probable noticed that the output from this is a total mess! Well I though so too, and created the system threaded console!
console(nonInit) = multi:newSystemThreadedConsole(STRING name) — Creates a console object called name. The name is mandatory!concole = console:inti() — initiates the console objectconsole:print(...) — prints to the consoleconsole:write(msg) — writes to the console, to be fair you wouldn’t want to use this one.
The console makes printing from threads much cleaner. We will use the same example from above with the console implemented and compare the outputs and how readable they now are!
multi = require("multi")
+thread_names = {"Thread_A","Thread_B","Thread_C","Thread_D"}
+local GLOBAL, THREAD = require("multi.integration.lanesManager").init()
+multi:newSystemThreadedConsole("console"):init()
+queue = multi:newSystemThreadedQueue("myQueue"):init()
+for _,n in pairs(thread_names) do
+ multi:newSystemThread(n,function()
+ local queue = THREAD.waitFor("myQueue"):init()
+ local console = THREAD.waitFor("console"):init()
+ local name = THREAD.getName()
+ local data = queue:pop()
+ while data do
+ --THREAD.sleep(.1) -- uncomment this to see them all work
+ console:print(name.." "..data)
+ data = queue:pop()
+ end
+ end)
+end
+for i=1,100 do
+ queue:push(math.random(1,1000))
+end
+multi:newEvent(function()
+ return not queue:peek()
+end):OnEvent(function()
+ multi:newAlarm(.1):OnRing(function() -- Well the mainthread has to read from an internal queue so we have to wait a sec
+ print("No more data within the queue!")
+ os.exit()
+ end)
+end)
+multi:mainloop()
+As you see the output here is so much cleaner, but we have a small gotcha, you probably noticed that I used an alarm to delay the exiting of the program for a bit. This is due to how the console object works, I send all the print data into a queue that the main thread then reads and prints out when it looks at the queue. This should not be an issue since you gain so much by having clean outputs!
Another thing to note, because system threads are put to work one thread at a time, really quick though, the first thread that is loaded is able to complete the tasks really fast, its just printing after all. If you want to see all the threads working uncomment the code with THREAD.sleep(.1)
connection(nonInit) = multi:newSystemThreadedConnection(name,protect) — creates a connecion objectconnection = connection:init() — initaties the connection objectconnectionID = connection:connect(FUNCTION func) — works like the regular connect functionvoid = connection:holdUT(NUMBER/FUNCTION n) — works just like the regular holdut functionvoid = connection:Remove() — works the same as the defaultvoic = connection:Fire(ARGS ...) — works the same as the default
In the current form a connection object requires that the multi:mainloop() is running on the threads that are sharing this object! By extention since SystemThreadedTables rely on SystemThreadedConnections they have the same requirements. Both objects should not be used for now.
Since the current object is not in a stable condition, I will not be providing examples of how to use it just yet!
*The main issue we have with the connection objects in this form is proper comunication and memory managament between threads. For example if a thread crashes or no longer exists the current apporach to how I manage the connection objects will cause all connections to halt. This feature is still being worked on and has many bugs to be patched out. for now only use for testing purposes.
bench = multi:SystemThreadedBenchmark(NUMBER seconds) — runs a benchmark for a certain amount of timebench:OnBench(FUNCTION callback(NUMBER steps/second))
multi = require("multi")
+local GLOBAL, THREAD = require("multi.integration.lanesManager").init()
+multi:SystemThreadedBenchmark(1).OnBench(function(...)
+ print(...)
+end)
+multi:mainloop()
+Fixed: multi.Stop() not actually stopping due to the new pirority management scheme and preformance boost changes.
Thats all for this update
Fixed: SystemThreadedJobQueues
Quick note on the 13.0.0 update:
This update I went all in finding bugs and improving proformance within the library. I added some new features and the new task manager, which I used as a way to debug the library was a great help, so much so thats it is now a permanent feature. It’s been about half a year since my last update, but so much work needed to be done. I hope you can find a use in your code to use my library. I am extremely proud of my work; 7 years of development, I learned so much about lua and programming through the creation of this library. It was fun, but there will always be more to add and bugs crawling there way in. I can’t wait to see where this library goes in the future!
Fixed: Tons of bugs, I actually went through the entire library and did a full test of everything, I mean everything, while writing the documentation.
Changed:
Connection Example:
loop = multi:newTLoop(function(self)
+ self:OnLoops() -- new way to Fire a connection! Only works when used on a multi object, bin objects, or any object that contains a Type variable
+end,1)
+loop.OnLoops = multi:newConnection()
+loop.OnLoops(function()
+ print("Looping")
+end)
+multi:mainloop()
+Function Example:
func = multi:newFunction(function(self,a,b)
+ self:Pause()
+ return 1,2,3
+end)
+print(func()) -- returns: 1, 2, 3
+print(func()) -- nil, true
+Removed:
These didn’t have much use in their previous form, but with the addition of hyper threaded processes the goals that these objects aimed to solve are now possible using a process
Fixed:
Added:
print enables multi.print() to workpackage.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+conn = multi:newConnector()
+conn.OnTest = multi:newConnection()
+conn.OnTest(function()
+ print("Yes!")
+end)
+test = multi:newHyperThreadedProcess("test")
+test:newTLoop(function()
+ print("HI!")
+ conn:OnTest()
+end,1)
+test:newLoop(function()
+ print("HEY!")
+ thread.sleep(.5)
+end)
+multi:newAlarm(3):OnRing(function()
+ test:Sleep(10)
+end)
+test:Start()
+multi:mainloop()
+Table format for getTasksDetails(STRING format)
{
+ {TID = 1,Type="",Priority="",Uptime=0}
+ {TID = 2,Type="",Priority="",Uptime=0}
+ ...
+ {TID = n,Type="",Priority="",Uptime=0}
+ ThreadCount = 0
+ threads={
+ [Thread_Name]={
+ Uptime = 0
+ }
+ }
+}
+Note: After adding the getTasksDetails() function I noticed many areas where threads, and tasks were not being cleaned up and fixed the leaks. I also found out that a lot of tasks were starting by default and made them enable only. If you compare the benchmark from this version to last version you;ll notice a signifacant increase in performance.
Going forward:
Fixed: multi.Stop() not actually stopping due to the new pirority management scheme and preformance boost changes.
Thats all for this update
Fixed: SystemThreadedJobQueues
Fixed: SystemThreadedConnection
Removed: multi:newQueuer
Going forward:
Going forwardGoing forward:
Destroy some misspellings.
+- Massive object management bugs which caused performance to drop like a rock.
+- Found a bug with processors not having the Destroy() function implemented properly.
+- Found an issue with the rockspec which is due to the networkManager additon. The net Library and the multi Library are now codependent if using that feature. Going forward you will have to now install the network library separately
+- Insane proformance bug found in the networkManager file, where each connection to a node created a new thread (VERY BAD) If say you connected to 100s of threads, you would lose a lot of processing power due to a bad implementation of this feature. But it goes futhur than this, the net library also creates a new thread for each connection made, so times that initial 100 by about 3, you end up with a system that quickly eats itself. I have to do tons of rewriting of everything. Yet another setback for the 13.0.0 release (Im releasing 13.0.0 though this hasn't been ironed out just yet)
+- Fixed an issue where any argument greater than 256^2 or 65536 bytes is sent the networkmanager would soft crash. This was fixed by increading the limit to 256^4 or 4294967296. The fix was changing a 2 to a 4. Arguments greater than 256^4 would be impossible in 32 bit lua, and highly unlikely even in lua 64 bit. Perhaps someone is reading an entire file into ram and then sending the entire file that they read over a socket for some reason all at once!?
+- Fixed an issue with processors not properly destroying objects within them and not being destroyable themselves
+- Fixed a bug where pause and resume would duplicate objects! Not good
+- Noticed that the switching of lua states, corutine based threading, is slower than multi-objs (Not by much though).
+- multi:newSystemThreadedConnection(name,protect) -- I did it! It works and I believe all the gotchas are fixed as well.
+-- Issue one, if a thread died that was connected to that connection all connections would stop since the queue would get clogged! FIXED
+-- There is one thing, the connection does have some handshakes that need to be done before it functions as normal!
+
+Added:
+- Documentation, the purpose of 13.0.0, orginally going to be 12.2.3, but due to the amount of bugs and features added it couldn't be a simple bug fix update.
+- multi:newHyperThreadedProcess(STRING name) -- This is a version of the threaded process that gives each object created its own coroutine based thread which means you can use thread.* without affecting other objects created within the hyper threaded processes. Though, creating a self contained single thread is a better idea which when I eventually create the wiki page I'll discuss
+- multi:newConnector() -- A simple object that allows you to use the new connection Fire syntax without using a multi obj or the standard object format that I follow.
+- multi:purge() -- Removes all references to objects that are contained withing the processes list of tasks to do. Doing this will stop all objects from functioning. Calling Resume on an object should make it work again.
+- multi:getTasksDetails(STRING format) -- Simple function, will get massive updates in the future, as of right now It will print out the current processes that are running; listing their type, uptime, and priority. More useful additions will be added in due time. Format can be either a string "s" or "t" see below for the table format
+- multi:endTask(TID) -- Use multi:getTasksDetails("t") to get the tid of a task
+- multi:enableLoadDetection() -- Reworked how load detection works. It gives better values now, but it still needs some work before I am happy with it
+- THREAD.getID() -- returns a unique ID for the current thread. This varaiable is visible to the main thread as well by accessing it through the returned thread object. OBJ.Id Do not confuse this with thread.* this refers to the system threading interface. Each thread, including the main thread has a threadID the main thread has an ID of 0!
+- multi.print(...) works like normal print, but only prints if the setting print is set to true
+- setting: `print` enables multi.print() to work
+- STC: IgnoreSelf defaults to false, if true a Fire command will not be sent to the self
+- STC: OnConnectionAdded(function(connID)) -- Is fired when a connection is added you can use STC:FireTo(id,...) to trigger a specific connection. Works like the named non threaded connections, only the id's are genereated for you.
+- STC: FireTo(id,...) -- Described above.
+
+```lua
+package.path="?/init.lua;?.lua;"..package.path
+local multi = require("multi")
+conn = multi:newConnector()
+conn.OnTest = multi:newConnection()
+conn.OnTest(function()
+ print("Yes!")
+end)
+test = multi:newHyperThreadedProcess("test")
+test:newTLoop(function()
+ print("HI!")
+ conn:OnTest()
+end,1)
+test:newLoop(function()
+ print("HEY!")
+ thread.sleep(.5)
+end)
+multi:newAlarm(3):OnRing(function()
+ test:Sleep(10)
+end)
+test:Start()
+multi:mainloop()
+```
+Table format for getTasksDetails(STRING format)
+```lua
+{
+ {TID = 1,Type="",Priority="",Uptime=0}
+ {TID = 2,Type="",Priority="",Uptime=0}
+ ...
+ {TID = n,Type="",Priority="",Uptime=0}
+ ThreadCount = 0
+ threads={
+ [Thread_Name]={
+ Uptime = 0
+ }
+ }
+}
+```
+**Note:** After adding the getTasksDetails() function I noticed many areas where threads, and tasks were not being cleaned up and fixed the leaks. I also found out that a lot of tasks were starting by default and made them enable only. If you compare the benchmark from this version to last version you;ll notice a signifacant increase in performance.
+
+**Going forward:**
+- Work on system threaded functions
+- work on the node manager
+- patch up bugs
+- finish documentstion
+
+Update 12.2.2 Time for some more bug fixes!
-------------
Fixed: multi.Stop() not actually stopping due to the new pirority management scheme and preformance boost changes.
Thats all for this update
-Update 12.2.1 Time for some bug fixes!
+Update 12.2.1 Time for some bug fixes!
-------------
Fixed: SystemThreadedJobQueues
- You can now make as many job queues as you want! Just a warning when using a large amount of cores for the queue it takes a second or 2 to set up the jobqueues for data transfer. I am unsure if this is a lanes thing or not, but love2d has no such delay when setting up the jobqueue!
@@ -19,7 +142,7 @@ Fixed: SystemThreadedConnection
Removed: multi:newQueuer
- This feature has no real use after corutine based threads were introduced. You can use those to get the same effect as the queuer and do it better too.
-Going forward:
+Going forwardGoing forward:
- Will I ever finish steralization? Who knows, but being able to save state would be nice. The main issue is there is no simple way to save state. While I can provide methods to allow one to turn the objects into strings and back, there is no way for me to make your code work with it in a simple way. For now only the basic functions will be here.
- I need to make better documentation for this library as well. In its current state, all I have are examples and not a list of what is what.
diff --git a/examples/RegisteredFunctions.dat b/examples/RegisteredFunctions.dat
new file mode 100644
index 0000000..e69de29
diff --git a/examples/network-node1.lua b/examples/network-node1.lua
index 4a828a5..8d3b0ca 100644
--- a/examples/network-node1.lua
+++ b/examples/network-node1.lua
@@ -5,30 +5,13 @@ nGLOBAL = require("multi.integration.networkManager").init()
node = multi:newNode{
crossTalk = false, -- default value, allows nodes to talk to eachother. WIP NOT READY YET!
allowRemoteRegistering = true, -- allows you to register functions from the master on the node, default is false
- name = nil, --"TESTNODE", -- default value is nil, if nil a random name is generated. Naming nodes are important if you assign each node on a network with a different task
+ name = "MASTERPC", -- default value is nil, if nil a random name is generated. Naming nodes are important if you assign each node on a network with a different task
--noBroadCast = true, -- if using the node manager, set this to true to save on some cpu cycles
--managerDetails = {"localhost",12345}, -- connects to the node manager if one exists
}
function RemoteTest(a,b,c) -- a function that we will be executing remotely
- --print("Yes I work!",a,b,c)
- multi:newThread("waiter",function()
- print("Hello!")
- while true do
- thread.sleep(2)
- node:pushTo("Main","This is a test")
- end
- end)
+ print("Yes I work!",a,b,c)
end
-multi:newThread("some-test",function()
- local dat = node:pop()
- while true do
- thread.skip(10)
- if dat then
- print(dat)
- end
- dat = node:pop()
- end
-end,"NODE_TESTNODE")
settings = {
priority = 0, -- 1 or 2
stopOnError = true,
diff --git a/multi/compat/love2d.lua b/multi/compat/love2d.lua
index fe77db1..7048deb 100644
--- a/multi/compat/love2d.lua
+++ b/multi/compat/love2d.lua
@@ -1,7 +1,7 @@
--[[
MIT License
-Copyright (c) 2018 Ryan Ward
+Copyright (c) 2019 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
@@ -37,6 +37,7 @@ 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
@@ -51,6 +52,7 @@ multi.OnPreLoad(function()
end
end
end
+ Hook("quit",multi.OnQuit)
Hook("keypressed",multi.OnKeyPressed)
Hook("keyreleased",multi.OnKeyReleased)
Hook("mousepressed",multi.OnMousePressed)
@@ -67,4 +69,8 @@ multi.OnPreLoad(function()
end
end)
end)
-return multi
\ No newline at end of file
+multi.OnQuit(function()
+ multi.Stop()
+ love.event.quit()
+end)
+return multi
diff --git a/multi/init.lua b/multi/init.lua
index b75d144..19894e1 100644
--- a/multi/init.lua
+++ b/multi/init.lua
@@ -1,7 +1,7 @@
--[[
MIT License
-Copyright (c) 2018 Ryan Ward
+Copyright (c) 2019 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
@@ -24,17 +24,17 @@ SOFTWARE.
local bin = pcall(require,"bin")
local multi = {}
local clock = os.clock
-multi.Version = "12.2.2"
-multi._VERSION = "12.2.2"
+multi.Version = "13.0.0"
+multi._VERSION = "13.0.0"
multi.stage = "stable"
multi.__index = multi
+multi.Name = "multi.root"
multi.Mainloop = {}
multi.Garbage = {}
multi.ender = {}
multi.Children = {}
multi.Active = true
multi.fps = 60
-multi.Id = -1
multi.Type = "mainprocess"
multi.Rest = 0
multi._type = type
@@ -61,11 +61,20 @@ multi.Priority_Normal = 64
multi.Priority_Below_Normal = 256
multi.Priority_Low = 1024
multi.Priority_Idle = 4096
+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 -- Between 1, 2 and 4
-multi.Priority=multi.Priority_Core
+multi.Priority=multi.Priority_High
multi.threshold=256
multi.threstimed=.001
function multi.queuefinal(self)
@@ -101,25 +110,65 @@ function table.merge(t1, t2)
end
return t1
end
-_print=print
-function print(...)
- if not __SUPPRESSPRINTS then
- _print(...)
- end
-end
-_write=io.write
-function io.write(...)
- if not __SUPPRESSWRITES then
- _write(...)
- end
-end
function multi:setThrestimed(n)
self.deltaTarget=n or .1
end
+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 MaxLoad = nil
+function multi:setLoad(n)
+ MaxLoad = n
+end
+local busy = false
+local lastVal = 0
+local bb = 0
function multi:getLoad()
- if multi.load_updater:isPaused() then multi.load_updater:Resume() return 0 end
- local val = math.abs(self.dStepA-self.dStepB)/multi.deltaTarget*100
- if val > 100 then return 100 else return val end
+ 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:setDomainName(name)
self[name]={}
@@ -151,6 +200,13 @@ function multi:setPriority(s)
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
-- System
function os.getOS()
@@ -184,21 +240,6 @@ multi.GetParentProcess=multi.getParentProcess
function multi.Stop()
mainloopActive=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
-multi.Condition=multi.condition
function multi:isHeld()
return self.held
end
@@ -208,7 +249,7 @@ function multi.executeFunction(name,...)
if type(_G[name])=='function' then
_G[name](...)
else
- print('Error: Not a function')
+ multi.print('Error: Not a function')
end
end
function multi:getChildren()
@@ -240,7 +281,7 @@ function multi:benchMark(sec,p,pt)
local temp=self:newLoop(function(self,t)
if t>sec then
if pt then
- print(pt.." "..c.." Steps in "..sec.." second(s)!")
+ multi.print(pt.." "..c.." Steps in "..sec.." second(s)!")
end
self.tt(sec,c)
self:Destroy()
@@ -255,6 +296,103 @@ function multi:benchMark(sec,p,pt)
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]","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],i})
+ 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 multi.scheduler then
+ for i=1,#multi.scheduler.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: "..#multi.scheduler.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 = #multi.scheduler.Threads,
+ MemoryUsage = math.ceil(collectgarbage("count")).." KB",
+ PriorityScheme = priorityTable[multi.defaultSettings.priority or 0],
+ SystemLoad = multi.Round(load,2),
+ CyclesPerSecondPerTask = steps,
+ }
+ str.threads = {}
+ str.systemthreads = {}
+ for i,v in pairs(self.Mainloop) do
+ str[#str+1]={Type=v.Type,Name=v.Name,Uptime=os.clock()-v.creationTime,Priority=self.PriorityResolve[v.Priority],TID = i}
+ end
+ for i=1,#multi.scheduler.Threads do
+ str.threads[multi.scheduler.Threads[i].Name]={Uptime = os.clock()-multi.scheduler.Threads[i].creationTime}
+ end
+ if multi.canSystemThread then
+ for i=1,#multi.SystemThreads do
+ str.systemthreads[multi.SystemThreads[i].Name]={Uptime = os.clock()-multi.SystemThreads[i].creationTime}
+ end
+ end
+ return str
+ end
+end
+function multi:endTask(TID)
+ self.Mainloop[TID]:Destroy()
+end
function multi.startFPSMonitior()
if not multi.runFPS then
multi.doFPS(s)
@@ -277,10 +415,11 @@ function multi.timer(func,...)
return t,unpack(args)
end
function multi:IsAnActor()
- return ({watcher=true,tstep=true,step=true,updater=true,loop=true,alarm=true,event=true})[self.Type]
+ return self.Act~=nil
end
function multi:OnMainConnect(func)
table.insert(self.func,func)
+ return self
end
function multi:reallocate(o,n)
n=n or #o.Mainloop+1
@@ -301,11 +440,14 @@ function multi:getJobs()
return #self.Jobs
end
function multi:removeJob(name)
+ local count = 0
for i=#self.Jobs,1,-1 do
if self.Jobs[i][2]==name then
table.remove(self.Jobs,i)
+ count = count + 1
end
end
+ return count
end
function multi:FreeMainEvent()
self.func={}
@@ -318,7 +460,7 @@ function multi:connectFinal(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!")
+ 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
end
@@ -366,7 +508,7 @@ function multi:SetTime(n)
self:Destroy()
end
end
- return c
+ return self
end
multi.ResetTime=multi.SetTime
function multi:ResolveTimer(...)
@@ -388,11 +530,15 @@ end
-- Timer stuff done
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()")
+ 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
- if self.Parent.Mainloop[self.Id]~=nil then
- table.remove(self.Parent.Mainloop,self.Id)
+ local loop = self.Parent.Mainloop
+ for i=1,#loop do
+ if loop[i] == self then
+ table.remove(loop,i)
+ break
+ end
end
end
return self
@@ -405,9 +551,8 @@ function multi:Resume()
c[i]:Resume()
end
else
- if self:isPaused() then
+ if self.Active==false then
table.insert(self.Parent.Mainloop,self)
- self.Id=#self.Parent.Mainloop
self.Active=true
end
end
@@ -441,8 +586,13 @@ function multi:isDone()
end
multi.IsDone=multi.isDone
function multi:create(ref)
- multi.OnObjectCreated:Fire(ref)
+ multi.OnObjectCreated:Fire(ref,self)
end
+function multi:setName(name)
+ self.Name = name
+ return self
+end
+multi.SetName = multi.setName
--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
@@ -458,10 +608,10 @@ function multi:newBase(ins)
c.funcTMR={}
c.ender={}
c.important={}
- c.Id=0
c.Act=function() end
c.Parent=self
c.held=false
+ c.creationTime = os.clock()
if ins then
table.insert(self.Mainloop,ins,c)
else
@@ -469,7 +619,7 @@ function multi:newBase(ins)
end
return c
end
-function multi:newProcess(file)
+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, self)
@@ -480,27 +630,31 @@ function multi:newProcess(file)
c.Mainloop={}
c.Garbage={}
c.Children={}
- c.Active=true
- c.Id=-1
+ c.Active=false
c.Rest=0
c.Jobs={}
c.queue={}
c.jobUS=2
- c.l=self:newLoop(function(self,dt) c:uManager() end)
- c.l:Pause()
+ 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()
- if self.l then
- self.l:Resume()
- end
+ self.Active = true
return self
end
function c:Resume()
- if self.l then
- self.l:Resume()
- end
+ self.Active = false
+ return self
+ end
+ function c:setName(name)
+ c.l.Name = name
return self
end
function c:Pause()
@@ -510,14 +664,31 @@ function multi:newProcess(file)
return self
end
function c:Remove()
- self:Destroy()
- self.l:Destroy()
- return self
+ 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
+ -- c.__Destroy = self.Destroy
+ -- c.Destroy = c.Remove
self:create(c)
--~ c:IngoreObject()
return c
@@ -561,10 +732,22 @@ function multi:newTimer()
self:create(c)
return c
end
-function multi:newConnection(protect)
+function multi:newConnector()
+ local c = {Type = "connector"}
+ return c
+end
+function multi:newConnection(protect,func)
local c={}
+ c.callback = func
c.Parent=self
- setmetatable(c,{__call=function(self,...) return self:connect(...) end})
+ setmetatable(c,{__call=function(self,...)
+ local t = ...
+ if type(t)=="table" and t.Type ~= nil then
+ return self:Fire(args,select(2,...))
+ else
+ return self:connect(...)
+ end
+ end})
c.Type='connector'
c.func={}
c.ID=0
@@ -599,7 +782,7 @@ function multi:newConnection(protect)
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 ignore
+ Fire=function() return end -- if the connection doesn't exist lets call all of them or silently ignore
}
else
return self.connections[name] or self
@@ -614,7 +797,7 @@ function multi:newConnection(protect)
table.remove(temp,1)
table.insert(ret,temp)
else
- print(temp[2])
+ multi.print(temp[2])
end
else
table.insert(ret,{self.func[i][1](...)})
@@ -671,6 +854,9 @@ function multi:newConnection(protect)
if name then
self.connections[name]=temp
end
+ if self.callback then
+ self.callback(temp)
+ end
return temp
end
c.Connect=c.connect
@@ -696,7 +882,6 @@ function multi:newJob(func,name)
end
c.Active=true
c.func={}
- c.Id=0
c.Parent=self
c.Type='job'
c.trigfunc=func or function() end
@@ -705,7 +890,7 @@ function multi:newJob(func,name)
end
table.insert(self.Jobs,{c,name})
if self.JobRunner==nil then
- self.JobRunner=self:newAlarm(self.jobUS)
+ self.JobRunner=self:newAlarm(self.jobUS):setName("multi.jobHandler")
self.JobRunner:OnRing(function(self)
if #self.Parent.Jobs>0 then
if self.Parent.Jobs[1] then
@@ -722,37 +907,891 @@ function multi.nextStep(func)
if not next then
next = {func}
else
- next[#self.next+1] = func
+ next[#next+1] = func
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
+multi.OnPreLoad=multi:newConnection()
+--Core Actors
+function multi:newCustomObject(objRef,isActor)
+ local c={}
+ if isActor then
+ c=self:newBase()
+ if type(objRef)=='table' then
+ table.merge(c,objRef)
end
- })
- self:create(temp)
- return temp
-end
-multi.NewRange=multi.newRange
-function multi:newCondition(func)
- local c={['condition']=func,Type="condition"}
+ 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
self:create(c)
return c
end
-multi.OnPreLoad=multi:newConnection()
-multi.NewCondition=multi.newCondition
+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=self.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
+ 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:newTrigger(func)
+ local c={}
+ c.Type='trigger'
+ c.trigfunc=func or function() end
+ function c:Fire(...)
+ self:trigfunc(...)
+ return self
+ end
+ self:create(c)
+ return c
+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=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()
+ return self
+ 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,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=self.clock()
+ self:Resume()
+ return self
+ end
+ self:create(c)
+ return c
+end
+function multi:newTimeStamper()
+ local c=self:newUpdater(self.Priority_Idle)
+ c:OnUpdate(function()
+ c:Run()
+ end)
+ local feb = 28
+ local leap = tonumber(os.date("%Y"))%4==0 and (tonumber(os.date("%Y"))%100~=0 or tonumber(os.date("%Y"))%400==0)
+ if leap then
+ feb = 29
+ end
+ local dInM = {
+ ["01"] = 31,
+ ["02"] = feb,
+ ["03"] = 31,
+ ["04"] = 30,
+ ["05"] = 31,
+ ["06"] = 30,
+ ["07"] = 31, -- This is dumb, why do we still follow this double 31 days!?
+ ["08"] = 31,
+ ["09"] = 30,
+ ["10"] = 31,
+ ["11"] = 30,
+ ["12"] = 31,
+ }
+ c.Type='timestamper'
+ c:setPriority("Idle")
+ c.hour = {}
+ c.minute = {}
+ c.second = {}
+ c.time = {}
+ c.day = {}
+ c.month = {}
+ c.year = {}
+ function c:Run()
+ for i=1,#self.hour do
+ if self.hour[i][1]==os.date("%H") and self.hour[i][3] then
+ self.hour[i][2](self)
+ self.hour[i][3]=false
+ elseif self.hour[i][1]~=os.date("%H") and not self.hour[i][3] then
+ self.hour[i][3]=true
+ end
+ end
+ for i=1,#self.minute do
+ if self.minute[i][1]==os.date("%M") and self.minute[i][3] then
+ self.minute[i][2](self)
+ self.minute[i][3]=false
+ elseif self.minute[i][1]~=os.date("%M") and not self.minute[i][3] then
+ self.minute[i][3]=true
+ end
+ end
+ for i=1,#self.second do
+ if self.second[i][1]==os.date("%S") and self.second[i][3] then
+ self.second[i][2](self)
+ self.second[i][3]=false
+ elseif self.second[i][1]~=os.date("%S") and not self.second[i][3] then
+ self.second[i][3]=true
+ end
+ end
+ for i=1,#self.day do
+ if type(self.day[i][1])=="string" then
+ if self.day[i][1]==os.date("%a") and self.day[i][3] then
+ self.day[i][2](self)
+ self.day[i][3]=false
+ elseif self.day[i][1]~=os.date("%a") and not self.day[i][3] then
+ self.day[i][3]=true
+ end
+ else
+ local dday = self.day[i][1]
+ if dday < 0 then
+ dday = dInM[os.date("%m")]+(dday+1)
+ end
+ if string.format("%02d",dday)==os.date("%d") and self.day[i][3] then
+ self.day[i][2](self)
+ self.day[i][3]=false
+ elseif string.format("%02d",dday)~=os.date("%d") and not self.day[i][3] then
+ self.day[i][3]=true
+ end
+ end
+ end
+ for i=1,#self.month do
+ if self.month[i][1]==os.date("%m") and self.month[i][3] then
+ self.month[i][2](self)
+ self.month[i][3]=false
+ elseif self.month[i][1]~=os.date("%m") and not self.month[i][3] then
+ self.month[i][3]=true
+ end
+ end
+ for i=1,#self.time do
+ if self.time[i][1]==os.date("%X") and self.time[i][3] then
+ self.time[i][2](self)
+ self.time[i][3]=false
+ elseif self.time[i][1]~=os.date("%X") and not self.time[i][3] then
+ self.time[i][3]=true
+ end
+ end
+ for i=1,#self.year do
+ if self.year[i][1]==os.date("%y") and self.year[i][3] then
+ self.year[i][2](self)
+ self.year[i][3]=false
+ elseif self.year[i][1]~=os.date("%y") and not self.year[i][3] then
+ self.year[i][3]=true
+ end
+ end
+ end
+ function c:OnTime(hour,minute,second,func)
+ if type(hour)=="number" then
+ self.time[#self.time+1]={string.format("%02d:%02d:%02d",hour,minute,second),func,true}
+ else
+ self.time[#self.time+1]={hour,minute,true}
+ end
+ return self
+ end
+ function c:OnHour(hour,func)
+ self.hour[#self.hour+1]={string.format("%02d",hour),func,true}
+ return self
+ end
+ function c:OnMinute(minute,func)
+ self.minute[#self.minute+1]={string.format("%02d",minute),func,true}
+ return self
+ end
+ function c:OnSecond(second,func)
+ self.second[#self.second+1]={string.format("%02d",second),func,true}
+ return self
+ end
+ function c:OnDay(day,func)
+ self.day[#self.day+1]={day,func,true}
+ return self
+ end
+ function c:OnMonth(month,func)
+ self.month[#self.month+1]={string.format("%02d",month),func,true}
+ return self
+ end
+ function c:OnYear(year,func)
+ self.year[#self.year+1]={string.format("%02d",year),func,true}
+ return self
+ end
+ self:create(c)
+ return c
+end
+function multi:newWatcher(namespace,name)
+ local function WatcherObj(ns,n)
+ if self.Type=='queue' then
+ multi.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)
+ return self
+ 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
+ multi.print('Warning, invalid arguments! Nothing returned!')
+ end
+end
+-- Threading stuff
+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)
+ return 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.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 thread.testFor(name,_val,sym)
+ thread.hold(function()
+ local val = thread.get(name)~=nil
+ if val then
+ if sym == "==" or sym == "=" then
+ return _val==val
+ elseif sym == ">" then
+ return _val>val
+ elseif sym == "<" then
+ return _val=" then
+ return _val>=val
+ end
+ end
+ end)
+ return thread.get(name)
+end
+function multi.print(...)
+ if multi.defaultSettings.print then
+ print(...)
+ end
+end
+multi:setDomainName("Threads")
+multi:setDomainName("Globals")
+local initT = false
+function multi:newThread(name,func)
+ if not func then return end
+ local c={}
+ c.ref={}
+ c.Name=name
+ c.thread=coroutine.create(func)
+ c.sleep=1
+ c.Type="thread"
+ c.firstRunDone=false
+ c.timer=multi: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 initT==false then
+ multi.initThreads()
+ end
+ c.creationTime = os.clock()
+end
+function multi.initThreads()
+ 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
+ multi.scheduler.counter=0
+ multi.scheduler.Threads=multi:linkDomain("Threads")
+ multi.scheduler.Globals=multi:linkDomain("Globals")
+ multi.scheduler:OnLoop(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()
+ if unpack(self.Threads[i].returns or {}) then
+ _,ret=coroutine.resume(self.Threads[i].thread,unpack(self.Threads[i].returns))
+ else
+ _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref)
+ end
+ else
+ if unpack(self.Threads[i].returns or {}) then
+ _,ret=coroutine.resume(self.Threads[i].thread,unpack(self.Threads[i].returns))
+ else
+ _,ret=coroutine.resume(self.Threads[i].thread,self.Globals)
+ end
+ end
+ if _==false then
+ self.Parent.OnError:Fire(self.Threads[i],"Error in thread: <"..self.Threads[i].Name.."> "..ret)
+ end
+ if ret==true or ret==false then
+ multi.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
+ evnt:Destroy()
+ end):setName("multi.thread.skip")
+ elseif ret[1]=="_hold_" then
+ self.Threads[i].timer:Reset()
+ self.Threads[i].sleep=math.huge
+ local event=multi:newEvent(ret[2])
+ event.returns = nil
+ event.link=self.Threads[i]
+ event:OnEvent(function(evnt)
+ evnt.link.sleep=0
+ evnt.link.returns = evnt.returns
+ multi.nextStep(function()
+ evnt:Destroy()
+ end)
+ end):setName("multi.thread.hold")
+ elseif ret.Name then
+ self.Globals[ret.Name]=ret.Value
+ end
+ end
+ end
+ end
+ end)
+end
+multi.OnError=multi:newConnection()
+function multi:newThreadedProcess(name)
+ local c = {}
+ local holding = false
+ local kill = false
+ setmetatable(c, multi)
+ function c:newBase(ins)
+ local ct = {}
+ ct.Active=true
+ ct.func={}
+ ct.ender={}
+ ct.Act=function() end
+ ct.Parent=self
+ ct.held=false
+ ct.ref=self.ref
+ table.insert(self.Mainloop,ct)
+ return ct
+ end
+ c.Parent=self
+ c.Active=true
+ c.func={}
+ c.Type='threadedprocess'
+ c.Mainloop={}
+ c.Garbage={}
+ c.Children={}
+ c.Active=true
+ c.Rest=0
+ c.updaterate=.01
+ c.restRate=.1
+ c.Jobs={}
+ c.queue={}
+ c.jobUS=2
+ c.rest=false
+ function c:getController()
+ return nil
+ end
+ function c:Start()
+ self.rest=false
+ return self
+ end
+ function c:Resume()
+ self.rest=false
+ return self
+ end
+ function c:Pause()
+ self.rest=true
+ return self
+ end
+ function c:Remove()
+ self.ref:kill()
+ return self
+ end
+ function c:Kill()
+ kill = true
+ return self
+ end
+ function c:Sleep(n)
+ holding = true
+ if type(n)=="number" then
+ multi:newAlarm(n):OnRing(function(a)
+ holding = false
+ a:Destroy()
+ end):setName("multi.TPSleep")
+ elseif type(n)=="function" then
+ multi:newEvent(n):OnEvent(function(e)
+ holding = false
+ e:Destroy()
+ end):setName("multi.TPHold")
+ end
+ return self
+ end
+ c.Hold=c.Sleep
+ multi:newThread(name,function(ref)
+ while true do
+ thread.hold(function()
+ return not(holding)
+ end)
+ c:uManager()
+ end
+ end)
+ return c
+end
+function multi:newHyperThreadedProcess(name)
+ if not name then error("All threads must have a name!") end
+ local c = {}
+ setmetatable(c, multi)
+ local ind = 0
+ local holding = true
+ local kill = false
+ function c:newBase(ins)
+ local ct = {}
+ ct.Active=true
+ ct.func={}
+ ct.ender={}
+ ct.Act=function() end
+ ct.Parent=self
+ ct.held=false
+ ct.ref=self.ref
+ ind = ind + 1
+ multi:newThread("Proc <"..name.."> #"..ind,function()
+ while true do
+ thread.hold(function()
+ return not(holding)
+ end)
+ if kill then
+ err=coroutine.yield({"_kill_"})
+ if err then
+ error("Failed to kill a thread! Exiting...")
+ end
+ end
+ ct:Act()
+ end
+ end)
+ return ct
+ end
+ c.Parent=self
+ c.Active=true
+ c.func={}
+ c.Type='hyperthreadedprocess'
+ c.Mainloop={}
+ c.Garbage={}
+ c.Children={}
+ c.Active=true
+ c.Rest=0
+ c.updaterate=.01
+ c.restRate=.1
+ c.Jobs={}
+ c.queue={}
+ c.jobUS=2
+ c.rest=false
+ function c:getController()
+ return nil
+ end
+ function c:Start()
+ holding = false
+ return self
+ end
+ function c:Resume()
+ holding = false
+ return self
+ end
+ function c:Pause()
+ holding = true
+ return self
+ end
+ function c:Remove()
+ self.ref:kill()
+ return self
+ end
+ function c:Kill()
+ kill = true
+ return self
+ end
+ function c:Sleep(b)
+ holding = true
+ if type(b)=="number" then
+ local t = os.clock()
+ multi:newAlarm(b):OnRing(function(a)
+ holding = false
+ a:Destroy()
+ end):setName("multi.HTPSleep")
+ elseif type(b)=="function" then
+ multi:newEvent(b):OnEvent(function(e)
+ holding = false
+ e:Destroy()
+ end):setName("multi.HTPHold")
+ end
+ return self
+ end
+ c.Hold=c.Sleep
+ return c
+end
+-- Multi runners
function multi:threadloop(settings)
multi.scheduler:Destroy() -- destroy is an interesting thing... if you dont set references to nil, then you only remove it from the mainloop
local Threads=multi:linkDomain("Threads")
@@ -800,6 +1839,7 @@ function multi:threadloop(settings)
event.counter=counter+ret[2]
event:OnEvent(function(evnt)
evnt.link.sleep=0
+ evnt:Destroy()
end)
elseif ret[1]=="_hold_" then
Threads[i].timer:Reset()
@@ -808,6 +1848,7 @@ function multi:threadloop(settings)
event.link=Threads[i]
event:OnEvent(function(evnt)
evnt.link.sleep=0
+ evnt:Destroy()
end)
elseif ret.Name then
Globals[ret.Name]=ret.Value
@@ -859,14 +1900,15 @@ function multi:mainloop(settings)
local PS=self
local PStep = 1
local autoP = 0
- local solid
- local sRef
+ local solid,sRef
+ local cc=0
while mainloopActive do
- if ncount ~= 0 then
- for i = 1, ncount do
- next[i]()
+ if next then
+ local DD = table.remove(next,1)
+ while DD do
+ DD()
+ DD = table.remove(next,1)
end
- ncount = 0
end
if priority == 1 then
for _D=#Loop,1,-1 do
@@ -919,8 +1961,12 @@ function multi:mainloop(settings)
PStep=0
end
elseif priority == 3 then
- tt = clock()-t
- t = clock()
+ 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
@@ -1056,11 +2102,12 @@ function multi:uManager(settings)
end
function multi:uManagerRef(settings)
if self.Active then
- if ncount ~= 0 then
- for i = 1, ncount do
- next[i]()
+ if next then
+ local DD = table.remove(next,1)
+ while DD do
+ DD()
+ DD = table.remove(next,1)
end
- ncount = 0
end
local Loop=self.Mainloop
local PS=self
@@ -1219,1125 +2266,16 @@ function multi:uManagerRef(settings)
end
end
end
---Core Actors
-function multi:newCustomObject(objRef,t)
- local c={}
- if t=='process' then
- c=self:newBase()
- 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
- self:create(c)
- return c
-end
-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: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.Priority=self.Priority_Low
- c.timer=self:newTimer()
- c.set=set or 0
- 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()
- return self
- end
- function c:Reset(n)
- if n then self.set=n end
- self:Resume()
- self.timer:Reset()
- return self
- end
- function c:OnRing(func)
- table.insert(self.func,func)
- return self
- end
- function c:Pause()
- self.timer:Pause()
- 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=self.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
- 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
- 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
- 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:newTrigger(func)
- local c={}
- c.Type='trigger'
- c.trigfunc=func or function() end
- function c:Fire(...)
- self:trigfunc(...)
- return self
- end
- self:create(c)
- return c
-end
-function multi:newTStep(start,reset,count,set)
- local c=self:newBase()
- 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()
- return self
- 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,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=self.clock()
- self:Resume()
- return self
- end
- self:create(c)
- return c
-end
-function multi:newTimeStamper()
- local c=self:newBase()
- c.Type='timestamper'
- c.Priority=self.Priority_Idle
- c.hour = {}
- c.minute = {}
- c.second = {}
- c.time = {}
- c.day = {}
- c.month = {}
- c.year = {}
- function c:Act()
- for i=1,#self.hour do
- if self.hour[i][1]==os.date("%H") and self.hour[i][3] then
- self.hour[i][2](self)
- self.hour[i][3]=false
- elseif self.hour[i][1]~=os.date("%H") and not self.hour[i][3] then
- self.hour[i][3]=true
- end
- end
- for i=1,#self.minute do
- if self.minute[i][1]==os.date("%M") and self.minute[i][3] then
- self.minute[i][2](self)
- self.minute[i][3]=false
- elseif self.minute[i][1]~=os.date("%M") and not self.minute[i][3] then
- self.minute[i][3]=true
- end
- end
- for i=1,#self.second do
- if self.second[i][1]==os.date("%S") and self.second[i][3] then
- self.second[i][2](self)
- self.second[i][3]=false
- elseif self.second[i][1]~=os.date("%S") and not self.second[i][3] then
- self.second[i][3]=true
- end
- end
- for i=1,#self.day do
- if type(self.day[i][1])=="string" then
- if self.day[i][1]==os.date("%a") and self.day[i][3] then
- self.day[i][2](self)
- self.day[i][3]=false
- elseif self.day[i][1]~=os.date("%a") and not self.day[i][3] then
- self.day[i][3]=true
- end
- else
- if string.format("%02d",self.day[i][1])==os.date("%d") and self.day[i][3] then
- self.day[i][2](self)
- self.day[i][3]=false
- elseif string.format("%02d",self.day[i][1])~=os.date("%d") and not self.day[i][3] then
- self.day[i][3]=true
- end
- end
- end
- for i=1,#self.month do
- if self.month[i][1]==os.date("%m") and self.month[i][3] then
- self.month[i][2](self)
- self.month[i][3]=false
- elseif self.month[i][1]~=os.date("%m") and not self.month[i][3] then
- self.month[i][3]=true
- end
- end
- for i=1,#self.time do
- if self.time[i][1]==os.date("%X") and self.time[i][3] then
- self.time[i][2](self)
- self.time[i][3]=false
- elseif self.time[i][1]~=os.date("%X") and not self.time[i][3] then
- self.time[i][3]=true
- end
- end
- for i=1,#self.year do
- if self.year[i][1]==os.date("%y") and self.year[i][3] then
- self.year[i][2](self)
- self.year[i][3]=false
- elseif self.year[i][1]~=os.date("%y") and not self.year[i][3] then
- self.year[i][3]=true
- end
- end
- end
- function c:OnTime(hour,minute,second,func)
- if type(hour)=="number" then
- self.time[#self.time+1]={string.format("%02d:%02d:%02d",hour,minute,second),func,true}
- else
- self.time[#self.time+1]={hour,minute,true}
- end
- return self
- end
- function c:OnHour(hour,func)
- self.hour[#self.hour+1]={string.format("%02d",hour),func,true}
- return self
- end
- function c:OnMinute(minute,func)
- self.minute[#self.minute+1]={string.format("%02d",minute),func,true}
- return self
- end
- function c:OnSecond(second,func)
- self.second[#self.second+1]={string.format("%02d",second),func,true}
- return self
- end
- function c:OnDay(day,func)
- self.day[#self.day+1]={day,func,true}
- return self
- end
- function c:OnMonth(month,func)
- self.month[#self.month+1]={string.format("%02d",month),func,true}
- return self
- end
- function c:OnYear(year,func)
- self.year[#self.year+1]={string.format("%02d",year),func,true}
- return self
- 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)
- return self
- 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
--- Threading stuff
-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)
- return 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.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 thread.testFor(name,val,sym)
- thread.hold(function() return thread.get(name)~=nil end)
- return thread.get(name)
-end
-function multi:newTBase(name)
- local c = {}
- c.name=name
- c.Active=true
- c.func={}
- c.ender={}
- c.Id=0
- c.Parent=self
- c.important={}
- c.held=false
- c.ToString=multi.ToString
- c.ToFile=multi.ToFile
- 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:newLoop()
-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:OnLoop(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()
- if unpack(self.Threads[i].returns or {}) then
- _,ret=coroutine.resume(self.Threads[i].thread,unpack(self.Threads[i].returns))
- else
- _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref)
- end
- else
- if unpack(self.Threads[i].returns or {}) then
- _,ret=coroutine.resume(self.Threads[i].thread,unpack(self.Threads[i].returns))
- else
- _,ret=coroutine.resume(self.Threads[i].thread,self.Globals)
- end
- end
- if _==false then
- self.Parent.OnError:Fire(self.Threads[i],"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.returns = nil
- event.link=self.Threads[i]
- event:OnEvent(function(evnt)
- evnt.link.sleep=0
- evnt.link.returns = evnt.returns
- end)
- elseif ret.Name then
- self.Globals[ret.Name]=ret.Value
- end
- end
- end
- end
-end)
-multi.scheduler:Pause()
-multi.OnError=multi:newConnection()
-function multi:newThreadedAlarm(name,set)
- local c=self:newTBase(name)
- c.Type='alarmThread'
- c.timer=self:newTimer()
- c.set=set or 0
- function c:Resume()
- self.rest=false
- self.timer:Resume()
- return self
- end
- function c:Reset(n)
- if n then self.set=n end
- self.rest=false
- self.timer:Reset(n)
- return self
- end
- function c:OnRing(func)
- table.insert(self.func,func)
- return self
- end
- function c:Pause()
- self.timer:Pause()
- self.rest=true
- return self
- end
- c.rest=false
- c.updaterate=multi.Priority_Low -- skips
- c.restRate=0 -- secs
- multi:newThread(name,function(ref)
- while true do
- if c.rest then
- thread.sleep(c.restRate) -- rest a bit more when a thread is paused
- else
- if c.timer:Get()>=c.set then
- c:Pause()
- for i=1,#c.func do
- c.func[i](c)
- end
- end
- thread.skip(c.updaterate) -- lets rest a bit
- end
- end
- end)
- self:create(c)
- return c
-end
-function multi:newThreadedUpdater(name,skip)
- local c=self:newTBase(name)
- c.Type='updaterThread'
- c.pos=1
- c.skip=skip or 1
- function c:Resume()
- self.rest=false
- return self
- end
- function c:Pause()
- self.rest=true
- return self
- end
- c.OnUpdate=self.OnMainConnect
- c.rest=false
- c.updaterate=0
- c.restRate=.75
- multi:newThread(name,function(ref)
- while true do
- if c.rest then
- thread.sleep(c.restRate) -- rest a bit more when a thread is paused
- else
- for i=1,#c.func do
- c.func[i](c)
- end
- c.pos=c.pos+1
- thread.skip(c.skip)
- end
- end
- end)
- self:create(c)
- return c
-end
-function multi:newThreadedTStep(name,start,reset,count,set)
- local c=self:newTBase(name)
- local think=1
- c.Type='tstepThread'
- 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=os.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=os.clock()
- self:Resume()
- return self
- end
- function c:Resume()
- self.rest=false
- return self
- end
- function c:Pause()
- self.rest=true
- return self
- 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=os.clock()
- self:Resume()
- return self
- end
- c.updaterate=0--multi.Priority_Low -- skips
- c.restRate=0
- multi:newThread(name,function(ref)
- while true do
- if c.rest then
- thread.sleep(c.restRate) -- rest a bit more when a thread is paused
- else
- if os.clock()-c.timer>=c.set then
- c:Reset()
- if c.pos==c.start then
- for fe=1,#c.funcS do
- c.funcS[fe](c)
- end
- end
- for i=1,#c.func do
- c.func[i](c,c.pos)
- end
- c.pos=c.pos+c.count
- if c.pos-c.count==c.endAt then
- c:Pause()
- for fe=1,#c.funcE do
- c.funcE[fe](c)
- end
- c.pos=c.start
- end
- end
- thread.skip(c.updaterate) -- lets rest a bit
- end
- end
- end)
- self:create(c)
- return c
-end
-function multi:newThreadedTLoop(name,func,n)
- local c=self:newTBase(name)
- c.Type='tloopThread'
- c.restN=n or 1
- if func then
- c.func={func}
- end
- function c:Resume()
- self.rest=false
- return self
- end
- function c:Pause()
- self.rest=true
- return self
- end
- function c:OnLoop(func)
- table.insert(self.func,func)
- return self
- end
- c.rest=false
- c.updaterate=0
- c.restRate=.75
- multi:newThread(name,function(ref)
- while true do
- if c.rest then
- thread.sleep(c.restRate) -- rest a bit more when a thread is paused
- else
- for i=1,#c.func do
- c.func[i](c)
- end
- thread.sleep(c.restN) -- lets rest a bit
- end
- end
- end)
- self:create(c)
- return c
-end
-function multi:newThreadedStep(name,start,reset,count,skip)
- local c=self:newTBase(name)
- local think=1
- c.Type='stepThread'
- 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:Resume()
- self.rest=false
- return self
- end
- function c:Pause()
- self.rest=true
- return self
- 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.rest=true
- 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
- c.updaterate=0
- c.restRate=.1
- multi:newThread(name,function(ref)
- while true do
- if c.rest then
- ref:sleep(c.restRate) -- rest a bit more when a thread is paused
- else
- if c~=nil then
- if c.spos==0 then
- if c.pos==c.start then
- for fe=1,#c.funcS do
- c.funcS[fe](c)
- end
- end
- for i=1,#c.func do
- c.func[i](c,c.pos)
- end
- c.pos=c.pos+c.count
- if c.pos-c.count==c.endAt then
- c:Pause()
- for fe=1,#c.funcE do
- c.funcE[fe](c)
- end
- c.pos=c.start
- end
- end
- end
- c.spos=c.spos+1
- if c.spos>=c.skip then
- c.spos=0
- end
- ref:sleep(c.updaterate) -- lets rest a bit
- end
- end
- end)
- self:create(c)
- return c
-end
-function multi:newThreadedProcess(name)
- local c = {}
- setmetatable(c, multi)
- function c:newBase(ins)
- local ct = {}
- setmetatable(ct, self.Parent)
- ct.Active=true
- ct.func={}
- ct.ender={}
- ct.Id=0
- ct.Act=function() end
- ct.Parent=self
- ct.held=false
- ct.ref=self.ref
- table.insert(self.Mainloop,ct)
- return ct
- end
- c.Parent=self
- c.Active=true
- c.func={}
- c.Id=0
- c.Type='process'
- c.Mainloop={}
- c.Garbage={}
- c.Children={}
- c.Active=true
- c.Id=-1
- c.Rest=0
- c.updaterate=.01
- c.restRate=.1
- c.Jobs={}
- c.queue={}
- c.jobUS=2
- c.rest=false
- function c:getController()
- return nil
- end
- function c:Start()
- self.rest=false
- return self
- end
- function c:Resume()
- self.rest=false
- return self
- end
- function c:Pause()
- self.rest=true
- return self
- end
- function c:Remove()
- self.ref:kill()
- return self
- end
- function c:kill()
- err=coroutine.yield({"_kill_"})
- if err then
- error("Failed to kill a thread! Exiting...")
- end
- return self
- end
- function c:sleep(n)
- if type(n)=="function" then
- ret=coroutine.yield({"_hold_",n})
- elseif type(n)=="number" then
- n = tonumber(n) or 0
- ret=coroutine.yield({"_sleep_",n})
- else
- error("Invalid Type for sleep!")
- end
- return self
- end
- c.hold=c.sleep
- multi:newThread(name,function(ref)
- while true do
- if c.rest then
- ref:Sleep(c.restRate) -- rest a bit more when a thread is paused
- else
- c:uManager()
- ref:sleep(c.updaterate) -- lets rest a bit
- end
- end
- end)
- return c
-end
-function multi:newThreadedLoop(name,func)
- local c=self:newTBase(name)
- c.Type='loopThread'
- c.Start=os.clock()
- if func then
- c.func={func}
- end
- function c:Resume()
- self.rest=false
- return self
- end
- function c:Pause()
- self.rest=true
- return self
- end
- function c:OnLoop(func)
- table.insert(self.func,func)
- return self
- end
- c.rest=false
- c.updaterate=0
- c.restRate=.75
- multi:newThread(name,function(ref)
- while true do
- if c.rest then
- thread.sleep(c.restRate) -- rest a bit more when a thread is paused
- else
- for i=1,#c.func do
- c.func[i](os.clock()-self.Start,c)
- end
- thread.sleep(c.updaterate) -- lets rest a bit
- end
- end
- end)
- self:create(c)
- return c
-end
-function multi:newThreadedEvent(name,task)
- local c=self:newTBase(name)
- c.Type='eventThread'
- c.Task=task or function() end
- function c:OnEvent(func)
- table.insert(self.func,func)
- return self
- end
- function c:Resume()
- self.rest=false
- return self
- end
- function c:Pause()
- self.rest=true
- return self
- end
- c.rest=false
- c.updaterate=0
- c.restRate=1
- multi:newThread(name,function(ref)
- while true do
- if c.rest then
- ref:sleep(c.restRate) -- rest a bit more when a thread is paused
- else
- if c.Task(self) then
- for _E=1,#c.func do
- c.func[_E](c)
- end
- c:Pause()
- end
- ref:sleep(c.updaterate) -- lets rest a bit
- end
- end
- end)
- self:create(c)
- return c
-end
-- State Saving Stuff
function multi:IngoreObject()
self.Ingore=true
return self
end
-multi.scheduler:IngoreObject()
function multi:ToString()
if self.Ingore then return end
local t=self.Type
local data;
- print(t)
+ multi.print(t)
if t:sub(-6)=="Thread" then
data={
Type=t,
@@ -2349,8 +2287,6 @@ function multi:ToString()
important=self.important,
Active=self.Active,
ender=self.ender,
- -- IDK if these need to be present...
- -- Id=self.Id,
held=self.held,
}
else
@@ -2361,8 +2297,6 @@ function multi:ToString()
funcTMR=self.funcTMR,
important=self.important,
ender=self.ender,
- -- IDK if these need to be present...
- -- Id=self.Id,
held=self.held,
}
end
@@ -2418,7 +2352,7 @@ function multi:ToString()
set=self.set,
})
elseif t=="watcher" then
- print("Currently cannot sterilize a watcher object!")
+ multi.print("Currently cannot sterilize a watcher object!")
-- needs testing
-- table.merge(data,{
-- ns=self.ns,
@@ -2475,7 +2409,7 @@ function multi:newFromString(str)
end
return self
elseif t=="process" then
- local temp=multi:newProcess()
+ local temp=multi:newProcessor()
local objs=handle:getBlock("n",4)
for i=1,objs do
temp:newFromString(handle:getBlock("s",(handle:getBlock("n",4))))
@@ -2513,34 +2447,6 @@ function multi:newFromString(str)
local item=self:newLoop()
table.merge(item,data)
return item
- elseif t=="eventThread" then -- GOOD
- local item=self:newThreadedEvent(data.name)
- table.merge(item,data)
- return item
- elseif t=="loopThread" then -- GOOD
- local item=self:newThreadedLoop(data.name)
- table.merge(item,data)
- return item
- elseif t=="stepThread" then -- GOOD
- local item=self:newThreadedStep(data.name)
- table.merge(item,data)
- return item
- elseif t=="tloopThread" then -- GOOD
- local item=self:newThreadedTLoop(data.name,nil,data.restN)
- table.merge(item,data)
- return item
- elseif t=="tstepThread" then -- GOOD
- local item=self:newThreadedTStep(data.name)
- table.merge(item,data)
- return item
- elseif t=="updaterThread" then -- GOOD
- local item=self:newThreadedUpdater(data.name)
- table.merge(item,data)
- return item
- elseif t=="alarmThread" then -- GOOD
- local item=self:newThreadedAlarm(data.name)
- table.merge(item,data)
- return item
end
end
function multi:Important(varname)
@@ -2567,19 +2473,4 @@ end
function multi:setDefualtStateFlag(opt)
--
end
-multi.dStepA = 0
-multi.dStepB = 0
-multi.dSwap = 0
-multi.deltaTarget = .05
-multi.load_updater = multi:newUpdater(2)
-multi.load_updater:Pause()
-multi.load_updater:OnUpdate(function(self)
- if self.Parent.dSwap == 0 then
- self.Parent.dStepA = os.clock()
- self.Parent.dSwap = 1
- else
- self.Parent.dSwap = 0
- self.Parent.dStepB = os.clock()
- end
-end)
return multi
diff --git a/multi/integration/lanesManager.lua b/multi/integration/lanesManager.lua
index ea201f4..2172f7c 100644
--- a/multi/integration/lanesManager.lua
+++ b/multi/integration/lanesManager.lua
@@ -1,7 +1,7 @@
--[[
MIT License
-Copyright (c) 2018 Ryan Ward
+Copyright (c) 2019 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
@@ -32,6 +32,8 @@ end
-- Step 1 get lanes
lanes=require("lanes").configure()
local multi = require("multi") -- get it all and have it on all lanes
+multi.SystemThreads = {}
+local thread = thread
multi.isMainThread=true
function multi:canSystemThread()
return true
@@ -39,10 +41,10 @@ end
function multi:getPlatform()
return "lanes"
end
--- Step 2 set up the linda objects
+-- Step 2 set up the Linda objects
local __GlobalLinda = lanes.linda() -- handles global stuff
local __SleepingLinda = lanes.linda() -- handles sleeping stuff
--- For convience a GLOBAL table will be constructed to handle requests
+-- For convenience a GLOBAL table will be constructed to handle requests
local GLOBAL={}
setmetatable(GLOBAL,{
__index=function(t,k)
@@ -52,7 +54,7 @@ setmetatable(GLOBAL,{
__GlobalLinda:set(k,v)
end,
})
--- Step 3 rewrite the thread methods to use lindas
+-- Step 3 rewrite the thread methods to use Lindas
local THREAD={}
function THREAD.set(name,val)
__GlobalLinda:set(name,val)
@@ -82,6 +84,9 @@ end
function THREAD.getCores()
return THREAD.__CORES
end
+function THREAD.getThreads()
+ return GLOBAL.__THREADS__
+end
if os.getOS()=="windows" then
THREAD.__CORES=tonumber(os.getenv("NUMBER_OF_PROCESSORS"))
else
@@ -93,6 +98,10 @@ end
function THREAD.getName()
return THREAD_NAME
end
+function THREAD.getID()
+ return THREAD_ID
+end
+_G.THREAD_ID = 0
--[[ Step 4 We need to get sleeping working to handle timing... We want idle wait, not busy wait
Idle wait keeps the CPU running better where busy wait wastes CPU cycles... Lanes does not have a sleep method
however, a linda recieve will in fact be a idle wait! So we use that and wrap it in a nice package]]
@@ -109,36 +118,74 @@ function THREAD.hold(n)
end
local rand = math.random(1,10000000)
-- Step 5 Basic Threads!
+local threads = {}
+local count = 1
+local started = false
+local livingThreads = {}
function multi:newSystemThread(name,func,...)
+ multi.InitSystemThreadErrorHandler()
rand = math.random(1,10000000)
local c={}
local __self=c
c.name=name
+ c.Name = name
+ c.Id = count
+ livingThreads[count] = {true,name}
+ local THREAD_ID = count
+ count = count + 1
c.Type="sthread"
+ c.creationTime = os.clock()
local THREAD_NAME=name
local function func2(...)
+ local multi = require("multi")
_G["THREAD_NAME"]=THREAD_NAME
+ _G["THREAD_ID"]=THREAD_ID
math.randomseed(rand)
func(...)
+ if _G.__Needs_Multi then
+ multi:mainloop()
+ end
+ THREAD.kill()
end
c.thread=lanes.gen("*", func2)(...)
function c:kill()
- --self.status:Destroy()
self.thread:cancel()
- print("Thread: '"..self.name.."' has been stopped!")
+ multi.print("Thread: '"..self.name.."' has been stopped!")
end
- c.status=multi:newUpdater(multi.Priority_IDLE)
- c.status.link=c
- c.status:OnUpdate(function(self)
- local v,err,t=self.link.thread:join(.001)
- if err then
- multi.OnError:Fire(self.link,err,"Error in systemThread: '"..self.link.name.."' <"..err..">")
- self:Destroy()
- end
- end)
+ table.insert(multi.SystemThreads,c)
+ c.OnError = multi:newConnection()
+ GLOBAL["__THREADS__"]=livingThreads
return c
end
-print("Integrated Lanes!")
+multi.OnSystemThreadDied = multi:newConnection()
+function multi.InitSystemThreadErrorHandler()
+ if started==true then return end
+ started = true
+ multi:newThread("ThreadErrorHandler",function()
+ local threads = multi.SystemThreads
+ while true do
+ thread.sleep(.5) -- 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 v,err,t=threads[i].thread:join(.001)
+ if err then
+ if err:find("Thread was killed!") then
+ livingThreads[threads[i].Id] = {false,threads[i].Name}
+ multi.OnSystemThreadDied:Fire(threads[i].Id)
+ GLOBAL["__THREADS__"]=livingThreads
+ table.remove(threads,i)
+ else
+ threads[i].OnError:Fire(threads[i],err,"Error in systemThread: '"..threads[i].name.."' <"..err..">")
+ livingThreads[threads[i].Id] = {false,threads[i].Name}
+ multi.OnSystemThreadDied:Fire(threads[i].Id)
+ GLOBAL["__THREADS__"]=livingThreads
+ table.remove(threads,i)
+ end
+ end
+ end
+ end
+ end)
+end
+multi.print("Integrated Lanes!")
multi.integration={} -- for module creators
multi.integration.GLOBAL=GLOBAL
multi.integration.THREAD=THREAD
diff --git a/multi/integration/loveManager.lua b/multi/integration/loveManager.lua
index bc886eb..02f2f9e 100644
--- a/multi/integration/loveManager.lua
+++ b/multi/integration/loveManager.lua
@@ -1,7 +1,7 @@
--[[
MIT License
-Copyright (c) 2018 Ryan Ward
+Copyright (c) 2019 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
@@ -34,6 +34,7 @@ multi.integration.love2d.ThreadBase=[[
tab={...}
__THREADID__=table.remove(tab,1)
__THREADNAME__=table.remove(tab,1)
+THREAD_ID=table.remove(tab,1)
require("love.filesystem")
require("love.system")
require("love.timer")
@@ -167,6 +168,9 @@ end
function sThread.getName()
return __THREADNAME__
end
+function sThread.getID()
+ return THREAD_ID
+end
function sThread.kill()
error("Thread was killed!")
end
@@ -195,6 +199,7 @@ func=loadDump([=[INSERT_USER_CODE]=])(unpack(tab))
multi:mainloop()
]]
GLOBAL={} -- Allow main thread to interact with these objects as well
+_G.THREAD_ID = 0
__proxy__={}
setmetatable(GLOBAL,{
__index=function(t,k)
@@ -214,9 +219,13 @@ setmetatable(GLOBAL,{
THREAD={} -- Allow main thread to interact with these objects as well
multi.integration.love2d.mainChannel=love.thread.getChannel("__MainChan__")
isMainThread=true
+multi.SystemThreads = {}
function THREAD.getName()
return __THREADNAME__
end
+function THREAD.getID()
+ return THREAD_ID
+end
function ToStr(val, name, skipnewlines, depth)
skipnewlines = skipnewlines or false
depth = depth or 0
@@ -295,12 +304,19 @@ local function randomString(n)
end
return str
end
+local count = 1
+local livingThreads = {}
function multi:newSystemThread(name,func,...) -- the main method
+ multi.InitSystemThreadErrorHandler()
local c={}
c.name=name
+ c.Name = name
c.ID=c.name..""
+ c.Id=count
+ count = count + 1
+ livingThreads[count] = {true,name}
c.thread=love.thread.newThread(multi.integration.love2d.ThreadBase:gsub("INSERT_USER_CODE",dump(func)))
- c.thread:start(c.ID,c.name,...)
+ c.thread:start(c.ID,c.name,THREAD_ID,...)
function c:kill()
multi.integration.GLOBAL["__DIEPLZ"..self.ID.."__"]="__DIEPLZ"..self.ID.."__"
end
@@ -308,7 +324,7 @@ function multi:newSystemThread(name,func,...) -- the main method
end
function love.threaderror( thread, errorstr )
multi.OnError:Fire(thread,errorstr)
- print("Error in systemThread: "..tostring(thread)..": "..errorstr)
+ multi.print("Error in systemThread: "..tostring(thread)..": "..errorstr)
end
local THREAD={}
function THREAD.set(name,val)
@@ -333,8 +349,7 @@ end
__channels__={}
multi.integration.GLOBAL=GLOBAL
multi.integration.THREAD=THREAD
-updater=multi:newUpdater()
-updater:OnUpdate(function(self)
+updater=multi:newLoop(function(self)
local data=multi.integration.love2d.mainChannel:pop()
while data do
if type(data)=="string" then
@@ -365,8 +380,37 @@ updater:OnUpdate(function(self)
data=multi.integration.love2d.mainChannel:pop()
end
end)
+multi.OnSystemThreadDied = multi:newConnection()
+local started = false
+function multi.InitSystemThreadErrorHandler()
+ if started==true then return end
+ started = true
+ multi:newThread("ThreadErrorHandler",function()
+ local threads = multi.SystemThreads
+ while true do
+ thread.sleep(.5) -- 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 v,err,t=threads[i].thread:join(.001)
+ if err then
+ if err:find("Thread was killed!") then
+ livingThreads[threads[i].Id] = {false,threads[i].Name}
+ multi.OnSystemThreadDied:Fire(threads[i].Id)
+ GLOBAL["__THREADS__"]=livingThreads
+ table.remove(threads,i)
+ else
+ threads[i].OnError:Fire(threads[i],err,"Error in systemThread: '"..threads[i].name.."' <"..err..">")
+ livingThreads[threads[i].Id] = {false,threads[i].Name}
+ multi.OnSystemThreadDied:Fire(threads[i].Id)
+ GLOBAL["__THREADS__"]=livingThreads
+ table.remove(threads,i)
+ end
+ end
+ end
+ end
+ end)
+end
require("multi.integration.shared")
-print("Integrated Love2d!")
+multi.print("Integrated Love2d!")
return {
init=function(t)
if t then
diff --git a/multi/integration/luvitManager.lua b/multi/integration/luvitManager.lua
index 401501c..862783d 100644
--- a/multi/integration/luvitManager.lua
+++ b/multi/integration/luvitManager.lua
@@ -1,7 +1,7 @@
--[[
MIT License
-Copyright (c) 2018 Ryan Ward
+Copyright (c) 2019 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
@@ -114,7 +114,7 @@ local function _INIT(luvitThread,timer)
luvitThread.start(entry,package.path,name,c.func,...)
return c
end
- print("Integrated Luvit!")
+ multi.print("Integrated Luvit!")
multi.integration={} -- for module creators
multi.integration.GLOBAL=GLOBAL
multi.integration.THREAD=THREAD
diff --git a/multi/integration/networkManager.lua b/multi/integration/networkManager.lua
index ca620de..7102b7d 100644
--- a/multi/integration/networkManager.lua
+++ b/multi/integration/networkManager.lua
@@ -1,7 +1,7 @@
--[[
MIT License
-Copyright (c) 2018 Ryan Ward
+Copyright (c) 2019 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
@@ -23,7 +23,7 @@ SOFTWARE.
]]
local multi = require("multi")
local net = require("net")
-require("bin")
+local bin = require("bin")
bin.setBitsInterface(infinabits) -- the bits interface does not work so well, another bug to fix
-- Commands that the master and node will respect, max of 256 commands
@@ -42,6 +42,7 @@ local CMD_CONSOLE = 0x0B
local char = string.char
local byte = string.byte
+-- Process to hold all of the networkManager's muilt objects
-- Helper for piecing commands
local function pieceCommand(cmd,...)
@@ -142,17 +143,20 @@ function multi:nodeManager(port)
server.OnDataRecieved(function(server,data,cid,ip,port)
local cmd = data:sub(1,1)
if cmd == "R" then
- multi:newTLoop(function(loop)
- if server.timeouts[cid]==true then
- server.OnNodeRemoved:Fire(server.nodes[cid])
- server.nodes[cid] = nil
- server.timeouts[cid] = nil
- loop:Destroy()
- return
+ multi:newThread("Node Client Manager",function(loop)
+ while true do
+ if server.timeouts[cid]==true then
+ server.OnNodeRemoved:Fire(server.nodes[cid])
+ server.nodes[cid] = nil
+ server.timeouts[cid] = nil
+ thread.kill()
+ else
+ server.timeouts[cid] = true
+ server:send(cid,"ping")
+ end
+ thread.sleep(1)
end
- server.timeouts[cid] = true
- server:send(cid,"ping")
- end,1)
+ end)
server.nodes[cid]=data:sub(2,-1)
server.OnNodeAdded:Fire(server.nodes[cid])
elseif cmd == "G" then
@@ -172,6 +176,7 @@ function multi:nodeManager(port)
end
-- The main driving force of the network manager: Nodes
function multi:newNode(settings)
+ multi:enableLoadDetection()
settings = settings or {}
-- Here we have to use the net library to broadcast our node across the network
math.randomseed(os.time())
@@ -189,21 +194,21 @@ function multi:newNode(settings)
node.hasFuncs = {}
node.OnError = multi:newConnection()
node.OnError(function(node,err,master)
- print("ERROR",err,node.name)
+ multi.print("ERROR",err,node.name)
local temp = bin.new()
temp:addBlock(#node.name,2)
temp:addBlock(node.name)
temp:addBlock(#err,2)
temp:addBlock(err)
for i,v in pairs(node.connections) do
- print(i)
+ multi.print(i)
v[1]:send(v[2],char(CMD_ERROR)..temp.data,v[3])
end
end)
if settings.managerDetails then
local c = net:newTCPClient(settings.managerDetails[1],settings.managerDetails[2])
if not c then
- print("Cannot connect to the node manager! Ensuring broadcast is enabled!") settings.noBroadCast = false
+ multi.print("Cannot connect to the node manager! Ensuring broadcast is enabled!") settings.noBroadCast = false
else
c.OnDataRecieved(function(self,data)
if data == "ping" then
@@ -215,7 +220,7 @@ function multi:newNode(settings)
end
if not settings.preload then
if node.functions:getSize()~=0 then
- print("We have function(s) to preload!")
+ multi.print("We have function(s) to preload!")
local len = node.functions:getBlock("n",1)
local name,func
while len do
@@ -265,14 +270,14 @@ function multi:newNode(settings)
node.queue:push(resolveData(dat))
elseif cmd == CMD_REG then
if not settings.allowRemoteRegistering then
- print(ip..": has attempted to register a function when it is currently not allowed!")
+ multi.print(ip..": has attempted to register a function when it is currently not allowed!")
return
end
local temp = bin.new(dat)
local len = temp:getBlock("n",1)
local name = temp:getBlock("s",len)
if node.hasFuncs[name] then
- print("Function already preloaded onto the node!")
+ multi.print("Function already preloaded onto the node!")
return
end
len = temp:getBlock("n",2)
@@ -283,7 +288,7 @@ function multi:newNode(settings)
local temp = bin.new(dat)
local len = temp:getBlock("n",1)
local name = temp:getBlock("s",len)
- len = temp:getBlock("n",2)
+ len = temp:getBlock("n",4)
local args = temp:getBlock("s",len)
_G[name](unpack(resolveData(args)))
elseif cmd == CMD_TASK then
@@ -299,13 +304,13 @@ function multi:newNode(settings)
node.OnError:Fire(node,err,server)
end
elseif cmd == CMD_INITNODE then
- print("Connected with another node!")
+ multi.print("Connected with another node!")
node.connections[dat]={server,ip,port}
multi.OnGUpdate(function(k,v)
server:send(ip,table.concat{char(CMD_GLOBAL),k,"|",v},port)
end)-- set this up
elseif cmd == CMD_INITMASTER then
- print("Connected to the master!",dat)
+ multi.print("Connected to the master!",dat)
node.connections[dat]={server,ip,port}
multi.OnGUpdate(function(k,v)
server:send(ip,table.concat{char(CMD_GLOBAL),k,"|",v},port)
@@ -352,7 +357,7 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
if settings.managerDetails then
local client = net:newTCPClient(settings.managerDetails[1],settings.managerDetails[2])
if not client then
- print("Cannot connect to the node manager! Ensuring broadcast listening is enabled!") settings.noBroadCast = false
+ multi.print("Warning: Cannot connect to the node manager! Ensuring broadcast listening is enabled!") settings.noBroadCast = false
else
client.OnDataRecieved(function(client,data)
local cmd = data:sub(1,1)
@@ -402,7 +407,7 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
temp:addBlock(CMD_CALL,1)
temp:addBlock(#name,1)
temp:addBlock(name,#name)
- temp:addBlock(#args,2)
+ temp:addBlock(#args,4)
temp:addBlock(args,#args)
master:sendTo(node,temp.data)
end
@@ -436,12 +441,12 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
name = self:getRandomNode()
end
if name==nil then
- multi:newTLoop(function(loop)
- if name~=nil then
- self:sendTo(name,char(CMD_TASK)..len..aData..len2..fData)
- loop:Desrtoy()
- end
- end,.1)
+ multi:newEvent(function() return name~=nil end):OnEvent(function(evnt)
+ self:sendTo(name,char(CMD_TASK)..len..aData..len2..fData)
+ evnt:Destroy()
+ end):SetName("DelayedSendTask"):SetName("DelayedSendTask"):SetTime(8):OnTimedOut(function(self)
+ self:Destroy()
+ end)
else
self:sendTo(name,char(CMD_TASK)..len..aData..len2..fData)
end
@@ -455,12 +460,12 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
name = "NODE_"..name
end
if self.connections[name]==nil then
- multi:newTLoop(function(loop)
- if self.connections[name]~=nil then
- self.connections[name]:send(data)
- loop:Destroy()
- end
- end,.1)
+ multi:newEvent(function() return self.connections[name]~=nil end):OnEvent(function(evnt)
+ self.connections[name]:send(data)
+ evnt:Destroy()
+ end):SetName("DelayedSendTask"):SetTime(8):OnTimedOut(function(self)
+ self:Destroy()
+ end)
else
self.connections[name]:send(data)
end
@@ -495,16 +500,19 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
client.OnClientReady(function()
client:send(char(CMD_INITMASTER)..master.name) -- Tell the node that you are a master trying to connect
if not settings.managerDetails then
- multi:newTLoop(function(loop)
- if master.timeouts[name]==true then
- master.timeouts[name] = nil
- master.connections[name] = nil
- loop:Destroy()
- return
+ multi:newThread("Node Data Link Controller",function(loop)
+ while true do
+ if master.timeouts[name]==true then
+ master.timeouts[name] = nil
+ master.connections[name] = nil
+ thread.kill()
+ else
+ master.timeouts[name] = true
+ master:sendTo(name,char(CMD_PING))
+ end
+ thread.sleep(1)
end
- master.timeouts[name] = true
- master:sendTo(name,char(CMD_PING))
- end,1)
+ end)
end
client.name = name
client.OnDataRecieved(function(client,data)
@@ -542,7 +550,7 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
return master
end
-- The init function that gets returned
-print("Integrated Network Parallelism")
+multi.print("Integrated Network Parallelism")
return {init = function()
return GLOBAL
end}
diff --git a/multi/integration/shared.lua b/multi/integration/shared.lua
index 1d16d2a..6d420ba 100644
--- a/multi/integration/shared.lua
+++ b/multi/integration/shared.lua
@@ -1,7 +1,7 @@
--[[
MIT License
-Copyright (c) 2018 Ryan Ward
+Copyright (c) 2019 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
@@ -112,30 +112,37 @@ function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a chann
end
return c
end
+
function multi:newSystemThreadedConnection(name,protect)
local c={}
+ c.name = name or error("You must provide a name for the connection object!")
+ c.protect = protect or false
+ c.idle = nil
local sThread=multi.integration.THREAD
local GLOBAL=multi.integration.GLOBAL
- c.name = name or error("You must supply a name for this object!")
- c.protect = protect or false
- c.count = 0
- multi:newSystemThreadedQueue(name.."THREADED_CALLFIRE"):init()
- local qsm = multi:newSystemThreadedQueue(name.."THREADED_CALLSYNCM"):init()
- local qs = multi:newSystemThreadedQueue(name.."THREADED_CALLSYNC"):init()
+ local connSync = multi:newSystemThreadedQueue(c.name.."_CONN_SYNC")
+ local connFire = multi:newSystemThreadedQueue(c.name.."_CONN_FIRE")
function c:init()
local multi = require("multi")
- if multi:getPlatform()=="love2d" then
+ if love then -- lets make sure we don't reference up-values if using love2d
GLOBAL=_G.GLOBAL
sThread=_G.sThread
end
- local conns = 0
- local qF = sThread.waitFor(self.name.."THREADED_CALLFIRE"):init()
- local qSM = sThread.waitFor(self.name.."THREADED_CALLSYNCM"):init()
- local qS = sThread.waitFor(self.name.."THREADED_CALLSYNC"):init()
- qSM:push("OK")
local conn = {}
- conn.obj = multi:newConnection(self.protect)
- setmetatable(conn,{__call=function(self,...) return self:connect(...) end})
+ conn.obj = multi:newConnection()
+ setmetatable(conn,{
+ __call=function(self,...)
+ return self:connect(...)
+ end
+ })
+ local ID = sThread.getID()
+ local sync = sThread.waitFor(self.name.."_CONN_SYNC"):init()
+ local fire = sThread.waitFor(self.name.."_CONN_FIRE"):init()
+ local connections = {}
+ if not multi.isMainThread then
+ connections = {0}
+ end
+ sync:push{"INIT",ID} -- Register this as an active connection!
function conn:connect(func)
return self.obj(func)
end
@@ -146,54 +153,98 @@ function multi:newSystemThreadedConnection(name,protect)
self.obj:Remove()
end
function conn:Fire(...)
- local args = {multi.randomString(8),...}
- for i = 1, conns do
- qF:push(args)
+ for i = 1,#connections do
+ fire:push{connections[i],ID,{...}}
end
end
- local lastID = ""
- local lastCount = 0
- multi:newThread("syncer",function()
- while true do
- thread.skip(1)
- local fire = qF:peek()
- local count = qS:peek()
- if fire and fire[1]~=lastID then
- lastID = fire[1]
- qF:pop()
- table.remove(fire,1)
- conn.obj:Fire(unpack(fire))
- end
- if count and count[1]~=lastCount then
- conns = count[2]
- lastCount = count[1]
- qs:pop()
+ function conn:FireTo(to,...)
+ local good = false
+ for i = 1,#connections do
+ if connections[i]==to then
+ good = true
+ break
end
end
- end)
+ if not good then return multi.print("NonExisting Connection!") end
+ fire:push{to,ID,{...}}
+ end
+ -- FIRE {TO,FROM,{ARGS}}
+ local data
+ local clock = os.clock
+ conn.OnConnectionAdded = multi:newConnection()
+ multi:newLoop(function()
+ data = fire:peek()
+ if type(data)=="table" and data[1]==ID then
+ if data[2]==ID and conn.IgnoreSelf then
+ fire:pop()
+ return
+ end
+ fire:pop()
+ conn.obj:Fire(unpack(data[3]))
+ end
+ data = sync:peek()
+ if data~=nil and data[1]=="SYNCA" and data[2]==ID then
+ sync:pop()
+ multi.nextStep(function()
+ conn.OnConnectionAdded:Fire(data[3])
+ end)
+ table.insert(connections,data[3])
+ end
+ if type(data)=="table" and data[1]=="SYNCR" and data[2]==ID then
+ sync:pop()
+ for i=1,#connections do
+ if connections[i] == data[3] then
+ table.remove(connections,i)
+ end
+ end
+ end
+ end):setName("STConn.syncer")
return conn
end
- multi:newThread("connSync",function()
+ local cleanUp = {}
+ multi.OnSystemThreadDied(function(ThreadID)
+ for i=1,#syncs do
+ connSync:push{"SYNCR",syncs[i],ThreadID}
+ end
+ cleanUp[ThreadID] = true
+ end)
+ multi:newThread(c.name.." Connection-Handler",function()
+ local data
+ local clock = os.clock
+ local syncs = {}
while true do
- thread.skip(1)
- local syncIN = qsm:pop()
- if syncIN then
- if syncIN=="OK" then
- c.count = c.count + 1
- else
- c.count = c.count - 1
+ if not c.idle then
+ thread.sleep(.5)
+ else
+ if clock() - c.idle >= 15 then
+ c.idle = nil
end
- local rand = math.random(1,1000000)
- for i = 1, c.count do
- qs:push({rand,c.count})
+ thread.skip()
+ end
+ data = connSync:peek()
+ if data~= nil and data[1]=="INIT" then
+ connSync:pop()
+ c.idle = clock()
+ table.insert(syncs,data[2])
+ for i=1,#syncs do
+ connSync:push{"SYNCA",syncs[i],data[2]}
end
end
+ data = connFire:peek()
+ if data~=nil and cleanUp[data[1]] then
+ local meh = data[1]
+ connFire:pop() -- lets remove dead thread stuff
+ multi:newAlarm(15):OnRing(function(a)
+ cleanUp[meh] = nil
+ end)
+ end
end
end)
- GLOBAL[name]=c
+ GLOBAL[c.name]=c
return c
end
-function multi:systemThreadedBenchmark(n)
+
+function multi:SystemThreadedBenchmark(n)
n=n or 1
local cores=multi.integration.THREAD.getCores()
local queue=multi:newSystemThreadedQueue("THREAD_BENCH_QUEUE"):init()
@@ -211,6 +262,7 @@ function multi:systemThreadedBenchmark(n)
multi:benchMark(n):OnBench(function(self,count)
queue:push(count)
sThread.kill()
+ error("Thread was killed!")
end)
multi:mainloop()
end,n)
@@ -240,6 +292,7 @@ function multi:newSystemThreadedConsole(name)
local sThread=multi.integration.THREAD
local GLOBAL=multi.integration.GLOBAL
function c:init()
+ _G.__Needs_Multi = true
local multi = require("multi")
if multi:getPlatform()=="love2d" then
GLOBAL=_G.GLOBAL
@@ -247,10 +300,10 @@ function multi:newSystemThreadedConsole(name)
end
local cc={}
if multi.isMainThread then
- if GLOBAL["__SYSTEM_CONSLOE__"] then
- cc.stream = sThread.waitFor("__SYSTEM_CONSLOE__"):init()
+ if GLOBAL["__SYSTEM_CONSOLE__"] then
+ cc.stream = sThread.waitFor("__SYSTEM_CONSOLE__"):init()
else
- cc.stream = multi:newSystemThreadedQueue("__SYSTEM_CONSLOE__"):init()
+ cc.stream = multi:newSystemThreadedQueue("__SYSTEM_CONSOLE__"):init()
multi:newLoop(function()
local data = cc.stream:pop()
if data then
@@ -261,10 +314,10 @@ function multi:newSystemThreadedConsole(name)
print(unpack(data))
end
end
- end)
+ end):setName("ST.consoleSyncer")
end
else
- cc.stream = sThread.waitFor("__SYSTEM_CONSLOE__"):init()
+ cc.stream = sThread.waitFor("__SYSTEM_CONSOLE__"):init()
end
function cc:write(msg)
self.stream:push({"w",tostring(msg)})
@@ -281,12 +334,14 @@ function multi:newSystemThreadedConsole(name)
GLOBAL[c.name]=c
return c
end
+-- NEEDS WORK
function multi:newSystemThreadedTable(name)
local c={}
c.name=name -- set the name this is important for identifying what is what
local sThread=multi.integration.THREAD
local GLOBAL=multi.integration.GLOBAL
function c:init() -- create an init function so we can mimic on both love2d and lanes
+ _G.__Needs_Multi = true
local multi = require("multi")
if multi:getPlatform()=="love2d" then
GLOBAL=_G.GLOBAL
@@ -324,14 +379,16 @@ function multi:newSystemThreadedTable(name)
return c
end
local jobqueuecount = 0
+local jqueues = {}
function multi:newSystemThreadedJobQueue(a,b)
jobqueuecount=jobqueuecount+1
local GLOBAL=multi.integration.GLOBAL
local sThread=multi.integration.THREAD
local c = {}
c.numberofcores = 4
+ c.idle = nil
c.name = "SYSTEM_THREADED_JOBQUEUE_"..jobqueuecount
- -- This is done to keep backwards compatability for older code
+ -- This is done to keep backwards compatibility for older code
if type(a)=="string" and not(b) then
c.name = a
elseif type(a)=="number" and not (b) then
@@ -343,6 +400,10 @@ function multi:newSystemThreadedJobQueue(a,b)
c.name = b
c.numberofcores = a
end
+ if jqueues[c.name] then
+ error("A job queue by the name: "..c.name.." already exists!")
+ end
+ jqueues[c.name] = true
c.isReady = false
c.jobnum=1
c.OnJobCompleted = multi:newConnection()
@@ -359,6 +420,7 @@ function multi:newSystemThreadedJobQueue(a,b)
end
c.tempQueue = {}
function c:pushJob(name,...)
+ c.idle = os.clock()
if not self.isReady then
table.insert(c.tempQueue,{self.jobnum,name,...})
self.jobnum=self.jobnum+1
@@ -370,8 +432,9 @@ function multi:newSystemThreadedJobQueue(a,b)
end
end
function c:doToAll(func)
+ local r = multi.randomString(12)
for i = 1, self.numberofcores do
- queueDA:push{multi.randomString(12),func}
+ queueDA:push{r,func}
end
end
for i=1,c.numberofcores do
@@ -425,12 +488,9 @@ function multi:newSystemThreadedJobQueue(a,b)
end
end
end)
- multi:newThread("Idler",function()
- while true do
- if os.clock()-lastjob>1 then
- sThread.sleep(.1)
- end
- thread.sleep(.001)
+ multi:newLoop(function()
+ if os.clock()-lastjob>1 then
+ sThread.sleep(.1)
end
end)
setmetatable(_G,{
@@ -443,11 +503,11 @@ function multi:newSystemThreadedJobQueue(a,b)
end
end,c.name)
end
- multi:newThread("counter",function()
- print("thread started")
+ local clock = os.clock
+ multi:newThread("JQ-"..c.name.." Manager",function()
local _count = 0
while _count= 15 then
+ c.idle = nil
+ end
+ thread.skip()
+ end
dat = queueJD:pop()
if dat then
+ c.idle = clock()
c.OnJobCompleted:Fire(unpack(dat))
end
end
diff --git a/rockspecs/multi-12.2-1.rockspec b/rockspecs/multi-12.2-1.rockspec
index c1c70d0..c110b89 100644
--- a/rockspecs/multi-12.2-1.rockspec
+++ b/rockspecs/multi-12.2-1.rockspec
@@ -16,7 +16,6 @@ dependencies = {
"lua >= 5.1",
"bin",
"lanes",
- "lua-net"
}
build = {
type = "builtin",
diff --git a/rockspecs/multi-13.0-0.rockspec b/rockspecs/multi-13.0-0.rockspec
new file mode 100644
index 0000000..9737df5
--- /dev/null
+++ b/rockspecs/multi-13.0-0.rockspec
@@ -0,0 +1,31 @@
+package = "multi"
+version = "13.0-0"
+source = {
+ url = "git://github.com/rayaman/multi.git",
+ tag = "v13.0.0",
+}
+description = {
+ summary = "Lua Multi tasking library",
+ detailed = [[
+ This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d)
+ ]],
+ homepage = "https://github.com/rayaman/multi",
+ license = "MIT"
+}
+dependencies = {
+ "lua >= 5.1",
+ "bin",
+ "lanes",
+}
+build = {
+ type = "builtin",
+ modules = {
+ ["multi"] = "multi/init.lua",
+ ["multi.compat.love2d"] = "multi/compat/love2d.lua",
+ ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua",
+ ["multi.integration.loveManager"] = "multi/integration/loveManager.lua",
+ ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua",
+ ["multi.integration.networkManager"] = "multi/integration/networkManager.lua",
+ ["multi.integration.shared"] = "multi/integration/shared.lua"
+ }
+}
\ No newline at end of file
diff --git a/sample-nodeManager.lua b/sample-nodeManager.lua
new file mode 100644
index 0000000..7d57596
--- /dev/null
+++ b/sample-nodeManager.lua
@@ -0,0 +1,12 @@
+package.path="?/init.lua;?.lua;"..package.path
+multi = require("multi")
+local GLOBAL, THREAD = require("multi.integration.lanesManager").init()
+nGLOBAL = require("multi.integration.networkManager").init()
+multi:nodeManager(12345) -- Host a node manager on port: 12345
+print("Node Manager Running...")
+settings = {
+ priority = 0, -- 1 or 2
+ protect = false,
+}
+multi:mainloop(settings)
+-- Thats all you need to run the node manager, everything else is done automatically
diff --git a/test.lua b/test.lua
index e1e1cba..73d9f9c 100644
--- a/test.lua
+++ b/test.lua
@@ -1,36 +1,38 @@
package.path="?/init.lua;?.lua;"..package.path
multi = require("multi")
-local GLOBAL, THREAD = require("multi.integration.lanesManager").init()
-conn = multi:newSystemThreadedConnection("test"):init()
-multi:newSystemThread("Work",function()
- local multi = require("multi")
- conn = THREAD.waitFor("test"):init()
- conn(function(...)
- print(...)
- end)
- multi:newTLoop(function()
- conn:Fire("meh2")
- end,1)
- multi:mainloop()
-end)
-multi.OnError(function(a,b,c)
- print(c)
-end)
-multi:newTLoop(function()
- conn:Fire("meh")
-end,1)
-conn(function(...)
- print(">",...)
-end)
-
---~ jq = multi:newSystemThreadedJobQueue()
---~ jq:registerJob("test",function(a)
---~ return "Hello",a
---~ end)
---~ jq.OnJobCompleted(function(ID,...)
---~ print(ID,...)
---~ end)
---~ for i=1,16 do
---~ jq:pushJob("test",5)
+local GLOBAL,THREAD = require("multi.integration.lanesManager").init()
+nGLOBAL = require("multi.integration.networkManager").init()
+--~ local a
+--~ local clock = os.clock
+--~ function sleep(n) -- seconds
+--~ local t0 = clock()
+--~ while clock() - t0 <= n do end
--~ end
-multi:mainloop()
+--~ master = multi:newMaster{
+--~ name = "Main", -- the name of the master
+--~ noBroadCast = true, -- if using the node manager, set this to true to avoid double connections
+--~ managerDetails = {"localhost",12345}, -- the details to connect to the node manager (ip,port)
+--~ }
+--~ master.OnError(function(name,err)
+--~ print(name.." has encountered an error: "..err)
+--~ end)
+--~ local connlist = {}
+--~ multi:newThread("NodeUpdater",function()
+--~ while true do
+--~ thread.sleep(1)
+--~ for i=1,#connlist do
+--~ master:execute("TASK_MAN",connlist[i], multi:getTasksDetails())
+--~ end
+--~ end
+--~ end)
+--~ master.OnNodeConnected(function(name)
+--~ print("Connected to the node")
+--~ table.insert(connlist,name)
+--~ end)
+--~ multi.OnError(function(...)
+--~ print(...)
+--~ end)
+multi:mainloop{
+ protect = false,
+ print = true
+}