Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing user data as a member of another user data in LUA C++

Tags:

c++

lua

I am trying to index into a user data called entity to get to another user data called transform component. I am relatively new to lua w/ c++ binding but after I got my entity up and running with metatables and an index method that would allow me to do stuff like this in Lua:

entity1 = Entity.create()
entity1:setID(4)
print(entity1)

This works perfectly but I also have a user data called TransformComponent that looks like this in LUA

transform1 = TransformComponent.create()
transform1:setPosition(4,3)
print(transform1)

Here is where I get stuck. What I WANT to be able to do in LUA is something like this:

entity1 = Entity.create()
entity1.transform:setPosition(4,3)
print(entity1)

How do I (in C++) set up this kind of relationship where Entity is a table that contains all its methods but also contains a key called "transform" that maps to the TransformComponent table. In C++ I tried setting it as a key and I tried reading the "transform" key from the index method and placing the TransformComponent methods in the table but that didn't work.

Any help is extremely appreciated.

EDIT

I initially didn't want to include the attempts I made because they were futile and I knew it but what I attempted to do was during the entity index, I would push the table for TransformComponent and hope the next part of the index (i.e. the methods for TransformComponent)

So here is the index method for Entity


static int _LUA_index(lua_State* L) {
        assert(lua_isstring(L, -1));
        Entity* luaEntity1 = (Entity*)lua_touserdata(L, -2); assert(luaEntity1);
        std::string index = lua_tostring(L, -1);
        if (index == "transform") {
            lua_getglobal(L, "TransformComponent");
            return 1;
        }
        lua_getglobal(L, "Entity");
        lua_pushstring(L, index.c_str());
        lua_rawget(L, -2);
        return 1;
    }

If i print out the result of "index" its just transform, I can never get it to try and parse "setPosition". I have no Idea how to progress here. Here is the code to setup the tables:


    lua_State* L = luaL_newstate();
    luaL_openlibs(L);



    /* --------- Transform table ----------- */
    lua_newtable(L);
    int transformTableIndex = lua_gettop(L);
    lua_pushvalue(L, transformTableIndex);
    lua_setglobal(L, "TransformComponent");

    lua_pushcfunction(L, TransformComponent::_LUA_CreateTransform);
    lua_setfield(L, -2, "create");
    lua_pushcfunction(L, TransformComponent::_LUA_SetPosition);
    lua_setfield(L, -2, "setPosition");

    /* --------- Transform Meta table ----------- */
    luaL_newmetatable(L, "TransformComponentMetaTable");
    lua_pushcfunction(L, TransformComponent::_LUA_gc);
    lua_setfield(L, -2, "__gc");
    lua_pushcfunction(L, TransformComponent::_LUA_eq);
    lua_setfield(L, -2, "__eq");
    lua_pushcfunction(L, TransformComponent::_LUA_tostring);
    lua_setfield(L, -2, "__tostring");
    lua_pushcfunction(L, TransformComponent::_LUA_index);
    lua_setfield(L, -2, "__index");



    /* --------- Entity table --------- */
    lua_newtable(L);
    int entityTableIndex = lua_gettop(L);
    lua_pushvalue(L, entityTableIndex);
    lua_setglobal(L, "Entity");

    constexpr int NUM_OF_UPVALUES = 1;
    lua_pushlightuserdata(L, &manager);
    lua_pushcclosure(L, Entity::_LUA_CreateEntity, NUM_OF_UPVALUES);
    //lua_pushcfunction(L, Entity::_LUA_CreateEntity);
    lua_setfield(L, -2, "create");
    lua_pushcfunction(L, Entity::_LUA_MoveEntity);
    lua_setfield(L, -2, "move");
    lua_pushcfunction(L, Entity::_LUA_DrawEntity);
    lua_setfield(L, -2, "draw");

    /* --------- Entity Meta table --------- */
    luaL_newmetatable(L, "EntityMetaTable");
    lua_pushstring(L, "__gc");
    lua_pushcfunction(L, Entity::_LUA_gc);
    lua_settable(L, -3);
    lua_pushstring(L, "__eq");
    lua_pushcfunction(L, Entity::_LUA_eq);
    lua_settable(L, -3);
    lua_pushstring(L, "__tostring");
    lua_pushcfunction(L, Entity::_LUA_tostring);
    lua_settable(L, -3);
    lua_pushstring(L, "__index");
    lua_pushcfunction(L, Entity::_LUA_index);
    lua_settable(L, -3);

