Monday, August 30, 2010

[luabind] Global table of classes from C++

These days I am playing with Lua and LuaBind (yet another game project, more on that hopefuly soon). LuaBind is really quite amazing and is a perfect complement to Lua for C++ programmers.

There is one thing, however, that gave me a lot of trouble. I wanted to define from C++ a global table of class instances in the Lua script. With LuaBind you can easily define globals using luabind::globals. For instance, here is how to define, from C++, a global named 'test' and having 1234 for value:

luabind::globals(luaState)[ "test" ] = 1234;

In LuaBind (v0.9), a table is created using luabind::newtable, which returns a generic object. For instance:


luabind::object table = luabind::newtable( luaState );
table[ "Instance1" ] = new MyClass( "A" );
table[ "Instance2" ] = new MyClass( "B" );
table[ "Instance3" ] = new MyClass( "C" );


where 'MyClass' is a C++ class previously registered into LuaBind.

Defining the table as a global is as easy as:


luabind::globals(luaState)[ "AllInstances" ] = table;


This was working fine, but the code would keep randomly crashing after the lua script execution. This was really a strange behavior. Fortunately, this post saved my day.

The problem is not with the code above, but with the context around it. Here is the entire function running my script:


void threadLua()
{
...
lua_State* luaState = lua_open();
luabind::open(luaState);
...
luabind::object table = luabind::newtable(luaState);
table[ "Instance1" ] = new MyClass( 1 );
table[ "Instance2" ] = new MyClass( 2 );
table[ "Instance3" ] = new MyClass( 2 );
luabind::globals(luaState)["TableOfInstances"] = table;
...
int ret = luaL_dostring(luaState, program);
...
lua_close(luaState);
} /// things would crash here


So what's wrong? Well, lua_close destroys the lua context. Unfortunately, my seemingly innocent object 'table' is still alive when this happens. And as it turns out, the destructor of a luabind::object does expect the lua context to still be valid. Hence the crash. The fix is desperately simple - it is enough to limit the scope of the variable:


void threadLua()
{
...
lua_State* luaState = lua_open();
luabind::open(luaState);
...
{
luabind::object table = luabind::newtable(luaState);
table[ "Instance1" ] = new MyClass( 1 );
table[ "Instance2" ] = new MyClass( 2 );
table[ "Instance3" ] = new MyClass( 2 );
luabind::globals(luaState)["TableOfInstances"] = table;
} /// keep this: 'table' must not live after this point
...
int ret = luaL_dostring(luaState, program);
...
lua_close(luaState);
} /// no crash!


This one was painful to find out so I thought it was worth a post!

No comments: