SHARE
TWEET

Untitled

a guest May 19th, 2019 64 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --------------------------------------------------------------------------------
  2. -- Kui Nameplates
  3. -- By Kesava at curse.com
  4. -- All rights reserved
  5. --------------------------------------------------------------------------------
  6. -- Handle frame event listeners, dispatch messages, init plugins/elements/layout
  7. --------------------------------------------------------------------------------
  8. local addon = KuiNameplates
  9. local kui = LibStub('Kui-1.0')
  10.  
  11. local k,listener,plugin,_
  12. local type,strsub,pairs,ipairs,unpack,tinsert,tremove=
  13.       type,strsub,pairs,ipairs,unpack,tinsert,tremove
  14.  
  15. local listeners = {}
  16.  
  17. local messages = {}
  18. messages.__index = messages
  19.  
  20. -------------------------------------------------------------- debug helpers --
  21. local function TableToString(tbl)
  22.     if type(tbl) ~= 'table' then return end
  23.     if type(tbl.state) == 'table' and type(tbl.state.name) == 'string' then
  24.         -- assuming KNP frame
  25.         return 'frame:`'..tbl.state.name..'`'
  26.     elseif type(tbl.name) == 'string' then
  27.         -- assuming KNP plugin
  28.         return 'table:'..tbl.name
  29.     end
  30. end
  31. local function VarArgsToString(...)
  32.     local ac
  33.     if #{...} > 0 then
  34.         for k,v in pairs({...}) do
  35.             if type(v) == 'table' then
  36.                 v = TableToString(v)
  37.             end
  38.             ac = (ac and ac..', '..k..':' or k..':')..tostring(v)
  39.         end
  40.     end
  41.     return ac
  42. end
  43. local function PrintDebugForMessage(msg,listener,...)
  44.     if addon.DEBUG_IGNORE and addon.DEBUG_IGNORE['m:'..msg] then return end
  45.  
  46.     local ac = VarArgsToString(...)
  47.     addon:print('p:'..(listener.priority or '?')..' |cff88ff88m:'..msg..'|r > '..(listener.name or 'nil')..(ac and ' |cffaaaaaa'..ac or ''))
  48. end
  49. local function PrintDebugForEvent(event,table,unit,...)
  50.     if addon.DEBUG_IGNORE and addon.DEBUG_IGNORE['e:'..event] then return end
  51.  
  52.     local ac = VarArgsToString(...)
  53.     addon:print('p:'..(table.priority or '?')..' |cffffff88e:'..event..(unit and '|cff8888ff:'..unit or '')..'|r > '..(table.name or 'nil')..(ac and ' |cffaaaaaa'..ac or ''))
  54. end
  55. local function PrintDebugForCallback(plugin,callback,...)
  56.     local fn = plugin.name..':'..callback
  57.     if addon.DEBUG_IGNORE and addon.DEBUG_IGNORE['c:'..fn] then return end
  58.  
  59.     local ac = VarArgsToString(...)
  60.     local cbc = type(plugin.callbacks[callback][1]) == 'function' and 1 or #plugin.callbacks[callback]
  61.     addon:print('|cff88ffffc:'..fn..'|r:'..cbc..(ac and ' |cffaaaaaa'..ac or ''))
  62. end
  63.  
  64. -- event/message performance tracer
  65. local TraceStart,TraceEnd
  66. do
  67.     local ev_start,ev_sum,ev_count
  68.     function TraceStart(uid)
  69.         if not addon.profiling then return end
  70.         UpdateAddOnCPUUsage()
  71.         ev_start = ev_start or {}
  72.         ev_start[uid] = GetAddOnCPUUsage('Kui_Nameplates')
  73.     end
  74.     function TraceEnd(uid)
  75.         if not addon.profiling or not ev_start or not ev_start[uid] then
  76.             return
  77.         end
  78.         UpdateAddOnCPUUsage()
  79.         local ev_end = GetAddOnCPUUsage('Kui_Nameplates')
  80.         local ev_delta = ev_end - ev_start[uid]
  81.         ev_start[uid] = nil
  82.  
  83.         ev_sum = ev_sum or {}
  84.         ev_count = ev_count or {}
  85.  
  86.         ev_count[uid] = 1 + (ev_count[uid] or 0)
  87.         ev_sum[uid] = ev_delta + (ev_sum[uid] or 0)
  88.     end
  89.     function addon:PrintTrace(sort_key)
  90.         if not ev_count or not ev_sum then return end
  91.         sort_key = (sort_key or 3)+1
  92.         local ev_sort = {}
  93.         for uid,count in pairs(ev_count) do
  94.             local sum = ev_sum[uid]
  95.             local avg = sum / count
  96.             tinsert(ev_sort,{uid,count,sum,avg})
  97.         end
  98.         table.sort(ev_sort,function(a,b)
  99.             return a[sort_key] > b[sort_key]
  100.         end)
  101.         local d = kui:DebugPopup()
  102.         for i,v in ipairs(ev_sort) do
  103.             d:AddText(format('|cffffff88%s|r #%d | sum: %.4fms | avg: %.4fms',unpack(v)))
  104.         end
  105.         d:Show()
  106.     end
  107. end
  108. ----------------------------------------------------- core message dispatcher --
  109. function addon:DispatchMessage(msg,...)
  110.     if listeners[msg] then
  111.         --[===[@debug@
  112.         TraceStart('m:'..msg)
  113.         --@end-debug@]===]
  114.  
  115.         for i,listener_tbl in ipairs(listeners[msg]) do
  116.             local listener,func = unpack(listener_tbl)
  117.  
  118.             if addon.debug_messages then
  119.                 PrintDebugForMessage(msg,listener,...)
  120.             end
  121.  
  122.             if type(func) == 'string' and type(listener[func]) == 'function' then
  123.                 func = listener[func]
  124.             elseif type(listener[msg]) == 'function' then
  125.                 func = listener[msg]
  126.             end
  127.  
  128.             if type(func) == 'function' then
  129.                 func(listener,...)
  130.             else
  131.                 addon:print(format('|cffff0000no listener for m:%s in %s',msg,listener.name or 'nil'))
  132.             end
  133.         end
  134.  
  135.         --[===[@debug@
  136.         TraceEnd('m:'..msg)
  137.         --@end-debug@]===]
  138.     end
  139. end
  140. ------------------------------------------------------------- event functions --
  141. local unit_event_frame = CreateFrame('Frame')
  142. local event_frame = CreateFrame('Frame')
  143. local event_index = {}
  144.  
  145. -- iterate plugins/elements which have registered the given event
  146. local function DispatchEventToListeners(event,unit,unit_frame,...)
  147.     --[===[@debug@
  148.     TraceStart('e:'..event)
  149.     --@end-debug@]===]
  150.  
  151.     for i,listener_tbl in ipairs(event_index[event]) do
  152.         local table,func = unpack(listener_tbl)
  153.  
  154.         -- resolve function...
  155.         if type(func) == 'string' and type(table[func]) == 'function' then
  156.             func = table[func]
  157.         elseif type(table[event]) == 'function' then
  158.             func = table[event]
  159.         end
  160.  
  161.         -- call registered function
  162.         if type(func) == 'function' then
  163.             if unit_frame then
  164.                 func(table,event,unit_frame,unit,...)
  165.             else
  166.                 func(table,event,...)
  167.             end
  168.  
  169.             if addon.debug_events then
  170.                 PrintDebugForEvent(event,table,unit,...)
  171.             end
  172.         else
  173.             addon:print('|cffff0000no listener for ue:'..event..' in '..(table.name or 'nil'))
  174.         end
  175.     end
  176.  
  177.     --[===[@debug@
  178.     TraceEnd('e:'..event)
  179.     --@end-debug@]===]
  180. end
  181. ------------------------------------------------------------ unit event frame --
  182. -- a "unit event" by this definition relies on the event returning a unit,
  183. -- and a nameplate being available with that unit. We find the nameplate for
  184. -- the plugin/element and pass it in an argument to its function, or do not
  185. -- call the function if a nameplate cannot be found.
  186. local function unit_event_frame_OnEvent(self,event,unit,...)
  187.     if not event_index[event] then
  188.         self:UnregisterEvent(event)
  189.         return
  190.     end
  191.  
  192.     -- find nameplate matching returned unit
  193.     if not unit then
  194.         addon:print('ue:'..event..':nil returned no unit')
  195.         return
  196.     end
  197.     if type(unit) ~= 'string' or strsub(unit,1,9) ~= 'nameplate' then
  198.         -- filter out non-nameplate units
  199.         return
  200.     end
  201.  
  202.     local unit_frame = C_NamePlate.GetNamePlateForUnit(unit)
  203.     unit_frame = unit_frame and unit_frame.kui
  204.  
  205.     if not unit_frame or not unit_frame.unit then
  206.         -- this happens when restricted nameplates are visible,
  207.         -- as events are still fired for those units
  208.         return
  209.     end
  210.  
  211.     DispatchEventToListeners(event,unit,unit_frame,...)
  212. end
  213. unit_event_frame:SetScript('OnEvent',unit_event_frame_OnEvent)
  214. ---------------------------------------------------------- simple event frame --
  215. local function event_frame_OnEvent(self,event,...)
  216.     if not event_index[event] then
  217.         self:UnregisterEvent(event)
  218.         return
  219.     end
  220.  
  221.     DispatchEventToListeners(event,nil,nil,...)
  222. end
  223. event_frame:SetScript('OnEvent',event_frame_OnEvent)
  224. ----------------------------------------------------------- message registrar --
  225. local function pluginHasMessage(table,msg)
  226.     return (type(table.__MESSAGES) == 'table' and table.__MESSAGES[msg])
  227. end
  228. function messages.RegisterMessage(table,msg,func)
  229.     if not table then return end
  230.     if not msg or type(msg) ~= 'string' then
  231.         addon:print('|cffff0000invalid message passed to RegisterMessage by '..(table.name or 'nil'))
  232.         return
  233.     end
  234.     if func and type(func) ~= 'string' and type(func) ~= 'function' then
  235.         addon:print('|cffff0000invalid function passed to RegisterMessage by '..(table.name or 'nil'))
  236.         return
  237.     end
  238.  
  239.     if pluginHasMessage(table,msg) then return end
  240.  
  241.     if addon.debug_messages and table.name then
  242.         addon:print(table.name..' registered m:'..msg)
  243.     end
  244.  
  245.     if not listeners[msg] then
  246.         listeners[msg] = {}
  247.     end
  248.  
  249.     local insert_tbl = { table, func }
  250.  
  251.     -- insert by priority
  252.     if #listeners[msg] > 0 then
  253.         local inserted
  254.         for k,listener in ipairs(listeners[msg]) do
  255.             listener = listener[1]
  256.             if listener.priority > table.priority then
  257.                 -- insert before a higher priority plugin
  258.                 tinsert(listeners[msg], k, insert_tbl)
  259.                 inserted = true
  260.                 break
  261.             end
  262.         end
  263.  
  264.         if not inserted then
  265.             -- no higher priority plugin was found; insert at the end
  266.             tinsert(listeners[msg], insert_tbl)
  267.         end
  268.     else
  269.         -- no current listeners
  270.         tinsert(listeners[msg], insert_tbl)
  271.     end
  272.  
  273.     if not table.__MESSAGES then
  274.         table.__MESSAGES = {}
  275.     end
  276.     table.__MESSAGES[msg] = true
  277. end
  278. function messages.UnregisterMessage(table,msg)
  279.     if not pluginHasMessage(table,msg) then return end
  280.     if type(listeners[msg]) == 'table' then
  281.         for i,listener_tbl in ipairs(listeners[msg]) do
  282.             if listener_tbl[1] == table then
  283.                 tremove(listeners[msg],i)
  284.                 table.__MESSAGES[msg] = nil
  285.                 return
  286.             end
  287.         end
  288.     end
  289. end
  290. function messages.UnregisterAllMessages(table)
  291.     if type(table.__MESSAGES) ~= 'table' then return end
  292.     for msg,_ in pairs(table.__MESSAGES) do
  293.         table:UnregisterMessage(msg)
  294.     end
  295.     table.__MESSAGES = nil
  296. end
  297. ------------------------------------------------------------- event registrar --
  298. local function pluginHasEvent(table,event)
  299.     -- true if plugin is registered for given event
  300.     return (type(table.__EVENTS) == 'table' and table.__EVENTS[event])
  301. end
  302. function messages.RegisterEvent(table,event,func,unit_only)
  303.     -- unit_only: only fire callback if a valid nameplate exists for event unit
  304.     if func and type(func) ~= 'string' and type(func) ~= 'function' then
  305.         addon:print('|cffff0000invalid function passed to RegisterEvent by '..(table.name or 'nil'))
  306.         return
  307.     end
  308.     if not event or type(event) ~= 'string' then
  309.         addon:print('|cffff0000invalid event passed to RegisterEvent by '..(table.name or 'nil'))
  310.         return
  311.     end
  312.     if unit_only and event:find('UNIT') ~= 1 then
  313.         addon:print('|cffff0000unit_only doesn\'t make sense for '..event)
  314.         return
  315.     end
  316.  
  317.     -- XXX possibly allow overwrites
  318.     -- what happens if a plugin registers an event as both types?
  319.     -- does unregistering work correctly?
  320.     if pluginHasEvent(table,event) then return end
  321.  
  322.     local insert_tbl = { table, func }
  323.     event_index[event] = event_index[event] or {}
  324.  
  325.     -- insert by priority
  326.     if #event_index[event] > 0 then
  327.         local inserted
  328.         for k,listener in ipairs(event_index[event]) do
  329.             listener = listener[1]
  330.             if listener.priority > table.priority then
  331.                 tinsert(event_index[event], k, insert_tbl)
  332.                 inserted = true
  333.                 break
  334.             end
  335.         end
  336.  
  337.         if not inserted then
  338.             tinsert(event_index[event], insert_tbl)
  339.         end
  340.     else
  341.         tinsert(event_index[event], insert_tbl)
  342.     end
  343.  
  344.     if not table.__EVENTS then
  345.         table.__EVENTS = {}
  346.     end
  347.     table.__EVENTS[event] = true
  348.  
  349.     if unit_only then
  350.         unit_event_frame:RegisterEvent(event)
  351.     else
  352.         event_frame:RegisterEvent(event)
  353.     end
  354. end
  355. function messages.RegisterUnitEvent(table,event,func)
  356.     table:RegisterEvent(event,func,true)
  357. end
  358. function messages.UnregisterEvent(table,event)
  359.     if not pluginHasEvent(table,event) then return end
  360.     if type(event_index[event]) == 'table' then
  361.         for i,r_table in ipairs(event_index[event]) do
  362.             if r_table[1] == table then
  363.                 tremove(event_index[event],i)
  364.                 table.__EVENTS[event] = nil
  365.                 return
  366.             end
  367.         end
  368.     end
  369. end
  370. function messages.UnregisterAllEvents(table)
  371.     if type(table.__EVENTS) ~= 'table' then return end
  372.     for event,_ in pairs(table.__EVENTS) do
  373.         table:UnregisterEvent(event)
  374.     end
  375.     table.__EVENTS = nil
  376. end
  377. ------------------------------------------------------------- callback helper --
  378. local function VerifyCallbackArguments(table,target,name,func)
  379.     if type(func) ~= 'function' then
  380.         addon:print((table.name or 'nil')..': invalid call to AddCallback: no function')
  381.         return
  382.     end
  383.  
  384.     target = addon:GetPlugin(target)
  385.     if not target then
  386.         addon:print((table.name or 'nil')..': invalid call to Callback function: no plugin by given name')
  387.         return
  388.     end
  389.  
  390.     if type(target.__CALLBACKS) ~= 'table' or not target.__CALLBACKS[name] then
  391.         addon:print((table.name or 'nil')..': no callback '..name..' in '..(target.name or 'nil'))
  392.         return
  393.     end
  394.  
  395.     return target
  396. end
  397. function messages.RegisterCallback(table,name,return_needed)
  398.     -- register a callback to this plugin
  399.     -- return_needed: only allow one callback function
  400.     if not table.__CALLBACKS then
  401.         table.__CALLBACKS = {}
  402.     end
  403.     table.__CALLBACKS[name] = return_needed and 2 or 1
  404. end
  405. function messages.AddCallback(table,target,name,func,priority)
  406.     -- add a callback function
  407.     target = VerifyCallbackArguments(table,target,name,func)
  408.     if not target then return end
  409.  
  410.     if not priority then
  411.         priority = table.priority or 0
  412.     end
  413.  
  414.     local insert_tbl = { func,priority }
  415.  
  416.     if not target.callbacks then
  417.         target.callbacks = {}
  418.     end
  419.  
  420.     if target.__CALLBACKS[name] == 1 then
  421.         if not target.callbacks[name] then
  422.             target.callbacks[name] = {}
  423.         end
  424.  
  425.         local inserted
  426.         for i,cb in ipairs(target.callbacks[name]) do
  427.             if cb[2] > priority then
  428.                 tinsert(target.callbacks[name],i,insert_tbl)
  429.                 inserted = true
  430.                 break
  431.             end
  432.         end
  433.  
  434.         if not inserted then
  435.             tinsert(target.callbacks[name],insert_tbl)
  436.         end
  437.     elseif target.__CALLBACKS[name] == 2 then
  438.         if not target.callbacks[name] or
  439.            priority > target.callbacks[name][2]
  440.         then
  441.             target.callbacks[name] = insert_tbl
  442.         end
  443.     end
  444. end
  445. function messages.RemoveCallback(table,target,name,func)
  446.     -- remove callback function matching given arguments
  447.     target = VerifyCallbackArguments(table,target,name,func)
  448.     if not target then return end
  449.     if not target:HasCallback(name) then return end
  450.  
  451.     if target.__CALLBACKS[name] == 1 then
  452.         for i,cb in ipairs(target.callbacks[name]) do
  453.             if cb[1] == func then
  454.                 tremove(target.callbacks[name],i)
  455.             end
  456.         end
  457.     else
  458.         if target.callbacks[name][1] == func then
  459.             target.callbacks[name] = nil
  460.         end
  461.     end
  462. end
  463. function messages.HasCallback(table,name)
  464.     if  table.__CALLBACKS and table.__CALLBACKS[name] and table.callbacks and
  465.         table.callbacks[name] and #table.callbacks[name] > 0
  466.     then
  467.         return true
  468.     end
  469. end
  470. function messages.RunCallback(table,name,...)
  471.     -- run this plugin's named callback
  472.     if not table:HasCallback(name) then return end
  473.     if addon.debug_callbacks then
  474.         PrintDebugForCallback(table,name,...)
  475.     end
  476.     --[===[@debug@
  477.     TraceStart('c:'..name)
  478.     --@end-debug@]===]
  479.  
  480.     if table.__CALLBACKS[name] == 2 then
  481.         -- inherit return from forced single callback
  482.         --[===[@debug@
  483.         if addon.profiling then
  484.             local r = {table.callbacks[name][1](...)}
  485.             TraceEnd('c:'..name)
  486.             return unpack(r)
  487.         end
  488.         --@end-debug@]===]
  489.         return table.callbacks[name][1](...)
  490.     else
  491.         for i,cb in ipairs(table.callbacks[name]) do
  492.             cb[1](...)
  493.         end
  494.         --[===[@debug@
  495.         TraceEnd('c:'..name)
  496.         --@end-debug@]===]
  497.         return true
  498.     end
  499. end
  500. ----------------------------------------------- plugin/element-only functions --
  501. local function plugin_Enable(table)
  502.     if not table.enabled then
  503.         table.enabled = true
  504.  
  505.         if type(table.OnEnable) == 'function' then
  506.             table:OnEnable()
  507.         end
  508.     end
  509. end
  510. local function plugin_Disable(table)
  511.     if table.enabled then
  512.         table.enabled = nil
  513.  
  514.         if type(table.OnDisable) == 'function' then
  515.             table:OnDisable()
  516.         end
  517.  
  518.         table:UnregisterAllMessages()
  519.         table:UnregisterAllEvents()
  520.     end
  521. end
  522. ------------------------------------------------------------ plugin registrar --
  523. -- priority         = Any number. Defines the load order. Default of 5.
  524. --                    Plugins with a higher priority are executed later.
  525. -- [max_minor]      = Maximum NKP minor this plugin is known to support.
  526. --                    Ignored if nil.
  527. -- [enable_on_load] = Enable this plugin upon initialise.
  528. --                    True if nil.
  529. function addon:NewPlugin(name,priority,max_minor,enable_on_load)
  530.     if not name then
  531.         error('Plugin with no name ignored')
  532.         return
  533.     end
  534.  
  535.     if (name == 'BarAuras' and not max_minor) or -- XXX legacy
  536.        (max_minor and self.MINOR > max_minor)
  537.     then
  538.         error('Ignoring out of date plugin: `'..name..'`')
  539.         return
  540.     end
  541.  
  542.     if enable_on_load == nil then
  543.         enable_on_load = true
  544.     end
  545.  
  546.     local pluginTable = {
  547.         Enable = plugin_Enable,
  548.         Disable = plugin_Disable,
  549.         name = name,
  550.         enable_on_load = enable_on_load,
  551.         plugin = true,
  552.         priority = tonumber(priority) or 5
  553.     }
  554.     setmetatable(pluginTable, messages)
  555.     tinsert(addon.plugins, pluginTable)
  556.  
  557.     return pluginTable
  558. end
  559. function addon:GetPlugin(name)
  560.     for i,plugin in ipairs(addon.plugins) do
  561.         if plugin.name == name then return plugin end
  562.     end
  563. end
  564. -------------------------------------------------- external element registrar --
  565. -- elements are just plugins with a lower default priority
  566. function addon:NewElement(name,priority,max_minor)
  567.     local ele = self:NewPlugin(name,tonumber(priority) or 0,max_minor,true)
  568.     ele.plugin = nil
  569.     ele.element = true
  570.     return ele
  571. end
  572. ------------------------------------------------------------ layout registrar --
  573. function addon:Layout()
  574.     if self.layout then
  575.         self:ui_print('More than one layout is enabled.')
  576.         return
  577.     end
  578.  
  579.     self.layout = {
  580.         layout = true,
  581.         priority = 100
  582.     }
  583.     setmetatable(self.layout, messages)
  584.  
  585.     return self.layout
  586. end
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top