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
[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
-------------
Added:

View File

@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local multi = require("multi")
local multi, thread = require("multi").init()
os.sleep = love.timer.sleep
multi.drawF = {}
function multi:onDraw(func, i)
@ -38,14 +38,18 @@ multi.OnDraw = multi:newConnection()
multi.OnTextInput = multi:newConnection()
multi.OnUpdate = multi:newConnection()
multi.OnQuit = multi:newConnection()
multi.OnPreLoad(function()
multi.OnPreLoad(
function()
local function Hook(func, conn)
if love[func] ~= nil then
love[func] = Library.convert(love[func])
love[func]:inject(function(...)
love[func]:inject(
function(...)
conn:Fire(...)
return {...}
end,1)
end,
1
)
elseif love[func] == nil then
love[func] = function(...)
conn:Fire(...)
@ -62,15 +66,20 @@ multi.OnPreLoad(function()
Hook("draw", multi.OnDraw)
Hook("textinput", multi.OnTextInput)
Hook("update", multi.OnUpdate)
multi.OnDraw(function()
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()
end
)
end
)
multi.OnQuit(
function()
multi.Stop()
love.event.quit()
end)
end
)
return multi

View File

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

View File

@ -22,18 +22,24 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
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()
if package.config:sub(1,1)=='\\' then
return 'windows'
if package.config:sub(1, 1) == "\\" then
return "windows"
else
return 'unix'
return "unix"
end
end
-- Step 1 get lanes
lanes = require("lanes").configure()
local multi, thread = require("multi") -- get it all and have it on all lanes
multi.SystemThreads = {}
local thread = thread
multi.isMainThread = true
function multi:canSystemThread()
return true
@ -46,14 +52,17 @@ local __GlobalLinda = lanes.linda() -- handles global stuff
local __SleepingLinda = lanes.linda() -- handles sleeping stuff
-- For convenience a GLOBAL table will be constructed to handle requests
local GLOBAL = {}
setmetatable(GLOBAL,{
setmetatable(
GLOBAL,
{
__index = function(t, k)
return __GlobalLinda:get(k)
end,
__newindex = function(t, k, v)
__GlobalLinda:set(k, v)
end,
})
end
}
)
-- Step 3 rewrite the thread methods to use Lindas
local THREAD = {}
function THREAD.set(name, val)
@ -63,10 +72,10 @@ function THREAD.get(name)
__GlobalLinda:get(name)
end
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'}
for i = 1, n do
str = str..''..strings[math.random(1,#strings)]
str = str .. "" .. strings[math.random(1, #strings)]
end
return str
end
@ -75,7 +84,9 @@ function THREAD.waitFor(name)
math.randomseed(os.time())
__SleepingLinda:receive(.001, randomString(12))
end
repeat wait() until __GlobalLinda:get(name)
repeat
wait()
until __GlobalLinda:get(name)
return __GlobalLinda:get(name)
end
function THREAD.testFor(name, val, sym)
@ -114,7 +125,9 @@ function THREAD.hold(n)
math.randomseed(os.time())
__SleepingLinda:receive(.001, randomString(12))
end
repeat wait() until n()
repeat
wait()
until n()
end
local rand = math.random(1, 10000000)
-- Step 5 Basic Threads!
@ -161,9 +174,13 @@ function multi:newSystemThread(name,func,...)
end
multi.OnSystemThreadDied = multi:newConnection()
function multi.InitSystemThreadErrorHandler()
if started==true then return end
if started == true then
return
end
started = true
multi:newThread("ThreadErrorHandler",function()
multi:newThread(
"ThreadErrorHandler",
function()
local threads = multi.SystemThreads
while true do
thread.sleep(.5) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough.
@ -189,11 +206,16 @@ function multi.InitSystemThreadErrorHandler()
end
end
end
end)
end
)
end
multi.print("Integrated Lanes!")
multi.integration = {} -- for module creators
multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD = THREAD
require("multi.integration.shared")
return {init=function() return GLOBAL, THREAD end}
return {
init = function()
return GLOBAL, THREAD
end
}

View File

@ -22,6 +22,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
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()
return true
end
@ -30,7 +37,8 @@ function multi:getPlatform()
end
multi.integration = {}
multi.integration.love2d = {}
multi.integration.love2d.ThreadBase=[[
multi.integration.love2d.ThreadBase =
[[
tab={...}
__THREADID__=table.remove(tab,1)
__THREADNAME__=table.remove(tab,1)
@ -201,7 +209,9 @@ multi:mainloop()
GLOBAL = {} -- Allow main thread to interact with these objects as well
_G.THREAD_ID = 0
__proxy__ = {}
setmetatable(GLOBAL,{
setmetatable(
GLOBAL,
{
__index = function(t, k)
return __proxy__[k]
end,
@ -214,8 +224,9 @@ setmetatable(GLOBAL,{
__channels__[i]:push("SYNC " .. type(v) .. " " .. k .. " " .. resolveData(v))
end
end
end,
})
end
}
)
THREAD = {} -- Allow main thread to interact with these objects as well
multi.integration.love2d.mainChannel = love.thread.getChannel("__MainChan__")
isMainThread = true
@ -232,7 +243,7 @@ function ToStr(val, name, skipnewlines, depth)
local tmp = string.rep(" ", depth)
if name then
if type(name) == "string" then
tmp = tmp .. "[\""..name.."\"] = "
tmp = tmp .. '["' .. name .. '"] = '
else
tmp = tmp .. "[" .. (name or "") .. "] = "
end
@ -252,7 +263,7 @@ function ToStr(val, name, skipnewlines, depth)
elseif type(val) == "function" then
tmp = tmp .. "loadDump([===[" .. dump(val) .. "]===])"
else
tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\""
tmp = tmp .. '"[inserializeable datatype:' .. type(val) .. ']"'
end
return tmp
end
@ -297,10 +308,10 @@ function dump(func)
return table.concat(code)
end
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'}
for i = 1, n do
str = str..''..strings[math.random(1,#strings)]
str = str .. "" .. strings[math.random(1, #strings)]
end
return str
end
@ -328,7 +339,11 @@ function multi:newSystemThread(name,func,...) -- the main method
end
function love.threaderror(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(
threads[i],
err,
"Error in systemThread: '" .. livingThreads[thread].name .. "' <" .. errorstr .. ">"
)
multi.print("Error in systemThread: " .. tostring(thread) .. ": " .. errorstr)
end
local THREAD = {}
@ -339,7 +354,9 @@ function THREAD.get(name)
return GLOBAL[name]
end
function THREAD.waitFor(name)
repeat multi:uManager() until GLOBAL[name]
repeat
multi:uManager()
until GLOBAL[name]
return GLOBAL[name]
end
function THREAD.getCores()
@ -349,12 +366,16 @@ function THREAD.sleep(n)
love.timer.sleep(n)
end
function THREAD.hold(n)
repeat multi:uManager() until n()
repeat
multi:uManager()
until n()
end
__channels__ = {}
multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD = THREAD
updater=multi:newLoop(function(self)
updater =
multi:newLoop(
function(self)
local data = multi.integration.love2d.mainChannel:pop()
while data do
if type(data) == "string" then
@ -384,13 +405,18 @@ updater=multi:newLoop(function(self)
end
data = multi.integration.love2d.mainChannel:pop()
end
end)
end
)
multi.OnSystemThreadDied = multi:newConnection()
local started = false
function multi.InitSystemThreadErrorHandler()
if started==true then return end
if started == true then
return
end
started = true
multi:newThread("ThreadErrorHandler",function()
multi:newThread(
"ThreadErrorHandler",
function()
local threads = multi.SystemThreads
while true do
thread.sleep(.5) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough.
@ -412,7 +438,8 @@ function multi.InitSystemThreadErrorHandler()
end
end
end
end)
end
)
end
require("multi.integration.shared")
multi.print("Integrated Love2d!")

View File

@ -21,21 +21,20 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
-- 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)
package.path = "?/init.lua;?.lua;" .. package.path
local function _INIT(luvitThread, timer)
-- lots of this stuff should be able to stay the same
function os.getOS()
if package.config:sub(1,1)=='\\' then
return 'windows'
if package.config:sub(1, 1) == "\\" then
return "windows"
else
return 'unix'
return "unix"
end
end
-- 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
function multi:canSystemThread()
return true
@ -46,15 +45,18 @@ local function _INIT(luvitThread,timer)
local multi = multi
-- Step 2 set up the Global table... is this possible?
local GLOBAL = {}
setmetatable(GLOBAL,{
setmetatable(
GLOBAL,
{
__index = function(t, k)
--print("No Global table when using luvit integration!")
return nil
end,
__newindex = function(t, k, v)
--print("No Global table when using luvit integration!")
end,
})
end
}
)
local THREAD = {}
function THREAD.set(name, val)
--print("No Global table when using luvit integration!")
@ -63,10 +65,73 @@ local function _INIT(luvitThread,timer)
--print("No Global table when using luvit integration!")
end
local function randomString(n)
local str = ''
local strings = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'}
local str = ""
local strings = {
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z"
}
for i = 1, n do
str = str..''..strings[math.random(1,#strings)]
str = str .. "" .. strings[math.random(1, #strings)]
end
return str
end
@ -96,8 +161,8 @@ local function _INIT(luvitThread,timer)
end
-- Step 5 Basic Threads!
local function entry(path, name, func, ...)
local timer = require'timer'
local luvitThread = require'thread'
local timer = require "timer"
local luvitThread = require "thread"
package.path = path
loadstring(func)(...)
end
@ -120,9 +185,16 @@ local function _INIT(luvitThread,timer)
multi.integration.THREAD = THREAD
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...
local interval = timer.setInterval(1, function ()
local interval =
timer.setInterval(
1,
function()
multi:uManager()
end)
end
)
return multi
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,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local multi, thread = require("multi")
local multi, thread = require("multi").init()
local net = require("net")
local bin = require("bin")
bin.setBitsInterface(infinabits) -- the bits interface does not work so well, another bug to fix
@ -119,7 +119,9 @@ end
-- internal global system
local GLOBAL = {}
local PROXY = {}
setmetatable(GLOBAL,{
setmetatable(
GLOBAL,
{
__index = function(t, k)
return PROXY[k]
end,
@ -128,7 +130,8 @@ setmetatable(GLOBAL,{
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
function multi:nodeManager(port)
@ -140,10 +143,13 @@ function multi:nodeManager(port)
server.timeouts = {}
server.OnNodeAdded = multi:newConnection()
server.OnNodeRemoved = multi:newConnection()
server.OnDataRecieved(function(server,data,cid,ip,port)
server.OnDataRecieved(
function(server, data, cid, ip, port)
local cmd = data:sub(1, 1)
if cmd == "R" then
multi:newThread("Node Client Manager",function(loop)
multi:newThread(
"Node Client Manager",
function(loop)
while true do
if server.timeouts[cid] == true then
server.OnNodeRemoved:Fire(server.nodes[cid])
@ -156,23 +162,29 @@ function multi:nodeManager(port)
end
thread.sleep(1)
end
end)
end
)
server.nodes[cid] = data:sub(2, -1)
server.OnNodeAdded:Fire(server.nodes[cid])
elseif cmd == "G" then
server.OnNodeAdded(function(node)
server.OnNodeAdded(
function(node)
server:send(cid, node)
end)
server.OnNodeRemoved(function(node)
end
)
server.OnNodeRemoved(
function(node)
server:send(cid, "R" .. node:match("(.-)|"))
end)
end
)
for i, v in pairs(server.nodes) do
server:send(cid, v)
end
elseif cmd == "P" then
server.timeouts[cid] = nil
end
end)
end
)
end
-- The main driving force of the network manager: Nodes
function multi:newNode(settings)
@ -183,9 +195,11 @@ function multi:newNode(settings)
local name = settings.name or multi.randomString(8)
local node = {}
node.name = name
multi.OnError(function(i,error)
multi.OnError(
function(i, error)
node.OnError:Fire(node, error, node.server)
end)
end
)
node.server = net:newUDPServer(0) -- hosts the node using the default port
_, node.port = node.server.udp:getsockname()
node.connections = net.ClientCache
@ -193,7 +207,8 @@ function multi:newNode(settings)
node.functions = bin.stream("RegisteredFunctions.dat", false)
node.hasFuncs = {}
node.OnError = multi:newConnection()
node.OnError(function(node,err,master)
node.OnError(
function(node, err, master)
multi.print("ERROR", err, node.name)
local temp = bin.new()
temp:addBlock(#node.name, 2)
@ -204,17 +219,21 @@ function multi:newNode(settings)
multi.print(i)
v[1]:send(v[2], char(CMD_ERROR) .. temp.data, v[3])
end
end)
end
)
if settings.managerDetails then
local c = net:newTCPClient(settings.managerDetails[1], settings.managerDetails[2])
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
c.OnDataRecieved(function(self,data)
c.OnDataRecieved(
function(self, data)
if data == "ping" then
self:send("P")
end
end)
end
)
c:send("RNODE_" .. name .. "|" .. net.getLocalIP() .. "|" .. node.port)
end
end
@ -230,7 +249,9 @@ function multi:newNode(settings)
len = node.functions:read(1)
_G[name] = resolveData(func)
node.hasFuncs[name] = true
if not len then break end
if not len then
break
end
len = byte(len)
end
end
@ -261,7 +282,8 @@ function multi:newNode(settings)
end
node.loadRate = 1
-- Lets tell the network we are alive!
node.server.OnDataRecieved(function(server,data,cid,ip,port)
node.server.OnDataRecieved(
function(server, data, cid, ip, port)
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
if cmd == CMD_PING then
@ -306,25 +328,35 @@ function multi:newNode(settings)
elseif cmd == CMD_INITNODE then
multi.print("Connected with another node!")
node.connections[dat] = {server, ip, port}
multi.OnGUpdate(function(k,v)
multi.OnGUpdate(
function(k, v)
server:send(ip, table.concat {char(CMD_GLOBAL), k, "|", v}, port)
end)-- set this up
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)
multi.OnGUpdate(
function(k, v)
server:send(ip, table.concat {char(CMD_GLOBAL), k, "|", v}, port)
end)-- set this up
multi:newTLoop(function()
end
)
-- set this up
multi:newTLoop(
function()
server:send(ip, char(CMD_LOAD) .. node.name .. "|" .. multi:getLoad(), port)
end,node.loadRate)
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)
local conn = node.connections[name]
conn[1]:send(conn[2], data, conn[3])
@ -350,27 +382,34 @@ 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.loads = {}
master.timeouts = {}
master.trigger = multi:newFunction(function(self,node)
master.trigger =
multi:newFunction(
function(self, node)
master.OnFirstNodeConnected:Fire(node)
self:Pause()
end)
end
)
if settings.managerDetails then
local client = net:newTCPClient(settings.managerDetails[1], settings.managerDetails[2])
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
client.OnDataRecieved(function(client,data)
client.OnDataRecieved(
function(client, data)
local cmd = data:sub(1, 1)
if cmd == "N" then
print(data)
local name, ip, port = data:match("(.-)|(.-)|(.+)")
local c = net:newUDPClient(ip, port)
net.OnCastedClientInfo:Fire(c,name,ip,port)master.connections[name]=c
net.OnCastedClientInfo:Fire(c, name, ip, port)
master.connections[name] = c
elseif cmd == "R" then
local name = data:sub(2, -1)
master.connections[name] = nil
end
end)
end
)
client:send("G") -- init your connection as a master
end
end
@ -441,12 +480,20 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
name = self:getRandomNode()
end
if name == nil then
multi:newEvent(function() return name~=nil end):OnEvent(function(evnt)
multi:newEvent(
function()
return name ~= nil
end
):OnEvent(
function(evnt)
self:sendTo(name, char(CMD_TASK) .. len .. aData .. len2 .. fData)
evnt:Destroy()
end):SetName("DelayedSendTask"):SetName("DelayedSendTask"):SetTime(8):OnTimedOut(function(self)
end
):SetName("DelayedSendTask"):SetName("DelayedSendTask"):SetTime(8):OnTimedOut(
function(self)
self:Destroy()
end)
end
)
else
self:sendTo(name, char(CMD_TASK) .. len .. aData .. len2 .. fData)
end
@ -460,12 +507,20 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
name = "NODE_" .. name
end
if self.connections[name] == nil then
multi:newEvent(function() return self.connections[name]~=nil end):OnEvent(function(evnt)
multi:newEvent(
function()
return self.connections[name] ~= nil
end
):OnEvent(
function(evnt)
self.connections[name]:send(data)
evnt:Destroy()
end):SetName("DelayedSendTask"):SetTime(8):OnTimedOut(function(self)
end
):SetName("DelayedSendTask"):SetTime(8):OnTimedOut(
function(self)
self:Destroy()
end)
end
)
else
self.connections[name]:send(data)
end
@ -489,18 +544,24 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
end
return list[math.random(1, #list)]
end
net.OnCastedClientInfo(function(client,name,ip,port)
multi.OnGUpdate(function(k,v)
net.OnCastedClientInfo(
function(client, name, ip, port)
multi.OnGUpdate(
function(k, v)
client:send(table.concat {char(CMD_GLOBAL), k, "|", v})
end)
end
)
local nodename
for i, v in pairs(master.connections) do
nodename = i
end
client.OnClientReady(function()
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)
multi:newThread(
"Node Data Link Controller",
function(loop)
while true do
if master.timeouts[name] == true then
master.timeouts[name] = nil
@ -512,10 +573,12 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
end
thread.sleep(1)
end
end)
end
)
end
client.name = name
client.OnDataRecieved(function(client,data)
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)
@ -541,9 +604,12 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
local name, load = dat:match("(.-)|(.+)")
master.loads[name] = tonumber(load)
end
end)
end)
end)
end
)
end
)
end
)
if not settings.noBroadCast then
net:newCastedClients("NODE_(.+)") -- Searches for nodes and connects to them, the master.clients table will contain them by name
end
@ -551,6 +617,8 @@ function multi:newMaster(settings) -- You will be able to have more than one mas
end
-- The init function that gets returned
multi.print("Integrated Network Parallelism")
return {init = function()
return {
init = function()
return GLOBAL
end}
end
}

View File

@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
multi, thread = require("multi")
local multi, thread = require("multi").init()
function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a channel on both ends
local c = {} -- where we will store our object
c.name = name -- set the name this is important for the love2d side
@ -47,7 +47,9 @@ function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a chann
end
function self:pop() -- pop from the channel
local v = self.chan:pop()
if not v then return end
if not v then
return
end
if type(v) == "table" then
tab = {}
for i, c in pairs(v) do
@ -68,7 +70,9 @@ function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a chann
end
function self:peek()
local v = self.chan:peek()
if not v then return end
if not v then
return
end
if type(v) == "table" then
tab = {}
for i, c in pairs(v) do
@ -130,11 +134,14 @@ function multi:newSystemThreadedConnection(name,protect)
end
local conn = {}
conn.obj = multi:newConnection()
setmetatable(conn,{
setmetatable(
conn,
{
__call = function(self, ...)
return self:connect(...)
end
})
}
)
local ID = sThread.getID()
local sync = sThread.waitFor(self.name .. "_CONN_SYNC"):init()
local fire = sThread.waitFor(self.name .. "_CONN_FIRE"):init()
@ -165,14 +172,17 @@ function multi:newSystemThreadedConnection(name,protect)
break
end
end
if not good then return multi.print("NonExisting Connection!") end
if not good then
return multi.print("NonExisting Connection!")
end
fire:push {to, ID, {...}}
end
-- FIRE {TO,FROM,{ARGS}}
local data
local clock = os.clock
conn.OnConnectionAdded = multi:newConnection()
multi:newLoop(function()
multi:newLoop(
function()
data = fire:peek()
if type(data) == "table" and data[1] == ID then
if data[2] == ID and conn.IgnoreSelf then
@ -185,9 +195,11 @@ function multi:newSystemThreadedConnection(name,protect)
data = sync:peek()
if data ~= nil and data[1] == "SYNCA" and data[2] == ID then
sync:pop()
multi.nextStep(function()
multi.nextStep(
function()
conn.OnConnectionAdded:Fire(data[3])
end)
end
)
table.insert(connections, data[3])
end
if type(data) == "table" and data[1] == "SYNCR" and data[2] == ID then
@ -198,17 +210,22 @@ function multi:newSystemThreadedConnection(name,protect)
end
end
end
end):setName("STConn.syncer")
end
):setName("STConn.syncer")
return conn
end
local cleanUp = {}
multi.OnSystemThreadDied(function(ThreadID)
multi.OnSystemThreadDied(
function(ThreadID)
for i = 1, #syncs do
connSync:push {"SYNCR", syncs[i], ThreadID}
end
cleanUp[ThreadID] = true
end)
multi:newThread(c.name.." Connection-Handler",function()
end
)
multi:newThread(
c.name .. " Connection-Handler",
function()
local data
local clock = os.clock
local syncs = {}
@ -234,12 +251,15 @@ function multi:newSystemThreadedConnection(name,protect)
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)
multi:newAlarm(15):OnRing(
function(a)
cleanUp[meh] = nil
end)
end
)
end
end
end)
end
)
GLOBAL[c.name] = c
return c
end
@ -252,22 +272,30 @@ function multi:SystemThreadedBenchmark(n)
local GLOBAL = multi.integration.GLOBAL
local c = {}
for i = 1, cores do
multi:newSystemThread("STHREAD_BENCH",function(n)
multi:newSystemThread(
"STHREAD_BENCH",
function(n)
local multi = require("multi")
if multi:getPlatform() == "love2d" then
GLOBAL = _G.GLOBAL
sThread = _G.sThread
end -- we cannot have upvalues... in love2d globals, not locals must be used
queue = sThread.waitFor("THREAD_BENCH_QUEUE"):init() -- always wait for when looking for a variable at the start of the thread!
multi:benchMark(n):OnBench(function(self,count)
multi:benchMark(n):OnBench(
function(self, count)
queue:push(count)
sThread.kill()
error("Thread was killed!")
end)
multi:mainloop()
end,n)
end
multi:newThread("THREAD_BENCH",function()
)
multi:mainloop()
end,
n
)
end
multi:newThread(
"THREAD_BENCH",
function()
local count = 0
local cc = 0
while true do
@ -282,7 +310,8 @@ function multi:SystemThreadedBenchmark(n)
end
end
end
end)
end
)
c.OnBench = multi:newConnection()
return c
end
@ -304,7 +333,8 @@ function multi:newSystemThreadedConsole(name)
cc.stream = sThread.waitFor("__SYSTEM_CONSOLE__"):init()
else
cc.stream = multi:newSystemThreadedQueue("__SYSTEM_CONSOLE__"):init()
multi:newLoop(function()
multi:newLoop(
function()
local data = cc.stream:pop()
if data then
local dat = table.remove(data, 1)
@ -314,7 +344,8 @@ function multi:newSystemThreadedConsole(name)
print(unpack(data))
end
end
end):setName("ST.consoleSyncer")
end
):setName("ST.consoleSyncer")
end
else
cc.stream = sThread.waitFor("__SYSTEM_CONSOLE__"):init()
@ -357,14 +388,20 @@ function multi:newSystemThreadedTable(name)
cc.conn = sThread.waitFor(self.name .. "_Tabled_Connection"):init()
end
function cc:waitFor(name)
repeat multi:uManager() until tab[name]~=nil
repeat
multi:uManager()
until tab[name] ~= nil
return tab[name]
end
local link = cc
cc.conn(function(k,v)
cc.conn(
function(k, v)
link.tab[k] = v
end)
setmetatable(cc,{
end
)
setmetatable(
cc,
{
__index = function(t, k)
return t.tab[k]
end,
@ -372,7 +409,8 @@ function multi:newSystemThreadedTable(name)
t.tab[k] = v
t.conn:Fire(k, v)
end
})
}
)
return cc
end
GLOBAL[c.name] = c
@ -438,7 +476,9 @@ function multi:newSystemThreadedJobQueue(a,b)
end
end
for i = 1, c.numberofcores do
multi:newSystemThread(c.name.." Worker Thread #"..i,function(name)
multi:newSystemThread(
c.name .. " Worker Thread #" .. i,
function(name)
local multi = require("multi")
if love then -- lets make sure we don't reference up-values if using love2d
GLOBAL = _G.GLOBAL
@ -453,7 +493,8 @@ function multi:newSystemThreadedJobQueue(a,b)
local REG = sThread.waitFor("QUEUE_REG_" .. name):init()
local DA = sThread.waitFor("QUEUE_DA_" .. name):init()
local lastjob = os.clock()
multi:newLoop(function()
multi:newLoop(
function()
local job = JQI:pop()
local rd = REG:peek()
local da = DA:peek()
@ -471,10 +512,12 @@ function multi:newSystemThreadedJobQueue(a,b)
da[2](multi)
da = nil
DA:pop()
multi:newAlarm(60):OnRing(function(a)
multi:newAlarm(60):OnRing(
function(a)
ids[meh] = nil
a:Destroy()
end)
end
)
end
end
if job then
@ -487,24 +530,34 @@ function multi:newSystemThreadedJobQueue(a,b)
JD:push({ID, FUNCS:waitFor(_name)(unpack(job))})
end
end
end)
multi:newLoop(function()
end
)
multi:newLoop(
function()
if os.clock() - lastjob > 1 then
sThread.sleep(.1)
end
end)
setmetatable(_G,{
end
)
setmetatable(
_G,
{
__index = function(t, k)
return FUNCS[k]
end
})
}
)
if not love then
multi:mainloop()
end
end,c.name)
end,
c.name
)
end
local clock = os.clock
multi:newThread("JQ-"..c.name.." Manager",function()
multi:newThread(
"JQ-" .. c.name .. " Manager",
function()
local _count = 0
while _count < c.numberofcores do
thread.skip()
@ -534,7 +587,8 @@ function multi:newSystemThreadedJobQueue(a,b)
c.OnJobCompleted:Fire(unpack(dat))
end
end
end)
end
)
return c
end
function multi:newSystemThreadedExecute(cmd)
@ -544,7 +598,9 @@ function multi:newSystemThreadedExecute(cmd)
local name = "Execute_Thread" .. multi.randomString(16)
c.name = name
GLOBAL[name .. "CMD"] = cmd
multi:newSystemThread(name,function()
multi:newSystemThread(
name,
function()
if love then -- lets make sure we don't reference upvalues if using love2d
GLOBAL = _G.GLOBAL
sThread = _G.sThread
@ -553,15 +609,19 @@ function multi:newSystemThreadedExecute(cmd)
cmd = sThread.waitFor(name .. "CMD")
local ret = os.execute(cmd)
GLOBAL[name .. "R"] = ret
end)
end
)
c.OnCMDFinished = multi:newConnection()
c.looper=multi:newLoop(function(self)
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
return c
end