diff --git a/init.lua b/init.lua index 10ba6d5..f93a1ae 100644 --- a/init.lua +++ b/init.lua @@ -2152,8 +2152,7 @@ local init = false multi.settingsHook = multi:newConnection() function multi.init(settings, realsettings) if settings == multi then settings = realsettings end - if init then return _G["$multi"].multi,_G["$multi"].thread end - init = true + if type(settings)=="table" then multi.defaultSettings = settings @@ -2164,18 +2163,22 @@ function multi.init(settings, realsettings) multi.mainloop = multi.mainloopRef end - if settings.findopt then - find_optimization = true - doOpt() - multi.enableOptimization:Fire(multi, thread) - end + if not init then + + if settings.findopt then + find_optimization = true + doOpt() + multi.enableOptimization:Fire(multi, thread) + end - if settings.debugging then - require("multi.integration.debugManager") - end + if settings.debugging then + require("multi.integration.debugManager") + end - multi.settingsHook:Fire(settings) + multi.settingsHook:Fire(settings) + end end + init = true return _G["$multi"].multi,_G["$multi"].thread end diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index 82d5610..f9d0848 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -85,7 +85,8 @@ function multi:newSystemThread(name, func, ...) THREAD_ID = count, THREAD = THREAD, GLOBAL = GLOBAL, - _Console = __ConsoleLinda + _Console = __ConsoleLinda, + _DEFER = {} } if GLOBAL["__env"] then for i,v in pairs(GLOBAL["__env"]) do @@ -97,26 +98,16 @@ function multi:newSystemThread(name, func, ...) globals = globe, priority = c.priority },function(...) - local profi - - if multi_settings.debug then - profi = require("proFI") - profi:start() - end - multi, thread = require("multi"):init(multi_settings) require("multi.integration.lanesManager.extensions") require("multi.integration.sharedExtensions") local has_error = true returns = {pcall(func, ...)} return_linda:set("returns", returns) - has_error = false - if profi then - multi.OnExit(function(...) - profi:stop() - profi:writeReport("Profiling Report [".. THREAD_NAME .."].txt") - end) + for i,v in pairs(_DEFER) do + pcall(v) end + has_error = false end)(...) count = count + 1 function c:getName() diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index 2c416ea..2dc231c 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -88,6 +88,10 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) function THREAD.kill() -- trigger the lane destruction error("Thread was killed!\1") end + + function THREAD.sync() + -- Maybe do something... + end function THREAD.pushStatus(...) local args = multi.pack(...) @@ -138,13 +142,7 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) end function THREAD.defer(func) - local m = {onexit = function() func() end} - if _VERSION >= "Lua 5.2" then - setmetatable(m, {__gc = m.onexit}) - else - m.sentinel = newproxy(true) - getmetatable(m.sentinel).__gc = m.onexit - end + table.insert(_DEFER, func) end return GLOBAL, THREAD diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 9a22c5b..ee29baf 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -11,25 +11,27 @@ function multi:newSystemThreadedQueue(name) c.Name = name c.Type = multi.registerType("s_queue") - c.chan = love.thread.newChannel() + c.chan = love.thread.getChannel(name) function c:push(dat) self.chan:push(THREAD.packValue(dat)) end function c:pop() - return THREAD.unpackValue(self.chan:pop()) + return THREAD.unpackValue(self.chan:pop()) end function c:peek() - return THREAD.unpackValue(self.chan:peek()) + return THREAD.unpackValue(self.chan:peek()) end function c:init() + self.chan = love.thread.getChannel(self.Name) return self end function c:Hold(opt) + local multi, thread = require("multi"):init() if opt.peek then return thread.hold(function() return self:peek() @@ -73,6 +75,7 @@ function multi:newSystemThreadedTable(name) c.__init = c.init function c:Hold(opt) + local multi, thread = require("multi"):init() if opt.key then return thread.hold(function() return self.tab[opt.key] diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 0bc062b..44ec815 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -5,11 +5,13 @@ end local ThreadFileData = [[ ISTHREAD = true args = {...} -THREAD_ID = table.remove(args, 1) -THREAD_NAME = table.remove(args, 1) -GLOBAL, THREAD = require("multi.integration.loveManager.threads"):init() -__FUNC = THREAD.unpackValue(table.remove(args, 1)) -ARGS = THREAD.unpackValue(table.remove(args, 1)) +THREAD_ID = args[1] +THREAD_NAME = args[2] +GLOBAL, THREAD, DEFER = require("multi.integration.loveManager.threads"):init() +__FUNC = THREAD.unpackValue(args[3]) +ARGS = THREAD.unpackValue(args[4]) +settings = args[5] +if ARGS == nil then ARGS = {} end math.randomseed(THREAD_ID) math.random() math.random() @@ -21,10 +23,16 @@ if GLOBAL["__env"] then _G[i] = v end end -multi, thread = require("multi"):init() +multi, thread = require("multi"):init{error=true, warning=true, print=true, priority=true} +multi.defaultSettings.print = true require("multi.integration.loveManager.extensions") require("multi.integration.sharedExtensions") -stab["returns"] = {__FUNC(multi.unpack(ARGS))} +local returns = {pcall(__FUNC, multi.unpack(ARGS))} +table.remove(returns,1) +stab["returns"] = returns +for i,v in pairs(DEFER) do + pcall(v) +end ]] _G.THREAD_NAME = "MAIN_THREAD" @@ -49,7 +57,7 @@ function multi:newSystemThread(name, func, ...) c.Name = name c.ID = tid c.thread = love.thread.newThread(ThreadFileData) - c.thread:start(c.ID, c.Name, THREAD.packValue(func), THREAD.packValue({...})) + c.thread:start(c.ID, c.Name, THREAD.packValue(func), THREAD.packValue({...}), multi.defaultSettings) c.stab = THREAD.createTable(name .. c.ID) c.creationTime = os.clock() c.OnDeath = multi:newConnection() diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 82d12f1..4ad7474 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -25,19 +25,76 @@ require("love.timer") require("love.system") require("love.data") require("love.thread") -local utils = require("multi.integration.loveManager.utils") local multi, thread = require("multi"):init() -local NIL = love.data.newByteData("\3") - --- If a non table/function is supplied we just return it -local function packValue(t) - return utils.pack(t) +-- Checks if the given value is a LOVE2D object (i.e. has metatable with __index field) and if that __index field contains functions typical of LOVE2D objects +function isLoveObject(value) + -- Check if the value has metatable + if type(value) == "userdata" and getmetatable(value) then + -- Check if the metatable has the __index field + local index = getmetatable(value).__index + if type(index) == "table" then + -- Check if the metatable's __index table contains functions typical of LOVE2D objects + if index.draw or index.update or index.getWidth or index.getHeight or index.getString or index.getPointer then + return true + end + end + end + return false end --- If a non table/function is supplied we just return it -local function unpackValue(d) - return utils.unpack(d) +-- Converts any function values in a table to a string with the value "\1\2:func:" where is the Lua stringified version of the function +function tableToFunctionString(t) + if type(t) == "nil" then return "\1\2:nil:" end + if type(t) == "function" then return "\1\2:func:"..string.dump(t) end + if type(t) ~= "table" then return t end + local newtable = {} + for k, v in pairs(t) do + if type(v) == "function" then + newtable[k] = "\1\2:func:"..string.dump(v) + elseif type(v) == "table" then + newtable[k] = tableToFunctionString(v) + elseif isLoveObject(v) then + newtable[k] = v + elseif type(v) == "userdata" then + newtable[k] = tostring(v) + else + newtable[k] = v + end + end + return newtable +end + +-- Converts strings with the value "\1\2:func:" back to functions +function functionStringToTable(t) + if type(t) == "string" and t:sub(1, 8) == "\1\2:func:" then return loadstring(t:sub(9, -1)) end + if type(t) == "string" and t:sub(1, 7) == "\1\2:nil:" then return nil end + if type(t) ~= "table" then return t end + for k, v in pairs(t) do + if type(v) == "string" then + if v:sub(1, 8) == "\1\2:func:" then + t[k] = loadstring(v:sub(9, -1)) + else + t[k] = v + end + elseif type(v) == "table" then + t[k] = functionStringToTable(v) + else + t[k] = v + end + end + if t.init then + t:init() + end + return t +end + +local function packValue(t) + return tableToFunctionString(t) +end + +local function unpackValue(t) + return functionStringToTable(t) end local function createTable(n) @@ -53,6 +110,11 @@ local function createTable(n) end local function get(name) return unpackValue(love.thread.getChannel(n .. name):peek()) + -- if type(data) == "table" and data.init then + -- return data:init() + -- else + -- return data + -- end end return setmetatable({}, { @@ -67,7 +129,7 @@ local function createTable(n) end function INIT() - local GLOBAL, THREAD = createTable("__GLOBAL__"), {} + local GLOBAL, THREAD, DEFER = createTable("__GLOBAL__"), {}, {} local status_channel, console_channel = love.thread.getChannel("__status_channel__" .. THREAD_ID), love.thread.getChannel("__console_channel__") @@ -92,11 +154,7 @@ function INIT() repeat wait() until GLOBAL[name] ~= nil - if type(GLOBAL[name].__init) == "function" then - return GLOBAL[name]:__init() - else - return GLOBAL[name] - end + return GLOBAL[name] end, true) function THREAD.getCores() @@ -153,10 +211,14 @@ function INIT() end function THREAD.defer(func) - multi.OnExit(func) + table.insert(DEFER, func) end - return GLOBAL, THREAD + function THREAD.sync() + -- Maybe do something... + end + + return GLOBAL, THREAD, DEFER end return { diff --git a/integration/loveManager/utils.lua b/integration/loveManager/utils.lua deleted file mode 100644 index 46a9a91..0000000 --- a/integration/loveManager/utils.lua +++ /dev/null @@ -1,58 +0,0 @@ -require("love.data") -local utils = {} -local NIL = {Type="nil"} - ---love.data.newByteData("\2"..serpent.dump({t,true},{safe = true})) - -local ltype = function(v) return v:type() end -local t = function(value) - local v = type(value) - if v == "userdata" then - local status, return_or_err = pcall(ltype, value) - if status then return return_or_err else return "userdata" end - else return v end -end - -function utils.pack(tbl, seen) - if type(tbl) == "function" then return {["__$FUNC$__"] = love.data.newByteData(string.dump(tbl))} end - if type(tbl) ~= "table" then return tbl end - local seen = seen or {} - local result = {} - result.__isPacked = true - for i,v in pairs(tbl) do - if seen[v] then - result[i] = v - elseif t(v) == "table" then - seen[v] = true - result[i] = utils.pack(v, seen) - elseif t(v) == "function" then - result["$F"..i] = love.data.newByteData(string.dump(v)) - elseif t{v} == "userdata" then - result[i] = "userdata" - else -- Handle what we need to and pass the rest along as a value - result[i] = v - end - end - return result -end - -function utils.unpack(tbl) - if not tbl then return nil end - if type(tbl) ~= "table" then return tbl end - if tbl["__$FUNC$__"] then return loadstring(tbl["__$FUNC$__"]:getString()) end - for i,v in pairs(tbl) do - if type(i) == "string" and i:sub(1,2) == "$F" then - local rawfunc = v:getString() - v:release() - tbl[i] = nil - tbl[i:sub(3,-1)] = loadstring(rawfunc) - end - if type(v) == "table" then - utils.unpack(v) - end - end - tbl.__isPacked = nil - return tbl -end - -return utils \ No newline at end of file diff --git a/integration/loveManagerold/extensions.lua b/integration/loveManagerold/extensions.lua deleted file mode 100644 index 91f5819..0000000 --- a/integration/loveManagerold/extensions.lua +++ /dev/null @@ -1,389 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 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. -]] - -if not ISTHREAD then - multi, thread = require("multi").init() - GLOBAL = multi.integration.GLOBAL - THREAD = multi.integration.THREAD -else - GLOBAL = multi.integration.GLOBAL - THREAD = multi.integration.THREAD -end - -function multi:newSystemThreadedQueue(name) - local name = name or multi.randomString(16) - local c = {} - c.Name = name - c.Type = multi.SQUEUE - local fRef = {"\2",nil} - function c:init() - local q = {} - q.chan = love.thread.getChannel(self.Name) - function q:push(dat) - if type(dat) == "table" then - self.chan:push{"DATA",THREAD.packTable(dat)} - else - self.chan:push(dat) - end - -- if type(dat) == "function" then - -- fRef[2] = THREAD.dump(dat) - -- self.chan:push(fRef) - -- return - -- else - -- self.chan:push(dat) - -- end - end - function q:pop() - local dat = self.chan:pop() - if type(dat)=="table" and dat[1]=="DATA" then - return THREAD.unpackTable(dat[2])--THREAD.loadDump(dat[2]) - else - return dat - end - end - function q:peek() - local dat = self.chan:peek() - if type(dat)=="table" and dat[1]=="DATA" then - return THREAD.unpackTable(dat[2])--THREAD.loadDump(dat[2]) - else - return dat - end - end - return q - end - - THREAD.package(name,c) - - self:create(c) - - return c -end - -function multi:newSystemThreadedTable(name) - local name = name or multi.randomString(16) - - local c = {} - - c.Name = name - c.Type = multi.STABLE - - function c:init() - return THREAD.createTable(self.Name) - end - - THREAD.package(name,c) - - self:create(c) - - return c -end - -local jqc = 1 -function multi:newSystemThreadedJobQueue(n) - local c = {} - - c.cores = n or THREAD.getCores() - c.registerQueue = {} - c.Type = multi.SJOBQUEUE - c.funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") - c.queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") - c.queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") - c.queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") - c.id = 0 - c.OnJobCompleted = multi:newConnection() - - local allfunc = 0 - - function c:doToAll(func) - local f = THREAD.dump(func) - for i = 1, self.cores do - self.queueAll:push({allfunc,f}) - end - allfunc = allfunc + 1 - end - function c:registerFunction(name,func) - if self.funcs[name] then - error("A function by the name "..name.." has already been registered!") - end - self.funcs[name] = func - end - function c:pushJob(name,...) - self.id = self.id + 1 - self.queue:push{name,self.id,...} - return self.id - end - function c:isEmpty() - return queueJob:peek()==nil - end - local nFunc = 0 - function c:newFunction(name,func,holup) -- This registers with the queue - if type(name)=="function" then - holup = func - func = name - name = "JQ_Function_"..nFunc - end - nFunc = nFunc + 1 - c:registerFunction(name,func) - return thread:newFunction(function(...) - local id = c:pushJob(name,...) - local link - local rets - link = c.OnJobCompleted(function(jid,...) - if id==jid then - rets = multi.pack(...) - end - end) - return thread.hold(function() - if rets then - return multi.unpack(rets) or multi.NIL - end - end) - end,holup),name - end - thread:newThread("jobManager",function() - while true do - thread.yield() - local dat = c.queueReturn:pop() - if dat then - c.OnJobCompleted:Fire(multi.unpack(dat)) - end - end - end) - for i=1,c.cores do - multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) - local multi, thread = require("multi"):init() - require("love.timer") - local function atomic(channel) - return channel:pop() - end - local clock = os.clock - local funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") - local queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") - local queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") - local lastProc = clock() - local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") - local registry = {} - _G["__QR"] = queueReturn - setmetatable(_G,{__index = funcs}) - thread:newThread("startUp",function() - while true do - thread.yield() - local all = queueAll:peek() - if all and not registry[all[1]] then - lastProc = os.clock() - THREAD.loadDump(queueAll:pop()[2])() - end - end - end) - thread:newThread("runner",function() - thread.sleep(.1) - while true do - thread.yield() - local all = queueAll:peek() - if all and not registry[all[1]] then - lastProc = os.clock() - THREAD.loadDump(queueAll:pop()[2])() - end - local dat = queue:performAtomic(atomic) - if dat then - multi:newThread("Test",function() - lastProc = os.clock() - local name = table.remove(dat,1) - local id = table.remove(dat,1) - local tab = {funcs[name](multi.unpack(dat))} - table.insert(tab,1,id) - queueReturn:push(tab) - end) - end - end - end):OnError(function(...) - error(...) - end) - thread:newThread("Idler",function() - while true do - thread.yield() - if clock()-lastProc> 2 then - THREAD.sleep(.05) - else - THREAD.sleep(.001) - end - end - end) - multi:mainloop() - end,jqc) - end - - jqc = jqc + 1 - - self:create(c) - - return c -end - -function multi:newSystemThreadedConnection(name) - local name = name or multi.randomString(16) - - local c = {} - - c.Type = multi.SCONNECTION - c.CONN = 0x00 - c.TRIG = 0x01 - c.PING = 0x02 - c.PONG = 0x03 - - local subscribe = love.thread.getChannel("SUB_STC_" .. name) - - function c:init() - - self.subscribe = love.thread.getChannel("SUB_STC_" .. self.Name) - - function self:Fire(...) - local args = multi.pack(...) - if self.CID == THREAD_ID then -- Host Call - for _, link in pairs(self.links) do - love.thread.getChannel(link):push{self.TRIG, args} - end - self.proxy_conn:Fire(...) - else - self.subscribe:push{self.TRIG, args} - end - end - - local multi, thread = require("multi"):init() - self.links = {} - self.proxy_conn = multi:newConnection() - local mt = getmetatable(self.proxy_conn) - setmetatable(self, {__index = self.proxy_conn, __call = function(t,func) self.proxy_conn(func) end, __add = mt.__add}) - if self.CID == THREAD_ID then return self end - thread:newThread("STC_CONN_MAN" .. self.Name,function() - local item - local string_self_ref = "LSF_" .. multi.randomString(16) - local link_self_ref = love.thread.getChannel(string_self_ref) - self.subscribe:push{self.CONN, string_self_ref} - while true do - item = thread.hold(function() - return link_self_ref:peek() - end) - if item[1] == self.PING then - link_self_ref:push{self.PONG} - link_self_ref:pop() - elseif item[1] == self.CONN then - if string_self_ref ~= item[2] then - table.insert(self.links, love.thread.getChannel(item[2])) - end - link_self_ref:pop() - elseif item[1] == self.TRIG then - self.proxy_conn:Fire(multi.unpack(item[2])) - link_self_ref:pop() - else - -- This shouldn't be the case - end - end - end) - return self - end - - local function remove(a, b) - local ai = {} - local r = {} - for k,v in pairs(a) do ai[v]=true end - for k,v in pairs(b) do - if ai[v]==nil then table.insert(r,a[k]) end - end - return r - end - - c.CID = THREAD_ID - c.Name = name - c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. - - -- Locals will only live in the thread that creates the original object - local ping - local pong = function(link, links) - local res = thread.hold(function() - return love.thread.getChannel(link):peek()[1] == c.PONG - end,{sleep=3}) - - if not res then - for i=1,#links do - if links[i] == link then - table.remove(links,i,link) - break - end - end - else - love.thread.getChannel(link):pop() - end - end - - ping = thread:newFunction(function(self) - ping:Pause() - - multi.ForEach(self.links, function(link) -- Sync new connections - love.thread.getChannel(link):push{self.PING} - multi:newThread("pong Thread", pong, link, self.links) - end) - - thread.sleep(3) - - ping:Resume() - end, false) - - local function fire(...) - for _, link in pairs(c.links) do - love.thread.getChannel(link):push {c.TRIG, multi.pack(...)} - end - end - - thread:newThread("STC_SUB_MAN"..name,function() - local item - while true do - thread.yield() - -- We need to check on broken connections - ping(c) -- Should return instantlly and process this in another thread - item = thread.hold(function() -- This will keep things held up until there is something to process - return c.subscribe:pop() - end) - if item[1] == c.CONN then - - multi.ForEach(c.links, function(link) -- Sync new connections - love.thread.getChannel(item[2]):push{c.CONN, link} - end) - c.links[#c.links+1] = item[2] - - elseif item[1] == c.TRIG then - fire(multi.unpack(item[2])) - c.proxy_conn:Fire(multi.unpack(item[2])) - end - end - end) - --- ^^^ This will only exist in the init thread - - THREAD.package(name,c) - - self:create(c) - - return c -end -require("multi.integration.sharedExtensions") \ No newline at end of file diff --git a/integration/loveManagerold/init.lua b/integration/loveManagerold/init.lua deleted file mode 100644 index 2ab3061..0000000 --- a/integration/loveManagerold/init.lua +++ /dev/null @@ -1,138 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 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. -]] - -if ISTHREAD then - error("You cannot require the loveManager from within a thread!") -end - -local ThreadFileData = [[ -ISTHREAD = true -THREAD = require("multi.integration.loveManager.threads") -sThread = THREAD -__IMPORTS = {...} -__FUNC__=table.remove(__IMPORTS,1) -THREAD_ID=table.remove(__IMPORTS,1) -THREAD_NAME=table.remove(__IMPORTS,1) -math.randomseed(THREAD_ID) -math.random() -math.random() -math.random() -stab = THREAD.createStaticTable(THREAD_NAME .. THREAD_ID) -GLOBAL = THREAD.getGlobal() -if GLOBAL["__env"] then - local env = THREAD.unpackENV(GLOBAL["__env"]) - for i,v in pairs(env) do - _G[i] = v - end -end -multi, thread = require("multi").init() -multi.integration={} -multi.integration.GLOBAL = GLOBAL -multi.integration.THREAD = THREAD -pcall(require,"multi.integration.loveManager.extensions") -pcall(require,"multi.integration.sharedExtensions") -stab["returns"] = {THREAD.loadDump(__FUNC__)(multi.unpack(__IMPORTS))} -]] - -local multi, thread = require("multi"):init() - -local THREAD = {} -_G.THREAD_NAME = "MAIN_THREAD" -_G.THREAD_ID = 0 -multi.integration = {} -local THREAD = require("multi.integration.loveManager.threads") -local GLOBAL = THREAD.getGlobal() -multi.isMainThread = true - -function multi:newSystemThread(name, func, ...) - THREAD_ID = THREAD_ID + 1 - local c = {} - c.Type = multi.STHREAD - c.name = name - c.ID = THREAD_ID - c.thread = love.thread.newThread(ThreadFileData) - c.thread:start(THREAD.dump(func), c.ID, c.name, ...) - c.stab = THREAD.createStaticTable(name .. c.ID) - c.OnDeath = multi:newConnection() - c.OnError = multi:newConnection() - GLOBAL["__THREAD_" .. c.ID] = {ID = c.ID, Name = c.name, Thread = c.thread} - GLOBAL["__THREAD_COUNT"] = THREAD_ID - - function c:getName() return c.name end - thread:newThread(name .. "_System_Thread_Handler",function() - if name == "SystemThreaded Function Handler" then - local status_channel = love.thread.getChannel("STATCHAN_" .. c.ID) - thread.hold(function() - -- While the thread is running we might as well do something in the loop - if status_channel:peek() ~= nil then - c.statusconnector:Fire(multi.unpack(status_channel:pop())) - end - return not c.thread:isRunning() - end) - else - thread.hold(function() return not c.thread:isRunning() end) - end - -- If the thread is not running let's handle that. - local thread_err = c.thread:getError() - if thread_err == "Thread Killed!\1" then - c.OnDeath:Fire("Thread Killed!") - elseif thread_err then - c.OnError:Fire(c, thread_err) - elseif c.stab.returns then - c.OnDeath:Fire(multi.unpack(c.stab.returns)) - c.stab.returns = nil - end - end) - - c.OnError(multi.error) - - if self.isActor then - self:create(c) - else - multi.create(multi, c) - end - - return c -end - -function THREAD:newFunction(func, holdme) - return thread:newFunctionBase(function(...) - return multi:newSystemThread("SystemThreaded Function Handler", func, ...) - end, holdme, multi.SFUNCTION)() -end - -THREAD.newSystemThread = function(...) - multi:newSystemThread(...) -end - -function love.threaderror(thread, errorstr) - multi.print("Thread error!\n" .. errorstr) -end - -multi.integration.GLOBAL = GLOBAL -multi.integration.THREAD = THREAD -require("multi.integration.loveManager.extensions") -require("multi.integration.sharedExtensions") -multi.print("Integrated Love Threading!") -return {init = function() return GLOBAL, THREAD end} diff --git a/integration/loveManagerold/threads.lua b/integration/loveManagerold/threads.lua deleted file mode 100644 index 7749c98..0000000 --- a/integration/loveManagerold/threads.lua +++ /dev/null @@ -1,257 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2022 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. -]] -require("love.timer") -require("love.system") -require("love.data") -require("love.thread") -local serpent = require("multi.integration.loveManager.serpent") -local multi, thread = require("multi"):init() -local threads = {} - -function threads.loadDump(d) - return loadstring(d:getString()) -end - -function threads.dump(func) - return love.data.newByteData(string.dump(func)) -end - -function threads.packTable(table) - return love.data.newByteData(serpent.dump(table)) -end - -function threads.unpackTable(data) - return serpent.load(data:getString()) -end - -local fRef = {"func",nil} -local function manage(channel, value) - channel:clear() - if type(value) == "table" then - channel:push{"DATA",threads.packTable(value)} - else - channel:push(value) - end -end - -local GNAME = "__GLOBAL_" -local proxy = {} -function threads.set(name,val) - if not proxy[name] then proxy[name] = love.thread.getChannel(GNAME..name) end - proxy[name]:performAtomic(manage, val) -end - -function threads.get(name) - if not proxy[name] then proxy[name] = love.thread.getChannel(GNAME..name) end - local dat = proxy[name]:peek() - if type(dat)=="table" and dat[1]=="DATA" then - return threads.unpackTable(dat[2]) - else - return dat - end -end - -function threads.waitFor(name) - if thread.isThread() then - return thread.hold(function() - return threads.get(name) - end) - end - while threads.get(name)==nil do - love.timer.sleep(.001) - end - local dat = threads.get(name) - if type(dat) == "table" and dat.init then - dat.init = threads.loadDump(dat.init) - end - return dat -end - -function threads.package(name,val) - local init = val.init - val.init=threads.dump(val.init) - GLOBAL[name]=val - val.init=init -end - -function threads.getCores() - return love.system.getProcessorCount() -end - -function threads.kill() - error("Thread Killed!\1") -end - -function threads.pushStatus(...) - local status_channel = love.thread.getChannel("STATCHAN_" ..__THREADID__) - local args = multi.pack(...) - status_channel:push(args) -end - -function threads.getThreads() - local t = {} - for i=1,GLOBAL["__THREAD_COUNT"] do - t[#t+1]=GLOBAL["__THREAD_"..i] - end - return t -end - -function threads.getThread(n) - return GLOBAL["__THREAD_"..n] -end - -function threads.sleep(n) - love.timer.sleep(n) -end - -function threads.getGlobal() - return setmetatable({}, - { - __index = function(t, k) - return THREAD.get(k) - end, - __newindex = function(t, k, v) - THREAD.set(k,v) - end - } - ) -end - -function threads.packENV(env) - return threads.packTable(env) -end - -function threads.unpackENV(env) - return threads.unpackTable(env) -end - - -function threads.setENV(env, name) - name = name or "__env" - (threads.getGlobal())[name] = threads.packTable(env) -end - -function threads.getENV(name) - name = name or "__env" - return threads.unpackTable((threads.getGlobal())[name]) -end - -function threads.exposeENV(name) - name = name or "__env" - local env = threads.getENV(name) - for i,v in pairs(env) do - _G[i] = v - end -end - -function threads.createTable(n) - local _proxy = {} - local function set(name,val) - if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end - _proxy[name]:performAtomic(manage, val) - end - local function get(name) - if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end - local dat = _proxy[name]:peek() - if type(dat)=="table" and dat[1]=="DATA" then - return threads.unpackTable(dat[2]) - else - return dat - end - end - return setmetatable({}, - { - __index = function(t, k) - return get(k) - end, - __newindex = function(t, k, v) - set(k,v) - end - } - ) -end - -function threads.getConsole() - local c = {} - c.queue = love.thread.getChannel("__CONSOLE__") - function c.print(...) - c.queue:push(multi.pack(...)) - end - function c.error(err) - c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} - error(err) - end - return c -end - -if not ISTHREAD then - local queue = love.thread.getChannel("__CONSOLE__") - multi:newLoop(function(loop) - dat = queue:pop() - if dat then - print(multi.unpack(dat)) - end - end) -end - -function threads.createStaticTable(n) - local __proxy = {} - local function set(name,val) - if __proxy[name] then return end - local chan = love.thread.getChannel(n..name) - if chan:getCount()>0 then return end - chan:performAtomic(manage, val) - __proxy[name] = val - end - local function get(name) - if __proxy[name] then return __proxy[name] end - local dat = love.thread.getChannel(n..name):peek() - if type(dat)=="table" and dat[1]=="func" then - __proxy[name] = threads.loadDump(dat[2]) - return __proxy[name] - else - __proxy[name] = dat - return __proxy[name] - end - end - return setmetatable({}, - { - __index = function(t, k) - return get(k) - end, - __newindex = function(t, k, v) - set(k,v) - end - } - ) -end - -function threads.hold(n) - local dat - while not(dat) do - dat = n() - end -end - -return threads \ No newline at end of file diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index d21aebb..00ddfa3 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] local multi, thread = require("multi"):init() -local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD +local GLOBAL, THREAD = multi.integration.GLOBAL, multi.integration.THREAD local function stripUpValues(func) local dmp = string.dump(func) @@ -156,7 +156,7 @@ function multi:newSystemThreadedJobQueue(n) end end) for i=1,c.cores do - multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) + multi:newSystemThread("STJQ_"..multi.randomString(8),function(jqc) local GLOBAL, THREAD = require("multi.integration.pseudoManager"):init() local multi, thread = require("multi"):init() local clock = os.clock @@ -217,4 +217,5 @@ function multi:newSystemThreadedConnection(name) GLOBAL[name or "_"] = conn return conn end + require("multi.integration.sharedExtensions") \ No newline at end of file diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index 31106e9..46c4c9b 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -24,6 +24,8 @@ SOFTWARE. package.path = "?/init.lua;?.lua;" .. package.path local multi, thread = require("multi"):init() +local pseudoProcessor = multi:newProcessor() + if multi.integration then return { init = function() @@ -89,7 +91,7 @@ function multi:newSystemThread(name, func, ...) local GLOBAL, THREAD = activator.init(thread, env) - local th = thread:newISOThread(name, func, env, ...) + local th = pseudoProcessor:newISOThread(name, func, env, ...) th.Type = multi.registerType("s_thread", "pseudoThreads") th.OnError(multi.error) diff --git a/integration/pseudoManager/threads.lua b/integration/pseudoManager/threads.lua index 91a1667..5490607 100644 --- a/integration/pseudoManager/threads.lua +++ b/integration/pseudoManager/threads.lua @@ -110,6 +110,10 @@ local function INIT(thread) end end + function THREAD.sync() + thread.sleep(.5) + end + return GLOBAL, THREAD end diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 8526954..01bae7f 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -60,7 +60,7 @@ function multi:newProxy(list) return res end if not(self.is_init) then - THREAD.sleep(.3) + THREAD.sync() self.is_init = true local multi, thread = require("multi"):init() self.proxy_link = "PL" .. multi.randomString(12) @@ -92,7 +92,6 @@ function multi:newProxy(list) local func = table.remove(data, 1) local sref = table.remove(data, 1) local ret - if sref then ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))} else @@ -119,7 +118,8 @@ function multi:newProxy(list) end) return self else - THREAD.sleep(.3) + THREAD.sync() + if not self.funcs then return self end local function copy(obj) if type(obj) ~= 'table' then return obj end local res = {} @@ -267,12 +267,12 @@ function multi:newSystemThreadedProcessor(cores) } for _, method in pairs(implement) do - c[method] = function(self, ...) + c[method] = thread:newFunction(function(self, ...) proxy = self.spawnTask(method, ...) proxy:init() references[proxy] = self return proxy - end + end, true) end function c:newThread(name, func, ...) diff --git a/tests/conf.lua b/tests/conf.lua index 6b728c5..3a072a4 100644 --- a/tests/conf.lua +++ b/tests/conf.lua @@ -1,22 +1,3 @@ function love.conf(t) - t.identity = nil -- The name of the save directory (string) - t.version = "11.4" -- The LOVE version this game was made for (string) - t.console = true -- Attach a console (boolean, Windows only) - - t.window = false - - t.modules.audio = false -- Enable the audio module (boolean) - t.modules.event = false -- Enable the event module (boolean) - t.modules.graphics = false -- Enable the graphics module (boolean) - t.modules.image = false -- Enable the image module (boolean) - t.modules.joystick = false -- Enable the joystick module (boolean) - t.modules.keyboard = false -- Enable the keyboard module (boolean) - t.modules.math = false -- Enable the math module (boolean) - t.modules.mouse = false -- Enable the mouse module (boolean) - t.modules.physics = false -- Enable the physics module (boolean) - t.modules.sound = false -- Enable the sound module (boolean) - t.modules.system = true -- Enable the system module (boolean) - t.modules.timer = true -- Enable the timer module (boolean) - t.modules.window = false -- Enable the window module (boolean) - t.modules.thread = true -- Enable the thread module (boolean) -end + t.console = true +end \ No newline at end of file diff --git a/tests/main.lua b/tests/main.lua index b23a884..edeee3a 100644 --- a/tests/main.lua +++ b/tests/main.lua @@ -1,12 +1,10 @@ package.path = "../?/init.lua;../?.lua;"..package.path -require("runtests") -require("threadtests") --- Allows you to run "love tests" which runs the tests -multi, thread = require("multi"):init() +if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then + require("lldebugger").start() +end + GLOBAL, THREAD = require("multi.integration.loveManager"):init() - -function love.update() - multi:uManager() -end \ No newline at end of file +require("runtests") +require("threadtests") diff --git a/tests/test.lua b/tests/test.lua index 8ca374e..d5056db 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,8 +1,9 @@ package.path = "../?/init.lua;../?.lua;"..package.path multi, thread = require("multi"):init{print=true,warn=true,debugging=true} -for i,v in pairs(thread) do - print(i,v) -end +-- for i,v in pairs(thread) do +-- print(i,v) +-- end + -- require("multi.integration.priorityManager") -- multi.debugging.OnObjectCreated(function(obj, process) @@ -14,10 +15,6 @@ end -- end) - - - - -- test = multi:newProcessor("Test") -- test:setPriorityScheme(multi.priorityScheme.TimeBased) @@ -102,39 +99,39 @@ end -- multi:mainloop() -multi:setTaskDelay(.05) -multi:newTask(function() - for i = 1, 10 do - multi:newTask(function() - print("Task "..i) - end) - end -end) +-- multi:setTaskDelay(.05) +-- multi:newTask(function() +-- for i = 1, 10 do +-- multi:newTask(function() +-- print("Task "..i) +-- end) +-- end +-- end) -local conn = multi:newConnection() -conn(function() print("Test 1") end) -conn(function() print("Test 2") end) -conn(function() print("Test 3") end) -conn(function() print("Test 4") end) +-- local conn = multi:newConnection() +-- conn(function() print("Test 1") end) +-- conn(function() print("Test 2") end) +-- conn(function() print("Test 3") end) +-- conn(function() print("Test 4") end) -print("Fire 1") -conn:Fire() -conn = -conn -print("Fire 2") -conn:Fire() +-- print("Fire 1") +-- conn:Fire() +-- conn = -conn +-- print("Fire 2") +-- conn:Fire() -print(#conn) +-- print(#conn) -thread:newThread("Test thread", function() - print("Starting thread!") - thread.defer(function() -- Runs when the thread finishes execution - print("Clean up time!") - end) - --[[ - Do lot's of stuff - ]] - thread.sleep(3) -end) +-- thread:newThread("Test thread", function() +-- print("Starting thread!") +-- thread.defer(function() -- Runs when the thread finishes execution +-- print("Clean up time!") +-- end) +-- --[[ +-- Do lot's of stuff +-- ]] +-- thread.sleep(3) +-- end) multi:mainloop() diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 3f62321..9570a39 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -1,6 +1,6 @@ package.path = "D:/VSCWorkspace/?/init.lua;D:/VSCWorkspace/?.lua;"..package.path package.cpath = "C:/luaInstalls/lua5.4/lib/lua/5.4/?/core.dll;" .. package.cpath -multi, thread = require("multi"):init{error=true,warning=true,print=true}--{priority=true} +multi, thread = require("multi"):init{error=true,warning=true,print=true, priority=true} proc = multi:newProcessor("Thread Test",true) local LANES, LOVE, PSEUDO = 1, 2, 3 local env, we_good @@ -38,15 +38,15 @@ THREAD.setENV({ }) multi:newThread("Scheduler Thread",function() - -- multi:newThread(function() - -- thread.sleep(30) - -- print("Timeout tests took longer than 30 seconds") - -- multi:Stop() - -- os.exit(1) - -- end) + multi:newThread(function() + thread.sleep(30) + print("Timeout tests took longer than 30 seconds") + multi:Stop() + os.exit(1) + end) - queue = multi:newSystemThreadedQueue("Test_Queue"):init() - defer_queue = multi:newSystemThreadedQueue("Defer_Queue"):init() + queue = multi:newSystemThreadedQueue("Test_Queue") + defer_queue = multi:newSystemThreadedQueue("Defer_Queue") multi:newSystemThread("Test_Thread_0", function() defer_queue = THREAD.waitFor("Defer_Queue"):init() @@ -63,13 +63,11 @@ multi:newThread("Scheduler Thread",function() end end) - thread:newThread(function() - if thread.hold(function() - return defer_queue:pop() == "done" - end,{sleep=1}) == nil then - multi.error("Thread.defer didn't work!") - end - end) + if thread.hold(function() + return defer_queue:pop() == "done" + end,{sleep=3}) == nil then + multi.error("Thread.defer didn't work!") + end th1 = multi:newSystemThread("Test_Thread_1", function(a,b,c,d,e,f) queue = THREAD.waitFor("Test_Queue"):init() @@ -135,7 +133,7 @@ multi:newThread("Scheduler Thread",function() local ready = false - jq = multi:newSystemThreadedJobQueue(1) -- Job queue with 4 worker threads + jq = multi:newSystemThreadedJobQueue(4) -- Job queue with 4 worker threads func2 = jq:newFunction("sleep",function(a,b) THREAD.sleep(.2) end) @@ -168,7 +166,7 @@ multi:newThread("Scheduler Thread",function() --print("Test") end, 1) - multi:newSystemThread("Testing proxy copy THREAD",function(tloop) + multi:newSystemThread("PROX_THREAD",function(tloop) local multi, thread = require("multi"):init() tloop = tloop:init() multi.print("tloop type:",tloop.Type) @@ -176,19 +174,21 @@ multi:newThread("Scheduler Thread",function() thread:newThread(function() while true do thread.hold(tloop.OnLoop) - --print(THREAD_NAME,"Loopy") + print(THREAD_NAME,"Loopy") end end) tloop.OnLoop(function(a) - --print(THREAD_NAME, "Got loop...") + print(THREAD_NAME, "Got loop...") end) multi:mainloop() end, tloop:getTransferable()) + local test = tloop:getTransferable() + multi.print("tloop", tloop.Type) multi.print("tloop.OnLoop", tloop.OnLoop.Type) - thread:newThread(function() + thread:newThread("Proxy Test Thread",function() multi.print("Testing holding on a proxy connection!") thread.hold(tloop.OnLoop) multi.print("Held on proxy connection... once") @@ -197,17 +197,22 @@ multi:newThread("Scheduler Thread",function() thread.hold(tloop.OnLoop) multi.print("Held on proxy connection... finally") proxy_test = true - end) + end).OnError(print) thread:newThread(function() + thread.defer(function() + multi.print("Something happened!") + end) while true do thread.hold(tloop.OnLoop) - --print(THREAD_NAME,"Local Loopy") + multi.print(THREAD_NAME,"Local Loopy") end + end).OnError(function(...) + print("Error",...) end) tloop.OnLoop(function() - --print("OnLoop",THREAD_NAME) + print("OnLoop", THREAD_NAME) end) t, val = thread.hold(function()