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