notsanequarium/lib/tick.lua

166 lines
3.4 KiB
Lua

--
-- 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