Quantcast
[LUA] How to do this library works for more than 1 addon at the same time - WoWInterface
Thread Tools Display Modes
12-15-16, 11:44 AM   #1
millanzarreta
A Deviate Faerie Dragon
 
millanzarreta's Avatar
AddOn Author - Click to view addons
Join Date: Dec 2010
Posts: 11
[LUA] How to do this library works for more than 1 addon at the same time

Hi, I create a new library with the purpose to track some units to check what talents are they using (enemy units), but I have one problem to separate the dates from the diferents addons that use it.

When I started to create it, I believed all addons that use it would retrieve a different "copy" of my addon library, but no, using LibStub only one Library object it's created and used on all addons (this make sense to save resources, but I had not noticed...)

My code looks similar to this (example code to show concept):

Lua Code:
  1. local LIB_MAJOR, LIB_MINOR = "LibTalentsTrack-1.0", 1
  2.  
  3. local LibTalentsTrack = LibStub:NewLibrary(LIB_MAJOR, LIB_MINOR)
  4. if not LibTalentsTrack then return end
  5.  
  6. local libUnitsToTrack = {}  -- the variable that saves the units to track and their info
  7.  
  8. function LibTalentsTrack:RegisterUnit(unitName) do
  9.     -- ... do stuff
  10.     ...
  11.     libUnitsToTrack[unitName] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} --the 0s are the talents tiers and pvp talents tiers
  12.     return 0
  13. end
  14.  
  15. function LibTalentsTrack:UnregisterUnit(unitName) do
  16.     -- ... do stuff
  17.     ...
  18.     libUnitsToTrack[unitName] = nil
  19.     return 0
  20. end
  21.  
  22. function LibTalentsTrack:UnregisterAllUnits(unitName) do
  23.     -- ... do stuff
  24.     ...
  25.     for k, v in pairs(libUnitsToTrack) do
  26.         self:UnregisterUnit(unitName)
  27.     end
  28.     return 0
  29. end
  30.  
  31. -- more functions etc...
  32. ...

The problem it's all addons have the same object, so, for example, if addon #1 register some units, and addon #2 register other units, and addon #2 then calls UnregisterAllUnits, this action deletes all the units (including units registered by addon #1 and #2). My intention is if the addon #2 calls UnregisterAllUnits only addon #2 unit's should be removed.

Ok, how can I deal with this? For the data variable "libUnitsToTrack" I know that I can do a structure like this:

Lua Code:
  1. libUnitsToTrack[addonName][unitName] =  = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

or like this:

Lua Code:
  1. libUnitsToTrack[unitName] = {{}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
  2. --where:
  3. libUnitsToTrack[unitName][1] = {addon1, addon2, addon3, ...}  -- In this [1] I store the addons that tracks this unit

instead of the actual:

Lua Code:
  1. libUnitsToTrack[unitName] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

But the problem it's how I can easy manage this since the addon part, I don't like add one more argument (the addon name) for all "RegisterUnit" and "UnregisterUnit" functions. I really have many functions to register and unregister units (allowing the addon that use this library register units by unitId, unitName, unitGUID, etc...). I prefer a solution more simplest and more transparent for the user. I like some like this but I don't know how I can do this:

Lua Code:
  1. -- this it's the addon code that wants use my lib
  2. local LibTalentsTrack = LibStub("LibTalentsTrack-1.0")
  3. IDKWhatShouldReturnHere = LibTalentsTrack:RegisterNewAddon("MyAddonName")
  4.  
  5. -- and now the addon can use the functions without pass the addon name as argument, and the library function know what addon call's the function
  6. LibTalentsTrack:RegisterUnit(unitName)
  7.  
  8. ...

Some way to do this? I don't know if I have explained well :S

Thanks for the help!

TL;DR: I am doing a library. I am looking for some way to use a global library object in diferents addons and I wants the library functions know the addon that which were called.
  Reply With Quote
12-15-16, 04:12 PM   #2
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,055
There are two ways you can go at this. The first is requiring the addon to provide its name to every function requiring the differentiation. Another is to change your API so every addon has to call a generator function that returns a control object which is used by the library to isolate data for each addon.
__________________
ESOUI AddOns | WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)

Last edited by SDPhantom : 12-15-16 at 04:55 PM.
  Reply With Quote
