working on 1.14.4

This commit is contained in:
Ryan Ward 2019-10-29 23:57:04 -04:00
parent 296d56d233
commit 689133e71f
8 changed files with 1279 additions and 944 deletions

View File

@ -1,5 +1,38 @@
# Changes # Changes
[TOC] [TOC]
Update 14.0.0 Consistency and stability
-------------
Added:
- multi.init() -- Initlizes the library! Must be called for multiple files to have the same handle. Example below
- thread.holdFor(NUMBER sec, FUNCTION condition) -- Works like hold, but timesout when a certain amount of time has passed!
- thread.holdWithin(NUMBER; cycles,FUNCTION; condition) -- Holds until the condition is met! If the number of cycles passed is equal to cycles, hold will return a timeout error
**Note:** when hold has a timeout the first argument will return nil and the second atgument will be TIMEOUT, if not timed out hold will return the values from the conditions
Fixed:
- Connections had a preformance issue where they would create a non function when using connection.getConnection() of a non existing label.
- An internal mismanagement of the treads scheduler was fixed. Now it should be quicker and free of bugs
- Thread error management is the integrations was not properly implemented. This is now fixed
-
Changed:
- Ties in to the new function that has been added multi.init()
```lua
local multi, thread = require("multi").init() -- The require multi function still returns the multi object like before
```
Note: Using init allows you to get access to the thread handle. This was done because thread was modifying the global space as well as multi. I wanted to not modify the global space anymore.
internally most of your code can stay the same, you only need to change how the library is required. I do toy a bit with the global space, buy I use a variable name that is invalid as a variable name. The variable name is $multi. This is used internally to keep some records and maintain a clean space
Also when using intergrations things now look more consistant.
```lua
local multi, thread = require("multi").init()
local GLOBSL, THREAD = require("multi.integration.lanesManager").init() -- or whichever manager you are using
local nGLOBAL, nTHREAD = require("multi.intergration.networkManager).inti()
```
Note: You can mix and match integrations together. You can create systemthreads within network threads, and you can also create cotoutine based threads within bothe network and system threads. This gives you quite a bit of flexibility to create something awesome.
Going forward:
- Sterlization is still being worked on. I was planning of having a way to save state of multi objects and such, but that isn't possible without knowing how your code is strutured or if it is even made to handle something like that. So I decided on giving a tostirng/tofile method for each multi object as well as a fromstring/fromfile method for use. This is technically in the code already, but not documented. It has actually been in the code for a while, but its not done just yet and I want to make it perfect before sending it out.
Update 13.1.0 Bug fixes and features added Update 13.1.0 Bug fixes and features added
------------- -------------
Added: Added:

View File

@ -21,12 +21,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]] ]]
local multi = require("multi") local multi, thread = require("multi").init()
os.sleep=love.timer.sleep os.sleep = love.timer.sleep
multi.drawF={} multi.drawF = {}
function multi:onDraw(func,i) function multi:onDraw(func, i)
i=i or 1 i = i or 1
table.insert(self.drawF,i,func) table.insert(self.drawF, i, func)
end end
multi.OnKeyPressed = multi:newConnection() multi.OnKeyPressed = multi:newConnection()
multi.OnKeyReleased = multi:newConnection() multi.OnKeyReleased = multi:newConnection()
@ -38,39 +38,48 @@ multi.OnDraw = multi:newConnection()
multi.OnTextInput = multi:newConnection() multi.OnTextInput = multi:newConnection()
multi.OnUpdate = multi:newConnection() multi.OnUpdate = multi:newConnection()
multi.OnQuit = multi:newConnection() multi.OnQuit = multi:newConnection()
multi.OnPreLoad(function() multi.OnPreLoad(
local function Hook(func,conn) function()
if love[func]~=nil then local function Hook(func, conn)
love[func] = Library.convert(love[func]) if love[func] ~= nil then
love[func]:inject(function(...) love[func] = Library.convert(love[func])
conn:Fire(...) love[func]:inject(
return {...} function(...)
end,1) conn:Fire(...)
elseif love[func]==nil then return {...}
love[func] = function(...) end,
conn:Fire(...) 1
)
elseif love[func] == nil then
love[func] = function(...)
conn:Fire(...)
end
end end
end end
Hook("quit", multi.OnQuit)
Hook("keypressed", multi.OnKeyPressed)
Hook("keyreleased", multi.OnKeyReleased)
Hook("mousepressed", multi.OnMousePressed)
Hook("mousereleased", multi.OnMouseReleased)
Hook("wheelmoved", multi.OnMouseWheelMoved)
Hook("mousemoved", multi.OnMouseMoved)
Hook("draw", multi.OnDraw)
Hook("textinput", multi.OnTextInput)
Hook("update", multi.OnUpdate)
multi.OnDraw(
function()
for i = 1, #multi.drawF do
love.graphics.setColor(255, 255, 255, 255)
multi.drawF[i]()
end
end
)
end end
Hook("quit",multi.OnQuit) )
Hook("keypressed",multi.OnKeyPressed) multi.OnQuit(
Hook("keyreleased",multi.OnKeyReleased) function()
Hook("mousepressed",multi.OnMousePressed) multi.Stop()
Hook("mousereleased",multi.OnMouseReleased) love.event.quit()
Hook("wheelmoved",multi.OnMouseWheelMoved) end
Hook("mousemoved",multi.OnMouseMoved) )
Hook("draw",multi.OnDraw)
Hook("textinput",multi.OnTextInput)
Hook("update",multi.OnUpdate)
multi.OnDraw(function()
for i=1,#multi.drawF do
love.graphics.setColor(255,255,255,255)
multi.drawF[i]()
end
end)
end)
multi.OnQuit(function()
multi.Stop()
love.event.quit()
end)
return multi return multi

View File

