diff --git a/docs/source/api/api-top.rst b/docs/source/api/api-top.rst index eab03bfe..861d05d4 100644 --- a/docs/source/api/api-top.rst +++ b/docs/source/api/api-top.rst @@ -7,7 +7,7 @@ Browse the various function and classes :doc:`Sol<../index>` utilizes to make yo .. toctree:: :caption: Sol API :name: apitoc - :maxdepth: 1 + :maxdepth: 2 compatibility coroutine diff --git a/docs/source/index.rst b/docs/source/index.rst index 6e822cea..651402fc 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -22,7 +22,7 @@ get going: ---------- .. toctree:: - :maxdepth: 1 + :maxdepth: 2 :name: mastertoc tutorial/all-the-things diff --git a/docs/source/tutorial/tutorial-top.rst b/docs/source/tutorial/tutorial-top.rst index 0fa90819..6888006a 100644 --- a/docs/source/tutorial/tutorial-top.rst +++ b/docs/source/tutorial/tutorial-top.rst @@ -7,7 +7,7 @@ Take some time to learn the framework with thse tutorials. But, if you need to g .. toctree:: :caption: Sol Tutorial :name: tutorialtoc - :maxdepth: 1 + :maxdepth: 2 all-the-things getting-started diff --git a/sol/call.hpp b/sol/call.hpp index 67b1fa44..4b477bed 100644 --- a/sol/call.hpp +++ b/sol/call.hpp @@ -61,14 +61,6 @@ namespace sol { } }; - template - inline int destruct(lua_State* L) { - T* obj = stack::get>(L, 1); - std::allocator alloc{}; - alloc.destroy(obj); - return 0; - } - namespace overload_detail { template inline int overload_match_arity(types<>, std::index_sequence<>, std::index_sequence, Match&&, lua_State* L, int, int, Args&&...) { @@ -251,7 +243,7 @@ namespace sol { } return call(L, std::forward(f), *o); #else - object_type& o = static_cast(stack::get(L, 1)); + object_type& o = static_cast(*stack::get>(L, 1)); return call(L, std::forward(f), o); #endif // Safety } @@ -283,7 +275,7 @@ namespace sol { } return call_assign(std::true_type(), L, f, *o); #else - object_type& o = static_cast(stack::get(L, 1)); + object_type& o = static_cast(*stack::get>(L, 1)); return call_assign(std::true_type(), L, f, o); #endif // Safety } @@ -341,7 +333,7 @@ namespace sol { } return call(L, f, *o); #else - object_type& o = static_cast(stack::get(L, 1)); + object_type& o = static_cast(*stack::get>(L, 1)); return call(L, f, o); #endif // Safety } @@ -422,7 +414,7 @@ namespace sol { typedef destructor_wrapper F; static int call(lua_State* L, const F&) { - return destruct(L); + return detail::usertype_alloc_destroy(L); } }; @@ -493,7 +485,7 @@ namespace sol { } object_type& o = *po; #else - object_type& o = static_cast(stack::get(L, 1)); + object_type& o = static_cast(*stack::get>(L, 1)); #endif // Safety typedef typename wrap::returns_list returns_list; typedef typename wrap::caller caller; diff --git a/sol/container_usertype_metatable.hpp b/sol/container_usertype_metatable.hpp new file mode 100644 index 00000000..8fceb051 --- /dev/null +++ b/sol/container_usertype_metatable.hpp @@ -0,0 +1,393 @@ +// The MIT License (MIT) + +// Copyright (c) 2013-2016 Rapptz, ThePhD and contributors + +// 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_CONTAINER_USERTYPE_HPP +#define SOL_CONTAINER_USERTYPE_HPP + +#include "stack.hpp" + +namespace sol { + + namespace detail { + + template + struct has_find { + private: + typedef std::array one; + typedef std::array two; + + template static one test(decltype(&C::find)); + template static two test(...); + + public: + static const bool value = sizeof(test(0)) == sizeof(char); + }; + + template + T& get_first(const T& t) { + return std::forward(t); + } + + template + decltype(auto) get_first(const std::pair& t) { + return t.first; + } + + template >> = meta::enabler> + auto find(C& c, I&& i) { + return c.find(std::forward(i)); + } + + template >> = meta::enabler> + auto find(C& c, I&& i) { + using std::begin; + using std::end; + return std::find_if(begin(c), end(c), [&i](auto&& x) { + return i == get_first(x); + }); + } + + } + + template + struct container_usertype_metatable { + typedef meta::unqualified_t U; + typedef std::size_t K; + typedef typename U::value_type V; + typedef typename U::iterator I; + struct iter { + U& source; + I it; + + iter(U& source, I it) : source(source), it(std::move(it)) {} + }; + + static auto& get_src(lua_State* L) { +#ifdef SOL_SAFE_USERTYPE + auto p = stack::get(L, 1); + if (p == nullptr) { + luaL_error(L, "sol: 'self' argument is nil (pass 'self' as first argument or call on proper type)"); + } + return *p; +#else + return stack::get(L, 1); +#endif + } + + static int real_index_call(lua_State* L) { + auto& src = get_src(L); +#ifdef SOL_SAFE_USERTYPE + auto maybek = stack::check_get(L, 2); + if (maybek) { + using std::begin; + auto it = begin(src); + K k = *maybek; + if (k <= src.size() && k > 0) { + --k; + std::advance(it, k); + return stack::push(L, *it); + } + } + return stack::push(L, nil); +#else + using std::begin; + auto it = begin(src); + K k = stack::get(L, 2); + --k; + std::advance(it, k); + return stack::push(L, *it); +#endif // Safety + } + + static int real_new_index_call_const(std::false_type, lua_State* L) { + luaL_error(L, "sol: cannot write to a const value type"); + return 0; + } + + static int real_new_index_call_const(std::true_type, lua_State* L) { + auto& src = get_src(L); +#ifdef SOL_SAFE_USERTYPE + auto maybek = stack::check_get(L, 2); + if (maybek) { + K k = *maybek; + if (k <= src.size() && k > 0) { + --k; + using std::begin; + auto it = begin(src); + std::advance(it, k); + *it = stack::get(L, 3); + } + } +#else + using std::begin; + auto it = begin(src); + K k = stack::get(L, 2); + --k; + std::advance(it, k); + *it = stack::get(L, 3); +#endif + return 0; + } + + static int real_new_index_call(lua_State* L) { + return real_new_index_call_const(meta::neg>(), L); + } + + static int real_pairs_next_call(lua_State* L) { + using std::end; + iter& i = stack::get>(L, 1); + auto& source = i.source; + auto& it = i.it; + K k = stack::get(L, 2); + if (it == end(source)) { + return 0; + } + int p = stack::push(L, k + 1); + p += stack::push(L, *it); + std::advance(it, 1); + return p; + } + + static int real_pairs_call(lua_State* L) { + auto& src = get_src(L); + using std::begin; + stack::push(L, pairs_next_call); + stack::push>(L, src, begin(src)); + stack::push(L, 0); + return 3; + } + + static int real_length_call(lua_State*L) { + auto& src = get_src(L); + return stack::push(L, src.size()); + } + +#if 0 + static int real_push_back_call(lua_State*L) { + auto& src = get_src(L); + src.push_back(stack::get(L, 2)); + return 0; + } + + static int real_insert_call(lua_State*L) { + using std::begin; + auto& src = get_src(L); + src.insert(std::next(begin(src), stack::get(L, 2)), stack::get(L, 3)); + return 0; + } + + static int push_back_call(lua_State*L) { + return detail::static_trampoline<(&real_length_call)>(L); + } + + static int insert_call(lua_State*L) { + return detail::static_trampoline<(&real_insert_call)>(L); + } +#endif // Sometime later, in a distant universe... + + static int length_call(lua_State*L) { + return detail::static_trampoline<(&real_length_call)>(L); + } + + static int pairs_next_call(lua_State*L) { + return detail::static_trampoline<(&real_pairs_next_call)>(L); + } + + static int pairs_call(lua_State*L) { + return detail::static_trampoline<(&real_pairs_call)>(L); + } + + static int index_call(lua_State*L) { + return detail::static_trampoline<(&real_index_call)>(L); + } + + static int new_index_call(lua_State*L) { + return detail::static_trampoline<(&real_new_index_call)>(L); + } + }; + + template + struct container_usertype_metatable::value>> { + typedef meta::unqualified_t U; + typedef typename U::value_type KV; + typedef typename KV::first_type K; + typedef typename KV::second_type V; + typedef typename U::iterator I; + struct iter { + U& source; + I it; + + iter(U& source, I it) : source(source), it(std::move(it)) {} + }; + + static auto& get_src(lua_State* L) { +#ifdef SOL_SAFE_USERTYPE + auto p = stack::get(L, 1); + if (p == nullptr) { + luaL_error(L, "sol: 'self' argument is nil (pass 'self' as first argument or call on proper type)"); + } + return *p; +#else + return stack::get(L, 1); +#endif + } + + static int real_index_call(lua_State* L) { + auto& src = get_src(L); + auto k = stack::check_get(L, 2); + if (k) { + using std::end; + auto it = detail::find(src, *k); + if (it != end(src)) { + auto& v = *it; + return stack::push(L, v.second); + } + } + return stack::push(L, nil); + } + + static int real_new_index_call_const(std::false_type, lua_State* L) { + luaL_error(L, "sol: cannot write to a const value type"); + return 0; + } + + static int real_new_index_call_const(std::true_type, lua_State* L) { + auto& src = get_src(L); + auto k = stack::check_get(L, 2); + if (k) { + using std::end; + auto it = detail::find(src, *k); + if (it != end(src)) { + auto& v = *it; + v.second = stack::get(L, 3); + } + else { + src.insert(it, { std::move(*k), stack::get(L, 3) }); + } + } + return 0; + } + + static int real_new_index_call(lua_State* L) { + return real_new_index_call_const(meta::neg>(), L); + } + + static int real_pairs_next_call(lua_State* L) { + using std::end; + iter& i = stack::get>(L, 1); + auto& source = i.source; + auto& it = i.it; + K k = stack::get(L, 2); + std::advance(it, 1); + if (it == end(source)) { + return 0; + } + return stack::multi_push_reference(L, it->first, it->second); + } + + static int real_pairs_call(lua_State* L) { + auto& src = get_src(L); + using std::begin; + stack::push(L, pairs_next_call); + stack::push>(L, src, begin(src)); + stack::push(L, 1); + return 3; + } + + static int real_length_call(lua_State*L) { + auto& src = get_src(L); + return stack::push(L, src.size()); + } + + static int length_call(lua_State*L) { + return detail::static_trampoline<(&real_length_call)>(L); + } + + static int pairs_next_call(lua_State*L) { + return detail::static_trampoline<(&real_pairs_next_call)>(L); + } + + static int pairs_call(lua_State*L) { + return detail::static_trampoline<(&real_pairs_call)>(L); + } + + static int index_call(lua_State*L) { + return detail::static_trampoline<(&real_index_call)>(L); + } + + static int new_index_call(lua_State*L) { + return detail::static_trampoline<(&real_new_index_call)>(L); + } + }; + + namespace stack { + + template + struct pusher, meta::neg, std::is_base_of>>>::value>> { + typedef container_usertype_metatable cumt; + template + static int push(lua_State* L, C&& cont) { + auto fx = [&L]() { + const char* metakey = &usertype_traits::metatable[0]; + if (luaL_newmetatable(L, metakey) == 1) { + luaL_Reg reg[] = { + { "__index", &cumt::index_call }, + { "__newindex", &cumt::new_index_call }, + { "__pairs", &cumt::pairs_call }, + { "__len", &cumt::length_call }, + { "__gc", &detail::usertype_alloc_destroy }, + { nullptr, nullptr } + }; + luaL_setfuncs(L, reg, 0); + } + lua_setmetatable(L, -2); + }; + return pusher>{}.push_fx(L, fx, std::forward(cont)); + } + }; + + template + struct pusher>, meta::neg>, std::is_base_of>>>>::value>> { + typedef container_usertype_metatable cumt; + template + static int push(lua_State* L, C&& cont) { + auto fx = [&L]() { + const char* metakey = &usertype_traits*>::metatable[0]; + if (luaL_newmetatable(L, metakey) == 1) { + luaL_Reg reg[] = { + { "__index", &cumt::index_call }, + { "__newindex", &cumt::new_index_call }, + { "__pairs", &cumt::pairs_call }, + { "__len", &cumt::length_call }, + { nullptr, nullptr } + }; + luaL_setfuncs(L, reg, 0); + } + lua_setmetatable(L, -2); + }; + return pusher>>{}.push_fx(L, fx, std::forward(cont)); + } + }; + } // stack + +} // sol + +#endif // SOL_CONTAINER_USERTYPE_HPP diff --git a/sol/function_types.hpp b/sol/function_types.hpp index 1bf14dac..4dedfaba 100644 --- a/sol/function_types.hpp +++ b/sol/function_types.hpp @@ -31,18 +31,6 @@ #include "call.hpp" namespace sol { - template - struct function_arguments { - std::tuple params; - template - function_arguments(Args&&... args) : params(std::forward(args)...) {} - }; - - template , typename... Args> - function_arguments as_function(Args&&... args) { - return function_arguments(std::forward(args)...); - } - namespace stack { template struct pusher> { @@ -318,7 +306,7 @@ namespace sol { template struct pusher>> { static int push(lua_State* L, detail::tagged>) { - lua_CFunction cf = call_detail::destruct; + lua_CFunction cf = detail::user_alloc_destroy; return stack::push(L, cf); } }; diff --git a/sol/stack_core.hpp b/sol/stack_core.hpp index b5061803..94bb42be 100644 --- a/sol/stack_core.hpp +++ b/sol/stack_core.hpp @@ -35,6 +35,10 @@ namespace sol { namespace detail { struct as_reference_tag {}; + template + struct as_pointer_tag {}; + template + struct as_value_tag {}; using special_destruct_func = void(*)(void*); @@ -55,6 +59,25 @@ namespace sol { return 0; } + template + inline int user_alloc_destroy(lua_State* L) { + void* rawdata = lua_touserdata(L, upvalue_index(1)); + T* data = static_cast(rawdata); + std::allocator alloc; + alloc.destroy(data); + return 0; + } + + template + inline int usertype_alloc_destroy(lua_State* L) { + void* rawdata = lua_touserdata(L, 1); + T** pdata = static_cast(rawdata); + T* data = *pdata; + std::allocator alloc{}; + alloc.destroy(data); + return 0; + } + template void reserve(T&, std::size_t) {} @@ -248,15 +271,6 @@ namespace sol { return stack_detail::unchecked_get>(L, index, tracking); } - template - inline int alloc_destroy(lua_State* L) { - void* rawdata = lua_touserdata(L, upvalue_index(1)); - T* data = static_cast(rawdata); - std::allocator alloc; - alloc.destroy(data); - return 0; - } - template struct check_types { template diff --git a/sol/stack_field.hpp b/sol/stack_field.hpp index f9dd9855..220aabd3 100644 --- a/sol/stack_field.hpp +++ b/sol/stack_field.hpp @@ -132,7 +132,6 @@ namespace sol { } }; - template struct field_setter { template diff --git a/sol/stack_get.hpp b/sol/stack_get.hpp index 1c71a9e3..ba485502 100644 --- a/sol/stack_get.hpp +++ b/sol/stack_get.hpp @@ -78,7 +78,7 @@ namespace sol { }; template - struct getter, meta::neg>, meta::neg, std::is_base_of>>>::value>> { + struct getter, std::enable_if_t>::value>> { static T get(lua_State* L, int index, record& tracking) { typedef typename T::value_type V; tracking.use(1); @@ -88,45 +88,53 @@ namespace sol { get_field(L, static_cast(-1), index); int isnum; std::size_t sizehint = static_cast(lua_tointegerx(L, -1, &isnum)); - if (isnum == 0) { + if (isnum != 0) { detail::reserve(arr, sizehint); } lua_pop(L, 1); #if SOL_LUA_VERSION >= 503 // This method is HIGHLY performant over regular table iteration thanks to the Lua API changes in 5.3 - for (lua_Integer i = 0; ; ++i, lua_pop(L, 1)) { - type t = static_cast(lua_geti(L, index, i)); - if (t == type::nil) { - if (i == 0) - continue; - else - break; + for (lua_Integer i = 0; ; i += lua_size::value, lua_pop(L, lua_size::value)) { + for (int vi = 0; vi < lua_size::value; ++vi) { + type t = static_cast(lua_geti(L, index, i + vi)); + if (t == type::nil) { + if (i == 0) { + continue; + } + else { + lua_pop(L, (vi + 1)); + return arr; + } + } } - arr.push_back(stack::get(L, -1)); + arr.push_back(stack::get(L, -lua_size::value)); } - lua_pop(L, 1); #else // Zzzz slower but necessary thanks to the lower version API and missing functions qq - for (lua_Integer i = 0; ; ++i, lua_pop(L, 1)) { - lua_pushinteger(L, i); - lua_gettable(L, index); - type t = type_of(L, -1); - if (t == type::nil) { - if (i == 0) - continue; - else - break; + for (lua_Integer i = 0; ; i += lua_size::value, lua_pop(L, lua_size::value)) { + for (int vi = 0; vi < lua_size::value; ++vi) { + lua_pushinteger(L, i); + lua_gettable(L, index); + type t = type_of(L, -1); + if (t == type::nil) { + if (i == 0) { + continue; + } + else { + lua_pop(L, (vi + 1)); + return arr; + } + } } arr.push_back(stack::get(L, -1)); } - lua_pop(L, 1); #endif return arr; } }; template - struct getter, meta::has_key_value_pair, meta::neg, std::is_base_of>>>::value>> { + struct getter, std::enable_if_t>::value>> { static T get(lua_State* L, int index, record& tracking) { typedef typename T::value_type P; typedef typename P::first_type K; diff --git a/sol/stack_push.hpp b/sol/stack_push.hpp index 2462bff1..89e0df4f 100644 --- a/sol/stack_push.hpp +++ b/sol/stack_push.hpp @@ -33,10 +33,10 @@ namespace sol { namespace stack { - template - struct pusher { - template - static int push_keyed(lua_State* L, K&& k, Args&&... args) { + template + struct pusher> { + template + static int push_fx(lua_State* L, F&& f, Args&&... args) { // Basically, we store all user-data like this: // If it's a movable/copyable value (no std::ref(x)), then we store the pointer to the new // data in the first sizeof(T*) bytes, and then however many bytes it takes to @@ -48,30 +48,44 @@ namespace sol { referencereference = allocationtarget; std::allocator alloc{}; alloc.construct(allocationtarget, std::forward(args)...); - luaL_newmetatable(L, &k[0]); - lua_setmetatable(L, -2); + f(); return 1; } + template + static int push_keyed(lua_State* L, K&& k, Args&&... args) { + return push_fx(L, [&L, &k]() { + luaL_newmetatable(L, &k[0]); + lua_setmetatable(L, -2); + }, std::forward(args)...); + } + template static int push(lua_State* L, Args&&... args) { return push_keyed(L, usertype_traits::metatable, std::forward(args)...); } }; - template - struct pusher { - template - static int push_keyed(lua_State* L, K&& k, T* obj) { + template + struct pusher> { + template + static int push_fx(lua_State* L, F&& f, T* obj) { if (obj == nullptr) return stack::push(L, nil); T** pref = static_cast(lua_newuserdata(L, sizeof(T*))); *pref = obj; - luaL_newmetatable(L, &k[0]); - lua_setmetatable(L, -2); + f(); return 1; } + template + static int push_keyed(lua_State* L, K&& k, T* obj) { + return push_fx(L, [&L, &k]() { + luaL_newmetatable(L, &k[0]); + lua_setmetatable(L, -2); + }, obj); + } + static int push(lua_State* L, T* obj) { return push_keyed(L, usertype_traits*>::metatable, obj); } @@ -85,6 +99,22 @@ namespace sol { } }; + template + struct pusher { + template + static int push(lua_State* L, Args&&... args) { + return pusher>{}.push(L, std::forward(args)...); + } + }; + + template + struct pusher>, meta::neg>, std::is_base_of>>>>::value>> { + template + static int push(lua_State* L, Args&&... args) { + return pusher>{}.push(L, std::forward(args)...); + } + }; + template struct pusher::value>> { typedef typename unique_usertype_traits::type P; @@ -160,13 +190,37 @@ namespace sol { }; template - struct pusher, meta::neg>, meta::neg, std::is_base_of>>>::value>> { - static int push(lua_State* L, const T& cont) { + struct pusher, std::enable_if_t>>::value>> { + static int push(lua_State* L, const as_table_t& tablecont) { + auto& cont = detail::deref(detail::unwrap(tablecont.source)); lua_createtable(L, static_cast(cont.size()), 0); int tableindex = lua_gettop(L); - unsigned index = 1; - for (auto&& i : cont) { - set_field(L, index++, i, tableindex); + std::size_t index = 1; + for (const auto& i : cont) { +#if SOL_LUA_VERSION >= 503 + int p = stack::push(L, i); + for (int pi = 0; pi < p; ++pi) { + lua_seti(L, tableindex, static_cast(index++)); + } +#else + lua_pushinteger(L, static_cast(index)); + int p = stack::push(L, i); + if (p == 1) { + ++index; + lua_settable(L, tableindex); + } + else { + int firstindex = tableindex + 1 + 1; + for (int pi = 0; pi < p; ++pi) { + stack::push(L, index); + lua_pushvalue(L, firstindex); + lua_settable(L, tableindex); + ++index; + ++firstindex; + } + lua_pop(L, 1 + p); + } +#endif } set_field(L, -1, cont.size()); return 1; @@ -174,11 +228,12 @@ namespace sol { }; template - struct pusher, meta::has_key_value_pair, meta::neg, std::is_base_of>>>::value>> { - static int push(lua_State* L, const T& cont) { + struct pusher, std::enable_if_t>>::value>> { + static int push(lua_State* L, const as_table_t& tablecont) { + auto& cont = detail::deref(detail::unwrap(tablecont.source)); lua_createtable(L, static_cast(cont.size()), 0); int tableindex = lua_gettop(L); - for (auto&& pair : cont) { + for (const auto& pair : cont) { set_field(L, pair.first, pair.second, tableindex); } return 1; @@ -292,7 +347,7 @@ namespace sol { std::allocator alloc; alloc.construct(data, std::forward(args)...); if (with_meta) { - lua_CFunction cdel = stack_detail::alloc_destroy; + lua_CFunction cdel = detail::user_alloc_destroy; // Make sure we have a plain GC set for this data if (luaL_newmetatable(L, name) != 0) { lua_pushlightuserdata(L, rawdata); diff --git a/sol/table_core.hpp b/sol/table_core.hpp index 49c117d0..059a08da 100644 --- a/sol/table_core.hpp +++ b/sol/table_core.hpp @@ -317,7 +317,8 @@ namespace sol { size_t size() const { auto pp = stack::push_pop(*this); - return lua_rawlen(base_t::lua_state(), -1); + lua_len(base_t::lua_state(), -1); + return stack::pop(base_t::lua_state()); } bool empty() const { diff --git a/sol/traits.hpp b/sol/traits.hpp index c5a26687..e2b44fa1 100644 --- a/sol/traits.hpp +++ b/sol/traits.hpp @@ -127,6 +127,9 @@ namespace sol { constexpr const auto enabler = enable_t::_; + template + using disable_if_t = std::enable_if_t; + template using enable = std::enable_if_t::value, enable_t>; diff --git a/sol/types.hpp b/sol/types.hpp index a18fc116..56662a2d 100644 --- a/sol/types.hpp +++ b/sol/types.hpp @@ -259,6 +259,34 @@ namespace sol { return closure(f, std::forward(args)...); } + template + struct function_arguments { + std::tuple params; + template + function_arguments(Args&&... args) : params(std::forward(args)...) {} + }; + + template , typename... Args> + function_arguments as_function(Args&&... args) { + return function_arguments(std::forward(args)...); + } + + template + struct as_table_t { + T source; + template + as_table_t(Args&&... args) : source(std::forward(args)...) {} + + operator std::add_lvalue_reference_t () { + return source; + } + }; + + template + as_table_t as_table(T&& container) { + return as_table_t(std::forward(container)); + } + struct this_state { lua_State* L; operator lua_State* () const { @@ -602,17 +630,6 @@ namespace sol { template <> struct lua_type_of : std::integral_constant {}; - template - struct lua_type_of, - meta::neg, - std::is_base_of - >> - >::value - >> : std::integral_constant {}; - template class V, typename... Args> struct accumulate : std::integral_constant {}; diff --git a/sol/usertype.hpp b/sol/usertype.hpp index 62da01a7..dfe9cd0a 100644 --- a/sol/usertype.hpp +++ b/sol/usertype.hpp @@ -25,6 +25,7 @@ #include "stack.hpp" #include "usertype_metatable.hpp" #include "simple_usertype_metatable.hpp" +#include "container_usertype_metatable.hpp" #include namespace sol { diff --git a/test_containers.cpp b/test_containers.cpp new file mode 100644 index 00000000..ebc92863 --- /dev/null +++ b/test_containers.cpp @@ -0,0 +1,247 @@ +#define SOL_CHECK_ARGUMENTS + +#include +#include + +#include +#include +#include +#include +#include + +std::vector test_table_return_one() { + return{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; +} + +std::vector> test_table_return_two() { + return{ { "one", 1 },{ "two", 2 },{ "three", 3 } }; +} + +std::map test_table_return_three() { + return{ { "name", "Rapptz" },{ "friend", "ThePhD" },{ "project", "sol" } }; +} + +TEST_CASE("containers/returns", "make sure that even references to vectors are being serialized as tables") { + sol::state lua; + std::vector v{ 1, 2, 3 }; + lua.set_function("f", [&]() -> std::vector& { + return v; + }); + lua.script("x = f()"); + sol::object x = lua["x"]; + sol::type xt = x.get_type(); + REQUIRE(xt == sol::type::userdata); + sol::table t = x; + bool matching; + matching = t[1] == 1; + REQUIRE(matching); + matching = t[2] == 2; + REQUIRE(matching); + matching = t[3] == 3; + REQUIRE(matching); +} + +TEST_CASE("containers/vector_roundtrip", "make sure vectors can be round-tripped") { + sol::state lua; + std::vector v{ 1, 2, 3 }; + lua.set_function("f", [&]() -> std::vector& { + return v; + }); + lua.script("x = f()"); + std::vector x = lua["x"]; + bool areequal = x == v; + REQUIRE(areequal); +} + +TEST_CASE("containers/list_roundtrip", "make sure lists can be round-tripped") { + sol::state lua; + std::list v{ 1, 2, 3 }; + lua.set_function("f", [&]() -> std::list& { + return v; + }); + lua.script("x = f()"); + std::list x = lua["x"]; + bool areequal = x == v; + REQUIRE(areequal); +} + +TEST_CASE("containers/map_roundtrip", "make sure maps can be round-tripped") { + sol::state lua; + std::map v{ { "a", 1 },{ "b", 2 },{ "c", 3 } }; + lua.set_function("f", [&]() -> std::map& { + return v; + }); + lua.script("x = f()"); + std::map x = lua["x"]; + bool areequal = x == v; + REQUIRE(areequal); +} + +TEST_CASE("containers/unordered_map_roundtrip", "make sure unordered_maps can be round-tripped") { + sol::state lua; + std::unordered_map v{ { "a", 1 },{ "b", 2 },{ "c", 3 } }; + lua.set_function("f", [&]() -> std::unordered_map& { + return v; + }); + lua.script("x = f()"); + std::unordered_map x = lua["x"]; + bool areequal = x == v; + REQUIRE(areequal); +} + +TEST_CASE("containers/custom-usertype", "make sure container usertype metatables can be overridden") { + typedef std::unordered_map bark; + + sol::state lua; + lua.open_libraries(); + lua.new_usertype("bark", + "something", [](const bark& b) { + INFO("It works: " << b.at(24)); + }, + "size", &bark::size, + "at", sol::resolve(&bark::at), + "clear", &bark::clear + ); + bark obj{ { 24, 50 } }; + lua.set("a", &obj); + REQUIRE_NOTHROW(lua.script("assert(a:at(24) == 50)")); + REQUIRE_NOTHROW(lua.script("a:something()")); + lua.set("a", obj); + REQUIRE_NOTHROW(lua.script("assert(a:at(24) == 50)")); + REQUIRE_NOTHROW(lua.script("a:something()")); +} + +TEST_CASE("containers/const-serialization-kvp", "make sure const keys / values are respected") { + typedef std::map bark; + + sol::state lua; + lua.open_libraries(); + bark obj{ { 24, 50 } }; + lua.set("a", &obj); + REQUIRE_NOTHROW(lua.script("assert(a[24] == 50)")); + REQUIRE_THROWS(lua.script("a[24] = 51")); + REQUIRE_NOTHROW(lua.script("assert(a[24] == 50)")); +} + +TEST_CASE("containers/basic-serialization", "make sure containers are turned into proper userdata and have basic hooks established") { + typedef std::vector woof; + sol::state lua; + lua.open_libraries(); + lua.set("b", woof{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }); + REQUIRE_NOTHROW( + lua.script("for k, v in pairs(b) do assert(k == v) end"); + ); + woof w{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }; + lua.set("b", w); + REQUIRE_NOTHROW( + lua.script("for k, v in pairs(b) do assert(k == v) end"); + ); + lua.set("b", &w); + REQUIRE_NOTHROW( + lua.script("for k, v in pairs(b) do assert(k == v) end"); + ); + lua.set("b", std::ref(w)); + REQUIRE_NOTHROW( + lua.script("for k, v in pairs(b) do assert(k == v) end"); + ); +} + +#if 0 // glibc is a fuccboi +TEST_CASE("containers/const-serialization", "make sure containers are turned into proper userdata and the basic hooks respect const-ness") { + typedef std::vector woof; + sol::state lua; + lua.open_libraries(); + lua.set("b", woof{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }); + REQUIRE_NOTHROW( + lua.script("for k, v in pairs(b) do assert(k == v) end"); + ); + REQUIRE_THROWS(lua.script("b[1] = 20")); +} +#endif // Fuck you, glibc + +TEST_CASE("containers/table-serialization", "ensure types can be serialized as tables still") { + typedef std::vector woof; + sol::state lua; + lua.open_libraries(); + lua.set("b", sol::as_table(woof{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 })); + REQUIRE_NOTHROW( + lua.script("for k, v in ipairs(b) do assert(k == v) end"); + ); + woof w{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }; + lua.set("b", sol::as_table(w)); + REQUIRE_NOTHROW( + lua.script("for k, v in ipairs(b) do assert(k == v) end"); + ); + lua.set("b", sol::as_table(&w)); + REQUIRE_NOTHROW( + lua.script("for k, v in ipairs(b) do assert(k == v) end"); + ); + lua.set("b", sol::as_table(std::ref(w))); + REQUIRE_NOTHROW( + lua.script("for k, v in ipairs(b) do assert(k == v) end"); + ); +} + +TEST_CASE("containers/const-correctness", "usertype metatable names should reasonably ignore const attributes") { + struct Vec { + int x, y, z; + }; + + sol::state lua; + lua.open_libraries(sol::lib::base); + lua.new_usertype("Vec", "x", &Vec::x, "y", &Vec::y, "z", &Vec::z); + + Vec vec; + vec.x = 1; + vec.y = 2; + vec.z = -3; + + std::vector foo; + foo.push_back(vec); + + std::vector bar; + bar.push_back(&vec); + + lua.script(R"( +func = function(vecs) + for i, vec in pairs(vecs) do + print(i, ":", vec.x, vec.y, vec.z) + end +end +)"); + + REQUIRE_NOTHROW({ + lua["func"](foo); + lua["func"](bar); + }); +} + +TEST_CASE("containers/arbitrary-creation", "userdata and tables should be usable from standard containers") { + sol::state lua; + lua.open_libraries(sol::lib::base); + lua.set_function("test_one", test_table_return_one); + lua.set_function("test_two", test_table_return_two); + lua.set_function("test_three", test_table_return_three); + + REQUIRE_NOTHROW(lua.script("a = test_one()")); + REQUIRE_NOTHROW(lua.script("b = test_two()")); + REQUIRE_NOTHROW(lua.script("c = test_three()")); + + REQUIRE_NOTHROW(lua.script("assert(#a == 10, 'error')")); + REQUIRE_NOTHROW(lua.script("assert(a[3] == 3, 'error')")); + REQUIRE_NOTHROW(lua.script("assert(b.one == 1, 'error')")); + REQUIRE_NOTHROW(lua.script("assert(b.three == 3, 'error')")); + REQUIRE_NOTHROW(lua.script("assert(c.name == 'Rapptz', 'error')")); + REQUIRE_NOTHROW(lua.script("assert(c.project == 'sol', 'error')")); + + sol::table a = lua.get("a"); + sol::table b = lua.get("b"); + sol::table c = lua.get("c"); + + REQUIRE(a.size() == 10ULL); + REQUIRE(a.get(3) == 3); + REQUIRE(b.get("one") == 1); + REQUIRE(b.get("three") == 3); + REQUIRE(c.get("name") == "Rapptz"); + REQUIRE(c.get("project") == "sol"); +} diff --git a/test_customizations.cpp b/test_customizations.cpp index 74d7a25d..dc15a920 100644 --- a/test_customizations.cpp +++ b/test_customizations.cpp @@ -3,6 +3,9 @@ #include #include +#include +#include + struct two_things { int a; bool b; diff --git a/test_tables.cpp b/test_tables.cpp index f50d7da2..bb5a8cbf 100644 --- a/test_tables.cpp +++ b/test_tables.cpp @@ -2,14 +2,12 @@ #include #include + #include #include #include -#include #include -#include -#include -#include + #include "test_stack_guard.hpp" std::string free_function() { @@ -17,18 +15,6 @@ std::string free_function() { return "test"; } -std::vector test_table_return_one() { - return{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; -} - -std::vector> test_table_return_two() { - return{ { "one", 1 },{ "two", 2 },{ "three", 3 } }; -} - -std::map test_table_return_three() { - return{ { "name", "Rapptz" },{ "friend", "ThePhD" },{ "project", "sol" } }; -} - struct object { std::string operator() () { INFO("member_test()"); @@ -289,37 +275,6 @@ TEST_CASE("tables/iterators", "Testing the use of iteratrs to get values from a REQUIRE(iterations == tablesize); } -TEST_CASE("tables/arbitrary-creation", "tables should be created from standard containers") { - sol::state lua; - lua.open_libraries(sol::lib::base); - lua.set_function("test_one", test_table_return_one); - lua.set_function("test_two", test_table_return_two); - lua.set_function("test_three", test_table_return_three); - - REQUIRE_NOTHROW(lua.script("a = test_one()")); - REQUIRE_NOTHROW(lua.script("b = test_two()")); - REQUIRE_NOTHROW(lua.script("c = test_three()")); - - REQUIRE_NOTHROW(lua.script("assert(#a == 10, 'error')")); - REQUIRE_NOTHROW(lua.script("assert(a[3] == 3, 'error')")); - REQUIRE_NOTHROW(lua.script("assert(b.one == 1, 'error')")); - REQUIRE_NOTHROW(lua.script("assert(b.three == 3, 'error')")); - REQUIRE_NOTHROW(lua.script("assert(c.name == 'Rapptz', 'error')")); - REQUIRE_NOTHROW(lua.script("assert(c.project == 'sol', 'error')")); - - auto&& a = lua.get("a"); - auto&& b = lua.get("b"); - auto&& c = lua.get("c"); - - REQUIRE(a.size() == 10ULL); - REQUIRE(a.get(3) == 3); - REQUIRE(b.get("one") == 1); - REQUIRE(b.get("three") == 3); - REQUIRE(c.get("name") == "Rapptz"); - REQUIRE(c.get("project") == "sol"); -} - - TEST_CASE("tables/variables", "Check if tables and variables work as intended") { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::os); @@ -555,71 +510,3 @@ TEST_CASE("tables/add", "Basic test to make sure the 'add' feature works") { REQUIRE(val == bigvec[i]); } } - -TEST_CASE("tables/returns", "make sure that even references to vectors are being serialized as tables") { - sol::state lua; - std::vector v{ 1, 2, 3 }; - lua.set_function("f", [&]() -> std::vector& { - return v; - }); - lua.script("x = f()"); - sol::object x = lua["x"]; - sol::type xt = x.get_type(); - REQUIRE(xt == sol::type::table); - sol::table t = x; - bool matching; - matching = t[1] == 1; - REQUIRE(matching); - matching = t[2] == 2; - REQUIRE(matching); - matching = t[3] == 3; - REQUIRE(matching); -} - -TEST_CASE("tables/vector_roundtrip", "make sure vectors can be round-tripped") { - sol::state lua; - std::vector v{ 1, 2, 3 }; - lua.set_function("f", [&]() -> std::vector& { - return v; - }); - lua.script("x = f()"); - std::vector x = lua["x"]; - bool areequal = x == v; - REQUIRE(areequal); -} - -TEST_CASE("tables/list_roundtrip", "make sure lists can be round-tripped") { - sol::state lua; - std::list v{ 1, 2, 3 }; - lua.set_function("f", [&]() -> std::list& { - return v; - }); - lua.script("x = f()"); - std::list x = lua["x"]; - bool areequal = x == v; - REQUIRE(areequal); -} - -TEST_CASE("tables/map_roundtrip", "make sure maps can be round-tripped") { - sol::state lua; - std::map v{ { "a", 1 },{ "b", 2 },{ "c", 3 } }; - lua.set_function("f", [&]() -> std::map& { - return v; - }); - lua.script("x = f()"); - std::map x = lua["x"]; - bool areequal = x == v; - REQUIRE(areequal); -} - -TEST_CASE("tables/unordered_map_roundtrip", "make sure unordered_maps can be round-tripped") { - sol::state lua; - std::unordered_map v{ { "a", 1 },{ "b", 2 },{ "c", 3 } }; - lua.set_function("f", [&]() -> std::unordered_map& { - return v; - }); - lua.script("x = f()"); - std::unordered_map x = lua["x"]; - bool areequal = x == v; - REQUIRE(areequal); -} diff --git a/test_usertypes.cpp b/test_usertypes.cpp index 4c92cf0a..16413e01 100644 --- a/test_usertypes.cpp +++ b/test_usertypes.cpp @@ -1203,40 +1203,6 @@ TEST_CASE("usertype/double-deleter-guards", "usertype metatables internally must }); } -TEST_CASE("usertype/const-correctness", "usertype metatable names should reasonably ignore const attributes") { - struct Vec { - int x, y, z; - }; - - sol::state lua; - lua.open_libraries(sol::lib::base); - lua.new_usertype("Vec", "x", &Vec::x, "y", &Vec::y, "z", &Vec::z); - - Vec vec; - vec.x = 1; - vec.y = 2; - vec.z = -3; - - std::vector foo; - foo.push_back(vec); - - std::vector bar; - bar.push_back(&vec); - - lua.script(R"( -func = function(vecs) - for i, vec in ipairs(vecs) do - print(i, ":", vec.x, vec.y, vec.z) - end -end -)"); - - REQUIRE_NOTHROW({ - lua["func"](foo); - lua["func"](bar); - }); -} - TEST_CASE("usertype/vars", "usertype vars can bind various class items") { static int muh_variable = 25; static int through_variable = 10;