Lua meta tables are a versatile tool that allows you to modify the basic behavior of tables and types inside of lua. In this article we will be talking about how you can directly apply them to your game engine however if you want to read a broad overview then feel free to visit this page. To save you some time though i'll start with a summary.
Meta methods are what you call specific built in functions which are called by lua when any operation is performed on a data type. These operations can range from addition and subtraction to indexing into a table or comparing two objects. By overriding the default meta methods we can expand the functionality of our lua types while keeping things simple and easy to use. Here is a small list of very useful meta methods:
__index(self, key) - Used when indexing into a table to retrieve a value* ex: tab["key"]*
***__*newindex(self, key, value) **- Used when indexing into a table to assign to a value.
__call(...) - Used when a function call operator is used on the type ex: tab()
__add(left, right) - Used when adding together two objects using "+"
**__eq(left, right) - **Used when checking if two variables are equal ex: a == b
*__*set(left, right) - Used when setting one variable to another.
__**tostring() - **Used when converting a variable to string form.
It's easy to see the application that some of these meta methods have for basic math types like 2D, 3D, and 4D vectors. By overloading the addition, subtraction, multiplication, and division operators we can make working with these types a simple task. Something that is a bit different however from what you may be used to in C++ is that there is no overloading meta methods for when dealing with specific types (for example adding a 2D vector to a 3D vector instead of adding two 3D vectors together.)
Due to this you must write additional logic inside of these functions which will determine how exactly these operations are performed. A useful tool in this is by using the type function which will return the basic type of a variable. Pay close attention to the "basic type" in that last sentence, lua is not capable of determining user-defined types you have created by using meta tables and will just return "table" (or "number" if it's a number.) What we will discuss doing later on and I recommend doing it as well is having a field inside of your meta tables with the name of the type for you to double check against.
If you've been reading my earlier blog posts then you've heard me talking about reflecting C++ components to lua so that you can call member functions (and adjusting their state through getter/setter functions) but have you every tried indexing into a reflected component?Take a look at this code snippet:
missing picture
As you can see writing or reading from an index inside of a userdata variable isn't possible due to the nature of a userdata variable (Userdata is essentially just arbitrary memory defined by you in C++, in this case it would be meta data relating to the C++ transform component.) Wouldn't it be nice to be able to write to the indexes of C++ components though? This means we could cache information needed by lua on them and do things like batch draw calls. Lucky for you this is actually a fairly simple task!
If you've been reading my other posts on lua then you might have seen me recommend storing a reference all of your meta tables inside of a global table named **_R **so that you can easily access them later on.
The more you know:
Unlike most variables in lua, tables are handled by reference instead of by value. What this means is that when you assign a variable to equal an existing table you aren't actually creating a new copy of the original table, just a reference to it. Think of shallow copying vs deep copying in C++. To make a real copy of a table you will have to make a helper function to do this for you. (Reference Manual)
We want to do this recursively in lua so we are going to make a function which takes a meta table and then modifies it to do what we need. Something that is very cool inside of lua is the ability to declare functions inside of functions which we are going to be taking advantage of here. Essentially what we will be doing is creating an external storage table for the each userdata variable and then reference that table whenever we index into the userdata variable. Here's a diagram and then some sample code.
missing picture
So essentially what's going on in our __index function is we verify that we have an external table dedicated for storing additional values, we do this by indexing into the VARDATA table with "self" which would be the user data variable. For simplicity we store VARDATA inside of _R but you could store it anywhere really.
After we have a table to store any additional values we check to see if the key we are searching for is inside of that table, if it is then we return that value and go on our merry way. If it doesn't happen to be there then we return the value for that key from our meta table. We do this because any C++ functions that we have reflected will be stored inside of our meta table and we still want to be able to call them.
We've left an important piece out though which is the function for writing to an index, the __newindex function which is very straight forward since all we have to do is ensure the external table exists, and then write to it.
missing picture
For this section I suggest reading my post on setting up your lua interface beforehand so you can better understand when exactly this step should be performed.
At this point we have a function for setting up the meta tables for our C++ types however we now need to actually run that function on each type. We do this by declaring a simple function which iterates through each meta table inside of _R and running the *SetupMetatable *function on each table. We aren't done yet though, we also need to declare the *VARDATA *table inside of **_R **otherwise our __index and __newindex functions will cause errors!
If you end up using the **_R **table of your lua types as well (which is something I also do) then make sure to perform this step before you declare any of your lua types otherwise strange things will happen. The way I accomplish this is by initializing my lua environment, run my first lua script which defines these functions, create meta tables for each C++ type, setup all of their meta tables, then load the rest of my lua types. An alternative solution however is to also reference your C++ type meta tables in another table somewhere and just iterate through that.
You may have noticed in my earlier code snippet that the function call I had for getting the transform component from an object was **:GetComponent("Transform") **which isn't too bad but isn't it a bit annoying to have to type out "GetComponent(...)" every single time we want to get a component? The whole point of using lua is to make things easier and quicker to prototype so why stop here? Using our friend, the __index function, we can get rid of the annoying GetComponent function and instead just have people type the name of the component they want like this:
missing picture
This makes accessing components look much cleaner and user friendly but also has some (minor) drawbacks. By doing this we have restricted our ability to name variables the same as our components on our game object type inside of lua. This shouldn't be an issue however since you should never be storing information on the game object itself, that's what components are for.
So how do we do this by using the __index function? Well first off we need to keep a list of all C++ components that lua can get which is simply just a table with the name of the components as the key (and the component ID as the value if you use those.) In my implementation I will call this table the NativeComponentDB (DB implies Database except this isn't a database but whatever) and the values of the table will be the enumeration id of the component since I use enum's to get components in C++.
missing picture
It's recommended that you don't fill this table in by hand and instead generate it when you initialize lua.
This is another place where we are going to flex our lua muscles and easily add additional functionality to our old __index function. Essentially what we will be doing is copying the old __index function to a new location, then overwrite the function on the meta table to handle our component look up code. What's cool about this is since we stored the old __index function under a new name we can still call it if we ended up not wanting a component, here's what it would look like in code:
missing picture
Pay close attention to the line: "local oldindex = META.__index"
If you had forgotten to make oldindex a local variable or if it were a table entry then this wouldn't work.
Everybody knows that strings are slower than integers when it comes to comparison and transferring so you should use integers whenever possible. Before we implemented the GetComponent shortcut our GetComponent function took strings, however now that it is never directly called it uses the integer id of the component it is retrieving. I recommend you do the same with your implementation or at the very least have a second function which does.
These last two things are in my opinion what I think are some of the most useful usages of meta tables. Hopefully these examples have inspired you and shown you neat ways you can take advantage of the awesome features meta methods and meta tables offer.