@ -25,6 +25,9 @@ local bin = pcall(require,"bin")
local multi = {} local multi = {}
local clock = os.clock local clock = os.clock
local thread = {} local thread = {}
if not _G["$multi"] then
_G["$multi"] = {multi=multi,thread=thread}
end
multi.Version = "13.1.0" multi.Version = "13.1.0"
multi._VERSION = "13.1.0" multi._VERSION = "13.1.0"
multi.stage = "stable" multi.stage = "stable"
@ -78,6 +81,9 @@ multi.PriorityTick=1 -- Between 1, 2 and 4
multi.Priority=multi.Priority_High multi.Priority=multi.Priority_High
multi.threshold=256 multi.threshold=256
multi.threstimed=.001 multi.threstimed=.001
function multi.init()
return _G["$multi"].multi,_G["$multi"].thread
end
function multi.queuefinal(self) function multi.queuefinal(self)
self:Destroy() self:Destroy()
if self.Parent.Mainloop[#self.Parent.Mainloop] then if self.Parent.Mainloop[#self.Parent.Mainloop] then
@ -758,6 +764,9 @@ function multi:newConnector()
local c = {Type = "connector"} local c = {Type = "connector"}
return c return c
end end
local CRef = {
Fire = function() end
}
function multi:newConnection(protect,func) function multi:newConnection(protect,func)
local c={} local c={}
c.callback = func c.callback = func
@ -802,11 +811,9 @@ function multi:newConnection(protect,func)
return self return self
end end
c.FConnect=c.fConnect c.FConnect=c.fConnect
function c:getConnection(name,ingore) function c:getConnection(name,ignore)
if ingore then if ignore then
return self.connections[name] or { return self.connections[name] or CRef
Fire=function() return end -- if the connection doesn't exist lets call all of them or silently ignore
}
else else
return self.connections[name] or self return self.connections[name] or self
end end
@ -1489,6 +1496,10 @@ function thread.hold(n)
thread._Requests() thread._Requests()
return coroutine.yield({"_hold_",n or function() return true end}) return coroutine.yield({"_hold_",n or function() return true end})
end end
function thread.holdFor(sec,n)
thread._Requests()
return coroutine.yield({"_holdF_", sec, n or function() return true end})
end
function thread.skip(n) function thread.skip(n)
thread._Requests() thread._Requests()
if not n then n = 1 elseif n<1 then n = 1 end if not n then n = 1 elseif n<1 then n = 1 end
@ -1652,6 +1663,13 @@ function multi.initThreads()
threads[i].task = "hold" threads[i].task = "hold"
threads[i].__ready = false threads[i].__ready = false
ret = nil ret = nil
elseif ret[1]=="_holdF_" then
threads[i].sec = ret[2]
threads[i].func = ret[3]
threads[i].task = "holdF"
threads[i].time = clock()
threads[i].__ready = false
ret = nil
end end
end end
end end
@ -1684,8 +1702,19 @@ function multi.initThreads()
threads[i].task = "" threads[i].task = ""
threads[i].__ready = true threads[i].__ready = true
end end
elseif threads[i].task == "holdF" then
t0,t1,t2,t3,t4,t5,t6 = threads[i].func()
if t0 then
threads[i].task = ""
threads[i].__ready = true
elseif clock() - threads[i].time>=threads[i].sec then
threads[i].task = ""
threads[i].__ready = true
t0 = nil
t1 = "TIMEOUT"
end
end end
if threads[i].__ready then if threads[i] and threads[i].__ready then
threads[i].__ready = false threads[i].__ready = false
_,ret=coroutine.resume(threads[i].thread,t0,t1,t2,t3,t4,t5,t6) _,ret=coroutine.resume(threads[i].thread,t0,t1,t2,t3,t4,t5,t6)
end end
@ -1724,6 +1753,13 @@ function multi:threadloop()
threads[i].task = "hold" threads[i].task = "hold"
threads[i].__ready = false threads[i].__ready = false
ret = nil ret = nil
elseif ret[1]=="_holdF_" then
threads[i].sec = ret[2]
threads[i].func = ret[3]
threads[i].task = "holdF"
threads[i].time = clock()
threads[i].__ready = false
ret = nil
end end
end end
end end
@ -1753,6 +1789,17 @@ function multi:threadloop()
threads[i].task = "" threads[i].task = ""
threads[i].__ready = true threads[i].__ready = true
end end
elseif threads[i].task == "holdF" then
t0,t1,t2,t3,t4,t5,t6 = threads[i].func()
if t0 then
threads[i].task = ""
threads[i].__ready = true
elseif clock() - threads[i].time>=threads[i].sec then
threads[i].task = ""
threads[i].__ready = true
t0 = nil
t1 = "TIMEOUT"
end
end end
if threads[i].__ready then if threads[i].__ready then
threads[i].__ready = false threads[i].__ready = false
@ -2550,7 +2597,4 @@ end
function multi:setDefualtStateFlag(opt) function multi:setDefualtStateFlag(opt)
-- --
end end
if not(multi.Version == "13.2.0" or multi.Version == "14.0.0") then return multi
_G.thread = thread
end
return multi, thread

View File

@ -21,20 +21,26 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]] ]]
package.path="?/init.lua;?.lua;"..package.path package.path = "?/init.lua;?.lua;" .. package.path
local multi, thread = require("multi").init() -- get it all and have it on all lanes
if multi.integration then -- This allows us to call the lanes manager from supporting modules without a hassel
return {
init = function()
return multi.integration.GLOBAL, multi.integration.THREAD
end
}
end
function os.getOS() function os.getOS()
if package.config:sub(1,1)=='\\' then if package.config:sub(1, 1) == "\\" then
return 'windows' return "windows"
else else
return 'unix' return "unix"
end end
end end
-- Step 1 get lanes -- Step 1 get lanes
lanes=require("lanes").configure() lanes = require("lanes").configure()
local multi, thread = require("multi") -- get it all and have it on all lanes
multi.SystemThreads = {} multi.SystemThreads = {}
local thread = thread multi.isMainThread = true
multi.isMainThread=true
function multi:canSystemThread() function multi:canSystemThread()
return true return true
end end
@ -45,40 +51,45 @@ end
local __GlobalLinda = lanes.linda() -- handles global stuff local __GlobalLinda = lanes.linda() -- handles global stuff
local __SleepingLinda = lanes.linda() -- handles sleeping stuff local __SleepingLinda = lanes.linda() -- handles sleeping stuff
-- For convenience a GLOBAL table will be constructed to handle requests -- For convenience a GLOBAL table will be constructed to handle requests
local GLOBAL={} local GLOBAL = {}
setmetatable(GLOBAL,{ setmetatable(
__index=function(t,k) GLOBAL,
return __GlobalLinda:get(k) {
end, __index = function(t, k)
__newindex=function(t,k,v) return __GlobalLinda:get(k)
__GlobalLinda:set(k,v) end,
end, __newindex = function(t, k, v)
}) __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={} local THREAD = {}
function THREAD.set(name,val) function THREAD.set(name, val)
__GlobalLinda:set(name,val) __GlobalLinda:set(name, val)
end end
function THREAD.get(name) function THREAD.get(name)
__GlobalLinda:get(name) __GlobalLinda:get(name)
end end
local function randomString(n) local function randomString(n)
local str = '' 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'} 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 for i = 1, n do
str = str..''..strings[math.random(1,#strings)] str = str .. "" .. strings[math.random(1, #strings)]
end end
return str return str
end end
function THREAD.waitFor(name) function THREAD.waitFor(name)
local function wait() local function wait()
math.randomseed(os.time()) math.randomseed(os.time())
__SleepingLinda:receive(.001,randomString(12)) __SleepingLinda:receive(.001, randomString(12))
end end
repeat wait() until __GlobalLinda:get(name) repeat
wait()
until __GlobalLinda:get(name)
return __GlobalLinda:get(name) return __GlobalLinda:get(name)
end end
function THREAD.testFor(name,val,sym) function THREAD.testFor(name, val, sym)
-- --
end end
function THREAD.getCores() function THREAD.getCores()
@ -87,10 +98,10 @@ end
function THREAD.getThreads() function THREAD.getThreads()
return GLOBAL.__THREADS__ return GLOBAL.__THREADS__
end end
if os.getOS()=="windows" then if os.getOS() == "windows" then
THREAD.__CORES=tonumber(os.getenv("NUMBER_OF_PROCESSORS")) THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS"))
else else
THREAD.__CORES=tonumber(io.popen("nproc --all"):read("*n")) THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n"))
end end
function THREAD.kill() -- trigger the lane destruction function THREAD.kill() -- trigger the lane destruction
error("Thread was killed!") error("Thread was killed!")
@ -107,40 +118,42 @@ Idle wait keeps the CPU running better where busy wait wastes CPU cycles... Lane
however, a linda recieve will in fact be a idle wait! So we use that and wrap it in a nice package]] however, a linda recieve will in fact be a idle wait! So we use that and wrap it in a nice package]]
function THREAD.sleep(n) function THREAD.sleep(n)
math.randomseed(os.time()) math.randomseed(os.time())
__SleepingLinda:receive(n,randomString(12)) __SleepingLinda:receive(n, randomString(12))
end end
function THREAD.hold(n) function THREAD.hold(n)
local function wait() local function wait()
math.randomseed(os.time()) math.randomseed(os.time())
__SleepingLinda:receive(.001,randomString(12)) __SleepingLinda:receive(.001, randomString(12))
end end
repeat wait() until n() repeat
wait()
until n()
end end
local rand = math.random(1,10000000) local rand = math.random(1, 10000000)
-- Step 5 Basic Threads! -- Step 5 Basic Threads!
local threads = {} local threads = {}
local count = 1 local count = 1
local started = false local started = false
local livingThreads = {} local livingThreads = {}
function multi:newSystemThread(name,func,...) function multi:newSystemThread(name, func, ...)
multi.InitSystemThreadErrorHandler() multi.InitSystemThreadErrorHandler()
rand = math.random(1,10000000) rand = math.random(1, 10000000)
local c={} local c = {}
local __self=c local __self = c
c.name=name c.name = name
c.Name = name c.Name = name
c.Id = count c.Id = count
livingThreads[count] = {true,name} livingThreads[count] = {true, name}
local THREAD_ID = count local THREAD_ID = count
count = count + 1 count = count + 1
c.Type="sthread" c.Type = "sthread"
c.creationTime = os.clock() c.creationTime = os.clock()
c.alive = true c.alive = true
local THREAD_NAME=name local THREAD_NAME = name
local function func2(...) local function func2(...)
local multi = require("multi") local multi = require("multi")
_G["THREAD_NAME"]=THREAD_NAME _G["THREAD_NAME"] = THREAD_NAME
_G["THREAD_ID"]=THREAD_ID _G["THREAD_ID"] = THREAD_ID
math.randomseed(rand) math.randomseed(rand)
func(...) func(...)
if _G.__Needs_Multi then if _G.__Needs_Multi then
@ -148,52 +161,61 @@ function multi:newSystemThread(name,func,...)
end end
THREAD.kill() THREAD.kill()
end end
c.thread=lanes.gen("*", func2)(...) c.thread = lanes.gen("*", func2)(...)
function c:kill() function c:kill()
self.thread:cancel() self.thread:cancel()
multi.print("Thread: '"..self.name.."' has been stopped!") multi.print("Thread: '" .. self.name .. "' has been stopped!")
self.alive = false self.alive = false
end end
table.insert(multi.SystemThreads,c) table.insert(multi.SystemThreads, c)
c.OnError = multi:newConnection() c.OnError = multi:newConnection()
GLOBAL["__THREADS__"]=livingThreads GLOBAL["__THREADS__"] = livingThreads
return c return c
end end
multi.OnSystemThreadDied = multi:newConnection() multi.OnSystemThreadDied = multi:newConnection()
function multi.InitSystemThreadErrorHandler() function multi.InitSystemThreadErrorHandler()
if started==true then return end if started == true then
return
end
started = true started = true
multi:newThread("ThreadErrorHandler",function() multi:newThread(
local threads = multi.SystemThreads "ThreadErrorHandler",
while true do function()
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. local threads = multi.SystemThreads
for i=#threads,1,-1 do while true do
local v,err,t=threads[i].thread:join(.001) 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.
if err then for i = #threads, 1, -1 do
if err:find("Thread was killed!") then local v, err, t = threads[i].thread:join(.001)
print(err) if err then
livingThreads[threads[i].Id] = {false,threads[i].Name} if err:find("Thread was killed!") then
threads[i].alive = false print(err)
multi.OnSystemThreadDied:Fire(threads[i].Id) livingThreads[threads[i].Id] = {false, threads[i].Name}
GLOBAL["__THREADS__"]=livingThreads threads[i].alive = false
table.remove(threads,i) multi.OnSystemThreadDied:Fire(threads[i].Id)
elseif err:find("stack traceback") then GLOBAL["__THREADS__"] = livingThreads
print(err) table.remove(threads, i)
threads[i].OnError:Fire(threads[i],err,"Error in systemThread: '"..threads[i].name.."' <"..err..">") elseif err:find("stack traceback") then
threads[i].alive = false print(err)
livingThreads[threads[i].Id] = {false,threads[i].Name} threads[i].OnError:Fire(threads[i], err, "Error in systemThread: '" .. threads[i].name .. "' <" .. err .. ">")
multi.OnSystemThreadDied:Fire(threads[i].Id) threads[i].alive = false
GLOBAL["__THREADS__"]=livingThreads livingThreads[threads[i].Id] = {false, threads[i].Name}
table.remove(threads,i) multi.OnSystemThreadDied:Fire(threads[i].Id)
GLOBAL["__THREADS__"] = livingThreads
table.remove(threads, i)
end
end end
end end
end end
end end
end) )
end end
multi.print("Integrated Lanes!") multi.print("Integrated Lanes!")
multi.integration={} -- for module creators multi.integration = {} -- for module creators
multi.integration.GLOBAL=GLOBAL multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD=THREAD multi.integration.THREAD = THREAD
require("multi.integration.shared") require("multi.integration.shared")
return {init=function() return GLOBAL, THREAD end} return {
init = function()
return GLOBAL, THREAD
end
}

View File

