Git Product home page Git Product logo

Comments (7)

timfel avatar timfel commented on September 26, 2024

For reference, this is what I have right now:

        template <class TG, class TS = TG>
        Class<T>& addArrayProperty(char const* name, TG (*get)(const T*, int), void (*set)(T*, int, TS) = nullptr)
        {
            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            lua_pushlightuserdata(L, get);
            lua_pushlightuserdata(L, set);
            lua_pushcclosure(L, +[](lua_State *l) {
                // make the proxy table
                lua_newtable(l); // table
                lua_newtable(l); // metatable, table

                lua_pushstring(l, "__index"); // key, metatable, table
                lua_pushvalue(l, 1); // this pointer
                lua_pushvalue(l, lua_upvalueindex(1)); // getter pointer
                lua_pushcclosure(l, +[](lua_State *l) {
                    // Args: table, name
                    if (!lua_isnumber(l, 2)) {
                        return luaL_error(l, "Not an index '%s'", lua_tostring(l, 2));
                    }
                    T *s = reinterpret_cast<T *>(lua_touserdata(l, lua_upvalueindex(1)));
                    int idx = lua_tointeger(l, 2);
                    Stack<TG>::push(l, reinterpret_cast<TG(*)(const T*, int)>(lua_touserdata(l, lua_upvalueindex(2)))(s, idx));
                    return 1;
                }, 2); // func, key, metatable, table
                lua_rawset(l, -2); // metatable, table

                lua_pushstring(l, "__newindex"); // key, metatable, table
                lua_pushvalue(l, 1); // this pointer
                lua_pushvalue(l, lua_upvalueindex(2)); // setter pointer
                lua_pushcclosure(l, +[](lua_State *l) {
                    // Args: table, name, new value
                    if (!lua_isnumber(l, 2)) {
                        return luaL_error(l, "Not an index '%s'", lua_tostring(l, 2));
                    }
                    T *s = reinterpret_cast<T *>(lua_touserdata(l, lua_upvalueindex(1)));
                    int idx = lua_tointeger(l, 2);
                    void *setter = lua_touserdata(l, lua_upvalueindex(2));
                    if (setter == nullptr) {
                        return luaL_error(l, "read-only array");
                    } else {
                        reinterpret_cast<void(*)(T*, int, TS)>(setter)(s, idx, Stack<TS>::get(l, 3));
                    }
                    return 0;
                }, 2); // func, key, metatable, table
                lua_rawset(l, -2); // metatable, table
                lua_setmetatable(l, -1); // table
                return 1;
            }, 2); // Stack: co, cl, st, getter
            lua_pushvalue(L, -1); // Stack: co, cl, st, getter, getter
            CFunc::addGetter(L, name, -5); // Stack: co, cl, st, getter
            CFunc::addGetter(L, name, -3); // Stack: co, cl, st,
            
            return *this;
        }

and

    template <class TG, class TS = TG>
    Namespace& addArrayProperty(char const* name, TG (*get)(int), void (*set)(int, TS) = 0)
    {
        if (m_stackSize == 1)
            throw std::logic_error("addProperty () called on global namespace");

        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: namespace table (ns)

        lua_pushlightuserdata(L, get);
        lua_pushlightuserdata(L, set);
        lua_pushcclosure(L, +[](lua_State *l) {
            // make the proxy table
            lua_newtable(l); // table
            lua_newtable(l); // metatable, table

            lua_pushstring(l, "__index"); // key, metatable, table
            lua_pushvalue(l, lua_upvalueindex(1)); // getter pointer
            lua_pushcclosure(l, +[](lua_State *l) {
                // Args: table, name
                if (!lua_isnumber(l, 2)) {
                    return luaL_error(l, "Not an index '%s'", lua_tostring(l, 2));
                }
                int idx = lua_tointeger(l, 2);
                Stack<TG>::push(l, reinterpret_cast<TG(*)(int)>(lua_touserdata(l, lua_upvalueindex(1)))(idx));
                return 1;
            }, 1); // func, key, metatable, table
            lua_rawset(l, -2); // metatable, table

            lua_pushstring(l, "__newindex"); // key, metatable, table
            lua_pushvalue(l, lua_upvalueindex(2)); // setter pointer
            lua_pushcclosure(l, +[](lua_State *l) {
                // Args: table, name, new value
                if (!lua_isnumber(l, 2)) {
                    return luaL_error(l, "Not an index '%s'", lua_tostring(l, 2));
                }
                int idx = lua_tointeger(l, 2);
                void *setter = lua_touserdata(l, lua_upvalueindex(1));
                if (setter == nullptr) {
                    return luaL_error(l, "read-only array");
                } else {
                    reinterpret_cast<void(*)(int, TS)>(setter)(idx, Stack<TS>::get(l, 3));
                }
                return 0;
            }, 1); // func, key, metatable, table
            lua_rawset(l, -2); // metatable, table
            lua_setmetatable(l, -1); // table
            return 1;
        }, 2); // Stack: co, cl, st, getter
        lua_pushvalue(L, -1); // Stack: co, cl, st, getter, getter
        CFunc::addGetter(L, name, -5); // Stack: co, cl, st, getter
        CFunc::addGetter(L, name, -3); // Stack: co, cl, st,
        
        return *this;
    }

