Working on custom prioritySchemes

This commit is contained in:
Ryan Ward 2023-05-03 23:09:37 -04:00
parent 42149ffab2
commit 189552ac65
5 changed files with 500 additions and 185 deletions

View File

@ -1,6 +1,10 @@
# Multi Version: 16.0.0 Connecting the dots
# Multi Version: 16.0.0 - Getting the priorities straight
**Key Changes**
- Concat connections
- Expanded connection logic
- New integration priorityManager
- Tests for threads
- Consistent behavior between the threading integrations
- Bug fixes
Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it!
@ -16,9 +20,19 @@ Progress is being made in [v16.0.0](https://github.com/rayaman/multi/tree/v16.0.
INSTALLING
----------
Link to optional dependencies:
- [lanes](https://github.com/LuaLanes/lanes)
- [lanes](https://github.com/LuaLanes/lanes) `luarocks install lanes`
- [chronos](https://github.com/ldrumm/chronos) `luarocks install chronos`
- [love2d](https://love2d.org/)
When using love2d add multi:uManager() or any processor to love.update()
```lua
function love.update(dt)
multi:uManager()
end
```
To install copy the multi folder into your environment and you are good to go</br>
If you want to use the system threads, then you'll need to install lanes or love2d game engine!
@ -34,7 +48,7 @@ https://discord.gg/U8UspuA
Planned features/TODO
---------------------
- [x] Create test suite (In progress, mostly done)
- [x] ~~Create test suite (In progress, mostly done)~~
- [ ] Network Parallelism rework
Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/Documentation.md)

View File

@ -1,7 +1,7 @@
# Changelog
Table of contents
---
[Update 16.0.0 - Connecting the dots](#update-1600---connecting-the-dots)</br>
[Update 16.0.0 - Connecting the dots](#update-1600---getting-the-priorities-straight)</br>
[Update 15.3.1 - Bug fix](#update-1531---bug-fix)</br>
[Update 15.3.0 - A world of connections](#update-1530---a-world-of-connections)</br>
[Update 15.2.1 - Bug fix](#update-1521---bug-fix)</br>
@ -58,15 +58,30 @@ Table of contents
[Update: EventManager 1.0.0 - Error checking](#update-eventmanager-100---error-checking)</br>
[Version: EventManager 0.0.1 - In The Beginning things were very different](#version-eventmanager-001---in-the-beginning-things-were-very-different)
# Update 16.0.0 - Connecting the dots
# Update 16.0.0 - Getting the priorities straight
Added
---
### New Integration - priorityManager
Allows the user to have multi auto set priorities. Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed these features will still work!
- multi:setCurrentTask() -- Used to set the current processor. Used in custom processors.
- multi:setCurrentProcess() -- Used to set the current processor. It should only be called on a processor object
- multi.warn(...) -- Sends a warning.
- multi.error(err) -- When called this function will gracefully kill multi, cleaning things up.
- multi.warn(...) -- Sends a warning. Yellow `WARNING`
- multi.error(err) -- When called this function will gracefully kill multi, cleaning things up. Red `ERROR`
**Note:** If you want to have multi.print, multi.warn and multi.error to work you need to enable them in settings
```lua
multi, thread = require("multi"):init {
print=true,
warn=true,
error=true -- Errors will throw regardless. Setting to true will
-- cause the library to force hard crash itself!
}
```
- THREAD.setENV(table) -- Set a simple table that will be merged into the global namespace
**Note:** To maintain compatibility between each integration use simple tables. No self references, and string indices only
**Note:** To maintain compatibility between each integration use simple tables. No self references, and string indices only.
```lua
THREAD.setENV({
shared_function = function()
@ -204,8 +219,18 @@ Added
Changed
---
- multi errors now internally call `multi.error` instead of `multi.print`
- Actors Act() method now returns true when the main event is fired. Steps/Loops always return true. Nil is returned otherwise.
- Connection:Connect(func, name) Now you can supply a name and name the connection.
- Connection:getConnection(name) This will return the connection function which you can do what you will with it.
- Fast connections are the only connections. Legacy connections have been removed completely. Not much should change on the users end. Perhaps some minor changes.
- conn:Lock(conn) When supplied with a connection reference (What is returned by Connect(func)) it will only lock that connection Reference and not the entire connection. Calling without any arguments will lock the entire connection.
- connUnlock(conn) When supplied with a connection reference it restores that reference and it can be fired again. When no arguments are supplied it unlocks the entire connection.
**Note:** Lock and Unlock when supplied with arguments and not supplied with arguments operate on different objects. If you unlock an entire connection. Individual connection refs will not unlock. The same applies with locking. The entire connection and references are treated differently.
- multi.OnObjectCreated is only called when an object is created in a particular process. Proc.OnObjectCreated is needed to detect when an object is created within a process.
- multi.print shows "INFO" before it's message.
- multi.print shows "INFO" before it's message. Blue `INFO`
- Connections internals changed, not too much changed on the surface.
- newConnection(protect, func, kill)
- `protect` disables fastmode, but protects the connection
@ -221,8 +246,9 @@ Removed
Fixed
---
- multi:reallocate(processor, index) has been fixed to work with the current changes of the library.
- Issue with lanes not handling errors properly. This is now resolved
- Oversight with how pushStatus worked with nesting threaded functions, connections and forwarding events
- Oversight with how pushStatus worked with nesting threaded functions, connections and forwarding events. Changes made and this works now!
```lua
func = thread:newFunction(function()
for i=1,10 do

299
init.lua
View File

@ -142,25 +142,34 @@ function multi.ForEach(tab,func)
for i=1,#tab do func(tab[i]) end
end
function multi.randomString(n)
local str = ''
local strings = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'}
for i=1,n do
str = str..''..strings[math.random(1,#strings)]
end
return str
end
local optimization_stats = {}
local ignoreconn = true
function multi:newConnection(protect, func, kill)
local empty_func = function() end
function multi:newConnection(protect,func,kill)
local c={}
local call_funcs = {}
local lock = false
c.__connectionAdded = function() end
c.rawadd = false
c.Parent = self
local locked = {}
local fast = {}
c.Parent=self
setmetatable(c,{
__call=function(self, ...) -- ()
setmetatable(c,{__call=function(self,...)
local t = ...
if type(t)=="table" then
for i,v in pairs(t) do
if v == self then
local ref = self:Connect(select(2, ...))
if v==self then
local ref = self:Connect(select(2,...))
if ref then
ref.root_link = select(1, ...)
ref.root_link = select(1,...)
return ref
end
return self
@ -171,49 +180,6 @@ function multi:newConnection(protect, func, kill)
return self:Connect(...)
end
end,
__mod = function(obj1, obj2) -- %
local cn = multi:newConnection()
if type(obj1) == "function" and type(obj2) == "table" then
obj2(function(...)
cn:Fire(obj1(...))
end)
else
multi.error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)")
end
return cn
end,
__concat = function(obj1, obj2) -- ..
local cn = multi:newConnection()
local ref
if type(obj1) == "function" and type(obj2) == "table" then
cn(function(...)
if obj1(...) then
obj2:Fire(...)
end
end)
cn.__connectionAdded = function(conn, func)
cn:Unconnect(conn)
obj2:Connect(func)
end
elseif type(obj1) == "table" and type(obj2) == "function" then
ref = cn(function(...)
obj1:Fire(...)
obj2(...)
end)
cn.__connectionAdded = function()
cn.rawadd = true
cn:Unconnect(ref)
ref = cn(function(...)
if obj2(...) then
obj1:Fire(...)
end
end)
end
else
multi.error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function")
end
return cn
end,
__add = function(c1,c2) -- Or
local cn = multi:newConnection()
c1(function(...)
@ -270,63 +236,106 @@ function multi:newConnection(protect, func, kill)
return #call_funcs~=0
end
function c:getConnection(name,ignore)
if ignore then
return connections[name] or CRef
else
return connections[name] or self
function c:Lock(conn)
if conn and not conn.lock then
conn.lock = function() end
for i = 1, #fast do
if conn.ref == fast[i] then
fast[i] = conn.lock
return self
end
end
return self
end
end
function c:Lock()
lock = self.Fire
self.Fire = function() end
lock = true
return self
end
function c:Unlock()
self.Fire = lock
function c:Unlock(conn)
if conn and conn.lock then
for i = 1, #fast do
if conn.lock == fast[i] then
fast[i] = conn.ref
return self
end
end
return self
end
lock = false
return self
end
if protect then
function c:Fire(...)
if lock then return end
local kills = {}
for i=1,#fast do
local suc, err = pcall(fast[i], ...)
if not suc then
print(err)
end
if kill then
table.remove(kills,i)
multi:newTask(function()
for _,k in pairs(kills) do
table.remove(fast, k)
end
end)
end
end
end
end
function c:getConnections()
return call_funcs
end
function c:getConnection(name, ignore)
return fast[name] or function() multi:warning("") end
end
function c:Unconnect(conn)
if conn.fast then
for i = 1, #call_funcs do
if conn.ref == call_funcs[i] then
table.remove(call_funcs, i)
for i = 1, #fast do
if conn.ref == fast[i] then
return table.remove(fast, i), i
end
end
end
function c:fastMode() return self end
if kill then
local kills = {}
function c:Fire(...)
if lock then return end
for i=1,#fast do
fast[i](...)
if kill then
table.insert(kills,i)
multi:newTask(function()
for k = #kills, 1, -1 do
table.remove(fast, k)
table.remove(kills,i)
end
end)
end
end
elseif conn.Destroy then
conn:Destroy()
end
end
function c:Fire(...)
for i=1, #call_funcs do
call_funcs[i](...)
end
end
-- Not needed anymore, since it's so light, I'll leave it in forever
function c:fastMode() return self end
function c:Connect(func)
local th
if thread.getRunningThread then
th = thread.getRunningThread()
end
if th then
local fref = func
func = function(...)
__CurrentConnectionThread = th
fref(...)
else
function c:Fire(...)
if lock then return end
for i=1,#fast do
fast[i](...)
end
end
table.insert(call_funcs, func)
end
function c:Connect(func, name)
table.insert(fast, func)
if name then
fast[name] = func
else
fast["Conn_"..multi.randomString(12)] = func
end
local temp = {fast = true}
setmetatable(temp,{
__call=function(s,...)
@ -346,11 +355,7 @@ function multi:newConnection(protect, func, kill)
end,
})
temp.ref = func
if self.rawadd then
self.rawadd = false
else
self.__connectionAdded(temp, func)
end
temp.name = name
return temp
end
@ -366,41 +371,11 @@ function multi:newConnection(protect, func, kill)
return temp
end
if find_optimization then
--
end
c.connect=c.Connect
c.GetConnection=c.getConnection
c.HasConnections = c.hasConnections
c.GetConnection = c.getConnection
if protect then -- Do some tests and override the fastmode if you want to do something differently
function c:Fire(...)
for i=#call_funcs,1,-1 do
if not call_funcs[i] then return end
local suc, err = pcall(call_funcs[i],...)
if not suc then
multi.print(err)
end
if kill then
table.remove(call_funcs,i)
end
end
end
elseif kill then
function c:Fire(...)
for i=#call_funcs,1,-1 do
call_funcs[i](...)
table.remove(call_funcs,i)
end
end
end
if func then
c = c .. func
end
if not(ignoreconn) then
self:create(c)
end
@ -474,6 +449,7 @@ function multi:SetTime(n)
self.link:Pause()
self.OnTimedOut:Fire(self.link)
self:Destroy()
return true
end
end
return self
@ -564,7 +540,7 @@ function multi:isDone()
end
function multi:create(ref)
self.OnObjectCreated:Fire(ref,self)
self.OnObjectCreated:Fire(ref, self)
return self
end
@ -653,6 +629,7 @@ function multi:newEvent(task)
self:Pause()
self.returns = t
c.OnEvent:Fire(self)
return true
end
end
function c:SetTask(func)
@ -675,6 +652,7 @@ function multi:newUpdater(skip)
if pos >= skip then
pos = 0
self.OnUpdate:Fire(self)
return true
end
pos = pos+1
end
@ -701,6 +679,7 @@ function multi:newAlarm(set)
self.Active=false
self.OnRing:Fire(self)
t = clock()
return true
end
end
function c:Resume()
@ -732,10 +711,12 @@ function multi:newLoop(func,notime)
if notime then
function c:Act()
self.OnLoop:Fire(self)
return true
end
else
function c:Act()
self.OnLoop:Fire(self,clock()-start)
return true
end
end
c.OnLoop = self:newConnection():fastMode()
@ -783,6 +764,7 @@ function multi:newStep(start,reset,count,skip)
if self.spos>=self.skip then
self.spos=0
end
return true
end
c.Reset=c.Resume
c.OnStart = self:newConnection():fastMode()
@ -820,6 +802,7 @@ function multi:newTLoop(func,set)
self.life=self.life+1
self.timer:Reset()
self.OnLoop:Fire(self,self.life)
return true
end
end
function c:Set(set)
@ -878,6 +861,7 @@ function multi:newTStep(start,reset,count,set)
self.OnEnd:Fire(self)
self.pos=self.start
end
return true
end
end
function c:Set(set)
@ -957,6 +941,14 @@ function multi.getCurrentTask()
return __CurrentTask
end
function multi:setCurrentProcess()
__CurrentProcess = self
end
function multi:setCurrentTask()
__CurrentTask = self
end
function multi:getName()
return self.Name
end
@ -998,7 +990,7 @@ function multi:newProcessor(name, nothread)
c.OnError = multi:newConnection()
end
c.OnError(multi.print)
c.OnError(multi.error)
function c:getThreads()
return c.threads
@ -1379,7 +1371,7 @@ function thread:newThread(name, func, ...)
c.isError = false
c.OnError = multi:newConnection(true,nil,true)
c.OnDeath = multi:newConnection(true,nil,true)
c.OnError(multi.print)
c.OnError(multi.error)
function c:getName()
return c.Name
@ -1450,7 +1442,7 @@ function thread:newThread(name, func, ...)
globalThreads[c] = multi
threadid = threadid + 1
self:create(c)
multi:getCurrentProcess():create(c)
c.creationTime = os.clock()
return c
end
@ -1865,10 +1857,6 @@ local function doOpt()
end
end
function multi:setCurrentProcess()
__CurrentProcess = self
end
local init = false
function multi.init(settings, realsettings)
if settings == multi then settings = realsettings end
@ -2076,15 +2064,6 @@ else
end
end
function multi.randomString(n)
local str = ''
local strings = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'}
for i=1,n do
str = str..''..strings[math.random(1,#strings)]
end
return str
end
function multi:getChildren()
return self.Mainloop
end
@ -2173,12 +2152,16 @@ function multi:IsAnActor()
return self.Act~=nil
end
function multi:reallocate(o, n)
n=n or #o.Mainloop+1
function multi:reallocate(processor, index)
index=index or #processor.Mainloop+1
local int=self.Parent
self:Destroy()
self.Parent=o
table.insert(o.Mainloop,n,self)
self.Parent=processor
print("Moving task to new processor!")
if index then
table.insert(processor.Mainloop, index, self)
else
table.insert(processor.Mainloop, self)
end
self.Active=true
return self
end
@ -2200,18 +2183,28 @@ end
function multi.print(...)
if multi.defaultSettings.print then
print("INFO:", table.concat({...}, " "))
local t = {}
for i,v in pairs({...}) do t[#t+1] = tostring(v) end
io.write("\x1b[94mINFO:\x1b[0m " .. table.concat(t," ") .. "\n")
end
end
function multi.warn(...)
if multi.defaultSettings.warn then
print("WARNING:", table.concat({...}, " "))
local t = {}
for i,v in pairs({...}) do t[#t+1] = tostring(v) end
io.write("\x1b[93mWARNING:\x1b[0m " .. table.concat(t," ") .. "\n")
end
end
function multi.error(err)
error("ERROR: " .. err)
function multi.error(self, err)
if type(err) == "bool" then crash = err end
if type(self) == "string" then err = self end
io.write("\x1b[91mERROR:\x1b[0m " .. err .. "\n")
error("^^^")
if multi.defaultSettings.error then
os.exit(1)
end
end
multi.GetType = multi.getType

View File

@ -25,57 +25,193 @@ local mainloop_p = multi.mainloop_p
local uManagerRef = multi.uManagerRef
local uManagerRefP = multi.uManagerRefP1
local PROFILE_COUNT = 5
-- self:setCurrentProcess() a bit slower than using the local var, but there isn't another option
local priorityManager = multi:newProcessor("Priority Manager", true)
priorityManager.newThread = function() multi.warn("You cannot spawn threads on the priority manager!") end
priorityManager.setPriorityScheme = function() multi.warn("You cannot set priority on the priorityManager!") end
local function average(t)
local sum = 0
for _,v in pairs(t) do
sum = sum + v
end
return sum / #t
end
local function getPriority(obj)
local avg = average(obj.__profiling)
if avg < 0.0002 then
multi.print("Setting priority to: core")
return PList[1]
elseif avg < 0.0004 then
multi.print("Setting priority to: very high")
return PList[2]
elseif avg < 0.0008 then
multi.print("Setting priority to: high")
return PList[3]
elseif avg < 0.001 then
multi.print("Setting priority to: above normal")
return PList[4]
elseif avg < 0.0025 then
multi.print("Setting priority to: normal")
return PList[5]
elseif avg < 0.005 then
multi.print("Setting priority to: below normal")
return PList[6]
elseif avg < 0.008 then
multi.print("Setting priority to: low")
return PList[7]
elseif avg < 0.01 then
multi.print("Setting priority to: very low")
return PList[8]
else
multi.print("Setting priority to: idle")
return PList[9]
end
end
local start, stop
priorityManager.uManager = function(self)
-- proc.run already checks if the processor is active
self:setCurrentProcess()
local Loop=self.Mainloop
local ctask
for _D=#Loop,1,-1 do
ctask = Loop[_D]
ctask:setCurrentTask()
start = chronos.nanotime()
if ctask:Act() then
stop = chronos.nanotime()
if ctask.__profiling then
table.insert(ctask.__profiling, stop - start)
end
if ctask.__profiling and #ctask.__profiling == PROFILE_COUNT then
ctask:setPriority(getPriority(ctask))
ctask:reallocate(ctask.__restoreProc)
ctask.__restoreProc = nil
ctask.__profiling = nil
end
end
self:setCurrentProcess()
end
end
local function processHook(obj, proc)
if obj.Type == multi.PROCESS or not(obj.IsAnActor) then return end
obj.__restoreProc = proc
obj.__profiling = {}
obj:reallocate(priorityManager)
end
local function init()
local RR, PB, TB = 0, 1, 2
local registry = {}
multi.priorityScheme = {
RoundRobin = 0,
PriorityBased = 1,
TimedBased = 2
RoundRobin = "RoundRobin",
PriorityBased = "PriorityBased",
TimeBased = "TimeBased"
}
function multi:setProfilerCount(count)
PROFILE_COUNT = count
end
function multi:recalibrate()
if self.__processConn then
local items = self.Mainloop
for i,v in pairs(items) do
processHook(v, self)
end
else
multi.error("Cannot recalibrate the priority if not using Time based mangement!")
end
end
function multi:isRegistredScheme(scheme)
--
end
function multi:getRegisteredScheme(scheme)
--
end
local empty_func = function() return true end
function multi:registerScheme(name,options)
local mainloop = options.mainloop or multi.error("You must provide a mainloop option when registring a scheme!")
local umanager = options.umanager or multi.error("You must provide a umanager option when registring a scheme!")
if not options.condition then
multi.warn("You might want to use condition when registring a scheme! A function that returns true has been auto generated for you!")
end
local condition = options.condition or empty_func
if registry[name] and not registry[name].static then
multi.warn("A scheme named: \"" .. name .. "\" has already been registred, overriting!")
else
multi.error("A scheme named: \"" .. name .. "\" has already been registred!")
end
registry[name] = {
mainloop = mainloop,
umanager = umanger,
condition = condition,
static = options.static or false
}
return true
end
function multi:setPriorityScheme(scheme)
if not self.Type == multi.PROCESS or not self.Type == multi.ROOTPROCESS then
multi.warn("You should only invoke setPriorityScheme on a processor object!")
end
if scheme == RR then
multi.mainloop = mainloop
multi.uManager = uManagerRef
elseif scheme == PB then
multi.mainloop = mainloop_p
multi.uManager = uManagerRefP
elseif scheme == TB then
--
if scheme == multi.priorityScheme.RoundRobin then
if self.__processConn then self.OnObjectCreated:Unconnect(self.__processConn) self.__processConn = nil end
self.mainloop = mainloop
self.uManager = uManagerRef
elseif scheme == multi.priorityScheme.PriorityBased then
if self.__processConn then self.OnObjectCreated:Unconnect(self.__processConn) self.__processConn = nil end
self.mainloop = mainloop_p
self.uManager = uManagerRefP
elseif scheme == multi.priorityScheme.TimeBased then
if not chronos then return multi.warn("Unable to use TimeBased Priority without the chronos library!") end
if self.__processConn then multi.warn("Already enabled TimeBased Priority!") end
self.__processConn = self.OnObjectCreated(processHook)
self.mainloop = mainloop_p
self.uManager = uManagerRefP
elseif self:isRegistredScheme(scheme) then
local mainloop, umanager, condition = self:getRegisteredScheme(scheme)
if condition() then
self.mainloop = mainloop
self.uManager = umanager
end
else
multi.error("Invalid priority scheme passed!")
self.error("Invalid priority scheme selected!")
end
end
end
local function init_chronos()
multi:newThread("Priority Manager", function()
thread:newThread("System Priority Manager", function()
while true do
thread.yield()
priorityManager.run()
end
end)
end).OnError(multi.error)
end
if chronos then
init_chronos()
else
multi.print("In order to have time based priority management, you need to install the chronos library!")
multi.warn("In order to have time based priority management, you need to install the chronos library!")
end
init()
--chronos.nanotime()
init()

View File

@ -1,5 +1,151 @@
package.path = "../?/init.lua;../?.lua;"..package.path
multi, thread = require("multi"):init{print=true,findopt=true}
multi, thread = require("multi"):init{print=true,warn=true,error=true}
require("multi.integration.priorityManager")
multi:mainloop()
test = multi:newProcessor("Test")
test:setPriorityScheme(multi.priorityScheme.TimeBased)
multi.OnObjectCreated(function(proc, obj)
print("MULTI",proc.Type,obj.Type)
end)
local a = 0
test:newUpdater(100000):OnUpdate(function()
print("Print is slowish")
end)
print("Running...")
multi:mainloop()
-- local conn1, conn2, conn3 = multi:newConnection(nil,nil,true), multi:newConnection(), multi:newConnection()
-- local link = conn1(function()
-- print("Conn1, first")
-- end)
-- local link2 = conn1(function()
-- print("Conn1, second")
-- end)
-- local link3 = conn1(function()
-- print("Conn1, third")
-- end)
-- local link4 = conn2(function()
-- print("Conn2, first")
-- end)
-- local link5 = conn2(function()
-- print("Conn2, second")
-- end)
-- local link6 = conn2(function()
-- print("Conn2, third")
-- end)
-- print("Links 1-6",link,link2,link3,link4,link5,link6)
-- conn1:Lock(link)
-- print("All conns\n-------------")
-- conn1:Fire()
-- conn2:Fire()
-- conn1:Unlock(link)
-- conn1:Unconnect(link3)
-- conn2:Unconnect(link6)
-- print("All conns Edit\n---------------------")
-- conn1:Fire()
-- conn2:Fire()
-- thread:newThread(function()
-- print("Awaiting status")
-- thread.hold(conn1 + (conn2 * conn3))
-- print("Conn or Conn2 and Conn3")
-- end)
-- multi:newAlarm(1):OnRing(function()
-- print("Conn")
-- conn1:Fire()
-- end)
-- multi:newAlarm(2):OnRing(function()
-- print("Conn2")
-- conn2:Fire()
-- end)
-- multi:newAlarm(3):OnRing(function()
-- print("Conn3")
-- conn3:Fire()
-- os.exit()
-- end)
-- local conn = multi:newSystemThreadedConnection("conn"):init()
-- multi:newSystemThread("Thread_Test_1", function()
-- local multi, thread = require("multi"):init()
-- local conn = GLOBAL["conn"]:init()
-- local console = THREAD.getConsole()
-- conn(function(a,b,c)
-- console.print(THREAD:getName().." was triggered!",a,b,c)
-- end)
-- multi:mainloop()
-- end)
-- multi:newSystemThread("Thread_Test_2", function()
-- local multi, thread = require("multi"):init()
-- local conn = GLOBAL["conn"]:init()
-- local console = THREAD.getConsole()
-- conn(function(a,b,c)
-- console.print(THREAD:getName().." was triggered!",a,b,c)
-- end)
-- multi:newAlarm(2):OnRing(function()
-- console.print("Fire 2!!!")
-- conn:Fire(4,5,6)
-- THREAD.kill()
-- end)
-- multi:mainloop()
-- end)
-- local console = THREAD.getConsole()
-- conn(function(a,b,c)
-- console.print("Mainloop conn got triggered!",a,b,c)
-- end)
-- alarm = multi:newAlarm(1)
-- alarm:OnRing(function()
-- console.print("Fire 1!!!")
-- conn:Fire(1,2,3)
-- end)
-- alarm = multi:newAlarm(3):OnRing(function()
-- multi:newSystemThread("Thread_Test_3",function()
-- local multi, thread = require("multi"):init()
-- local conn = GLOBAL["conn"]:init()
-- local console = THREAD.getConsole()
-- conn(function(a,b,c)
-- console.print(THREAD:getName().." was triggered!",a,b,c)
-- end)
-- multi:newAlarm(4):OnRing(function()
-- console.print("Fire 3!!!")
-- conn:Fire(7,8,9)
-- end)
-- multi:mainloop()
-- end)
-- end)
-- multi:newSystemThread("Thread_Test_4",function()
-- local multi, thread = require("multi"):init()
-- local conn = GLOBAL["conn"]:init()
-- local conn2 = multi:newConnection()
-- local console = THREAD.getConsole()
-- multi:newAlarm(2):OnRing(function()
-- conn2:Fire()
-- end)
-- multi:newThread(function()
-- console.print("Conn Test!")
-- thread.hold(conn + conn2)
-- console.print("It held!")
-- end)
-- multi:mainloop()
-- end)
-- multi:mainloop()