Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lua: methods and properties when exporting class from c

I use lua as a scripting language for my 3d engine. I have lua "classes" for several objects and now I want to use properties instead of getters and setters. So instead of something like this

local oldState = ui:GetChild("Panel1"):GetVisible()
ui:GetChild("Panel1"):SetVisible(not oldState)

I would just

ui.Panel1.visible = not ui.Panel1.visible

The problem is my C++ code for creating metatables and instanced overrides the __index method. Here it is by the way:

  1. Create a metatable:

    void CLUAScript::RegisterClass(const luaL_Reg funcs[], std::string const& className)
    {
        luaL_newmetatable(m_lua_state, std::string("Classes." + className).c_str());
        luaL_newlib( m_lua_state, funcs);
        lua_setglobal(m_lua_state, className.c_str());
    }
    
  2. Instantiate the class (the lua object only holds a pointer to an actual data that is stored in C++ code):

    int CLUAScript::NewInstanceClass(void* instance, std::string const& className)
    {
        if (!instance)
        {
            lua_pushnil(m_lua_state);
            return 1;
        }
    
        luaL_checktype(m_lua_state, 1, LUA_TTABLE);
    
        lua_newtable(m_lua_state);
    
        lua_pushvalue(m_lua_state,1);       
        lua_setmetatable(m_lua_state, -2);
    
        lua_pushvalue(m_lua_state,1);
        lua_setfield(m_lua_state, 1, "__index");  
    
        void **s = (void **)lua_newuserdata(m_lua_state, sizeof(void *));
    
        *s = instance;
        luaL_getmetatable(m_lua_state, std::string("Classes." + className).c_str());
        lua_setmetatable(m_lua_state, -2);
        lua_setfield(m_lua_state, -2, "__self"); 
    
        return 1; 
    }
    

The question is how can I have both methods and properties. If I just add __index to CLUAScript::RegisterClass funcs array it is never called. And I cannot imagine a way to remove its redefinition in CLUAScript::NewInstanceClass.

If this code is not enough, here is the links to files working with lua: lua helper class, functions for UI, functions for Objects, and testing lua script

like image 866
Warboss-rus Avatar asked Nov 01 '22 11:11

Warboss-rus


1 Answers

The question is how can I have both methods and properties.

Broadly speaking, methods are just properties that happen to resolve to functions.

If I just add __index to RegisterClass funcs array it is never called.

This is the actual problem, right? The rest of your post distracts from the real issue.

According to the docs, luaL_newlib creates a new table. So does luaL_newmetatable. You're creating two tables in RegisterClass, which makes no sense. You need to create only the metatable, and it's this metatable that you need to add your __index and __newindex metamethods to.

You won't be able to have __index simply point to a table of funcs (the shortcut way to implement class methods), not if you want to manually marshal data to and from your C++ class instance properties. It needs to be a function that distinguishes method access (value comes from class-scope) and property access (value comes from instance-scope).


Here's an example of how your method/property access would work in Lua. The specifics are different using the C API, but the approach would be the same:

-- This is the metatable you create in RegisterClass for the C++ class 'Foo'
Foo = { }

-- This is pretty close to how your __index metamethod would work in the C++ code,
-- except you're going to have to write code that resolves which field on the C++ object
-- corresponds to 'key', if any, and push that onto the stack.
function Foo.__index(instance,key)
    local method = rawget(Foo,key)
    if method then
        return method
    end
    return instance.properties[key]
end

-- This is pretty close, too, except that if you want users to be able to add properties to the Lua object
-- that coexist with the C++ object properties, you'll need to write the value to the right place.
function Foo.__newindex(instance,key,value)
    instance.properties[key] = value
end

-- this doesn't have to be a method on the metatable
function Foo:new(state)
    return setmetatable({ properties = state}, self)
end

-- example of a class method
function Foo:dump()
    print('dump:', self.x, self.y)
end

-- simulation of the userdata, an instance of your C++ class
cppClassInstance = {
    x = 10,
    y = 20,
}

obj = Foo:new(cppClassInstance)
print(obj.x) -- read `x`, which is resolved to cppClassInstance.x
obj.x = 5150 -- write to 'x', which writes to cppClassInstance.x
print(obj.x) -- witness our change
obj:dump() -- call a class method
like image 143
Mud Avatar answered Nov 15 '22 03:11

Mud