I started thinking this was impossible to accomplish or I am just unable to do this Lua. I am unsure but I am definitely not talented enough to figure this out.

EDIT2 EntityClass + TransformComponent:


struct _API SpriteComponent {
    const char* fileName;
};
struct _API TransformComponent {
    glm::vec2 position;
    glm::vec3 rotation;

    TransformComponent();
    ~TransformComponent();

    static int _LUA_CreateTransform(lua_State* L);
    static int _LUA_SetPosition(lua_State* L);
    static int _LUA_gc(lua_State* L);
    static int _LUA_eq(lua_State* L);
    static int _LUA_index(lua_State* L);
    static int _LUA_tostring(lua_State* L);

};

class _API Entity{
public:
    TransformComponent transform;
    SpriteComponent sprite;

    Entity();
    ~Entity();
    void moveEntity(float x, float y);
    void drawEntity();

    static int _LUA_CreateEntity(lua_State* L);
..etc etc

EDIT3

_LUA_CreateTransform method


int TransformComponent::_LUA_CreateTransform(lua_State* L) {

    void* entityPointer = lua_newuserdata(L, sizeof(TransformComponent));
    new(entityPointer) TransformComponent(); assert(entityPointer);
    luaL_getmetatable(L, "TransformComponentMetaTable");
    lua_setmetatable(L, -2);
    return 1;
}

Again any help is super appreciated.

like image 426
Yea okay Avatar asked Oct 19 '25 03:10

Yea okay


2 Answers

Your _LUA_index C++ function is more-or-less equivalent to this in pure Lua:

function EntityMetaTable.__index(luaEntity1, index)
    if tostring(index) == 'transform' then
        return TransformComponent
    end
    return rawget(Entity, index)
end

Thus, when you do entity1.transform:setPosition(4,3), it's basically equivalent to TransformComponent:setPosition(4,3). The problem is that you can't do setPosition directly on the TransformComponent table. You need to do it on a userdata that TransformComponent.create() gives you, like transform1 is, instead. If you want to avoid making a fresh TransformComponent userdata every time, you can save the result as a uservalue (or environment if you're on Lua 5.1 still) on your entity userdata.


Edit: Now that I see more of your code, here's what you should do. In your Entity class, change TransformComponent transform to TransformComponent *transform. Then, in your _LUA_CreateEntity function, set up transform the same way that you do in _LUA_CreateTransform. Then set the transform userdata as the environment or uservalue (depending on Lua version) of the entity userdata. Finally, in your index method, return that environment/uservalue instead of doing lua_getglobal(L, "TransformComponent");.

like image 188
Joseph Sible-Reinstate Monica Avatar answered Oct 20 '25 16:10

Joseph Sible-Reinstate Monica


You are returning a static class/table (TransformComponent in this case) when your entity's index transform is called. What you really need is the actual instance of your transform that is present inside your entity class. So technically, you want properties of your object to be accessible from Lua easily by calling ent.transform.

To achieve this, you can use sol which is "a fast, simple C++ and Lua Binding".

class _API Entity{
public:
    TransformComponent transform;

    TransformComponent get_transform() {
        return transform;
    }

    void set_transform(TransformComponent value) {
        transform = value;
    }

...

#include <sol/sol.hpp>

int main (int, char*[]) {
    sol::state lua;
    lua.open_libraries(sol::lib::base);


    lua.new_usertype<Entity>( "Entity",
        "transform", sol::property(&Entity::get_transform, &Entity::set_transform)
    );

Additionally, you can have properties that are read-only which would fit your transform property perfectly since you do not want it to be replaced with a new transform and rather you want the transform properties to be changed.

sol::usertype<Entity> type_ent = lua.new_usertype<Entity>("Entity",
        sol::constructors<Entity()>());
// Read only property (Still, you can call your Transform methods on it).
type_ent.set("transform", sol::readonly(&Entity::get_transform));

As a side note, be sure to first register your TransformComponent with all it's functions and properties and then register your Entity.

Have a look at sol properties and sol classes instruction for more details.

like image 21
Ali Deym Avatar answered Oct 20 '25 17:10

Ali Deym