Files
Questie-Octo/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua
T
2026-08-16 20:28:28 -05:00

253 lines
8.0 KiB
Lua

--[[ Questie CallbackHandler-1.0 API, Lua 5.0 syntax compatibility ]]
-- Original Questie foundation: CallbackHandler-1.0 r1131 / MINOR 6.
-- Turtle 1.12 Lua 5.0 supports vararg declarations but consumes them through
-- the implicit 'arg' table rather than Lua 5.1's '...' expression.
local MAJOR, MINOR = "QuestieOcto_CallbackHandler-1.0", 6
local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
if not CallbackHandler then return end
local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
local tconcat = table.concat
local assert, error, loadstring = assert, error, loadstring
local setmetatable, rawset, rawget = setmetatable, rawset, rawget
local next, pairs, type, tostring = next, pairs, type, tostring
local unpack = unpack
local xpcall = xpcall
local function errorhandler(err)
return geterrorhandler()(err)
end
-- Same Questie dispatcher strategy, emitted with fixed named arguments so the
-- generated source contains no Lua 5.1 vararg expressions.
local function CreateDispatcher(argCount)
local args={}
local oldargs={}
for i=1,argCount do
args[i]="arg"..i
oldargs[i]="old_arg"..i
end
local argList=tconcat(args,", ")
local oldArgList=tconcat(oldargs,", ")
local dispatchSignature="handlers"
if argCount>0 then dispatchSignature=dispatchSignature..", "..argList end
local saveOld
local assignArgs
local restoreOld
if argCount>0 then
saveOld="local old_CALL_ARGS = CALL_ARGS"
assignArgs="CALL_ARGS = {"..argList.."}"
restoreOld="CALL_ARGS = old_CALL_ARGS"
else
saveOld="local old_CALL_ARGS = CALL_ARGS"
assignArgs="CALL_ARGS = {}"
restoreOld="CALL_ARGS = old_CALL_ARGS"
end
local code=[[
return function(next, xpcall, eh, unpack)
local method
local CALL_ARGS = {}
local function call()
method(unpack(CALL_ARGS, 1, CALL_ARGS.n or table.getn(CALL_ARGS)))
end
local function dispatch(DISPATCH_SIGNATURE)
local index
index, method = next(handlers)
if not method then return end
SAVE_OLD
ASSIGN_ARGS
CALL_ARGS.n = ARG_COUNT
repeat
xpcall(call, eh)
index, method = next(handlers, index)
until not method
RESTORE_OLD
end
return dispatch
end
]]
code=string.gsub(code,"DISPATCH_SIGNATURE",dispatchSignature)
code=string.gsub(code,"SAVE_OLD",saveOld)
code=string.gsub(code,"ASSIGN_ARGS",assignArgs)
code=string.gsub(code,"RESTORE_OLD",restoreOld)
code=string.gsub(code,"ARG_COUNT",tostring(argCount))
local chunk=assert(loadstring(code,"safecall Dispatcher["..argCount.."]"))
local factory=chunk()
return factory(next,xpcall,errorhandler,unpack)
end
local Dispatchers=setmetatable({},{
__index=function(self,argCount)
local dispatcher=CreateDispatcher(argCount)
rawset(self,argCount,dispatcher)
return dispatcher
end
})
function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName)
RegisterName=RegisterName or "RegisterCallback"
UnregisterName=UnregisterName or "UnregisterCallback"
if UnregisterAllName==nil then
UnregisterAllName="UnregisterAllCallbacks"
end
local events=setmetatable({},meta)
local registry={recurse=0,events=events}
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
local count=arg.n or table.getn(arg)
local callargs={eventname}
callargs.n=count+1
for i=1,count do callargs[i+1]=arg[i] end
Dispatchers[count+1](events[eventname],unpack(callargs,1,callargs.n))
registry.recurse=oldrecurse
if registry.insertQueue and oldrecurse==0 then
for queuedEvent,callbacks in pairs(registry.insertQueue) do
local first=not rawget(events,queuedEvent) or not next(events[queuedEvent])
for self,func in pairs(callbacks) do
events[queuedEvent][self]=func
if first and registry.OnUsed then
registry.OnUsed(registry,target,queuedEvent)
first=nil
end
end
end
registry.insertQueue=nil
end
end
target[RegisterName]=function(self,eventname,method,...)
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])
if type(method)~="string" and type(method)~="function" then
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.",2)
end
local regfunc
local optionalCount=arg.n or table.getn(arg)
if type(method)=="string" then
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 optionalCount>=1 then
local extra=arg[1]
regfunc=function(...)
self[method](self,extra,unpack(arg,1,arg.n or table.getn(arg)))
end
else
regfunc=function(...)
self[method](self,unpack(arg,1,arg.n or table.getn(arg)))
end
end
else
if type(self)~="table" and type(self)~="string" and type(self)~="thread" then
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.",2)
end
if optionalCount>=1 then
local extra=arg[1]
regfunc=function(...)
method(extra,unpack(arg,1,arg.n or table.getn(arg)))
end
else
regfunc=method
end
end
if events[eventname][self] or registry.recurse<1 then
events[eventname][self]=regfunc
if registry.OnUsed and first then
registry.OnUsed(registry,target,eventname)
end
else
registry.insertQueue=registry.insertQueue or setmetatable({},meta)
registry.insertQueue[eventname][self]=regfunc
end
end
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
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
if UnregisterAllName then
target[UnregisterAllName]=function(...)
local count=arg.n or table.getn(arg)
if count<1 then
error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.",2)
end
if count==1 and arg[1]==target then
error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"",2)
end
for i=1,count do
local self=arg[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
if registry.OnUnused and not next(callbacks) then
registry.OnUnused(registry,target,eventname)
end
end
end
end
end
end
return registry
end