from luabridge3.

kunitoki avatar kunitoki commented on September 26, 2024

Yeah it's an interesting use case, and could probably be solved in many different ways. I would personally use methods to set and get those values in lua (instead of accessing metamethods like __index and __newindex), but this could find place in the api for sure.

An alternative solution using the apis provided by luabridge already i could come up with is involving defining a luabridge class for your type and defining the metamethods there directly. Probably less code overall and better performance as well (no hidden tables created on get / set calls):

TEST_F(ClassMetaMethods, SimulateArray)
{
    using ContainerType = std::vector<std::string>;
    
    luabridge::getGlobalNamespace(L)
        .beginClass<ContainerType>("__x")
            .addFunction("__index", [](ContainerType* thiz, int index, lua_State* L) -> std::string
            {
                if (index < 0 || index >= thiz->size())
                    luaL_error(L, "Invalid index access in table %d", index);
                
                return (*thiz)[index];
            })
            .addFunction("__newindex", [](ContainerType* thiz, int index, const std::string& value, lua_State* L)
            {
                if (index < 0)
                    luaL_error(L, "Invalid index access in table %d", index);
                
                if (index >= thiz->size())
                    thiz->resize(index + 1);
                
                (*thiz)[index] = value;
            })
        .endClass();

    ContainerType data(1);
    data[0] = "abcdefg";
    
    luabridge::setGlobal(L, data, "x");
    
    runLua("result = x[0]");
    ASSERT_EQ("abcdefg", result<std::string>());

    runLua("x[10] = '123456'; result = x[10]");
    ASSERT_EQ("123456", result<std::string>());
}

from luabridge3.

kunitoki avatar kunitoki commented on September 26, 2024

Alternatively i came up with an API like this:

    using ContainerType = std::vector<std::string>;

    ContainerType data(1);
    data[0] = "abcdefg";

    luabridge::getGlobalNamespace(L)
        .beginTable("xyz")
            .addMetaFunction("__index", [&data](luabridge::LuaRef, int index, lua_State* L)
            {
                if (index < 0 || index >= data.size())
                    luaL_error(L, "Invalid index access in table %d", index);

                return data[index];
            })
            .addMetaFunction("__newindex", [&data](luabridge::LuaRef, int index, luabridge::LuaRef ref, lua_State* L)
            {
                if (index < 0)
                    luaL_error(L, "Invalid index access in table %d", index);
                
                if (! ref.isString())
                    luaL_error(L, "Invalid value provided to set table at index %d", index);

                if (index >= data.size())
                    data.resize(index + 1);
                
                data[index] = ref.cast<std::string>();
            })
        .endTable();

    runLua("xyz[0] = '123'; result = xyz[0]");
    ASSERT_EQ("123", result<std::string>());

so instead of requiring to define a class, we can define a newTable directly into the namespaces, and set meta methods directly on it, so it's possible to handle conversions and validation outside of the infrastructure in user code (which offer better flexibility for custom situations).

from luabridge3.

timfel avatar timfel commented on September 26, 2024

Ah, I didn't realise I can use addFunction to also define meta methods. So I guess I would define a class for each array type I have, and on the owner class of the array just a property getter that returns that class

from luabridge3.

timfel avatar timfel commented on September 26, 2024

I would personally use methods to set and get those values in lua

Ideally I would, too. But I have the case where I have a lot of Lua code that is currently written against tolua++ generated bindings, and I want to migrate off of tolua++, but don't want to immediately break all the existing Lua code :)

from luabridge3.

timfel avatar timfel commented on September 26, 2024

Your first approach works very well in my project, thanks!

from luabridge3.

kunitoki avatar kunitoki commented on September 26, 2024

Great to hear! Yeah second approach was more experimental api design

from luabridge3.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.