Blob


1 --
2 -- tick
3 --
4 -- Copyright (c) 2015 rxi
5 --
6 -- This library is free software; you can redistribute it and/or modify it
7 -- under the terms of the MIT license. See LICENSE for details.
8 --
10 local tick = { _version = "0.1.1" }
11 tick.__index = tick
14 local iscallable = function(x)
15 if type(x) == "function" then return true end
16 local mt = getmetatable(x)
17 return mt and mt.__call ~= nil
18 end
20 local noop = function()
21 end
24 local event = {}
25 event.__index = event
27 function event.new(parent, fn, delay, recur, err)
28 err = err or 0
29 -- Create and return event
30 return setmetatable({
31 parent = parent,
32 delay = delay,
33 timer = delay + err,
34 fn = fn,
35 recur = recur,
36 }, event)
37 end
40 function event:after(fn, delay)
41 -- Error check
42 if self.recur then
43 error("cannot chain a recurring event")
44 end
45 -- Chain event
46 local oldfn = self.fn
47 local e = event.new(self.parent, fn, delay, false)
48 self.fn = function()
49 oldfn()
50 e.timer = e.timer + self.parent.err
51 self.parent:add(e)
52 end
53 return e
54 end
57 function event:stop()
58 tick.remove(self.parent, self)
59 end
63 function tick.group()
64 return setmetatable({ err = 0 }, tick)
65 end
68 function tick:add(e)
69 self[e] = true
70 table.insert(self, e)
71 return e
72 end
75 function tick:remove(e)
76 if type(e) == "number" then
77 -- Remove and return event
78 local idx = e
79 e = self[idx]
80 self[e] = nil
81 self[idx] = self[#self]
82 table.remove(self)
83 return e
84 end
85 self[e] = false
86 for i, v in ipairs(self) do
87 if v == e then
88 return self:remove(i)
89 end
90 end
91 end
94 function tick:update(dt)
95 for i = #self, 1, -1 do
96 local e = self[i]
97 e.timer = e.timer - dt
98 while e.timer <= 0 do
99 if e.recur then
100 e.timer = e.timer + e.delay
101 else
102 self:remove(i)
103 end
104 self.err = e.timer
105 e.fn()
106 if not e.recur then
107 break
108 end
109 end
110 end
111 self.err = 0
112 end
115 function tick:event(fn, delay, recur)
116 delay = tonumber(delay)
117 -- Error check
118 if not iscallable(fn) then
119 error("expected `fn` to be callable")
120 end
121 if type(delay) ~= "number" then
122 error("expected `delay` to be a number")
123 end
124 if delay < 0 then
125 error("expected `delay` of zero or greater")
126 end
127 -- If, factoring in the timing error, the event should happen *now* the
128 -- function is immediately called and the error is temporarily carried
129 -- through. This assures nested events with delays shorter than the update()
130 -- delta-time do not accumulate error; several nested events with very small
131 -- delays may end up being called on the same frame. A dummy event is created
132 -- and returned so :after() still functions correctly.
133 local d = delay + self.err
134 if d < 0 then
135 local err = self.err
136 self.err = d
137 fn()
138 self.err = err
139 return self:add(event.new(self, noop, delay, recur, self.err))
140 end
141 -- Create, add and return a normal event
142 return self:add(event.new(self, fn, delay, recur, self.err))
143 end
146 function tick:delay(fn, delay)
147 return self:event(fn, delay, false)
148 end
151 function tick:recur(fn, delay)
152 return self:event(fn, delay, true)
153 end
156 local group = tick.group()
158 local bound = {
159 update = function(...) return tick.update(group, ...) end,
160 delay = function(...) return tick.delay (group, ...) end,
161 recur = function(...) return tick.recur (group, ...) end,
162 remove = function(...) return tick.remove(group, ...) end,
164 setmetatable(bound, tick)
166 return bound