public
Description: WoW Addon - Handles all your mount & travel-form needs
Homepage: http://www.tekkub.net/
Clone URL: git://github.com/tekkub/mountme.git
Click here to lend your support to: mountme and make a donation at www.pledgie.com !
tekkub (author)
Sun Jul 06 23:26:28 -0700 2008
commit  27f4cdd44a2f02c204862db67a6371770d02b35d
tree    ec2c89303b644f8fc89b1a5443c0184bf6d83fdf
parent  caaa9c063b639eab47c6b72fc59a7f480d134486
mountme / CallbackHandler-1.0.lua
100644 240 lines (201 sloc) 8.744 kb
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
--[[ $Id: CallbackHandler-1.0.lua 60548 2008-02-07 11:04:06Z nevcairiel $ ]]
local MAJOR, MINOR = "CallbackHandler-1.0", 3
local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
 
if not CallbackHandler then return end -- No upgrade needed
 
local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
 
local type = type
local pcall = pcall
local pairs = pairs
local assert = assert
local concat = table.concat
local loadstring = loadstring
local next = next
local select = select
local type = type
local xpcall = xpcall
 
local function errorhandler(err)
  return geterrorhandler()(err)
end
 
local function CreateDispatcher(argCount)
  local code = [[
  local next, xpcall, eh = ...
 
  local method, ARGS
  local function call() method(ARGS) end
 
  local function dispatch(handlers, ...)
    local index
    index, method = next(handlers)
    if not method then return end
    local OLD_ARGS = ARGS
    ARGS = ...
    repeat
      xpcall(call, eh)
      index, method = next(handlers, index)
    until not method
    ARGS = OLD_ARGS
  end
 
  return dispatch
  ]]
 
  local ARGS, OLD_ARGS = {}, {}
  for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end
  code = code:gsub("OLD_ARGS", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(ARGS, ", "))
  return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler)
end
 
local Dispatchers = setmetatable({}, {__index=function(self, argCount)
  local dispatcher = CreateDispatcher(argCount)
  rawset(self, argCount, dispatcher)
  return dispatcher
end})
 
--------------------------------------------------------------------------
-- CallbackHandler:New
--
-- target - target object to embed public APIs in
-- RegisterName - name of the callback registration API, default "RegisterCallback"
-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
 
function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused)
  -- TODO: Remove this after beta has gone out
  assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused")
 
  RegisterName = RegisterName or "RegisterCallback"
  UnregisterName = UnregisterName or "UnregisterCallback"
  if UnregisterAllName==nil then  -- false is used to indicate "don't want this method"
    UnregisterAllName = "UnregisterAllCallbacks"
  end
 
  -- we declare all objects and exported APIs inside this closure to quickly gain access
  -- to e.g. function names, the "target" parameter, etc
 
 
  -- Create the registry object
  local events = setmetatable({}, meta)
  local registry = { recurse=0, events=events }
 
  -- registry:Fire() - fires the given event/message into the registry
  function registry:Fire(eventname, ...)
    if not rawget(events, eventname) or not next(events[eventname]) then return end
    local oldrecurse = registry.recurse
    registry.recurse = oldrecurse + 1
 
    Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...)
 
    registry.recurse = oldrecurse
 
    if registry.insertQueue and oldrecurse==0 then
      -- Something in one of our callbacks wanted to register more callbacks; they got queued
      for eventname,callbacks in pairs(registry.insertQueue) do
        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.
        for self,func in pairs(callbacks) do
          events[eventname][self] = func
          -- fire OnUsed callback?
          if first and registry.OnUsed then
            registry.OnUsed(registry, target, eventname)
            first = nil
          end
        end
      end
      registry.insertQueue = nil
    end
  end
 
  -- Registration of a callback, handles:
  -- self["method"], leads to self["method"](self, ...)
  -- self with function ref, leads to functionref(...)
  -- "addonId" (instead of self) with function ref, leads to functionref(...)
  -- all with an optional arg, which, if present, gets passed as first argument (after self if present)
  target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
    if type(eventname) ~= "string" then
      error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
    end
 
    method = method or eventname
 
    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.
 
    if type(method) ~= "string" and type(method) ~= "function" then
      error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
    end
 
    local regfunc
 
    if type(method) == "string" then
      -- self["method"] calling style
      if type(self) ~= "table" then
        error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
      elseif self==target then
        error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
      elseif type(self[method]) ~= "function" then
        error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
      end
 
      if select("#",...)>=1 then  -- this is not the same as testing for arg==nil!
        local arg=select(1,...)
        regfunc = function(...) self[method](self,arg,...) end
      else
        regfunc = function(...) self[method](self,...) end
      end
    else
      -- function ref with self=object or self="addonId"
      if type(self)~="table" and type(self)~="string" then
        error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2)
      end
 
      if select("#",...)>=1 then  -- this is not the same as testing for arg==nil!
        local arg=select(1,...)
        regfunc = function(...) method(arg,...) end
      else
        regfunc = method
      end
    end
 
 
    if events[eventname][self] or registry.recurse<1 then
    -- if registry.recurse<1 then
      -- we're overwriting an existing entry, or not currently recursing. just set it.
      events[eventname][self] = regfunc
      -- fire OnUsed callback?
      if registry.OnUsed and first then
        registry.OnUsed(registry, target, eventname)
      end
    else
      -- we're currently processing a callback in this registry, so delay the registration of this new entry!
      -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
      registry.insertQueue = registry.insertQueue or setmetatable({},meta)
      registry.insertQueue[eventname][self] = regfunc
    end
  end
 
  -- Unregister a callback
  target[UnregisterName] = function(self, eventname)
    if not self or self==target then
      error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
    end
    if type(eventname) ~= "string" then
      error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
    end
    if rawget(events, eventname) and events[eventname][self] then
      events[eventname][self] = nil
      -- Fire OnUnused callback?
      if registry.OnUnused and not next(events[eventname]) then
        registry.OnUnused(registry, target, eventname)
      end
    end
    if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
      registry.insertQueue[eventname][self] = nil
    end
  end
 
  -- OPTIONAL: Unregister all callbacks for given selfs/addonIds
  if UnregisterAllName then
    target[UnregisterAllName] = function(...)
      if select("#",...)<1 then
        error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
      end
      if select("#",...)==1 and ...==target then
        error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
      end
 
 
      for i=1,select("#",...) do
        local self = select(i,...)
        if registry.insertQueue then
          for eventname, callbacks in pairs(registry.insertQueue) do
            if callbacks[self] then
              callbacks[self] = nil
            end
          end
        end
        for eventname, callbacks in pairs(events) do
          if callbacks[self] then
            callbacks[self] = nil
            -- Fire OnUnused callback?
            if registry.OnUnused and not next(callbacks) then
              registry.OnUnused(registry, target, eventname)
            end
          end
        end
      end
    end
  end
 
  return registry
end
 
 
-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
-- try to upgrade old implicit embeds since the system is selfcontained and
-- relies on closures to work.