9016d1edd1177db209268826bb67c41f95d026dd
[wowui.git] / libs / AceGUI-3.0-SharedMediaWidgets / Libs / CallbackHandler-1.0 / CallbackHandler-1.0.lua
1 --[[ $Id: CallbackHandler-1.0.lua 3 2008-09-29 16:54: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