WoWInterface

WoWInterface (https://www.wowinterface.com/forums/index.php)
-   Lua/XML Help (https://www.wowinterface.com/forums/forumdisplay.php?f=16)
-   -   separate modules within 1 addon (https://www.wowinterface.com/forums/showthread.php?t=45098)

Aanson 11-08-12 11:18 AM

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.

SDPhantom 11-08-12 11:35 AM

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.

Aanson 11-08-12 01:44 PM

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

Phanx 11-08-12 03:50 PM

Quote:

Originally Posted by Aanson (Post 268554)
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)
Quote:

Originally Posted by Aanson (Post 268554)
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:

Quote:

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",
          },
    }
}


SDPhantom 11-08-12 06:32 PM

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.

Aanson 11-09-12 10:21 AM

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

Torhal 11-09-12 05:26 PM

What I'm not understanding is why you're going with an array-style table (using addonTable[1] and addonTable[2])...

Aanson 11-09-12 06:38 PM

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

semlar 11-09-12 08:09 PM

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.

SDPhantom 11-09-12 08:32 PM

Could be that he doesn't know tables can have indicies of different types too.

Quote:

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

semlar 11-09-12 10:33 PM

Well, he uses V.Defaults, etc. in his last post. The vararg might be throwing him off.

Phanx 11-10-12 02:06 AM

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.

Aanson 11-11-12 03:19 PM

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?

Aanson 11-11-12 04:04 PM

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.

jeruku 11-11-12 04:40 PM

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)

Phanx 11-11-12 07:53 PM

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.

AnrDaemon 11-14-12 06:26 PM

I don't think, that blindly hooking "OnEvent" is a good practice?

Seerah 11-14-12 08:20 PM

No one's hooking anything.

Phanx 11-14-12 08:32 PM

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. :(

AnrDaemon 11-15-12 09:55 AM

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.


All times are GMT -6. The time now is 03:43 AM.

vBulletin © 2024, Jelsoft Enterprises Ltd
© 2004 - 2022 MMOUI