Hi. I copied the code from the "Fading Text Frame" snippet and added it to my addon. I then triggered it using the code: MOD_TextMessage("Test Message"); - sure enough, the message pops up, but it doesn't go away after a few seconds. Am I missing something?:)

Posted by stolenlegacy on Fri, 27 Jan 2012 22:10:20

Move the function definition further up and local it. The function's undefined when :SetScript is called.

Thanks for the reply! Being as I'm just starting out (I'm writing my first Addon), I guess I should learn to avoid bad habits! What is so bad/inefficient about the code & why is accessing a global OnUpdate bad?:)

Posted by stolenlegacy on Fri, 27 Jan 2012 23:46:41

Well, first off, I'm going to assume you're familiar with the basic principle of global/local scopes and how they're different. In general, you want to restrain the amount of global namespace you use to a minimum - not only that, but GETGLOBAL lookups are far slower than the upvalue (local) equivalent - thus, anything that does not have to be global should not be global. This includes global functions you frequently use - you will often see something like this at the start of bigger addons' files:

What this does is it essentially fetches every function reference you need from the global environment and stores it in a local variable. Note that this also prevents your addon from being affected by pre-hooks of thusly fetched functions if said hook is added after the reference is fetched - whether this is a good or bad thing depends on the hook. However, if using hooked functions is necessary, simply adding the hooking addon as an OptDep in your ToC will suffice.

To get back on the subject, as previously established, global lookups are relatively slow (in addition to polluting namespace). An OnUpdate handler runs once per drawn frame - on average machines, this should happen around 40-60 times per second. While on a single lookup, the difference between a local and a global lookup is minor, it quickly becomes an issue when occurring in a frequently-used function, especially OnUpdate scripts.

To conclude, a more efficient version of the snippet in question would look like this:

local function onUpdate(self,elapsed) -- OnUpdate already provides an argument 'elapsed' as arg2 of the call - this contains the amount of seconds passed since the last OnUpdate fired. We can use this for timing purposes instead of calling GetTime on every OnUpdate.
self.time = (self.time or 3)-elapsed
local alpha = self:GetAlpha()
if self.time > 0 then return end
while self.time <= 0 do -- usually, this loop will execute at most once - however, it's possible to have heavy FPS lag and drop below 1/3 frames per second. It never hurts to account for extreme cases.
self.time = self.time+3
alpha = alpha-0.05 -- you can increase this value to quicken the fade. You could also decrease the time delay (lower time delay means 'smoother' fade at the cost of slightly higher resource consumption)
end
if alpha <= 0 then
self:Hide()
else
self:SetAlpha(alpha)
end
end
local textFrame = CreateFrame("Frame","MOD_TextFrame",UIParent) -- make sure your frame name (arg2) is unique (best done by prefixing your addon name), as a global with this name is automatically created for your frame. Pass nil as arg2 if a name is not needed to avoid namespace pollution.
textFrame:SetSize(300,300)
textFrame:Hide()
textFrame:SetScript("OnUpdate",onUpdate)
textFrame.text = textFrame:CreateFontString(nil,"BACKGROUND","PVPInfoTextFont")
textFrame.text:SetAllPoints()
textFrame:SetPoint("CENTER",0,200)
function textFrame:message(message) -- last i checked, method lookups were faster than globals. plus no namespace pollution. if you want to call it from outside this file though, you probably want to make a global function - global lookup (function) + local lookup (frame) vs. global lookup (frame) + method lookup (function). if you do, make sure you prefix with addon name to avoid conflicts.
self.text:SetText(message)
self:SetAlpha(1)
self.time = 3
self:Show()
end

Posted by zookia on Sat, 28 Jan 2012 02:28:52

That's amazingly helpful, thanks so much for taking the time to write the reply. I have done some programming in the past (mostly perl & PHP) but I'm rather rusty & this is my first attempt at writing LUA. I am waiting for the wowprogramming book to arrive, but until then I'm struggling along by looking at other people's examples!:)

I looked quickly at making frames and saw it could be done in XML or LUA, but I decided to just stick with LUA for the time being - if there's an advantage to using XML, I'm sure I'll come across it in due course. My first addon is quite simple as it was just a way of getting into things - it just sends a message when you start or finish resting (something I thought would be useful for my low-level alts, as not every inn gives you rested XP). The main part of it works, but I made a simple function just to tell me that the addon had loaded properly - I thought that would be the easiest part, but it doesn't work at all - not sure why, as before I put the popup message code in, I was getting the rested/not rested message to display in the chat window in the exact same way as the loaded message and that worked fine...

Not a major issue, but rather puzzling why something so simple doesn't work. I checked the code of another addon that writes a "welcome" message & it's identical, so why it works and mine doesn't is quite a mystery - no doubt I've missed something strikingly obvious, but I can't see what!:)

Posted by stolenlegacy on Sun, 29 Jan 2012 00:52:48

