Lua Metatable C++

For the last couple of weeks, a lot of work has been going into developing the scripting API that the engine is exposing to Lua. Embedding a scripting language into a large C++ codebase has been a very interesting experience and I’ve been able to experience first hand why Lua is regarded as such a strong scripting language.

Introspection of an Entity from a Lua script running in the Console.

Lua offers a myriad of ways we can develop a scripting interface for our native code.

What is Lua API Purpose: embed Lua into C application – create and expose functions and data to Lua Easy to use – automatic stack management – expressive OO syntax – natural expressions: calls, indexing, arithmetics and comparison – automatic function wrapping Lightweight, little overhead – no dynamic polymorphism – header-only mode available.

  1. Lua 元表(Metatable) 在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。 因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。 例如,使用元表我们可以定义Lua如何计算两个table的相加操作a+b。 当Lua试图对两个表进行相加时,先检.
  2. If the structures that you want to manipulate from Lua need to be allocated or created by C/C code, then it's best to just store a pointer to the structure inside the userdata. For example, when creating images with Thomas Boutell's GD Graphics Library 1, the function gdImageCreate only returns a pointer to an image object.

A naïve approach would be to expose every function of the engine in the global namespace and have scripts use these directly. Although this method would certainly work, we want to offer an object-oriented API to the engine and its different components, so a more elaborate solution is required.

I ultimately decided to build the interface from scratch, following the Lua concepts of tables and metatables. The reason being that building everything myself would allow me to clearly see the costs of the binding as objects are passed back and forth. This will help keep an eye on performance.

Initial Test of the Lua-C++ binding. In this example, we query and rename an Entity.

In order to keep the global namespace as clean as possible, the idea was to create a Lua Table procedurally from the C++ side where all functions and types would live. Conceptually, this table is our namespace, so I named it vtx, accordingly. It’s really the only global variable that the engine registers.

The next step was to start populating the vtx namespace. Two functions I know I wanted to expose right away were Instantiate and Find:

We now have functions. But how do we do objects? And how do we expose our vtx::Entity objects to Lua?

Let’s recap a bit. We know that entities are “engine objects”, in the sense that they live in C++ and their lifecycles are managed by the Vortex Engine. What we want is to provide a lightweight object that Lua can interact with, but when push comes to shove, the native side will be able to leverage the full C++ interface of the engine.

Lua offers the concept of a metatable that helps achieve this. Metatables are can be associated to any table to provide special semantics to them. One special semantic we are interested in is the __index property, which allows implementing the Prototype design pattern.

I won’t go into details of the the Prototype design pattern works, but suffice it to say that whenever a function is called on a table, and the table does not have an implementation for it, the prototype will be responsible to service it.

This is exactly what we want. What we can do then is wrap our vtx::Entity instances in Lua tables and provide a common metatable to all of them that we implement in the C++ side. Even better, because of this approach Lua will take care of passing the Entity Table we are operating on as the first parameter to every function call. We can use this as the “this” object for the method.

Putting it all together, let’s walk over how entities expose the vtx::Entity::setName() function to Lua:

  1. From the native side, we create a metatable. Call it vtx.Entity.
  2. We register in this metatable a C++ function that receives a table and a string and can set the name of a native Entity. We assign it to the “set_name” property of the metatable.
  3. Whenever a script requests an Entity (instantiate, find), the function servicing the call will:
    1. Create a new table.
    2. Set the table’s metatable to vtx.Entity.
    3. Store a pointer to the C++ Entity in it.
  4. When a script invokes the Entity’s set_name function, it will trigger a lookup into the metatable’s functions.
  5. The function we registered under set_name will be called. We are now back in C++.
  6. The native function will pop from the stack a string (the new name) and the “Entity” on which the method was called.
  7. We reinterpret_cast the Entity Table’s stored pointer as a vtx::Entity pointer and call our normal setName() function, passing down the string.

Et voila. That is everything. The second image above shows in the console log how all this looks to a Lua script. At no point must the script developer know that the logic flow is jumping between Lua and C++ as her program executes.

We can also see in the screenshot how the Editor’s entity list picks up the name change. This shows how we are actually altering the real engine objects and not some mock Lua clone. Download kitkat rom for android 4.2.2.

As I mentioned in the beginning of this post, developing a Lua binding for a large C++ codebase from scratch is a lot of fun. I will continue adding more functionality over the coming weeks and then we’re going to be ready to go back and revisit scene serialization.

Stay tuned for more!

This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.

Lua Metatable C++
Programming in Lua
Part IV. The C APIChapter 28. User-Defined Types in C

28.2 – Metatables

Our current implementation has a major security hole.Suppose the user writes something like array.set(io.stdin, 1, 0).The value in io.stdin is a userdatumwith a pointer to a stream (FILE*).Because it is a userdatum,array.set will gladly accept it as a valid argument;the probable result will be a memory corruption(with luck you can get an index-out-of-range error instead).Such behavior is unacceptable for any Lua library.No matter how you use a C library,it should not corrupt C data or produce a core dump from Lua.

Lua Metatable Index

To distinguish arrays from other userdata,we create a unique metatable for it.(Remember that userdata can also have metatables.)Then, every time we create an array,we mark it with this metatable;and every time we get an array,we check whether it has the right metatable.Because Lua code cannot change the metatable of a userdatum,it cannot fake our code.

We also need a place to store this new metatable,so that we can access it to create new arrays andto check whether a given userdatum is an array.As we saw earlier,there are two common options for storing the metatable:in the registry,or as an upvalue for the functions in the library.It is customary, in Lua,to register any new C type into the registry,using a type name as the index and the metatable as the value.As with any other registry index,we must choose a type name with care, to avoid clashes.We will call this new type 'LuaBook.array'.

As usual, the auxiliary library offers some functions to help us here.The new auxiliary functions we will use areThe luaL_newmetatable functioncreates a new table (to be used as a metatable),leaves the new table in the top of the stack,and associates the table and the given name in the registry.It does a dual association:It uses the name as a key to the tableand the table as a key to the name.(This dual association allows faster implementations forthe other two functions.)The luaL_getmetatable function retrievesthe metatable associated with tname from the registry.Finally, luaL_checkudata checks whether the object at thegiven stack position is a userdatum with a metatable that matchesthe given name.It returns NULL if the object does not have the correct metatable(or if it is not a userdata);otherwise, it returns the userdata address.

Now we can start our implementation.The first step it to change the function that opens the library.The new version must create a table to be used as the metatable for arrays:

Lua Metatable Tutorial

The next step is to change newarray so that it setsthis metatable in all arrays that it creates:The lua_setmetatable function pops a table from the stackand sets it as the metatable of the object at the given index.In our case, this object is the new userdatum.

Lua Metatable Len

Finally, setarray, getarray, and getsize have to checkwhether they got a valid array as their first argument.Because we want to raise an error in case of wrong arguments,we define the following auxiliary function:Using checkarray,the new definition for getsize is straightforward:

Lua Metatable Methods

Because setarray and getarray also share code to check theindex as their second argument,we factor out their common parts in the following function:After the definition of getelem,setarray and getarray are straightforward:Now, if you try something like array.get(io.stdin, 10),you will get a proper error message:

Lua Metatable __mode

Copyright © 2003–2004 Roberto Ierusalimschy. All rights reserved.