Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to register member function to lua without lua bind in c++

Tags:

c++

lua

I use lua 5.1 in my c++ game project, but I had a trouble to use lua when I try to register a c++ member function. I want to use my c++ class member function in lua , but lua_register() function's 3rd parameter can only accept c type normal function pointer or static member function's pointer.

I heard that lua bind library can solve this problem, but I do not want to use lua bind. It's good, but too heavy for my project. Is there have any method to register c++ member function without any libraries? How should I go about this?

like image 207
HelloWorld Avatar asked Sep 05 '15 18:09

HelloWorld


1 Answers

I have gone through this same experience myself.

There are basically two good solutions that I know. One is good if the member function is for a class which there will only be one of, for each lua state. Another is more flexible but more complicated and slower. (I would be keen to learn other methods / improvements on these methods!)

I think lua_bind uses some templates quite similar to method 1, but uses tricks to make the implementation as flexible as method 2. I think either of these is more transparent than what lua_bind does.

Method 1

(1) For each member function of my_class which you want to pass to lua, it should take lua_State * L and return int.

(2) At the time that lua is initialized, store a pointer to the associated my_class in the lua_extraspace.

*static_cast<my_class**>(lua_getextraspace(L_)) = &instance;

(3) When you want to pass member functions to lua, use a template like this:

typedef int (my_class::*mem_func)(lua_State * L);

// This template wraps a member function into a C-style "free" function compatible with lua.
template <mem_func func>
int dispatch(lua_State * L) {
    my_class * ptr = *static_cast<my_class**>(lua_getextraspace(L));
    return ((*ptr).*func)(L);
}

You then register things like this:

const luaL_Reg regs[] = {
    { "callback_1", &dispatch<&my_class::callback_1> },
    { "callback_2", &dispatch<&my_class::callback_2> },
    { "callback_3", &dispatch<&my_class::callback_3> },
    { NULL, NULL }
};
luaL_register(L, regs); 

Method 1 has the benefit that it is pretty simple and extremely fast, I think it will be faster than lua bind. Because, get_extraspace doesn't do anything except a little pointer arithmetic. Most likely, a good compiler can optimize the dispatch template so that the function it makes is inlined, and there would be no overhead.

You may want to change the dispatch template so that there is a null pointer check at the extraspace, it depends on how the lifetimes of your lua state and your my_class are managed.

Potentially, you can also store more complicated stuff in the extraspace, like pointers to several different objects, or even like a vector or something (You can read about how to configure the lua extraspace in their docs). Or you could store things in the lua registry and the dispatch function can retrieve them from there, but the extraspace is faster -- it's up to you.

Method 2

In method 2, you basically use the usual lua techniques for pushing a C++ object to lua that is owned by lua, but you do it where the object is a C++ std::function and you overload the _call metafunction so that it calls the function. (If you are not in C++11 you can use boost::function.)

Then when you want to push a c++ member function to lua, you use std::bind to make it a function object.

This method has the drawback that within lua, the type of the "function" will actually be userdata, but since you can call it just fine and use it as a function it doesn't really matter. If this is bad for you, one thing you can do is to use the same trick, but also afterwards, make a closure which has the function object userdata as an upvalue, and when the closure is called, it just forwards the arguments to the function object and returns the results. Then the type of the closure will be function in lua, but it will do basically the same thing.

typedef std::function<int(lua_State *)> lua_function;
char const * cpp_function = "CPP_Function";

static int intf_dispatcher ( lua_State* L )
{
    //make a temporary copy, in case lua_remove(L,1) might cause lua to garbage collect and destroy it
    lua_function f = * static_cast<lua_function *> (luaL_checkudata(L, 1, cpp_function));
    // remove from the stack before executing, so that like all other callbacks, f finds only its intended arguments on the stack.
    lua_remove(L,1);
    int result = (f)(L);
    return result;
}

static int intf_cleanup ( lua_State* L )
{
    lua_function * d = static_cast< lua_function *> (luaL_testudata(L, 1, cpp_function));
    if (d == NULL) {
        std::cerr << "ERROR: intf_cleanup called on data of type: " << lua_typename( L, lua_type( L, 1 ) ) << std::endl;
        lua_pushstring(L, "C++ function object garbage collection failure");
        lua_error(L);
    } else {
        d->~lua_function();
    }
    return 0;
}

static int intf_tostring( lua_State* L )
{
    lua_function * d = static_cast< lua_function *> (luaL_checkudata(L, 1, cpp_function));
    // d is not null, if it was null then checkudata raised a lua error and a longjump was executed.
    std::stringstream result;
    result << "c++ function: " << std::hex << d;
    lua_pushstring(L, result.str().c_str());
    return 1;
}

void register_metatable ( lua_State* L )
{
    luaL_newmetatable(L, cpp_function);
    lua_pushcfunction(L, intf_dispatcher);
    lua_setfield(L, -2, "__call");
    lua_pushcfunction(L, intf_cleanup);
    lua_setfield(L, -2, "__gc");
    lua_pushcfunction(L, intf_tostring);
    lua_setfield(L, -2, "__tostring");
    lua_pushvalue(L, -1); //make a copy of this table, set it to be its own __index table
    lua_setfield(L, -2, "__index");

    lua_pop(L, 1);
}

void push_function( lua_State* L, const lua_function & f )
{
    void * p = lua_newuserdata(L, sizeof(lua_function));
    luaL_setmetatable(L, cpp_function);
    new (p) lua_function(f);
}
like image 56
Chris Beck Avatar answered Sep 22 '22 02:09

Chris Beck