Thread Tools Display Modes
11-08-12, 11:18 AM   #1
Aanson
A Flamescale Wyrmkin
Join Date: Aug 2009
Posts: 124
separate modules within 1 addon

Hi there ladies / gents.

Over the last year or so, I've been self learning Lua and it's turned into a bit of a hobby where writing code has become more enjoyable than.. ehem.. playing the game!

I've written a UI with optional integration into different aspects of the game that the user can disable/enable at will (ie Chat, Auction House etc etc). For clarity's sake, modules.

Each module is a different Lua file within the one addon, so effectively, the TOC reads:

Core.lua
Auction_Module.lua
Chat_Module.lua
KeyBindButtonBox_Module.lua

While the Core.lua file contains many functions that other modules use, functions within each module are created solely for that module.

What I would very much like to do is this... If, say, the Chat module is shown as disabled in my DB on startup or reload, I'd like to be able to prevent any of that module's code from being read.

I've experimented in several ways to do this. My most recent attempt was to call a function in Core.lua which would send a addon message with the 2nd arg being "load Chat Module". The chat_Module.lua in turn has an OnEvent script that would listen for that call and read/call the functions when it gets it.

It works. But not very well. I've found that it never loads the chat script when I log in for the first time, but it always does when I reload.

I'm guessing that the reason for this is that by the time the game has read (and called) my "load Chat Module" function in Core.lua, it hasn't even started reading Chat_Module.lua yet. Like I say, that's just my guess, I might be well off.

Any suggestions for this would be greatly appreciated. I'm just very cautious about wasting memory if it's not required... especially with me being a beginner and all.

P.S. Phanx, if you read this, thanks very much for your suggestions and code regarding setting up defaults for the addon's DB.


Aanson.
__________________
__________________
  Reply With Quote
11-08-12, 11:35 AM   #2
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
I would suggest putting the modules in their own LoD addon with the core as a dependency. Then whenever you need to load them, you can use LoadAddOn() on them. If you really want to continue having them contained in the single addon, then you could either make a toggle function in the module and either make that global so you can call it or put a pointer to it in the addon's private table so the core has access to it.

Here's an example of the second method using the addon's private table. Note, this is only accessible in Lua files, there is no equivalent for XML.

In Chat_Module.lua
Lua Code:
  1. local name,addon=...;
  2. function addon.ChatModuleEnable()
  3. -- Enable your chat module here
  4. end

In Core.lua
Lua Code:
  1. local name,addon=...;
  2. addon.ChatModuleEnable();-- Calls function defined in Chat_Module.lua

This works because a vararg in the main chunk of your Lua files returns the Addon's string ID (the name of the folder your addon is contained in) and an empty local table generated on load that is shared among all of your Lua files.

Note you can put the function call anywhere, but the variables need to be defined in the main chunk as the vararg changes if put in a function.
__________________
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 : 11-08-12 at 11:40 AM.
  Reply With Quote
11-08-12, 01:44 PM   #3
Aanson
A Flamescale Wyrmkin
Join Date: Aug 2009
Posts: 124
Cheers Phantom,

Yeah that's how I have it just now. Well, the only difference is that in your example code, I've split 'addon' into two separate tables with the code:

Lua Code:
  1. local addon, addonName = ...
  2.  
  3. core[1] = {};
  4. core[2] = {};
  5.  
  6. local F, V = unpack(select(2, ...));

But even with that, I still can't get it to work. The main reason I think is that I'd need to encompass all of my Chat_Module.lua functions within the ChatModuleEnable() function that you suggested in order to ensure that the code is only read if the module is enabled.

Can I dare ask?

On a scale of 1 to 10 on the bad practice scale, how bad would it be for the Chat_Module.lua functions to be read and stored to memory, but never called? (when the module is disabled, that is).

Hope I'm making sense here.


Aanson
__________________
__________________
  Reply With Quote
