-
Notifications
You must be signed in to change notification settings - Fork 6
/
events.lua
252 lines (222 loc) · 6.27 KB
/
events.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
--[[
Event system (aka pub/sub) mixin for any object or class.
Written by Cosmin Apreutesei. Public Domain.
Events are a way to associate an action with one or more callback functions
to be called on that action, with the distinct ability to remove one or more
callbacks later on, based on a criteria which can include the original
callback function object or one or more associated identities.
This module is only a mixin (a plain table with methods). It must be added to
your particular object system by copying the methods over to your base class.
NOTE: if event removal is not a requirement, after() might be enough.
EVENT FACTS
* events fire in the order in which they were added.
* extra args passed to `fire()` are passed to each event handler.
* returning a non-nil value from a handler interrupts the event handling
call chain and the value is returned back by `fire()`.
* the meta-event called `'event'` is fired on all events (the name of the
event that was fired is received as arg#1).
* events can be tagged with multiple tags/namespaces `'event.ns1.ns2...'`
or `{event, ns1, ns2, ...}`: tags/namespaces are useful for easy bulk
event removal with `obj:off'.ns1'` or `obj:off({nil, ns1})`.
* multiple handlers can be added for the same event and/or namespace.
* handlers are stored in `self.__observers`.
* tags/namespaces can be of any type, which allows objects to register
event handlers on other objects using `self` as tag so they can later
remove them with `obj:off({nil, self})`.
* `obj:off()` can be safely called inside any event handler, even to remove
itself.
LIMITATIONS
* no bubbling or trickling/capturing as there is no awareness of a hierarchy.
Add them yourself as needed by walking up or down the tree and firing them
for each node (don't forget to inject the target object in the arg list).
EXAMPLES
* `apple:on('falling.ns1.ns2', function(self, args...) ... end)` - register
an event handler and associate it with the `ns1` and `ns2` tags/namespaces.
* `apple:on({'falling', ns1, ns2}, function ... end)` - same but the tags
can be any type.
* `apple:once('falling', function ... end)` - fires only once.
* `Apple:falling(args...)` - default event handler for the `falling` event.
* `apple:fire('falling', args...)` - call all `falling` event handlers.
* `apple:off'falling'` - remove all `falling` event handlers.
* `apple:off'.ns1'` - remove all event handlers on the `ns1` tag.
* `apple:off{nil, ns1}` - remove all event handlers on the `ns1` tag.
* `apple:off() - remove all event handlers registered on `apple`.
API
obj:on('event[.ns1...]', function(self, args...) ... end)
obj:on({event_name, ns1, ...}, function(self, args...) ... end)
obj:once(event, function(self, args...) ... end)
obj:fire(event, args...) -> ret
obj:off('[event][.ns1...]')
obj:off({[event], [ns1, ...]})
obj:off()
]]
require'glue'
events = {}
--default values to speed up look-up in class systems with dynamic dispatch.
events.event = false
events.__observers = false
local function parse_event(s)
local ev, t
if istab(s) then -- {ev|false, ns1, ...}
ev = s[1] or nil
t = {}
for i=2,#s do t[i-1] = s[i] end
elseif s:find('.', 1, true) then -- `[ev].ns1.ns2`
t = {}
for s in s:gmatch'[^%.]*' do
if not ev then
ev = s
elseif s ~= '' then
add(t, s)
end
end
else --`ev`
ev = s
end
if ev == '' then ev = nil end
return ev, t
end
--register a function to be called for a specific event type.
function events:on(s, fn, on)
if not fn then
return
end
if on == false then
return self:off(s, fn)
end
local ev, nss = parse_event(s)
assert(ev, 'event name missing')
local t = self.__observers
if not t then
t = {}
self.__observers = t
end
local t = attr(t, ev)
add(t, fn)
if nss then
for _,ns in ipairs(nss) do
attr(t, ns)[fn] = true
end
end
end
--remove a handler or all handlers of an event and/or namespace.
function events:off(s, fn)
local t = self.__observers
if not t then return end
local ev, nss = parse_event(s)
if fn then
local t = t[ev]
local i = t and indexof(fn, t)
elseif ev and nss then
local t = t[ev]
if t then
for _,ns in ipairs(nss) do
local fns = t[ns]
if fns then
for fn in pairs(fns) do
local i = indexof(fn, t)
if i then
remove(t, i)
end
fns[fn] = nil
end
end
end
end
elseif ev then
t[ev] = nil
elseif nss then
for _,ns in ipairs(nss) do
for _,t in pairs(t) do
local fns = t[ns]
if fns then
for fn in pairs(fns) do
local i = indexof(fn, t)
if i then
remove(t, i)
end
fns[fn] = nil
end
end
end
end
end
end
function events:once(ev, func)
local ev, nss = parse_event(ev)
local id = {}
local ev
if nss then
add(nss, 1, ev)
add(nss, id)
else
ev = {ev, id}
end
self:on(ev, function(...)
self:off(ev)
return func(...)
end)
end
--fire an event, i.e. call its handler method and all observers.
function events:fire(ev, ...)
if self['on_'..ev] then
local ret = self['on_'..ev](self, ...)
if ret ~= nil then return ret end
end
local t = self.__observers
local t = t and t[ev]
if t then
local i = 1
while true do
local handler = t[i]
if not handler then break end --list end or handler removed
local ret = handler(self, ...)
if ret ~= nil then return ret end
if t[i] ~= handler then
--handler was removed from inside itself, stay at i
else
i = i + 1
end
end
end
if ev ~= 'event' then
return self:fire('event', ev, ...)
end
end
--tests ----------------------------------------------------------------------
if not ... then
local obj = {}
for k,v in pairs(events) do obj[k] = v end
local n = 0
local t = {}
local function handler_func(order)
return function(self, a, b, c)
assert(a == 3)
assert(b == 5)
assert(c == nil)
n = n + 1
table.insert(t, order)
end
end
obj:on('testing' , handler_func(1))
obj:on('testing.ns1', handler_func(2))
obj:on('testing.ns2', handler_func(3))
obj:on('testing.ns3', handler_func(4))
obj:fire('testing', 3, 5)
assert(#t == 4)
assert(t[1] == 1)
assert(t[2] == 2)
assert(t[3] == 3)
assert(t[4] == 4)
t = {}
obj:off'.ns2'
obj:fire('testing', 3, 5)
assert(#t == 3)
assert(t[1] == 1)
assert(t[2] == 2)
assert(t[3] == 4)
t = {}
obj:off'testing'
obj:fire('testing', 3, 5)
assert(#t == 0)
end