ThePhD / sol2

Sol3 (sol2 v3.0) - a C++ <-> Lua API wrapper with advanced features and top notch performance - is here, and it's great! Documentation:
http://sol2.rtfd.io/
MIT License
4.06k stars 493 forks source link

Recursively create table as sol::object becomes userdata for depth > 1 #1478

Closed etorth closed 1 year ago

etorth commented 1 year ago

I am trying to convert a resursive std::variant to a sol::object. Code looks like following but more details needed, you can check link here if you need:

https://github.com/etorth/mir2x/blob/a640d319854ad751ae0c3c7b1305a78d06ea376f/common/src/luaf.cpp#L72

using luaVar = std::variant<long, double, std::string, bool, std::unordered_map<luaVar, luaVar>>;

sol::object buildLuaObj(sol::state_view sv, luaVar v)
{
    if (v.index() == 0)
        return sol::make_object(sv, std::get<0>(v));
    else if (v.index() == 1)
        return sol::make_object(sv, std::get<1>(v));
    else if (v.index() == 2)
        return sol::make_object(sv, std::get<2>(v));
    else if (v.index() == 3)
        return sol::make_object(sv, std::get<3>(v));
    else if (v.index() == 4)
    {
        sol::table t(sv.lua_state(), sol::create);
        for (auto& [k, v] : std::get<4>(v)){
            t[buildLuaObj(sv, k)] = buildLuaObj(sv, v);
        }
        return t;
    }
    else
        throw std::runtime_error("Invalid luaVar");
}

Then if the luaVar is a nested table, like

