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.
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);