Working on 16.0.0 #53

Merged
rayaman merged 120 commits from v16.0.0 into master 2024-02-25 00:00:51 -05:00
18 changed files with 210 additions and 999 deletions
Showing only changes of commit e3ac0951ab - Show all commits

View File

@ -2152,8 +2152,7 @@ local init = false
multi.settingsHook = multi:newConnection() multi.settingsHook = multi:newConnection()
function multi.init(settings, realsettings) function multi.init(settings, realsettings)
if settings == multi then settings = realsettings end 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 if type(settings)=="table" then
multi.defaultSettings = settings multi.defaultSettings = settings
@ -2164,6 +2163,8 @@ function multi.init(settings, realsettings)
multi.mainloop = multi.mainloopRef multi.mainloop = multi.mainloopRef
end end
if not init then
if settings.findopt then if settings.findopt then
find_optimization = true find_optimization = true
doOpt() doOpt()
@ -2176,6 +2177,8 @@ function multi.init(settings, realsettings)
multi.settingsHook:Fire(settings) multi.settingsHook:Fire(settings)
end end
end
init = true
return _G["$multi"].multi,_G["$multi"].thread return _G["$multi"].multi,_G["$multi"].thread
end end

View File

@ -85,7 +85,8 @@ function multi:newSystemThread(name, func, ...)
THREAD_ID = count, THREAD_ID = count,
THREAD = THREAD, THREAD = THREAD,
GLOBAL = GLOBAL, GLOBAL = GLOBAL,
_Console = __ConsoleLinda _Console = __ConsoleLinda,
_DEFER = {}
} }
if GLOBAL["__env"] then if GLOBAL["__env"] then
for i,v in pairs(GLOBAL["__env"]) do for i,v in pairs(GLOBAL["__env"]) do
@ -97,26 +98,16 @@ function multi:newSystemThread(name, func, ...)
globals = globe, globals = globe,
priority = c.priority priority = c.priority
},function(...) },function(...)
local profi
if multi_settings.debug then
profi = require("proFI")
profi:start()
end
multi, thread = require("multi"):init(multi_settings) multi, thread = require("multi"):init(multi_settings)
require("multi.integration.lanesManager.extensions") require("multi.integration.lanesManager.extensions")
require("multi.integration.sharedExtensions") require("multi.integration.sharedExtensions")
local has_error = true local has_error = true
returns = {pcall(func, ...)} returns = {pcall(func, ...)}
return_linda:set("returns", returns) return_linda:set("returns", returns)
has_error = false for i,v in pairs(_DEFER) do
if profi then pcall(v)
multi.OnExit(function(...)
profi:stop()
profi:writeReport("Profiling Report [".. THREAD_NAME .."].txt")
end)
end end
has_error = false
end)(...) end)(...)
count = count + 1 count = count + 1
function c:getName() function c:getName()

View File

@ -89,6 +89,10 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console)
error("Thread was killed!\1") error("Thread was killed!\1")
end end
function THREAD.sync()
-- Maybe do something...
end
function THREAD.pushStatus(...) function THREAD.pushStatus(...)
local args = multi.pack(...) local args = multi.pack(...)
__StatusLinda:send(nil,THREAD_ID, args) __StatusLinda:send(nil,THREAD_ID, args)
@ -138,13 +142,7 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console)
end end
function THREAD.defer(func) function THREAD.defer(func)
local m = {onexit = function() func() end} table.insert(_DEFER, func)
if _VERSION >= "Lua 5.2" then
setmetatable(m, {__gc = m.onexit})
else
m.sentinel = newproxy(true)
getmetatable(m.sentinel).__gc = m.onexit
end
end end
return GLOBAL, THREAD return GLOBAL, THREAD

View File

