comparison lib/CallbackHandler-1.0/CallbackHandler-1.0.lua @ 28:21bcaf8215ff

- converted to Ace3 - rearranged file layout - configGUI menus not working right now
author Flick <flickerstreak@gmail.com>
date Mon, 17 Mar 2008 18:24:53 +0000
parents
children
comparison
equal deleted inserted replaced
27:f1e838841ce1 28:21bcaf8215ff
1 --[[ $Id: CallbackHandler-1.0.lua 60697 2008-02-09 16:51:20Z nevcairiel $ ]]
2 local MAJOR, MINOR = "CallbackHandler-1.0", 3
3 local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
4
5 if not CallbackHandler then return end -- No upgrade needed
6
7 local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
8
9 local type = type
10 local pcall = pcall
11 local pairs = pairs
12 local assert = assert
13 local concat = table.concat
14 local loadstring = loadstring
15 local next = next
16 local select = select
17 local type = type
18 local xpcall = xpcall
19
20 local function errorhandler(err)
21 return geterrorhandler()(err)
22 end
23
24 local function CreateDispatcher(argCount)
25 local code = [[
26 local next, xpcall, eh = ...
27
28 local method, ARGS
29 local function call() method(ARGS) end
30
31 local function dispatch(handlers, ...)
32 local index
33 index, method = next(handlers)
34 if not method then return end
35 local OLD_ARGS = ARGS
36 ARGS = ...
37 repeat
38 xpcall(call, eh)
39 index, method = next(handlers, index)
40 until not method
41 ARGS = OLD_ARGS
42 end
43
44 return dispatch
45 ]]
46
47 local ARGS, OLD_ARGS = {}, {}
48 for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end
49 code = code:gsub("OLD_ARGS", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(ARGS, ", "))
50 return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler)
51 end
52
53 local Dispatchers = setmetatable({}, {__index=function(self, argCount)
54 local dispatcher = CreateDispatcher(argCount)
55 rawset(self, argCount, dispatcher)
56 return dispatcher
57 end})
58
59 --------------------------------------------------------------------------
60 -- CallbackHandler:New
61 --
62 -- target - target object to embed public APIs in
63 -- RegisterName - name of the callback registration API, default "RegisterCallback"
64 -- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
65 -- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
66
67 function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused)
68 -- TODO: Remove this after beta has gone out
69 assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused")
70
71 RegisterName = RegisterName or "RegisterCallback"
72 UnregisterName = UnregisterName or "UnregisterCallback"
73 if UnregisterAllName==nil then -- false is used to indicate "don't want this method"
74 UnregisterAllName = "UnregisterAllCallbacks"
75 end
76
77 -- we declare all objects and exported APIs inside this closure to quickly gain access
78 -- to e.g. function names, the "target" parameter, etc
79
80
81 -- Create the registry object
82 local events = setmetatable({}, meta)
83 local registry = { recurse=0, events=events }
84
85 -- registry:Fire() - fires the given event/message into the registry
86 function registry:Fire(eventname, ...)
87 if not rawget(events, eventname) or not next(events[eventname]) then return end
88 local oldrecurse = registry.recurse
89 registry.recurse = oldrecurse + 1
90
91 Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...)
92
93 registry.recurse = oldrecurse
94
95 if registry.insertQueue and oldrecurse==0 then
96 -- Something in one of our callbacks wanted to register more callbacks; they got queued
97 for eventname,callbacks in pairs(registry.insertQueue) do
98 local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
99 for self,func in pairs(callbacks) do
100 events[eventname][self] = func
101 -- fire OnUsed callback?
102 if first and registry.OnUsed then
103 registry.OnUsed(registry, target, eventname)
104 first = nil
105 end
106 end
107 end
108 registry.insertQueue = nil
109 end
110 end
111
112 -- Registration of a callback, handles:
113 -- self["method"], leads to self["method"](self, ...)
114 -- self with function ref, leads to functionref(...)
115 -- "addonId" (instead of self) with function ref, leads to functionref(...)
116 -- all with an optional arg, which, if present, gets passed as first argument (after self if present)
117 target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
118 if type(eventname) ~= "string" then
119 error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
120 end
121
122 method = method or eventname
123
124 local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
125
126 if type(method) ~= "string" and type(method) ~= "function" then
127 error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
128 end
129
130 local regfunc
131
132 if type(method) == "string" then
133 -- self["method"] calling style
134 if type(self) ~= "table" then
135 error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
136 elseif self==target then
137 error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
138 elseif type(self[method]) ~= "function" then
139 error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
140 end
141
142 if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
143 local arg=select(1,...)
144 regfunc = function(...) self[method](self,arg,...) end
145 else
146 regfunc = function(...) self[method](self,...) end
147 end
148 else
149 -- function ref with self=object or self="addonId"
150 if type(self)~="table" and type(self)~="string" then
151 error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2)
152 end
153
154 if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
155 local arg=select(1,...)
156 regfunc = function(...) method(arg,...) end
157 else
158 regfunc = method
159 end
160 end
161
162
163 if events[eventname][self] or registry.recurse<1 then
164 -- if registry.recurse<1 then
165 -- we're overwriting an existing entry, or not currently recursing. just set it.
166 events[eventname][self] = regfunc
167 -- fire OnUsed callback?
168 if registry.OnUsed and first then
169 registry.OnUsed(registry, target, eventname)
170 end
171 else
172 -- we're currently processing a callback in this registry, so delay the registration of this new entry!
173 -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
174 registry.insertQueue = registry.insertQueue or setmetatable({},meta)
175 registry.insertQueue[eventname][self] = regfunc
176 end
177 end
178
179 -- Unregister a callback
180 target[UnregisterName] = function(self, eventname)
181 if not self or self==target then
182 error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
183 end
184 if type(eventname) ~= "string" then
185 error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
186 end
187 if rawget(events, eventname) and events[eventname][self] then
188 events[eventname][self] = nil
189 -- Fire OnUnused callback?
190 if registry.OnUnused and not next(events[eventname]) then
191 registry.OnUnused(registry, target, eventname)
192 end
193 end
194 if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
195 registry.insertQueue[eventname][self] = nil
196 end
197 end
198
199 -- OPTIONAL: Unregister all callbacks for given selfs/addonIds
200 if UnregisterAllName then
201 target[UnregisterAllName] = function(...)
202 if select("#",...)<1 then
203 error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
204 end
205 if select("#",...)==1 and ...==target then
206 error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
207 end
208
209
210 for i=1,select("#",...) do
211 local self = select(i,...)
212 if registry.insertQueue then
213 for eventname, callbacks in pairs(registry.insertQueue) do
214 if callbacks[self] then
215 callbacks[self] = nil
216 end
217 end
218 end
219 for eventname, callbacks in pairs(events) do
220 if callbacks[self] then
221 callbacks[self] = nil
222 -- Fire OnUnused callback?
223 if registry.OnUnused and not next(callbacks) then
224 registry.OnUnused(registry, target, eventname)
225 end
226 end
227 end
228 end
229 end
230 end
231
232 return registry
233 end
234
235
236 -- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
237 -- try to upgrade old implicit embeds since the system is selfcontained and
238 -- relies on closures to work.
239