Jump to content

Lua variables and function scope: Writing efficient Lua (global vs local)


Fenris_Wolf

Recommended Posts

This tutorial assumes you know the very basics of Lua:
what a variable is, and how to assign one.
what a function is, and how to call one and pass variables to it.
what a table is, and how to declare one.
It is dedicated to modders new to Lua, and is a general tutorial on Lua... not specific to modding PZ.

Variables and function scope:
This seems to be one of the more overlooked aspect I've noticed in mods, and is EXTREMELY important for efficient and fast code.
There are 2 scopes: global and local. 
* Global variables can be accessed by any part of the code, from any function, in any file, and are declared like:
* Local variables can only be accessed from the file (or function) that they are declared in.

The more variables declared, the longer its going to take to find any specific one.  Think of it like a phonebook... if you need to look up a number and that book has 1000 different numbers in it, it will take longer to find the one you want.  Ideally you only want the book to contain numbers your actually planning on calling.
One of the reasons Lua is used as a language for a lot of games is due to its speed.  One of the reasons its fast is because by default it doesn't pollute the global space with unneeded stuff.

The most important thing to remember: ALL VARIABLES ARE GLOBAL BY DEFAULT UNLESS DECLARED LOCAL.  This is often overlooked.

 

If we wanted to make sure a variable can not be accessed outside the file or function (thus not polluting the global namespace), we declare it like this:

local modName = "My new mod"


If we wanted a to declare this in a function printModInfo instead:

function printModInfo()
    local modName = "My new mod"
    print(modName)
end


Had we left out the 'local' key word there, modName reverts to global, and can now be accessed anywhere.  Not only is it bad for performance, it also runs the risk of accidentally over writing another variable...if someone else's mod used the same variable name in the global namespace.

Lets assume we actually want modName to be global, as well as modInfo, modAuthor and modVersion, so we can access them from other files in the mod.  We could declare them all global but why pollute the global space more then we have to?  We're better off sticking them in a global table:
 

MyModData = {
    modName = "My new mod",
    modInfo = "My description",
    modAuthor = "Me!",
    modVersion = 1.0
}

print(MyModData.modName)

It may not seem like we've saved much, only adding 1 item into the global space instead of 4, but it adds up.   Tables are important for organizing and separating data.

This same concept applies to functions, they can exist in the global or local space, and are assumed global unless declared with the local keyword. They can also be declared in 2 styles:

-- global
function printModInfo() ... end
printModInfo = function() ... end

-- local
local function printModInfo() ... end
local printModInfo = function() ... end

You may have noticed the second style there actually creates a function but assigns it to a variable name, this is perfectly valid and can be a handy trick:

function printModInfoStyle1() ... end
function printModInfoStyle2() ... end
printModInfo = printModInfoStyle1

we can reassign printModInfo to Style1 or Style2, depending on some condition and call it later simply as printModInfo()

Lets say you want to add a Event that triggers on keypress:

local function someFunction(key) ... end
Events.OnKeyPressed.Add(someFunction)

Remember to declare that function local, its not getting called from anywhere else. Actually, since its only getting passed to Events.OnKeyPressed.Add, and not even getting called locally, you can shortcut and skip polluting the local space as well:

Events.OnKeyPressed.Add(function(key)
    ...
end)


Some coders may disagree with that last example, but remember the trick to writing efficient Lua is to keep the scope as small as possible, since we'll never need that function again, there is no point assigning it a name.

If you create a number of functions, and the only code that calls those functions exists within the same file, be sure to make them local.
The same thing I said above about sticking global variables into a table to avoid namespace pollution also applies to functions.
If all those functions you declared you need to access elsewhere, put them in a table:

MyModFunc = {
    functionNo1 = function() ... end,
    functionNo2 = function() ... end,
    functionNo3 = function() ... end,
}
MyModFunc.functionNo4 = function() ... end -- this works too


This isn't related to global/local scope, but is often the most confusing (and annoying) part for new Lua coders so worth mentioning.  Functions inside tables can actually be declared and accessed 2 ways:

MyModFunc = { }
function MyModFunc.functionNo1() ... end
function MyModFunc:functionNo2() ... end


and calling them as:

MyModFunc.functionNo1()
MyModFunc:functionNo2()


Notice the use of : in functionNo2 instead of the .
The first style is a normal function, and behaves exactly like you'd expect.  The second style is technically called a 'method', it will pass a variable named 'self' to the function. In this case 'self' is the table MyModFunc. The functionNo2 method is basically doing this:

function MyModFunc.functionNo2(self) ... end
MyModFunc.functionNo2(MyModFunc)


Now that all that is sorted out, here's a additional scoping trick...
functions can be created inside other functions:


local functionNo1()
    local myVariable = "some text"
    local functionNo2 = function()
        print(myVariable)
    end
    return functionNo2
end

local result = functionNo1()
result() -- prints "some text"

this calls the first function, which returns the second, the second (now in the variable 'result') is called, and prints "some text"
While the above bit of code itself not overly useful, it demonstrates a few points: functions inside functions, and variables are inherited from the above scope (even locals) and can be used after their scope has ended.

 

 

Edited by Fenris_Wolf
editted for error fixes due to not enough coffee before original post.
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...