Time to break everything.

Added the ability to extend all usertypes at runtime. The performance implications need to be examined closely.
variadic_args documentation was updated with the desired example demonstrating proper usage
usertype examples were updated demonstrating Lua runtime and C++ runtime updating of a usertype table
SOL_SAFE_FUNCTIONS is now part of the definitions and defined (thanks @eliasdaler)
This commit is contained in:
ThePhD 2017-03-12 21:35:19 -04:00
parent ab9126d892
commit dce8053248
15 changed files with 575 additions and 276 deletions

View File

@ -4,9 +4,11 @@ structures and classes from C++ made available to Lua code (simpler)
--------------------------------------------------------------------
This type is no different from :doc:`regular usertype<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.
This type is no different from :doc:`regular usertype<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`_. This means both adding incrementally during registration, and afterwards by adding items to the metatable at runtime.
You can set functions incrementally to reduce compile-time burden with ``simple_usertype`` as well, as shown in `this example`_. This means both adding incrementally during registration.
You can add functions to both regular and simple usertypes afterwards by adding items to the metatable directly at runtime (e.g., with :doc:`metatable_key<metatable_key>` or by accessing the named metatable yourself).
Some developers used ``simple_usertype`` in older versions to have variables automatically be functions. To achieve this behavior, wrap the desired variable into :doc:`sol::as_function<as_function>`.

View File

@ -5,7 +5,7 @@ structures and classes from C++ made available to Lua code
*Note: ``T`` refers to the type being turned into a usertype.*
While other frameworks extend lua's syntax or create Data Structure Languages (DSLs) to create classes in lua, :doc:`Sol<../index>` instead offers the ability to generate easy bindings. These use metatables and userdata in lua for their implementation. If you need a usertype that is also extensible at runtime and has less compiler crunch to it, try the :doc:`simple version of this after reading these docs<simple_usertype>` Given this C++ class:
While other frameworks extend lua's syntax or create Data Structure Languages (DSLs) to create classes in Lua, :doc:`Sol<../index>` instead offers the ability to generate easy bindings. These use metatables and userdata in Lua for their implementation. Usertypes are also `runtime extensible`_. If you need a usertype that has less compiler crunch-time to it, try the :doc:`simple version of this after reading these docs<simple_usertype>` Given this C++ class:
.. code-block:: cpp
:linenos:
@ -327,4 +327,5 @@ performance note
.. _destructible: http://en.cppreference.com/w/cpp/types/is_destructible
.. _default_constructible: http://en.cppreference.com/w/cpp/types/is_constructible
.. _default_constructible: http://en.cppreference.com/w/cpp/types/is_constructible
.. _runtime extensible: https://github.com/ThePhD/sol2/blob/develop/examples/usertype_advanced.cpp#L81

View File

@ -47,3 +47,41 @@ This class is meant to represent every single argument at its current index and
lua.script("print(x2)"); // 600
lua.script("print(x3)"); // 21
}
You can also "save" arguments and the like later, by stuffing them into a ``std::vector<sol::object>`` or something similar that pulls out all the arguments. Below is an example of saving all of the arguments provided by ``sol::variadic_args`` in a lambda capture variable called ``args``.
.. code-block:: cpp
:linenos:
#include "sol.hpp"
#include <functional>
std::function<void()> function_storage;
void store_routine(const sol::function& f, const sol::variadic_args& va) {
function_storage = [=, args = std::vector<sol::object>(va.begin(), va.end())]() {
f(sol::as_args(args));
};
}
int main() {
sol::state lua;
lua.open_libraries(sol::lib::base);
lua.set_function("store_routine", &store_routine);
lua.script(R"(
function a(name)
print(name)
end
store_routine(a, "some name")
)");
function_storage();
lua.script(R"(
function b(number, text)
print(number, "of", text)
end
store_routine(b, 20, "these apples")
)");
function_storage();
}

View File

@ -9,14 +9,19 @@ config
Note that you can obtain safety with regards to functions you bind by using the :doc:`protect<api/protect>` wrapper around function/variable bindings you set into Lua. Additionally, you can have basic boolean checks when using the API by just converting to a :doc:`sol::optional\<T><api/optional>` when necessary for getting things out of Lua and for function arguments.
``SOL_SAFE_USERTYPE`` triggers the following change:
* If the userdata to a usertype function is nil, will trigger an error instead of letting things go through and letting the system segfault.
* If the userdata to a usertype function is nil, will trigger an error instead of letting things go through and letting the system segfault/crash.
* Turned on by default with clang++, g++ and VC++ if a basic check for building in debug mode is detected
``SOL_SAFE_FUNCTION`` triggers the following change:
* All uses of ``sol::function`` and ``sol::stack_function`` will default to ``sol::protected_function`` and ``sol::stack_protected_function``, respectively.
* Not turned on by default under any detectible compiler settings
``SOL_CHECK_ARGUMENTS`` triggers the following changes:
* ``sol::stack::get`` (used everywhere) defaults to using ``sol::stack::check_get`` and dereferencing the argument. It uses ``sol::type_panic`` as the handler if something goes wrong.
* ``sol::stack::call`` and its variants will, if no templated boolean is specified, check all of the arguments for a function call.
* If ``SOL_SAFE_USERTYPE`` is not defined, it gets defined to turn being on.
* If ``SOL_SAFE_USERTYPE`` is not defined, it gets defined to turn being on and the effects described above kick in
Tests are compiled with this on to ensure everything is going as expected. Remember that if you want these features, you must explicitly turn them on.
Tests are compiled with this on to ensure everything is going as expected. Remember that if you want these features, you must explicitly turn them on all of them to be sure you are getting them.
Finally, some warnings that may help with errors when working with Sol:
@ -26,7 +31,7 @@ functions
The *vast majority* of all users are going to want to work with :doc:`sol::safe_function/sol::protected_function<api/protected_function>`. This version allows for error checking, prunes results, and responds to the defines listed above by throwing errors if you try to use the result of a function without checking. :doc:`sol::function/sol::unsafe_function<api/function>` is unsafe. It assumes that its contents run correctly and throw no errors, which can result in crashes that are hard to debug while offering a very tiny performance boost for not checking error codes or catching exceptions.
If you find yourself crashing inside of ``sol::function``, try changing it to a ``sol::protected_function`` and seeing if the error codes and such help you find out what's going on. You can read more about the API on :doc:`the page itself<api/protected_function>`.
If you find yourself crashing inside of ``sol::function``, try changing it to a ``sol::protected_function`` and seeing if the error codes and such help you find out what's going on. You can read more about the API on :doc:`the page itself<api/protected_function>`. You can also define ``SOL_SAFE_FUNCTION`` as described above, but be warned that the ``protected_function`` API is a superset of the regular default ``function`` API: trying to revert back after defining ``SOL_SAFE_FUNCTION`` may result in some compiler errors if you use things beyond the basic, shared interface of the two types.
As a side note, binding functions with default parameters does not magically bind multiple versions of the function to be called with the default parameters. You must instead use :doc:`sol::overload<api/overload>`.

View File

@ -12,18 +12,24 @@ To learn more about usertypes, visit:
The examples folder also has a number of really great examples for you to see. There are also some notes about guarantees you can find about usertypes, and their associated userdata, below:
* All usertypes are runtime extensible in both `Lua`_ and `C++`_
* You can push types classified as userdata before you register a usertype.
- You can register a usertype with the Lua runtime at any time sol2
- You can retrieve them from the Lua runtime as well through sol2
- Methods and properties will be added to the type only after you register it in the Lua runtime
- Methods and properties will be added to the type only after you register the usertype with the Lua runtime
- All methods and properties will appear on all userdata, even if that object was pushed before the usertype (all userdata will be updated)
* Types either copy once or move once into the memory location, if it is a value type. If it is a pointer, we store only the reference.
- This means take arguments of class types (not primitive types like strings or integers) by ``T&`` or ``T*`` to modify the data in Lua directly, or by plain ``T`` to get a copy
- Return types and passing arguments to ``sol::function`` use perfect forwarding and reference semantics, which means no copies happen unless you specify a value explicitly. See :ref:`this note for details<function-argument-handling>`.
* The first ``sizeof( void* )`` bytes is always a pointer to the typed C++ memory. What comes after is based on what you've pushed into the system according to :doc:`the memory specification for usertypes<api/usertype_memory>`. This is compatible with a number of systems.
- This means retrieval of class types (not primitive types like strings or integers) by ``T&`` or ``T*`` allow you to modify the data in Lua directly.
- Retrieve a plain ``T`` to get a copy
- Return types and passing arguments to ``sol::function``-types use perfect forwarding and reference semantics, which means no copies happen unless you specify a value explicitly. See :ref:`this note for details<function-argument-handling>`.
* The first ``sizeof( void* )`` bytes is always a pointer to the typed C++ memory. What comes after is based on what you've pushed into the system according to :doc:`the memory specification for usertypes<api/usertype_memory>`. This is compatible with a number of systems other than just sol2, making it easy to interop with select other Lua systems.
* Member methods, properties, variables and functions taking ``self&`` arguments modify data directly
- Work on a copy by taking or returning a copy by value.
* The actual metatable associated with the usertype has a long name and is defined to be opaque by the Sol implementation.
* Containers get pushed as special usertypes, but can be disabled if problems arising as detailed :doc:`here<api/containers>`.
* You can use bitfields but it requires some finesse on your part. We have an example to help you get started `here that uses a few tricks`_.
* The actual metatable inner workings is opaque and defined by the Sol implementation, and there are no internal docs because optimizations on the operations are applied based on heuristics we discover from performance testing the system.
* Containers get pushed as special usertypes, but can be disabled if problems arise as detailed :doc:`here<api/containers>`.
* You can use bitfields but it requires some finesse on your part. We have an example to help you get started `here, that uses a few tricks`_.
.. _here that uses a few tricks: https://github.com/ThePhD/sol2/blob/develop/examples/usertype_bitfields.cpp
.. _Lua: https://github.com/ThePhD/sol2/blob/develop/examples/usertype_advanced.cpp#L81
.. _C++: https://github.com/ThePhD/sol2/blob/develop/examples/usertype_simple.cpp#L51

View File

@ -78,6 +78,15 @@ int main() {
// can only read from, not write to
"bullets", sol::readonly(&player::bullets)
);
// You can also add members to the code, defined in Lua!
// This lets you have a high degree of flexibility in the code
std::string prelude_script = R"(
function player:brake ()
self.speed = 0
print("we hit the brakes!")
end
)";
std::string player_script = R"(
-- call single argument integer constructor
@ -110,12 +119,16 @@ print(p1.bullets)
-- p1.bullets = 20
p1:boost()
-- call the function we define at runtime from a Lua script
p1:brake()
)";
// Uncomment and use the file to try that out, too!
// Make sure it's in the local directory of the executable after you build, or adjust the filename path
// Or whatever else you like!
//lua.script_file("prelude_script.lua");
//lua.script_file("player_script.lua");
lua.script(prelude_script);
lua.script(player_script);
std::cout << std::endl;
}

View File

@ -48,7 +48,7 @@ int main() {
lua.set_usertype("generator", generator_registration);
}
// Can update a simple_usertype at runtime, after registration
// Can update a 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; };

View File

@ -25,6 +25,7 @@
#include <string>
#include <array>
#include <cctype>
#include <locale>
namespace sol {
namespace detail {

View File

@ -1,154 +1,157 @@
// 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_OBJECT_HPP
#define SOL_OBJECT_HPP
#include "reference.hpp"
#include "stack.hpp"
#include "userdata.hpp"
#include "as_args.hpp"
#include "variadic_args.hpp"
#include "optional.hpp"
namespace sol {
template <typename R = reference, bool should_pop = !std::is_base_of<stack_reference, R>::value, typename T>
R make_reference(lua_State* L, T&& value) {
int backpedal = stack::push(L, std::forward<T>(value));
R r = stack::get<R>(L, -backpedal);
if (should_pop) {
lua_pop(L, backpedal);
}
return r;
}
template <typename T, typename R = reference, bool should_pop = !std::is_base_of<stack_reference, R>::value, typename... Args>
R make_reference(lua_State* L, Args&&... args) {
int backpedal = stack::push<T>(L, std::forward<Args>(args)...);
R r = stack::get<R>(L, -backpedal);
if (should_pop) {
lua_pop(L, backpedal);
}
return r;
}
template <typename base_t>
class basic_object : public base_t {
private:
template<typename T>
decltype(auto) as_stack(std::true_type) const {
return stack::get<T>(base_t::lua_state(), base_t::stack_index());
}
template<typename T>
decltype(auto) as_stack(std::false_type) const {
base_t::push();
return stack::pop<T>(base_t::lua_state());
}
template<typename T>
bool is_stack(std::true_type) const {
return stack::check<T>(base_t::lua_state(), base_t::stack_index(), no_panic);
}
template<typename T>
bool is_stack(std::false_type) const {
auto pp = stack::push_pop(*this);
return stack::check<T>(base_t::lua_state(), -1, no_panic);
}
template <bool invert_and_pop = false>
basic_object(std::integral_constant<bool, invert_and_pop>, lua_State* L, int index = -1) noexcept : base_t(L, index) {
if (invert_and_pop) {
lua_pop(L, -index);
}
}
public:
basic_object() noexcept = default;
template <typename T, meta::enable<meta::neg<std::is_same<meta::unqualified_t<T>, basic_object>>, meta::neg<std::is_same<base_t, stack_reference>>, std::is_base_of<base_t, meta::unqualified_t<T>>> = meta::enabler>
basic_object(T&& r) : base_t(std::forward<T>(r)) {}
basic_object(lua_nil_t r) : base_t(r) {}
basic_object(const basic_object&) = default;
basic_object(basic_object&&) = default;
basic_object(const stack_reference& r) noexcept : basic_object(r.lua_state(), r.stack_index()) {}
basic_object(stack_reference&& r) noexcept : basic_object(r.lua_state(), r.stack_index()) {}
template <typename Super>
basic_object(const proxy_base<Super>& r) noexcept : basic_object(r.operator basic_object()) {}
template <typename Super>
basic_object(proxy_base<Super>&& r) noexcept : basic_object(r.operator basic_object()) {}
basic_object(lua_State* L, int index = -1) noexcept : base_t(L, index) {}
basic_object(lua_State* L, ref_index index) noexcept : base_t(L, index) {}
template <typename T, typename... Args>
basic_object(lua_State* L, in_place_type_t<T>, Args&&... args) noexcept : basic_object(std::integral_constant<bool, !std::is_base_of<stack_reference, base_t>::value>(), L, -stack::push<T>(L, std::forward<Args>(args)...)) {}
template <typename T, typename... Args>
basic_object(lua_State* L, in_place_t, T&& arg, Args&&... args) noexcept : basic_object(L, in_place<T>, std::forward<T>(arg), std::forward<Args>(args)...) {}
basic_object& operator=(const basic_object&) = default;
basic_object& operator=(basic_object&&) = default;
basic_object& operator=(const base_t& b) { base_t::operator=(b); return *this; }
basic_object& operator=(base_t&& b) { base_t::operator=(std::move(b)); return *this; }
template <typename Super>
basic_object& operator=(const proxy_base<Super>& r) { this->operator=(r.operator basic_object()); return *this; }
template <typename Super>
basic_object& operator=(proxy_base<Super>&& r) { this->operator=(r.operator basic_object()); return *this; }
template<typename T>
decltype(auto) as() const {
return as_stack<T>(std::is_same<base_t, stack_reference>());
}
template<typename T>
bool is() const {
if (!base_t::valid())
return false;
return is_stack<T>(std::is_same<base_t, stack_reference>());
}
};
template <typename T>
object make_object(lua_State* L, T&& value) {
return make_reference<object, true>(L, std::forward<T>(value));
}
template <typename T, typename... Args>
object make_object(lua_State* L, Args&&... args) {
return make_reference<T, object, true>(L, std::forward<Args>(args)...);
}
inline bool operator==(const object& lhs, const lua_nil_t&) {
return !lhs.valid();
}
inline bool operator==(const lua_nil_t&, const object& rhs) {
return !rhs.valid();
}
inline bool operator!=(const object& lhs, const lua_nil_t&) {
return lhs.valid();
}
inline bool operator!=(const lua_nil_t&, const object& rhs) {
return rhs.valid();
}
} // sol
#endif // SOL_OBJECT_HPP
// 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_OBJECT_HPP
#define SOL_OBJECT_HPP
#include "reference.hpp"
#include "stack.hpp"
#include "userdata.hpp"
#include "as_args.hpp"
#include "variadic_args.hpp"
#include "optional.hpp"
namespace sol {
template <typename R = reference, bool should_pop = !std::is_base_of<stack_reference, R>::value, typename T>
R make_reference(lua_State* L, T&& value) {
int backpedal = stack::push(L, std::forward<T>(value));
R r = stack::get<R>(L, -backpedal);
if (should_pop) {
lua_pop(L, backpedal);
}
return r;
}
template <typename T, typename R = reference, bool should_pop = !std::is_base_of<stack_reference, R>::value, typename... Args>
R make_reference(lua_State* L, Args&&... args) {
int backpedal = stack::push<T>(L, std::forward<Args>(args)...);
R r = stack::get<R>(L, -backpedal);
if (should_pop) {
lua_pop(L, backpedal);
}
return r;
}
template <typename base_t>
class basic_object : public base_t {
private:
template<typename T>
decltype(auto) as_stack(std::true_type) const {
return stack::get<T>(base_t::lua_state(), base_t::stack_index());
}
template<typename T>
decltype(auto) as_stack(std::false_type) const {
base_t::push();
return stack::pop<T>(base_t::lua_state());
}
template<typename T>
bool is_stack(std::true_type) const {
return stack::check<T>(base_t::lua_state(), base_t::stack_index(), no_panic);
}
template<typename T>
bool is_stack(std::false_type) const {
auto pp = stack::push_pop(*this);
return stack::check<T>(base_t::lua_state(), -1, no_panic);
}
template <bool invert_and_pop = false>
basic_object(std::integral_constant<bool, invert_and_pop>, lua_State* L, int index = -1) noexcept : base_t(L, index) {
if (invert_and_pop) {
lua_pop(L, -index);
}
}
public:
basic_object() noexcept = default;
template <typename T, meta::enable<meta::neg<std::is_same<meta::unqualified_t<T>, basic_object>>, meta::neg<std::is_same<base_t, stack_reference>>, std::is_base_of<base_t, meta::unqualified_t<T>>> = meta::enabler>
basic_object(T&& r) : base_t(std::forward<T>(r)) {}
basic_object(lua_nil_t r) : base_t(r) {}
basic_object(const basic_object&) = default;
basic_object(basic_object&&) = default;
basic_object(const stack_reference& r) noexcept : basic_object(r.lua_state(), r.stack_index()) {}
basic_object(stack_reference&& r) noexcept : basic_object(r.lua_state(), r.stack_index()) {}
template <typename Super>
basic_object(const proxy_base<Super>& r) noexcept : basic_object(r.operator basic_object()) {}
template <typename Super>
basic_object(proxy_base<Super>&& r) noexcept : basic_object(r.operator basic_object()) {}
basic_object(lua_State* L, int index = -1) noexcept : base_t(L, index) {}
basic_object(lua_State* L, ref_index index) noexcept : base_t(L, index) {}
template <typename T, typename... Args>
basic_object(lua_State* L, in_place_type_t<T>, Args&&... args) noexcept : basic_object(std::integral_constant<bool, !std::is_base_of<stack_reference, base_t>::value>(), L, -stack::push<T>(L, std::forward<Args>(args)...)) {}
template <typename T, typename... Args>
basic_object(lua_State* L, in_place_t, T&& arg, Args&&... args) noexcept : basic_object(L, in_place<T>, std::forward<T>(arg), std::forward<Args>(args)...) {}
basic_object& operator=(const basic_object&) = default;
basic_object& operator=(basic_object&&) = default;
basic_object& operator=(const base_t& b) { base_t::operator=(b); return *this; }
basic_object& operator=(base_t&& b) { base_t::operator=(std::move(b)); return *this; }
template <typename Super>
basic_object& operator=(const proxy_base<Super>& r) { this->operator=(r.operator basic_object()); return *this; }
template <typename Super>
basic_object& operator=(proxy_base<Super>&& r) { this->operator=(r.operator basic_object()); return *this; }
template<typename T>
decltype(auto) as() const {
return as_stack<T>(std::is_same<base_t, stack_reference>());
}
template<typename T>
bool is() const {
int r = base_t::registry_index();
if (r == LUA_REFNIL)
return meta::any_same<meta::unqualified_t<T>, lua_nil_t, nullopt_t, std::nullptr_t>::value ? true : false;
if (r == LUA_NOREF)
return false;
return is_stack<T>(std::is_same<base_t, stack_reference>());
}
};
template <typename T>
object make_object(lua_State* L, T&& value) {
return make_reference<object, true>(L, std::forward<T>(value));
}
template <typename T, typename... Args>
object make_object(lua_State* L, Args&&... args) {
return make_reference<T, object, true>(L, std::forward<Args>(args)...);
}
inline bool operator==(const object& lhs, const lua_nil_t&) {
return !lhs.valid();
}
inline bool operator==(const lua_nil_t&, const object& rhs) {
return !rhs.valid();
}
inline bool operator!=(const object& lhs, const lua_nil_t&) {
return lhs.valid();
}
inline bool operator!=(const lua_nil_t&, const object& rhs) {
return rhs.valid();
}
} // sol
#endif // SOL_OBJECT_HPP

View File

@ -68,43 +68,6 @@ 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) {}
};
template <typename T>
inline int simple_metatable_newindex(lua_State* L) {
int isnum = 0;
lua_Integer magic = lua_tointegerx(L, lua_upvalueindex(4), &isnum);
if (isnum != 0 && magic == toplevel_magic) {
for (std::size_t i = 0; i < 3; lua_pop(L, 1), ++i) {
// Pointer types, AKA "references" from C++
const char* metakey = nullptr;
switch (i) {
case 0:
metakey = &usertype_traits<T*>::metatable()[0];
break;
case 1:
metakey = &usertype_traits<detail::unique_usertype<T>>::metatable()[0];
break;
case 2:
default:
metakey = &usertype_traits<T>::metatable()[0];
break;
}
luaL_getmetatable(L, metakey);
int tableindex = lua_gettop(L);
if (type_of(L, tableindex) == type::lua_nil) {
continue;
}
stack::set_field<false, true>(L, stack_reference(L, 2), stack_reference(L, 3), tableindex);
}
lua_settop(L, 0);
return 0;
}
return indexing_fail<false>(L);
}
inline int simple_indexing_fail(lua_State* L) {
return stack::push(L, sol::lua_nil);
}
template <bool is_index, bool toplevel = false>
inline int simple_core_indexing_call(lua_State* L) {
simple_map& sm = toplevel ? stack::get<user<simple_map>>(L, upvalue_index(1)) : stack::pop<user<simple_map>>(L);
@ -328,7 +291,7 @@ namespace sol {
template<std::size_t... I, typename Tuple>
simple_usertype_metatable(usertype_detail::verified_tag, std::index_sequence<I...>, lua_State* L, Tuple&& args)
: callconstructfunc(lua_nil),
indexfunc(&usertype_detail::simple_indexing_fail), newindexfunc(&usertype_detail::simple_metatable_newindex<T>),
indexfunc(&usertype_detail::indexing_fail<true>), newindexfunc(&usertype_detail::metatable_newindex<T, true>),
indexbase(&usertype_detail::simple_core_indexing_call<true>), newindexbase(&usertype_detail::simple_core_indexing_call<false>),
indexbaseclasspropogation(usertype_detail::walk_all_bases<true>), newindexbaseclasspropogation(&usertype_detail::walk_all_bases<false>),
baseclasscheck(nullptr), baseclasscast(nullptr),

View File

@ -510,14 +510,19 @@ namespace sol {
class basic_function;
template <typename T>
class basic_protected_function;
using function = basic_function<reference>;
using protected_function = basic_protected_function<reference>;
using stack_function = basic_function<stack_reference>;
using stack_protected_function = basic_protected_function<stack_reference>;
using unsafe_function = basic_function<reference>;
using safe_function = basic_protected_function<reference>;
using stack_unsafe_function = basic_function<stack_reference>;
using stack_safe_function = basic_protected_function<stack_reference>;
#ifdef SOL_SAFE_FUNCTIONS
using function = protected_function;
using stack_function = stack_protected_function;
#else
using function = unsafe_function;
using stack_function = stack_unsafe_function;
#endif
template <typename base_t>
class basic_object;
template <typename base_t>

View File

@ -36,6 +36,41 @@
namespace sol {
namespace usertype_detail {
struct add_destructor_tag {};
struct check_destructor_tag {};
struct verified_tag {} const verified{};
template <typename T>
struct is_non_factory_constructor : std::false_type {};
template <typename... Args>
struct is_non_factory_constructor<constructors<Args...>> : std::true_type {};
template <typename... Args>
struct is_non_factory_constructor<constructor_wrapper<Args...>> : std::true_type {};
template <>
struct is_non_factory_constructor<no_construction> : std::true_type {};
template <typename T>
struct is_constructor : is_non_factory_constructor<T> {};
template <typename... Args>
struct is_constructor<factory_wrapper<Args...>> : std::true_type {};
template <typename... Args>
using has_constructor = meta::any<is_constructor<meta::unqualified_t<Args>>...>;
template <typename T>
struct is_destructor : std::false_type {};
template <typename Fx>
struct is_destructor<destructor_wrapper<Fx>> : std::true_type {};
template <typename... Args>
using has_destructor = meta::any<is_destructor<meta::unqualified_t<Args>>...>;
struct no_comp {
template <typename A, typename B>
bool operator()(A&&, B&&) const {
@ -44,15 +79,19 @@ namespace sol {
};
typedef void(*base_walk)(lua_State*, bool&, int&, string_detail::string_shim&);
typedef int(*member_search)(lua_State*, void*);
typedef int(*member_search)(lua_State*, void*, int);
struct find_call_pair {
struct call_information {
member_search first;
member_search second;
int runtime_target;
find_call_pair(member_search first, member_search second) : first(first), second(second) {}
call_information(member_search first, member_search second) : call_information(first, second, -1) {}
call_information(member_search first, member_search second, int runtimetarget) : first(first), second(second), runtime_target(runtimetarget) {}
};
typedef std::unordered_map<std::string, call_information> mapping_t;
inline bool is_indexer(string_detail::string_shim s) {
return s == name_of(meta_function::index) || s == name_of(meta_function::new_index);
}
@ -102,14 +141,71 @@ namespace sol {
virtual ~registrar() {}
};
inline int runtime_object_call(lua_State* L, void*, int runtimetarget) {
std::vector<object>& runtime = stack::get<light<std::vector<object>>>(L, lua_upvalueindex(2));
return stack::push(L, runtime[runtimetarget]);
}
template <bool is_index>
inline int indexing_fail(lua_State* L) {
auto maybeaccessor = stack::get<optional<string_detail::string_shim>>(L, is_index ? -1 : -2);
string_detail::string_shim accessor = maybeaccessor.value_or(string_detail::string_shim("(unknown)"));
if (is_index)
if (is_index) {
#if 0//def SOL_SAFE_USERTYPE
auto maybeaccessor = stack::get<optional<string_detail::string_shim>>(L, is_index ? -1 : -2);
string_detail::string_shim accessor = maybeaccessor.value_or(string_detail::string_shim("(unknown)"));
return luaL_error(L, "sol: attempt to index (get) nil value \"%s\" on userdata (bad (misspelled?) key name or does not exist)", accessor.c_str());
else
#else
// With runtime extensibility, we can't hard-error things. They have to return nil, like regular table types, unfortunately...
return stack::push(L, lua_nil);
#endif
}
else {
auto maybeaccessor = stack::get<optional<string_detail::string_shim>>(L, is_index ? -1 : -2);
string_detail::string_shim accessor = maybeaccessor.value_or(string_detail::string_shim("(unknown)"));
return luaL_error(L, "sol: attempt to index (set) nil value \"%s\" on userdata (bad (misspelled?) key name or does not exist)", accessor.c_str());
}
}
template <typename T, bool is_simple>
inline int metatable_newindex(lua_State* L) {
int isnum = 0;
lua_Integer magic = lua_tointegerx(L, lua_upvalueindex(4), &isnum);
if (isnum != 0 && magic == toplevel_magic) {
bool mustindex = lua_isboolean(L, lua_upvalueindex(5)) != 0 && (lua_toboolean(L, lua_upvalueindex(5)) != 0);
if (!is_simple && mustindex) {
mapping_t& mapping = stack::get<light<mapping_t>>(L, lua_upvalueindex(3));
std::vector<object>& runtime = stack::get<light<std::vector<object>>>(L, lua_upvalueindex(2));
int target = static_cast<int>(runtime.size());
runtime.emplace_back(L, 3);
mapping.emplace_hint(mapping.cend(), stack::get<std::string>(L, 2), call_information(&runtime_object_call, &runtime_object_call, target));
}
for (std::size_t i = 0; i < 4; lua_pop(L, 1), ++i) {
const char* metakey = nullptr;
switch (i) {
case 0:
metakey = &usertype_traits<T*>::metatable()[0];
break;
case 1:
metakey = &usertype_traits<detail::unique_usertype<T>>::metatable()[0];
break;
case 2:
metakey = &usertype_traits<T>::user_metatable()[0];
break;
case 3:
default:
metakey = &usertype_traits<T>::metatable()[0];
break;
}
luaL_getmetatable(L, metakey);
int tableindex = lua_gettop(L);
if (type_of(L, tableindex) == type::lua_nil) {
continue;
}
stack::set_field<false, true>(L, stack_reference(L, 2), stack_reference(L, 3), tableindex);
}
lua_settop(L, 0);
return 0;
}
return indexing_fail<false>(L);
}
template <bool is_index, typename Base>
@ -181,41 +277,6 @@ namespace sol {
inline void make_reg_op(Regs&, int&, const char*) {
// Do nothing if there's no support
}
struct add_destructor_tag {};
struct check_destructor_tag {};
struct verified_tag {} const verified{};
template <typename T>
struct is_non_factory_constructor : std::false_type {};
template <typename... Args>
struct is_non_factory_constructor<constructors<Args...>> : std::true_type {};
template <typename... Args>
struct is_non_factory_constructor<constructor_wrapper<Args...>> : std::true_type {};
template <>
struct is_non_factory_constructor<no_construction> : std::true_type {};
template <typename T>
struct is_constructor : is_non_factory_constructor<T> {};
template <typename... Args>
struct is_constructor<factory_wrapper<Args...>> : std::true_type {};
template <typename... Args>
using has_constructor = meta::any<is_constructor<meta::unqualified_t<Args>>...>;
template <typename T>
struct is_destructor : std::false_type {};
template <typename Fx>
struct is_destructor<destructor_wrapper<Fx>> : std::true_type {};
template <typename... Args>
using has_destructor = meta::any<is_destructor<meta::unqualified_t<Args>>...>;
} // usertype_detail
template <typename T>
@ -238,9 +299,9 @@ namespace sol {
typedef std::tuple<clean_type_t<Tn> ...> Tuple;
template <std::size_t Idx>
struct check_binding : is_variable_binding<meta::unqualified_tuple_element_t<Idx, Tuple>> {};
typedef std::unordered_map<std::string, usertype_detail::find_call_pair> mapping_t;
usertype_detail::mapping_t mapping;
std::vector<object> runtime;
Tuple functions;
mapping_t mapping;
lua_CFunction indexfunc;
lua_CFunction newindexfunc;
lua_CFunction destructfunc;
@ -357,19 +418,20 @@ namespace sol {
}
template <typename... Args, typename = std::enable_if_t<sizeof...(Args) == sizeof...(Tn)>>
usertype_metatable(Args&&... args) : functions(std::forward<Args>(args)...),
usertype_metatable(Args&&... args) :
mapping(),
indexfunc(usertype_detail::indexing_fail<true>), newindexfunc(usertype_detail::indexing_fail<false>),
functions(std::forward<Args>(args)...),
indexfunc(&usertype_detail::indexing_fail<true>), newindexfunc(&usertype_detail::metatable_newindex<T, false>),
destructfunc(nullptr), callconstructfunc(nullptr),
indexbase(&core_indexing_call<true>), newindexbase(&core_indexing_call<false>),
indexbaseclasspropogation(usertype_detail::walk_all_bases<true>), newindexbaseclasspropogation(usertype_detail::walk_all_bases<false>),
baseclasscheck(nullptr), baseclasscast(nullptr),
mustindex(contains_variable() || contains_index()), secondarymeta(contains_variable()),
hasequals(false), hasless(false), haslessequals(false) {
std::initializer_list<typename mapping_t::value_type> ilist{ {
std::pair<std::string, usertype_detail::find_call_pair>(
std::initializer_list<typename usertype_detail::mapping_t::value_type> ilist{ {
std::pair<std::string, usertype_detail::call_information>(
usertype_detail::make_string(std::get<I * 2>(functions)),
usertype_detail::find_call_pair(&usertype_metatable::real_find_call<I * 2, I * 2 + 1, false>,
usertype_detail::call_information(&usertype_metatable::real_find_call<I * 2, I * 2 + 1, false>,
&usertype_metatable::real_find_call<I * 2, I * 2 + 1, true>)
)
}... };
@ -377,7 +439,7 @@ namespace sol {
}
template <std::size_t I0, std::size_t I1, bool is_index>
static int real_find_call(lua_State* L, void* um) {
static int real_find_call(lua_State* L, void* um, int) {
auto& f = *static_cast<usertype_metatable*>(um);
if (is_variable_binding<decltype(std::get<I1>(f.functions))>::value) {
return real_call_with<I1, is_index, true>(L, f);
@ -395,8 +457,9 @@ namespace sol {
std::string name = stack::get<std::string>(L, keyidx);
auto memberit = f.mapping.find(name);
if (memberit != f.mapping.cend()) {
auto& member = is_index ? memberit->second.second : memberit->second.first;
return (member)(L, static_cast<void*>(&f));
const usertype_detail::call_information& ci = memberit->second;
const usertype_detail::member_search& member = is_index ? ci.second : ci.first;
return (member)(L, static_cast<void*>(&f), ci.runtime_target);
}
string_detail::string_shim accessor = name;
int ret = 0;
@ -548,14 +611,14 @@ namespace sol {
stack::set_field(L, detail::base_class_cast_key(), um.baseclasscast, 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());
stack::set_field(L, detail::base_class_index_propogation_key(), make_closure(um.indexbase, make_light(um), make_light(um.runtime)), t.stack_index());
stack::set_field(L, detail::base_class_new_index_propogation_key(), make_closure(um.newindexbase, make_light(um), make_light(um.runtime)), t.stack_index());
if (mustindex) {
// Basic index pushing: specialize
// index and newindex to give variables and stuff
stack::set_field(L, meta_function::index, make_closure(umt_t::index_call, make_light(um)), t.stack_index());
stack::set_field(L, meta_function::new_index, make_closure(umt_t::new_index_call, make_light(um)), t.stack_index());
stack::set_field(L, meta_function::index, make_closure(umt_t::index_call, make_light(um), make_light(um.runtime)), t.stack_index());
stack::set_field(L, meta_function::new_index, make_closure(umt_t::new_index_call, make_light(um), make_light(um.runtime)), t.stack_index());
}
else {
// If there's only functions, we can use the fast index version
@ -566,11 +629,11 @@ namespace sol {
lua_createtable(L, 0, 3);
stack_reference metabehind(L, -1);
if (um.callconstructfunc != nullptr) {
stack::set_field(L, meta_function::call_function, make_closure(um.callconstructfunc, make_light(um)), metabehind.stack_index());
stack::set_field(L, meta_function::call_function, make_closure(um.callconstructfunc, make_light(um), make_light(um.runtime)), metabehind.stack_index());
}
if (um.secondarymeta) {
stack::set_field(L, meta_function::index, make_closure(umt_t::index_call, make_light(um)), metabehind.stack_index());
stack::set_field(L, meta_function::new_index, make_closure(umt_t::new_index_call, make_light(um)), metabehind.stack_index());
stack::set_field(L, meta_function::index, make_closure(umt_t::index_call, make_light(um), make_light(um.runtime)), metabehind.stack_index());
stack::set_field(L, meta_function::new_index, make_closure(umt_t::new_index_call, make_light(um), make_light(um.runtime)), metabehind.stack_index());
}
stack::set_field(L, metatable_key, metabehind, t.stack_index());
metabehind.pop();
@ -588,12 +651,12 @@ namespace sol {
lua_createtable(L, 0, 3);
stack_reference metabehind(L, -1);
if (um.callconstructfunc != nullptr) {
stack::set_field(L, meta_function::call_function, make_closure(um.callconstructfunc, make_light(um)), metabehind.stack_index());
}
if (um.secondarymeta) {
stack::set_field(L, meta_function::index, make_closure(umt_t::index_call, make_light(um)), metabehind.stack_index());
stack::set_field(L, meta_function::new_index, make_closure(umt_t::new_index_call, make_light(um)), metabehind.stack_index());
stack::set_field(L, meta_function::call_function, make_closure(um.callconstructfunc, make_light(um), static_cast<void*>(&um.runtime)), metabehind.stack_index());
}
stack::set_field(L, meta_function::index, make_closure(umt_t::index_call, make_light(um), static_cast<void*>(&um.runtime), static_cast<void*>(&um.mapping), usertype_detail::toplevel_magic, um.mustindex), metabehind.stack_index());
stack::set_field(L, meta_function::new_index, make_closure(umt_t::new_index_call, make_light(um), static_cast<void*>(&um.runtime), static_cast<void*>(&um.mapping), usertype_detail::toplevel_magic, um.mustindex), metabehind.stack_index());
stack::set_field(L, metatable_key, metabehind, t.stack_index());
metabehind.pop();
}

View File

@ -598,3 +598,101 @@ TEST_CASE("usertype/simple-missing-key", "make sure a missing key returns nil")
lua.new_simple_usertype<thing>("thing");
REQUIRE_NOTHROW(lua.script("print(thing.missingKey)"));
}
TEST_CASE("usertype/simple-runtime-extensibility", "Check if usertypes are runtime extensible") {
struct thing {
int v = 20;
int func(int a) { return a; }
};
int val = 0;
SECTION("just functions") {
sol::state lua;
lua.open_libraries(sol::lib::base);
lua.new_simple_usertype<thing>("thing",
"func", &thing::func
);
lua.script(R"(
t = thing.new()
)");
REQUIRE_THROWS([&lua]() {
lua.script(R"(
t.runtime_func = function (a)
return a + 50
end
)");
}());
REQUIRE_THROWS([&lua]() {
lua.script(R"(
function t:runtime_func(a)
return a + 52
end
)");
}());
lua.script("val = t:func(2)");
val = lua["val"];
REQUIRE(val == 2);
REQUIRE_NOTHROW([&lua]() {
lua.script(R"(
function thing:runtime_func(a)
return a + 1
end
)");
}());
lua.script("val = t:runtime_func(2)");
val = lua["val"];
REQUIRE(val == 3);
}
SECTION("with variable") {
sol::state lua;
lua.open_libraries(sol::lib::base);
lua.new_simple_usertype<thing>("thing",
"func", &thing::func,
"v", &thing::v
);
lua.script(R"(
t = thing.new()
)");
REQUIRE_THROWS([&lua]() {
lua.script(R"(
t.runtime_func = function (a)
return a + 50
end
)");
}());
REQUIRE_THROWS([&lua]() {
lua.script(R"(
function t:runtime_func(a)
return a + 52
end
)");
}());
lua.script("val = t:func(2)");
val = lua["val"];
REQUIRE(val == 2);
REQUIRE_NOTHROW([&lua]() {
lua.script(R"(
function thing:runtime_func(a)
return a + 1
end
)");
}());
lua.script("val = t:runtime_func(2)");
val = lua["val"];
REQUIRE(val == 3);
}
}

View File

@ -1520,5 +1520,106 @@ TEST_CASE("usertype/missing-key", "make sure a missing key returns nil") {
lua.open_libraries(sol::lib::base);
lua.new_usertype<thing>("thing");
REQUIRE_NOTHROW(lua.script("print(thing.missingKey)"));
REQUIRE_NOTHROW(lua.script("v = thing.missingKey\nprint(v)"));
sol::object o = lua["v"];
bool isnil = o.is<sol::lua_nil_t>();
REQUIRE(isnil);
}
TEST_CASE("usertype/runtime-extensibility", "Check if usertypes are runtime extensible") {
struct thing {
int v = 20;
int func(int a) { return a; }
};
int val = 0;
SECTION("just functions") {
sol::state lua;
lua.open_libraries(sol::lib::base);
lua.new_usertype<thing>("thing",
"func", &thing::func
);
lua.script(R"(
t = thing.new()
)");
REQUIRE_THROWS([&lua]() {
lua.script(R"(
t.runtime_func = function (a)
return a + 50
end
)");
}());
REQUIRE_THROWS([&lua]() {
lua.script(R"(
function t:runtime_func(a)
return a + 52
end
)");
}());
lua.script("val = t:func(2)");
val = lua["val"];
REQUIRE(val == 2);
REQUIRE_NOTHROW([&lua]() {
lua.script(R"(
function thing:runtime_func(a)
return a + 1
end
)");
}());
lua.script("val = t:runtime_func(2)");
val = lua["val"];
REQUIRE(val == 3);
}
SECTION("with variable") {
sol::state lua;
lua.open_libraries(sol::lib::base);
lua.new_usertype<thing>("thing",
"func", &thing::func,
"v", &thing::v
);
lua.script(R"(
t = thing.new()
)");
REQUIRE_THROWS([&lua]() {
lua.script(R"(
t.runtime_func = function (a)
return a + 50
end
)");
}());
REQUIRE_THROWS([&lua]() {
lua.script(R"(
function t:runtime_func(a)
return a + 52
end
)");
}());
lua.script("val = t:func(2)");
val = lua["val"];
REQUIRE(val == 2);
REQUIRE_NOTHROW([&lua]() {
lua.script(R"(
function thing:runtime_func(a)
return a + 1
end
)");
}());
lua.script("val = t:runtime_func(2)");
val = lua["val"];
REQUIRE(val == 3);
}
}

View File

@ -326,8 +326,6 @@ TEST_CASE("object/conversions", "make sure all basic reference types can be made
TEST_CASE("state/require_file", "opening files as 'requires'") {
static const char FILE_NAME[] = "./tmp_thingy.lua";
std::fstream file(FILE_NAME, std::ios::out);
sol::state lua;
lua.open_libraries(sol::lib::base);
@ -344,6 +342,7 @@ TEST_CASE("state/require_file", "opening files as 'requires'") {
"bar", &foo::bar
);
std::fstream file(FILE_NAME, std::ios::out);
file << "return { modfunc = function () return foo.new(221) end }" << std::endl;
file.close();
@ -360,6 +359,7 @@ TEST_CASE("state/require_file", "opening files as 'requires'") {
SECTION("simple")
{
std::fstream file(FILE_NAME, std::ios::out);
file << "return { modfunc = function () return 221 end }" << std::endl;
file.close();