annotate Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua @ 6:f10c8a083d2a

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