From ae9f76d9388143940e0eebc5fcd8611753cbaad6 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 16 Dec 2019 20:15:34 -0500 Subject: [PATCH] Some major changes to loveManager Still working on version 14.0,.0 --- changes.md | 53 ++++- multi/compat/scratchpad.lua | 39 ++++ multi/init.lua | 107 +++------- multi/integration/lanesManager.lua | 65 +++++- multi/integration/loveManager.lua | 65 +++++- multi/integration/loveManager/extensions.lua | 160 ++++++++++++++ multi/integration/loveManager/init.lua | 33 +++ multi/integration/loveManager/scratchpad.lua | 41 ++++ multi/integration/loveManager/status.lua | 13 ++ multi/integration/loveManager/threadREF.lua | 13 ++ multi/integration/loveManager/threads.lua | 208 +++++++++++++++++++ multi/integration/shared.lua | 101 ++------- 12 files changed, 727 insertions(+), 171 deletions(-) create mode 100644 multi/compat/scratchpad.lua create mode 100644 multi/integration/loveManager/extensions.lua create mode 100644 multi/integration/loveManager/init.lua create mode 100644 multi/integration/loveManager/scratchpad.lua create mode 100644 multi/integration/loveManager/status.lua create mode 100644 multi/integration/loveManager/threadREF.lua create mode 100644 multi/integration/loveManager/threads.lua diff --git a/changes.md b/changes.md index 240a1f6..c60a72e 100644 --- a/changes.md +++ b/changes.md @@ -11,7 +11,7 @@ Added: -- tobj.OnDeath(self,status,returns[...]) -- This is a connection that passes a reference to the self, the status, whether or not the thread ended or was killed, and the returns of the thread. -- tobj.OnError(self,error) -- returns a reference to self and the error as a string -- **Limitations:** only 7 returns are possible! This was done because creating and destroying table objects are slow. Instead I capture the return values from coroutine.resume into local variables and only allowed it to collect 6 max. -- thread.run(function) -- Can only be used within a thread, creates another thread that can do work, but automatically returns whatever from the run function +- thread.run(function) -- Can only be used within a thread, creates another thread that can do work, but automatically returns whatever from the run function -- Use thread newfunctions for a more powerful version of thread.run() - thread:newFunction(FUNCTION; func) -- returns a function that gives you the option to wait or connect to the returns of the function. -- func().wait() -- only works when within a coroutine based thread @@ -20,13 +20,58 @@ Added: -- If the created function encounters an error, it will return nil, the error message! - special variable multi.NIL was added to allow error handling in threaded functions. -- multi.NIL can be used in to force a nil value when using thread.hold() -- All functions created in the root of a thread are now converted to threaded functions, which allow for wait and connect features +- All functions created in the root of a thread are now converted to threaded functions, which allow for wait and connect features. **Note:** these functions are local to the function! And are only converted if they aren't set as local! Otherwise the function + +thread newFunction +```lua +func=thread:newFunction(function(...) + print("Function running...") + thread.sleep(1) + return {1,2,3},"done" +end) +multi:newThread("Test",function() + func().connect(function(...) + print(...) + end) +end) +----OUTPUT---- +> Function running... +> table: 0x008cf340 done nil nil nil nil nil +``` + +thread newFunction using auto convert +```lua +package.path = "./?/init.lua;" .. package.path +multi, thread = require("multi").init() +a=5 +multi:newThread("Test",function() + function hmm() -- Auto converted into a threaded function + return "Hello!",2 + end + print(a) + a=10 + print(hmm().wait()) +end) +multi:newAlarm(3):OnRing(function() + print(a) +end) +print(hmm) +multi:mainloop() +-----OUTPUT----- +> nil +> 5 +> Hello! 2 nil nil nil nil nil -- The way I manage function returns is by allocating them to predefined locals. Because I pass these values regardless they technically get passed even when they are nil. This choice was made to keep the creation of tables to capture arguments then using unpack to pass them on when processing is done +> 10 +``` Fixed: - Connections had a preformance issue where they would create a non function when using connection.getConnection() of a non existing label. - An internal mismanagement of the treads scheduler was fixed. Now it should be quicker and free of bugs - Thread error management is the integrations was not properly implemented. This is now fixed -- + +Removed: +- multi:newWatcher() -- No real use +- multi:newCustomObject() -- No real use Changed: - Ties in to the new function that has been added multi.init() @@ -45,7 +90,7 @@ local nGLOBAL, nTHREAD = require("multi.intergration.networkManager).inti() Note: You can mix and match integrations together. You can create systemthreads within network threads, and you can also create cotoutine based threads within bothe network and system threads. This gives you quite a bit of flexibility to create something awesome. Going forward: -- Sterlization is still being worked on. I was planning of having a way to save state of multi objects and such, but that isn't possible without knowing how your code is strutured or if it is even made to handle something like that. So I decided on giving a tostirng/tofile method for each multi object as well as a fromstring/fromfile method for use. This is technically in the code already, but not documented. It has actually been in the code for a while, but its not done just yet and I want to make it perfect before sending it out. +- Do not expect anything new from the next update. I have a bunch of bugs to iron out. Most pertainint to network and system threading. Maybe Update 13.1.0 Bug fixes and features added ------------- diff --git a/multi/compat/scratchpad.lua b/multi/compat/scratchpad.lua new file mode 100644 index 0000000..9a8619d --- /dev/null +++ b/multi/compat/scratchpad.lua @@ -0,0 +1,39 @@ +--local test = love.newByteData(string.rep("\0",16) +local ffi = require("ffi") +local scratchpad = {} +local mt = { + __index = function(t, k) + if type(k)=="string" then + local a, b = k:match("(%d+):(%d+)") + return t:read(tonumber(a),tonumber(b)) + elseif type(k)=="number" then + return t:read(k,1) + end + end, + __newindex = function(t, k, v) + t:write(v,k) + end +} +function scratchpad:new(data, size, rep) + local c = {} + local pad + if type(data)=="string" then + pad = love.data.newByteData(data or string.rep(rep or "\0",size or 16)) + elseif data:type()=="ByteData" then + pad = bytedata + end + local ptr = ffi.cast("unsigned char*",pad:getPointer()) + local size = pad:getSize() + function c:write(data, loc, len) + if loc+(len or #data)>size then + error("Attpemting to write data outside the bounds of data byte array!") + end + ffi.copy(ptr+(loc or 0), data, len or #data) + end + function c:read(loc, len) + return ffi.string(ptr+(loc or 0), len or size) + end + setmetatable(c,mt) + return c +end +return scratchpad \ No newline at end of file diff --git a/multi/init.lua b/multi/init.lua index b810683..998157e 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -952,27 +952,6 @@ function multi.nextStep(func) end multi.OnPreLoad=multi:newConnection() --Core Actors -function multi:newCustomObject(objRef,isActor) - local c={} - if isActor then - c=self:newBase() - if type(objRef)=='table' then - table.merge(c,objRef) - end - if not c.Act then - function c:Act() - -- Empty function - end - end - else - c=objRef or {} - end - if not c.Type then - c.Type='coustomObject' - end - self:create(c) - return c -end function multi:newEvent(task) local c=self:newBase() c.Type='event' @@ -1434,40 +1413,6 @@ function multi:newTimeStamper() self:create(c) return c end -function multi:newWatcher(namespace,name) - local function WatcherObj(ns,n) - if self.Type=='queue' then - multi.print("Cannot create a watcher on a queue! Creating on 'multi' instead!") - self=multi - end - local c=self:newBase() - c.Type='watcher' - c.ns=ns - c.n=n - c.cv=ns[n] - function c:OnValueChanged(func) - table.insert(self.func,func) - return self - end - function c:Act() - if self.cv~=self.ns[self.n] then - for i=1,#self.func do - self.func[i](self,self.cv,self.ns[self.n]) - end - self.cv=self.ns[self.n] - end - end - self:create(c) - return c - end - if type(namespace)~='table' and type(namespace)=='string' then - return WatcherObj(_G,namespace) - elseif type(namespace)=='table' and (type(name)=='string' or 'number') then - return WatcherObj(namespace,name) - else - multi.print('Warning, invalid arguments! Nothing returned!') - end -end -- Threading stuff multi.GlobalVariables={} if os.getOS()=="windows" then @@ -1536,27 +1481,33 @@ function thread.waitFor(name) return thread.get(name) end function thread:newFunction(func) - local c = {} + local c = {Type = "tfunc"} c.__call = function(self,...) - local rets, err - local t = multi:newThread("TempThread",func) + local rets, err + local function wait() + return thread.hold(function() + if err then + return multi.NIL, err + elseif rets then + return unpack(rets) + end + end) + end + local t = multi:newThread("TempThread",func,...) t.OnDeath(function(self,status,...) rets = {...} end) t.OnError(function(self,e) err = e end) - return { - wait = function() - return thread.hold(function() - if err then - return multi.NIL, err - elseif rets then - return unpack(rets) - end - end) - end, - connect = function(f) - t.OnDeath(function(self,status,...) f(...) end) - t.OnError(function(self,err) f(self, err) end) - end - } + --if thread.isThread() then + -- return wait() + --else + return { + isTFunc = true, + wait = wait, + connect = function(f) + t.OnDeath(function(self,status,...) f(...) end) + t.OnError(function(self,err) f(self, err) end) + end + } + --end end setmetatable(c,c) return c @@ -1600,24 +1551,26 @@ local threadCount = 0 local threadid = 0 thread.__threads = {} local threads = thread.__threads -function multi:newThread(name,func) +local Gref = _G +function multi:newThread(name,func,...) local func = func or name if type(name) == "function" then name = "Thread#"..threadCount end local env = {} setmetatable(env,{ - __index = _G, + __index = Gref, __newindex = function(t,k,v) if type(v)=="function" then rawset(t,k,thread:newFunction(v)) else - rawset(t,k,v) + Gref[k]=v end end }) setfenv(func,env) local c={} + c.startArgs = {...} c.ref={} c.Name=name c.thread=coroutine.create(func) @@ -1741,7 +1694,7 @@ function multi.initThreads() multi.scheduler:OnLoop(function(self) for i=#threads,1,-1 do if not threads[i].__started then - _,ret,r1,r2,r3,r4,r5,r6=coroutine.resume(threads[i].thread) + _,ret,r1,r2,r3,r4,r5,r6=coroutine.resume(threads[i].thread,unpack(threads[i].startArgs)) threads[i].__started = true helper(i) end diff --git a/multi/integration/lanesManager.lua b/multi/integration/lanesManager.lua index 4c5845c..c02c788 100644 --- a/multi/integration/lanesManager.lua +++ b/multi/integration/lanesManager.lua @@ -73,7 +73,70 @@ function THREAD.get(name) end local function randomString(n) local str = "" - local strings = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'} + local strings = { + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z" + } for i = 1, n do str = str .. "" .. strings[math.random(1, #strings)] end diff --git a/multi/integration/loveManager.lua b/multi/integration/loveManager.lua index d43db36..8c74f47 100644 --- a/multi/integration/loveManager.lua +++ b/multi/integration/loveManager.lua @@ -309,7 +309,70 @@ function dump(func) end local function randomString(n) local str = "" - local strings = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'} + local strings = { + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z" + } for i = 1, n do str = str .. "" .. strings[math.random(1, #strings)] end diff --git a/multi/integration/loveManager/extensions.lua b/multi/integration/loveManager/extensions.lua new file mode 100644 index 0000000..19dcdcf --- /dev/null +++ b/multi/integration/loveManager/extensions.lua @@ -0,0 +1,160 @@ +local multi, thread = require("multi").init() +local status = require("multi.integration.loveManager.status") +local pad = require("multi.integration.loveManager.scratchpad") +GLOBAL = multi.integration.GLOBAL +THREAD = multi.integration.THREAD +function multi:newSystemThreadedQueue(name) + local c = {} + c.Name = name + local fRef = {"func",nil} + function c:init() + local q = {} + q.chan = love.thread.getChannel(self.Name) + function q:push(dat) + 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]=="func" then + return THREAD.loadDump(dat[2]) + else + return dat + end + end + function q:peek() + local dat = self.chan:peek() + if type(dat)=="table" and dat[1]=="func" then + return THREAD.loadDump(dat[2]) + else + return dat + end + end + return q + end + THREAD.package(name,c) + return c +end +function multi:newSystemThreadedTable(name) + local c = {} + c.name = name + function c:init() + return THREAD.createTable(self.name) + end + THREAD.package(name,c) + return c +end +local jqc = 1 +function multi:newSystemThreadedJobQueue(n) + local c = {} + c.cores = n or THREAD.getCores() + c.registerQueue = {} + 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() + c._bytedata = love.data.newByteData(string.rep(status.BUSY,c.cores)) + c.bytedata = pad:new(c._bytedata) + local allfunc = 0 + function c:doToAll(func) + self.bytedata:write(string.rep(status.BUSY,c.cores)) -- set all variables to busy + 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.bytedata:write(string.rep(status.BUSY,c.cores)) -- set all variables to busy + self.funcs[name] = func + end + function c:pushJob(name,...) + self.id = self.id + 1 + self.queue:push{name,self.id,...} + return self.id + end + multi:newThread("jobManager",function() + while true do + thread.yield() + local dat = c.queueReturn:pop() + if dat then + print(dat) + c.OnJobCompleted:Fire(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() + local function atomic(channel) + return channel:pop() + end + require("love.timer") + local clock = os.clock + local pad = require("multi.integration.loveManager.scratchpad") + local status = require("multi.integration.loveManager.status") + 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 = {} + setmetatable(_G,{__index = funcs}) + multi: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) + multi: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 + lastProc = os.clock() + local name = table.remove(dat,1) + local id = table.remove(dat,1) + local tab = {funcs[name](unpack(dat))} + table.insert(tab,1,id) + queueReturn:push(tab) + end + end + end):OnError(function(...) + error(...) + end) + multi: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 + return c +end \ No newline at end of file diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua new file mode 100644 index 0000000..2ce9635 --- /dev/null +++ b/multi/integration/loveManager/init.lua @@ -0,0 +1,33 @@ +if ISTHREAD then + error("You cannot require the loveManager from within a thread!") +end +local multi, thread = require("multi.compat.love2d"):init() +local THREAD = {} +__THREADID__ = 0 +__THREADNAME__ = "MainThread" +multi.integration={} +multi.integration.love2d={} +multi.integration.love2d.defaultScratchpadSize = 1024 +multi.integration.love2d.GlobalScratchpad = love.data.newByteData(string.rep("\0",16*multi.integration.love2d.defaultScratchpadSize)) +local THREAD = require("multi.integration.loveManager.threads") +local scratchpad = require("multi.integration.loveManager.scratchpad") +local STATUS = require("multi.integration.loveManager.status") +local GLOBAL = THREAD.getGlobal() +local THREAD_ID = 1 -- The Main thread has ID of 0 +local OBJECT_ID = 0 +function multi:newSystemThread(name,func,...) + local c = {} + c.scratchpad = love.data.newByteData(string.rep("\0",multi.integration.love2d.defaultScratchpadSize)) + c.name = name + c.ID=THREAD_ID + c.thread=love.thread.newThread("multi/integration/loveManager/threadREF.lua") + c.thread:start(THREAD.dump(func),c.ID,c.name,c.scratchpad,multi.integration.love2d.GlobalScratchpad,...) + GLOBAL["__THREAD_"..c.ID] = {ID=c.ID,Name=c.name,Thread=c.thread} + GLOBAL["__THREAD_COUNT"] = THREAD_ID + THREAD_ID=THREAD_ID+1 +end +multi.integration.GLOBAL = GLOBAL +multi.integration.THREAD = THREAD +return {init=function() + return GLOBAL,THREAD +end} \ No newline at end of file diff --git a/multi/integration/loveManager/scratchpad.lua b/multi/integration/loveManager/scratchpad.lua new file mode 100644 index 0000000..54d5a6a --- /dev/null +++ b/multi/integration/loveManager/scratchpad.lua @@ -0,0 +1,41 @@ +local ffi = require("ffi") +local scratchpad = {} +local mt = { + __index = function(t, k) + if type(k)=="string" then + local a, b = k:match("(%d+):(%d+)") + return t:read(tonumber(a),tonumber(b)) + elseif type(k)=="number" then + return t:read(k,1) + end + end, + __newindex = function(t, k, v) + t:write(v,k) + end +} +function scratchpad:new(data, size, rep) + local c = {} + local pad + if type(data)=="string" then + pad = love.data.newByteData(data or string.rep(rep or "\0",size or 16)) + elseif data:type()=="ByteData" then + pad = data + end + local ptr = ffi.cast("unsigned char*",pad:getPointer()) + local size = pad:getSize() + function c:write(data, loc, len) + if (loc or 0)+(len or #data)>size then + error("Attpemting to write data outside the bounds of data byte array!") + end + ffi.copy(ptr+(loc or 0), data, len or #data) + end + function c:read(loc, len) + if (((loc or 0)+(len or size)) > size) or ((loc or 0)<0) then + error("Attempt to read outside the bounds of the scratchpad!") + end + return ffi.string(ptr+(loc or 0), len or size) + end + setmetatable(c,mt) + return c +end +return scratchpad \ No newline at end of file diff --git a/multi/integration/loveManager/status.lua b/multi/integration/loveManager/status.lua new file mode 100644 index 0000000..71a44fb --- /dev/null +++ b/multi/integration/loveManager/status.lua @@ -0,0 +1,13 @@ +--[[ + global pads allow us to directly write data to threads. Each thread has a unique ID which means we can allocate space in memory for each thread to relay stats + Below are codes, If there is more data that needs to be sent we can use byte 0 for that and byte 1,2 and 3 to define a channel +]] +local char = string.char +local cmds = { + OK = char(0x00), -- All is good thread is running can recieve and send data + ERR = char(0x01), -- This tells the system that an error has occured + STOP = char(0x02), -- Thread has finished + BUSY = char(0x03), -- Thread is busy and isn't responding to messages right now + POST = char(0x04), -- Important message for other threads to see, ChannelData with message MSG_TID +} +return cmds \ No newline at end of file diff --git a/multi/integration/loveManager/threadREF.lua b/multi/integration/loveManager/threadREF.lua new file mode 100644 index 0000000..c8d8956 --- /dev/null +++ b/multi/integration/loveManager/threadREF.lua @@ -0,0 +1,13 @@ +ISTHREAD = true +THREAD = require("multi.integration.loveManager.threads") -- order is important! +scratchpad = require("multi.integration.loveManager.scratchpad") +STATUS = require("multi.integration.loveManager.status") +__IMPORTS = {...} +__FUNC__=table.remove(__IMPORTS,1) +__THREADID__=table.remove(__IMPORTS,1) +__THREADNAME__=table.remove(__IMPORTS,1) +pad=table.remove(__IMPORTS,1) +globalhpad=table.remove(__IMPORTS,1) +GLOBAL = THREAD.getGlobal() +multi, thread = require("multi").init() +THREAD.loadDump(__FUNC__)(unpack(__IMPORTS)) \ No newline at end of file diff --git a/multi/integration/loveManager/threads.lua b/multi/integration/loveManager/threads.lua new file mode 100644 index 0000000..f0d945e --- /dev/null +++ b/multi/integration/loveManager/threads.lua @@ -0,0 +1,208 @@ +--[[ + Shared methods for both the main thread and +]] +require("love.timer") +require("love.system") +require("love.data") +local socket = require("socket") +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 +local fRef = {"func",nil} +local function manage(channel, value) + channel:clear() + if type(value) == "function" then + fRef[2] = THREAD.dump(value) + channel:push(fRef) + return + else + channel:push(value) + end +end +local function RandomVariable(length) + local res = {} + math.randomseed(socket.gettime()*10000) + for i = 1, length do + res[#res+1] = string.char(math.random(97, 122)) + end + return table.concat(res) +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]=="func" then + return THREAD.loadDump(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.testFor(name,val,sym) + -- Not yet implemented +end +function threads.getCores() + return love.system.getProcessorCount() +end +function threads.kill() + +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.getName() + return __THREADNAME__ +end +function threads.getID() + return __THREADID__ +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.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]=="func" then + return THREAD.loadDump(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{...} + end + function c.error(err) + c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} + error(err) + end + return c +end +if not ISTHREAD then + print("mainthread") + local clock = os.clock + local lastproc = clock() + local queue = love.thread.getChannel("__CONSOLE__") + multi:newThread("consoleManager",function() + while true do + thread.yield() + dat = queue:pop() + if dat then + lastproc = clock() + print(unpack(dat)) + end + if clock()-lastproc>2 then + thread.sleep(.1) -- Printing is for humans, sleep can be big to lower processing + end + 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] = THREAD.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/multi/integration/shared.lua b/multi/integration/shared.lua index 1586acb..6562ea3 100644 --- a/multi/integration/shared.lua +++ b/multi/integration/shared.lua @@ -25,95 +25,20 @@ local multi, thread = require("multi").init() function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a channel on both ends local c = {} -- where we will store our object c.name = name -- set the name this is important for the love2d side - if love then -- check love - if love.thread then -- make sure we can use the threading module - function c:init() -- create an init function so we can mimic on both love2d and lanes - self.chan = love.thread.getChannel(self.name) -- create channel by the name self.name - function self:push(v) -- push to the channel - local tab - if type(v) == "table" then - tab = {} - for i, c in pairs(v) do - if type(c) == "function" then - tab[i] = "\1" .. string.dump(c) - else - tab[i] = c - end - end - self.chan:push(tab) - else - self.chan:push(c) - end - end - function self:pop() -- pop from the channel - local v = self.chan:pop() - if not v then - return - end - if type(v) == "table" then - tab = {} - for i, c in pairs(v) do - if type(c) == "string" then - if c:sub(1, 1) == "\1" then - tab[i] = loadstring(c:sub(2, -1)) - else - tab[i] = c - end - else - tab[i] = c - end - end - return tab - else - return self.chan:pop() - end - end - function self:peek() - local v = self.chan:peek() - if not v then - return - end - if type(v) == "table" then - tab = {} - for i, c in pairs(v) do - if type(c) == "string" then - if c:sub(1, 1) == "\1" then - tab[i] = loadstring(c:sub(2, -1)) - else - tab[i] = c - end - else - tab[i] = c - end - end - return tab - else - return self.chan:pop() - end - end - GLOBAL[self.name] = self -- send the object to the thread through the global interface - return self -- return the object - end - return c - else - error("Make sure you required the love.thread module!") -- tell the user if he/she didn't require said module - end - else - c.linda = lanes.linda() -- lanes is a bit easier, create the linda on the main thread - function c:push(v) -- push to the queue - self.linda:send("Q", v) - end - function c:pop() -- pop the queue - return ({self.linda:receive(0, "Q")})[2] - end - function c:peek() - return self.linda:get("Q") - end - function c:init() -- mimic the feature that love2d requires, so code can be consistent - return self - end - multi.integration.GLOBAL[name] = c -- send the object to the thread through the global interface + c.linda = lanes.linda() -- lanes is a bit easier, create the linda on the main thread + function c:push(v) -- push to the queue + self.linda:send("Q", v) end + function c:pop() -- pop the queue + return ({self.linda:receive(0, "Q")})[2] + end + function c:peek() + return self.linda:get("Q") + end + function c:init() -- mimic the feature that love2d requires, so code can be consistent + return self + end + multi.integration.GLOBAL[name] = c -- send the object to the thread through the global interface return c end