11-08-12, 03:50 PM   #4
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Originally Posted by Aanson View Post
Lua Code:
  1. local addon, addonName = ...
  2.  
  3. core[1] = {};
  4. core[2] = {};
  5.  
  6. local F, V = unpack(select(2, ...));
I'm not really sure where you are going (or trying to go) with that code, and it should be dying with nil value errors because you're attempting to add values to a core table you never defined as a table (or anything else).

Also, the vararg passed to addon Lua files gives the addon's name first, and the private table second, so your order is backwards:

Code:
local addonName, addonPrivateTable = ...
Also also, since you already have a reference to the private table, there's no need to spend a select call to get it again:

Code:
local F, V = unpack(addonPrivateTable)
Originally Posted by Aanson View Post
how bad would it be for the Chat_Module.lua functions to be read and stored to memory, but never called?
Not bad at all. Even if your chat module is 100 KB of code, who cares? The static memory usage of a WoW addon is absolutely irrelevant on any machine capable of running WoW in the first place. If you are somehow eating up 100 MB then you will probably want to look into why your chat module was taking up that much memory, but otherwise, remember the two rules of program optimization:

First Rule of Program Optimization: Don’t do it.
Second Rule of Program Optimization (for experts only!): Don’t do it yet.
If you'd prefer to roll your own module system, I'd go with something like this:

Core file loads first:
Code:
addon.modules = {}

function addon:RegisterModule(name, object)
     -- Really, this function is not necessary, but it makes it much easier
     -- to add common features to all of your modules, like self:Print or
     -- other common methods. Also, an API feels cleaner than just adding
     -- stuff to your table from other files.

     self.modules[name] = object
end

function addon:ADDON_LOADED(name)
     if name ~= addonName then return end
     -- By the time this event fires for this addon, all of
     -- this addon's files and saved variables have loaded.

     -- Initialize your saved variables here.

     -- Do core initialization stuff here.

     for moduleName, moduleObject in pairs(self.modules) do
          if self.db.modules[moduleName].enable then
               moduleObject:Enable()
          end
     end
end
And in each module file, which loads after the core file:
Code:
function module:Enable()
          [color="Silver"][i]-- Do stuff here that should happen when the module loads.
end

addon:RegisterModule("SomeModule", module)
Then, set up your addon's saved variables to include something like this:
Code:
addon.db = {
     modules = {
          SomeModule = {
               enable = true, -- This module is enabled.
               someOtherSetting = 9000,
          },
          OtherModule = {
               enable = false, -- This module is NOT enabled.
               foo = "bar",
          },
     }
}
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.
  Reply With Quote
11-08-12, 06:32 PM   #5
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
Whenever I need to have a set of functions specific to a module in the private table, I usually have a table specifically for the module and store it within the private table. This would produce something like the following.

Module1
Code:
local name,addon=...;
local module={};-- Create module table
addon.Module1=module;-- This stores our local module table as "Module1" of the addon table

module.Variable="Some value";

function module.Enable()
-- Enable module here
end

function module.Disable()
-- Disable module here (if you want to be able to)
end

function module.DoSomething()
-- Any other stuff you want to do
end
Core
Code:
local name,addon=...;
addon.Module1.Enable();-- Enable Module1


Note you can use the private table like any other table and being shared among all Lua files of your addon opens it up to easily be used to create an internal API in whatever structure you want.
__________________
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 : 11-08-12 at 06:38 PM.
  Reply With Quote
11-09-12, 10:21 AM   #6
Aanson
A Flamescale Wyrmkin
Join Date: Aug 2009
Posts: 124
Thank you

That sounds great. Phanx, apologies, I wrote that from memory yesterday and obviously didn't pay attention to what I was doing, I've copy / pasted now which might explain where I'm going with that first bit of code you quoted there:

Lua Code:
  1. local addon, addonName = ...
  2.  
  3. addonName[1] = {};
  4. addonName[2] = {};
  5.  
  6. EarthernUI = addonName;
  7. EarthernDB = {};
  8.  
  9. local F, V = unpack(select(2, ...));

