mirror of
https://github.com/rayaman/multi.git
synced 2026-09-05 07:27:35 -04:00
working on new network manager
New features coming soon. Redoing networking management system threads are mostly done, whats left are some threaded objects like systemthreadedconnection
This commit is contained in:
+39
-10
@@ -1512,8 +1512,22 @@ function thread.waitFor(name)
|
||||
return thread.get(name)
|
||||
end
|
||||
function multi.hold(func)
|
||||
if thread.isThread() then
|
||||
if type(func) == "function" or type(func) == "table" then
|
||||
return thread.hold(func)
|
||||
end
|
||||
return thread.sleep(func)
|
||||
end
|
||||
local death = false
|
||||
if type(func)=="function" then
|
||||
if type(func)=="number" then
|
||||
multi:newThread("Hold_func",function()
|
||||
thread.sleep(func)
|
||||
death = true
|
||||
end)
|
||||
while not death do
|
||||
multi.scheduler:Act()
|
||||
end
|
||||
else
|
||||
local rets
|
||||
multi:newThread("Hold_func",function()
|
||||
rets = {thread.hold(func)}
|
||||
@@ -1523,16 +1537,22 @@ function multi.hold(func)
|
||||
multi.scheduler:Act()
|
||||
end
|
||||
return unpack(rets)
|
||||
elseif type(func)=="number" then
|
||||
multi:newThread("Hold_func",function()
|
||||
thread.sleep(func)
|
||||
death = true
|
||||
end)
|
||||
while not death do
|
||||
multi.scheduler:Act()
|
||||
end
|
||||
end
|
||||
end
|
||||
function multi.holdFor(n,func)
|
||||
local temp
|
||||
multi:newThread(function()
|
||||
thread.sleep(n)
|
||||
temp = true
|
||||
end)
|
||||
return multi.hold(function()
|
||||
if func() then
|
||||
return func()
|
||||
elseif temp then
|
||||
return multi.NIL, "TIMEOUT"
|
||||
end
|
||||
end)
|
||||
end
|
||||
function thread:newFunction(func)
|
||||
local c = {Type = "tfunc"}
|
||||
c.__call = function(self,...)
|
||||
@@ -1725,6 +1745,15 @@ function multi.initThreads(justThreads)
|
||||
threads[i].__ready = false
|
||||
ret = nil
|
||||
elseif ret[1]=="_hold_" then
|
||||
if type(ret[2])=="table" and ret[2].Type=='connector' then
|
||||
local letsgo
|
||||
ret[2](function(...) letsgo = {...} end)
|
||||
ret[2] = function()
|
||||
if letsgo then
|
||||
return unpack(letsgo)
|
||||
end
|
||||
end
|
||||
end
|
||||
threads[i].func = ret[2]
|
||||
threads[i].task = "hold"
|
||||
threads[i].__ready = false
|
||||
@@ -1766,7 +1795,7 @@ function multi.initThreads(justThreads)
|
||||
threads[i].task = ""
|
||||
threads[i].__ready = true
|
||||
end
|
||||
elseif threads[i].task == "hold" then
|
||||
elseif threads[i].task == "hold" then --GOHERE
|
||||
t0,t1,t2,t3,t4,t5,t6 = threads[i].func()
|
||||
if t0 then
|
||||
if t0==multi.NIL then
|
||||
|
||||
@@ -1,624 +0,0 @@
|
||||
--[[
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Ryan Ward
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
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"):init()
|
||||
local net = require("net")
|
||||
local bin = require("bin")
|
||||
bin.setBitsInterface(infinabits) -- the bits interface does not work so well, another bug to fix
|
||||
|
||||
-- Commands that the master and node will respect, max of 256 commands
|
||||
local CMD_ERROR = 0x00
|
||||
local CMD_PING = 0x01
|
||||
local CMD_PONG = 0x02
|
||||
local CMD_QUEUE = 0x03
|
||||
local CMD_TASK = 0x04
|
||||
local CMD_INITNODE = 0x05
|
||||
local CMD_INITMASTER = 0x06
|
||||
local CMD_GLOBAL = 0x07
|
||||
local CMD_LOAD = 0x08
|
||||
local CMD_CALL = 0x09
|
||||
local CMD_REG = 0x0A
|
||||
local CMD_CONSOLE = 0x0B
|
||||
|
||||
local char = string.char
|
||||
local byte = string.byte
|
||||
-- Process to hold all of the networkManager's muilt objects
|
||||
|
||||
-- Helper for piecing commands
|
||||
local function pieceCommand(cmd, ...)
|
||||
local tab = {...}
|
||||
table.insert(tab, 1, cmd)
|
||||
return table.concat(tab)
|
||||
end
|
||||
|
||||
-- Internal queue system for network queues
|
||||
local Queue = {}
|
||||
Queue.__index = Queue
|
||||
function Queue:newQueue()
|
||||
local c = {}
|
||||
setmetatable(c, self)
|
||||
return c
|
||||
end
|
||||
function Queue:push(data)
|
||||
table.insert(self, data)
|
||||
end
|
||||
function Queue:raw_push(data) -- Internal usage only
|
||||
table.insert(self, data)
|
||||
end
|
||||
function Queue:pop()
|
||||
return table.remove(self, 1)
|
||||
end
|
||||
function Queue:peek()
|
||||
return self[1]
|
||||
end
|
||||
local queues = {}
|
||||
multi.OnNetQueue = multi:newConnection()
|
||||
multi.OnGUpdate = multi:newConnection()
|
||||
|
||||
-- Managing the data that goes through the system
|
||||
local function packData(data)
|
||||
-- returns the data that was sterilized
|
||||
local dat = bin.new()
|
||||
dat:addBlock(#type(data), 1)
|
||||
dat:addBlock(type(data)) -- The type is first
|
||||
if type(data) == "table" then
|
||||
dat:addBlock(data, nil, "t")
|
||||
elseif type(data) == "userdata" then
|
||||
error("Cannot sterilize userdata!")
|
||||
elseif type(data) == "number" then
|
||||
dat:addBlock(data, nil, "d")
|
||||
elseif type(data) == "string" then
|
||||
dat:addBlock(#data, 4)
|
||||
dat:addBlock(data, nil, "s")
|
||||
elseif type(data) == "boolean" then
|
||||
dat:addBlock(data, 1)
|
||||
elseif type(data) == "function" then
|
||||
dat:addBlock(data, nil, "f")
|
||||
end
|
||||
return dat.data
|
||||
end
|
||||
local function resolveData(data)
|
||||
-- returns the data that was sterilized
|
||||
local dat = bin.new(data)
|
||||
local tlen = dat:getBlock("n", 1)
|
||||
local tp = dat:getBlock("s", tlen)
|
||||
if tp == "table" then
|
||||
return dat:getBlock("t")
|
||||
elseif tp == "number" then
|
||||
return dat:getBlock("d")
|
||||
elseif tp == "string" then
|
||||
local num = dat:getBlock("n", 4)
|
||||
return dat:getBlock("s", num)
|
||||
elseif tp == "boolean" then
|
||||
return dat:getBlock("b", 1)
|
||||
elseif tp == "function" then
|
||||
return dat:getBlock("f")
|
||||
end
|
||||
end
|
||||
|
||||
-- internal global system
|
||||
local GLOBAL = {}
|
||||
local PROXY = {}
|
||||
setmetatable(
|
||||
GLOBAL,
|
||||
{
|
||||
__index = function(t, k)
|
||||
return PROXY[k]
|
||||
end,
|
||||
__newindex = function(t, k, v)
|
||||
local v = v
|
||||
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)
|
||||
if not port then
|
||||
error("You must provide a port in order to host the node manager!")
|
||||
end
|
||||
local server = net:newTCPServer(port)
|
||||
server.nodes = {}
|
||||
server.timeouts = {}
|
||||
server.OnNodeAdded = multi:newConnection()
|
||||
server.OnNodeRemoved = multi:newConnection()
|
||||
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)
|
||||
while true do
|
||||
if server.timeouts[cid] == true then
|
||||
server.OnNodeRemoved:Fire(server.nodes[cid])
|
||||
server.nodes[cid] = nil
|
||||
server.timeouts[cid] = nil
|
||||
thread.kill()
|
||||
else
|
||||
server.timeouts[cid] = true
|
||||
server:send(cid, "ping")
|
||||
end
|
||||
thread.sleep(1)
|
||||
end
|
||||
end
|
||||
)
|
||||
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
|
||||
elseif cmd == "P" then
|
||||
server.timeouts[cid] = nil
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
-- The main driving force of the network manager: Nodes
|
||||
function multi:newNode(settings)
|
||||
multi:enableLoadDetection()
|
||||
settings = settings or {}
|
||||
-- Here we have to use the net library to broadcast our node across the network
|
||||
math.randomseed(os.time())
|
||||
local name = settings.name or multi.randomString(8)
|
||||
local node = {}
|
||||
node.name = name
|
||||
multi.OnError(
|
||||
function(i, error)
|
||||
node.OnError:Fire(node, error, node.server)
|
||||
end
|
||||
)
|
||||
node.server = net:newUDPServer(0) -- hosts the node using the default port
|
||||
_, node.port = node.server.udp:getsockname()
|
||||
node.connections = net.ClientCache
|
||||
node.queue = Queue:newQueue()
|
||||
node.functions = bin.stream("RegisteredFunctions.dat", false)
|
||||
node.hasFuncs = {}
|
||||
node.OnError = multi:newConnection()
|
||||
node.OnError(
|
||||
function(node, err, master)
|
||||
multi.print("ERROR", err, node.name)
|
||||
local temp = bin.new()
|
||||
temp:addBlock(#node.name, 2)
|
||||
temp:addBlock(node.name)
|
||||
temp:addBlock(#err, 2)
|
||||
temp:addBlock(err)
|
||||
for i, v in pairs(node.connections) do
|
||||
multi.print(i)
|
||||
v[1]:send(v[2], char(CMD_ERROR) .. temp.data, v[3])
|
||||
end
|
||||
end
|
||||
)
|
||||
if settings.managerDetails then
|
||||
local c = net:newTCPClient(settings.managerDetails[1], settings.managerDetails[2])
|
||||
if not c then
|
||||
multi.print("Cannot connect to the node manager! Ensuring broadcast is enabled!")
|
||||
settings.noBroadCast = false
|
||||
else
|
||||
c.OnDataRecieved(
|
||||
function(self, data)
|
||||
if data == "ping" then
|
||||
self:send("P")
|
||||
end
|
||||
end
|
||||
)
|
||||
c:send("RNODE_" .. name .. "|" .. net.getLocalIP() .. "|" .. node.port)
|
||||
end
|
||||
end
|
||||
if not settings.preload then
|
||||
if node.functions:getSize() ~= 0 then
|
||||
multi.print("We have function(s) to preload!")
|
||||
local len = node.functions:getBlock("n", 1)
|
||||
local name, func
|
||||
while len do
|
||||
name = node.functions:getBlock("s", len)
|
||||
len = node.functions:getBlock("n", 2)
|
||||
func = node.functions:getBlock("s", len)
|
||||
len = node.functions:read(1)
|
||||
_G[name] = resolveData(func)
|
||||
node.hasFuncs[name] = true
|
||||
if not len then
|
||||
break
|
||||
end
|
||||
len = byte(len)
|
||||
end
|
||||
end
|
||||
end
|
||||
function node:pushTo(name, data)
|
||||
node:sendTo(name, char(CMD_QUEUE) .. packData(data))
|
||||
end
|
||||
function node:peek()
|
||||
return node.queue:peek()
|
||||
end
|
||||
function node:pop()
|
||||
return node.queue:pop()
|
||||
end
|
||||
function node:getConsole()
|
||||
local c = {}
|
||||
local conn = node.connections
|
||||
function c.print(...)
|
||||
local data = char(CMD_CONSOLE) .. packData({...})
|
||||
for i, v in pairs(conn) do
|
||||
--print(i)
|
||||
v[1]:send(v[2], data, v[3])
|
||||
end
|
||||
end
|
||||
-- function c:printTo()
|
||||
|
||||
-- end
|
||||
return c
|
||||
end
|
||||
node.loadRate = 1
|
||||
-- Lets tell the network we are alive!
|
||||
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
|
||||
server:send(ip, char(CMD_PONG), port)
|
||||
elseif cmd == CMD_QUEUE then
|
||||
node.queue:push(resolveData(dat))
|
||||
elseif cmd == CMD_REG then
|
||||
if not settings.allowRemoteRegistering then
|
||||
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
|
||||
)
|
||||
function node:sendTo(name, data)
|
||||
local conn = node.connections[name]
|
||||
conn[1]:send(conn[2], data, conn[3])
|
||||
end
|
||||
if not settings.noBroadCast then
|
||||
node.server:broadcast("NODE_" .. name)
|
||||
end
|
||||
return node
|
||||
end
|
||||
|
||||
-- Masters
|
||||
function multi:newMaster(settings) -- You will be able to have more than one master connecting to node(s) if that is what you want to do. I want you to be able to have the freedom to code any way that you want to code.
|
||||
local master = {}
|
||||
local settings = settings or {}
|
||||
master.name = settings.name or multi.randomString(8)
|
||||
local name = master.name
|
||||
master.conn = multi:newConnection()
|
||||
master.conn2 = multi:newConnection()
|
||||
master.OnFirstNodeConnected = multi:newConnection()
|
||||
master.OnNodeConnected = multi:newConnection()
|
||||
master.OnError = multi:newConnection()
|
||||
master.queue = Queue:newQueue()
|
||||
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.OnFirstNodeConnected:Fire(node)
|
||||
self:Pause()
|
||||
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
|
||||
else
|
||||
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
|
||||
elseif cmd == "R" then
|
||||
local name = data:sub(2, -1)
|
||||
master.connections[name] = nil
|
||||
end
|
||||
end
|
||||
)
|
||||
client:send("G") -- init your connection as a master
|
||||
end
|
||||
end
|
||||
function master:doToAll(func)
|
||||
for i, v in pairs(master.connections) do
|
||||
func(i, v)
|
||||
end
|
||||
end
|
||||
function master:register(name, node, func)
|
||||
if not node then
|
||||
error("You must specify a node to execute a command on!")
|
||||
end
|
||||
local temp = bin.new()
|
||||
local fData = packData(func)
|
||||
temp:addBlock(CMD_REG, 1)
|
||||
temp:addBlock(#name, 1)
|
||||
temp:addBlock(name, #name)
|
||||
temp:addBlock(#fData, 2)
|
||||
temp:addBlock(fData, #fData)
|
||||
master:sendTo(node, temp.data)
|
||||
end
|
||||
function master:execute(name, node, ...)
|
||||
if not node then
|
||||
error("You must specify a node to execute a command on!")
|
||||
end
|
||||
if not name then
|
||||
error("You must specify a function name to call on the node!")
|
||||
end
|
||||
local args = packData {...}
|
||||
local name = name
|
||||
local node = node
|
||||
local temp, len, data
|
||||
temp = bin.new()
|
||||
temp:addBlock(CMD_CALL, 1)
|
||||
temp:addBlock(#name, 1)
|
||||
temp:addBlock(name, #name)
|
||||
temp:addBlock(#args, 4)
|
||||
temp:addBlock(args, #args)
|
||||
master:sendTo(node, temp.data)
|
||||
end
|
||||
function master:pushTo(name, data)
|
||||
master:sendTo(name, char(CMD_QUEUE) .. packData(data))
|
||||
end
|
||||
function master:peek()
|
||||
return self.queue:peek()
|
||||
end
|
||||
function master:pop()
|
||||
return self.queue:pop()
|
||||
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
|
||||
local fData = packData(func)
|
||||
local tab = {...}
|
||||
local aData = ""
|
||||
if #tab ~= o then
|
||||
aData = (packData {...})
|
||||
else
|
||||
aData = (packData {"NO", "ARGS"})
|
||||
end
|
||||
local temp = bin.new()
|
||||
temp:addBlock(#aData, 4)
|
||||
local len = temp.data
|
||||
local temp2 = bin.new()
|
||||
temp2:addBlock(#fData, 4)
|
||||
local len2 = temp2.data
|
||||
if not name then
|
||||
local name = self:getFreeNode()
|
||||
if not name then
|
||||
name = self:getRandomNode()
|
||||
end
|
||||
if name == nil then
|
||||
multi:newEvent(
|
||||
function()
|
||||
return name ~= nil
|
||||
end
|
||||
):OnEvent(
|
||||
function(evnt)
|
||||
self:sendTo(name, char(CMD_TASK) .. len .. aData .. len2 .. fData)
|
||||
evnt:Destroy()
|
||||
end
|
||||
):SetName("DelayedSendTask"):SetName("DelayedSendTask"):SetTime(8):OnTimedOut(
|
||||
function(self)
|
||||
self:Destroy()
|
||||
end
|
||||
)
|
||||
else
|
||||
self:sendTo(name, char(CMD_TASK) .. len .. aData .. len2 .. fData)
|
||||
end
|
||||
else
|
||||
local name = "NODE_" .. name
|
||||
self:sendTo(name, char(CMD_TASK) .. len .. aData .. len2 .. fData)
|
||||
end
|
||||
end
|
||||
function master:sendTo(name, data)
|
||||
if name:sub(1, 5) ~= "NODE_" then
|
||||
name = "NODE_" .. name
|
||||
end
|
||||
if self.connections[name] == nil then
|
||||
multi:newEvent(
|
||||
function()
|
||||
return self.connections[name] ~= nil
|
||||
end
|
||||
):OnEvent(
|
||||
function(evnt)
|
||||
self.connections[name]:send(data)
|
||||
evnt:Destroy()
|
||||
end
|
||||
):SetName("DelayedSendTask"):SetTime(8):OnTimedOut(
|
||||
function(self)
|
||||
self:Destroy()
|
||||
end
|
||||
)
|
||||
else
|
||||
self.connections[name]:send(data)
|
||||
end
|
||||
end
|
||||
function master:getFreeNode()
|
||||
local count = 0
|
||||
local min = math.huge
|
||||
local refO
|
||||
for i, v in pairs(master.loads) do
|
||||
if v < min then
|
||||
min = v
|
||||
refO = i
|
||||
end
|
||||
end
|
||||
return refO
|
||||
end
|
||||
function master:getRandomNode()
|
||||
local list = {}
|
||||
for i, v in pairs(master.connections) do
|
||||
list[#list + 1] = i:sub(6, -1)
|
||||
end
|
||||
return list[math.random(1, #list)]
|
||||
end
|
||||
net.OnCastedClientInfo(
|
||||
function(client, name, ip, port)
|
||||
multi.OnGUpdate(
|
||||
function(k, v)
|
||||
client:send(table.concat {char(CMD_GLOBAL), k, "|", v})
|
||||
end
|
||||
)
|
||||
local nodename
|
||||
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
|
||||
net:newCastedClients("NODE_(.+)") -- Searches for nodes and connects to them, the master.clients table will contain them by name
|
||||
end
|
||||
return master
|
||||
end
|
||||
-- The init function that gets returned
|
||||
multi.print("Integrated Network Parallelism")
|
||||
return {
|
||||
init = function()
|
||||
return GLOBAL
|
||||
end
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
_G["__CHANNEL__"] = {}
|
||||
local channel = {}
|
||||
channel.__index = channel
|
||||
|
||||
-- Creates/Gets a channel of name
|
||||
function channel:newChannel(name)
|
||||
local chan = _G["__CHANNEL__"]
|
||||
if chan then
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
local multi, thread = require("multi"):init()
|
||||
local cmd = require("multi.integration.networkManager.cmds")
|
||||
local node = require("multi.integration.networkManager.node")
|
||||
local net = require("net")
|
||||
local bin = require("bin")
|
||||
local child = {}
|
||||
child.__index = child
|
||||
function multi:newChildNode(cd)
|
||||
local c = {}
|
||||
setmetatable(c,child)
|
||||
local name
|
||||
if cd then
|
||||
if cd.name then
|
||||
name = cd.name
|
||||
end
|
||||
c.node = node:new(cd.nodePort or cmd.defaultPort,nil,name)
|
||||
if cd.managerHost then
|
||||
cd.managerPort = cd.managerPort or cmd.defaultManagerPort
|
||||
c.node:registerWithManager(cd.managerHost,cd.managerPort)
|
||||
end
|
||||
end
|
||||
return c
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
return function(self,data)
|
||||
local cmd,data = data:match("!(.-)!(.*)")
|
||||
if cmd == "PONG" then
|
||||
--
|
||||
elseif cmd == "CHANNEL" then
|
||||
--
|
||||
elseif cmd == "RETURNS" then
|
||||
local rets = bin.new(data):getBlock("t")
|
||||
self.node.master.OnDataReturned:Fire(rets)
|
||||
end
|
||||
end
|
||||
@@ -1,15 +1,19 @@
|
||||
local cmds = {
|
||||
ERROR = 0x00,
|
||||
PING = 0x01,
|
||||
PONG = 0x02,
|
||||
QUEUE = 0x03,
|
||||
TASK = 0x04,
|
||||
INITNODE = 0x05,
|
||||
INITMASTER = 0x06,
|
||||
GLOBAL = 0x07,
|
||||
LOAD = 0x08,
|
||||
CALL = 0x09,
|
||||
REG = 0x0A,
|
||||
CONSOLE = 0x0B,
|
||||
defaultManagerPort = 0XDE2,
|
||||
defaultWait = 0X002,
|
||||
defaultPort = 0X000, -- We will let the OS assign us one
|
||||
standardSkip = 0X018,
|
||||
ERROR = 0X000,
|
||||
PING = 0X001,
|
||||
PONG = 0X002,
|
||||
QUEUE = 0X003,
|
||||
TASK = 0X004,
|
||||
INITNODE = 0X005,
|
||||
INITMASTER = 0X006,
|
||||
GLOBAL = 0X007,
|
||||
LOAD = 0X008,
|
||||
CALL = 0X009,
|
||||
REG = 0X00A,
|
||||
CONSOLE = 0X00B,
|
||||
}
|
||||
return cmds
|
||||
@@ -23,48 +23,141 @@ SOFTWARE.
|
||||
]]
|
||||
local multi, thread = require("multi"):init()
|
||||
local cmd = require("multi.integration.networkManager.cmds")
|
||||
local node = require("multi.integration.networkManager.node")
|
||||
local net = require("net")
|
||||
local bin = require("bin")
|
||||
local master = {}
|
||||
master.__index = master
|
||||
function multi:newMasterNode(connectionDetails)
|
||||
local c = {}
|
||||
c.OnDataRecieved = multi:newConnection()
|
||||
c.defaultNode = ""
|
||||
c.nodes = {}
|
||||
if connectionDetails then
|
||||
-- We do what we do
|
||||
else
|
||||
-- Lets make assumptions
|
||||
function master:addNode(ip,port)
|
||||
return node:new(ip,port)
|
||||
end
|
||||
function master:getNodesFromBroadcast()
|
||||
net:newCastedClients("NODE_.+")
|
||||
net.OnCastedClientInfo(function(client, n, ip, port)
|
||||
self.nodes[n] = node:new(client)
|
||||
end)
|
||||
end
|
||||
function master:getNodesFromManager(ip,port)
|
||||
local mn = self.nodes
|
||||
if not self.manager then
|
||||
self.manager = net:newTCPClient(ip,port)
|
||||
if not self.manager then
|
||||
error("Unable to connect to the node Manager! Is it running? Perhaps the hostname or port is incorrect!")
|
||||
end
|
||||
end
|
||||
setmetatable(c, master)
|
||||
return c
|
||||
self.manager.OnDataRecieved(function(self,data,client)
|
||||
local cmd = data:match("!(.+)!")
|
||||
data = data:gsub("!"..cmd.."!","")
|
||||
if cmd == "NODE" then
|
||||
local n,h,p = data:match("(.-)|(.-)|(.+)")
|
||||
mn[n] = node:new(h,tonumber(p))
|
||||
end
|
||||
end)
|
||||
self.manager:send("!NODES!")
|
||||
end
|
||||
function master:setDefaultNode(nodeName)
|
||||
-- Verify Node is active
|
||||
if self:nodeExists(nodeName) then
|
||||
self.defaultNode = nodeName
|
||||
end
|
||||
end
|
||||
function master:newNetworkThread(nodeName,func)
|
||||
--
|
||||
function master:getRandomNode()
|
||||
local t = {}
|
||||
for i,v in pairs(self.nodes) do t[#t+1] = i end
|
||||
return t[math.random(1,#t)]
|
||||
end
|
||||
local netID = 0
|
||||
function master:newNetworkThread(nodeName,func,...)
|
||||
local args = {...}
|
||||
local dat = bin.new()
|
||||
local ret
|
||||
local nID = netID
|
||||
local conn = multi:newConnection()
|
||||
multi:newThread(function()
|
||||
dat:addBlock{
|
||||
args = args,
|
||||
func = func,
|
||||
id = netID
|
||||
}
|
||||
netID = netID + 1
|
||||
if type(nodeName) == "function" then
|
||||
func = nodeName
|
||||
nodeName = self.defaultNode or self:getRandomNode()
|
||||
if not func then
|
||||
error("You must provide a function!")
|
||||
end
|
||||
end
|
||||
self:sendTo(nodeName,"!N_THREAD!"..dat.data)
|
||||
self.OnDataReturned(function(rets)
|
||||
if rets.ID == nID then
|
||||
conn:Fire(unpack(rets.rets))
|
||||
end
|
||||
end)
|
||||
end)
|
||||
return conn
|
||||
end
|
||||
function master:newNetworkChannel(nodeName)
|
||||
--
|
||||
end
|
||||
function master:sendTo(nodeName,data)
|
||||
if self:nodeExists(nodeName):wait() then
|
||||
print("It exists!")
|
||||
end
|
||||
self:queue("send",nodeName,data)
|
||||
end
|
||||
master.nodeExists = thread:newFunction(function(self,nodeName)
|
||||
function master:demandNodeExistance(nodeName)
|
||||
if self.nodes[nodeName] then
|
||||
local wait = nodes[nodeName]:ping()
|
||||
local bool = thread.hold(function()
|
||||
return wait()
|
||||
end)
|
||||
return bool
|
||||
return multi.hold(self.nodes[nodeName]:ping().pong)
|
||||
else
|
||||
return false
|
||||
end
|
||||
end)
|
||||
end
|
||||
function master:queue(c,...)
|
||||
table.insert(self._queue,{c,{...}})
|
||||
end
|
||||
function multi:newMasterNode(cd)
|
||||
local c = {}
|
||||
setmetatable(c, master)
|
||||
c.OnNodeDiscovered = multi:newConnection()
|
||||
c.OnNodeRemoved = multi:newConnection()
|
||||
c.OnDataRecieved = multi:newConnection()
|
||||
c.OnDataReturned = multi:newConnection()
|
||||
c.defaultNode = ""
|
||||
c.nodes = {}
|
||||
setmetatable(c.nodes,
|
||||
{__newindex = function(t,k,v)
|
||||
rawset(t,k,v)
|
||||
v.master = c
|
||||
c.OnNodeDiscovered:Fire(k,v)
|
||||
end})
|
||||
c._queue = {}
|
||||
if cd then
|
||||
if cd.nodeHost then
|
||||
cd.nodePort = cd.nodePort or cmd.defaultPort
|
||||
local n,no = c:addNode(cd.nodeHost,cd.nodePort)
|
||||
if n then
|
||||
c.nodes[n] = no
|
||||
end
|
||||
elseif cd.managerHost then
|
||||
cd.managerPort = cd.managerPort or cmd.defaultManagerPort
|
||||
c:getNodesFromManager(cd.managerHost,cd.managerPort)
|
||||
else
|
||||
c:getNodesFromBroadcast()
|
||||
end
|
||||
else
|
||||
c:getNodesFromBroadcast()
|
||||
end
|
||||
multi:newThread("CMDQueueProcessor",function()
|
||||
while true do
|
||||
thread.skip(128)
|
||||
local data = table.remove(c._queue,1)
|
||||
if data then
|
||||
local cmd = data[1]
|
||||
if cmd == "send" then
|
||||
local nodeName = data[2][1]
|
||||
local dat = data[2][2]
|
||||
c.nodes[nodeName]:send(dat)
|
||||
end
|
||||
end
|
||||
end
|
||||
end):OnError(function(...)
|
||||
print(...)
|
||||
end)
|
||||
return c
|
||||
end
|
||||
@@ -0,0 +1,104 @@
|
||||
local net = require("net")
|
||||
local cmd = require("multi.integration.networkManager.cmds")
|
||||
local multi,thread = require("multi"):init()
|
||||
local node = {}
|
||||
node.__index = node
|
||||
local rand = {}
|
||||
for i = 65,90 do
|
||||
rand[#rand+1] = string.char(i)
|
||||
end
|
||||
local function randName(n)
|
||||
local str = {}
|
||||
for i=1,(n or 10) do
|
||||
str[#str+1] = rand[math.random(1,#rand)]
|
||||
end
|
||||
return table.concat(str)
|
||||
end
|
||||
local getNames = thread:newFunction(function(names)
|
||||
local listen = socket.udp() -- make a new socket
|
||||
listen:setsockname(net.getLocalIP(), 11111)
|
||||
listen:settimeout(0)
|
||||
local data, ip, port = listen:receivefrom()
|
||||
thread.holdWithin(1,function()
|
||||
if data then
|
||||
local n, tp, ip, port = data:match("(%S-)|(%S-)|(%S-):(%d+)")
|
||||
if n then
|
||||
names[n]=true
|
||||
end
|
||||
end
|
||||
end)
|
||||
return multi.NIL
|
||||
end)
|
||||
local function setName(ref,name)
|
||||
if name then
|
||||
ref.name = "NODE_"..name
|
||||
ref.connection:broadcast(name)
|
||||
return
|
||||
end
|
||||
local names = {}
|
||||
getNames(names).wait() -- Prevents duplicate names from spawning!
|
||||
local name = randName()
|
||||
while names["NODE_"..name] do
|
||||
name = randName()
|
||||
end
|
||||
ref.name = "NODE_"..name
|
||||
ref.connection:broadcast(ref.name)
|
||||
end
|
||||
node.ServerCode = require("multi.integration.networkManager.serverSide")
|
||||
node.ClientCode = require("multi.integration.networkManager.clientSide")
|
||||
function node.random()
|
||||
return randName(12)
|
||||
end
|
||||
function node:registerWithManager(ip,port)
|
||||
if self.type ~= "server" then return end
|
||||
if not self.manager then
|
||||
self.manager = net:newTCPClient(ip,port)
|
||||
if not self.manager then
|
||||
error("Unable to connect to the node Manager! Is it running? Perhaps the hostname or port is incorrect!")
|
||||
end
|
||||
end
|
||||
thread:newFunction(function()
|
||||
thread.hold(function() return self.name end)
|
||||
self.manager:send("!REG_NODE!"..self.name.."|"..net.getLocalIP().."|"..self.connection.port)
|
||||
end)()
|
||||
end
|
||||
function node:new(host,port,name)
|
||||
local c = {}
|
||||
c.links = {}
|
||||
setmetatable(c,node)
|
||||
if type(host)=="number" or type(host)=="nil" then
|
||||
c.connection = net:newTCPServer(host or cmd.defaultPort)
|
||||
c.connection:enableBinaryMode()
|
||||
c.type = "server"
|
||||
c.connection.node = c
|
||||
c.connection.OnDataRecieved(self.ServerCode)
|
||||
setName(c)
|
||||
elseif type(host)=="table" and host.Type == "tcp" then
|
||||
c.connection = host
|
||||
c.connection:enableBinaryMode()
|
||||
c.type = "client"
|
||||
c.connection.node = c
|
||||
c.connection.OnDataRecieved(self.ClientCode)
|
||||
c.name = "MASTER_NODE"
|
||||
elseif type(host) == "string" and type(port)=="number" then
|
||||
c.connection = net:newTCPClient(host, port)
|
||||
c.connection:enableBinaryMode()
|
||||
c.type = "client"
|
||||
c.connection.node = c
|
||||
c.connection.OnDataRecieved(self.ClientCode)
|
||||
c.name = "MASTER_NODE"
|
||||
else
|
||||
error("Invalid arguments!")
|
||||
end
|
||||
return c
|
||||
end
|
||||
function node:ping()
|
||||
if self.type ~= "client" then return end
|
||||
self:send("!PING!")
|
||||
return {pong=self.connection.OnDataRecieved}
|
||||
end
|
||||
function node:send(data)
|
||||
if self.type ~= "client" then return end
|
||||
self.connection:send(data)
|
||||
end
|
||||
return node
|
||||
@@ -0,0 +1,25 @@
|
||||
local multi, thread = require("multi"):init()
|
||||
local cmd = require("multi.integration.networkManager.cmds")
|
||||
local net = require("net")
|
||||
local bin = require("bin")
|
||||
local nodes = { -- Testing stuff
|
||||
|
||||
}
|
||||
function multi:newNodeManager(port)
|
||||
print("Running node manager on port: "..(port or cmd.defaultManagerPort))
|
||||
local server = net:newTCPServer(port or cmd.defaultManagerPort)
|
||||
server.OnDataRecieved(function(serv, data, client)
|
||||
local cmd = data:match("!(.+)!")
|
||||
data = data:gsub("!"..cmd.."!","")
|
||||
if cmd == "NODES" then
|
||||
for i,v in ipairs(nodes) do
|
||||
-- Sample data
|
||||
serv:send(client, "!NODE!".. v[1].."|"..v[2].."|"..v[3])
|
||||
end
|
||||
elseif cmd == "REG_NODE" then
|
||||
local name, ip, port = data:match("(.-)|(.-)|(.+)")
|
||||
table.insert(nodes,{name,ip,port})
|
||||
print("Registering Node:",name, ip, port)
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
local bin, bits = require("bin").init()
|
||||
return function(self,data,client)
|
||||
local cmd,data = data:match("!(.-)!(.*)")
|
||||
--print("SERVER",cmd,data)
|
||||
if cmd == "PING" then
|
||||
self:send(client,"!PONG!")
|
||||
elseif cmd == "N_THREAD" then
|
||||
local dat = bin.new(data)
|
||||
local t = dat:getBlock("t")
|
||||
local ret = bin.new()
|
||||
ret:addBlock{ID = t.id,rets = {t.func(unpack(t.args))}}
|
||||
self:send(client,"!RETURNS!"..ret:getData())
|
||||
elseif cmd == "CHANNEL" then
|
||||
local dat = bin.new(data):getBlock("t")
|
||||
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user