Skip to content

Commit c4a667d

Browse files
committed
callback handler is needed for libSharedMedia
1 parent ce94d48 commit c4a667d

File tree

2 files changed

+214
-1
lines changed

2 files changed

+214
-1
lines changed
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
--[[ $Id: CallbackHandler-1.0.lua 1186 2018-07-21 14:19:18Z nevcairiel $ ]]
2+
local MAJOR, MINOR = "CallbackHandler-1.0", 7
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+
-- Lua APIs
10+
local tconcat = table.concat
11+
local assert, error, loadstring = assert, error, loadstring
12+
local setmetatable, rawset, rawget = setmetatable, rawset, rawget
13+
local next, select, pairs, type, tostring = next, select, pairs, type, tostring
14+
15+
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
16+
-- List them here for Mikk's FindGlobals script
17+
-- GLOBALS: geterrorhandler
18+
19+
local xpcall = xpcall
20+
21+
local function errorhandler(err)
22+
return geterrorhandler()(err)
23+
end
24+
25+
local function Dispatch(handlers, ...)
26+
local index, method = next(handlers)
27+
if not method then return end
28+
repeat
29+
xpcall(method, errorhandler, ...)
30+
index, method = next(handlers, index)
31+
until not method
32+
end
33+
34+
--------------------------------------------------------------------------
35+
-- CallbackHandler:New
36+
--
37+
-- target - target object to embed public APIs in
38+
-- RegisterName - name of the callback registration API, default "RegisterCallback"
39+
-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
40+
-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
41+
42+
function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName)
43+
44+
RegisterName = RegisterName or "RegisterCallback"
45+
UnregisterName = UnregisterName or "UnregisterCallback"
46+
if UnregisterAllName==nil then -- false is used to indicate "don't want this method"
47+
UnregisterAllName = "UnregisterAllCallbacks"
48+
end
49+
50+
-- we declare all objects and exported APIs inside this closure to quickly gain access
51+
-- to e.g. function names, the "target" parameter, etc
52+
53+
54+
-- Create the registry object
55+
local events = setmetatable({}, meta)
56+
local registry = { recurse=0, events=events }
57+
58+
-- registry:Fire() - fires the given event/message into the registry
59+
function registry:Fire(eventname, ...)
60+
if not rawget(events, eventname) or not next(events[eventname]) then return end
61+
local oldrecurse = registry.recurse
62+
registry.recurse = oldrecurse + 1
63+
64+
Dispatch(events[eventname], eventname, ...)
65+
66+
registry.recurse = oldrecurse
67+
68+
if registry.insertQueue and oldrecurse==0 then
69+
-- Something in one of our callbacks wanted to register more callbacks; they got queued
70+
for eventname,callbacks in pairs(registry.insertQueue) do
71+
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.
72+
for self,func in pairs(callbacks) do
73+
events[eventname][self] = func
74+
-- fire OnUsed callback?
75+
if first and registry.OnUsed then
76+
registry.OnUsed(registry, target, eventname)
77+
first = nil
78+
end
79+
end
80+
end
81+
registry.insertQueue = nil
82+
end
83+
end
84+
85+
-- Registration of a callback, handles:
86+
-- self["method"], leads to self["method"](self, ...)
87+
-- self with function ref, leads to functionref(...)
88+
-- "addonId" (instead of self) with function ref, leads to functionref(...)
89+
-- all with an optional arg, which, if present, gets passed as first argument (after self if present)
90+
target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
91+
if type(eventname) ~= "string" then
92+
error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
93+
end
94+
95+
method = method or eventname
96+
97+
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.
98+
99+
if type(method) ~= "string" and type(method) ~= "function" then
100+
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
101+
end
102+
103+
local regfunc
104+
105+
if type(method) == "string" then
106+
-- self["method"] calling style
107+
if type(self) ~= "table" then
108+
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
109+
elseif self==target then
110+
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
111+
elseif type(self[method]) ~= "function" then
112+
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
113+
end
114+
115+
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
116+
local arg=select(1,...)
117+
regfunc = function(...) self[method](self,arg,...) end
118+
else
119+
regfunc = function(...) self[method](self,...) end
120+
end
121+
else
122+
-- function ref with self=object or self="addonId" or self=thread
123+
if type(self)~="table" and type(self)~="string" and type(self)~="thread" then
124+
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2)
125+
end
126+
127+
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
128+
local arg=select(1,...)
129+
regfunc = function(...) method(arg,...) end
130+
else
131+
regfunc = method
132+
end
133+
end
134+
135+
136+
if events[eventname][self] or registry.recurse<1 then
137+
-- if registry.recurse<1 then
138+
-- we're overwriting an existing entry, or not currently recursing. just set it.
139+
events[eventname][self] = regfunc
140+
-- fire OnUsed callback?
141+
if registry.OnUsed and first then
142+
registry.OnUsed(registry, target, eventname)
143+
end
144+
else
145+
-- we're currently processing a callback in this registry, so delay the registration of this new entry!
146+
-- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
147+
registry.insertQueue = registry.insertQueue or setmetatable({},meta)
148+
registry.insertQueue[eventname][self] = regfunc
149+
end
150+
end
151+
152+
-- Unregister a callback
153+
target[UnregisterName] = function(self, eventname)
154+
if not self or self==target then
155+
error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
156+
end
157+
if type(eventname) ~= "string" then
158+
error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
159+
end
160+
if rawget(events, eventname) and events[eventname][self] then
161+
events[eventname][self] = nil
162+
-- Fire OnUnused callback?
163+
if registry.OnUnused and not next(events[eventname]) then
164+
registry.OnUnused(registry, target, eventname)
165+
end
166+
end
167+
if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
168+
registry.insertQueue[eventname][self] = nil
169+
end
170+
end
171+
172+
-- OPTIONAL: Unregister all callbacks for given selfs/addonIds
173+
if UnregisterAllName then
174+
target[UnregisterAllName] = function(...)
175+
if select("#",...)<1 then
176+
error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
177+
end
178+
if select("#",...)==1 and ...==target then
179+
error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
180+
end
181+
182+
183+
for i=1,select("#",...) do
184+
local self = select(i,...)
185+
if registry.insertQueue then
186+
for eventname, callbacks in pairs(registry.insertQueue) do
187+
if callbacks[self] then
188+
callbacks[self] = nil
189+
end
190+
end
191+
end
192+
for eventname, callbacks in pairs(events) do
193+
if callbacks[self] then
194+
callbacks[self] = nil
195+
-- Fire OnUnused callback?
196+
if registry.OnUnused and not next(callbacks) then
197+
registry.OnUnused(registry, target, eventname)
198+
end
199+
end
200+
end
201+
end
202+
end
203+
end
204+
205+
return registry
206+
end
207+
208+
209+
-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
210+
-- try to upgrade old implicit embeds since the system is selfcontained and
211+
-- relies on closures to work.
212+

libs/Libs.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
2-
<Script file="LibSharedMedia-3.0.lua" />
32
<Script file="LibStub\LibStub.lua"/>
3+
<Script file="CallbackHandler-1.0\CallbackHandler-1.0.lua"/>
4+
<Script file="LibSharedMedia-3.0.lua" />
45
<Script file="Taka-0.0\Taka-0.0.lua"/>
56
<Include file="AceAddon-3.0\AceAddon-3.0.xml"/>
67
<Include file="AceGUI-3.0\AceGUI-3.0.xml"/>

0 commit comments

Comments
 (0)