@ -11,7 +11,7 @@ function multi:newSystemThreadedQueue(name)
c.Name = name c.Name = name
c.Type = multi.registerType("s_queue") c.Type = multi.registerType("s_queue")
c.chan = love.thread.newChannel() c.chan = love.thread.getChannel(name)
function c:push(dat) function c:push(dat)
self.chan:push(THREAD.packValue(dat)) self.chan:push(THREAD.packValue(dat))
@ -26,10 +26,12 @@ function multi:newSystemThreadedQueue(name)
end end
function c:init() function c:init()
self.chan = love.thread.getChannel(self.Name)
return self return self
end end
function c:Hold(opt) function c:Hold(opt)
local multi, thread = require("multi"):init()
if opt.peek then if opt.peek then
return thread.hold(function() return thread.hold(function()
return self:peek() return self:peek()
@ -73,6 +75,7 @@ function multi:newSystemThreadedTable(name)
c.__init = c.init c.__init = c.init
function c:Hold(opt) function c:Hold(opt)
local multi, thread = require("multi"):init()
if opt.key then if opt.key then
return thread.hold(function() return thread.hold(function()
return self.tab[opt.key] return self.tab[opt.key]

View File

@ -5,11 +5,13 @@ end
local ThreadFileData = [[ local ThreadFileData = [[
ISTHREAD = true ISTHREAD = true
args = {...} args = {...}
THREAD_ID = table.remove(args, 1) THREAD_ID = args[1]
THREAD_NAME = table.remove(args, 1) THREAD_NAME = args[2]
GLOBAL, THREAD = require("multi.integration.loveManager.threads"):init() GLOBAL, THREAD, DEFER = require("multi.integration.loveManager.threads"):init()
__FUNC = THREAD.unpackValue(table.remove(args, 1)) __FUNC = THREAD.unpackValue(args[3])
ARGS = THREAD.unpackValue(table.remove(args, 1)) ARGS = THREAD.unpackValue(args[4])
settings = args[5]
if ARGS == nil then ARGS = {} end
math.randomseed(THREAD_ID) math.randomseed(THREAD_ID)
math.random() math.random()
math.random() math.random()
@ -21,10 +23,16 @@ if GLOBAL["__env"] then
_G[i] = v _G[i] = v
end end
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.loveManager.extensions")
require("multi.integration.sharedExtensions") 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" _G.THREAD_NAME = "MAIN_THREAD"
@ -49,7 +57,7 @@ function multi:newSystemThread(name, func, ...)
c.Name = name c.Name = name
c.ID = tid c.ID = tid
c.thread = love.thread.newThread(ThreadFileData) 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.stab = THREAD.createTable(name .. c.ID)
c.creationTime = os.clock() c.creationTime = os.clock()
c.OnDeath = multi:newConnection() c.OnDeath = multi:newConnection()

View File

@ -25,19 +25,76 @@ require("love.timer")
require("love.system") require("love.system")
require("love.data") require("love.data")
require("love.thread") require("love.thread")
local utils = require("multi.integration.loveManager.utils")
local multi, thread = require("multi"):init() local multi, thread = require("multi"):init()
local NIL = love.data.newByteData("\3") -- 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)
-- If a non table/function is supplied we just return it -- Check if the value has metatable
local function packValue(t) if type(value) == "userdata" and getmetatable(value) then
return utils.pack(t) -- 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 end
-- If a non table/function is supplied we just return it -- Converts any function values in a table to a string with the value "\1\2:func:<function_string>" where <function_string> is the Lua stringified version of the function
local function unpackValue(d) function tableToFunctionString(t)
return utils.unpack(d) 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:<function_string>" 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 end
local function createTable(n) local function createTable(n)
@ -53,6 +110,11 @@ local function createTable(n)
end end
local function get(name) local function get(name)
return unpackValue(love.thread.getChannel(n .. name):peek()) return unpackValue(love.thread.getChannel(n .. name):peek())
-- if type(data) == "table" and data.init then
-- return data:init()
-- else
-- return data
-- end
end end
return setmetatable({}, return setmetatable({},
{ {
@ -67,7 +129,7 @@ local function createTable(n)
end end
function INIT() 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), local status_channel, console_channel = love.thread.getChannel("__status_channel__" .. THREAD_ID),
love.thread.getChannel("__console_channel__") love.thread.getChannel("__console_channel__")
@ -92,11 +154,7 @@ function INIT()
repeat repeat
wait() wait()
until GLOBAL[name] ~= nil until GLOBAL[name] ~= nil
if type(GLOBAL[name].__init) == "function" then
return GLOBAL[name]:__init()
else
return GLOBAL[name] return GLOBAL[name]
end
end, true) end, true)
function THREAD.getCores() function THREAD.getCores()
@ -153,10 +211,14 @@ function INIT()
end end
function THREAD.defer(func) function THREAD.defer(func)
multi.OnExit(func) table.insert(DEFER, func)
end end
return GLOBAL, THREAD function THREAD.sync()
-- Maybe do something...
end
return GLOBAL, THREAD, DEFER
end end
return { return {

View File

@ -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

View File

@ -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")

View File

@ -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}

View File

@ -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

View File

@ -156,7 +156,7 @@ function multi:newSystemThreadedJobQueue(n)
end end
end) end)
for i=1,c.cores do 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 GLOBAL, THREAD = require("multi.integration.pseudoManager"):init()
local multi, thread = require("multi"):init() local multi, thread = require("multi"):init()
local clock = os.clock local clock = os.clock
@ -217,4 +217,5 @@ function multi:newSystemThreadedConnection(name)
GLOBAL[name or "_"] = conn GLOBAL[name or "_"] = conn
return conn return conn
end end
require("multi.integration.sharedExtensions") require("multi.integration.sharedExtensions")

View File

@ -24,6 +24,8 @@ SOFTWARE.
package.path = "?/init.lua;?.lua;" .. package.path package.path = "?/init.lua;?.lua;" .. package.path
local multi, thread = require("multi"):init() local multi, thread = require("multi"):init()
local pseudoProcessor = multi:newProcessor()
if multi.integration then if multi.integration then
return { return {
init = function() init = function()
@ -89,7 +91,7 @@ function multi:newSystemThread(name, func, ...)
local GLOBAL, THREAD = activator.init(thread, env) 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.Type = multi.registerType("s_thread", "pseudoThreads")
th.OnError(multi.error) th.OnError(multi.error)

View File

@ -110,6 +110,10 @@ local function INIT(thread)
end end
end end
function THREAD.sync()
thread.sleep(.5)
end
return GLOBAL, THREAD return GLOBAL, THREAD
end end

View File

@ -60,7 +60,7 @@ function multi:newProxy(list)
return res return res
end end
if not(self.is_init) then if not(self.is_init) then
THREAD.sleep(.3) THREAD.sync()
self.is_init = true self.is_init = true
local multi, thread = require("multi"):init() local multi, thread = require("multi"):init()
self.proxy_link = "PL" .. multi.randomString(12) self.proxy_link = "PL" .. multi.randomString(12)
@ -92,7 +92,6 @@ function multi:newProxy(list)
local func = table.remove(data, 1) local func = table.remove(data, 1)
local sref = table.remove(data, 1) local sref = table.remove(data, 1)
local ret local ret
if sref then if sref then
ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))} ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))}
else else
@ -119,7 +118,8 @@ function multi:newProxy(list)
end) end)
return self return self
else else
THREAD.sleep(.3) THREAD.sync()
if not self.funcs then return self end
local function copy(obj) local function copy(obj)
if type(obj) ~= 'table' then return obj end if type(obj) ~= 'table' then return obj end
local res = {} local res = {}
@ -267,12 +267,12 @@ function multi:newSystemThreadedProcessor(cores)
} }
for _, method in pairs(implement) do for _, method in pairs(implement) do
c[method] = function(self, ...) c[method] = thread:newFunction(function(self, ...)
proxy = self.spawnTask(method, ...) proxy = self.spawnTask(method, ...)
proxy:init() proxy:init()
references[proxy] = self references[proxy] = self
return proxy return proxy
end end, true)
end end
function c:newThread(name, func, ...) function c:newThread(name, func, ...)

