Some major changes to loveManager
Still working on version 14.0,.0
This commit is contained in:
parent
c2488ed5ce
commit
ae9f76d938
53
changes.md
53
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
|
||||
-------------
|
||||
|
||||
39
multi/compat/scratchpad.lua
Normal file
39
multi/compat/scratchpad.lua
Normal file
@ -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
|
||||
@ -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,14 +1481,10 @@ 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)
|
||||
t.OnDeath(function(self,status,...) rets = {...} end)
|
||||
t.OnError(function(self,e) err = e end)
|
||||
return {
|
||||
wait = function()
|
||||
local function wait()
|
||||
return thread.hold(function()
|
||||
if err then
|
||||
return multi.NIL, err
|
||||
@ -1551,12 +1492,22 @@ function thread:newFunction(func)
|
||||
return unpack(rets)
|
||||
end
|
||||
end)
|
||||
end,
|
||||
end
|
||||
local t = multi:newThread("TempThread",func,...)
|
||||
t.OnDeath(function(self,status,...) rets = {...} end)
|
||||
t.OnError(function(self,e) err = e 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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
160
multi/integration/loveManager/extensions.lua
Normal file
160
multi/integration/loveManager/extensions.lua
Normal file
@ -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
|
||||
33
multi/integration/loveManager/init.lua
Normal file
33
multi/integration/loveManager/init.lua
Normal file
@ -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}
|
||||
41
multi/integration/loveManager/scratchpad.lua
Normal file
41
multi/integration/loveManager/scratchpad.lua
Normal file
@ -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
|
||||
13
multi/integration/loveManager/status.lua
Normal file
13
multi/integration/loveManager/status.lua
Normal file
@ -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
|
||||
13
multi/integration/loveManager/threadREF.lua
Normal file
13
multi/integration/loveManager/threadREF.lua
Normal file
@ -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))
|
||||
208
multi/integration/loveManager/threads.lua
Normal file
208
multi/integration/loveManager/threads.lua
Normal file
@ -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
|
||||
@ -25,80 +25,6 @@ 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)
|
||||
@ -113,7 +39,6 @@ function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a chann
|
||||
return self
|
||||
end
|
||||
multi.integration.GLOBAL[name] = c -- send the object to the thread through the global interface
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user