Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing existing C++ objects to Lua

Tags:

c++

class

lua

I am currently working on my own videogame engine and I'm trying to implement support for lua scripts in order to code in game's behaviour. However, I'm currently struggling with C++ classes in Lua. I understand how to create a new instance of the class on heap by lua - but that's (probably) not what I want to do.

I rather need to pass an object that already exists in C++ into Lua and then work with it inside of the script. (Example: The engine has an instance of a monster and I'd like to run a script for the monster to see whether it sees the player - and if so, then the monster would attack the player).

The only solution I have found is this one: Passing existing C++ objects to Lua and calling the passed objects' member functions - however, the original poster is using luabind which requires boost (which I don't really want to use).

Thus, my questions are these:

How can I pass an object already allocated on heap in C++ to a Lua script? (without using luabind)

Is this approach even correct? Most of what I've found tends to answer the question "how to create an instance of a C++ class in Lua" rather than just passing it, which leads me to think whether is my idea even right or not.

Note: I do not mind using a tool such as luabind, I just don't want to use them in case they depend on external libraries such as boost. If there's any easy solution like this then I will gladly use it.

like image 502
prkist Avatar asked Jul 14 '26 16:07

prkist


1 Answers

If you want truly independent binding, here is classic C-style boilerplate. Just replace "Object" with your class name and implement // { } blocks. Turning push_Object from static to public will give you universal existing Object pusher that also caches objects in metatable (otherwise multi-push would create many distinct userdata with all-obvious gc-trouble).

You may also C++ify or library-fy that if you want, but I personally do not do this, because adding e.g. __newindex-to-environment proxy and other quirks would not be so straightforward if it was library. Actually, all boilerplate is in push_mt, push_Object, forget_Object and check_Object, everything other is subject to fine-tuning.

Note that this only binds a single class, not all classes at once.

// { class Object { ... } }

static const char *tname = "Object";

static void push_Object(lua_State *L, Object *object);
static Object *check_Object(lua_State *L, int i);

static int
l_gc(lua_State *L)
{
    Object **ud = luaL_checkudata(L, 1, tname);

    if (*ud) {
        // { delete *ud }
        *ud = NULL;
    }
    return 0;
}

static int
l_tostring(lua_State *L)
{
    Object **ud = luaL_checkudata(L, 1, tname);

    lua_pushfstring(L, "%s: %p", tname, *ud);
    return 1;
}

static int
l_new(lua_State *L)
{
    Object *object = NULL; // { = new Object }

    push_Object(L, object);
    return 1;
}

static int
l_method(lua_State *L)
{
    Object *object = check_Object(L, 1);
    lua_Integer int_arg = luaL_checkinteger(L, 2);
    const char *str_arg = luaL_checklstring(L, 3, NULL);

    // { object->method(int_arg, str_arg) }

    return 0;
}

static const luaL_Reg lib[] = {
    // functions
    { "new",    l_new    }, // () -> object

    // methods
    { "method", l_method }, // (object, int, string) -> none

    { NULL, NULL },
};
static lua_CFunction first_m = l_method;

static void
push_mt(lua_State *L)
{
    if (luaL_newmetatable(L, tname)) {
        size_t m = 0; while (first_m != lib[m].func) m++;
        lua_createtable(L, 0, 0);
        luaL_register(L, NULL, &lib[m]);
        lua_setfield(L, -2, "__index");

        lua_pushcfunction(L, l_tostring);
        lua_setfield(L, -2, "__tostring");

        lua_pushcfunction(L, l_gc);
        lua_setfield(L, -2, "__gc");

        lua_pushstring(L, tname);
        lua_setfield(L, -2, "__metatable");

        // mt.objects = setmetatable({ }, { __mode = "v" })
        lua_createtable(L, 0, 0);
        lua_createtable(L, 0, 1);
        lua_pushstring(L, "v");
        lua_setfield(L, -2, "__mode");
        lua_setmetatable(L, -2);
        lua_setfield(L, -2, "objects");
    }
}

static void
push_Object(lua_State *L, Object *object)
{
    int top = lua_gettop(L);

    push_mt(L);
    lua_getfield(L, -1, "objects");
    // top+1 = mt
    // top+2 = mt.objects

    // ud = mt.objects[object]
    lua_pushlightuserdata(L, object);
    lua_gettable(L, top+2);

    if (lua_isnil(L, -1)) {
        lua_pop(L, 1);

        Object **ud = lua_newuserdata(L, sizeof(*ud));
        *ud = object;

        // setmetatable(ud, mt)
        lua_pushvalue(L, top+1);
        lua_setmetatable(L, -2);

        // mt.objects[object] = ud
        lua_pushlightuserdata(L, object);
        lua_pushvalue(L, -3);
        lua_pushvalue(L, top+2);
    }

    // return ud
    lua_replace(L, top+1);
    lua_settop(L, top+1);
    return; // ud at top
}

static void
forget_Object(lua_State *L, Object *object)
{
    int top = lua_gettop(L);

    push_mt(L);
    lua_getfield(L, -1, "objects");
    // top+1 = mt
    // top+2 = mt.objects

    // ud = mt.objects[object]
    lua_pushlightuserdata(L, object);
    lua_pushnil(L);
    lua_settable(L, top+2);

    lua_settop(L, top);
}

static Object *
check_Object(lua_State *L, int i)
{
    Object **ud = luaL_checkudata(L, i, tname);
    Object *object = *ud;

    if (object == NULL)
        luaL_error(L, "%s is finalized", tname);

    return object;
}

int
luaopen_Object(lua_State *L)
{
    push_mt(L); // register tname

    lua_createtable(L, 0, sizeof(lib)-1);
    luaL_register(L, NULL, lib);
    return 1;
}
like image 59
user3125367 Avatar answered Jul 17 '26 20:07

user3125367