Tuesday, June 10, 2014

Getting Started with Lua

Hello again,

Warning: there is an update of the blog itself on the next paragraphs. You may skip to the Lua section below if you're not interested.

This blog has been inactive for quite some time as I took some time away from my game development hobby. Good news is: something happened that made me regret this immensely, so I am back. Bad news is: I must focus on my main personal project amap, period in which I'll leave our C++ game unattended given the giant amount of work it would be (as I chose to code the engine from SDL up).

I don't plan on abandoning this though, as I owe this same thing that brought me back to game development so quickly to this very blog. Creating a quality C++ framework that is flexible yet easy to use is something that is sure to take months of work. But I can't spare all this time right now nor want to take the easy route and create lower quality stuff as I wouldn't learn anything from it, in contrast to creating a quality one with which I, myself, would learn a lot by doing. So I'll change this blog's direction for the time being and retake its old course some months from now.

For now, I'll change my focus into getting results faster. I'll be using Löve2D for at least the next weeks, trying to get a hold of how it all works. It is a Lua framework, but my choice has nothing to do with the language, but with the framework itself. It has one of the easiest interfaces I've seen and, if necessary, I can always step into C and do what's necessary, although I don't believe it would really be a necessity.

Lua


It is a scripting language very popular on the game development world, possibly the most popular. The language itself is too big to fit in a blog post, and I am not proficient enough to give a good overview, but I'll still give you guys some short snippets to show the basics of the language.

HelloWorld.lua

print("Hello World!") --Self Explanatory
This print command would output the line to the console, as simple as that. To run this lua script, just type issue the console command [ lua path/HelloWorld.lua ] and it should output the message.

Lua doesn't support the most common syntaxes for most things, what can be a pain sometimes, but you'll soon get the hang of how it works. In the next example, I'll create a Lua array (table with numeric indexes) with two strings in it, and then print them.

Hell-o World.lua

myTable = { --Creates a Table
    "Hell",     --  First Entry
    "o World!"  --  Second Entry
}

print(myTable[1]..myTable[2])-- these .. concatenates strings
myTable here is an 'array' with two entries, that we can access by their numeric values (since it is not a map). The .. operator concatenates strings, really useful.

Did you notice that Lua's array indexes actually start with 1? Some people consider this the best thing about Lua, personally I don't like it as I already had to write workarounds for this when interfacing with C. Doesn't make much difference in the end though.

Metamorphosis.lua

print (myVar)   --myVar doesn't exist yet
                --Output:   nil

myVar = 1       --Creates myVar of number type (double)
print (myVar)   --Output:   1

myVar = {1,3,5} --Changes myVar into an array
print (myVar)   --Output:   table:0x1ed8de0

myVar = myVar[1]..myVar[2]..myVar[3] --Treats numbers as strings
print (myVar)   --Output 135
The output of this script is as follows:
[dejaime@manjaro BuildAndGun]$ lua Metamorphosis.lua
nil
1
table: 0x1e65d90
135

If you try to use a variable that doesn't exist, it will be treated as nil. nil in Lua is a special type of data that means "no data". It is treated as false when used in conditionals.

Lua uses double for all its numeric operations. If you want to change this behavior, you can do so by editing the Lua configuration file. More details on this can be found here.

Notice how a table is printed. First, Lua warns you that it is printing a table, and then it spits its memory address. This happens because it doesn't know exactly how to print a table, but, in case you want Lua to print your table in a given manner, you can use what's called a metatable. You can think of this as overloading some behaviors, but it is not exactly what is happening. If you wish to know more, take a look here.

MetaTable.lua

myTable = {
    "This ",
    "Is ",
    "Awesome!"
}
print(myTable) -- Outputs as usual: "table: 0x1f34abc"

myMetaTable = {
    __tostring = function (table)
        local resultingString = "" -- an empty string
        for i = 1, #table do -- # means total entries
            resultingString = resultingString..(tostring(table[i]))
        end
        return resultingString
    end
}

setmetatable(myTable, myMetaTable) -- sets the behavior for myTable

print(myTable) --Output: "This Is Awesome!"
As you can see, metatables must be explicitly set. In here, we used __tostring, but one can also overwrite several behaviors such as addition (__add), equality (__eq), userdata garbage collection (__gc, not for tables).

That # operator (#table) returns the total number of entries in a given array. 

