Thread Tools Display Modes
11-09-15, 04:07 AM   #1
Yukyuk
A Chromatic Dragonspawn
 
Yukyuk's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2015
Posts: 179
Is this smart programming (Understading lua).

Working on a new addon and I want to use constants.
My constans are placed in a seperate lua file that gets loaded before the rest of the lua files.

My question is about the Color constants (end of the code).
I want to use them as follows:
Lua Code:
  1. addon:Debug(private.COLORS["GREEN"].hex .. "variable text" .. private.COLORS["END"].hex)

Is this a good way to do this?
Is it better to "copy" (local COLORS = private.COLORS) to other lua files before using the COLORS table?
Is there another (easier/better) way I have't thought of?

Any advice about this topic by more experienced lua users is appriciated.

Lua Code:
  1. --[[    *** Deathcounter ***
  2. Written by : Yukyuk, EU-Moonglade
  3. --]]
  4.  
  5. -------------------------------------------------------------------------------
  6. --  From _Global to local.
  7. -------------------------------------------------------------------------------
  8. local _G = _G
  9.  
  10. local tinsert   = _G.table.insert
  11. local tremove   = _G.table.remove
  12. local tsort     = _G.table.sort
  13. local twipe     = _G.table.wipe
  14.  
  15. local pairs     = _G.pairs
  16. local ipairs    = _G.ipairs
  17.  
  18.  
  19. -------------------------------------------------------------------------------
  20. --  AddOn namespace.
  21. -------------------------------------------------------------------------------
  22. local FOLDER_NAME, private = ...
  23.  
  24.  
  25. -------------------------------------------------------------------------------
  26. --  AddOn constants.
  27. -------------------------------------------------------------------------------
  28. private.addon_name = "Deathcounter"
  29. private.addon_version = "1.0"  
  30.  
  31.  
  32. -------------------------------------------------------------------------------
  33. --  Player constants.
  34. -------------------------------------------------------------------------------
  35. private.PLAYER_NAME     = _G.UnitName("player")
  36. private.PLAYER_FACTION  = _G.UnitFactionGroup("player")
  37. private.REALM_NAME      = _G.GetRealmName()
  38.  
  39.  
  40. -------------------------------------------------------------------------------
  41. --  Color constants
  42. -------------------------------------------------------------------------------
  43. private.COLORS = {
  44.     ["GREEN"]   = {hex = "|cff00ff00"},
  45.     ["RED"]     = {hex = "|cffff0000"},
  46.     ["END"]     = {hex = "|r"}
  47.     }
__________________
Better to fail then never have tried at all.
  Reply With Quote
11-09-15, 04:45 AM   #2
lightspark
A Rage Talon Dragon Guard
 
lightspark's Avatar
AddOn Author - Click to view addons
Join Date: Sep 2012
Posts: 341
Originally Posted by Yukyuk View Post
Is this a good way to do this?
It's a good way to keep your code tidy. As for you colours table, if you only keep hex values, why dun you just store it this way?

Lua Code:
  1. private.COLORS = {
  2.     ["GREEN"]   = "|cff00ff00",
  3.     ["RED"]     = "|cffff0000",
  4.     ["END"]     = "|r",
  5. }

Having one extra table isn't cool...

Originally Posted by Yukyuk View Post
Is it better to "copy" (local COLORS = private.COLORS) to other lua files before using the COLORS table?
You can read Phanx' post on this topic. I prefer to create shortcuts to certain tables, but private.COLORS is fine.

Originally Posted by Yukyuk View Post
Is there another (easier/better) way I have't thought of?
Not really...
__________________

Last edited by lightspark : 11-09-15 at 05:13 AM.
  Reply With Quote
11-09-15, 11:06 AM   #3
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Originally Posted by Yukyuk View Post
Is it better to "copy" (local COLORS = private.COLORS) to other lua files before using the COLORS table?
It depends on how often you're going to access the table. If you're only reading it occasionally to print a message to the user, you don't need an upvalue. If you're looking up colors every time a combat log event occurs, inside an OnUpdate script, or even in response to something like UNIT_HEALTH, you should use an upvalue.

On a side note, "local colors = private.COLORS" is not creating a copy of the table; it's only creating a "shortcut" to accessing the same table. This shortcut is called an "upvalue" (shortcut to a value up in a higher scope) or a "local reference". It's like using a bookmark to quickly find your place the next time you open the book, instead of having to flip through all the pages and figure out where you were, or scan through the table of contents. However, you don't want to upvalue everything -- if you end up with a bookmark on every page that's just as bad, so you need to have a balance.