... but clearly from what you've said there, I'll be changing it to ...

Lua Code:
  1. local addonName, addonTable = ...
  2.  
  3. addonTable[1] = {};
  4. addonTable[2] = {};
  5.  
  6. EarthernUI = addonTable;
  7. EarthernDB = {};
  8.  
  9. local F, V = unpack(select(2, ...));

Thank you for taking the time out to help guys. I think I'll def go with the format you provided and I like the idea of using separate tables within 'F' for each module.

Thanks again!


Aanson
__________________
__________________

Last edited by Aanson : 11-09-12 at 10:35 AM.
  Reply With Quote
11-09-12, 05:26 PM   #7
Torhal
A Pyroguard Emberseer
 
Torhal's Avatar
AddOn Author - Click to view addons
Join Date: Aug 2008
Posts: 1,196
What I'm not understanding is why you're going with an array-style table (using addonTable[1] and addonTable[2])...
__________________
Whenever someone says "pls" because it's shorter than "please", I say "no" because it's shorter than "yes".

Author of NPCScan and many other AddOns.
  Reply With Quote
11-09-12, 06:38 PM   #8
Aanson
A Flamescale Wyrmkin
Join Date: Aug 2009
Posts: 124
Yeah, that's likely because I didn't mention why. It's solely for the purpose of separating functions from variables/constants/defaults.

F stores only functions
V.Defaults = {}
V.Media = {}
V.Colours = {}

It's just my preference, nothing more.


Aanson
__________________
__________________
  Reply With Quote
11-09-12, 08:09 PM   #9
semlar
A Pyroguard Emberseer
 
semlar's Avatar
AddOn Author - Click to view addons
Join Date: Sep 2007
Posts: 1,060
I think what he means is why are you calling it addonTable[1] instead of addonTable.Functions or whatever.

Also, "local F, V = unpack(select(2, ...))" is a very bizarre way of assigning those variables. You've already assigned "addonTable" to what you're pulling out with "select(2,...)" and using unpack could potentially be dangerous unless you're quite sure of the order of things.

Last edited by semlar : 11-09-12 at 08:22 PM.
  Reply With Quote
11-09-12, 08:32 PM   #10
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
Could be that he doesn't know tables can have indicies of different types too.

The type table implements associative arrays, that is, arrays that can be indexed not only with numbers, but with any value (except nil). Tables can be heterogeneous; that is, they can contain values of all types (except nil). Tables are the sole data structuring mechanism in Lua; they can be used to represent ordinary arrays, symbol tables, sets, records, graphs, trees, etc. To represent records, Lua uses the field name as an index. The language supports this representation by providing a.name as syntactic sugar for a["name"]. There are several convenient ways to create tables in Lua (see §2.5.7).

Like indices, the value of a table field can be of any type (except nil). In particular, because functions are first-class values, table fields can contain functions. Thus tables can also carry methods (see §2.5.9).
Lua 5.1 Reference Manual §2.2
__________________
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
11-09-12, 10:33 PM   #11
semlar
A Pyroguard Emberseer
 
semlar's Avatar
AddOn Author - Click to view addons
Join Date: Sep 2007
Posts: 1,060
Well, he uses V.Defaults, etc. in his last post. The vararg might be throwing him off.
  Reply With Quote
11-10-12, 02:06 AM   #12
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
The addon-level vararg (...) always contains two values:

1. A string value containing the name of your addon's folder.
2. An empty table.

You can modify the contents of the table, but you cannot change which string or which table the vararg contains.

First, you're assigning those two values to the variables addonName and addonTable:

Code:
local addonName, addonTable = ...
Next, you're adding two empty tables to the addonTable table, with the numeric keys 1 and 2:

Code:
addonTable[1] = {}
addonTable[2] = {}
Then you're using the select function to get a reference to the second value in the vararg, instead of just using the addonTable variable you already assigned that value to.

