diff --git a/docs/source/api/simple_usertype.rst b/docs/source/api/simple_usertype.rst index 4b787464..1aefce75 100644 --- a/docs/source/api/simple_usertype.rst +++ b/docs/source/api/simple_usertype.rst @@ -4,10 +4,13 @@ structures and classes from C++ made available to Lua code (simpler) -------------------------------------------------------------------- -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. +This type is no different from :doc:`regular usertype`, but allows much of its work to be done at runtime instead of compile-time. You can reduce compilation times from a plain `usertype` when you have an exceedingly bulky registration listing. + +You can set functions incrementally to reduce compile-time burden with ``simple_usertype`` as well, as shown in `this example`_. Some developers used ``simple_usertype`` to have variables automatically be functions. To achieve this behavior, wrap the desired variable into :doc:`sol::as_function`. -The performance `seems to be good enough`_ to not warn about any implications of having to serialize things at runtime. You do run the risk of using (slightly?) more memory, however, since variables and functions need to be stored differently and separately from the metatable data itself like with a regular ``usertype``. +The performance `seems to be good enough`_ to not warn about any implications of having to serialize things at runtime. You do run the risk of using (slightly?) more memory, however, since variables and functions need to be stored differently and separately from the metatable data itself like with a regular ``usertype``. 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/exceptions. -.. _seems to be good enough: https://github.com/ThePhD/sol2/issues/202#issuecomment-246767629 \ No newline at end of file +.. _seems to be good enough: https://github.com/ThePhD/sol2/issues/202#issuecomment-246767629 +.. _this example: .. _ usertype examples: https://github.com/ThePhD/sol2/blob/develop/examples/usertype_simple.cpp \ No newline at end of file diff --git a/docs/source/tutorial/all-the-things.rst b/docs/source/tutorial/all-the-things.rst index 64aa8676..f2f5ec87 100644 --- a/docs/source/tutorial/all-the-things.rst +++ b/docs/source/tutorial/all-the-things.rst @@ -542,8 +542,7 @@ Note that you can change the data of usertype variables and it will affect thing C++ classes put into Lua ------------------------ -See this :doc:`section here`. - +See this :doc:`section here` and after perhaps see if :doc:`simple usertypes suit your needs<../api/simple_usertype>`. Also check out some `a basic example`_, `special functions`_ and `initializers`_, namespacing ----------- @@ -597,3 +596,8 @@ Some more advanced things you can do/read about: * :doc:`variadic arguments<../api/variadic_args>` in functions with ``sol::variadic_args``. * :doc:`this_state<../api/this_state>` to get the current ``lua_State*``. * :doc:`resolve<../api/resolve>` overloads in case you have overloaded functions; a cleaner casting utility. + +.. _a basic example: https://github.com/ThePhD/sol2/blob/develop/examples/usertype.cpp +.. _special functions: https://github.com/ThePhD/sol2/blob/develop/examples/usertype_special_functions.cpp +.. _initializers: https://github.com/ThePhD/sol2/blob/develop/examples/usertype_initializers.cpp + diff --git a/docs/source/tutorial/cxx-in-lua.rst b/docs/source/tutorial/cxx-in-lua.rst index be64e102..dac47dfb 100644 --- a/docs/source/tutorial/cxx-in-lua.rst +++ b/docs/source/tutorial/cxx-in-lua.rst @@ -61,7 +61,8 @@ It's a fairly minimal class, but we don't want to have to rewrite this with meta p1 = player.new(2) -- p2 is still here from being - -- set with lua["p2"] = player(0); below + -- set with lua["p2"] = player(0); + -- in cpp file local p2shoots = p2:shoot() assert(not p2shoots) -- had 0 ammo @@ -130,4 +131,8 @@ To do this, you bind things using the ``new_usertype`` and ``set_usertype`` meth That script should run fine now, and you can observe and play around with the values. Even more stuff :doc:`you can do<../api/usertype>` is described elsewhere, like initializer functions (private constructors / destructors support), "static" functions callable with ``name.my_function( ... )``, and overloaded member functions. You can even bind global variables (even by reference with ``std::ref``) with ``sol::var``. There's a lot to try out! -This is a powerful way to allow reuse of C++ code from Lua beyond just registering functions, and should get you on your way to having more complex classes and data structures! In the case that you need more customization than just usertypes, however, you can customize Sol to behave more fit to your desires by using the desired :doc:`customization and extension structures`. \ No newline at end of file +This is a powerful way to allow reuse of C++ code from Lua beyond just registering functions, and should get you on your way to having more complex classes and data structures! In the case that you need more customization than just usertypes, however, you can customize Sol to behave more fit to your desires by using the desired :doc:`customization and extension structures`. + +You can check out this code and more complicated code at the `examples directory`_ by looking at the ``usertype_``-prefixed examples. + +.. _examples directory: https://github.com/ThePhD/sol2/tree/develop/examples \ No newline at end of file diff --git a/examples/usertype_initializers.cpp b/examples/usertype_initializers.cpp new file mode 100644 index 00000000..10f1f68c --- /dev/null +++ b/examples/usertype_initializers.cpp @@ -0,0 +1,67 @@ +#define SOL_CHECK_ARGUMENTS +#include +#include +#include +#include + +struct holy { +private: + holy() : data() {} + holy(int value) : data(value) {} + ~holy() {} + +public: + struct deleter { + void operator()(holy* p) const { + destroy(*p); + } + }; + + const int data; + + static std::unique_ptr create() { + std::cout << "creating 'holy' unique_ptr directly and letting sol/Lua handle it" << std::endl; + return std::unique_ptr(new holy(50)); + } + + static void initialize(holy& uninitialized_memory) { + std::cout << "initializing 'holy' userdata at " << static_cast(&uninitialized_memory) << std::endl; + // receive uninitialized memory from Lua: + // properly set it by calling a constructor + // on it + // "placement new" + new (&uninitialized_memory) holy(); + } + + static void destroy(holy& memory_from_lua) { + std::cout << "destroying 'holy' userdata at " << static_cast(&memory_from_lua) << std::endl; + memory_from_lua.~holy(); + } +}; + +int main() { + std::cout << "=== usertype_initializers example ===" << std::endl; + { // additional scope to make usertype destroy earlier + sol::state lua; + lua.open_libraries(); + + lua.new_usertype("holy", + "new", sol::initializers(&holy::initialize), + "create", sol::factories(&holy::create), + sol::meta_function::garbage_collect, sol::destructor(&holy::destroy), + "data", &holy::data + ); + + lua.script(R"( +h1 = holy.create() +h2 = holy.new() +print('h1.data is ' .. h1.data) +print('h2.data is ' .. h2.data) +)"); + holy& h1 = lua["h1"]; + holy& h2 = lua["h2"]; + assert(h1.data == 50); + assert(h2.data == 0); + } + std::cout << std::endl; +} \ No newline at end of file diff --git a/examples/usertype_simple.cpp b/examples/usertype_simple.cpp new file mode 100644 index 00000000..e2dcbfe8 --- /dev/null +++ b/examples/usertype_simple.cpp @@ -0,0 +1,95 @@ +#define SOL_CHECK_ARGUMENTS +#include +#include +#include +#include + +class generator { +private: + int data = 2; + +public: + int get_data() const { return data; } + void set_data(int value) { data = value % 10; } + + std::vector generate_list() { + return { data, data * 2, data * 3, data * 4, data * 5 }; + } +}; + +struct my_data { + int first = 4; + int second = 8; + int third = 12; +}; + +int main() { + std::cout << "=== usertype_simple example ===" << std::endl; + + sol::state lua; + lua.open_libraries(); + + // simple_usertype acts and behaves like + // a regular usertype + lua.new_simple_usertype("my_data", + "first", &my_data::first, + "second", &my_data::second, + "third", &my_data::third + ); + + { + // But, simple_usertype also has a `set` function + // where you can append things to the + // method listing after doing `create_simple_usertype`. + auto generator_registration = lua.create_simple_usertype(); + generator_registration.set("data", sol::property(&generator::get_data, &generator::set_data)); + // you MUST set the usertype after + // creating it + lua.set_usertype("generator", generator_registration); + } + + // Can update a simple_usertype at runtime, after registration + lua["generator"]["generate_list"] = [](generator& self) { return self.generate_list(); }; + // can set 'static methods' (no self) as well + lua["generator"]["get_num"] = []() { return 100; }; + + // Mix it all together! + lua.script(R"( +mdata = my_data.new() + +local g = generator.new() +g.data = mdata.first +list1 = g:generate_list() +g.data = mdata.second +list2 = g:generate_list() +g.data = mdata.third +list3 = g:generate_list() + +print("From lua: ") +for i, v in pairs(list1) do + print("\tlist1[" .. i .. "] = " .. v) +end +for i, v in pairs(list2) do + print("\tlist2[" .. i .. "] = " .. v) +end +for i, v in pairs(list3) do + print("\tlist3[" .. i .. "] = " .. v) +end + +)"); + my_data& mdata = lua["mdata"]; + std::vector& list1 = lua["list1"]; + std::vector& list2 = lua["list2"]; + std::vector& list3 = lua["list3"]; + assert(list1.size() == 5); + assert(list2.size() == 5); + assert(list3.size() == 5); + for (int i = 1; i <= 5; ++i) { + assert(list1[i - 1] == (mdata.first % 10) * i); + assert(list2[i - 1] == (mdata.second % 10) * i); + assert(list3[i - 1] == (mdata.third % 10) * i); + } + + std::cout << std::endl; + return 0; +} \ No newline at end of file diff --git a/examples/usertype_special_functions.cpp b/examples/usertype_special_functions.cpp new file mode 100644 index 00000000..50421036 --- /dev/null +++ b/examples/usertype_special_functions.cpp @@ -0,0 +1,74 @@ +#define SOL_CHECK_ARGUMENTS +#include +#include +#include + +struct vec { + double x; + double y; + + vec() : x(0), y(0) {} + vec(double x, double y) : x(x), y(y) {} + + vec operator-(const vec& right) const { + return vec(x - right.x, y - right.y); + } +}; + +double dot(const vec& left, const vec& right) { + return left.x * right.x + left.x * right.x; +} + +vec operator+(const vec& left, const vec& right) { + return vec(left.x + right.x, left.y + right.y); +} + +int main() { + sol::state lua; + lua.open_libraries(); + + lua.new_usertype("vec", + sol::constructors, sol::types>(), + "dot", &dot, + "norm", [](const vec& self) { double len = std::sqrt(dot(self, self)); return vec(self.x / len, self.y / len); }, + // we use `sol::resolve` because other operator+ can exist + // in the (global) namespace + sol::meta_function::addition, sol::resolve(::operator+), + sol::meta_function::subtraction, &vec::operator- + ); + + lua.script(R"( + v1 = vec.new(1, 0) + v2 = vec.new(0, 1) + -- as "member function" + d1 = v1:dot(v2) + -- as "static" / "free function" + d2 = vec.dot(v1, v2) + assert(d1 == d2) + + -- doesn't matter if + -- bound as free function + -- or member function: + a1 = v1 + v2 + s1 = v1 - v2 +)"); + + vec& a1 = lua["a1"]; + vec& s1 = lua["s1"]; + + assert(a1.x == 1 && a1.y == 1); + assert(s1.x == 1 && s1.y == -1); + + lua["a2"] = lua["a1"]; + lua["a3"] = &a1; + + lua.script(R"( + -- automatic comparison generated for Lua: + -- pointers are equal + assert(a1 == a2) + assert(a1 == a3) + assert(a2 == a3) + )"); + + std::cout << std::endl; +} \ No newline at end of file diff --git a/single/sol/sol.hpp b/single/sol/sol.hpp index bb66b130..1f79f0ae 100644 --- a/single/sol/sol.hpp +++ b/single/sol/sol.hpp @@ -20,8 +20,8 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // This file was generated with a script. -// Generated 2016-09-20 03:39:12.452520 UTC -// This header was generated with sol v2.14.2 (revision 9d52ed4) +// Generated 2016-09-22 11:12:24.469225 UTC +// This header was generated with sol v2.14.2 (revision 77a1ce7) // https://github.com/ThePhD/sol2 #ifndef SOL_SINGLE_INCLUDE_HPP @@ -10019,6 +10019,14 @@ namespace sol { simple_map(const char* mkey, base_walk index, base_walk newindex, variable_map&& vars, function_map&& funcs) : metakey(mkey), variables(std::move(vars)), functions(std::move(funcs)), indexbaseclasspropogation(index), newindexbaseclasspropogation(newindex) {} }; + inline int simple_metatable_newindex(lua_State* L) { + simple_map& sm = stack::get>(L, upvalue_index(1)); + luaL_getmetatable(L, sm.metakey); + stack::set_field(L, stack_reference(L, 2), stack_reference(L, 3), lua_gettop(L)); + lua_settop(L, 0); + return 0; + } + template inline int simple_core_indexing_call(lua_State* L) { simple_map& sm = toplevel ? stack::get>(L, upvalue_index(1)) : stack::pop>(L); @@ -10220,7 +10228,7 @@ namespace sol { template simple_usertype_metatable(usertype_detail::verified_tag, std::index_sequence, lua_State* L, Tuple&& args) : callconstructfunc(nil), - indexfunc(&usertype_detail::indexing_fail), newindexfunc(&usertype_detail::indexing_fail), + indexfunc(&usertype_detail::indexing_fail), newindexfunc(&usertype_detail::simple_metatable_newindex), 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), diff --git a/sol/simple_usertype_metatable.hpp b/sol/simple_usertype_metatable.hpp index f43751da..2d11ed44 100644 --- a/sol/simple_usertype_metatable.hpp +++ b/sol/simple_usertype_metatable.hpp @@ -66,6 +66,14 @@ namespace sol { simple_map(const char* mkey, base_walk index, base_walk newindex, variable_map&& vars, function_map&& funcs) : metakey(mkey), variables(std::move(vars)), functions(std::move(funcs)), indexbaseclasspropogation(index), newindexbaseclasspropogation(newindex) {} }; + inline int simple_metatable_newindex(lua_State* L) { + simple_map& sm = stack::get>(L, upvalue_index(1)); + luaL_getmetatable(L, sm.metakey); + stack::set_field(L, stack_reference(L, 2), stack_reference(L, 3), lua_gettop(L)); + lua_settop(L, 0); + return 0; + } + template inline int simple_core_indexing_call(lua_State* L) { simple_map& sm = toplevel ? stack::get>(L, upvalue_index(1)) : stack::pop>(L); @@ -267,7 +275,7 @@ namespace sol { template simple_usertype_metatable(usertype_detail::verified_tag, std::index_sequence, lua_State* L, Tuple&& args) : callconstructfunc(nil), - indexfunc(&usertype_detail::indexing_fail), newindexfunc(&usertype_detail::indexing_fail), + indexfunc(&usertype_detail::indexing_fail), newindexfunc(&usertype_detail::simple_metatable_newindex), 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),