Advertisement
Guest User

Untitled

a guest
May 19th, 2019
79
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.17 KB | None | 0 0
  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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement