Modern C++ Lua Interface

As we continue to grow the Lua scripting API for Vortex, it’s become clear that we need a better, less verbose way to expose “classes” and their functions to the Lua VM.

In Vortex we decided to build our custom Lua binding. This initial work was done about two years ago, and it was built by directly invoking the Lua C API. This means that exposing something like the vtx::Transform class, along with a few methods, would look something like this:

void Bindings::registerBindings( lua_State* pLua )
	luaL_newmetatable( pLua, "vtx.Transform" );
	lua_pushvalue( pLua, -1 );
	lua_setfield( pLua, -2, "__index" );

	lua_pushcfunction( pLua, nativeTransformGetPosition );
	lua_setfield( pLua, -2, "get_position" );

	lua_pushcfunction( pLua, nativeTransformSetPosition );
	lua_setfield( pLua, -2, "set_position" );

	lua_pushcfunction( pLua, nativeTransformGetScale );
	lua_setfield( pLua, -2, "get_scale" );

Now, the problem with this approach is that it’s very verbose and also pretty error prone. Wouldn’t it be nice if we could have a Modern C++ way of exposing classes and their methods to Lua scripts that didn’t require all this typing?

A Better Interface

What we want to do is easily define a class with a few methods and their callbacks into native code.

Leveraging C++11’s initializer lists, it turns out it’s pretty simple to do this. Have a look at the following simplified way of defining functions. It looks more readable and way more in line with Modern C++ in my opinion:

void Bindings::registerBindings( lua_State* pLua )
	vtx::BindingUtils::pushClass( pLua, "vtx.Transform", {
		{ "get_position", nativeTransformGetPosition },
		{ "set_position", nativeTransformSetPosition },
		{ "get_scale", nativeTransformGetScale }
	} );

The pushClass method receives the name under which the class will be exposed to Lua scripts and a table of method names that map to the native implementation inside the engine. It also receives the Lua VM handle, of course :)

How can we implement this function?

We want to handle the function list as a vector. A vector of a composite type that maps a string to an implementation. To do this, we start by defining a FunctionDefinition struct. This will “map” function names (provided as strings) to implementations (provided as typed function pointers).

namespace vtx
	struct FunctionDefinition
		std::string name;
		int ( *implementationPtr )( lua_State* l );

Now that we have a way of defining functions, we provide an implementation of the pushClass() function that can take a vector of this struct:

void vtx::BindingUtils::pushClass( lua_State* L, const char* className, std::vector< vtx::FunctionDefinition > memberFunctions )
	assert( L );

	luaL_newmetatable( L, className );
	lua_pushvalue( L, -1 );
	lua_setfield( L, -1, "__index" );

	for ( auto&& functionDef : memberFunctions )
		assert( functionDef.implementationPtr );
		lua_pushcfunction( L, functionDef.implementationPtr );
		lua_setfield( L, -2, );

Notice how the case of receiving an empty vector is gracefully handled. If this happens, an empty table will be defined. This could be useful to have a type with no members to be used as a handle to an engine object.

So is this the best way to interface Lua and modern C++?

This method does work and I have already been using it to vastly simplify the binding code that exposes engine objects to scripts. This has made the client code much more readable and easier to extend.

This is, however, not necessarily the best way of doing this. Other authors have done an amazing job at solving this problem using excellent C++ template metaprogramming concepts. For our case, this simple implementation is enough to manage the complexity of the problems we have today.

Now it’s time to go build something with this! In my next post I’m going to be using this logic to build and expose a camera system to Lua scripts for them to be able to control and animate the point of view.

Stay tuned for more!