c++lualua-api

Not able to modify C++ object passed as parameter to Lua function


I have been working on SDL2 2D Game Engine for several years now. Just ditched inheritance approach to define game entities with composition approach where I have Entity class and it has vector of Component classes and recently I got into lua, because I want to define entities using Lua table with optional callback functions.

Working parts

I am using Lua5.4 and C API to bind some engine methods and Entity class to Lua. I use XML file to load list of scripts for each Entity defined by Lua:

  <script name="player" filename="scripts/player.lua" type="entity"/>

Then Entity gets created in C++ with ScriptComponent which holds a pointer to Lua state. Lua file gets loaded at this point and state is not closed unless Entity is destroyed. player.lua script might look something like this:

  -- Entity
player = {
    -- Entity components
    transform = {
         X = 100,
         Y = 250
    },
    physics = {
        mass = 1.0,
        friction = 0.2
    },
    sprite = {
        id = "player",
        animation = {},
        width = 48,
        height = 48
    },
    collider = {
        type = "player",
        onCollide = function(this, second)
            print("Lua: onCollide() listener called!")
        end
    },
    HP = 100
}

Using this I managed to create each Component class using Lua C API with no issues. Also while loading this I detect and set "onCollide" function in Lua.

Also I have managed to register some Engine functions so I can call them to lua: playSound("jump") in C++:

static int lua_playSound(lua_State *L) {
    std::string soundID = (std::string)lua_tostring(L, 1);
    TheSoundManager::Instance()->playSound(soundID, 0);
    return 0;
}

Also have created meta table for Entity class with __index and __gc metamethods and it works if I call these methods with Entity created in Lua outside of player table, such as:

-- This goes in player.lua script after the main table
testEntity = Entity.create() -- works fine, but entity is created in Lua
testEntity:move(400, 400)
testEntity:scale(2, 2)
testEntity:addSprite("slime", "assets/sprite/slime.png", 32, 32)

Problem

Now whenever collision happens and Entity has ScriptComponent, it correctly calls onCollide method in Lua. Even playSound method inside triggers correctly. Problem is when I try to manipulate Entities which are passed as this and seconds arguments to onCollide

onCollide = function(this, second)
        print(type(this)) -- userdata
        print(type(second)) --userdata
        --Entity.scale(this, 10, 10) --segfault
        --this:scale(10, 10) --segfault
        playSound("jump") -- works fine, does not need any metatables
    end

This is how I am calling onCollide method and passing existing C++ object to Lua:

// This is found in a method which belongs to ScriptComponent class, it holds lua state
// owner is Entity*, all Components have this
// second is also Entity*
if (lua_isfunction(state, -1)) {
    void* self = (Entity*)lua_newuserdata(state, sizeof(Entity));
    self = owner;

    luaL_getmetatable(state, "EntityMetaTable");
    assert(lua_isuserdata(state, -2));
    assert(lua_istable(state, -1));
    lua_setmetatable(state, -2);
    assert(lua_isuserdata(state, -1));

    void* second = (Entity*)lua_newuserdata(state, sizeof(Entity));
    second = entity;
                            
    luaL_getmetatable(state, "EntityMetaTable");
    lua_setmetatable(state, -2);

    // Code always reaches cout statement below unless I try to manipulate Entity
    // objects passed to Lua in Lua                     
    if (luaOk(state, lua_pcall(state, 2, 0, 0))) {
        std::cout << "onCollide() Called sucessfully!!!" << std::endl;
    }
    script->clean(); // Cleans lua stack
    return;

}

So basically I have managed to load data from table, bind and use some methods from C++ engine and mapped Entity class using metatable and __index and __gc meta methods which work fine for objects created in Lua but not when I try to pass existing C++ object and set existing meta table.

I still think I will be alright without using any Lua binders, because all I wanted here is to load data for all Components which works fine and script some behaviour based on events which also almost works except for not being able to correctly pass existing C++ object to onCollide method. Thank you for your help!


Solution

  • It is impossible to "pass an existing C++ object to Lua". You would either have to copy it to the storage you get with lua_newuserdata, or use lua_newuserdata from the beginning.

    Depending on what you can guarantee about the lifetime of the entity, you have several options: