sol2 icon indicating copy to clipboard operation
sol2 copied to clipboard

Accessing Lua self object from a C++ function

Open akovachev opened this issue 2 years ago • 4 comments

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).

akovachev avatar Dec 03 '23 08:12 akovachev

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.

Rochet2 avatar Dec 03 '23 10:12 Rochet2

@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 avatar Dec 03 '23 12:12 akovachev

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 avatar Dec 03 '23 16:12 akovachev

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);
		}

akovachev avatar Dec 03 '23 17:12 akovachev