Home > Software engineering >  How to properly register a C class in lua for multiple calls
How to properly register a C class in lua for multiple calls

Time:12-16

Can't correctly register the C   constructor in lua.

Using the lua C API I made such a wrapper of the class:

namespace API
{
    struct Vector2 {
        double x = 0;
        double y = 0;

        Vector2(const LuaStack& args);
    };

    static int Vector2_new(lua_State* L) {
        lua_newclass<Vector2>(L);

        static auto destructor = [](lua_State* L) {
            delete lua_getclass<Vector2>(L, "Vector2", 1);
            return 0;
        };

        static auto get_len = [](lua_State* L) {
            lua_pushnumber(L, 2);
            return 1;
        };

        static auto to_string = [](lua_State* L) {
            const auto self = lua_getclass<Vector2>(L, "Vector2", 1);

            std::string result;
            result = "{ "   std::to_string(self->x)   ", "   std::to_string(self->y)   " }";

            lua_pushstring(L, result.c_str());
            return 1;
        };

        static auto index_get = [](lua_State* L) {
            const auto self = lua_getclass<Vector2>(L, "Vector2", 1);

            if (lua_isnumber(L, 2)) {
                switch (lua_tointeger(L, 2)) {
                    case 1:
                        lua_pushnumber(L, self->x);
                        break;
                    case 2:
                        lua_pushnumber(L, self->y);
                        break;
                    default:
                        lua_pushnil(L);
                }
            }
            else {
                const std::string_view key = luaL_checkstring(L, 2);
                
                if (key == "x") lua_pushnumber(L, self->x);
                else if (key == "y") lua_pushnumber(L, self->y);
                else lua_pushnil(L);
            }

            return 1;
        };

        static auto index_set = [](lua_State* L) {
            const auto self = lua_getclass<Vector2>(L, "Vector2", 1);
            double new_value = luaL_checknumber(L, 3);

            if (lua_isnumber(L, 2)) {
                switch (lua_tointeger(L, 2)) {
                    case 1:
                        self->x = new_value;
                        break;
                    case 2:
                        self->y = new_value;
                        break;
                }
            }
            else {
                const std::string_view key = luaL_checkstring(L, 2);

                if (key == "x") self->x = new_value;
                if (key == "y") self->y = new_value;
            }

            return 0;
        };

        lua_setmethods(L, "Vector2", {
            { "__gc", destructor },
            { "__len", get_len },
            { "__index", index_get },
            { "__newindex", index_set },
            { "__tostring", to_string },
        });

        return 1;
    }
}

Registering a class constructor:

lua_register(lua_state, "Vector2", API::Vector2_new);

Misc functions:

template<typename Class>
__forceinline Class* lua_getclass(lua_State* L, const std::string& class_name, size_t index) {
    return *static_cast<Class**>(luaL_checkudata(L, index, class_name.c_str()));
};

template<typename Class>
__forceinline void lua_newclass(lua_State* L) {
    *static_cast<Class**>(lua_newuserdata(L, sizeof(Class*))) = new Class(std::move(LuaStack(L)));
};

void lua_setmethods(lua_State* L, const std::string& name, static const std::vector<std::pair<std::string, lua_CFunction>>& methods) {
    if (luaL_newmetatable(L, name.c_str())) {
        for (const auto& function : methods) {
            lua_pushcfunction(L, function.second);
        lua_setfield(L, -2, function.first.c_str());
        }

        lua_setmetatable(L, -2);
    }
}

In the lua script, the following code works:

local vec = Vector2(500, 800)

print(vec.x, vec.y)  -- output:   500   800
print(tostring(vec)) -- output:   { 500, 800 }

But if the constructor is called more than 1 time, the next objects will have the wrong type:

local vec1 = Vector2(500, 800)
local vec2 = Vector2(10, 15)

print(tostring(vec1)) -- output:   { 500, 800 }
print(vec1.x, vec1.y) -- output:   500   800

print(tostring(vec2)) -- output:   table: 08996658
print(vec2.x, vec2.y) -- output:   nil   nil

What did I do wrong? Why, when creating multiple objects, new ones seem to be created incorrectly?

Thank you in advance for your help.

CodePudding user response:

luaL_newmetatable returns 0 if the metatable with the name already exists, so it always pushes a table onto the stack, the return value is used to initialize the metatable.

if (luaL_newmetatable(L, name.c_str())) {
    for (const auto& function : methods) {
        lua_pushcfunction(L, function.second);
        lua_setfield(L, -2, function.first.c_str());
    }
}

lua_setmetatable(L, -2);
  • Related