Other notable thing about this snippet is the word local, right before resultingString. In Lua, everything is defaulted as global, but sometimes you'll want to set variables as local as they can be >3x faster to operate with (as in my benchmarks).

GlobalLocal.lua

GlobalPI = 3.14159

local FileLocalPI = 3.14159

function benchmark ()
    local FunctionLocalPI = 3.14159

    local total_operations = 10^8
    local trashVar

    local initial_time = os.time()

    for i=1,total_operations do
        GlobalPI = i*GlobalPI
        GlobalPI = i^GlobalPI
        GlobalPI = i/GlobalPI
        GlobalPI = i+GlobalPI
        GlobalPI = i-GlobalPI
    end
    
    print("Global: "..os.time() - initial_time.."s")
    initial_time = os.time()

    for i=1,total_operations do
        FileLocalPI = i*FileLocalPI
        FileLocalPI = i^FileLocalPI
        FileLocalPI = i/FileLocalPI
        FileLocalPI = i+FileLocalPI
        FileLocalPI = i-FileLocalPI
    end

    print("File level local: "..os.time() - initial_time.."s")
    initial_time = os.time()

    for i=1,total_operations do
        FunctionLocalPI = i*FunctionLocalPI
        FunctionLocalPI = i^FunctionLocalPI
        FunctionLocalPI = i/FunctionLocalPI
        FunctionLocalPI = i+FunctionLocalPI
        FunctionLocalPI = i-FunctionLocalPI
    end


    print("Function level local: "..os.time() - initial_time.."s")
end

benchmark()
The output I had for this script on my personal machine was the following:
[dejaime@manjaro BuildAndGun]$ lua GlobalLocal.lua
Global: 27s
File level local: 15s
Function level local: 6s
[dejaime@manjaro BuildAndGun]$ lua GlobalLocal.lua
Global: 29s
File level local: 15s
Function level local: 7s
As you can see, global variables are heavier than file-level local variables which are, in turn, heavier than function-level local variables. This is extremely situational though, but sometimes it is worth it copying a global into a local variable if you're going to perform too many operations with it. At least make the test wherever you think globals are holding you back and see if there is any performance gain. I guess this is enough to illustrate the importance of the local keyword in Lua. Keep this in mind when designing performance sensitive code.

Functions.lua

function GlobalPrintAll (tbl)
    for key,value in pairs(tbl) do
        print(key, value)
    end
end


myTable = {
    marco = "polo",
    humpty = "dumpty",
    hot = "dog",
    PrintAll = GlobalPrintAll
}

print("\nExplicit Argument:")
myTable.PrintAll(myTable)

print("\nImplicit Argument:")
myTable:PrintAll()
Output:
[dejaime@manjaro BuildAndGun]$ lua Functions.lua

Explicit Argument:
PrintAll    function: 0x746b20
hot dog
marco   polo
humpty  dumpty

Implicit Argument:
PrintAll    function: 0x746b20
hot dog
marco   polo
humpty  dumpty



Here, you can see that Lua can hold functions as variables. That is why so many Lua frameworks rely on callbacks, it is simply convenient to use callbacks in Lua.

Other important thing, especially when you guys go around hunting for examples in Lua, is the difference between . and : .
When using period, the function is called as one would expect. But the colon is actually OO eye candy, it passes the table itself to the function when calling it. In other words, table:function(p1, p2) is the same as table.function(table, p1, p2) .

ColonPeriod.lua

function GlobalFunction(printme, metoo)
    print(printme, metoo)
end

myTable = {}
myTable["l"] = "Yolo"
myTable["d"] = "Yodo"
myTable["Function"] = GlobalFunction

GlobalFunction(myTable["l"], myTable["d"])
myTable.Function(myTable["l"], myTable["d"])
myTable:Function(myTable["l"], myTable["d"])

Output:
[dejaime@manjaro Desktop]$ lua HelloWorld.lua
Yolo    Yodo
Yolo    Yodo
table: 0x20d9220    Yolo
Did you notice that the last call had its parameters displaced due to the myTable variable being passed along in the first slot? You need to be careful with this kind of thing, since in Lua there is no type checking, finding a problem where you used a colon instead of a period can be quite frustrating. The key is to be consistent and try to use one of the two whenever possible. It is up to personal preference though.


Lua is a really flexible language, and I certainly didn't do it justice with these few snippets. But I hope this was a good introduction nonetheless, and maybe sparked some interest. I'll be using it in the next posts while using Löve2D.

That'll be it for today,
Over and Out.