Advertisement
Snusmumriken

Love2d thread class

Feb 4th, 2017
433
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 8.43 KB | None | 0 0
  1. --[=[ Thread library.
  2.  
  3. Using like this:
  4.  
  5. Thread = require'thread'
  6.  
  7. local audiothread = {}
  8. audiothread.func = [[
  9.     require'love.sound'
  10.     require'love.audio'
  11.     require'love.timer'
  12.     local sound = love.audio.newSource('sound.ogg')
  13.     while true do
  14.         data = thread:pop() -- threads has access to special object
  15.         if data == 'play' then
  16.             sound:play()
  17.             thread:push('Yea!')
  18.         end
  19.         love.timer.sleep(.01)
  20.     end
  21. ]]
  22.  
  23. function audiothread.receive(msg)
  24.     print(msg)
  25. end
  26.  
  27. -- name, thread function and set of callbacks {receive, finish}
  28. music = Thread('Music', audiothread.func, {receive = audiothread.receive})
  29.  
  30. music:send('play')
  31. function love.update()
  32.     music:update()
  33. end
  34.  
  35. ]=]
  36. if not love or not love.filesystem or not love.thread then
  37.     error('This plugin for LOVE2D framework, and not works with other lua-builds'..
  38.                 'Also it requires love.filesystem and love.thread modules', 2)
  39. end
  40.  
  41.  
  42. if _VERSION == "Lua 5.1" then
  43.     local rg = assert(rawget)
  44.     local proxy_key = rg(_G, "__GC_PROXY") or "__gc_proxy"
  45.     local rs = assert(rawset)
  46.     local gmt = assert(debug.getmetatable)
  47.     local smt = assert(setmetatable)
  48.     local np = assert(newproxy)
  49.  
  50.     setmetatable = function(t, mt)
  51.         if mt ~= nil and rg(mt, "__gc") and not rg(t,"__gc_proxy") then
  52.             local p = np(true)
  53.             rs(t, proxy_key, p)
  54.             gmt(p).__gc = function()
  55.                 rs(t, proxy_key, nil)
  56.                 local nmt = gmt(t)
  57.                 if not nmt then return end
  58.                 local fin = rg(nmt, "__gc")
  59.                 if fin then return fin(t) end
  60.             end
  61.         end
  62.         return smt(t,mt)
  63.     end
  64. end
  65.  
  66. local namelist = {}
  67.  
  68. function namelist:checkName(name)
  69.     for i, v in ipairs(self) do
  70.         if name == v then return true end
  71.     end
  72. end
  73.  
  74. function namelist:removeName(name)
  75.     for i, v in ipairs(namelist) do
  76.         if name == v then
  77.             table.remove(namelist, i)
  78.             break
  79.         end
  80.     end
  81. end
  82.  
  83. function namelist:getThreads()
  84.     local s = ''
  85.     for i, v in ipairs(namelist) do
  86.         s = s..v..'\n'
  87.     end
  88.     return s
  89. end
  90.  
  91. --class with set of thread functions
  92.  
  93. local ThreadClass = [[
  94.     do
  95.         arg = {...}
  96.         local t = {}
  97.        
  98.         t.name = "TREAD_NAME"
  99.         t.inner = love.thread.getChannel(t.name.."_i")
  100.         t.outer = love.thread.getChannel(t.name.."_o")
  101.  
  102.         local link = {}
  103.         function link:new(inner, outer)
  104.             return setmetatable({inner = inner, outer = outer}, {__index = link})
  105.         end
  106.  
  107.         function link:clear()
  108.             self.inner:clear()
  109.             self.outer:clear()
  110.         end
  111.  
  112.         function link:push(data)
  113.             local succ, err = pcall(self.outer.push, self.outer, data)
  114.             err = err and error(err, 2)
  115.         end
  116.  
  117.         function link:pop()
  118.             local succ, v = pcall(self.inner.pop, self.inner)
  119.             if not succ then error(v, 2) end
  120.             return v
  121.         end
  122.  
  123.         thread = {}
  124.         function thread:newLink(name)
  125.             local nameA, nameB = self:getName(), name
  126.             if nameA > nameB then
  127.                 return link:new(
  128.                     love.thread.getChannel(nameA..nameB..'_blue'),
  129.                     love.thread.getChannel(nameA..nameB..'_red')
  130.                 )
  131.             else
  132.                 return link:new(
  133.                     love.thread.getChannel(nameB..nameA..'_red'),
  134.                     love.thread.getChannel(nameB..nameA..'_blue')
  135.                 )
  136.             end
  137.         end
  138.        
  139.         function thread:clear() t.inner:clear(); t.outer:clear() end
  140.  
  141.         function thread:push(data)
  142.             succ, err = pcall(t.outer.pop, t.outer)
  143.             if err then error(err, 2) end
  144.         end
  145.  
  146.         function thread:pop()
  147.             succ, v = pcall(t.inner.pop, t.inner)
  148.             if not succ then error(v, 3) end
  149.             return v
  150.         end
  151.  
  152.         function thread:getName() return t.name end
  153.  
  154.         function thread:getCount() return t.inner:getCount() end
  155.  
  156.         function thread:popAll()
  157.             local o = {}
  158.             local data = thread:pop()
  159.             while data do
  160.                 table.insert(o, data)
  161.                 data = thread:pop()
  162.             end
  163.             return o
  164.         end
  165.     end
  166. ]]
  167.  
  168. --remove comments
  169. ThreadClass = ThreadClass:gsub('%-%-.-[\n\r]', '')
  170.  
  171. --[[Replaceing all newline characters for make code-oneliner.
  172.     Errors returns number of line with error, but a lot lines of code before user
  173.     function make finding line with error too difficult. Oneliners avoid it.]]
  174. ThreadClass = ThreadClass:gsub('[\n\r$]+', ' ')
  175.  
  176. --remove all tab characters because i want to remove it (pretty style)
  177. ThreadClass = ThreadClass:gsub('[   ]+', '')
  178. --i love gsub so much!
  179.  
  180. --main thread class
  181. local thread = {}
  182. setmetatable(thread, {__call =
  183.     function(self, name, userfunction, callbackList)
  184.     --name - name of thread for named channels and links
  185.     --userfunction - string or path to file with thread code
  186.     --callbackList - table like this: {receive = function()...end, finish = function()...end, ...}
  187.  
  188.         --assertions
  189.         name = type(name) == 'string' and name
  190.             or error('Thread: arg#1 name as string expected, got '..type(name), 2)
  191.         userfunction = type(userfunction) == 'string' and userfunction
  192.             or error('Thread: arg#2 thread function as string expected, got '..type(userfunction), 2)
  193.         --check name
  194.         if namelist:checkName(name) then error('Thread "'..name..'" already exists', 2)
  195.         else table.insert(namelist, name) end
  196.         --check callbacks as functions
  197.         if callbackList then
  198.             for k, v in pairs(callbackList) do
  199.                 if type(v) ~= 'function' then v = nil end
  200.             end
  201.         end
  202.         --if function is path to file - load it
  203.         if love.filesystem.isFile(userfunction) then
  204.             userfunction = love.filesystem.read(userfunction)
  205.         end
  206.  
  207.         local class = ThreadClass:gsub('TREAD_NAME', name)
  208.         --set name of thread
  209.  
  210.         local o = {}
  211.         o.name = name
  212.         o.thread = love.thread.newThread(class..userfunction); class = nil
  213.         o.callback = callbackList or {}
  214.         o.channel = {}
  215.         o.channel.i = love.thread.getChannel(o.name.."_i")  --main --in channel--> thread
  216.         o.channel.o = love.thread.getChannel(o.name.."_o")  --main <-out channel-- thread
  217.         o.dbg = true
  218.         self.__index = self
  219.         self.__gc = function(self)
  220.             self:clear()
  221.             namelist:removeName(self.name)
  222.             print('thread '..self.name..' collected')
  223.         end
  224.         return setmetatable(o, self)
  225.     end,})
  226.  
  227. local callbackNames = {'receive', 'finish', 'error', 'kill'}
  228. local function checkCallback(name)
  229.     if not name or type(name) ~= 'string' then error('Thread:setCallback(name, func) arg#1 string expected got '..type(name), 3) end
  230.     for i, v in ipairs(callbackNames) do
  231.         if v == name:lower() then return true end
  232.     end
  233. end
  234.  
  235. function thread:setCallback(name, f)
  236.     if not checkCallback(name) then error('Thread:setCallback(name, func) arg#1 name must be "receive" or "finish" or "error" or "kill" ', 2) end
  237.     --List of name:
  238.     ---receive: function(self, msg) Calls if thread send the message to parent program.
  239.     ---finish:  function(self, ...) Calls if thread finish self work. '...' contain all unreaded messages
  240.     ---error:   function(self, err) Calls if thread got error.
  241.     ---kill:    function(self)    Calls if parent program kill the thread.
  242.     self.callback[name:lower()] = f
  243. end
  244.  
  245. function thread:start(...)
  246.     self.thread:start(...)
  247.     self.state = 'run'
  248. end
  249.  
  250. function thread:kill()
  251.     local res = self.callback.kill and {self.callback.kill(self, self:receiveAll())}
  252.     self = nil
  253.     collectgarbage()
  254.     return unpack(res)
  255. end
  256.  
  257. function thread:wait()
  258.     self.state = 'wait'
  259.     self.thread:wait()
  260. end
  261.  
  262. function thread:isRunning()
  263.     return self.thread:isRunning()
  264. end
  265.  
  266. function thread:getError()
  267.     return self.thread:getError()
  268. end
  269.  
  270. function thread:send(data)
  271.     self.channel.i:push(data)
  272. end
  273.  
  274. function thread:supply(data)
  275.     self.channel.i:supply(data)
  276. end
  277.  
  278. function thread:receive()
  279.     return self.channel.o:pop()
  280. end
  281.  
  282. function thread:receiveAll()
  283.     local o = {}
  284.     for i = 1, self.channel.o:getCount() do
  285.         table.insert(o, self:receive())
  286.     end
  287.     return o
  288. end
  289.  
  290. function thread:clear()
  291.     self.channel.i:clear()
  292.     self.channel.o:clear()
  293. end
  294.  
  295. function thread:getCount()
  296.     return self.channel.o:getCount()
  297. end
  298.  
  299. function thread:getChannel(name)
  300.     return  love.thread.getChannel(name)
  301. end
  302.  
  303. function thread:debug(v)
  304.     self.dbg = not not v
  305. end
  306.  
  307. function thread:getThreads()
  308.     return namelist:getThreads()
  309. end
  310.  
  311. function thread:update()
  312.     if self.state == 'run' then
  313.         if not self.thread:isRunning() then
  314.             self.state = 'finish'
  315.             if self.callback.finish then
  316.                 self.callback.finish(self, self:receiveAll())
  317.                 self:clear()
  318.             end
  319.         end
  320.        
  321.         local err = self.thread:getError()
  322.         if err and self.dbg then print('Thread "'..self.name..'" err: '..tostring(err)) end
  323.         if err and self.callback.error then
  324.             self.callback.error(self, err)
  325.         end
  326.  
  327.     end
  328.     if self.callback.receive then
  329.         for _, data in ipairs(self:receiveAll()) do
  330.             self.callback.receive(self, data)
  331.             if self.dbg then
  332.                 print('Thread send msg: '..tostring(data))
  333.             end
  334.         end
  335.     end
  336.     collectgarbage()
  337. end
  338.  
  339. return thread
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement