-- -- coil -- -- Copyright (c) 2014 rxi -- -- This library is free software; you can redistribute it and/or modify it -- under the terms of the MIT license. See LICENSE for details. -- local coil = { _version = "0.1.0" } coil.__index = coil coil.tasks = {} local unpack = unpack or table.unpack local _assert = function(cond, msg, lvl) if cond then return cond, msg, lvl end error(msg, lvl + 1) end local callback_mt = { __call = function(t, ...) t.args = {...} t.ready = true end} local task = {} task.__index = task function task.new(fn, parent) local self = setmetatable({}, task) self.routine = coroutine.wrap(fn) self.parent = parent self.pausecount = 0 return self end function task:pause() self.pausecount = self.pausecount + 1 end function task:resume() _assert(self.pausecount > 0, "unbalanced resume()", 2) self.pausecount = self.pausecount - 1 end function task:stop() coil.remove(self.parent, self) end function coil:update(dt) if #self == 0 then return end coil.deltatime = dt for i = #self, 1, -1 do local task = self[i] if task.wait then -- Handle wait if type(task.wait) == "number" then -- Handle numerical wait task.wait = task.wait - dt if task.wait <= 0 then task.waitrem = task.wait task.wait = nil end elseif type(task.wait) == "table" then -- Handle callback object if task.wait.ready then task.wait = nil end end end if not task.wait and task.pausecount == 0 then -- Run task coil.current = task if not task.routine() then coil.remove(self, i) end end end coil.current = nil end function coil:add(fn) local t = task.new(fn, self) table.insert(self, t) return t end function coil:remove(t) if type(t) == "number" then self[t] = self[#self] table.remove(self) return end for i, task in ipairs(self) do if task == t then return coil.remove(self, i) end end end function coil.wait(x, y) -- Discard first argument if its a coil group x = getmetatable(x) == coil and y or x local c = coil.current _assert(c, "wait() called from outside a coroutine", 2) if type(x) == "number" then -- Handle numerical wait c.wait = (c.waitrem or 0) + x if c.wait <= 0 then c.waitrem = c.wait return else c.waitrem = nil end else -- Handle next-frame / callback wait _assert(x == nil or getmetatable(x) == callback_mt, "wait() expected number, callback object or nothing as argument", 2) c.waitrem = nil c.wait = x end coroutine.yield(true) -- Return args if wait was a callback object if type(x) == "table" then return unpack(x.args) end -- Return delta time if wait had no args if x == nil then return coil.deltatime end end function coil.callback() return setmetatable({ ready = false }, callback_mt) end function coil.group() return setmetatable({}, coil) end local bound = { update = function(...) return coil.update(coil.tasks, ...) end, add = function(...) return coil.add(coil.tasks, ...) end, remove = function(...) return coil.remove(coil.tasks, ...) end, } setmetatable(bound, coil) return bound