Advertisement
Guest User

Untitled

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