Originally Posted by Yukyuk View Post
Is there another (easier/better) way I have't thought of?
Again, it really depends on what you're doing, but I would just write "|r" directly in your string and leave it out of your table. It's entirely static (eg. you could later decide you wanted a lighter red and change the hex code for "RED" accordingly, but there's nothing to change with "|r") and typing "|r" is much easier/faster/shorter (and more readable) than typing "private.COLORS["END"]".

Personally, I wouldn't even use a table at all for just two colors. I'd just create two variables:

Code:
private.COLOR_GREEN = "|cff00ff00"
private.COLOR_RED = "|cffff0000"
...and whether I'd upvalue those in other files would depend on how often they were being accessed. I would probably not bother with a table in this scenario until I was up to 5+ colors.

However, if you later made the colors user-editable with an in-game GUI (so they were stored in saved variables) then I would put them in a table even with just 2, to keep them separated from other settings, eg.

Code:
MyAddonSettings = {
     blah = 57,
     fdsfdafda = true,
     colors = {
        red = "|cffff0000",
        green = "|cff00ff00",
     }
}
-------------------------------------------

Finally, you should reconsider whether putting all your constants in a separate file is actually beneficial. Loading files is by far the slowest part of the addon loading process, so unless you have 5000 constants (and if you do, then you should probably reconsider your whole addon design!) it's probably better just to put them at the top of your main file, or (if your addon has multiple files) just define them as locals in the file that actually uses them.
__________________
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-09-15, 02:21 PM   #4
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
Lua Code:
  1. local tinsert   = table.insert
  2. local tremove   = table.remove
  3. local tsort     = table.sort
  4. local twipe     = table.wipe
  5.  
  6. local pairs     = pairs
  7. local ipairs    = ipairs
  8.  
  9. -------------------------------------------------------------------------------
  10. --  AddOn namespace.
  11. -------------------------------------------------------------------------------
  12. local FOLDER_NAME, private = ...
  13.  
  14. -------------------------------------------------------------------------------
  15. --  AddOn constants.
  16. -------------------------------------------------------------------------------
  17. private.addon_name = "Deathcounter" -- Isn't this FOLDER_NAME?
  18. private.addon_version = "1.0" -- This could be placed in the .toc file
  19.  
  20. -------------------------------------------------------------------------------
  21. --  Player constants.
  22. -------------------------------------------------------------------------------
  23. private.PLAYER_NAME     = UnitName("player")
  24. private.PLAYER_FACTION  = UnitFactionGroup("player")
  25. private.REALM_NAME      = GetRealmName()   
  26.  
  27. -------------------------------------------------------------------------------
  28. --  Color constants
  29. -------------------------------------------------------------------------------
  30. private.COLORS = {
  31.     GREEN   = "|cff00ff00%s|r",
  32.     RED     = "|cffff0000%s|r",
  33. }

Upvalueing the global table is pretty pointless if you're not doing thousands of lookups. The same can be said about storing name, faction and realm. Also, my personal preference for color escape sequences is to use %s and format the string instead of concatenating. Depending on what you're using it for, this might be a better approach:
Lua Code:
  1. print(format(private.COLORS.RED, "This text is red"))
  2. print(format(private.COLORS.GREEN, "This text is green"))
> This text is red
> This text is green
__________________

Last edited by MunkDev : 11-09-15 at 09:30 PM.
  Reply With Quote
11-09-15, 02:28 PM   #5
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Originally Posted by MunkDev View Post
Also, my personal preference for color escape sequences is to use %s and format the string instead of concatenating.
Calling a function is more expensive than a simple concatenation. There's no excuse for using string.format just to join two strings together.
__________________
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-09-15, 02:35 PM   #6
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
Originally Posted by Phanx View Post
Calling a function is more expensive than a simple concatenation. There's no excuse for using string.format just to join two strings together.
Hence, depends on usage. I just use that approach to keep the code readable and it's not exactly a huge overhead to format a string every now and then. I personally don't use format for simple concatenation, but I do use it in cases where I frequently use differing strings of the same format. Like this:
Lua Code:
  1. OVERRIDE            = "%s is already bound to %s.\nPress |T%s:20:20:0:0|t again to continue anyway.",
  2. SUCCESS             = "|T%s:16:16:0:0|t was successfully bound to %s.",
  3. CONTINUE            = "Press |T%s:20:20:0:0|t again to continue.",
  4. CONFIRM             = "Press |T%s:20:20:0:0|t again to confirm.",
__________________
  Reply With Quote
11-09-15, 03:05 PM   #7
Lombra
A Molten Giant
 
Lombra's Avatar
AddOn Author - Click to view addons
Join Date: Nov 2006
Posts: 554
I do like format for readability. At least if there's more than one variable, or if it's not at the beginning or the end.
__________________
Grab your sword and fight the Horde!
  Reply With Quote
11-09-15, 08:58 PM   #8
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Yes, string.format is fine and good for localization or inserting many variables into long strings, but that's not what we're talking about. You suggested doing this:

Code:
local msg = string.format("%s%s", private.COLOR_RED, "This text is red")
... instead of this:

Code:
local msg = private.COLOR_RED .. "This text is red"
... and there's really no excuse for that, even if you're only doing it once. If you just want to prepend a static value to a string, just do that. Don't overcomplicate things. This isn't even about program optimization; it's about just using the most straightforward method to achieve your goal. Using string.format in this case is as silly as using select(1, ...) -- which I have sadly seen, more than once, in live code.
__________________
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-09-15, 09:29 PM   #9
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
Sure, formatting is redundant in this case. But I actually suggested...
Lua Code:
  1. local msg = format(private.COLORS.GREEN, "variable text")
...instead of...
Lua Code:
  1. local msg = private.COLORS["GREEN"].hex .. "variable text" .. private.COLORS["END"].hex
...for the sake of readability. Your intentionally complicated extension of that is not very readable.
__________________

Last edited by MunkDev : 11-09-15 at 09:34 PM.
  Reply With Quote
11-10-15, 03:22 AM   #10
Yukyuk
A Chromatic Dragonspawn
 
Yukyuk's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2015
Posts: 179
Understanding lua.

Thank you all for the responses.
Learned (again) from this discussion.

For me personally this piece was the most valuable.
On a side note, "local colors = private.COLORS" is not creating a copy of the table; it's only creating a "shortcut" to accessing the same table. This shortcut is called an "upvalue" (shortcut to a value up in a higher scope) or a "local reference". It's like using a bookmark to quickly find your place the next time you open the book, instead of having to flip through all the pages and figure out where you were, or scan through the table of contents. However, you don't want to upvalue everything -- if you end up with a bookmark on every page that's just as bad, so you need to have a balance.
Keeping my code tidy matters to me quite a bit.
It's a good way to keep your code tidy
Have been programming most of my adult life (in various languages) and keeping my code clean, readable and maintainable matters.

Thats why I want to have a beter understanding of lua, because my lua coding skills still need a lot of work.
__________________
Better to fail then never have tried at all.
  Reply With Quote
11-10-15, 04:19 AM   #11
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
I suggest in some post to use environment control to make your code more simple to be read.

If global.lua is your first lua file in toc.

Lua Code:
  1. -- Modify the environment, using private table as environment
  2. setfenv(1, setmetatable( select(2, ...), { __index = function(self, key)
  3.     local val = _G[key]
  4.     if val ~= nil then rawset(self, key, val) end
  5.     return val
  6. end } ))
  7.  
  8. -- Global part
  9. -------------------------------------------------------------------------------
  10. --  AddOn constants.
  11. -------------------------------------------------------------------------------
  12. addon_name = "Deathcounter" -- Isn't this FOLDER_NAME?
  13. addon_version = "1.0" -- This could be placed in the .toc file
  14.  
  15. -------------------------------------------------------------------------------
  16. --  Player constants.
  17. -------------------------------------------------------------------------------
  18. PLAYER_NAME     = UnitName("player")
  19. PLAYER_FACTION  = UnitFactionGroup("player")
  20. REALM_NAME      = GetRealmName()    
  21.  
  22. -------------------------------------------------------------------------------
  23. --  Color constants
  24. -------------------------------------------------------------------------------
  25. COLORS = {
  26.     GREEN   = "|cff00ff00%s|r",
  27.     RED     = "|cffff0000%s|r",
  28. }

When use in your addons' other file :

Lua Code:
  1. setfenv(1, select(2, ...) )
  2.  
  3. addon:Debug(COLORS["GREEN"].hex .. "variable text" .. COLORS["END"].hex)

Use a private environment can make your codes be shared in every lua files and without taint the _G.

About the optimization, maybe you can check this post http://www.wowinterface.com/forums/s...d.php?p=283565

Last edited by kurapica.igas : 11-10-15 at 04:25 AM.
  Reply With Quote
11-10-15, 08:52 AM   #12
zork
A Pyroguard Emberseer
 
zork's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2008
Posts: 1,740
Interesting approach. But is there something wrong about doing this?

A.lua
Lua Code:
  1. local addonName, addonTable = ...
  2.  
  3.   addonTable.G = {} --global
  4.   addonTable.L = {} --local (addon only)
  5.   addonTable.C = {} --config
  6.   addonTable.DB = {} --database
  7.  
  8.   -------------------------------------
  9.   -- VARIABLES
  10.   -------------------------------------
  11.  
  12.   addonTable.L.constants = {
  13.     name          = addonName,
  14.     version       = GetAddOnMetadata(L.name, "Version"),
  15.     locale        = GetLocale()  
  16.   }
  17.  
  18.   --make table globally available
  19.   MyAddonGlobalName = addonTable.G

B.lua
Lua Code:
  1. local an, at = ...
  2. local L,C = at.L, at.C --just get what you need
  3.  
  4. --create button func
  5. function L:CreateButton(parent,name,text,adjustWidth,adjustHeight)
  6.  
  7.   --button frame
  8.   local b = CreateFrame("Button", name, parent, "UIPanelButtonTemplate")
  9.   b.text = _G[b:GetName().."Text"]
  10.   b.text:SetText(text)
  11.   b:SetWidth(b.text:GetStringWidth()+(adjustWidth or 20))
  12.   b:SetHeight(b.text:GetStringHeight()+(adjustHeight or 12))
  13.  
  14.   return b
  15.  
  16. end

If I expose addonTable.G to global scope. Can another addon iterate it and access addonTable.L?
__________________
| Simple is beautiful.
| WoWI AddOns | GitHub | Zork (WoW)

"I wonder what the non-pathetic people are doing tonight?" - Rajesh Koothrappali (The Big Bang Theory)

Last edited by zork : 11-10-15 at 09:00 AM.
  Reply With Quote
11-10-15, 11:42 AM   #13
Fizzlemizz
I did that?
 
Fizzlemizz's Avatar
Premium Member
AddOn Author - Click to view addons
Join Date: Dec 2011
Posts: 1,877
addonTable is private to the addon so unless there is a way to iterate up (a GetParent() for table structures) only the .G branch is available outside the addon via MyAddonGlobalName.
__________________
Fizzlemizz
Maintainer of Discord Unit Frames and Discord Art.
Author of FauxMazzle, FauxMazzleHUD and Move Pad Plus.

Last edited by Fizzlemizz : 11-10-15 at 11:48 AM.
  Reply With Quote
11-10-15, 06:15 PM   #14
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Originally Posted by kurapica.igas View Post
I suggest in some post to use environment control to make your code more simple to be read.
It's an absurd overreaction to create a custom environment instead of just typing the keyword "local" in the appropriate places. That doesn't make your code more readable -- it just lets you be lazy and not have to understand or pay attention to proper variable scoping.
__________________
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-10-15, 06:19 PM   #15
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Originally Posted by zork View Post
Interesting approach. But is there something wrong about doing this?

Lua Code:
  1. local addonName, addonTable = ...
  2.  
  3.   addonTable.G = {} --global
  4.   addonTable.L = {} --local (addon only)
  5.   addonTable.C = {} --config
  6.   addonTable.DB = {} --database

If I expose addonTable.G to global scope. Can another addon iterate it and access addonTable.L?
1. There's nothing wrong with that, but it's not "solving" the same "problem" -- kurapica.igas was proposing to use a custom function environment to avoid the use of "local" keywords.

2. There's no "get parent" method for tables. so if you're in t.G you cannot access t, or any other keys/subtables in t unless there's a pointer to it in t.G.

3. Names like "G" and "C" suck. If you come back to your code after 6 months or a year, are you going to remember what those even mean? Addons aren't macros, space is not limited, and there's no good reason to not use meaningful variable/key names. If you need a comment to remember what your variable is for, you're doing something wrong. It should be immediately obvious anywhere in your code what the purpose of a variable is -- not just at the point where it's initially defined.
__________________
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-15 at 06:21 PM.
  Reply With Quote
11-10-15, 08:31 PM   #16
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
Originally Posted by Phanx View Post
It's an absurd overreaction to create a custom environment instead of just typing the keyword "local" in the appropriate places. That doesn't make your code more readable -- it just lets you be lazy and not have to understand or pay attention to proper variable scoping.
Control environment is very useful in many ways, sharing data, avoid taint and control variable scope, if you mean two file may create same name variable, nested environment can be used like :

Module "MyAddon"

Module "MyAddon.SomeModule"

It won't block you using 'local' on frequently called apis to gain some performance improvement.

To make an add-on with many files, using nested environments to separate them is an easy way to control the data sharing.

The main problem of using environment is it'd cost time for learning and testing, it's not required for beginner. So I only suggest it, not recommend it.
  Reply With Quote
11-12-15, 03:29 PM   #17
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,326
Originally Posted by kurapica.igas View Post
Originally Posted by Phanx View Post
Originally Posted by kurapica.igas View Post
I suggest in some post to use environment control to make your code more simple to be read.
It's an absurd overreaction to create a custom environment instead of just typing the keyword "local" in the appropriate places. That doesn't make your code more readable -- it just lets you be lazy and not have to understand or pay attention to proper variable scoping.
Control environment is very useful in many ways, sharing data, avoid taint and control variable scope

The main problem of using environment is it'd cost time for learning and testing, it's not required for beginner. So I only suggest it, not recommend it.
There only proper use for isolated environments is to restrict what untrusted code can access and/or execute. It's a security device, not a crutch for poor variable scoping.
__________________
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-12-15, 09:38 PM   #18
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
Originally Posted by SDPhantom View Post
There only proper use for isolated environments is to restrict what untrusted code can access and/or execute. It's a security device, not a crutch for poor variable scoping.
If you mean the restricted environment used by secure template, that's yes, blz only use the environment control on that feature.

For lua, it's just a common feature.

In small add-on, with little features, control the variable scope seems meaningless, one table could to contains it all, the result would be same no matter you using environment control or not.

For an add-on with many of files, files are separated by their features, control the file's variable scoping will be useful to reduce the bugs.

And there are more in it, by control the environments, it would be able to provide more features, take an example, register event and handle them directly in the files like :

Lua Code:
  1. Module "xxxx"
  2.  
  3. function OnLoad(self)
  4.     self:RegisterEvent("UNIT_AURA")
  5. end
  6.  
  7. function UNIT_AURA(self, ...)
  8.    -- ...
  9. end

All those can be done without setfenv, that's right. So just the code for lazy guys.
  Reply With Quote
11-13-15, 12:47 AM   #19
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,326
Originally Posted by kurapica.igas View Post
If you mean the restricted environment used by secure template, that's yes, blz only use the environment control on that feature.
I mean that's all it ever should be used for. Blizzard uses it for secure handlers, others use it to run a nested Lua interpreter inside the main Lua engine that runs code written by an end-user. I've even seen examples of it used to implement a Lua-based OS using isolated environments to run programs that happen to be other Lua files.

These are legitimate needs for isolated environments. Your method is just a suggested replacement for variable scoping and in the long run, largely hinders performance. Not to mention adding another layer of unnecessary complexity to the code in addition to denying access to every crucial Lua and WoW API if the addon author wasn't wise enough to include references to said functions in each new environment.
__________________
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-13-15, 04:44 AM   #20
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Originally Posted by kurapica.igas View Post
For an add-on with many of files, files are separated by their features, control the file's variable scoping will be useful to reduce the bugs.
If someone is running into bugs caused by improper variable scoping that frequently, they need to learn how variable scoping works, not how to employ a(n expensive) workaround to shield them from their own incompetence and/or inexperience. And besides, if someone is not yet advanced enough as a programmer to understand how variable scoping works, they're almost certainly not yet advanced enough to understand how custom environments work either.

(Also, this isn't even the first time you've come into a thread that is clearly asking for advice/tips for new [to WoW] programmers, and started suggesting this overcomplicated setfenv nonsense. )
__________________
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

WoWInterface » Developer Discussions » Lua/XML Help » Is this smart programming (Understading lua).


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