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 492 forks source link

Accessing Lua self object from a C++ function #1559

Open akovachev opened 7 months ago

akovachev commented 7 months ago

I need to call a Lua function attached to the object instance from C++ class member function.

In other words I need to pass the self object to the function when calling from C++. If I know the name of the object I can access it by state["objectName"] and pass it. But unless I cache it somewhere I can't really convert this pointer to self object. I'm looking for an elegant mechanism to do this as I have a ton of bound C++ classes that need to call custom functions.

Ideally I'd want to do this at construction time. Perhaps by getting access to the Lua table object as a constructor parameter? Would that even work, or is the table created after the object has been constructed?

class Cpp { public: ... void callLuaMemberFunction { if (luaMemberFunc) { luaMemberFunc(, params...); } }

void bindLuaFunction(const std::string& key, sol::function func) { if (key == "memberFunc") { luaMemberFunc = std::move(func); } // ... [Handle other overridable functions similarly] }

private: sol::function luaMemberFunc; }

at binding time:

cpp.set(sol::meta_function::new_index, [](Cpp& _this, const std::string& key, sol::function func) { _this.bindLuaFunction(key, func); });

Lua:

cpp = Cpp() cpp:memberFunc = function(...) end

Currently I'm solving this by passing the name to the object at construction which then looks up the object by name every call. A typo could be an issue if we pass a different name than the object's assigned name. Something I'd like to avoid. I also want to avoid caching the object for each Lua member function (I'm assuming I'd have access to it at new_index call).

Rochet2 commented 7 months ago

Would something like this work for you?

#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

struct Cpp {
};

int main() {
    sol::state lua;
    lua.open_libraries(sol::lib::base, sol::lib::package);

    auto cpp_meta = lua.new_usertype<Cpp>("Cpp");

    lua.script(R"(
        Cpp.memberFunc = function(...)
            print(...)
        end

        cpp = Cpp.new()
        cpp:memberFunc("hello world!")
        -- prints sol.Cpp: 0x5b0038 hello world!
    )");
    return 0;
}

Its not what you ask, but it achieves what you set out to do in your code example from what I see.

akovachev commented 7 months ago

@Rochet2 Thank you for your suggestion.

However the goal is cpp:memberFunction to be called from the C++ class such that the Lua implementation has access to the Lua self table. I know I can pass in the C++ this pointer but that won’t ensure access to the self object.

On Sun, Dec 3, 2023 at 12:30 PM Rochet2 @.***> wrote:

Would something like this work for you?

define SOL_ALL_SAFETIES_ON 1

include <sol/sol.hpp>

struct Cpp { }; int main() { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::package);

auto cpp_meta = lua.new_usertype<Cpp>("Cpp");

lua.script(R"(        Cpp.memberFunc = function(...)            print(...)        end        cpp = Cpp.new()        cpp:memberFunc("hello world!")    )");
return 0;

}

Its not what you ask, but it achieves what you set out to do in your code example from what I see.

— Reply to this email directly, view it on GitHub https://github.com/ThePhD/sol2/issues/1559#issuecomment-1837436914, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAZKSUCE2KYGS4NUDHAOLHTYHRIFDAVCNFSM6AAAAABAEUFCKWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMZXGQZTMOJRGQ . You are receiving this because you authored the thread.Message ID: @.***>

akovachev commented 7 months ago

In short I need something like returns_self, but instead of returning it, for it to be passed in as sol::object or sol::reference to a bound member function.

Attaching one of the tests using returns_self from sol::policies:

TEST_CASE("policies/self", "ensure we return a direct reference to the lua userdata rather than creating a new one") {
    struct vec2 {
        float x = 20.f;
        float y = 20.f;

        vec2& normalize() {
            float len2 = x * x + y * y;
            if (len2 != 0) {
                float len = sqrtf(len2);
                x /= len;
                y /= len;
            }
            return *this;
        }

        ~vec2() {
            x = std::numeric_limits<float>::lowest();
            y = std::numeric_limits<float>::lowest();
        }
    };

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

    lua.new_usertype<vec2>("vec2", "x", &vec2::x, "y", &vec2::y, "normalize", sol::policies(&vec2::normalize, sol::returns_self()));

    auto result1 = lua.safe_script(R"(
v1 = vec2.new()
print('v1:', v1.x, v1.y)
v2 = v1:normalize()
print('v1:', v1.x, v1.y)
print('v2:', v2.x, v2.y)
print(v1, v2)
assert(rawequal(v1, v2))
v1 = nil
collectgarbage()
print(v2) -- v2 points to same, is not destroyed
        )",
         sol::script_pass_on_error);
    REQUIRE(result1.valid());
}
akovachev commented 7 months ago

I think I found the solution, it's not super obvious, but there is a working example:

https://github.com/ThePhD/sol2/blob/develop/examples/source/self_from_lua.cpp

The key is to dig inside the state's stack using this_state (from the example above):

        thing(sol::this_state ts) {
            lua_State* L = ts;
            // references the object that called this function
            // in constructors:
            sol::stack_object selfobj(L, 1);

            // definitely the same
            thing& self = selfobj.as<thing>();
            SOL_ASSERT(&self == this);
        }

        void func(sol::this_state ts) const {
            lua_State* L = ts;
            // references the object that called this function
            // in regular member functions:
            sol::stack_object selfobj(L, 1);
            // "1" is the bottom of the Lua stack
            // 2 is one up, so on and so forth...
            thing& self = selfobj.as<thing>();

            // definitely the same
            SOL_ASSERT(&self == this);
        }