OnLoad scripts are called when the frame is loaded - for Lua-created ones, this is as the CreateFrame function is executing. Calling :SetScript("OnLoad",func) later will reset in the script being set after it should be executed, thus never loading.

If you simply want to confirm that the addon has loaded, you probably want to use the OnEvent script handler while registering for ADDON_LOADED, which will fire after all your addon's files have been loaded (including saved variables files).

Example code for this:

local folderName = ... -- the triple dot is a special variable type called a vararg. it takes a list of all arguments passed to the function after the other ones specified (and can thus only be specified as the final argument to a function). for all intents and purposes, it works the same as if all the values contained within were listed in comma-separated format one after another. you can pass them to functions [func(...)], use them as initial content for a table [local t = {...}] and more. you can also use select(num,...) to retrieve all values after a specified index (or combine it with brackets, which are interpreted as "discard everything beyond the first value" to retrieve only one value). All WoW Lua files are called with 2 values inside a vararg by the engine - value 1 is the addon's folder name (which we're getting here) and value 2 is a private table shared among all the addon's files which is not accessible to other addons unless you make it so. as mentioned above, this line thus equivalents 'local folderName = "AddonFolderName", {}' (where the empty table is substituted for the addon table). Phew, that was a long comment.
local f=CreateFrame("Frame")
f:SetScript("OnEvent",function(self,event,...)
if event == "ADDON_LOADED" then -- this is unnecessary if you only listen for ADDON_LOADED, but I provided it anyways in case you want to add more events.
if (...) == folderName then -- this time, the vararg contains all the event-specific arguments (as the first two arguments to the function call, the frame reference itself and the event name are assigned their own variables with ... thus being arg3). the brackets make sure only the first vararg value (=folder name in the case of ADDON_LOADED) is considered (though I'm fairly sure they're redundant as == would only consider the first anyways).
print(("%s loaded!"):format(folderName))
f:UnregisterEvent("ADDON_LOADED") -- ADDON_LOADED will only fire once per addon - after ours has passed, we can save ourselves some memory and cpu cycles by not having to evaluate the conditionals again.
end
end
end)
f:RegisterEvent("ADDON_LOADED") -- you always need to tell your frame what events to listen for. OnEvent will only be invoked for registered events.

As for the difference between Lua and XML for frame creation, personally, I'll go ahead and say XML is useless for one objective and one subjective reason.
Objective: Any function you want to use for your XML definition has to be globally accessable, period. At best, this means cluttering your global addon variable. At worst, it means a large amount of namespace pollution.
Subjective: It's a pain to debug - Lua files will always give an appropriate error message. XML may just silently fail at places or attempt to "gracefully" resolve issues by adding/ignoring tags.

Speaking of globals, your example code has the function as a global variable. Seeing as you come from PHP, this is a slight change of the usual, but nothing too spectacular. Functions in Lua are a first-level variable. You can assign, reassign, overwrite, upvalue etc. them as you wish. Every function definition consists of two steps: One, loading the function body/metadata into memory. Two, assigning (in case of [local ]function NAME(args) definition) or returning (in case of function(args) definition as seen in my example code above) a memory pointer to that function. This memory pointer is the function "variable" you see in code, and should thus always be upvalued whenever possible (or simply passed directly to SetScript as seen in my example if the pointer is not needed again). What your own example code does is:
- Load the function body/metadata.
- Create a global variable called RestedAlert_OnLoad with a memory pointer to the function.
- Read the global variable RestedAlert_OnLoad and pass its content to the SetScript function (which sets the frame's script to the pointer, which is then used to find and call the function when appropriate).

The same basic behavior (only assigning pointers to the variable) is also seen in tables. For example, contrast a basic numeric variable:

This is because any changes made to the table will be reflected anywhere that table is referenced. If you want to break this reference "link", you have to recursively copy every table key-value pair to a new table(s).

Anyways, I guess this is enough food for thought for now since I don't want to confuse you with deeper levels of Lua mechanics, at least for the time being (and my apologies if I've already done so - please feel free to ask questions to resolve anything unclear) - however, you will somewhat frequently run into issues that might seem illogical unless you realize which variable types (functions, tables, userdata) are passed around only as references, so I believe I should put this out there.

PS: The markup used by wowprogramming tends to swallow line breaks. My apologies for any walls of text that might occur.

Posted by zookia on Wed, 01 Feb 2012 01:12:51

Ah - very helpful, thanks! The code is now all local and non-polluting!:D I've fiddled with the numbers a bit to get it to fade at a speed I'm happy with, so it's all good! Plus my WoWProgramming book has turned up - it's a lot bigger than I thought! I often wondered how people could do such amazing things with addons (a classic example is something like DBM - that is some epic programming there). I'm also glad you cleared up the LUA/XML thing - I can live without my addon telling me it's loaded now I know why it's not telling me! The first attempt I made at my addon was just to get it to tell me it's loaded before even starting to put any actual code in, so I was rather puzzled why it didn't work!;) Now I have to go mess around with CurseForge and figure out how their versioning system works!:D Thanks again for all the help :)