comparison Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua @ 0:169f5211fc7f

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