Then you're using the unpack table to get references to the values in that table with the numeric keys 1 and 2 (unpack returns all values in a table with sequential 1-based numeric keys), instead of just looking them up directly by key.

Then you're assigning those values back to the variables F and V.

Hopefully with this spelled out, you can see how convoluted what you're doing is.

If you want your addon's private table to contain two other tables, here is a more straightforward, more efficient, and less confusing way to do that:

Code:
-- Assign local reference names to the contents of the vararg:
local addonName, addonTable = ...

-- Create the two other tables with local reference names:
local F, V = {}, {}

-- Add those tables to the table that was the second content of the vararg:
addonTable[1], addonTable[2] = F, V
This is more straightforward, does not require any function calls (which are the slowest things you can do in Lua), and requires fewer table lookups (which are pretty fast, but definitely not free).

Also note that unpack ignores key/value pairs where the key is not a sequential integer. If you have this table:

Code:
local myTable = {
     [1] = "one",
     [2] = "two",
     [3] = "three",
     [7] = "SEVEN!",
     [8] = "eight",
     ["cat"] = "meow",
     ["dog"] = "woof",
     ["cow"] = "moo",
}
... and you call unpack on it, you will only get the values "one", "two", and "three" since those are the only values whose keys are sequential integers beginning from 1.
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.

Last edited by Phanx : 11-10-12 at 02:11 AM.
  Reply With Quote
11-11-12, 03:19 PM   #13
Aanson
A Flamescale Wyrmkin
Join Date: Aug 2009
Posts: 124
Understood, thanks alot as always. It was very beneficial to have it broken down in that way.

I always knew it was a bit backwards.

What I didn't realize though was that it is less efficient than other methods (ie just referencing addonTable direct).

I was never worried about unpack() because the table keys were just 1 and 2, but I'll not be using it anymore. From what you've said, I don't want to be needlessly calling a function every time I reference the tables.
Lua Code:
  1. local addonName, addonTable = ...
  2.  
  3. local F = {};
  4. local V = {};
  5.  
  6. addonTable[1] = F;
  7. addonTable[2] = V;
  8.  
  9. EarthernUI = addonTable;

My intention with the final line is to have a global reference to addonTable. That way, when working on modules, I can easily reference to the table by using something like ...

Lua Code:
  1. local F = EarthernUI[1];
  2. local V = EarthernUI[2];
  3.  
  4. local c = V.Checks;
  5. local f = V.Frames;
  6. local m = V.Media;
  7. local v = V.IntVars;

... at the start of each module.

Is it okay to do it in this way?
__________________
__________________
  Reply With Quote
11-11-12, 04:04 PM   #14
Aanson
A Flamescale Wyrmkin
Join Date: Aug 2009
Posts: 124
Sorry, could I ask one other question about the examples you provided earlier regarding the processing of modules?

See if I were to define this function (copy/pasted from your examples below):

Lua Code:
  1. function addon:ADDON_LOADED(name)
  2.      if name ~= addonName then return end
  3.      -- By the time this event fires for this addon, all of
  4.      -- this addon's files and saved variables have loaded.
  5.  
  6.      -- Initialize your saved variables here.
  7.  
  8.      -- Do core initialization stuff here.
  9.  
  10.      for moduleName, moduleObject in pairs(self.modules) do
  11.           if self.db.modules[moduleName].enable then
  12.                moduleObject:Enable()
  13.           end
  14.      end
  15. end

I'm sorry if this is a stupid question, but in this example where the function is called "ADDON_LOADED", I take it I am still required to create a handler frame and register to the event "ADDON_LOADED" and include a line like:

Lua Code:
  1. eventHandler:SetScript("OnEvent", function(self, event, arg1, arg2, arg3, arg4)
  2.     if (event == "ADDON_LOADED") then    -- does this line even need to be here?
  3.         addonName:ADDON_LOADED(arg1);
  4.     end
  5. end);