12-15-16, 04:40 PM   #3
millanzarreta
A Deviate Faerie Dragon
 
millanzarreta's Avatar
AddOn Author - Click to view addons
Join Date: Dec 2010
Posts: 11
How I can do the second way? Can you provide me some simple example?

Ty!
  Reply With Quote
12-15-16, 04:55 PM   #4
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,055
Here's an example of data isolation by generator function. The use of metatables can be tricky to comprehend, but it's easier on the system than scanning each addon's unit table. It uses a trick in which __mode in a metatable can control whether or not the GarbageCollector can take keys and/or values out of a table when it runs. When it does take either from a table, it removes the entire entry.

This actually uses two metatables. MasterList uses it as described above. The other is to apply our API functions to the individual tables we return to each addon in our generator function.
Lua Code:
  1. --[[    Library Registration    ]]
  2. local LibTalentsTrack=LibStub:NewLibrary("LibTalentsTrack-1.0",1);
  3. if not LibTalentsTrack then return; end
  4.  
  5. --[[    Internal Variables  ]]
  6. local MasterList=setmetatable({},{__mode="v"});--   Generate table with weak values to let GarbageCollection take them as necessary
  7.  
  8. --[[    Tracker Object Metatable    ]]
  9. local TrackerMeta={__index={}};
  10.  
  11. function TrackerMeta.__index:RegisterUnit(unit)
  12. --  self is our tracker object table given to addons
  13.     if UnitExists(unit) then
  14.         local guid=UnitGUID(unit);--    Save by GUID to guarantee correct tracking
  15.  
  16.         local unitdata=MasterList[guid];
  17.         if not unitdata then--  Is new unit?
  18.             unitdata={Name=UnitName(unit)};--   Populate this with default data
  19.             MasterList[guid]=userdata;--    Register table with our master list
  20.         end
  21.         self[guid]=unitdata;--  Save unit in our own list
  22.     end
  23. end
  24.  
  25. function TrackerMeta.__index:UnregisterUnit(unit)
  26. --  self is our tracker object table given to addons
  27.     if UnitExists(unit) then
  28.         self[UnitGUID(unit)]=nil;-- Erase our copy of the unit table (MasterList will lose its copy if this was the last reference to it next GarbageCollection cycle)
  29.     end
  30. end
  31.  
  32. function TrackerMeta.__index:UnregisterAllUnits(unit)
  33. --  self is our tracker object table given to addons
  34.     table.wipe(self);-- Erase everything (metatable stays intact)
  35. end
  36.  
  37. --  Generates new tracker object for addons to use
  38. function LibTalentsTrack:NewTracker()
  39.     return setmetatable({},TrackerMeta);
  40. end
  41.  
  42. --[[    Tracker Stuff   ]]
  43.  
  44. --[[
  45.     Do all your CLEU stuff and store all your data in the unit table at MasterList[GUID]
  46.     Be sure to check its existence every CLEU event as the GarbageCollector will randomly take it away after all other references to it are unregistered
  47. ]]



This allows addons using your lib to do this.
Lua Code:
  1. --  Grab a new tracker object from our lib
  2. local UnitTracker=LibStub:GetLibrary("LibTalentsTrack-1.0"):NewTracker();
  3.  
  4. local EventFrame=CreateFrame("Frame");
  5. EventFrame:RegisterEvent("PLAYER_TARGET_CHANGED");
  6. EventFrame:SetScript("OnEvent",function(self,event,...)
  7.     UnitTracker:RegisterUnit("target");--   Register target for tracking
  8. end);
__________________
ESOUI AddOns | WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)

Last edited by SDPhantom : 12-15-16 at 05:37 PM.
  Reply With Quote
12-16-16, 11:23 AM   #5
millanzarreta
A Deviate Faerie Dragon
 
millanzarreta's Avatar
AddOn Author - Click to view addons
Join Date: Dec 2010
Posts: 11
Ohh, that code looks nice... Can I modify it to not use the TrackerMeta object directly as data store? Can I use the TrackerMeta to store only the addon name (and the functions) and use this addon name to access a local variables when I wants?

I can't test this now, but I am looking some like this (modified version of your code):

