m
Move page script moved page UI best practices to WoW:UI best practices without leaving a redirect
m (Move page script moved page UI best practices to WoW:UI best practices without leaving a redirect) |
|||
| (13 intermediate revisions by 12 users not shown) | |||
| Line 1: | Line 1: | ||
{{ | {{uitech}} | ||
With the low barrier to entry, writing mods has become very popular. While this has greatly benefited the WoW community, it does have a dark side. There are idiosyncrasies in both the [[Lua|Lua language]] and the [[World of Warcraft API]] that many authors overlook. This can lead to poorly written programs as measured by performance, memory usage, interference with other addons and the default UI, etc. The techniques gathering on this page will help help addon authors, experienced and otherwise, make the most out of WoW's environment safely and efficiently. | With the low barrier to entry, writing mods has become very popular. While this has greatly benefited the WoW community, it does have a dark side. There are idiosyncrasies in both the [[Lua|Lua language]] and the [[World of Warcraft API]] that many authors overlook. This can lead to poorly written programs as measured by performance, memory usage, interference with other addons and the default UI, etc. The techniques gathering on this page will help help addon authors, experienced and otherwise, make the most out of WoW's environment safely and efficiently. | ||
= General Scripting = | == General Scripting == | ||
These tips relate to Lua scripting in general. They are offered to help you write safer and more efficient code, and are applicable to non-WoW scripts as well. | These tips relate to Lua scripting in general. They are offered to help you write safer and more efficient code, and are applicable to non-WoW scripts as well. | ||
== Use local variables == | === Use local variables === | ||
One stumbling block for Lua programmers coming from other languages is the fact that all variables are global unless specified otherwise. Many times, the use of global variables is necessary: saved variables are created via the global environment; all API functions and UI frames are in the global namespace. However, globals come at a cost of both performance, and naming conflicts. | One stumbling block for Lua programmers coming from other languages is the fact that all variables are global unless specified otherwise. Many times, the use of global variables is necessary: saved variables are created via the global environment; all API functions and UI frames are in the global namespace. However, globals come at a cost of both performance, and naming conflicts. | ||
Globals | Globals are inefficient to access compared to locals. Whenever a global is accessed, Lua has to call the internal equivalent of getfenv() to figure out where to retrieve the global from. Local variables, on the other hand, are stored on a stack. In code where performance is important (for instance an [[UIHANDLER OnUpdate|OnUpdate handler]]), it's often beneficial to create a local alias to a global function: | ||
local Foo = Foo | local Foo = Foo | ||
| Line 23: | Line 23: | ||
These problems are easily solved by declaring functions and variables local. There are other approaches as well, but this is by far the simplest. | These problems are easily solved by declaring functions and variables local. There are other approaches as well, but this is by far the simplest. | ||
== Hook functions safely == | === Hook functions safely === | ||
See also: [[HOWTO: | See also: [[HOWTO: Hook functions in a safer way]] | ||
You should do your best to write your addon in such a way that you don't need to hook functions. Even when it is necessary, most of the time [[ | You should do your best to write your addon in such a way that you don't need to hook functions. Even when it is necessary, most of the time [[API hooksecurefunc|hooksecurefunc]] is sufficient. However, there are occasional cases where "traditional" hooking must be used. When you do, remember to pass all parameters and return all results. Example: | ||
local OrigFunc = Func | local OrigFunc = Func | ||
| Line 49: | Line 49: | ||
return unpack(ret) | return unpack(ret) | ||
== Make efficient use of conditionals == | === Make efficient use of conditionals === | ||
If you have a series of if conditionals, test the most efficient ones first. For example: | If you have a series of if conditionals, test the most efficient ones first. For example: | ||
| Line 71: | Line 71: | ||
end | end | ||
=== | ==== Short-Circuiting ==== | ||
Lua uses | Lua uses short-circuiting of conditionals. This means it only evaluates enough of the condition from left to right to know for certain whether it's true or false. In the case of "or", the whole condition is known to be true as soon as one operand is true. For "and", the whole condition is known to be false as soon as one operand is false. You can take advantage of this to add a bit of efficiency: | ||
if Fast() or Slow() then | if Fast() or Slow() then | ||
| Line 83: | Line 83: | ||
end | end | ||
= API & XML = | ==== Order of Operations ==== | ||
Lua, like many other programming languages, executes expressions from left to right starting from the innermost parenthesis to the outermost. This allows for un-nesting of IF blocks. | |||
-- This: | |||
if a and b then | |||
if c or d then | |||
DoStuff() | |||
end | |||
end | |||
-- Can be written as: | |||
-- As described in the previous section, if "a" or "b" are false, then DoStuff() will never execute | |||
-- If "a" and "b" are true and "c" or "d" are true, then DoStuff() will run. | |||
if a and b and (c or d) then -- same as "a and ((b and c) or (b and d))" (yes, the distributive property works here too) | |||
DoStuff() | |||
end | |||
Note: There are several programming languages that do not read left to right (eg. Joy, Factor, J and K, etc.). Others - like Haskell - can be used both ways. There are even languages that are multi-dimensional. While many of these are mainly academic, they are not esoteric languages made to be weird, but rather based on recent theories and ideas in computer science. | |||
==== Lazy Coding ==== | |||
You can utilize the Short-Circuiting functionality to make sure a variable has a value before comparing it to a literal: | |||
if foo[bar] == 5 then -- might throw "attempting to index field ? a nil value" | |||
DoStuff() | |||
end | |||
if foo and foo[bar] == 5 then -- will not throw an error | |||
DoStuff() | |||
end | |||
You can also "cheat" if all you want to do is make sure a variable has a value other than nil or false: | |||
-- This: | |||
if foo then | |||
print(foo) | |||
elseif bar then | |||
print(bar) | |||
else | |||
print("nothing to print") | |||
end | |||
-- Can be written as: | |||
print(foo or bar or "nothing to print") | |||
=== Minimize use of throw-away tables === | |||
Tables in Lua, being powerful instrument, can cause your addon to pollute memory with unnecessary garbage if you're not careful, as tables are one of value types that are not reused automatically. It is better not to use repeatedly generated throw-away tables unless they provide far easier to read solution, faster code or if your task is impossible to handle without them at all. In some cases you can minimize garbage generation when using those as explained in [[HOWTO: Use Tables Without Generating Extra Garbage]]. | |||
== API & XML == | |||
This section applies specifically to the World of Warcraft UI environment. | This section applies specifically to the World of Warcraft UI environment. | ||
== Use local event handler parameters == | === Use local event handler parameters === | ||
With the introduction of WoW 2.0, [[ | With the introduction of WoW 2.0, [[Widget Handlers|widget event handlers]] now provide local parameters. As mentioned in [[#Use_local_variables|a previous section]], accessing local values is more efficient than accessing globals. Here are a few examples of the old way and how they should be implemented now: | ||
=== Code directly in XML === | ==== Code directly in XML ==== | ||
Old | Old | ||
<OnEvent> | <OnEvent> | ||
| Line 106: | Line 152: | ||
self:Show() | self:Show() | ||
elseif event == "SOME_OTHER_EVENT" then | elseif event == "SOME_OTHER_EVENT" then | ||
DEFAULT_CHAT_FRAME:AddMessage(format("%s: %s" | DEFAULT_CHAT_FRAME:AddMessage(format("%s: %s", ...)) | ||
end | end | ||
</OnEvent> | </OnEvent> | ||
Note: Lua code must be inserted inside event handlers or it will never run. | |||
=== XML calling Lua function === | ==== XML calling Lua function ==== | ||
Old XML | Old XML | ||
<OnEvent> | <OnEvent> | ||
MyEventHandler() | MyEventHandler() | ||
</OnEvent> | </OnEvent> | ||
Old Lua | Old Lua | ||
function MyEventHandler() | function MyEventHandler() | ||
| Line 129: | Line 175: | ||
MyEventHandler(self, event, ...) | MyEventHandler(self, event, ...) | ||
</OnEvent> | </OnEvent> | ||
New Lua | New Lua | ||
function MyEventHandler(frame, event, firstArg, secondArg) | function MyEventHandler(frame, event, firstArg, secondArg) | ||
| Line 139: | Line 184: | ||
end | end | ||
=== Lua only === | Even Newer XML | ||
<OnEvent function="MyEventHandler" /> | |||
This has the advantage of resulting in a reference call, without an anonymous code block calling a global function. | |||
==== Lua only ==== | |||
Old | Old | ||
| Line 158: | Line 206: | ||
end | end | ||
end) | end) | ||
[[Category:Interface | [[Category:Interface customization]] | ||