{1, 2, 3, 4, {5, 6}, {7, {8}}

Then I use buildLuaObj() to convert above table into a sol::object, when I passed the sol::object to lua script, the first layer is still a table. But for nested tables like {5, 6} and {7, {8}}, they all get parsed as userdata, what's more, I can not iterate these userdatas.

Does sol2 forget to recursively create table for lua, or something is wrong in my code? Thanks!

etorth commented 1 year ago

I prepared pice of code that can compile:

#include <memory>
#include <iostream>
#include <sstream>
#include <string>
#include <ostream>
#include <cstddef>
#include <variant>
#include <unordered_map>
#include <type_traits>
#include <sol/sol.hpp>

template<typename... Ts> struct luaVarDispatcher: Ts...
{
    using Ts::operator()...;
};

struct luaNil
{
    bool operator == (const luaNil &) const
    {
        return true;
    }
};

class luaVarWrapper;
namespace _details
{
    struct _luaVarWrapperHash
    {
        size_t operator() (const luaVarWrapper &) const noexcept;
    };
}

class luaVarWrapper;
using luaTable = std::unordered_map<luaVarWrapper, luaVarWrapper, _details::_luaVarWrapperHash>;

using luaVar = std::variant<
    luaNil,
    luaTable,

    lua_Integer,
    double,
    bool,
    std::string>;

class luaVarWrapper
{
    private:
        friend struct _details::_luaVarWrapperHash;

    private:
        std::unique_ptr<luaVar> m_ptr;

    public:
        /**/  luaVarWrapper() = default;
        /**/ ~luaVarWrapper() = default;

    public:
        template<typename T> luaVarWrapper(T t)
            : m_ptr(std::make_unique<luaVar>(std::move(t)))
        {}

    public:
        luaVarWrapper(luaNil)
            : luaVarWrapper()
        {}

    public:
        luaVarWrapper(luaVar v): m_ptr(std::visit(luaVarDispatcher
        {
            [](luaNil) -> std::unique_ptr<luaVar>
            {
                return nullptr;
            },

            [](auto &&arg) -> std::unique_ptr<luaVar>
            {
                return std::make_unique<luaVar>(std::move(arg));
            },
        }, std::move(v))){}

    public:
        luaVarWrapper(const luaVarWrapper &w)
            : m_ptr(w.m_ptr ? std::make_unique<luaVar>(*w.m_ptr) : nullptr)
        {}

        luaVarWrapper(luaVarWrapper &&w)
            : m_ptr(std::move(w.m_ptr))
        {}

        luaVarWrapper & operator = (luaVarWrapper w)
        {
            std::swap(m_ptr, w.m_ptr);
            return *this;
        }

    public:
        operator luaVar () const
        {
            if(m_ptr){
                return *m_ptr;
            }
            else{
                return luaNil{};
            }
        }

    public:
        /* */ luaVar &get()       { return *m_ptr; }
        const luaVar &get() const { return *m_ptr; }

    public:
        bool operator == (const luaVarWrapper &parm) const
        {
            if(m_ptr){
                return get() == parm.get();
            }
            else{
                return parm.m_ptr == nullptr; // luaNil
            }
        }

        bool operator == (const luaVar &parm) const
        {
            if(m_ptr){
                return get() == parm;
            }
            else{
                return parm.index() == 0; // luaNil
            }
        }

        bool operator == (const luaNil &) const
        {
            return m_ptr == nullptr || m_ptr->index() == 0;
        }
};

size_t _details::_luaVarWrapperHash::operator () (const luaVarWrapper &wrapper) const noexcept
{
    return std::visit(luaVarDispatcher
    {
        [](const luaNil &) -> size_t
        {
            return 2918357;
        },

        [](const luaTable &table) -> size_t // requires order-invariant
        {
            size_t h = 3679231;
            for(const auto &[k, v]: table){
                h ^= _details::_luaVarWrapperHash{}(k);
                h ^= _details::_luaVarWrapperHash{}(v);
            }
            return h;
        },

        [](const auto &t) -> size_t
        {
            return std::hash<std::remove_cvref_t<decltype(t)>>{}(t);
        },
    }, *wrapper.m_ptr);
}

template<typename T> sol::object buildLuaObj(sol::state_view sv, T t)
{
    return sol::object(sv, sol::in_place_type<std::remove_cvref_t<decltype(t)>>, std::move(t));
}

sol::object buildLuaObj(sol::state_view sv, luaNil)
{
    return sol::make_object(sv, sol::nil);
}

sol::object buildLuaObj(sol::state_view sv, luaVar v)
{
    return std::visit(luaVarDispatcher
    {
        [&sv](const luaTable &t) -> sol::object
        {
            sol::table tbl(sv.lua_state(), sol::create);
            for(const auto &[k, v]: t){
                tbl[buildLuaObj(sv, k)] = buildLuaObj(sv, v);
            }
            return tbl; // sol::table can be used as sol::object
        },

        [&sv](const auto &v) -> sol::object
        {
            return buildLuaObj(sv, v);
        }
    }, v);
}

luaTable makeLuaTable(std::initializer_list<luaVar> elems)
{
    luaTable table;
    lua_Integer index = 1;

    for(const auto &e: elems){
        table[luaVar(index++)] = luaVar(e);
    }
    return table;
}

int main()
{
    // { {1, 2}, {3, 4}, {5, 6} }

    luaVar tbl = makeLuaTable({
        makeLuaTable({
            1,
            2,
        }),

        makeLuaTable({
            3,
            4,
        }),

        makeLuaTable({
            5,
            6,
        }),
    });

    sol::state lua;
    lua.open_libraries();

    lua.set_function("func", [tbl](sol::function handler, sol::this_state s)
    {
        sol::object obj = buildLuaObj(sol::state_view(s), tbl);
        handler("hello", obj);
    });

    sol::protected_function_result pfr = lua.script(R"#(
        local function handler(msg, tbl)

            local printTable = nil
            printTable = function(prefix, t)
                print(prefix, t, type(t))
                for k, v in pairs(t) do
                    printTable(prefix .. '  ', v)
                end
            end

            printTable('', tbl)
        end

        func(handler)
    )#");

    if(!pfr.valid()){
        const sol::error err = pfr;
        std::stringstream errStream(err.what());

        std::string errStr;
        while(std::getline(errStream, errStr, '\n')){
            std::cout << errStr << std::endl;
        }
    }

    return 0;
}

output is:

        table: 0x7fffd6fa0e70   table
        sol.luaVarWrapper: 0x7fffd6fa30f8       userdata

You see the first layer is table``, but nested table areuserdata`` and can not iterate.

etorth commented 1 year ago

my fault! I didn't explicitly declare buildLuaObj(luaVarWrapper) which makes the trouble.