@ -22,15 +22,23 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]] ]]
local multi = require("multi.compat.love2d") local multi = require("multi.compat.love2d")
if multi.integration then -- This allows us to call the lanes manager from supporting modules without a hassel
return {
init = function()
return multi.integration.GLOBAL, multi.integration.THREAD
end
}
end
function multi:canSystemThread() function multi:canSystemThread()
return true return true
end end
function multi:getPlatform() function multi:getPlatform()
return "love2d" return "love2d"
end end
multi.integration={} multi.integration = {}
multi.integration.love2d={} multi.integration.love2d = {}
multi.integration.love2d.ThreadBase=[[ multi.integration.love2d.ThreadBase =
[[
tab={...} tab={...}
__THREADID__=table.remove(tab,1) __THREADID__=table.remove(tab,1)
__THREADNAME__=table.remove(tab,1) __THREADNAME__=table.remove(tab,1)
@ -198,27 +206,30 @@ updater:OnUpdate(__sync__)
func=loadDump([=[INSERT_USER_CODE]=])(unpack(tab)) func=loadDump([=[INSERT_USER_CODE]=])(unpack(tab))
multi:mainloop() multi:mainloop()
]] ]]
GLOBAL={} -- Allow main thread to interact with these objects as well GLOBAL = {} -- Allow main thread to interact with these objects as well
_G.THREAD_ID = 0 _G.THREAD_ID = 0
__proxy__={} __proxy__ = {}
setmetatable(GLOBAL,{ setmetatable(
__index=function(t,k) GLOBAL,
return __proxy__[k] {
end, __index = function(t, k)
__newindex=function(t,k,v) return __proxy__[k]
__proxy__[k]=v end,
for i=1,#__channels__ do __newindex = function(t, k, v)
if type(v)=="userdata" then __proxy__[k] = v
__channels__[i]:push(v) for i = 1, #__channels__ do
else if type(v) == "userdata" then
__channels__[i]:push("SYNC "..type(v).." "..k.." "..resolveData(v)) __channels__[i]:push(v)
else
__channels__[i]:push("SYNC " .. type(v) .. " " .. k .. " " .. resolveData(v))
end
end end
end end
end, }
}) )
THREAD={} -- Allow main thread to interact with these objects as well THREAD = {} -- Allow main thread to interact with these objects as well
multi.integration.love2d.mainChannel=love.thread.getChannel("__MainChan__") multi.integration.love2d.mainChannel = love.thread.getChannel("__MainChan__")
isMainThread=true isMainThread = true
multi.SystemThreads = {} multi.SystemThreads = {}
function THREAD.getName() function THREAD.getName()
return __THREADNAME__ return __THREADNAME__
@ -227,119 +238,125 @@ function THREAD.getID()
return THREAD_ID return THREAD_ID
end end
function ToStr(val, name, skipnewlines, depth) function ToStr(val, name, skipnewlines, depth)
skipnewlines = skipnewlines or false skipnewlines = skipnewlines or false
depth = depth or 0 depth = depth or 0
local tmp = string.rep(" ", depth) local tmp = string.rep(" ", depth)
if name then if name then
if type(name) == "string" then if type(name) == "string" then
tmp = tmp .. "[\""..name.."\"] = " tmp = tmp .. '["' .. name .. '"] = '
else else
tmp = tmp .. "["..(name or "").."] = " tmp = tmp .. "[" .. (name or "") .. "] = "
end end
end end
if type(val) == "table" then if type(val) == "table" then
tmp = tmp .. "{" .. (not skipnewlines and " " or "") tmp = tmp .. "{" .. (not skipnewlines and " " or "")
for k, v in pairs(val) do for k, v in pairs(val) do
tmp = tmp .. ToStr(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and " " or "") tmp = tmp .. ToStr(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and " " or "")
end end
tmp = tmp .. string.rep(" ", depth) .. "}" tmp = tmp .. string.rep(" ", depth) .. "}"
elseif type(val) == "number" then elseif type(val) == "number" then
tmp = tmp .. tostring(val) tmp = tmp .. tostring(val)
elseif type(val) == "string" then elseif type(val) == "string" then
tmp = tmp .. string.format("%q", val) tmp = tmp .. string.format("%q", val)
elseif type(val) == "boolean" then elseif type(val) == "boolean" then
tmp = tmp .. (val and "true" or "false") tmp = tmp .. (val and "true" or "false")
elseif type(val) == "function" then elseif type(val) == "function" then
tmp = tmp .. "loadDump([===["..dump(val).."]===])" tmp = tmp .. "loadDump([===[" .. dump(val) .. "]===])"
else else
tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\"" tmp = tmp .. '"[inserializeable datatype:' .. type(val) .. ']"'
end end
return tmp return tmp
end end
function resolveType(tp,d) function resolveType(tp, d)
if tp=="number" then if tp == "number" then
return tonumber(d) return tonumber(d)
elseif tp=="bool" then elseif tp == "bool" then
return (d=="true") return (d == "true")
elseif tp=="function" then elseif tp == "function" then
return loadDump("[==["..d.."]==]") return loadDump("[==[" .. d .. "]==]")
elseif tp=="table" then elseif tp == "table" then
return loadstring("return "..d)() return loadstring("return " .. d)()
elseif tp=="nil" then elseif tp == "nil" then
return nil return nil
else else
return d return d
end end
end end
function resolveData(v) function resolveData(v)
local data="" local data = ""
if type(v)=="table" then if type(v) == "table" then
return ToStr(v) return ToStr(v)
elseif type(v)=="function" then elseif type(v) == "function" then
return dump(v) return dump(v)
elseif type(v)=="string" or type(v)=="number" or type(v)=="bool" or type(v)=="nil" then elseif type(v) == "string" or type(v) == "number" or type(v) == "bool" or type(v) == "nil" then
return tostring(v) return tostring(v)
end end
return data return data
end end
function loadDump(d) function loadDump(d)
local s={} local s = {}
for p in d:gmatch("(%d-)\\") do for p in d:gmatch("(%d-)\\") do
s[#s+1]=string.char(tonumber(p)) s[#s + 1] = string.char(tonumber(p))
end end
return loadstring(table.concat(s)) return loadstring(table.concat(s))
end end
function dump(func) function dump(func)
local code,t={},string.dump(func) local code, t = {}, string.dump(func)
for i=1,#t do for i = 1, #t do
code[#code+1]=string.byte(t:sub(i,i)).."\\" code[#code + 1] = string.byte(t:sub(i, i)) .. "\\"
end end
return table.concat(code) return table.concat(code)
end end
local function randomString(n) local function randomString(n)
local str = '' 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'} 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 for i = 1, n do
str = str..''..strings[math.random(1,#strings)] str = str .. "" .. strings[math.random(1, #strings)]
end end
return str return str
end end
local count = 1 local count = 1
local livingThreads = {} local livingThreads = {}
function multi:newSystemThread(name,func,...) -- the main method function multi:newSystemThread(name, func, ...) -- the main method
multi.InitSystemThreadErrorHandler() multi.InitSystemThreadErrorHandler()
local c={} local c = {}
c.name=name c.name = name
c.Name = name c.Name = name
c.Type="sthread" c.Type = "sthread"
c.ID=c.name.."<ID|"..randomString(8)..">" c.ID = c.name .. "<ID|" .. randomString(8) .. ">"
c.Id=count c.Id = count
c.creationTime = os.clock() c.creationTime = os.clock()
count = count + 1 count = count + 1
c.thread=love.thread.newThread(multi.integration.love2d.ThreadBase:gsub("INSERT_USER_CODE",dump(func))) c.thread = love.thread.newThread(multi.integration.love2d.ThreadBase:gsub("INSERT_USER_CODE", dump(func)))
livingThreads[count] = {true,name} livingThreads[count] = {true, name}
livingThreads[c.thread] = c livingThreads[c.thread] = c
c.OnError = multi:newConnection() c.OnError = multi:newConnection()
c.thread:start(c.ID,c.name,THREAD_ID,...) c.thread:start(c.ID, c.name, THREAD_ID, ...)
function c:kill() function c:kill()
multi.integration.GLOBAL["__DIEPLZ"..self.ID.."__"]="__DIEPLZ"..self.ID.."__" multi.integration.GLOBAL["__DIEPLZ" .. self.ID .. "__"] = "__DIEPLZ" .. self.ID .. "__"
end end
return c return c
end end
function love.threaderror( thread, errorstr ) function love.threaderror(thread, errorstr)
multi.OnError:Fire(thread,errorstr) multi.OnError:Fire(thread, errorstr)
livingThreads[thread].OnError:Fire(threads[i],err,"Error in systemThread: '"..livingThreads[thread].name.."' <"..errorstr..">") livingThreads[thread].OnError:Fire(
multi.print("Error in systemThread: "..tostring(thread)..": "..errorstr) threads[i],
err,
"Error in systemThread: '" .. livingThreads[thread].name .. "' <" .. errorstr .. ">"
)
multi.print("Error in systemThread: " .. tostring(thread) .. ": " .. errorstr)
end end
local THREAD={} local THREAD = {}
function THREAD.set(name,val) function THREAD.set(name, val)
GLOBAL[name]=val GLOBAL[name] = val
end end
function THREAD.get(name) function THREAD.get(name)
return GLOBAL[name] return GLOBAL[name]
end end
function THREAD.waitFor(name) function THREAD.waitFor(name)
repeat multi:uManager() until GLOBAL[name] repeat
multi:uManager()
until GLOBAL[name]
return GLOBAL[name] return GLOBAL[name]
end end
function THREAD.getCores() function THREAD.getCores()
@ -349,85 +366,95 @@ function THREAD.sleep(n)
love.timer.sleep(n) love.timer.sleep(n)
end end
function THREAD.hold(n) function THREAD.hold(n)
repeat multi:uManager() until n() repeat
multi:uManager()
until n()
end end
__channels__={} __channels__ = {}
multi.integration.GLOBAL=GLOBAL multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD=THREAD multi.integration.THREAD = THREAD
updater=multi:newLoop(function(self) updater =
local data=multi.integration.love2d.mainChannel:pop() multi:newLoop(
while data do function(self)
if type(data)=="string" then local data = multi.integration.love2d.mainChannel:pop()
local cmd,tp,name,d=data:match("(%S-) (%S-) (%S-) (.+)") while data do
if cmd=="SYNC" then if type(data) == "string" then
__proxy__[name]=resolveType(tp,d) local cmd, tp, name, d = data:match("(%S-) (%S-) (%S-) (.+)")
for i=1,#__channels__ do if cmd == "SYNC" then
-- send data to other threads __proxy__[name] = resolveType(tp, d)
if type(v)=="userdata" then for i = 1, #__channels__ do
__channels__[i]:push(v) -- send data to other threads
else if type(v) == "userdata" then
__channels__[i]:push("SYNC "..tp.." "..name.." "..d) __channels__[i]:push(v)
end else
end __channels__[i]:push("SYNC " .. tp .. " " .. name .. " " .. d)
elseif cmd=="NEWTHREAD" then end
__channels__[#__channels__+1]=love.thread.getChannel(tp) end
for k,v in pairs(__proxy__) do -- sync the global with each new thread elseif cmd == "NEWTHREAD" then
if type(v)=="userdata" then __channels__[#__channels__ + 1] = love.thread.getChannel(tp)
__channels__[#__channels__]:push(v) for k, v in pairs(__proxy__) do -- sync the global with each new thread
else if type(v) == "userdata" then
__channels__[#__channels__]:push("SYNC "..type(v).." "..k.." "..resolveData(v)) __channels__[#__channels__]:push(v)
else
__channels__[#__channels__]:push("SYNC " .. type(v) .. " " .. k .. " " .. resolveData(v))
end
end end
end end
else
__proxy__[name] = data
end end
else data = multi.integration.love2d.mainChannel:pop()
__proxy__[name]=data
end end
data=multi.integration.love2d.mainChannel:pop()
end end
end) )
multi.OnSystemThreadDied = multi:newConnection() multi.OnSystemThreadDied = multi:newConnection()
local started = false local started = false
function multi.InitSystemThreadErrorHandler() function multi.InitSystemThreadErrorHandler()
if started==true then return end if started == true then
return
end
started = true started = true
multi:newThread("ThreadErrorHandler",function() multi:newThread(
local threads = multi.SystemThreads "ThreadErrorHandler",
while true do function()
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. local threads = multi.SystemThreads
for i=#threads,1,-1 do while true do
local v,err,t=threads[i].thread:join(.001) 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.
if err then for i = #threads, 1, -1 do
if err:find("Thread was killed!") then local v, err, t = threads[i].thread:join(.001)
livingThreads[threads[i].Id] = {false,threads[i].Name} if err then
multi.OnSystemThreadDied:Fire(threads[i].Id) if err:find("Thread was killed!") then
GLOBAL["__THREADS__"]=livingThreads livingThreads[threads[i].Id] = {false, threads[i].Name}
table.remove(threads,i) multi.OnSystemThreadDied:Fire(threads[i].Id)
else GLOBAL["__THREADS__"] = livingThreads
threads[i].OnError:Fire(threads[i],err,"Error in systemThread: '"..threads[i].name.."' <"..err..">") table.remove(threads, i)
livingThreads[threads[i].Id] = {false,threads[i].Name} else
multi.OnSystemThreadDied:Fire(threads[i].Id) threads[i].OnError:Fire(threads[i], err, "Error in systemThread: '" .. threads[i].name .. "' <" .. err .. ">")
GLOBAL["__THREADS__"]=livingThreads livingThreads[threads[i].Id] = {false, threads[i].Name}
table.remove(threads,i) multi.OnSystemThreadDied:Fire(threads[i].Id)
GLOBAL["__THREADS__"] = livingThreads
table.remove(threads, i)
end
end end
end end
end end
end end
end) )
end end
require("multi.integration.shared") require("multi.integration.shared")
multi.print("Integrated Love2d!") multi.print("Integrated Love2d!")
return { return {
init=function(t) init = function(t)
if t then if t then
if t.threadNamespace then if t.threadNamespace then
multi.integration.THREADNAME=t.threadNamespace multi.integration.THREADNAME = t.threadNamespace
multi.integration.love2d.ThreadBase:gsub("sThread",t.threadNamespace) multi.integration.love2d.ThreadBase:gsub("sThread", t.threadNamespace)
end end
if t.globalNamespace then if t.globalNamespace then
multi.integration.GLOBALNAME=t.globalNamespace multi.integration.GLOBALNAME = t.globalNamespace
multi.integration.love2d.ThreadBase:gsub("GLOBAL",t.globalNamespace) multi.integration.love2d.ThreadBase:gsub("GLOBAL", t.globalNamespace)
end end
end end
return GLOBAL,THREAD return GLOBAL, THREAD
end end
} }

View File

@ -21,68 +21,133 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]] ]]
-- I DEMAND USAGE FOR LUVIT -- I DEMAND USAGE FOR LUVIT
-- Cannot use discordia without my multitasking library (Which I love more that the luvit platform... then again i'm partial :P) -- Cannot use discordia without my multitasking library (Which I love more that the luvit platform... then again i'm partial :P)
package.path="?/init.lua;?.lua;"..package.path package.path = "?/init.lua;?.lua;" .. package.path
local function _INIT(luvitThread,timer) local function _INIT(luvitThread, timer)
-- lots of this stuff should be able to stay the same -- lots of this stuff should be able to stay the same
function os.getOS() function os.getOS()
if package.config:sub(1,1)=='\\' then if package.config:sub(1, 1) == "\\" then
return 'windows' return "windows"
else else
return 'unix' return "unix"
end end
end end
-- Step 1 get setup threads on luvit... Sigh how do i even... -- Step 1 get setup threads on luvit... Sigh how do i even...
local multi, thread = require("multi") local multi, thread = require("multi").init()
isMainThread=true isMainThread = true
function multi:canSystemThread() function multi:canSystemThread()
return true return true
end end
function multi:getPlatform() function multi:getPlatform()
return "luvit" return "luvit"
end end
local multi=multi local multi = multi
-- Step 2 set up the Global table... is this possible? -- Step 2 set up the Global table... is this possible?
local GLOBAL={} local GLOBAL = {}
setmetatable(GLOBAL,{ setmetatable(
__index=function(t,k) GLOBAL,
--print("No Global table when using luvit integration!") {
return nil __index = function(t, k)
end, --print("No Global table when using luvit integration!")
__newindex=function(t,k,v) return nil
--print("No Global table when using luvit integration!") end,
end, __newindex = function(t, k, v)
}) --print("No Global table when using luvit integration!")
local THREAD={} end
function THREAD.set(name,val) }
)
local THREAD = {}
function THREAD.set(name, val)
--print("No Global table when using luvit integration!") --print("No Global table when using luvit integration!")
end end
function THREAD.get(name) function THREAD.get(name)
--print("No Global table when using luvit integration!") --print("No Global table when using luvit integration!")
end end
local function randomString(n) local function randomString(n)
local str = '' 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'} local strings = {
for i=1,n do "a",
str = str..''..strings[math.random(1,#strings)] "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 end
return str return str
end end
function THREAD.waitFor(name) function THREAD.waitFor(name)
--print("No Global table when using luvit integration!") --print("No Global table when using luvit integration!")
end end
function THREAD.testFor(name,val,sym) function THREAD.testFor(name, val, sym)
--print("No Global table when using luvit integration!") --print("No Global table when using luvit integration!")
end end
function THREAD.getCores() function THREAD.getCores()
return THREAD.__CORES return THREAD.__CORES
end end
if os.getOS()=="windows" then if os.getOS() == "windows" then
THREAD.__CORES=tonumber(os.getenv("NUMBER_OF_PROCESSORS")) THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS"))
else else
THREAD.__CORES=tonumber(io.popen("nproc --all"):read("*n")) THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n"))
end end
function THREAD.kill() -- trigger the thread destruction function THREAD.kill() -- trigger the thread destruction
error("Thread was Killed!") error("Thread was Killed!")
@ -95,34 +160,41 @@ local function _INIT(luvitThread,timer)
--print("No Global table when using luvit integration!") --print("No Global table when using luvit integration!")
end end
-- Step 5 Basic Threads! -- Step 5 Basic Threads!
local function entry(path,name,func,...) local function entry(path, name, func, ...)
local timer = require'timer' local timer = require "timer"
local luvitThread = require'thread' local luvitThread = require "thread"
package.path=path package.path = path
loadstring(func)(...) loadstring(func)(...)
end end
function multi:newSystemThread(name,func,...) function multi:newSystemThread(name, func, ...)
local c={} local c = {}
local __self=c local __self = c
c.name=name c.name = name
c.Type="sthread" c.Type = "sthread"
c.thread={} c.thread = {}
c.func=string.dump(func) c.func = string.dump(func)
function c:kill() function c:kill()
-- print("No Global table when using luvit integration!") -- print("No Global table when using luvit integration!")
end end
luvitThread.start(entry,package.path,name,c.func,...) luvitThread.start(entry, package.path, name, c.func, ...)
return c return c
end end
multi.print("Integrated Luvit!") multi.print("Integrated Luvit!")
multi.integration={} -- for module creators multi.integration = {} -- for module creators
multi.integration.GLOBAL=GLOBAL multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD=THREAD multi.integration.THREAD = THREAD
require("multi.integration.shared") require("multi.integration.shared")
-- Start the main mainloop... This allows you to process your multi objects, but the engine on the main thread will be limited to .001 or 1 millisecond sigh... -- Start the main mainloop... This allows you to process your multi objects, but the engine on the main thread will be limited to .001 or 1 millisecond sigh...
local interval = timer.setInterval(1, function () local interval =
multi:uManager() timer.setInterval(
end) 1,
function()
multi:uManager()
end
)
return multi return multi
end end
return {init=function(threadHandle,timerHandle) local multi = _INIT(threadHandle,timerHandle) return GLOBAL, THREAD end} return {init = function(threadHandle, timerHandle)
local multi = _INIT(threadHandle, timerHandle)
return GLOBAL, THREAD
end}

View File

@ -21,33 +21,33 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]] ]]
local multi, thread = require("multi") local multi, thread = require("multi").init()
local net = require("net") local net = require("net")
local bin = require("bin") local bin = require("bin")
bin.setBitsInterface(infinabits) -- the bits interface does not work so well, another bug to fix 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 -- Commands that the master and node will respect, max of 256 commands
local CMD_ERROR = 0x00 local CMD_ERROR = 0x00
local CMD_PING = 0x01 local CMD_PING = 0x01
local CMD_PONG = 0x02 local CMD_PONG = 0x02
local CMD_QUEUE = 0x03 local CMD_QUEUE = 0x03
local CMD_TASK = 0x04 local CMD_TASK = 0x04
local CMD_INITNODE = 0x05 local CMD_INITNODE = 0x05
local CMD_INITMASTER = 0x06 local CMD_INITMASTER = 0x06
local CMD_GLOBAL = 0x07 local CMD_GLOBAL = 0x07
local CMD_LOAD = 0x08 local CMD_LOAD = 0x08
local CMD_CALL = 0x09 local CMD_CALL = 0x09
local CMD_REG = 0x0A local CMD_REG = 0x0A
local CMD_CONSOLE = 0x0B local CMD_CONSOLE = 0x0B
local char = string.char local char = string.char
local byte = string.byte local byte = string.byte
-- Process to hold all of the networkManager's muilt objects -- Process to hold all of the networkManager's muilt objects
-- Helper for piecing commands -- Helper for piecing commands
local function pieceCommand(cmd,...) local function pieceCommand(cmd, ...)
local tab = {...} local tab = {...}
table.insert(tab,1,cmd) table.insert(tab, 1, cmd)
return table.concat(tab) return table.concat(tab)
end end
@ -56,17 +56,17 @@ local Queue = {}
Queue.__index = Queue Queue.__index = Queue
function Queue:newQueue() function Queue:newQueue()
local c = {} local c = {}
setmetatable(c,self) setmetatable(c, self)
return c return c
end end
function Queue:push(data) function Queue:push(data)
table.insert(self,data) table.insert(self, data)
end end
function Queue:raw_push(data) -- Internal usage only function Queue:raw_push(data) -- Internal usage only
table.insert(self,data) table.insert(self, data)
end end
function Queue:pop() function Queue:pop()
return table.remove(self,1) return table.remove(self, 1)
end end
function Queue:peek() function Queue:peek()
return self[1] return self[1]
@ -79,39 +79,39 @@ multi.OnGUpdate = multi:newConnection()
local function packData(data) local function packData(data)
-- returns the data that was sterilized -- returns the data that was sterilized
local dat = bin.new() local dat = bin.new()
dat:addBlock(#type(data),1) dat:addBlock(#type(data), 1)
dat:addBlock(type(data)) -- The type is first dat:addBlock(type(data)) -- The type is first
if type(data)=="table" then if type(data) == "table" then
dat:addBlock(data,nil,"t") dat:addBlock(data, nil, "t")
elseif type(data) == "userdata" then elseif type(data) == "userdata" then
error("Cannot sterilize userdata!") error("Cannot sterilize userdata!")
elseif type(data) == "number" then elseif type(data) == "number" then
dat:addBlock(data,nil,"d") dat:addBlock(data, nil, "d")
elseif type(data) == "string" then elseif type(data) == "string" then
dat:addBlock(#data,4) dat:addBlock(#data, 4)
dat:addBlock(data,nil,"s") dat:addBlock(data, nil, "s")
elseif type(data) == "boolean" then elseif type(data) == "boolean" then
dat:addBlock(data,1) dat:addBlock(data, 1)
elseif type(data) == "function" then elseif type(data) == "function" then
dat:addBlock(data,nil,"f") dat:addBlock(data, nil, "f")
end end
return dat.data return dat.data
end end
local function resolveData(data) local function resolveData(data)
-- returns the data that was sterilized -- returns the data that was sterilized
local dat = bin.new(data) local dat = bin.new(data)
local tlen = dat:getBlock("n",1) local tlen = dat:getBlock("n", 1)
local tp = dat:getBlock("s",tlen) local tp = dat:getBlock("s", tlen)
if tp=="table" then if tp == "table" then
return dat:getBlock("t") return dat:getBlock("t")
elseif tp=="number" then elseif tp == "number" then
return dat:getBlock("d") return dat:getBlock("d")
elseif tp=="string" then elseif tp == "string" then
local num = dat:getBlock("n",4) local num = dat:getBlock("n", 4)
return dat:getBlock("s",num) return dat:getBlock("s", num)
elseif tp=="boolean" then elseif tp == "boolean" then
return dat:getBlock("b",1) return dat:getBlock("b", 1)
elseif tp=="function" then elseif tp == "function" then
return dat:getBlock("f") return dat:getBlock("f")
end end
end end
@ -119,16 +119,19 @@ end
-- internal global system -- internal global system
local GLOBAL = {} local GLOBAL = {}
local PROXY = {} local PROXY = {}
setmetatable(GLOBAL,{ setmetatable(
__index = function(t,k) GLOBAL,
return PROXY[k] {
end, __index = function(t, k)
__newindex = function(t,k,v) return PROXY[k]
local v = v end,
PROXY[k] = v __newindex = function(t, k, v)
multi.OnGUpdate:Fire(k,packData(v)) local v = v
end PROXY[k] = v
}) multi.OnGUpdate:Fire(k, packData(v))
end
}
)
-- In case you are unable to use broadcasting this can be used to help connect to nodes -- In case you are unable to use broadcasting this can be used to help connect to nodes
function multi:nodeManager(port) function multi:nodeManager(port)
@ -140,39 +143,48 @@ function multi:nodeManager(port)
server.timeouts = {} server.timeouts = {}
server.OnNodeAdded = multi:newConnection() server.OnNodeAdded = multi:newConnection()
server.OnNodeRemoved = multi:newConnection() server.OnNodeRemoved = multi:newConnection()
server.OnDataRecieved(function(server,data,cid,ip,port) server.OnDataRecieved(
local cmd = data:sub(1,1) function(server, data, cid, ip, port)
if cmd == "R" then local cmd = data:sub(1, 1)
multi:newThread("Node Client Manager",function(loop) if cmd == "R" then
while true do multi:newThread(
if server.timeouts[cid]==true then "Node Client Manager",
server.OnNodeRemoved:Fire(server.nodes[cid]) function(loop)
server.nodes[cid] = nil while true do
server.timeouts[cid] = nil if server.timeouts[cid] == true then
thread.kill() server.OnNodeRemoved:Fire(server.nodes[cid])
else server.nodes[cid] = nil
server.timeouts[cid] = true server.timeouts[cid] = nil
server:send(cid,"ping") thread.kill()
else
server.timeouts[cid] = true
server:send(cid, "ping")
end
thread.sleep(1)
end
end end
thread.sleep(1) )
server.nodes[cid] = data:sub(2, -1)
server.OnNodeAdded:Fire(server.nodes[cid])
elseif cmd == "G" then
server.OnNodeAdded(
function(node)
server:send(cid, node)
end
)
server.OnNodeRemoved(
function(node)
server:send(cid, "R" .. node:match("(.-)|"))
end
)
for i, v in pairs(server.nodes) do
server:send(cid, v)
end end
end) elseif cmd == "P" then
server.nodes[cid]=data:sub(2,-1) server.timeouts[cid] = nil
server.OnNodeAdded:Fire(server.nodes[cid])
elseif cmd == "G" then
server.OnNodeAdded(function(node)
server:send(cid,node)
end)
server.OnNodeRemoved(function(node)
server:send(cid,"R"..node:match("(.-)|"))
end)
for i,v in pairs(server.nodes) do
server:send(cid,v)
end end
elseif cmd == "P" then
server.timeouts[cid] = nil
end end
end) )
end end
-- The main driving force of the network manager: Nodes -- The main driving force of the network manager: Nodes
function multi:newNode(settings) function multi:newNode(settings)
@ -183,60 +195,69 @@ function multi:newNode(settings)
local name = settings.name or multi.randomString(8) local name = settings.name or multi.randomString(8)
local node = {} local node = {}
node.name = name node.name = name
multi.OnError(function(i,error) multi.OnError(
node.OnError:Fire(node,error,node.server) function(i, error)
end) node.OnError:Fire(node, error, node.server)
end
)
node.server = net:newUDPServer(0) -- hosts the node using the default port node.server = net:newUDPServer(0) -- hosts the node using the default port
_, node.port = node.server.udp:getsockname() _, node.port = node.server.udp:getsockname()
node.connections = net.ClientCache node.connections = net.ClientCache
node.queue = Queue:newQueue() node.queue = Queue:newQueue()
node.functions = bin.stream("RegisteredFunctions.dat",false) node.functions = bin.stream("RegisteredFunctions.dat", false)
node.hasFuncs = {} node.hasFuncs = {}
node.OnError = multi:newConnection() node.OnError = multi:newConnection()
node.OnError(function(node,err,master) node.OnError(
multi.print("ERROR",err,node.name) function(node, err, master)
local temp = bin.new() multi.print("ERROR", err, node.name)
temp:addBlock(#node.name,2) local temp = bin.new()
temp:addBlock(node.name) temp:addBlock(#node.name, 2)
temp:addBlock(#err,2) temp:addBlock(node.name)
temp:addBlock(err) temp:addBlock(#err, 2)
for i,v in pairs(node.connections) do temp:addBlock(err)
multi.print(i) for i, v in pairs(node.connections) do
v[1]:send(v[2],char(CMD_ERROR)..temp.data,v[3]) multi.print(i)
v[1]:send(v[2], char(CMD_ERROR) .. temp.data, v[3])
end
end end
end) )
if settings.managerDetails then if settings.managerDetails then
local c = net:newTCPClient(settings.managerDetails[1],settings.managerDetails[2]) local c = net:newTCPClient(settings.managerDetails[1], settings.managerDetails[2])
if not c then if not c then
multi.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 else
c.OnDataRecieved(function(self,data) c.OnDataRecieved(
if data == "ping" then function(self, data)
self:send("P") if data == "ping" then
self:send("P")
end
end end
end) )
c:send("RNODE_"..name.."|"..net.getLocalIP().."|"..node.port) c:send("RNODE_" .. name .. "|" .. net.getLocalIP() .. "|" .. node.port)
end end
end end
if not settings.preload then if not settings.preload then
if node.functions:getSize()~=0 then if node.functions:getSize() ~= 0 then
multi.print("We have function(s) to preload!") multi.print("We have function(s) to preload!")
local len = node.functions:getBlock("n",1) local len = node.functions:getBlock("n", 1)
local name,func local name, func
while len do while len do
name = node.functions:getBlock("s",len) name = node.functions:getBlock("s", len)
len = node.functions:getBlock("n",2) len = node.functions:getBlock("n", 2)
func = node.functions:getBlock("s",len) func = node.functions:getBlock("s", len)
len = node.functions:read(1) len = node.functions:read(1)
_G[name]=resolveData(func) _G[name] = resolveData(func)
node.hasFuncs[name]=true node.hasFuncs[name] = true
if not len then break end if not len then
break
end
len = byte(len) len = byte(len)
end end
end end
end end
function node:pushTo(name,data) function node:pushTo(name, data)
node:sendTo(name,char(CMD_QUEUE)..packData(data)) node:sendTo(name, char(CMD_QUEUE) .. packData(data))
end end
function node:peek() function node:peek()
return node.queue:peek() return node.queue:peek()
@ -248,89 +269,100 @@ function multi:newNode(settings)
local c = {} local c = {}
local conn = node.connections local conn = node.connections
function c.print(...) function c.print(...)
local data = char(CMD_CONSOLE)..packData({...}) local data = char(CMD_CONSOLE) .. packData({...})
for i,v in pairs(conn) do for i, v in pairs(conn) do
--print(i) --print(i)
v[1]:send(v[2],data,v[3]) v[1]:send(v[2], data, v[3])
end end
end end
-- function c:printTo() -- function c:printTo()
-- end -- end
return c return c
end end
node.loadRate=1 node.loadRate = 1
-- Lets tell the network we are alive! -- Lets tell the network we are alive!
node.server.OnDataRecieved(function(server,data,cid,ip,port) node.server.OnDataRecieved(
local cmd = byte(data:sub(1,1)) -- the first byte is the command function(server, data, cid, ip, port)
local dat = data:sub(2,-1) -- the data that you want to read local cmd = byte(data:sub(1, 1)) -- the first byte is the command
if cmd == CMD_PING then local dat = data:sub(2, -1) -- the data that you want to read
server:send(ip,char(CMD_PONG),port) if cmd == CMD_PING then
elseif cmd == CMD_QUEUE then server:send(ip, char(CMD_PONG), port)
node.queue:push(resolveData(dat)) elseif cmd == CMD_QUEUE then
elseif cmd == CMD_REG then node.queue:push(resolveData(dat))
if not settings.allowRemoteRegistering then elseif cmd == CMD_REG then
multi.print(ip..": has attempted to register a function when it is currently not allowed!") if not settings.allowRemoteRegistering then
return 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
multi.print("Function already preloaded onto the node!")
return
end
len = temp:getBlock("n", 2)
local func = temp:getBlock("s", len)
_G[name] = resolveData(func)
node.functions:addBlock(dat)
elseif cmd == CMD_CALL then
local temp = bin.new(dat)
local len = temp:getBlock("n", 1)
local name = temp:getBlock("s", len)
len = temp:getBlock("n", 4)
local args = temp:getBlock("s", len)
_G[name](unpack(resolveData(args)))
elseif cmd == CMD_TASK then
local holder = bin.new(dat)
local len = holder:getBlock("n", 4)
local args = holder:getBlock("s", len)
local len2 = holder:getBlock("n", 4)
local func = holder:getBlock("s", len2)
args = resolveData(args)
func = resolveData(func)
status, err = pcall(func, node, unpack(args))
if not status then
node.OnError:Fire(node, err, server)
end
elseif cmd == CMD_INITNODE then
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
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)
end
)
-- set this up
multi:newTLoop(
function()
server:send(ip, char(CMD_LOAD) .. node.name .. "|" .. multi:getLoad(), port)
end,
node.loadRate
)
server:send(ip, char(CMD_LOAD) .. node.name .. "|" .. multi:getLoad(), port)
server:send(ip, char(CMD_INITNODE) .. node.name, port)
elseif cmd == CMD_GLOBAL then
local k, v = dat:match("(.-)|(.+)")
PROXY[k] = resolveData(v)
end end
local temp = bin.new(dat)
local len = temp:getBlock("n",1)
local name = temp:getBlock("s",len)
if node.hasFuncs[name] then
multi.print("Function already preloaded onto the node!")
return
end
len = temp:getBlock("n",2)
local func = temp:getBlock("s",len)
_G[name]=resolveData(func)
node.functions:addBlock(dat)
elseif cmd == CMD_CALL then
local temp = bin.new(dat)
local len = temp:getBlock("n",1)
local name = temp:getBlock("s",len)
len = temp:getBlock("n",4)
local args = temp:getBlock("s",len)
_G[name](unpack(resolveData(args)))
elseif cmd == CMD_TASK then
local holder = bin.new(dat)
local len = holder:getBlock("n",4)
local args = holder:getBlock("s",len)
local len2 = holder:getBlock("n",4)
local func = holder:getBlock("s",len2)
args = resolveData(args)
func = resolveData(func)
status, err = pcall(func,node,unpack(args))
if not status then
node.OnError:Fire(node,err,server)
end
elseif cmd == CMD_INITNODE then
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
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)
end)-- set this up
multi:newTLoop(function()
server:send(ip,char(CMD_LOAD)..node.name.."|"..multi:getLoad(),port)
end,node.loadRate)
server:send(ip,char(CMD_LOAD)..node.name.."|"..multi:getLoad(),port)
server:send(ip,char(CMD_INITNODE)..node.name,port)
elseif cmd == CMD_GLOBAL then
local k,v = dat:match("(.-)|(.+)")
PROXY[k]=resolveData(v)
end end
end) )
function node:sendTo(name,data) function node:sendTo(name, data)
local conn = node.connections[name] local conn = node.connections[name]
conn[1]:send(conn[2],data,conn[3]) conn[1]:send(conn[2], data, conn[3])
end end
if not settings.noBroadCast then if not settings.noBroadCast then
node.server:broadcast("NODE_"..name) node.server:broadcast("NODE_" .. name)
end end
return node return node
end end
@ -350,69 +382,76 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
master.connections = net.ClientCache -- Link to the client cache that is created on the net interface master.connections = net.ClientCache -- Link to the client cache that is created on the net interface
master.loads = {} master.loads = {}
master.timeouts = {} master.timeouts = {}
master.trigger = multi:newFunction(function(self,node) master.trigger =
master.OnFirstNodeConnected:Fire(node) multi:newFunction(
self:Pause() function(self, node)
end) master.OnFirstNodeConnected:Fire(node)
self:Pause()
end
)
if settings.managerDetails then if settings.managerDetails then
local client = net:newTCPClient(settings.managerDetails[1],settings.managerDetails[2]) local client = net:newTCPClient(settings.managerDetails[1], settings.managerDetails[2])
if not client then if not client then
multi.print("Warning: 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 else
client.OnDataRecieved(function(client,data) client.OnDataRecieved(
local cmd = data:sub(1,1) function(client, data)
if cmd == "N" then local cmd = data:sub(1, 1)
print(data) if cmd == "N" then
local name,ip,port = data:match("(.-)|(.-)|(.+)") print(data)
local c = net:newUDPClient(ip,port) local name, ip, port = data:match("(.-)|(.-)|(.+)")
net.OnCastedClientInfo:Fire(c,name,ip,port)master.connections[name]=c local c = net:newUDPClient(ip, port)
elseif cmd == "R" then net.OnCastedClientInfo:Fire(c, name, ip, port)
local name = data:sub(2,-1) master.connections[name] = c
master.connections[name]=nil elseif cmd == "R" then
local name = data:sub(2, -1)
master.connections[name] = nil
end
end end
end) )
client:send("G") -- init your connection as a master client:send("G") -- init your connection as a master
end end
end end
function master:doToAll(func) function master:doToAll(func)
for i,v in pairs(master.connections) do for i, v in pairs(master.connections) do
func(i,v) func(i, v)
end end
end end
function master:register(name,node,func) function master:register(name, node, func)
if not node then if not node then
error("You must specify a node to execute a command on!") error("You must specify a node to execute a command on!")
end end
local temp = bin.new() local temp = bin.new()
local fData = packData(func) local fData = packData(func)
temp:addBlock(CMD_REG,1) temp:addBlock(CMD_REG, 1)
temp:addBlock(#name,1) temp:addBlock(#name, 1)
temp:addBlock(name,#name) temp:addBlock(name, #name)
temp:addBlock(#fData,2) temp:addBlock(#fData, 2)
temp:addBlock(fData,#fData) temp:addBlock(fData, #fData)
master:sendTo(node,temp.data) master:sendTo(node, temp.data)
end end
function master:execute(name,node,...) function master:execute(name, node, ...)
if not node then if not node then
error("You must specify a node to execute a command on!") error("You must specify a node to execute a command on!")
end end
if not name then if not name then
error("You must specify a function name to call on the node!") error("You must specify a function name to call on the node!")
end end
local args = packData{...} local args = packData {...}
local name = name local name = name
local node = node local node = node
local temp, len, data local temp, len, data
temp = bin.new() temp = bin.new()
temp:addBlock(CMD_CALL,1) temp:addBlock(CMD_CALL, 1)
temp:addBlock(#name,1) temp:addBlock(#name, 1)
temp:addBlock(name,#name) temp:addBlock(name, #name)
temp:addBlock(#args,4) temp:addBlock(#args, 4)
temp:addBlock(args,#args) temp:addBlock(args, #args)
master:sendTo(node,temp.data) master:sendTo(node, temp.data)
end end
function master:pushTo(name,data) function master:pushTo(name, data)
master:sendTo(name,char(CMD_QUEUE)..packData(data)) master:sendTo(name, char(CMD_QUEUE) .. packData(data))
end end
function master:peek() function master:peek()
return self.queue:peek() return self.queue:peek()
@ -420,52 +459,68 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
function master:pop() function master:pop()
return self.queue:pop() return self.queue:pop()
end end
function master:newNetworkThread(tname,func,name,...) -- If name specified then it will be sent to the specified node! Otherwise the least worked node will get the job function master:newNetworkThread(tname, func, name, ...) -- If name specified then it will be sent to the specified node! Otherwise the least worked node will get the job
local fData = packData(func) local fData = packData(func)
local tab = {...} local tab = {...}
local aData = "" local aData = ""
if #tab~=o then if #tab ~= o then
aData = (packData{...}) aData = (packData {...})
else else
aData = (packData{"NO","ARGS"}) aData = (packData {"NO", "ARGS"})
end end
local temp = bin.new() local temp = bin.new()
temp:addBlock(#aData,4) temp:addBlock(#aData, 4)
local len = temp.data local len = temp.data
local temp2 = bin.new() local temp2 = bin.new()
temp2:addBlock(#fData,4) temp2:addBlock(#fData, 4)
local len2 = temp2.data local len2 = temp2.data
if not name then if not name then
local name = self:getFreeNode() local name = self:getFreeNode()
if not name then if not name then
name = self:getRandomNode() name = self:getRandomNode()
end end
if name==nil then if name == nil then
multi:newEvent(function() return name~=nil end):OnEvent(function(evnt) multi:newEvent(
self:sendTo(name,char(CMD_TASK)..len..aData..len2..fData) function()
evnt:Destroy() return name ~= nil
end):SetName("DelayedSendTask"):SetName("DelayedSendTask"):SetTime(8):OnTimedOut(function(self) end
self:Destroy() ):OnEvent(
end) 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 else
self:sendTo(name,char(CMD_TASK)..len..aData..len2..fData) self:sendTo(name, char(CMD_TASK) .. len .. aData .. len2 .. fData)
end end
else else
local name = "NODE_"..name local name = "NODE_" .. name
self:sendTo(name,char(CMD_TASK)..len..aData..len2..fData) self:sendTo(name, char(CMD_TASK) .. len .. aData .. len2 .. fData)
end end
end end
function master:sendTo(name,data) function master:sendTo(name, data)
if name:sub(1,5)~="NODE_" then if name:sub(1, 5) ~= "NODE_" then
name = "NODE_"..name name = "NODE_" .. name
end end
if self.connections[name]==nil then if self.connections[name] == nil then
multi:newEvent(function() return self.connections[name]~=nil end):OnEvent(function(evnt) multi:newEvent(
self.connections[name]:send(data) function()
evnt:Destroy() return self.connections[name] ~= nil
end):SetName("DelayedSendTask"):SetTime(8):OnTimedOut(function(self) end
self:Destroy() ):OnEvent(
end) function(evnt)
self.connections[name]:send(data)
evnt:Destroy()
end
):SetName("DelayedSendTask"):SetTime(8):OnTimedOut(
function(self)
self:Destroy()
end
)
else else
self.connections[name]:send(data) self.connections[name]:send(data)
end end
@ -474,8 +529,8 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
local count = 0 local count = 0
local min = math.huge local min = math.huge
local refO local refO
for i,v in pairs(master.loads) do for i, v in pairs(master.loads) do
if v<min then if v < min then
min = v min = v
refO = i refO = i
end end
@ -484,66 +539,77 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
end end
function master:getRandomNode() function master:getRandomNode()
local list = {} local list = {}
for i,v in pairs(master.connections) do for i, v in pairs(master.connections) do
list[#list+1]=i:sub(6,-1) list[#list + 1] = i:sub(6, -1)
end end
return list[math.random(1,#list)] return list[math.random(1, #list)]
end end
net.OnCastedClientInfo(function(client,name,ip,port) net.OnCastedClientInfo(
multi.OnGUpdate(function(k,v) function(client, name, ip, port)
client:send(table.concat{char(CMD_GLOBAL),k,"|",v}) multi.OnGUpdate(
end) function(k, v)
local nodename client:send(table.concat {char(CMD_GLOBAL), k, "|", v})
for i,v in pairs(master.connections) do
nodename = i
end
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: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
end)
end
client.name = name
client.OnDataRecieved(function(client,data)
local cmd = byte(data:sub(1,1)) -- the first byte is the command
local dat = data:sub(2,-1) -- the data that you want to read
master.trigger(nodename)
if cmd == CMD_ERROR then
local temp = bin.new(dat)
local len = temp:getBlock("n",2)
local node = temp:getBlock("s",len)
len = temp:getBlock("n",2)
local err = temp:getBlock("s",len)
master.OnError:Fire(name,err)
elseif cmd == CMD_CONSOLE then
print(unpack(resolveData(dat)))
elseif cmd == CMD_PONG then
master.timeouts[client.name] = nil
elseif cmd == CMD_INITNODE then
master.OnNodeConnected:Fire(dat)
elseif cmd == CMD_QUEUE then
master.queue:push(resolveData(dat))
elseif cmd == CMD_GLOBAL then
local k,v = dat:match("(.-)|(.+)")
PROXY[k]=resolveData(v)
elseif cmd == CMD_LOAD then
local name,load = dat:match("(.-)|(.+)")
master.loads[name]=tonumber(load)
end end
end) )
end) local nodename
end) for i, v in pairs(master.connections) do
nodename = i
end
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: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
end
)
end
client.name = name
client.OnDataRecieved(
function(client, data)
local cmd = byte(data:sub(1, 1)) -- the first byte is the command
local dat = data:sub(2, -1) -- the data that you want to read
master.trigger(nodename)
if cmd == CMD_ERROR then
local temp = bin.new(dat)
local len = temp:getBlock("n", 2)
local node = temp:getBlock("s", len)
len = temp:getBlock("n", 2)
local err = temp:getBlock("s", len)
master.OnError:Fire(name, err)
elseif cmd == CMD_CONSOLE then
print(unpack(resolveData(dat)))
elseif cmd == CMD_PONG then
master.timeouts[client.name] = nil
elseif cmd == CMD_INITNODE then
master.OnNodeConnected:Fire(dat)
elseif cmd == CMD_QUEUE then
master.queue:push(resolveData(dat))
elseif cmd == CMD_GLOBAL then
local k, v = dat:match("(.-)|(.+)")
PROXY[k] = resolveData(v)
elseif cmd == CMD_LOAD then
local name, load = dat:match("(.-)|(.+)")
master.loads[name] = tonumber(load)
end
end
)
end
)
end
)
if not settings.noBroadCast then if not settings.noBroadCast then
net:newCastedClients("NODE_(.+)") -- Searches for nodes and connects to them, the master.clients table will contain them by name net:newCastedClients("NODE_(.+)") -- Searches for nodes and connects to them, the master.clients table will contain them by name
end end
@ -551,6 +617,8 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
end end
-- The init function that gets returned -- The init function that gets returned
multi.print("Integrated Network Parallelism") multi.print("Integrated Network Parallelism")
return {init = function() return {
return GLOBAL init = function()
end} return GLOBAL
end
}

View File

@ -21,23 +21,23 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]] ]]
multi, thread = require("multi") local multi, thread = require("multi").init()
function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a channel on both ends function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a channel on both ends
local c={} -- where we will store our object local c = {} -- where we will store our object
c.name=name -- set the name this is important for the love2d side c.name = name -- set the name this is important for the love2d side
if love then -- check love if love then -- check love
if love.thread then -- make sure we can use the threading module if love.thread then -- make sure we can use the threading module
function c:init() -- create an init function so we can mimic on both love2d and lanes function c:init() -- create an init function so we can mimic on both love2d and lanes
self.chan=love.thread.getChannel(self.name) -- create channel by the name self.name self.chan = love.thread.getChannel(self.name) -- create channel by the name self.name
function self:push(v) -- push to the channel function self:push(v) -- push to the channel
local tab local tab
if type(v)=="table" then if type(v) == "table" then
tab = {} tab = {}
for i,c in pairs(v) do for i, c in pairs(v) do
if type(c)=="function" then if type(c) == "function" then
tab[i]="\1"..string.dump(c) tab[i] = "\1" .. string.dump(c)
else else
tab[i]=c tab[i] = c
end end
end end
self.chan:push(tab) self.chan:push(tab)
@ -46,19 +46,21 @@ function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a chann
end end
end end
function self:pop() -- pop from the channel function self:pop() -- pop from the channel
local v=self.chan:pop() local v = self.chan:pop()
if not v then return end if not v then
if type(v)=="table" then return
end
if type(v) == "table" then
tab = {} tab = {}
for i,c in pairs(v) do for i, c in pairs(v) do
if type(c)=="string" then if type(c) == "string" then
if c:sub(1,1)=="\1" then if c:sub(1, 1) == "\1" then
tab[i]=loadstring(c:sub(2,-1)) tab[i] = loadstring(c:sub(2, -1))
else else
tab[i]=c tab[i] = c
end end
else else
tab[i]=c tab[i] = c
end end
end end
return tab return tab
@ -67,19 +69,21 @@ function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a chann
end end
end end
function self:peek() function self:peek()
local v=self.chan:peek() local v = self.chan:peek()
if not v then return end if not v then
if type(v)=="table" then return
end
if type(v) == "table" then
tab = {} tab = {}
for i,c in pairs(v) do for i, c in pairs(v) do
if type(c)=="string" then if type(c) == "string" then
if c:sub(1,1)=="\1" then if c:sub(1, 1) == "\1" then
tab[i]=loadstring(c:sub(2,-1)) tab[i] = loadstring(c:sub(2, -1))
else else
tab[i]=c tab[i] = c
end end
else else
tab[i]=c tab[i] = c
end end
end end
return tab return tab
@ -87,7 +91,7 @@ function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a chann
return self.chan:pop() return self.chan:pop()
end end
end end
GLOBAL[self.name]=self -- send the object to the thread through the global interface GLOBAL[self.name] = self -- send the object to the thread through the global interface
return self -- return the object return self -- return the object
end end
return c return c
@ -95,12 +99,12 @@ function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a chann
error("Make sure you required the love.thread module!") -- tell the user if he/she didn't require said module error("Make sure you required the love.thread module!") -- tell the user if he/she didn't require said module
end end
else else
c.linda=lanes.linda() -- lanes is a bit easier, create the linda on the main thread c.linda = lanes.linda() -- lanes is a bit easier, create the linda on the main thread
function c:push(v) -- push to the queue function c:push(v) -- push to the queue
self.linda:send("Q",v) self.linda:send("Q", v)
end end
function c:pop() -- pop the queue function c:pop() -- pop the queue
return ({self.linda:receive(0,"Q")})[2] return ({self.linda:receive(0, "Q")})[2]
end end
function c:peek() function c:peek()
return self.linda:get("Q") return self.linda:get("Q")
@ -108,41 +112,44 @@ function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a chann
function c:init() -- mimic the feature that love2d requires, so code can be consistent function c:init() -- mimic the feature that love2d requires, so code can be consistent
return self return self
end end
multi.integration.GLOBAL[name]=c -- send the object to the thread through the global interface multi.integration.GLOBAL[name] = c -- send the object to the thread through the global interface
end end
return c return c
end end
function multi:newSystemThreadedConnection(name,protect) function multi:newSystemThreadedConnection(name, protect)
local c={} local c = {}
c.name = name or error("You must provide a name for the connection object!") c.name = name or error("You must provide a name for the connection object!")
c.protect = protect or false c.protect = protect or false
c.idle = nil c.idle = nil
local sThread=multi.integration.THREAD local sThread = multi.integration.THREAD
local GLOBAL=multi.integration.GLOBAL local GLOBAL = multi.integration.GLOBAL
local connSync = multi:newSystemThreadedQueue(c.name.."_CONN_SYNC") local connSync = multi:newSystemThreadedQueue(c.name .. "_CONN_SYNC")
local connFire = multi:newSystemThreadedQueue(c.name.."_CONN_FIRE") local connFire = multi:newSystemThreadedQueue(c.name .. "_CONN_FIRE")
function c:init() function c:init()
local multi = require("multi") local multi = require("multi")
if love then -- lets make sure we don't reference up-values if using love2d if love then -- lets make sure we don't reference up-values if using love2d
GLOBAL=_G.GLOBAL GLOBAL = _G.GLOBAL
sThread=_G.sThread sThread = _G.sThread
end end
local conn = {} local conn = {}
conn.obj = multi:newConnection() conn.obj = multi:newConnection()
setmetatable(conn,{ setmetatable(
__call=function(self,...) conn,
return self:connect(...) {
end __call = function(self, ...)
}) return self:connect(...)
end
}
)
local ID = sThread.getID() local ID = sThread.getID()
local sync = sThread.waitFor(self.name.."_CONN_SYNC"):init() local sync = sThread.waitFor(self.name .. "_CONN_SYNC"):init()
local fire = sThread.waitFor(self.name.."_CONN_FIRE"):init() local fire = sThread.waitFor(self.name .. "_CONN_FIRE"):init()
local connections = {} local connections = {}
if not multi.isMainThread then if not multi.isMainThread then
connections = {0} connections = {0}
end end
sync:push{"INIT",ID} -- Register this as an active connection! sync:push {"INIT", ID} -- Register this as an active connection!
function conn:connect(func) function conn:connect(func)
return self.obj(func) return self.obj(func)
end end
@ -153,415 +160,468 @@ function multi:newSystemThreadedConnection(name,protect)
self.obj:Remove() self.obj:Remove()
end end
function conn:Fire(...) function conn:Fire(...)
for i = 1,#connections do for i = 1, #connections do
fire:push{connections[i],ID,{...}} fire:push {connections[i], ID, {...}}
end end
end end
function conn:FireTo(to,...) function conn:FireTo(to, ...)
local good = false local good = false
for i = 1,#connections do for i = 1, #connections do
if connections[i]==to then if connections[i] == to then
good = true good = true
break break
end end
end end
if not good then return multi.print("NonExisting Connection!") end if not good then
fire:push{to,ID,{...}} return multi.print("NonExisting Connection!")
end
fire:push {to, ID, {...}}
end end
-- FIRE {TO,FROM,{ARGS}} -- FIRE {TO,FROM,{ARGS}}
local data local data
local clock = os.clock local clock = os.clock
conn.OnConnectionAdded = multi:newConnection() conn.OnConnectionAdded = multi:newConnection()
multi:newLoop(function() multi:newLoop(
data = fire:peek() function()
if type(data)=="table" and data[1]==ID then data = fire:peek()
if data[2]==ID and conn.IgnoreSelf then if type(data) == "table" and data[1] == ID then
if data[2] == ID and conn.IgnoreSelf then
fire:pop()
return
end
fire:pop() fire:pop()
return conn.obj:Fire(unpack(data[3]))
end end
fire:pop() data = sync:peek()
conn.obj:Fire(unpack(data[3])) if data ~= nil and data[1] == "SYNCA" and data[2] == ID then
end sync:pop()
data = sync:peek() multi.nextStep(
if data~=nil and data[1]=="SYNCA" and data[2]==ID then function()
sync:pop() conn.OnConnectionAdded:Fire(data[3])
multi.nextStep(function() end
conn.OnConnectionAdded:Fire(data[3]) )
end) table.insert(connections, data[3])
table.insert(connections,data[3]) end
end if type(data) == "table" and data[1] == "SYNCR" and data[2] == ID then
if type(data)=="table" and data[1]=="SYNCR" and data[2]==ID then sync:pop()
sync:pop() for i = 1, #connections do
for i=1,#connections do if connections[i] == data[3] then
if connections[i] == data[3] then table.remove(connections, i)
table.remove(connections,i) end
end end
end end
end end
end):setName("STConn.syncer") ):setName("STConn.syncer")
return conn return conn
end end
local cleanUp = {} local cleanUp = {}
multi.OnSystemThreadDied(function(ThreadID) multi.OnSystemThreadDied(
for i=1,#syncs do function(ThreadID)
connSync:push{"SYNCR",syncs[i],ThreadID} for i = 1, #syncs do
end connSync:push {"SYNCR", syncs[i], ThreadID}
cleanUp[ThreadID] = true
end)
multi:newThread(c.name.." Connection-Handler",function()
local data
local clock = os.clock
local syncs = {}
while true do
if not c.idle then
thread.sleep(.5)
else
if clock() - c.idle >= 15 then
c.idle = nil
end
thread.skip()
end end
data = connSync:peek() cleanUp[ThreadID] = true
if data~= nil and data[1]=="INIT" then end
connSync:pop() )
c.idle = clock() multi:newThread(
table.insert(syncs,data[2]) c.name .. " Connection-Handler",
for i=1,#syncs do function()
connSync:push{"SYNCA",syncs[i],data[2]} local data
local clock = os.clock
local syncs = {}
while true do
if not c.idle then
thread.sleep(.5)
else
if clock() - c.idle >= 15 then
c.idle = nil
end
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 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
end) )
GLOBAL[c.name]=c GLOBAL[c.name] = c
return c return c
end end
function multi:SystemThreadedBenchmark(n) function multi:SystemThreadedBenchmark(n)
n=n or 1 n = n or 1
local cores=multi.integration.THREAD.getCores() local cores = multi.integration.THREAD.getCores()
local queue=multi:newSystemThreadedQueue("THREAD_BENCH_QUEUE"):init() local queue = multi:newSystemThreadedQueue("THREAD_BENCH_QUEUE"):init()
local sThread=multi.integration.THREAD local sThread = multi.integration.THREAD
local GLOBAL=multi.integration.GLOBAL local GLOBAL = multi.integration.GLOBAL
local c = {} local c = {}
for i=1,cores do for i = 1, cores do
multi:newSystemThread("STHREAD_BENCH",function(n) multi:newSystemThread(
local multi = require("multi") "STHREAD_BENCH",
if multi:getPlatform()=="love2d" then function(n)
GLOBAL=_G.GLOBAL local multi = require("multi")
sThread=_G.sThread if multi:getPlatform() == "love2d" then
end -- we cannot have upvalues... in love2d globals, not locals must be used GLOBAL = _G.GLOBAL
queue=sThread.waitFor("THREAD_BENCH_QUEUE"):init() -- always wait for when looking for a variable at the start of the thread! sThread = _G.sThread
multi:benchMark(n):OnBench(function(self,count) end -- we cannot have upvalues... in love2d globals, not locals must be used
queue:push(count) queue = sThread.waitFor("THREAD_BENCH_QUEUE"):init() -- always wait for when looking for a variable at the start of the thread!
sThread.kill() multi:benchMark(n):OnBench(
error("Thread was killed!") function(self, count)
end) queue:push(count)
multi:mainloop() sThread.kill()
end,n) error("Thread was killed!")
end
)
multi:mainloop()
end,
n
)
end end
multi:newThread("THREAD_BENCH",function() multi:newThread(
local count = 0 "THREAD_BENCH",
local cc = 0 function()
while true do local count = 0
thread.skip(1) local cc = 0
local dat = queue:pop() while true do
if dat then thread.skip(1)
cc=cc+1 local dat = queue:pop()
count = count + dat if dat then
if cc == cores then cc = cc + 1
c.OnBench:Fire(count) count = count + dat
thread.kill() if cc == cores then
c.OnBench:Fire(count)
thread.kill()
end
end end
end end
end end
end) )
c.OnBench = multi:newConnection() c.OnBench = multi:newConnection()
return c return c
end end
function multi:newSystemThreadedConsole(name) function multi:newSystemThreadedConsole(name)
local c={} local c = {}
c.name = name c.name = name
local sThread=multi.integration.THREAD local sThread = multi.integration.THREAD
local GLOBAL=multi.integration.GLOBAL local GLOBAL = multi.integration.GLOBAL
function c:init() function c:init()
_G.__Needs_Multi = true _G.__Needs_Multi = true
local multi = require("multi") local multi = require("multi")
if multi:getPlatform()=="love2d" then if multi:getPlatform() == "love2d" then
GLOBAL=_G.GLOBAL GLOBAL = _G.GLOBAL
sThread=_G.sThread sThread = _G.sThread
end end
local cc={} local cc = {}
if multi.isMainThread then if multi.isMainThread then
if GLOBAL["__SYSTEM_CONSOLE__"] then if GLOBAL["__SYSTEM_CONSOLE__"] then
cc.stream = sThread.waitFor("__SYSTEM_CONSOLE__"):init() cc.stream = sThread.waitFor("__SYSTEM_CONSOLE__"):init()
else else
cc.stream = multi:newSystemThreadedQueue("__SYSTEM_CONSOLE__"):init() cc.stream = multi:newSystemThreadedQueue("__SYSTEM_CONSOLE__"):init()
multi:newLoop(function() multi:newLoop(
local data = cc.stream:pop() function()
if data then local data = cc.stream:pop()
local dat = table.remove(data,1) if data then
if dat=="w" then local dat = table.remove(data, 1)
io.write(unpack(data)) if dat == "w" then
elseif dat=="p" then io.write(unpack(data))
print(unpack(data)) elseif dat == "p" then
print(unpack(data))
end
end end
end end
end):setName("ST.consoleSyncer") ):setName("ST.consoleSyncer")
end end
else else
cc.stream = sThread.waitFor("__SYSTEM_CONSOLE__"):init() cc.stream = sThread.waitFor("__SYSTEM_CONSOLE__"):init()
end end
function cc:write(msg) function cc:write(msg)
self.stream:push({"w",tostring(msg)}) self.stream:push({"w", tostring(msg)})
end end
function cc:print(...) function cc:print(...)
local tab = {...} local tab = {...}
for i=1,#tab do for i = 1, #tab do
tab[i]=tostring(tab[i]) tab[i] = tostring(tab[i])
end end
self.stream:push({"p",unpack(tab)}) self.stream:push({"p", unpack(tab)})
end end
return cc return cc
end end
GLOBAL[c.name]=c GLOBAL[c.name] = c
return c return c
end end
-- NEEDS WORK -- NEEDS WORK
function multi:newSystemThreadedTable(name) function multi:newSystemThreadedTable(name)
local c={} local c = {}
c.name=name -- set the name this is important for identifying what is what c.name = name -- set the name this is important for identifying what is what
local sThread=multi.integration.THREAD local sThread = multi.integration.THREAD
local GLOBAL=multi.integration.GLOBAL local GLOBAL = multi.integration.GLOBAL
function c:init() -- create an init function so we can mimic on both love2d and lanes function c:init() -- create an init function so we can mimic on both love2d and lanes
_G.__Needs_Multi = true _G.__Needs_Multi = true
local multi = require("multi") local multi = require("multi")
if multi:getPlatform()=="love2d" then if multi:getPlatform() == "love2d" then
GLOBAL=_G.GLOBAL GLOBAL = _G.GLOBAL
sThread=_G.sThread sThread = _G.sThread
end end
local cc={} local cc = {}
cc.tab={} cc.tab = {}
if multi.isMainThread then if multi.isMainThread then
if not GLOBAL[self.name.."_Tabled_Connection"] then if not GLOBAL[self.name .. "_Tabled_Connection"] then
cc.conn = multi:newSystemThreadedConnection(self.name.."_Tabled_Connection"):init() cc.conn = multi:newSystemThreadedConnection(self.name .. "_Tabled_Connection"):init()
end end
else else
cc.conn = sThread.waitFor(self.name.."_Tabled_Connection"):init() cc.conn = sThread.waitFor(self.name .. "_Tabled_Connection"):init()
end end
function cc:waitFor(name) function cc:waitFor(name)
repeat multi:uManager() until tab[name]~=nil repeat
multi:uManager()
until tab[name] ~= nil
return tab[name] return tab[name]
end end
local link = cc local link = cc
cc.conn(function(k,v) cc.conn(
link.tab[k]=v function(k, v)
end) link.tab[k] = v
setmetatable(cc,{
__index=function(t,k)
return t.tab[k]
end,
__newindex=function(t,k,v)
t.tab[k]=v
t.conn:Fire(k,v)
end end
}) )
setmetatable(
cc,
{
__index = function(t, k)
return t.tab[k]
end,
__newindex = function(t, k, v)
t.tab[k] = v
t.conn:Fire(k, v)
end
}
)
return cc return cc
end end
GLOBAL[c.name]=c GLOBAL[c.name] = c
return c return c
end end
local jobqueuecount = 0 local jobqueuecount = 0
local jqueues = {} local jqueues = {}
function multi:newSystemThreadedJobQueue(a,b) function multi:newSystemThreadedJobQueue(a, b)
jobqueuecount=jobqueuecount+1 jobqueuecount = jobqueuecount + 1
local GLOBAL=multi.integration.GLOBAL local GLOBAL = multi.integration.GLOBAL
local sThread=multi.integration.THREAD local sThread = multi.integration.THREAD
local c = {} local c = {}
c.numberofcores = 4 c.numberofcores = 4
c.idle = nil c.idle = nil
c.name = "SYSTEM_THREADED_JOBQUEUE_"..jobqueuecount c.name = "SYSTEM_THREADED_JOBQUEUE_" .. jobqueuecount
-- This is done to keep backwards compatibility for older code -- This is done to keep backwards compatibility for older code
if type(a)=="string" and not(b) then if type(a) == "string" and not (b) then
c.name = a c.name = a
elseif type(a)=="number" and not (b) then elseif type(a) == "number" and not (b) then
c.numberofcores = a c.numberofcores = a
elseif type(a)=="string" and type(b)=="number" then elseif type(a) == "string" and type(b) == "number" then
c.name = a c.name = a
c.numberofcores = b c.numberofcores = b
elseif type(a)=="number" and type(b)=="string" then elseif type(a) == "number" and type(b) == "string" then
c.name = b c.name = b
c.numberofcores = a c.numberofcores = a
end end
if jqueues[c.name] then if jqueues[c.name] then
error("A job queue by the name: "..c.name.." already exists!") error("A job queue by the name: " .. c.name .. " already exists!")
end end
jqueues[c.name] = true jqueues[c.name] = true
c.isReady = false c.isReady = false
c.jobnum=1 c.jobnum = 1
c.OnJobCompleted = multi:newConnection() c.OnJobCompleted = multi:newConnection()
local queueIN = self:newSystemThreadedQueue("QUEUE_IN_"..c.name):init() local queueIN = self:newSystemThreadedQueue("QUEUE_IN_" .. c.name):init()
local queueCC = self:newSystemThreadedQueue("QUEUE_CC_"..c.name):init() local queueCC = self:newSystemThreadedQueue("QUEUE_CC_" .. c.name):init()
local queueREG = self:newSystemThreadedQueue("QUEUE_REG_"..c.name):init() local queueREG = self:newSystemThreadedQueue("QUEUE_REG_" .. c.name):init()
local queueJD = self:newSystemThreadedQueue("QUEUE_JD_"..c.name):init() local queueJD = self:newSystemThreadedQueue("QUEUE_JD_" .. c.name):init()
local queueDA = self:newSystemThreadedQueue("QUEUE_DA_"..c.name):init() local queueDA = self:newSystemThreadedQueue("QUEUE_DA_" .. c.name):init()
c.OnReady = multi:newConnection() c.OnReady = multi:newConnection()
function c:registerJob(name,func) function c:registerJob(name, func)
for i = 1, self.numberofcores do for i = 1, self.numberofcores do
queueREG:push({name,func}) queueREG:push({name, func})
end end
end end
c.tempQueue = {} c.tempQueue = {}
function c:pushJob(name,...) function c:pushJob(name, ...)
c.idle = os.clock() c.idle = os.clock()
if not self.isReady then if not self.isReady then
table.insert(c.tempQueue,{self.jobnum,name,...}) table.insert(c.tempQueue, {self.jobnum, name, ...})
self.jobnum=self.jobnum+1 self.jobnum = self.jobnum + 1
return self.jobnum-1 return self.jobnum - 1
else else
queueIN:push{self.jobnum,name,...} queueIN:push {self.jobnum, name, ...}
self.jobnum=self.jobnum+1 self.jobnum = self.jobnum + 1
return self.jobnum-1 return self.jobnum - 1
end end
end end
function c:doToAll(func) function c:doToAll(func)
local r = multi.randomString(12) local r = multi.randomString(12)
for i = 1, self.numberofcores do for i = 1, self.numberofcores do
queueDA:push{r,func} queueDA:push {r, func}
end end
end end
for i=1,c.numberofcores do for i = 1, c.numberofcores do
multi:newSystemThread(c.name.." Worker Thread #"..i,function(name) multi:newSystemThread(
local multi = require("multi") c.name .. " Worker Thread #" .. i,
if love then -- lets make sure we don't reference up-values if using love2d function(name)
GLOBAL=_G.GLOBAL local multi = require("multi")
sThread=_G.sThread if love then -- lets make sure we don't reference up-values if using love2d
end GLOBAL = _G.GLOBAL
local CC = sThread.waitFor("QUEUE_CC_"..name):init() sThread = _G.sThread
CC:push("ready") end
local FUNCS={} local CC = sThread.waitFor("QUEUE_CC_" .. name):init()
local ids = {} CC:push("ready")
local JQI = sThread.waitFor("QUEUE_IN_"..name):init() local FUNCS = {}
local JD = sThread.waitFor("QUEUE_JD_"..name):init() local ids = {}
local REG = sThread.waitFor("QUEUE_REG_"..name):init() local JQI = sThread.waitFor("QUEUE_IN_" .. name):init()
local DA = sThread.waitFor("QUEUE_DA_"..name):init() local JD = sThread.waitFor("QUEUE_JD_" .. name):init()
local lastjob = os.clock() local REG = sThread.waitFor("QUEUE_REG_" .. name):init()
multi:newLoop(function() local DA = sThread.waitFor("QUEUE_DA_" .. name):init()
local job=JQI:pop() local lastjob = os.clock()
local rd=REG:peek() multi:newLoop(
local da=DA:peek() function()
if rd then local job = JQI:pop()
if not FUNCS[rd[1]] then local rd = REG:peek()
FUNCS[rd[1]]=rd[2] local da = DA:peek()
rd=nil if rd then
REG:pop() if not FUNCS[rd[1]] then
FUNCS[rd[1]] = rd[2]
rd = nil
REG:pop()
end
end
if da then
if not ids[da[1]] then
local meh = da[1]
ids[da[1]] = true
da[2](multi)
da = nil
DA:pop()
multi:newAlarm(60):OnRing(
function(a)
ids[meh] = nil
a:Destroy()
end
)
end
end
if job then
lastjob = os.clock()
local ID = table.remove(job, 1) -- return and remove
local _name = table.remove(job, 1) -- return and remove
if FUNCS[_name] then
JD:push({ID, FUNCS[_name](unpack(job))})
else -- making use of that new holding feature
JD:push({ID, FUNCS:waitFor(_name)(unpack(job))})
end
end
end end
end )
if da then multi:newLoop(
if not ids[da[1]] then function()
local meh = da[1] if os.clock() - lastjob > 1 then
ids[da[1]]=true sThread.sleep(.1)
da[2](multi) end
da=nil
DA:pop()
multi:newAlarm(60):OnRing(function(a)
ids[meh] = nil
a:Destroy()
end)
end end
)
setmetatable(
_G,
{
__index = function(t, k)
return FUNCS[k]
end
}
)
if not love then
multi:mainloop()
end end
if job then end,
lastjob = os.clock() c.name
local ID=table.remove(job,1) -- return and remove )
local _name=table.remove(job,1) -- return and remove
if FUNCS[_name] then
JD:push({ID,FUNCS[_name](unpack(job))})
else -- making use of that new holding feature
JD:push({ID,FUNCS:waitFor(_name)(unpack(job))})
end
end
end)
multi:newLoop(function()
if os.clock()-lastjob>1 then
sThread.sleep(.1)
end
end)
setmetatable(_G,{
__index=function(t,k)
return FUNCS[k]
end
})
if not love then
multi:mainloop()
end
end,c.name)
end end
local clock = os.clock local clock = os.clock
multi:newThread("JQ-"..c.name.." Manager",function() multi:newThread(
local _count = 0 "JQ-" .. c.name .. " Manager",
while _count<c.numberofcores do function()
thread.skip() local _count = 0
if queueCC:pop() then while _count < c.numberofcores do
_count = _count + 1
end
end
c.isReady = true
for i=1,#c.tempQueue do
queueIN:push(c.tempQueue[i])
end
c.tempQueue = nil
c.OnReady:Fire(c)
local dat
while true do
if not c.idle then
thread.sleep(.5)
else
if clock() - c.idle >= 15 then
c.idle = nil
end
thread.skip() thread.skip()
if queueCC:pop() then
_count = _count + 1
end
end end
dat = queueJD:pop() c.isReady = true
if dat then for i = 1, #c.tempQueue do
c.idle = clock() queueIN:push(c.tempQueue[i])
c.OnJobCompleted:Fire(unpack(dat)) end
c.tempQueue = nil
c.OnReady:Fire(c)
local dat
while true do
if not c.idle then
thread.sleep(.5)
else
if clock() - c.idle >= 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 end
end end
end) )
return c return c
end end
function multi:newSystemThreadedExecute(cmd) function multi:newSystemThreadedExecute(cmd)
local c={} local c = {}
local GLOBAL=multi.integration.GLOBAL -- set up locals incase we are using lanes local GLOBAL = multi.integration.GLOBAL -- set up locals incase we are using lanes
local sThread=multi.integration.THREAD -- set up locals incase we are using lanes local sThread = multi.integration.THREAD -- set up locals incase we are using lanes
local name="Execute_Thread"..multi.randomString(16) local name = "Execute_Thread" .. multi.randomString(16)
c.name=name c.name = name
GLOBAL[name.."CMD"]=cmd GLOBAL[name .. "CMD"] = cmd
multi:newSystemThread(name,function() multi:newSystemThread(
if love then -- lets make sure we don't reference upvalues if using love2d name,
GLOBAL=_G.GLOBAL function()
sThread=_G.sThread if love then -- lets make sure we don't reference upvalues if using love2d
name=__THREADNAME__ -- global data same as the name we used in this functions creation GLOBAL = _G.GLOBAL
end -- Lanes should take the local upvalues ^^^ sThread = _G.sThread
cmd=sThread.waitFor(name.."CMD") name = __THREADNAME__ -- global data same as the name we used in this functions creation
local ret=os.execute(cmd) end -- Lanes should take the local upvalues ^^^
GLOBAL[name.."R"]=ret cmd = sThread.waitFor(name .. "CMD")
end) local ret = os.execute(cmd)
c.OnCMDFinished=multi:newConnection() GLOBAL[name .. "R"] = ret
c.looper=multi:newLoop(function(self)
local ret=GLOBAL[self.link.name.."R"]
if ret then
self.link.OnCMDFinished:Fire(ret)
self:Destroy()
end end
end) )
c.looper.link=c c.OnCMDFinished = multi:newConnection()
c.looper =
multi:newLoop(
function(self)
local ret = GLOBAL[self.link.name .. "R"]
if ret then
self.link.OnCMDFinished:Fire(ret)
self:Destroy()
end
end
)
c.looper.link = c
return c return c
end end