diff --git a/sol/simple_usertype_metatable.hpp b/sol/simple_usertype_metatable.hpp index a0360885..d5add85d 100644 --- a/sol/simple_usertype_metatable.hpp +++ b/sol/simple_usertype_metatable.hpp @@ -57,12 +57,13 @@ namespace sol { typedef std::unordered_map function_map; struct simple_map { + const char* metakey; variable_map variables; function_map functions; base_walk indexbaseclasspropogation; base_walk newindexbaseclasspropogation; - simple_map(base_walk index, base_walk newindex, variable_map&& vars, function_map&& funcs) : variables(std::move(vars)), functions(std::move(funcs)), indexbaseclasspropogation(index), newindexbaseclasspropogation(newindex) {} + simple_map(const char* mkey, base_walk index, base_walk newindex, variable_map&& vars, function_map&& funcs) : metakey(mkey), variables(std::move(vars)), functions(std::move(funcs)), indexbaseclasspropogation(index), newindexbaseclasspropogation(newindex) {} }; template @@ -92,6 +93,19 @@ namespace sol { auto& func = (fit->second); return stack::push(L, func); } + // Check table storage first for a method that works + luaL_getmetatable(L, sm.metakey); + if (type_of(L, -1) != type::nil) { + stack::get_field(L, accessor.c_str(), lua_gettop(L)); + if (type_of(L, -1) != type::nil) { + // Woo, we found it? + lua_remove(L, -2); + return 1; + } + lua_pop(L, 1); + } + lua_pop(L, 1); + int ret = 0; bool found = false; // Otherwise, we need to do propagating calls through the bases @@ -234,7 +248,7 @@ namespace sol { simple_usertype_metatable(lua_State* L, usertype_detail::check_destructor_tag, Args&&... args) : simple_usertype_metatable(L, meta::condition, meta::neg>>, usertype_detail::add_destructor_tag, usertype_detail::verified_tag>(), std::forward(args)...) {} public: - simple_usertype_metatable(lua_State* L) : simple_usertype_metatable(meta::condition>, decltype(default_constructor), usertype_detail::check_destructor_tag>(), L) {} + simple_usertype_metatable(lua_State* L) : simple_usertype_metatable(L, meta::condition>, decltype(default_constructor), usertype_detail::check_destructor_tag>()) {} template, @@ -277,7 +291,7 @@ namespace sol { ++uniqueness; const char* gcmetakey = &usertype_traits::gc_table[0]; - stack::push>(L, metatable_key, uniquegcmetakey, umx.indexbaseclasspropogation, umx.newindexbaseclasspropogation, std::move(umx.varmap), std::move(umx.registrations)); + stack::push>(L, metatable_key, uniquegcmetakey, &usertype_traits::metatable[0], umx.indexbaseclasspropogation, umx.newindexbaseclasspropogation, std::move(umx.varmap), std::move(umx.registrations)); stack_reference stackvarmap(L, -1); stack::set_field(L, gcmetakey, stackvarmap); stackvarmap.pop(); diff --git a/sol/usertype_metatable.hpp b/sol/usertype_metatable.hpp index a75dd981..c978aa51 100644 --- a/sol/usertype_metatable.hpp +++ b/sol/usertype_metatable.hpp @@ -31,6 +31,7 @@ #include "inheritance.hpp" #include "raii.hpp" #include "deprecate.hpp" +#include #include namespace sol { @@ -43,6 +44,14 @@ namespace sol { }; typedef void(*base_walk)(lua_State*, bool&, int&, string_detail::string_shim&); + typedef int(*member_search)(lua_State*, void*); + + struct find_call_pair { + member_search first; + member_search second; + + find_call_pair(member_search first, member_search second) : first(first), second(second) {} + }; inline bool is_indexer(string_detail::string_shim s) { return s == name_of(meta_function::index) || s == name_of(meta_function::new_index); @@ -116,19 +125,7 @@ namespace sol { lua_pop(L, 1); return; } - stack::get_field(L, accessor.c_str(), lua_gettop(L)); - if (type_of(L, -1) == type::nil) { - lua_pop(L, 1); - } - else { - // Probably a function. Probably. - // Kick off metatable - lua_remove(L, -2); - // Return the field (which is probably a function) itself - found = true; - ret = 1; - return; - } + stack::get_field(L, basewalkkey); if (type_of(L, -1) == type::nil) { lua_pop(L, 2); @@ -239,6 +236,7 @@ namespace sol { template struct check_binding : is_variable_binding> {}; Tuple functions; + std::unordered_map mapping; lua_CFunction indexfunc; lua_CFunction newindexfunc; lua_CFunction destructfunc; @@ -356,6 +354,7 @@ namespace sol { template > usertype_metatable(Args&&... args) : functions(std::forward(args)...), + mapping(), indexfunc(usertype_detail::indexing_fail), newindexfunc(usertype_detail::indexing_fail), destructfunc(nullptr), callconstructfunc(nullptr), indexbase(&core_indexing_call), newindexbase(&core_indexing_call), @@ -363,32 +362,24 @@ namespace sol { baseclasscheck(nullptr), baseclasscast(nullptr), mustindex(contains_variable() || contains_index()), secondarymeta(contains_variable()), hasequals(false), hasless(false), haslessequals(false) { + mapping.insert( + { { + std::pair( + usertype_detail::make_string(std::get(functions)), + { &usertype_metatable::real_find_call, + &usertype_metatable::real_find_call } + ) + }... } + ); } template - int real_find_call(std::integral_constant, lua_State* L) { - if (is_variable_binding(functions))>::value) { - return real_call_with(L, *this); + static int real_find_call(lua_State* L, void* um) { + auto& f = *static_cast(um); + if (is_variable_binding(f.functions))>::value) { + return real_call_with(L, f); } - return stack::push(L, c_closure(call, stack::push(L, light(*this)))); - } - - template - void find_call(std::integral_constant idx, lua_State* L, bool& found, int& ret, const sol::string_detail::string_shim& accessor) { - if (found) { - return; - } - string_detail::string_shim name = usertype_detail::make_shim(std::get(functions)); - if (accessor != name) { - return; - } - found = true; - ret = real_find_call(idx, L); - } - - template - void propogating_call(lua_State* L, bool& found, int& ret, string_detail::string_shim& accessor) { - (void)detail::swallow{ 0, (find_call(std::integral_constant(), L, found, ret, accessor), 0)... }; + return stack::push(L, c_closure(call, stack::push(L, light(f)))); } template @@ -398,13 +389,15 @@ namespace sol { if (toplevel && stack::get(L, keyidx) != type::string) { return is_index ? f.indexfunc(L) : f.newindexfunc(L); } - string_detail::string_shim accessor = stack::get(L, keyidx); + std::string name = stack::get(L, keyidx); + auto memberit = f.mapping.find(name); + if (memberit != f.mapping.cend()) { + auto& member = is_index ? memberit->second.second : memberit->second.first; + return (member)(L, static_cast(&f)); + } + string_detail::string_shim accessor = name; int ret = 0; bool found = false; - f.propogating_call(L, found, ret, accessor); - if (found) { - return ret; - } // Otherwise, we need to do propagating calls through the bases if (is_index) f.indexbaseclasspropogation(L, found, ret, accessor); diff --git a/test_simple_usertypes.cpp b/test_simple_usertypes.cpp index ffef869d..b5687a2e 100644 --- a/test_simple_usertypes.cpp +++ b/test_simple_usertypes.cpp @@ -367,3 +367,24 @@ TEST_CASE("usertype/simple-factory-constructor-overload-usage", "simple usertype REQUIRE(y3 == 2); REQUIRE(y4 == 3); } + +TEST_CASE("usertype/simple-runtime-append", "allow extra functions to be appended at runtime directly to the metatable itself") { + class A { + }; + + class B : public A { + }; + + sol::state lua; + lua.new_simple_usertype("A"); + lua.new_simple_usertype("B", sol::base_classes, sol::bases()); // OFFTOP: It crashes here because there's no stuff registered in A usertype( is it an issue? ) + lua.set("b", std::make_unique()); + lua["A"]["method"] = []() { return 200; }; + lua.script("x = b.method()"); + lua.script("y = b:method()"); + + int x = lua["x"]; + int y = lua["y"]; + REQUIRE(x == 200); + REQUIRE(y == 200); +}