From 0de30f3b3a3853f491ac7d47a5049064c2c14156 Mon Sep 17 00:00:00 2001 From: ThePhD Date: Tue, 13 Sep 2016 12:37:08 -0400 Subject: [PATCH] Upgrading simple_usertype implementation with BREAKING changes. Docs now include that information too. as_function now works with usertypes for limited cases. --- README.md | 2 +- docs/source/api/as_function.rst | 65 ++++-- docs/source/api/simple_usertype.rst | 11 +- docs/source/api/usertype.rst | 3 + docs/source/conf.py | 4 +- docs/source/index.rst | 2 +- sol/call.hpp | 8 + sol/simple_usertype_metatable.hpp | 297 ++++++++++++++++++++++++---- sol/stack_field.hpp | 9 + sol/table_core.hpp | 4 +- sol/types.hpp | 16 +- sol/usertype_metatable.hpp | 130 ++++++------ test_simple_usertypes.cpp | 100 ++++++++-- test_usertypes.cpp | 23 ++- tests.cpp | 2 +- 15 files changed, 529 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index 8dfc40d3..3eb58f6d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Sol 2.12 +## Sol 2.14 [![Build Status](https://travis-ci.org/ThePhD/sol2.svg?branch=develop)](https://travis-ci.org/ThePhD/sol2) [![Documentation Status](https://readthedocs.org/projects/sol2/badge/?version=latest)](http://sol2.readthedocs.io/en/latest/?badge=latest) diff --git a/docs/source/api/as_function.rst b/docs/source/api/as_function.rst index 74be4335..bb058ba5 100644 --- a/docs/source/api/as_function.rst +++ b/docs/source/api/as_function.rst @@ -8,23 +8,60 @@ make sure an object is pushed as a function template , typename... Args> function_argumants as_function ( Args&& ... ); -This function serves the purpose of ensuring that a callable struct (like a lambda) can be passed to the ``set( key, value )`` calls on :ref:`sol::table` and be treated like a function binding instead of a userdata. It is recommended that one uses the :ref:`sol::table::set_function` call instead, but if for some reason one must use `set`, then `as_function` can help ensure a callable struct is handled like a lambda / callable, and not as just a userdata structure. +This function serves the purpose of ensuring that a callable struct (like a lambda) can be passed to the ``set( key, value )`` calls on :ref:`sol::table` and be treated like a function binding instead of a userdata. It is recommended that one uses the :ref:`sol::table::set_function` call instead, but if for some reason one must use ``set``, then ``as_function`` can help ensure a callable struct is handled like a lambda / callable, and not as just a userdata structure. + +This class can also make it so usertypes bind variable types as functions to for usertype bindings. .. code-block:: cpp - struct callable { - int operator()( int a, bool b ) { - return a + b ? 10 : 20; - } - }; + #include + + int main () { + struct callable { + int operator()( int a, bool b ) { + return a + b ? 10 : 20; + } + }; - sol::state lua; - // Binds struct as userdata - lua.set( "not_func", callable() ); - // Binds struct as function - lua.set( "func", sol::as_function( callable() ) ); - // equivalent: lua.set_function( "func", callable() ); - // equivalent: lua["func"] = callable(); + sol::state lua; + // Binds struct as userdata + lua.set( "not_func", callable() ); + // Binds struct as function + lua.set( "func", sol::as_function( callable() ) ); + // equivalent: lua.set_function( "func", callable() ); + // equivalent: lua["func"] = callable(); + } -Note that if you actually want a userdata, but you want it to be callable, you simply need to create a :ref:`sol::table::new_usertype` and then bind the ``"__call"`` metamethod (or just use ``sol::meta_function::call`` :ref:`enumeration`). \ No newline at end of file +Note that if you actually want a userdata, but you want it to be callable, you simply need to create a :ref:`sol::table::new_usertype` and then bind the ``"__call"`` metamethod (or just use ``sol::meta_function::call`` :ref:`enumeration`). + +Here's an example of binding a variable as a function to a usertype: + +.. code-block:: cpp + + #include + + int main () { + class B { + public: + int bvar = 24; + }; + + sol::state lua; + lua.open_libraries(); + lua.new_usertype("B", + // bind as variable + "b", &B::bvar, + // bind as function + "f", sol::as_function(&B::bvar) + ); + + B b; + lua.set("b", &b); + lua.script("x = b:f()"); + lua.script("y = b.b"); + int x = lua["x"]; + int y = lua["y"]; + assert(x == 24); + assert(y == 24); + } diff --git a/docs/source/api/simple_usertype.rst b/docs/source/api/simple_usertype.rst index 7bb0ceaf..2fa49f5b 100644 --- a/docs/source/api/simple_usertype.rst +++ b/docs/source/api/simple_usertype.rst @@ -4,13 +4,6 @@ structures and classes from C++ made available to Lua code (simpler) -------------------------------------------------------------------- -This type is no different from :doc:`regular usertype`, but with the following caveats: +This type is no different from :doc:`regular usertype`, but allows much of its work to be done at runtime instead of compile-time. The goal here was to avoid compiler complaints about too-large usertypes (some individuals needed to register 190+ functions, and the compiler choked from the templated implementation of ``usertype``). As of Sol 2.14, this implementation has been heavily refactored to allow for all the same syntax and uses of usertype to apply here, with no caveats. -* Dot (".") syntax is not natively supported for simple usertypes (e.g., typical member variable / property bindings) - - All member variables become functions of that name and are get/set in Lua with the syntax ``local v = obj:value()`` to get, and ``obj:value(24)`` to set - - :doc:`properties` also become functions, similar to how member variables are treated above - - ``sol::var`` takes the wrapped up type and pushes it directly into that named slot -* Automatic "__index" and "__newindex" handling is not done - - Overriding either of these properties leaves it entirely up to you to handle how you find variables - - This also means *no base class method lookup is done whatsoever*; please specify all base class variables/methods on the class itself - - If you override "__index" or "__newindex", you must perform a raw get on the original table and return a valid function / value if you want it to find the members you already set on the ``simple_usertype`` +Some developers used ``simple_usertype`` to have variables autoamtically be functions. To achieve this behavior, wrap the desired variable into :doc:`sol::as_function`. \ No newline at end of file diff --git a/docs/source/api/usertype.rst b/docs/source/api/usertype.rst index 9bf50eea..d3282cd5 100644 --- a/docs/source/api/usertype.rst +++ b/docs/source/api/usertype.rst @@ -203,6 +203,8 @@ Otherwise, the following is used to specify functions to bind on the specific us - Binds a typical member function or variable to ``"{name}"``. In the case of a member variable or member function, ``type`` must be ``T`` or a base of ``T`` * ``"{name}", sol::readonly( &type::member_variable )`` - Binds a typical variable to ``"{name}"``. Similar to the above, but the variable will be read-only, meaning an error will be generated if anything attemps to write to this variable +* ``"{name}", sol::as_function( &type::member_variable )`` + - Binds a typical variable to ``"{name}"`` *but forces the syntax to be callable like a function*. This produces a getter and a setter accessible by ``obj:name()`` to get and ``obj::name(value)`` to set. * ``"{name}", sol::property( getter_func, setter_func )`` - Binds a typical variable to ``"{name}"``, but gets and sets using the specified setter and getter functions. Not that if you do not pass a setter function, the variable will be read-only. Also not that if you do not pass a getter function, it will be write-only * ``"{name}", sol::var( some_value )`` or ``"{name}", sol::var( std::ref( some_value ) )`` @@ -220,6 +222,7 @@ usertype arguments - simple usertype - Only allowed as the first argument to the usertype constructor, must be accompanied by a ``lua_State*`` - This tag triggers the :doc:`simple usertype` changes / optimizations - Only supported when directly invoking the constructor (e.g. not when calling ``sol::table::new_usertype`` or ``sol::table::new_simple_usertype``) + - Should probably not be used directly. Use ``sol::table::new_usertype`` or ``sol::table::new_simple_usertype`` instead diff --git a/docs/source/conf.py b/docs/source/conf.py index ab3dc308..c23d9038 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -59,9 +59,9 @@ author = 'ThePhD' # built documents. # # The short X.Y version. -version = '2.12' +version = '2.14' # The full version, including alpha/beta/rc tags. -release = '2.12.3' +release = '2.14.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/index.rst b/docs/source/index.rst index 5e45612b..b1ec3a4f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -7,7 +7,7 @@ :target: https://github.com/ThePhD/sol2 :alt: sol2 repository -Sol 2.12 +Sol 2.14 ======== a fast, simple C++ and Lua Binding ---------------------------------- diff --git a/sol/call.hpp b/sol/call.hpp index 4b477bed..d2f2e90e 100644 --- a/sol/call.hpp +++ b/sol/call.hpp @@ -527,6 +527,14 @@ namespace sol { } }; + template + struct lua_call_wrapper, is_index, is_variable, checked, boost, C> { + template + static int call(lua_State* L, F&& f) { + return lua_call_wrapper, is_index, is_variable, stack::stack_detail::default_check_arguments, boost>{}.call(L, std::get<0>(f.params)); + } + }; + template inline int call_wrapped(lua_State* L, Fx&& fx, Args&&... args) { return lua_call_wrapper, is_index, is_variable, stack::stack_detail::default_check_arguments, boost>{}.call(L, std::forward(fx), std::forward(args)...); diff --git a/sol/simple_usertype_metatable.hpp b/sol/simple_usertype_metatable.hpp index 127be267..e8bd2061 100644 --- a/sol/simple_usertype_metatable.hpp +++ b/sol/simple_usertype_metatable.hpp @@ -25,35 +25,157 @@ #include "usertype_metatable.hpp" #include "object.hpp" #include +#include #include namespace sol { + namespace usertype_detail { + struct variable_wrapper { + virtual int index(lua_State* L) = 0; + virtual int new_index(lua_State* L) = 0; + virtual ~variable_wrapper() {}; + }; + + template + struct callable_binding : variable_wrapper { + F fx; + + template + callable_binding(Arg&& arg) : fx(std::forward(arg)) {} + + virtual int index(lua_State* L) override { + return call_detail::call_wrapped(L, fx); + } + + virtual int new_index(lua_State* L) override { + return call_detail::call_wrapped(L, fx); + } + }; + + typedef std::map, std::less<>> variable_map; + typedef std::map> function_map; + + struct simple_map { + 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) {} + }; + + template + inline int simple_core_indexing_call(lua_State* L) { + simple_map& sm = toplevel ? stack::get>(L, upvalue_index(1)) : stack::pop>(L); + variable_map& variables = sm.variables; + function_map& functions = sm.functions; + static const int keyidx = -2 + static_cast(is_index); + if (toplevel) { + if (stack::get(L, keyidx) != type::string) { + lua_CFunction indexingfunc = is_index ? stack::get(L, upvalue_index(2)) : stack::get(L, upvalue_index(3)); + return indexingfunc(L); + } + } + string_detail::string_shim accessor = stack::get(L, keyidx); + auto vit = variables.find(accessor.c_str()); + if (vit != variables.cend()) { + auto& varwrap = *(vit->second); + if (is_index) { + return varwrap.index(L); + } + return varwrap.new_index(L); + } + auto fit = functions.find(accessor.c_str()); + if (fit != functions.cend()) { + auto& func = (fit->second); + return stack::push(L, func); + } + int ret = 0; + bool found = false; + // Otherwise, we need to do propagating calls through the bases + if (is_index) { + sm.indexbaseclasspropogation(L, found, ret, accessor); + } + else { + sm.newindexbaseclasspropogation(L, found, ret, accessor); + } + if (found) { + return ret; + } + if (toplevel) { + lua_CFunction indexingfunc = is_index ? stack::get(L, upvalue_index(2)) : stack::get(L, upvalue_index(3)); + return indexingfunc(L); + } + return -1; + } + + static int simple_real_index_call(lua_State* L) { + return simple_core_indexing_call(L); + } + + static int simple_real_new_index_call(lua_State* L) { + return simple_core_indexing_call(L); + } + + static int simple_index_call(lua_State* L) { + return detail::static_trampoline<(&simple_real_index_call)>(L); + } + + static int simple_new_index_call(lua_State* L) { + return detail::static_trampoline<(&simple_real_new_index_call)>(L); + } + } + struct simple_tag {} const simple{}; template struct simple_usertype_metatable : usertype_detail::registrar { - std::vector> registrations; + public: + usertype_detail::function_map registrations; + usertype_detail::variable_map varmap; object callconstructfunc; - + lua_CFunction indexfunc; + lua_CFunction newindexfunc; + lua_CFunction indexbase; + lua_CFunction newindexbase; + usertype_detail::base_walk indexbaseclasspropogation; + usertype_detail::base_walk newindexbaseclasspropogation; + void* baseclasscheck; + void* baseclasscast; + bool mustindex; + bool secondarymeta; + template >> = meta::enabler> - void add(lua_State* L, N&& n, F&& f) { - registrations.emplace_back(make_object(L, std::forward(n)), make_object(L, as_function(std::forward(f)))); + void add_function(lua_State* L, N&& n, F&& f) { + registrations.emplace(usertype_detail::make_string(std::forward(n)), make_object(L, as_function_reference(std::forward(f)))); } template >> = meta::enabler> + void add_function(lua_State* L, N&& n, F&& f) { + registrations.emplace(usertype_detail::make_string(std::forward(n)), make_object(L, std::forward(f))); + } + + template >> = meta::enabler> void add(lua_State* L, N&& n, F&& f) { - registrations.emplace_back(make_object(L, std::forward(n)), make_object(L, std::forward(f))); + add_function(L, std::forward(n), std::forward(f)); + } + + template >> = meta::enabler> + void add(lua_State*, N&& n, F&& f) { + varmap.emplace(usertype_detail::make_string(std::forward(n)), std::make_unique>>(std::forward(f))); + mustindex = true; + secondarymeta = true; } template void add(lua_State* L, N&& n, constructor_wrapper c) { - registrations.emplace_back(make_object(L, std::forward(n)), make_object(L, detail::tagged>{std::move(c)})); + registrations.emplace(usertype_detail::make_string(std::forward(n)), make_object(L, detail::tagged>{std::move(c)})); } template void add(lua_State* L, N&& n, constructor_list c) { - registrations.emplace_back(make_object(L, std::forward(n)), make_object(L, detail::tagged>{std::move(c)})); + registrations.emplace(usertype_detail::make_string(std::forward(n)), make_object(L, detail::tagged>{std::move(c)})); } template @@ -61,15 +183,37 @@ namespace sol { callconstructfunc = make_object(L, std::forward(f)); } + template + void add(lua_State*, base_classes_tag, bases) { + static_assert(sizeof(usertype_detail::base_walk) <= sizeof(void*), "size of function pointer is greater than sizeof(void*); cannot work on this platform"); + if (sizeof...(Bases) < 1) { + return; + } + mustindex = true; + (void)detail::swallow{ 0, ((detail::has_derived::value = true), 0)... }; + + static_assert(sizeof(void*) <= sizeof(detail::inheritance_check_function), "The size of this data pointer is too small to fit the inheritance checking function: file a bug report."); + static_assert(sizeof(void*) <= sizeof(detail::inheritance_cast_function), "The size of this data pointer is too small to fit the inheritance checking function: file a bug report."); + baseclasscheck = (void*)&detail::inheritance::type_check; + baseclasscast = (void*)&detail::inheritance::type_cast; + indexbaseclasspropogation = usertype_detail::walk_all_bases; + newindexbaseclasspropogation = usertype_detail::walk_all_bases; + } + template simple_usertype_metatable(usertype_detail::verified_tag, std::index_sequence, lua_State* L, Tuple&& args) - : callconstructfunc(nil) { - registrations.reserve(std::tuple_size>::value); + : callconstructfunc(nil), + indexfunc(&usertype_detail::indexing_fail), newindexfunc(&usertype_detail::indexing_fail), + indexbase(&usertype_detail::simple_core_indexing_call), newindexbase(&usertype_detail::simple_core_indexing_call), + indexbaseclasspropogation(usertype_detail::walk_all_bases), newindexbaseclasspropogation(&usertype_detail::walk_all_bases), + baseclasscheck(nullptr), baseclasscast(nullptr), + mustindex(false), secondarymeta(false) { (void)detail::swallow{ 0, (add(L, detail::forward_get(args), detail::forward_get(args)),0)... }; } + private: template simple_usertype_metatable(lua_State* L, usertype_detail::verified_tag v, Args&&... args) : simple_usertype_metatable(v, std::make_index_sequence(), L, std::forward_as_tuple(std::forward(args)...)) {} @@ -109,7 +253,32 @@ namespace sol { struct pusher> { typedef simple_usertype_metatable umt_t; + static usertype_detail::simple_map& make_cleanup(lua_State* L, umt_t& umx) { + static int uniqueness = 0; + std::string uniquegcmetakey = usertype_traits::user_gc_metatable; + // std::to_string doesn't exist in android still, with NDK, so this bullshit + // is necessary + // thanks, Android :v + int appended = snprintf(nullptr, 0, "%d", uniqueness); + std::size_t insertionpoint = uniquegcmetakey.length() - 1; + uniquegcmetakey.append(appended, '\0'); + char* uniquetarget = &uniquegcmetakey[insertionpoint]; + snprintf(uniquetarget, uniquegcmetakey.length(), "%d", uniqueness); + ++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_reference stackvarmap(L, -1); + stack::set_field(L, gcmetakey, stackvarmap); + stackvarmap.pop(); + + stack::get_field(L, gcmetakey); + usertype_detail::simple_map& varmap = stack::pop>(L); + return varmap; + } + static int push(lua_State* L, umt_t&& umx) { + auto& varmap = make_cleanup(L, umx); bool hasequals = false; bool hasless = false; bool haslessequals = false; @@ -130,36 +299,41 @@ namespace sol { } luaL_newmetatable(L, metakey); stack_reference t(L, -1); - for (auto& kvp : umx.registrations) { - if (kvp.first.template is()) { - std::string regname = kvp.first.template as(); - if (regname == name_of(meta_function::equal_to)) { - hasequals = true; - } - else if (regname == name_of(meta_function::less_than)) { - hasless = true; - } - else if (regname == name_of(meta_function::less_than_or_equal_to)) { - haslessequals = true; - } - switch (i) { - case 0: - if (regname == name_of(meta_function::garbage_collect)) { - continue; - } - break; - case 1: - if (regname == name_of(meta_function::garbage_collect)) { - stack::set_field(L, kvp.first, detail::unique_destruct, t.stack_index()); - continue; - } - break; - case 2: - default: - break; - } + for (auto& kvp : varmap.functions) { + auto& first = std::get<0>(kvp); + auto& second = std::get<1>(kvp); + if (first == name_of(meta_function::equal_to)) { + hasequals = true; } - stack::set_field(L, kvp.first, kvp.second, t.stack_index()); + else if (first == name_of(meta_function::less_than)) { + hasless = true; + } + else if (first == name_of(meta_function::less_than_or_equal_to)) { + haslessequals = true; + } + else if (first == name_of(meta_function::index)) { + umx.indexfunc = second.template as(); + } + else if (first == name_of(meta_function::new_index)) { + umx.newindexfunc = second.template as(); + } + switch (i) { + case 0: + if (first == name_of(meta_function::garbage_collect)) { + continue; + } + break; + case 1: + if (first == name_of(meta_function::garbage_collect)) { + stack::set_field(L, first, detail::unique_destruct, t.stack_index()); + continue; + } + break; + case 2: + default: + break; + } + stack::set_field(L, first, second, t.stack_index()); } luaL_Reg opregs[4]{}; int opregsindex = 0; @@ -178,9 +352,38 @@ namespace sol { t.push(); luaL_setfuncs(L, opregs, 0); t.pop(); - // Metatable indexes itself - stack::set_field(L, meta_function::index, t, t.stack_index()); + if (umx.baseclasscheck != nullptr) { + stack::set_field(L, detail::base_class_check_key(), umx.baseclasscheck, t.stack_index()); + } + if (umx.baseclasscast != nullptr) { + stack::set_field(L, detail::base_class_cast_key(), umx.baseclasscast, t.stack_index()); + } + + // Base class propagation features + stack::set_field(L, detail::base_class_index_propogation_key(), umx.indexbase, t.stack_index()); + stack::set_field(L, detail::base_class_new_index_propogation_key(), umx.newindexbase, t.stack_index()); + + if (umx.mustindex) { + // use indexing function + static_assert(sizeof(usertype_detail::base_walk) <= sizeof(void*), "The size of this data pointer is too small to fit the base class index propagation key: file a bug report."); + stack::set_field(L, meta_function::index, + make_closure(&usertype_detail::simple_index_call, + make_light(varmap), + umx.indexfunc, + umx.newindexfunc + ), t.stack_index()); + stack::set_field(L, meta_function::new_index, + make_closure(&usertype_detail::simple_new_index_call, + make_light(varmap), + umx.indexfunc, + umx.newindexfunc + ), t.stack_index()); + } + else { + // Metatable indexes itself + stack::set_field(L, meta_function::index, t, t.stack_index()); + } // metatable on the metatable // for call constructor purposes and such lua_createtable(L, 0, 1); @@ -188,6 +391,20 @@ namespace sol { if (umx.callconstructfunc.valid()) { stack::set_field(L, sol::meta_function::call_function, umx.callconstructfunc, metabehind.stack_index()); } + if (umx.secondarymeta) { + stack::set_field(L, meta_function::index, + make_closure(&usertype_detail::simple_index_call, + make_light(varmap), + umx.indexfunc, + umx.newindexfunc + ), metabehind.stack_index()); + stack::set_field(L, meta_function::new_index, + make_closure(&usertype_detail::simple_new_index_call, + make_light(varmap), + umx.indexfunc, + umx.newindexfunc + ), metabehind.stack_index()); + } stack::set_field(L, metatable_key, metabehind, t.stack_index()); metabehind.pop(); diff --git a/sol/stack_field.hpp b/sol/stack_field.hpp index 220aabd3..eb7bab64 100644 --- a/sol/stack_field.hpp +++ b/sol/stack_field.hpp @@ -38,6 +38,15 @@ namespace sol { } }; + template + struct field_getter { + template + void get(lua_State* L, Key&& key, int tableindex = -2) { + push(L, std::forward(key)); + lua_rawget(L, tableindex); + } + }; + template struct field_getter { void get(lua_State* L, metatable_key_t, int tableindex = -1) { diff --git a/sol/table_core.hpp b/sol/table_core.hpp index 059a08da..663f6181 100644 --- a/sol/table_core.hpp +++ b/sol/table_core.hpp @@ -374,12 +374,12 @@ namespace sol { template>> = meta::enabler> void set_fx(types<>, Key&& key, Fx&& fx, Args&&... args) { - set(std::forward(key), as_function(std::forward(fx), std::forward(args)...)); + set(std::forward(key), as_function_reference(std::forward(fx), std::forward(args)...)); } template void set_resolved_function(Key&& key, Args&&... args) { - set(std::forward(key), as_function>(std::forward(args)...)); + set(std::forward(key), as_function_reference>(std::forward(args)...)); } public: diff --git a/sol/types.hpp b/sol/types.hpp index d6d21247..a0cf548b 100644 --- a/sol/types.hpp +++ b/sol/types.hpp @@ -165,7 +165,7 @@ namespace sol { template struct non_null {}; - template + template struct function_sig {}; struct upvalue_index { @@ -262,12 +262,17 @@ namespace sol { template struct function_arguments { std::tuple params; - template - function_arguments(Args&&... args) : params(std::forward(args)...) {} + template , function_arguments>> = meta::enabler> + function_arguments(Arg&& arg, Args&&... args) : params(std::forward(arg), std::forward(args)...) {} }; template , typename... Args> - function_arguments as_function(Args&&... args) { + function_arguments...> as_function(Args&&... args) { + return function_arguments...>(std::forward(args)...); + } + + template , typename... Args> + function_arguments as_function_reference(Args&&... args) { return function_arguments(std::forward(args)...); } @@ -569,6 +574,9 @@ namespace sol { template struct lua_type_of> : std::integral_constant {}; + template <> + struct lua_type_of : std::integral_constant {}; + template <> struct lua_type_of : std::integral_constant {}; diff --git a/sol/usertype_metatable.hpp b/sol/usertype_metatable.hpp index a41131f7..a75dd981 100644 --- a/sol/usertype_metatable.hpp +++ b/sol/usertype_metatable.hpp @@ -42,6 +42,8 @@ namespace sol { } }; + typedef void(*base_walk)(lua_State*, bool&, int&, string_detail::string_shim&); + inline bool is_indexer(string_detail::string_shim s) { return s == name_of(meta_function::index) || s == name_of(meta_function::new_index); } @@ -74,6 +76,12 @@ namespace sol { return string_detail::string_shim(detail::base_class_cast_key()); } + template + inline std::string make_string(Arg&& arg) { + string_detail::string_shim s = make_shim(arg); + return std::string(s.c_str(), s.size()); + } + template inline luaL_Reg make_reg(N&& n, lua_CFunction f) { luaL_Reg l{ make_shim(std::forward(n)).c_str(), f }; @@ -95,6 +103,57 @@ namespace sol { return luaL_error(L, "sol: attempt to index (set) nil value \"%s\" on userdata (bad (misspelled?) key name or does not exist)", accessor.data()); } + template + static void walk_single_base(lua_State* L, bool& found, int& ret, string_detail::string_shim& accessor) { + if (found) + return; + const char* metakey = &usertype_traits::metatable[0]; + const char* gcmetakey = &usertype_traits::gc_table[0]; + const char* basewalkkey = is_index ? detail::base_class_index_propogation_key() : detail::base_class_new_index_propogation_key(); + + luaL_getmetatable(L, metakey); + if (type_of(L, -1) == type::nil) { + 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); + return; + } + lua_CFunction basewalkfunc = stack::pop(L); + lua_pop(L, 1); + + stack::get_field(L, gcmetakey); + int value = basewalkfunc(L); + if (value > -1) { + found = true; + ret = value; + } + } + + template + static void walk_all_bases(lua_State* L, bool& found, int& ret, string_detail::string_shim& accessor) { + (void)L; + (void)found; + (void)ret; + (void)accessor; + (void)detail::swallow{ 0, (walk_single_base(L, found, ret, accessor), 0)... }; + } + template inline int operator_wrap(lua_State* L) { auto maybel = stack::check_get(L, 1); @@ -179,7 +238,6 @@ namespace sol { typedef std::tuple ...> Tuple; template struct check_binding : is_variable_binding> {}; - typedef void (*base_walk)(lua_State*, bool&, int&, string_detail::string_shim&); Tuple functions; lua_CFunction indexfunc; lua_CFunction newindexfunc; @@ -187,8 +245,8 @@ namespace sol { lua_CFunction callconstructfunc; lua_CFunction indexbase; lua_CFunction newindexbase; - base_walk indexbaseclasspropogation; - base_walk newindexbaseclasspropogation; + usertype_detail::base_walk indexbaseclasspropogation; + usertype_detail::base_walk newindexbaseclasspropogation; void* baseclasscheck; void* baseclasscast; bool mustindex; @@ -256,8 +314,8 @@ namespace sol { static_assert(sizeof(void*) <= sizeof(detail::inheritance_cast_function), "The size of this data pointer is too small to fit the inheritance checking function: file a bug report."); baseclasscheck = (void*)&detail::inheritance::type_check; baseclasscast = (void*)&detail::inheritance::type_cast; - indexbaseclasspropogation = walk_all_bases; - newindexbaseclasspropogation = walk_all_bases; + indexbaseclasspropogation = usertype_detail::walk_all_bases; + newindexbaseclasspropogation = usertype_detail::walk_all_bases; } template , base_classes_tag, call_construction>::value>> @@ -301,7 +359,7 @@ namespace sol { indexfunc(usertype_detail::indexing_fail), newindexfunc(usertype_detail::indexing_fail), destructfunc(nullptr), callconstructfunc(nullptr), indexbase(&core_indexing_call), newindexbase(&core_indexing_call), - indexbaseclasspropogation(walk_all_bases), newindexbaseclasspropogation(walk_all_bases), + indexbaseclasspropogation(usertype_detail::walk_all_bases), newindexbaseclasspropogation(usertype_detail::walk_all_bases), baseclasscheck(nullptr), baseclasscast(nullptr), mustindex(contains_variable() || contains_index()), secondarymeta(contains_variable()), hasequals(false), hasless(false), haslessequals(false) { @@ -328,72 +386,34 @@ namespace sol { ret = real_find_call(idx, L); } - template + 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)... }; + (void)detail::swallow{ 0, (find_call(std::integral_constant(), L, found, ret, accessor), 0)... }; } - template - static void walk_single_base(lua_State* L, bool& found, int& ret, string_detail::string_shim&) { - if (found) - return; - const char* metakey = &usertype_traits::metatable[0]; - const char* gcmetakey = &usertype_traits::gc_table[0]; - const char* basewalkkey = b ? detail::base_class_index_propogation_key() : detail::base_class_new_index_propogation_key(); - - luaL_getmetatable(L, metakey); - if (type_of(L, -1) == type::nil) { - lua_pop(L, 1); - return; - } - stack::get_field(L, basewalkkey); - if (type_of(L, -1) == type::nil) { - lua_pop(L, 2); - return; - } - lua_CFunction basewalkfunc = stack::pop(L); - lua_pop(L, 1); - - stack::get_field(L, gcmetakey); - int value = basewalkfunc(L); - if (value > -1) { - found = true; - ret = value; - } - } - - template - static void walk_all_bases(lua_State* L, bool& found, int& ret, string_detail::string_shim& accessor) { - (void)L; - (void)found; - (void)ret; - (void)accessor; - (void)detail::swallow{ 0, (walk_single_base(L, found, ret, accessor), 0)... }; - } - - template + template static int core_indexing_call(lua_State* L) { usertype_metatable& f = toplevel ? stack::get>(L, upvalue_index(1)) : stack::pop>(L); - static const int keyidx = -2 + static_cast(b); + static const int keyidx = -2 + static_cast(is_index); if (toplevel && stack::get(L, keyidx) != type::string) { - return b ? f.indexfunc(L) : f.newindexfunc(L); + return is_index ? f.indexfunc(L) : f.newindexfunc(L); } string_detail::string_shim accessor = stack::get(L, keyidx); int ret = 0; bool found = false; - f.propogating_call(L, found, ret, accessor); + f.propogating_call(L, found, ret, accessor); if (found) { return ret; } // Otherwise, we need to do propagating calls through the bases - if (b) + if (is_index) f.indexbaseclasspropogation(L, found, ret, accessor); else f.newindexbaseclasspropogation(L, found, ret, accessor); if (found) { return ret; } - return toplevel ? (b ? f.indexfunc(L) : f.newindexfunc(L)) : -1; + return toplevel ? (is_index ? f.indexfunc(L) : f.newindexfunc(L)) : -1; } static int real_index_call(lua_State* L) { @@ -522,15 +542,9 @@ namespace sol { if (um.baseclasscheck != nullptr) { stack::set_field(L, detail::base_class_check_key(), um.baseclasscheck, t.stack_index()); } - else { - stack::set_field(L, detail::base_class_check_key(), nil, t.stack_index()); - } if (um.baseclasscast != nullptr) { stack::set_field(L, detail::base_class_cast_key(), um.baseclasscast, t.stack_index()); } - else { - stack::set_field(L, detail::base_class_cast_key(), nil, t.stack_index()); - } stack::set_field(L, detail::base_class_index_propogation_key(), make_closure(um.indexbase, make_light(um)), t.stack_index()); stack::set_field(L, detail::base_class_new_index_propogation_key(), make_closure(um.newindexbase, make_light(um)), t.stack_index()); diff --git a/test_simple_usertypes.cpp b/test_simple_usertypes.cpp index a97e2c3b..0e13d96a 100644 --- a/test_simple_usertypes.cpp +++ b/test_simple_usertypes.cpp @@ -42,12 +42,12 @@ TEST_CASE("usertypes/simple-usertypes", "Ensure that simple usertypes properly w lua.new_simple_usertype("bark", "fun", &bark::fun, "get", &bark::get, - "var", &bark::var, - "the_marker", &bark::the_marker, - "x", sol::property(&bark::get), - "y", sol::property(&bark::set), - "z", sol::property(&bark::get, &bark::set) - ); + "var", sol::as_function( &bark::var ), + "the_marker", sol::as_function(&bark::the_marker), + "x", sol::overload(&bark::get), + "y", sol::overload(&bark::set), + "z", sol::overload(&bark::get, &bark::set) + ); lua.script("b = bark.new()"); bark& b = lua["b"]; @@ -60,6 +60,7 @@ TEST_CASE("usertypes/simple-usertypes", "Ensure that simple usertypes properly w lua.script("v = b:var()"); int v = lua["v"]; REQUIRE(v == 20); + REQUIRE(b.var == 20); lua.script("m = b:the_marker()"); marker& m = lua["m"]; @@ -124,12 +125,12 @@ TEST_CASE("usertypes/simple-usertypes-constructors", "Ensure that calls with spe sol::constructors, sol::types>(), "fun", sol::protect( &bark::fun ), "get", &bark::get, - "var", &bark::var, + "var", sol::as_function( &bark::var ), "the_marker", &bark::the_marker, - "x", sol::property(&bark::get), - "y", sol::property(&bark::set), - "z", sol::property(&bark::get, &bark::set) - ); + "x", sol::overload(&bark::get), + "y", sol::overload(&bark::set), + "z", sol::overload(&bark::get, &bark::set) + ); lua.script("bx = bark.new(760)"); bark& bx = lua["bx"]; @@ -210,7 +211,7 @@ TEST_CASE("usertype/simple-shared-ptr-regression", "simple usertype metatables s REQUIRE(destroyed == 1); } -TEST_CASE("usertypes/simple=vars", "simple usertype vars can bind various values (no ref)") { +TEST_CASE("usertypes/simple-vars", "simple usertype vars can bind various values (no ref)") { int muh_variable = 10; int through_variable = 25; @@ -222,7 +223,7 @@ TEST_CASE("usertypes/simple=vars", "simple usertype vars can bind various values "straight", sol::var(2), "global", sol::var(muh_variable), "global2", sol::var(through_variable) - ); + ); lua.script(R"( s = test.straight @@ -237,3 +238,76 @@ g2 = test.global2 REQUIRE(g == 10); REQUIRE(g2 == 25); } + +TEST_CASE("simple_usertypes/variable-control", "test to see if usertypes respond to inheritance and variable controls") { + class A { + public: + virtual void a() { throw std::runtime_error("entered base pure virtual implementation"); }; + }; + + class B : public A { + public: + virtual void a() override { } + }; + + class sA { + public: + virtual void a() { throw std::runtime_error("entered base pure virtual implementation"); }; + }; + + class sB : public sA { + public: + virtual void a() override { } + }; + + struct sV { + int a = 10; + int b = 20; + + int get_b() const { + return b + 2; + } + + void set_b(int value) { + b = value; + } + }; + + struct sW : sV {}; + + sol::state lua; + lua.open_libraries(); + + lua.new_usertype("A", "a", &A::a); + lua.new_usertype("B", sol::base_classes, sol::bases()); + lua.new_simple_usertype("sA", "a", &sA::a); + lua.new_simple_usertype("sB", sol::base_classes, sol::bases()); + lua.new_simple_usertype("sV", "a", &sV::a, "b", &sV::b, "pb", sol::property(&sV::get_b, &sV::set_b)); + lua.new_simple_usertype("sW", sol::base_classes, sol::bases()); + + B b; + lua.set("b", &b); + lua.script("b:a()"); + + sB sb; + lua.set("sb", &sb); + lua.script("sb:a()"); + + sV sv; + lua.set("sv", &sv); + lua.script("print(sv.b)assert(sv.b == 20)"); + + sW sw; + lua.set("sw", &sw); + lua.script("print(sw.a)assert(sw.a == 10)"); + lua.script("print(sw.b)assert(sw.b == 20)"); + lua.script("print(sw.pb)assert(sw.pb == 22)"); + lua.script("sw.a = 11"); + lua.script("sw.b = 21"); + lua.script("print(sw.a)assert(sw.a == 11)"); + lua.script("print(sw.b)assert(sw.b == 21)"); + lua.script("print(sw.pb)assert(sw.pb == 23)"); + lua.script("sw.pb = 25"); + lua.script("print(sw.b)assert(sw.b == 25)"); + lua.script("print(sw.pb)assert(sw.pb == 27)"); +} diff --git a/test_usertypes.cpp b/test_usertypes.cpp index d6736388..1ca7b5ef 100644 --- a/test_usertypes.cpp +++ b/test_usertypes.cpp @@ -1418,8 +1418,7 @@ TEST_CASE("usertype/unique_usertype-check", "make sure unique usertypes don't ge }); } -TEST_CASE("usertype/abstract-base-class", "Ensure that abstract base classes and such can be registered") { - +TEST_CASE("usertype/abstract-base-class", "Ensure that abstract base classes and such can be registered") { sol::state lua; lua.new_usertype("A", "a", &abstract_A::a); lua.new_usertype("B", sol::base_classes, sol::bases()); @@ -1427,3 +1426,23 @@ TEST_CASE("usertype/abstract-base-class", "Ensure that abstract base classes and b:a() )"); } + +TEST_CASE("usertype/as_function", "Ensure that variables can be turned into functions by as_function") { + class B { + public: + int bvar = 24; + }; + + sol::state lua; + lua.open_libraries(); + lua.new_usertype("B", "b", &B::bvar, "f", sol::as_function(&B::bvar)); + + B b; + lua.set("b", &b); + lua.script("x = b:f()"); + lua.script("y = b.b"); + int x = lua["x"]; + int y = lua["y"]; + REQUIRE(x == 24); + REQUIRE(y == 24); +} diff --git a/tests.cpp b/tests.cpp index 1b80a169..d79a5989 100644 --- a/tests.cpp +++ b/tests.cpp @@ -615,7 +615,7 @@ TEST_CASE("optional/left-out-args", "Make sure arguments can be left out of opti sol::state lua; lua.open_libraries(sol::lib::base); - // sol::optional needs an argument no matter what + // sol::optional needs an argument no matter what? lua.set_function("func_opt_ret_bool", func_opt_ret_bool); REQUIRE_NOTHROW( lua.script(R"(