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()
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.debugging then
require("multi.integration.debugManager")
end
if settings.findopt then
find_optimization = true
doOpt()
multi.enableOptimization:Fire(multi, thread)
end
multi.settingsHook:Fire(settings)
if settings.debugging then
require("multi.integration.debugManager")
end
multi.settingsHook:Fire(settings)
end
end
init = true
return _G["$multi"].multi,_G["$multi"].thread
end

View File

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

View File

@ -89,6 +89,10 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console)
error("Thread was killed!\1")
end
function THREAD.sync()
-- Maybe do something...
end
function THREAD.pushStatus(...)
local args = multi.pack(...)
__StatusLinda:send(nil,THREAD_ID, args)
@ -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

View File

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

View File

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

View File

@ -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:<function_string>" where <function_string> 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:<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
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 {

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

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

View File

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

View File

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

View File

@ -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, ...)

View File

@ -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)
t.console = true
end

View File

@ -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
require("runtests")
require("threadtests")

View File

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

View File

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