#define SOL_ALL_SAFETIES_ON 1 #include #include #include struct thing { int member_variable = 5; double member_function() const { return member_variable / 2.0; } }; #define TEMPLATE_AUTO(x) decltype(x), x // cheap storage: in reality, you'd need to find a // better way of handling this. // or not! It's up to you. static std::unordered_map thing_function_associations; static std::unordered_map thing_variable_associations; void register_thing_type(sol::state& lua) { thing_variable_associations.emplace_hint( thing_variable_associations.cend(), "member_variable", sol::object(lua.lua_state(), sol::in_place, &sol::c_call)); thing_function_associations.emplace_hint( thing_function_associations.cend(), "member_function", sol::object(lua.lua_state(), sol::in_place, &sol::c_call)); struct call_handler { static int lookup_function(lua_State* L) { sol::stack_object source(L, 1); sol::stack_object key(L, 2); if (!source.is()) { return luaL_error(L, "given an incorrect object for this " "call"); } sol::optional maybe_svkey = key.as>(); if (maybe_svkey) { { // functions are different from // variables functions, when obtain with // the syntax obj.f, obj.f(), and // obj:f() must return the function // itself so we just push it realy into // our target auto it = thing_function_associations.find( *maybe_svkey); if (it != thing_function_associations .cend()) { return it->second.push(L); } } { // variables are different than funtions // when someone does `obj.a`, they // expect this __index call (this lookup // function) to return to them the value // itself they're seeing so we call out // lua_CFunction that we serialized // earlier auto it = thing_variable_associations.find( *maybe_svkey); if (it != thing_variable_associations .cend()) { // note that calls generated by // sol2 for member variables expect // the stack ordering to be 2(, 3, // ..., n) -- value(s) 1 -- source // so we destroy the key on the // stack sol::stack::remove(L, 2, 1); lua_CFunction cf = it->second .as(); return cf(L); } } } return sol::stack::push(L, sol::lua_nil); } static int insertion_function(lua_State* L) { sol::stack_object source(L, 1); sol::stack_object key(L, 2); sol::stack_object value(L, 3); if (!source.is()) { return luaL_error(L, "given an incorrect object for this " "call"); } // write to member variables, etc. etc... sol::optional maybe_svkey = key.as>(); if (maybe_svkey) { { // variables are different than funtions // when someone does `obj.a`, they // expect this __index call (this lookup // function) to return to them the value // itself they're seeing so we call out // lua_CFunction that we serialized // earlier auto it = thing_variable_associations.find( *maybe_svkey); if (it != thing_variable_associations .cend()) { // note that calls generated by // sol2 for member variables expect // the stack ordering to be 2(, 3, // ..., n) -- value(s) 1 -- source // so we remove the key value sol::stack::remove(L, 2, 1); lua_CFunction cf = it->second .as(); return cf(L); } else { // write to member variable, maybe // override function if your class // allows for it? (void)value; } } // exercise for reader: // how do you override functions on the // metatable with proper syntax, but error // when the type is an "instance" object? } return 0; } }; lua.new_usertype("thing"); sol::table metatable = lua["thing"]; metatable[sol::meta_method::index] = &call_handler::lookup_function; metatable[sol::meta_method::new_index] = &call_handler::insertion_function; } void unregister_thing_type(sol::state&) { thing_function_associations.clear(); thing_variable_associations.clear(); } int main() { std::cout << "=== metatable with custom-built (static) " "handling ===" << std::endl; sol::state lua; lua.open_libraries(sol::lib::base); // register custom type + storage register_thing_type(lua); lua.script(R"(t = thing.new() print(t.member_variable) print(t:member_function()) t.member_variable = 24 print(t.member_variable) print(t:member_function()) )"); // clear storage unregister_thing_type(lua); std::cout << std::endl; return 0; }