View File

@ -1,22 +1,3 @@
function love.conf(t) function love.conf(t)
t.identity = nil -- The name of the save directory (string) t.console = true
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 end

View File

@ -1,12 +1,10 @@
package.path = "../?/init.lua;../?.lua;"..package.path 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() GLOBAL, THREAD = require("multi.integration.loveManager"):init()
require("runtests")
function love.update() require("threadtests")
multi:uManager()
end

View File

@ -1,8 +1,9 @@
package.path = "../?/init.lua;../?.lua;"..package.path package.path = "../?/init.lua;../?.lua;"..package.path
multi, thread = require("multi"):init{print=true,warn=true,debugging=true} multi, thread = require("multi"):init{print=true,warn=true,debugging=true}
for i,v in pairs(thread) do -- for i,v in pairs(thread) do
print(i,v) -- print(i,v)
end -- end
-- require("multi.integration.priorityManager") -- require("multi.integration.priorityManager")
-- multi.debugging.OnObjectCreated(function(obj, process) -- multi.debugging.OnObjectCreated(function(obj, process)
@ -14,10 +15,6 @@ end
-- end) -- end)
-- test = multi:newProcessor("Test") -- test = multi:newProcessor("Test")
-- test:setPriorityScheme(multi.priorityScheme.TimeBased) -- test:setPriorityScheme(multi.priorityScheme.TimeBased)
@ -102,39 +99,39 @@ end
-- multi:mainloop() -- multi:mainloop()
multi:setTaskDelay(.05) -- multi:setTaskDelay(.05)
multi:newTask(function() -- multi:newTask(function()
for i = 1, 10 do -- for i = 1, 10 do
multi:newTask(function() -- multi:newTask(function()
print("Task "..i) -- print("Task "..i)
end) -- end)
end -- end
end) -- end)
local conn = multi:newConnection() -- local conn = multi:newConnection()
conn(function() print("Test 1") end) -- conn(function() print("Test 1") end)
conn(function() print("Test 2") end) -- conn(function() print("Test 2") end)
conn(function() print("Test 3") end) -- conn(function() print("Test 3") end)
conn(function() print("Test 4") end) -- conn(function() print("Test 4") end)
print("Fire 1") -- print("Fire 1")
conn:Fire() -- conn:Fire()
conn = -conn -- conn = -conn
print("Fire 2") -- print("Fire 2")
conn:Fire() -- conn:Fire()
print(#conn) -- print(#conn)
thread:newThread("Test thread", function() -- thread:newThread("Test thread", function()
print("Starting thread!") -- print("Starting thread!")
thread.defer(function() -- Runs when the thread finishes execution -- thread.defer(function() -- Runs when the thread finishes execution
print("Clean up time!") -- print("Clean up time!")
end) -- end)
--[[ -- --[[
Do lot's of stuff -- Do lot's of stuff
]] -- ]]
thread.sleep(3) -- thread.sleep(3)
end) -- end)
multi:mainloop() multi:mainloop()

View File

@ -1,6 +1,6 @@
package.path = "D:/VSCWorkspace/?/init.lua;D:/VSCWorkspace/?.lua;"..package.path 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 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) proc = multi:newProcessor("Thread Test",true)
local LANES, LOVE, PSEUDO = 1, 2, 3 local LANES, LOVE, PSEUDO = 1, 2, 3
local env, we_good local env, we_good
@ -38,15 +38,15 @@ THREAD.setENV({
}) })
multi:newThread("Scheduler Thread",function() multi:newThread("Scheduler Thread",function()
-- multi:newThread(function() multi:newThread(function()
-- thread.sleep(30) thread.sleep(30)
-- print("Timeout tests took longer than 30 seconds") print("Timeout tests took longer than 30 seconds")
-- multi:Stop() multi:Stop()
-- os.exit(1) os.exit(1)
-- end) end)
queue = multi:newSystemThreadedQueue("Test_Queue"):init() queue = multi:newSystemThreadedQueue("Test_Queue")
defer_queue = multi:newSystemThreadedQueue("Defer_Queue"):init() defer_queue = multi:newSystemThreadedQueue("Defer_Queue")
multi:newSystemThread("Test_Thread_0", function() multi:newSystemThread("Test_Thread_0", function()
defer_queue = THREAD.waitFor("Defer_Queue"):init() defer_queue = THREAD.waitFor("Defer_Queue"):init()
@ -63,13 +63,11 @@ multi:newThread("Scheduler Thread",function()
end end
end) end)
thread:newThread(function()
if thread.hold(function() if thread.hold(function()
return defer_queue:pop() == "done" return defer_queue:pop() == "done"
end,{sleep=1}) == nil then end,{sleep=3}) == nil then
multi.error("Thread.defer didn't work!") multi.error("Thread.defer didn't work!")
end end
end)
th1 = multi:newSystemThread("Test_Thread_1", function(a,b,c,d,e,f) th1 = multi:newSystemThread("Test_Thread_1", function(a,b,c,d,e,f)
queue = THREAD.waitFor("Test_Queue"):init() queue = THREAD.waitFor("Test_Queue"):init()
@ -135,7 +133,7 @@ multi:newThread("Scheduler Thread",function()
local ready = false 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) func2 = jq:newFunction("sleep",function(a,b)
THREAD.sleep(.2) THREAD.sleep(.2)
end) end)
@ -168,7 +166,7 @@ multi:newThread("Scheduler Thread",function()
--print("Test") --print("Test")
end, 1) end, 1)
multi:newSystemThread("Testing proxy copy THREAD",function(tloop) multi:newSystemThread("PROX_THREAD",function(tloop)
local multi, thread = require("multi"):init() local multi, thread = require("multi"):init()
tloop = tloop:init() tloop = tloop:init()
multi.print("tloop type:",tloop.Type) multi.print("tloop type:",tloop.Type)
@ -176,19 +174,21 @@ multi:newThread("Scheduler Thread",function()
thread:newThread(function() thread:newThread(function()
while true do while true do
thread.hold(tloop.OnLoop) thread.hold(tloop.OnLoop)
--print(THREAD_NAME,"Loopy") print(THREAD_NAME,"Loopy")
end end
end) end)
tloop.OnLoop(function(a) tloop.OnLoop(function(a)
--print(THREAD_NAME, "Got loop...") print(THREAD_NAME, "Got loop...")
end) end)
multi:mainloop() multi:mainloop()
end, tloop:getTransferable()) end, tloop:getTransferable())
local test = tloop:getTransferable()
multi.print("tloop", tloop.Type) multi.print("tloop", tloop.Type)
multi.print("tloop.OnLoop", tloop.OnLoop.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!") multi.print("Testing holding on a proxy connection!")
thread.hold(tloop.OnLoop) thread.hold(tloop.OnLoop)
multi.print("Held on proxy connection... once") multi.print("Held on proxy connection... once")
@ -197,17 +197,22 @@ multi:newThread("Scheduler Thread",function()
thread.hold(tloop.OnLoop) thread.hold(tloop.OnLoop)
multi.print("Held on proxy connection... finally") multi.print("Held on proxy connection... finally")
proxy_test = true proxy_test = true
end) end).OnError(print)
thread:newThread(function() thread:newThread(function()
thread.defer(function()
multi.print("Something happened!")
end)
while true do while true do
thread.hold(tloop.OnLoop) thread.hold(tloop.OnLoop)
--print(THREAD_NAME,"Local Loopy") multi.print(THREAD_NAME,"Local Loopy")
end end
end).OnError(function(...)
print("Error",...)
end) end)
tloop.OnLoop(function() tloop.OnLoop(function()
--print("OnLoop",THREAD_NAME) print("OnLoop", THREAD_NAME)
end) end)
t, val = thread.hold(function() t, val = thread.hold(function()