lua Code:
  1. --[[    Library Registration    ]]
  2. local LibTalentsTrack=LibStub:NewLibrary("LibTalentsTrack-1.0",1);
  3. if not LibTalentsTrack then return; end
  4.  
  5. --[[    Internal Variables  ]]
  6. local MasterList=setmetatable({},{__mode="v"});--   Generate table with weak values to let GarbageCollection take them as necessary
  7.  
  8. --[[    Tracker Object Metatable    ]]
  9. local TrackerMeta={__index={}};
  10.  
  11. local libUnitsToTrack = {}          --accesed libUnitsToTrack[addonName][guid]
  12. local libUnitsToDoSomething2 = {}   --accesed libUnitsToDoSomething2[addonName][guid]
  13. local libUnitsToDoSomething3 = {}   --accesed libUnitsToDoSomething3[addonName][guid]
  14.  
  15. function TrackerMeta.__index:RegisterUnit(unit)
  16. --  self is our tracker object table given to addons
  17.     if UnitExists(unit) then
  18.         local guid=UnitGUID(unit);--    Save by GUID to guarantee correct tracking
  19.         local addonName = self["addonName"]
  20.         libUnitsToTrack[addonName][guid] = {0, 0, 0} --my default data
  21.     end
  22. end
  23.  
  24. function TrackerMeta.__index:UnregisterUnit(unit)
  25. --  self is our tracker object table given to addons
  26.     if UnitExists(unit) then
  27.         local addonName = self["addonName"]
  28.         libUnitsToTrack[addonName][UnitGUID(unit)]=nil;-- Erase data
  29.     end
  30. end
  31.  
  32. function TrackerMeta.__index:UnregisterAllUnits(unit)
  33. --  self is our tracker object table given to addons
  34.     local addonName = self["addonName"]
  35.     table.wipe(libUnitsToTrack[addonName]);-- Erase everything
  36. end
  37.  
  38. --  Generates new tracker object for addons to use (if exists, return the current tracker for that addon)
  39. function LibTalentsTrack:NewTracker(addonName)
  40.     if addonName == nil then return end
  41.     local existTracker=MasterList[addonName];
  42.     if not existTracker then
  43.         local tracker = setmetatable({},TrackerMeta);
  44.         tracker["addonName"] = addonName
  45.         MasterList[addonName]=tracker["addonName"]
  46.         return tracker
  47.     end
  48.     return existTracker
  49. end
  50.  
  51. function LibTalentsTrack:DeleteTracker(tracker)
  52.     if tracker == nil then return end
  53.     local addonName = tracker["addonName"]
  54.     table.wipe(tracker)
  55.     tracker = nil
  56.     MasterList[addonName]=nil
  57. end
  58.  
  59. --Do stuff without problems with MasterList and GarbageCollector

If this is possible, it's easy for me programming the rest of library and I can use infinity variables to store info (with your code my data it's limited to the TrackerMeta object). I could manage complicated think like MasterList only on NewTracker and DeleteTracker functions and forget about this in the rest of the code. On every function that I needs differentiation I only should use local addonName = self["addonName"] and no more complications by metatables and GarbageCollector. It's this possible?

Forgive me if I pretend do some in wrong way, but the use of metatables and metafunctions are difficult to undestand for me :S

And one more question... what it's the purpose for the MasterList? I know it's for GarbageCollection, but, I don't know why it's needed...

Seriously, thank you very much, your help is helping me a lot!
  Reply With Quote
12-16-16, 12:13 PM   #6
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,055
TrackerMeta only stores your API functions for the tracker objects. It doesn't store any data at all. When an addon wants to interact with your library, it calls the generator function, :NewTracker(). This returns an empty table used to store the unit data and TrackerMeta attaches its functions to the new table so they can be run on it.

MasterList allows central access and synchronization of all unit data. When an addon registers a unit, if it sees the unit already tracked, it grabs a pointer to the existing unit table. If there isn't one, it's created and stored both in the addon's own table and MasterList. This method also allows the library to update info directly to MasterList and the changes propagate to the individual addon tables as well.

A beneficial side effect of this method is it's entirely self-cleaning. If a tracker object gets released to the GarbageCollector, it starts a cascading effect where it acts as if the addon unregistered all of its units.
__________________
ESOUI AddOns | WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » [LUA] How to do this library works for more than 1 addon at the same time

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off