-- -- tick -- -- Copyright (c) 2015 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 tick = { _version = "0.1.1" } tick.__index = tick local iscallable = function(x) if type(x) == "function" then return true end local mt = getmetatable(x) return mt and mt.__call ~= nil end local noop = function() end local event = {} event.__index = event function event.new(parent, fn, delay, recur, err) err = err or 0 -- Create and return event return setmetatable({ parent = parent, delay = delay, timer = delay + err, fn = fn, recur = recur, }, event) end function event:after(fn, delay) -- Error check if self.recur then error("cannot chain a recurring event") end -- Chain event local oldfn = self.fn local e = event.new(self.parent, fn, delay, false) self.fn = function() oldfn() e.timer = e.timer + self.parent.err self.parent:add(e) end return e end function event:stop() tick.remove(self.parent, self) end function tick.group() return setmetatable({ err = 0 }, tick) end function tick:add(e) self[e] = true table.insert(self, e) return e end function tick:remove(e) if type(e) == "number" then -- Remove and return event local idx = e e = self[idx] self[e] = nil self[idx] = self[#self] table.remove(self) return e end self[e] = false for i, v in ipairs(self) do if v == e then return self:remove(i) end end end function tick:update(dt) for i = #self, 1, -1 do local e = self[i] e.timer = e.timer - dt while e.timer <= 0 do if e.recur then e.timer = e.timer + e.delay else self:remove(i) end self.err = e.timer e.fn() if not e.recur then break end end end self.err = 0 end function tick:event(fn, delay, recur) delay = tonumber(delay) -- Error check if not iscallable(fn) then error("expected `fn` to be callable") end if type(delay) ~= "number" then error("expected `delay` to be a number") end if delay < 0 then error("expected `delay` of zero or greater") end -- If, factoring in the timing error, the event should happen *now* the -- function is immediately called and the error is temporarily carried -- through. This assures nested events with delays shorter than the update() -- delta-time do not accumulate error; several nested events with very small -- delays may end up being called on the same frame. A dummy event is created -- and returned so :after() still functions correctly. local d = delay + self.err if d < 0 then local err = self.err self.err = d fn() self.err = err return self:add(event.new(self, noop, delay, recur, self.err)) end -- Create, add and return a normal event return self:add(event.new(self, fn, delay, recur, self.err)) end function tick:delay(fn, delay) return self:event(fn, delay, false) end function tick:recur(fn, delay) return self:event(fn, delay, true) end local group = tick.group() local bound = { update = function(...) return tick.update(group, ...) end, delay = function(...) return tick.delay (group, ...) end, recur = function(...) return tick.recur (group, ...) end, remove = function(...) return tick.remove(group, ...) end, } setmetatable(bound, tick) return bound