Friday, September 09, 2011

Lua and C++ Binding

I've been playing around with Lua lately. When lua code is executed it runs on a lua state (a c struct called lua_State). The lua state contains all the information lua needs - the variables, functions etc. All that is stored in the lua state. You can have several lua states if you want to nicely separate some functionality. Multithreaded programs will generally have a lua state per thread.

I'm using C++ and I have a C++ class the contains a lua state. So if you instantiate a number of these classes, they all have their own lua state.

class SomeClass
{
    lua_State* mState;
public:
    SomeClass();
    void SomeFunction();
} 

The thing that becomes fiddly with this approach is that you can't directly bind SomeFunction to the lua state. Lua only binds to free-floating functions or static functions. This makes sense if you think about, it needs a memory address to a function. You could get the memory address of the SomeClass::SomeFunction function - but you wouldn't be able to tell what object it was associated with, you also need the "this" address of the object instance.

One of the first places you might come across this - is the panic function. The panic function is the function the lua_State calls if your lua code breaks.

SomeClass:SomeClass()
{
    mLuaState = lua_open();
    lua_atpanic(mLuaState, panic);
}

This is in the .cpp file. The lua_atpanic function takes in a lua state and a function. The function is called when the lua code has some kind of problem - the code breaking due to some syntax usually.

The panic function is also defined in the .cpp file but just as a free floating function (this probably isn't the correct terminology but I can't remember what is!).

int panic(lua_State* state)
{
    // Report error!
    return 0;
}

Ok so easy enough - we can do the error reporting in the panic function - but how do we know which instance of SomeClass has had the error? Imagine we wanted to print out "Error with lua state in instance named [ConsoleSystem]", how would we know the name of the particular instance?

Well some how we need to associate the lua_State with the instance of the SomeClass class. Remember earlier we were talking about how lua_State is a collection of all the functions and variables of a Lua state? Well we can just add the instance of SomeClass into the state! Genius!

There are a number of places the variables can be added into a lua_State and the most obvious is the globals table.

lua_pushlightuserdata(mLuaState, (void *)this);
lua_setglobal(mLuaState, "__this");

(I haven't tested this code but you get the idea :))

This will do the job, the lua_State now has a global called __this which is a pointer to the SomeClass object that contains it. We can get the pointer back using lua_getglobal.

But I don't like the idea of adding it to the lua globals table because you then have this __this variable there hanging around in the global namespace. If you did

for k, v in pairs(_G) do
    print (k, v)
end

Then __this would pop up. I think it would be nicer if we could avoid polluting the global namespace but still get the same result.

Lua has a number of tables, not just the globals one - there is also a table called the registry table. Let's add it there.

SomeClass:SomeClass()
{
    mLuaState = lua_open();
    lua_atpanic(mLuaState, panic);
    lua_pushliteral(mLuaState, "this"); 
    lua_pushlightuserdata(mLuaState, (void *)this); 
    lua_settable(mLuaState, LUA_REGISTRYINDEX); /* registry["this"] = this */
}

Right so we've added a pointer to the SomeClass object into the mLuaState registry table, which is invisible from the lua side.

Now we can rewrite the panic function.

int panic(lua_State* state)
{
    // Report error!
    lua_pushliteral(state, "this"); 
    lua_gettable(state, LUA_REGISTRYINDEX);
    SomeClass* someClass = (Minigame*)lua_touserdata(state, -1);
    
    // Now we have access to the instance of the class so we could call something like
    // someClass->Panic(); or
    someClass->SomeFunction();


    return 0;
}

(I also haven't tested any of this code :) And I'm not sure if this is a abuse / misuse of the registry table!) And there you have it, pretty cool, a nice way to bind a lua_State to object instance.

No comments: