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.
Lua offers a myriad of ways we can develop a scripting interface for our native code.
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.
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:
vtx.instantiate( string ) -- Create a new entity corresponding to the identifier passed. -- Return the new entity created. vtx.find_first_entity_by_name( string ) -- Find the first entity in the scene that matches the name specified. -- Return the entity or nil if no matches are found.
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:
- From the native side, we create a metatable. Call it vtx.Entity.
- 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.
- Whenever a script requests an Entity (instantiate, find), the function servicing the call will:
- Create a new table.
- Set the table’s metatable to vtx.Entity.
- Store a pointer to the C++ Entity in it.
- When a script invokes the Entity’s set_name function, it will trigger a lookup into the metatable’s functions.
- The function we registered under set_name will be called. We are now back in C++.
- The native function will pop from the stack a string (the new name) and the “Entity” on which the method was called.
- 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.
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!