diff --git a/sol/debug.hpp b/sol/debug.hpp new file mode 100644 index 00000000..600b8989 --- /dev/null +++ b/sol/debug.hpp @@ -0,0 +1,53 @@ +// The MIT License (MIT) + +// Copyright (c) 2013 Danny Y., Rapptz + +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef SOL_DEBUG_HPP +#define SOL_DEBUG_HPP + +#include "stack.hpp" +#include + +namespace sol { +namespace debug { + +inline std::string dump_types(lua_State* L) { + std::string visual; + std::size_t size = lua_gettop(L) + 1; + for (std::size_t i = 1; i < size; ++i) { + if (i != 1) + visual += " | "; + visual += type_name(L, stack::get(L, i)); + } + return visual; +} + +inline void print_stack (lua_State* L) { + std::cout << dump_types(L) << std::endl; +} + +inline void print_section (const std::string& message, lua_State* L) { + std::cout << "-- " << message << " -- [ " << dump_types(L) << " ]" << std::endl; +} + +} // debug +} // sol + +#endif // SOL_DEBUG_HPP diff --git a/sol/function_types.hpp b/sol/function_types.hpp index f6b077f5..d993be83 100644 --- a/sol/function_types.hpp +++ b/sol/function_types.hpp @@ -24,10 +24,14 @@ #include "stack.hpp" #include +#include namespace sol { namespace detail { -template +struct ref_call_t {}; +const auto ref_call = ref_call_t{}; + +template ::value> struct functor { T* item; Func invocation; @@ -43,7 +47,7 @@ struct functor { }; template -struct functor { +struct functor { T* item; Func invocation; @@ -56,6 +60,26 @@ struct functor { (member.*invocation)(std::forward(args)...); } }; + +template +struct functor { + T* item; + Func invocation; + + template + functor(FxArgs&&... fxargs): item(nullptr), invocation(std::forward(fxargs)...) {} + + template + void operator()(Arg&& arg, Args&&... args) { + T& member = *item; + (member.*invocation) = std::forward(arg); + } + + R operator()() { + T& member = *item; + return (member.*invocation); + } +}; } // detail @@ -155,6 +179,17 @@ struct base_function { return r; } + static int ref_base_call(lua_State* L, void* inheritancedata) { + if (inheritancedata == nullptr) { + throw error("call from Lua to C++ function has null data"); + } + + base_function* pfx = static_cast(inheritancedata); + base_function& fx = *pfx; + int r = fx(L, detail::ref_call); + return r; + } + static int base_gc(lua_State*, void* udata) { if (udata == nullptr) { throw error("call from lua to C++ gc function with null data"); @@ -176,18 +211,19 @@ struct base_function { return base_gc(L, *pudata); } - template + template struct userdata { static int call(lua_State* L) { // Zero-based template parameter, but upvalues start at 1 - return base_call(L, stack::get(L, i + 1)); + return base_call(L, stack::get(L, I + 1)); + } + + static int ref_call(lua_State* L) { + return ref_base_call(L, stack::get(L, I + 1)); } - }; - template - struct userdata_gc { static int gc(lua_State* L) { - for (std::size_t i = 0; i < N; ++i) { + for (std::size_t i = 0; i < I; ++i) { upvalue_t up = stack::get(L, i + 1); base_function* obj = static_cast(up.value); std::allocator alloc{}; @@ -202,6 +238,10 @@ struct base_function { throw error("failure to call specialized wrapped C++ function from Lua"); } + virtual int operator()(lua_State*, detail::ref_call_t) { + throw error("failure to call reference specialized wrapped C++ function from Lua"); + } + virtual ~base_function() {} }; @@ -289,16 +329,17 @@ struct member_function : public base_function { }; template -struct userdata_function : public base_function { +struct userdata_function_core : public base_function { typedef typename std::remove_pointer::type T; typedef typename std::remove_pointer::type>::type function_type; - typedef function_args_t args_type; - typedef function_return_t return_type; + typedef member_traits traits_type; + typedef typename traits_type::args_type args_type; + typedef typename traits_type::return_type return_type; detail::functor fx; template - userdata_function(FxArgs&&... fxargs): fx(std::forward(fxargs)...) {} + userdata_function_core(FxArgs&&... fxargs): fx(std::forward(fxargs)...) {} template> typename std::enable_if::value, void>::type push(lua_State* L, Return&& r) { @@ -323,7 +364,8 @@ struct userdata_function : public base_function { template int operator()(types, types t, lua_State* L) { - stack::get_call(L, 2, fx, t); + static const std::size_t skew = static_cast(std::is_member_object_pointer::value); + stack::get_call(L, 2 + skew, fx, t); std::ptrdiff_t nargs = sizeof...(Args); lua_pop(L, nargs); return 0; @@ -342,12 +384,128 @@ struct userdata_function : public base_function { int operator()(types<>, types t, lua_State* L) { return (*this)(types(), t, L); } +}; + +template +struct userdata_function : public userdata_function_core { + typedef userdata_function_core base_t; + typedef typename std::remove_pointer::type T; + typedef member_traits traits_type; + typedef typename traits_type::args_type args_type; + typedef typename traits_type::return_type return_type; + + template + userdata_function(FxArgs&&... fxargs): base_t(std::forward(fxargs)...) {} + + template + int fx_call(lua_State* L) { + this->fx.item = detail::get_ptr(stack::get(L, 1)); + if (this->fx.item == nullptr) + throw error("userdata for function call is null: are you using the wrong syntax? (use item:function/variable(...) syntax)"); + return static_cast(*this)(tuple_types(), args_type(), L); + } virtual int operator()(lua_State* L) override { - fx.item = detail::get_ptr(stack::get(L, 1)); - if (fx.item == nullptr) - throw error("userdata for function call is null: are you using wrong call syntax? (use item:function(...) synax)"); - return (*this)(tuple_types(), args_type(), L); + return fx_call(L); + } + + virtual int operator()(lua_State* L, detail::ref_call_t) override { + return fx_call(L); + } +}; + +template +struct userdata_variable_function : public userdata_function_core { + typedef userdata_function_core base_t; + typedef typename std::remove_pointer::type T; + typedef member_traits traits_type; + typedef typename traits_type::args_type args_type; + typedef typename traits_type::return_type return_type; + + template + userdata_variable_function(FxArgs&&... fxargs): base_t(std::forward(fxargs)...) {} + + template + int fx_call (lua_State* L) { + type t = stack::get(L, 1); + switch(t) { + case type::table: + lua_getfield(L, 1, "sol.userdatavalue"); + this->fx.item = detail::get_ptr(stack::get(L, -1)); + lua_pop(L, 1); + break; + default: + this->fx.item = detail::get_ptr(stack::get(L, 1)); + break; + } + if (this->fx.item == nullptr) + throw error("userdata for member variable is null"); + int argcount = lua_gettop(L); + switch (argcount) { + case 2: + return static_cast(*this)(tuple_types(), types<>(), L); + case 3: + return static_cast(*this)(tuple_types(), args_type(), L); + default: + throw error("cannot get/set userdata member variable with inappropriate number of arguments"); + } + + } + + virtual int operator()(lua_State* L) override { + return fx_call(L); + } + + virtual int operator()(lua_State* L, detail::ref_call_t) override { + return fx_call(L); + } +}; + +template +struct userdata_indexing_function : public userdata_function_core { + typedef userdata_function_core base_t; + typedef typename std::remove_pointer::type T; + typedef member_traits traits_type; + typedef typename traits_type::args_type args_type; + typedef typename traits_type::return_type return_type; + + std::string name; + std::unordered_map, bool>> functions; + + template + userdata_indexing_function(std::string name, FxArgs&&... fxargs): base_t(std::forward(fxargs)...), name(std::move(name)) {} + + template + int fx_call (lua_State* L) { + std::string accessor = stack::get(L, 1 - lua_gettop(L)); + auto function = functions.find(accessor); + if (function != functions.end()) { + if (function->second.second) { + stack::push(L, function->second.first.get()); + stack::push(L, &base_function::userdata<0>::call, 1); + return 1; + } + else { + return (*function->second.first)(L); + } + } + if (this->fx.invocation == nullptr) { + std::string err = "invalid indexing \""; + err += accessor; + err += "\" on type: "; + err += name; + throw error(err); + } + this->fx.item = detail::get_ptr(stack::get(L, 1)); + return static_cast(*this)(tuple_types(), args_type(), L); + } + + virtual int operator()(lua_State* L) override { + return fx_call(L); + } + + virtual int operator()(lua_State* L, detail::ref_call_t) override { + return fx_call(L); } }; diff --git a/sol/stack.hpp b/sol/stack.hpp index e1bcf429..c736860c 100644 --- a/sol/stack.hpp +++ b/sol/stack.hpp @@ -462,17 +462,6 @@ inline call_syntax get_call_syntax(lua_State* L, const std::string& meta) { return call_syntax::dot; } -inline std::string dump_types(lua_State* L) { - std::string visual; - std::size_t size = lua_gettop(L) + 1; - for (std::size_t i = 1; i < size; ++i) { - if (i != 1) - visual += " | "; - visual += type_name(L, stack::get(L, i)); - } - return visual; -} - template struct get_return { typedef decltype(get(nullptr)) type; diff --git a/sol/state.hpp b/sol/state.hpp index 024f16ab..3b4aa448 100644 --- a/sol/state.hpp +++ b/sol/state.hpp @@ -79,33 +79,43 @@ public: switch(library) { case lib::base: luaL_requiref(L.get(), "base", luaopen_base, 1); + lua_pop(L.get(), 1); break; case lib::package: luaL_requiref(L.get(), "package", luaopen_package, 1); + lua_pop(L.get(), 1); break; case lib::coroutine: luaL_requiref(L.get(), "coroutine", luaopen_coroutine, 1); + lua_pop(L.get(), 1); break; case lib::string: luaL_requiref(L.get(), "string", luaopen_string, 1); + lua_pop(L.get(), 1); break; case lib::table: luaL_requiref(L.get(), "table", luaopen_table, 1); + lua_pop(L.get(), 1); break; case lib::math: luaL_requiref(L.get(), "math", luaopen_math, 1); + lua_pop(L.get(), 1); break; case lib::bit32: luaL_requiref(L.get(), "bit32", luaopen_bit32, 1); + lua_pop(L.get(), 1); break; case lib::io: luaL_requiref(L.get(), "io", luaopen_io, 1); + lua_pop(L.get(), 1); break; case lib::os: luaL_requiref(L.get(), "os", luaopen_os, 1); + lua_pop(L.get(), 1); break; case lib::debug: luaL_requiref(L.get(), "debug", luaopen_debug, 1); + lua_pop(L.get(), 1); break; case lib::count: break; diff --git a/sol/traits.hpp b/sol/traits.hpp index 059774bb..b5a00e1b 100644 --- a/sol/traits.hpp +++ b/sol/traits.hpp @@ -46,6 +46,14 @@ struct unwrap> { typedef typename std::add_lvalue_reference::type type; }; +template +struct remove_member_pointer; + +template +struct remove_member_pointer { + typedef R type; +}; + template class Templ> struct is_specialization_of : std::false_type { }; template class Templ> @@ -57,6 +65,9 @@ struct are_same : std::true_type { }; template struct are_same : std::integral_constant ::value && are_same::value> { }; +template +using Type = typename T::type; + template using Bool = std::integral_constant; @@ -66,6 +77,9 @@ using Not = Bool; template using If = typename std::conditional::type; +template +using TypeIf = typename std::conditional, Type>::type; + template struct And : Bool {}; @@ -206,6 +220,35 @@ struct function_traits { using arg = typename std::tuple_element::type; }; +namespace detail { +template ::value> +struct member_traits : function_traits { + +}; + +template +struct member_traits { + typedef typename remove_member_pointer::type Arg; + typedef typename remove_member_pointer::type R; + typedef Signature signature_type; + static const bool is_member_function = false; + static const std::size_t arity = 1; + typedef std::tuple arg_tuple_type; + typedef types args_type; + typedef R return_type; + typedef R(function_type)(Arg); + typedef R(*function_pointer_type)(Arg); + typedef R(*free_function_pointer_type)(Arg); + template + using arg = typename std::tuple_element::type; +}; +} // detail + +template +struct member_traits : detail::member_traits { + +}; + struct has_begin_end_impl { template, typename B = decltype(std::declval().begin()), diff --git a/sol/userdata.hpp b/sol/userdata.hpp index 6d18a7e4..34b68f4b 100644 --- a/sol/userdata.hpp +++ b/sol/userdata.hpp @@ -41,18 +41,16 @@ inline std::unique_ptr make_unique(Args&&... args) { template class userdata { private: + typedef std::unordered_map, bool>> function_map_t; + const static std::array metavariablenames; const static std::array metafunctionnames; - std::string luaname; + function_map_t indexmetafunctions, newindexmetafunctions; std::vector functionnames; - std::vector> funcs; - std::vector> ptrfuncs; - std::vector> metafuncs; - std::vector> ptrmetafuncs; - std::vector functiontable; - std::vector ptrfunctiontable; + std::vector> metafunctions; std::vector metafunctiontable; std::vector ptrmetafunctiontable; lua_CFunction cleanup; + std::string luaname; template struct constructor { @@ -84,14 +82,12 @@ private: T* obj = static_cast(udata); match_constructor(L, obj, syntax, argcount - static_cast(syntax), typename identity::type()...); - if (luaL_newmetatable(L, std::addressof(meta[0])) == 1) { lua_pop(L, 1); std::string err = "Unable to get userdata metatable for "; err += meta; throw error(err); } - lua_setmetatable(L, -2); return 1; @@ -108,59 +104,121 @@ private: } }; - template - static int push_upvalues (lua_State* L, TCont&& cont) { - int n = 0; - for (auto& c : cont) { - if (release) - stack::push(L, c.release()); - else - stack::push(L, c.get()); - ++n; - } - return n; + template + void build_cleanup () { + cleanup = &base_function::userdata::gc; } - template - static void push_metatable(lua_State* L, Meta&& meta, Funcs&& funcs, FuncTable&& functable, MetaFuncs&& metafuncs, MetaFuncTable&& metafunctable) { - luaL_newmetatable(L, std::addressof(meta[0])); - if (functable.size() > 1) { - // regular functions accessed through __index semantics - int up = push_upvalues(L, funcs); - luaL_setfuncs(L, functable.data(), up); + template + void build_function_tables(function_map_t*& index, function_map_t*& newindex) { + int extracount = 0; + if (!indexmetafunctions.empty()) { + if (index == nullptr) { + auto idxptr = detail::make_unique>("__index", nullptr); + index = &(idxptr->functions); + functionnames.emplace_back("__index"); + metafunctions.emplace_back(std::move(idxptr)); + std::string& name = functionnames.back(); + metafunctiontable.push_back( { name.c_str(), &base_function::userdata::call } ); + ptrmetafunctiontable.push_back( { name.c_str(), &base_function::userdata::ref_call } ); + ++extracount; + } + auto& idx = *index; + for (auto&& namedfunc : indexmetafunctions ) { + idx.emplace(std::move(namedfunc.first), std::move(namedfunc.second)); + } } - if (metafunctable.size() > 1) { - // meta functions - int up = push_upvalues(L, metafuncs); - luaL_setfuncs(L, metafunctable.data(), up); + if (!newindexmetafunctions.empty()) { + if (newindex == nullptr) { + auto idxptr = detail::make_unique>("__newindex", nullptr); + newindex = &(idxptr->functions); + functionnames.emplace_back("__newindex"); + metafunctions.emplace_back(std::move(idxptr)); + std::string& name = functionnames.back(); + if (extracount > 0) { + metafunctiontable.push_back( { name.c_str(), &base_function::userdata::call } ); + ptrmetafunctiontable.push_back( { name.c_str(), &base_function::userdata::ref_call } ); + } + else { + metafunctiontable.push_back( { name.c_str(), &base_function::userdata::call } ); + ptrmetafunctiontable.push_back( { name.c_str(), &base_function::userdata::ref_call } ); + } + ++extracount; + } + auto& idx = *newindex; + for (auto&& namedfunc : newindexmetafunctions ) { + idx.emplace(std::move(namedfunc.first), std::move(namedfunc.second)); + } + } + switch (extracount) { + case 2: + build_cleanup(); + break; + case 1: + build_cleanup(); + break; + case 0: + default: + build_cleanup(); + break; } - lua_pushvalue(L, -1); - lua_setfield(L, -1, "__index"); } - template - void build_function_tables() {} - - template - void build_function_tables(std::string funcname, Ret TBase::* func, Args&&... args) { + template + bool build_function(std::true_type, function_map_t*&, function_map_t*&, std::string funcname, Ret TBase::* func) { static_assert(std::is_base_of::value, "Any registered function must be part of the class"); typedef typename std::decay::type function_type; - functionnames.push_back(std::move(funcname)); - std::string& name = functionnames.back(); - auto metamethod = std::find(metafunctionnames.begin(), metafunctionnames.end(), name); + indexmetafunctions.emplace(funcname, std::make_pair(detail::make_unique>(func), false)); + newindexmetafunctions.emplace(funcname, std::make_pair(detail::make_unique>(func), false)); + return false; + } + + template + bool build_function(std::false_type, function_map_t*& index, function_map_t*& newindex, std::string funcname, Ret TBase::* func) { + static_assert(std::is_base_of::value, "Any registered function must be part of the class"); + typedef typename std::decay::type function_type; + auto metamethod = std::find(metafunctionnames.begin(), metafunctionnames.end(), funcname); if (metamethod != metafunctionnames.end()) { - metafuncs.emplace_back(detail::make_unique>(std::move(func))); - ptrmetafuncs.emplace_back(detail::make_unique::type>>(std::move(func))); - metafunctiontable.push_back({ name.c_str(), &base_function::userdata::call }); - ptrmetafunctiontable.push_back({ name.c_str(), &base_function::userdata::call }); - build_function_tables(std::forward(args)...); + functionnames.push_back(std::move(funcname)); + std::string& name = functionnames.back(); + auto indexmetamethod = std::find(metavariablenames.begin(), metavariablenames.end(), name); + std::unique_ptr ptr(nullptr); + if (indexmetamethod != metavariablenames.end()) { + auto idxptr = detail::make_unique>(name, func); + switch( std::distance(indexmetamethod, metavariablenames.end()) ) { + case 0: + index = &(idxptr->functions); + break; + case 1: + newindex = &(idxptr->functions); + break; + default: + break; + } + ptr = std::move(idxptr); + } + else { + ptr = detail::make_unique>(func); + } + metafunctions.emplace_back(std::move(ptr)); + metafunctiontable.push_back( { name.c_str(), &base_function::userdata::call } ); + ptrmetafunctiontable.push_back( { name.c_str(), &base_function::userdata::ref_call } ); + return true; + } + indexmetafunctions.emplace(funcname, std::make_pair(detail::make_unique>(func), true )); + newindexmetafunctions.emplace(funcname, std::make_pair(detail::make_unique>(func), true)); + return false; + } + + template + void build_function_tables(function_map_t*& index, function_map_t*& newindex, std::string funcname, Ret TBase::* func, Args&&... args) { + typedef typename std::is_member_object_pointer::type is_variable; + static const std::size_t V = static_cast( !is_variable::value ); + if (build_function(is_variable(), index, newindex, std::move(funcname), func)) { + build_function_tables(index, newindex, std::forward(args)...); } else { - funcs.emplace_back(detail::make_unique>(std::move(func))); - ptrfuncs.emplace_back(detail::make_unique::type>>(std::move(func))); - functiontable.push_back({ name.c_str(), &base_function::userdata::call }); - ptrfunctiontable.push_back({ name.c_str(), &base_function::userdata::call }); - build_function_tables(std::forward(args)...); + build_function_tables(index, newindex, std::forward(args)...); } } @@ -174,22 +232,16 @@ public: template userdata(std::string name, constructors, Args&&... args): luaname(std::move(name)) { functionnames.reserve(sizeof...(args) + 2); - - functiontable.reserve(sizeof...(args)); - ptrfunctiontable.reserve(sizeof...(args)); metafunctiontable.reserve(sizeof...(args)); ptrmetafunctiontable.reserve(sizeof...(args)); - funcs.reserve(sizeof...(args)); - ptrfuncs.reserve(sizeof...(args)); - metafuncs.reserve(sizeof...(args)); - ptrmetafuncs.reserve(sizeof...(args)); - - cleanup = &base_function::userdata_gc::gc; - - build_function_tables<0, 0>(std::forward(args)...); + function_map_t* index = nullptr; + function_map_t* newindex = nullptr; + build_function_tables<0>(index, newindex, std::forward(args)...); + indexmetafunctions.clear(); + newindexmetafunctions.clear(); functionnames.push_back("new"); - functiontable.push_back({ functionnames.back().c_str(), &constructor::construct }); + metafunctiontable.push_back({ functionnames.back().c_str(), &constructor::construct }); functionnames.push_back("__gc"); metafunctiontable.push_back({ functionnames.back().c_str(), &destructor::destruct }); // ptr_functions does not participate in garbage collection/new, @@ -197,10 +249,8 @@ public: // to be references. This makes returns of // `std::vector&` and `std::vector*` work - functiontable.push_back({ nullptr, nullptr }); - metafunctiontable.push_back({ nullptr, nullptr }); - ptrfunctiontable.push_back({ nullptr, nullptr }); - ptrmetafunctiontable.push_back({ nullptr, nullptr }); + metafunctiontable.push_back( { nullptr, nullptr } ); + ptrmetafunctiontable.push_back( { nullptr, nullptr } ); } template @@ -213,27 +263,60 @@ public: void push (lua_State* L) { // push pointer tables first, - // but leave the regular T table on last so it can be linked to a type for usage with `.new(...)` + // but leave the regular T table on last + // so it can be linked to a type for usage with `.new(...)` or `:new(...)` push_metatable(L, userdata_traits::metatable, - ptrfuncs, ptrfunctiontable, - ptrmetafuncs, ptrmetafunctiontable); + metafunctions, ptrmetafunctiontable); + lua_pop(L, 1); + push_metatable(L, userdata_traits::metatable, - funcs, functiontable, - metafuncs, metafunctiontable); + metafunctions, metafunctiontable); + set_global_deleter(L); + } +private: + + template + static void push_metatable(lua_State* L, Meta&& metakey, MetaFuncs&& metafuncs, MetaFuncTable&& metafunctable) { + luaL_newmetatable(L, std::addressof(metakey[0])); + if (metafunctable.size() > 1) { + // regular functions accessed through __index semantics + int up = push_upvalues(L, metafuncs); + luaL_setfuncs(L, metafunctable.data(), up); + } + + } + + void set_global_deleter (lua_State* L) { // Automatic deleter table -- stays alive until lua VM dies // even if the user calls collectgarbage() lua_createtable(L, 0, 0); lua_createtable(L, 0, 1); - int up = push_upvalues(L, funcs); - up += push_upvalues(L, ptrfuncs); - up += push_upvalues(L, metafuncs); - up += push_upvalues(L, ptrmetafuncs); + int up = push_upvalues(L, metafunctions); lua_pushcclosure(L, cleanup, up); lua_setfield(L, -2, "__gc"); lua_setmetatable(L, -2); // gctable name by default has ♻ part of it lua_setglobal(L, std::addressof(userdata_traits::gctable[0])); } + + template + static int push_upvalues (lua_State* L, TCont&& cont) { + int n = 0; + for (auto& c : cont) { + if (release) + stack::push(L, c.release()); + else + stack::push(L, c.get()); + ++n; + } + return n; + } +}; + +template +const std::array userdata::metavariablenames = { + "__index", + "__newindex" }; template diff --git a/tests.cpp b/tests.cpp index 4a3b7b9a..47106ed6 100644 --- a/tests.cpp +++ b/tests.cpp @@ -148,6 +148,18 @@ public: } }; +struct Vec { + float x, y, z; + Vec(float x, float y, float z) : x{x}, y{y}, z{z} {} + float length() { + return sqrtf(x*x + y*y + z*z); + } + Vec normalized() { + float invS = 1 / length(); + return {x * invS, y * invS, z * invS}; + } +}; + TEST_CASE("simple/set_global", "Check if the set_global works properly.") { sol::state lua; @@ -713,19 +725,6 @@ TEST_CASE("tables/issue-number-twenty-five", "Using pointers and references from } TEST_CASE("userdata/issue-number-thirty-five", "using value types created from lua-called C++ code, fixing user-defined types with constructors") { - struct Vec { - float x, y, z; - Vec(float x, float y, float z) : x{x}, y{y}, z{z} {} - float length() { - return sqrtf(x*x + y*y + z*z); - } - - Vec normalized() { - float invS = 1 / length(); - return {x * invS, y * invS, z * invS}; - } - }; - struct Line { Vec p1, p2; Line() : p1{0, 0, 0}, p2{0, 0, 0} {} @@ -753,18 +752,6 @@ TEST_CASE("userdata/issue-number-thirty-five", "using value types created from l } TEST_CASE("userdata/lua-stored-userdata", "ensure userdata values can be stored without keeping userdata object alive") { - struct Vec { - float x, y, z; - Vec(float x, float y, float z) : x{x}, y{y}, z{z} {} - float length() { - return sqrtf(x*x + y*y + z*z); - } - Vec normalized() { - float invS = 1 / length(); - return {x * invS, y * invS, z * invS}; - } - }; - sol::state lua; lua.open_libraries(sol::lib::base); @@ -785,3 +772,31 @@ TEST_CASE("userdata/lua-stored-userdata", "ensure userdata values can be stored REQUIRE_NOTHROW(lua.script("v = Vec.new(1, 2, 3)\n" "print(v:normalized():length())" )); } + +TEST_CASE("userdata/member-variables", "allow table-like accessors to behave as member variables for userdata") { + sol::state lua; + lua.open_libraries(sol::lib::base); + sol::constructors> ctor; + sol::userdata udata("Vec", ctor, + "x", &Vec::x, + "y", &Vec::y, + "z", &Vec::z, + "normalized", &Vec::normalized, + "length", &Vec::length); + lua.set_userdata(udata); + + REQUIRE_NOTHROW(lua.script("v = Vec.new(1, 2, 3)\n" + "v2 = Vec.new(0, 1, 0)\n" + "print(v:length())\n" + "v.x = 2\n" + "v2.y = 2\n" + "print(v.x, v.y, v.z)\n" + "print(v2.x, v2.y, v2.z)\n" + "assert(v.x == 2)\n" + "assert(v2.x == 0)\n" + "assert(v2.y == 2)\n" + "v.x = 3\n" + "local x = v.x\n" + "assert(x == 3)\n" + )); +}