5
votes

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

1
To help clarify the connection between C++ code and lua code, what is ui and what does ui:GetChild("property_name") return? For example, is ui an userdata from C++ land or is it some table returned by NewInstanceClass with __self = udata_object set?greatwolf
The "ui" is an instance of UI element like button, panel or an entire screen. Every element can have child elements, which can be accesed using GetChild(name) method. These childs will also be UI elements. So this is a table that contains a pointer to c++ instance as a userdata and some methods from metatable returned by NewInstanceClass function. In this case the "ui" is a local variable that represents the entire screen (the root element of UI tree).Warboss-rus
What if you make GetChild and GetVisible free standing functions in lua? Then you can just make ui.Panel1 as a form of syntax sugar that translates to GetChild(ui, "Panel1").greatwolf
The problem is not all methods can be replaced with properties. Thats why I want to use both methods and properties with the same entity. As for global functions, I don't think this is very nice solution, because different classes can have similar methods, which I will either have to check what type received object is on each call, and do different things or create different function for each class which will lead to complex names for these functions.Warboss-rus
Are there any restrictions on what can be properties and methods? For example, can a property and a method have the same name? Can new methods and properties be assigned to an instance from a lua script?greatwolf

1 Answers

2
votes

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