| 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 |