The reason I ask is because I've never actually defined a function using an event as it's name before. I've always just created a handler frame, given it an OnEvent handler and called each function that I want it to call when a particular event fires.

I'm just wanting to rule out that simply defining the function is enough... ie because of the way in which it has been defined, would it automatically be called when the event fires rendering the above SetScript pointless?

Thanks again. I hope I've been able to make the question clear enough.


Aanson.
__________________
__________________
  Reply With Quote
11-11-12, 04:40 PM   #15
jeruku
A Cobalt Mageweaver
 
jeruku's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2010
Posts: 223
To answer your question, that method will work and is about the same as the following. You can find more in depth detail at WoWWiki or WoWPedia on "handling events".

Lua Code:
  1. local core, events = CreateFrame('Frame', addonName .. 'CoreFrame'), {}
  2.  
  3. core:RegisterEvent('ADDON_LOADED')
  4. function events:ADDON_LOADED(aName, ...)
  5.     print(aName .. " was loaded. Event handled by " .. self:GetName())
  6.     --aName .. " was loaded.  Event handled by " .. core:GetName()
  7. end
  8.  
  9. core:SetScript("OnEvent", function(self, event, ...)
  10.     events[event](self, ...);
  11. end)
__________________
"I have not failed, I simply found 10,000 ways that did not work." - Thomas Edison
  Reply With Quote
11-11-12, 07:53 PM   #16
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Yes, you still need a frame to listen for events. Here's a simple snippet you can use to make events trigger methods of the same name on your addon table:

Code:
-- Create the frame:
local eventFrame = CreateFrame("Frame")

-- Make the frame call your addon methods on events,
-- passing them all the event's arguments:
eventFrame:SetScript("OnEvent", function(self, event, ...)
     return addon[event] and addon[event](addon, ...)
end)

-- Attach the frame to your addon table so you can
-- easily access it to register/unregister events:
addonTable.eventFrame = eventFrame
Then, when PLAYER_LOGIN fires, the function addonTable:PLAYER_LOGIN() will be called if it exists. When UNIT_POWER fires, the function addonTable:UNIT_POWER(unit, powerType) will be called if it exists. Both assuming, of course, that those events are registered on eventFrame.
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.
  Reply With Quote
11-14-12, 06:26 PM   #17
AnrDaemon
A Chromatic Dragonspawn
AddOn Author - Click to view addons
Join Date: Jul 2008
Posts: 156
I don't think, that blindly hooking "OnEvent" is a good practice?
  Reply With Quote
11-14-12, 08:20 PM   #18
Seerah
Fishing Trainer
 
Seerah's Avatar
WoWInterface Super Mod
Featured
Join Date: Oct 2006
Posts: 10,860
No one's hooking anything.
__________________
"You'd be surprised how many people violate this simple principle every day of their lives and try to fit square pegs into round holes, ignoring the clear reality that Things Are As They Are." -Benjamin Hoff, The Tao of Pooh

  Reply With Quote
11-14-12, 08:32 PM   #19
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Setting an OnEvent script on your frame is totally normal, has nothing to do with hooking, does not effect events or their processing by anyone else's frames, and is in fact the only way to respond to events at all.

If you are used to registering events through AceEvent-3.0 or some other event handling library, well, that's all they do on the backend... they just add several layers of convoluted and completely unnecessary wrappers around it. Also you should familiarize yourself with the WoW API basics instead of blindly using libraries whose functionality you don't understand at all.
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.
  Reply With Quote
11-15-12, 09:55 AM   #20
AnrDaemon
A Chromatic Dragonspawn
AddOn Author - Click to view addons
Join Date: Jul 2008
Posts: 156
Phanx, you making assumptions in the void Though, I do agree with your conclusions.
If something is simple as it is, you don't need to make it complicated.
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » separate modules within 1 addon


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