From 58003669fbd7a0a6b6f4f2a029db6ae4f1cd8f69 Mon Sep 17 00:00:00 2001 From: ThePhD Date: Tue, 9 May 2017 13:24:56 -0400 Subject: [PATCH] this_environment is now live --- docs/source/api/api-top.rst | 3 +- docs/source/api/stack.rst | 2 +- docs/source/api/state.rst | 2 +- docs/source/api/this_environment.rst | 44 ++++++++++++++++++ docs/source/api/usertype_memory.rst | 4 +- examples/environment_snooping.cpp | 31 +++++++++---- sol/call.hpp | 4 +- sol/environment.hpp | 66 ++++++++++++++++++++++++++- sol/function_types.hpp | 68 +++++++++++++++++++--------- sol/function_types_core.hpp | 4 +- sol/function_types_stateless.hpp | 8 ++-- sol/stack.hpp | 2 +- sol/stack_check.hpp | 9 ++++ sol/types.hpp | 7 +++ test_environments.cpp | 32 +++++++++++++ 15 files changed, 239 insertions(+), 47 deletions(-) create mode 100644 docs/source/api/this_environment.rst diff --git a/docs/source/api/api-top.rst b/docs/source/api/api-top.rst index 2719efc6..22d80dfc 100644 --- a/docs/source/api/api-top.rst +++ b/docs/source/api/api-top.rst @@ -10,12 +10,14 @@ Browse the various function and classes :doc:`Sol<../index>` utilizes to make yo :maxdepth: 2 state + this_state reference stack_reference make_reference table userdata environment + this_environment proxy containers nested @@ -32,7 +34,6 @@ Browse the various function and classes :doc:`Sol<../index>` utilizes to make yo object thread optional - this_state variadic_args as_args overload diff --git a/docs/source/api/stack.rst b/docs/source/api/stack.rst index 0ebb2d2e..0eff3384 100644 --- a/docs/source/api/stack.rst +++ b/docs/source/api/stack.rst @@ -175,7 +175,7 @@ This is an SFINAE-friendly struct that is meant to expose static function ``get` } }; -This is an SFINAE-friendly struct that is meant to expose static function ``push`` that returns the number of things pushed onto the stack. The default implementation assumes ``T`` is a usertype and pushes a userdata into Lua with a :ref:`usertype_traits\` metatable associated with it. There are implementations for pushing numbers (``std::is_floating``, ``std::is_integral``-matching types), getting ``std::string`` and ``const char*``, getting raw userdata with :doc:`userdata` and raw upvalues with :doc:`upvalue`, getting raw `lua_CFunction`_ s, and finally pulling out Lua functions into ``sol::function``. It is also defined for anything that derives from :doc:`sol::reference`. It also has a special implementation for the 2 standard library smart pointers (see :doc:`usertype memory`). +This is an SFINAE-friendly struct that is meant to expose static function ``push`` that returns the number of things pushed onto the stack. The default implementation assumes ``T`` is a usertype and pushes a userdata into Lua with a class-specific, state-wide metatable associated with it. There are implementations for pushing numbers (``std::is_floating``, ``std::is_integral``-matching types), getting ``std::string`` and ``const char*``, getting raw userdata with :doc:`userdata` and raw upvalues with :doc:`upvalue`, getting raw `lua_CFunction`_ s, and finally pulling out Lua functions into ``sol::function``. It is also defined for anything that derives from :doc:`sol::reference`. It also has a special implementation for the 2 standard library smart pointers (see :doc:`usertype memory`). .. code-block:: cpp :caption: struct: checker diff --git a/docs/source/api/state.rst b/docs/source/api/state.rst index 1a25dadb..351daee9 100644 --- a/docs/source/api/state.rst +++ b/docs/source/api/state.rst @@ -93,7 +93,7 @@ To handle errors when using the second overload, provide a callable function/obj }); } -You can also pass a :doc:`sol::environment` to ``script``/``script_file`` to have the script have sandboxed / contained in a way inside of a state. This is useful for runnig multiple different "perspectives" or "views" on the same state. See the ``sol: +You can also pass a :doc:`sol::environment` to ``script``/``script_file`` to have the script have sandboxed / contained in a way inside of a state. This is useful for runnig multiple different "perspectives" or "views" on the same state, and even has fallback support. See the :doc:`sol::environment` documentation for more details. .. code-block:: cpp :caption: function: require / require_file diff --git a/docs/source/api/this_environment.rst b/docs/source/api/this_environment.rst new file mode 100644 index 00000000..2bce4b78 --- /dev/null +++ b/docs/source/api/this_environment.rst @@ -0,0 +1,44 @@ +this_environment +================ +retrieving the environment of the calling function +-------------------------------------------------- + +Sometimes in C++ it's useful to know where a Lua call is coming from and what :doc:`environment` it is from. The former is covered by Lua's Debug API, which is extensive and is not fully wrapped up by sol2. But, sol2 covers the latter in letting you get the environment of the calling script / function, if it has one. ``sol::this_environment`` is a *transparent argument* and does not need to be passed in Lua scripts or provided when using :doc:`sol::function`, similar to :doc:`sol::this_state`: + +.. code-block:: cpp + :linenos: + + #define SOL_CHECK_ARGUMENTS + #include + + #include + + + void env_check(sol::this_state ts, int x, sol::this_environment te) { + std::cout << "In C++, 'int x' is in the second position, and its value is: " << x << std::endl; + if (!te) { + std::cout << "function does not have an environment: exiting function early" << std::endl; + return; + } + sol::environment& env = te; + sol::state_view lua = ts; + sol::environment freshenv = lua["freshenv"]; + bool is_same_env = freshenv == env; + std::cout << "env == freshenv : " << is_same_env << std::endl; + } + + int main() { + sol::state lua; + sol::environment freshenv(lua, sol::create, lua.globals()); + lua["freshenv"] = freshenv; + + lua.set_function("f", env_check); + lua.script("f(25)", freshenv); + + return 0; + } + + +Also see `this example`_ for more details. + +.. _this example: https://github.com/ThePhD/sol2/blob/develop/examples/environment_snooping.cpp \ No newline at end of file diff --git a/docs/source/api/usertype_memory.rst b/docs/source/api/usertype_memory.rst index e3a82ed1..c714638b 100644 --- a/docs/source/api/usertype_memory.rst +++ b/docs/source/api/usertype_memory.rst @@ -5,14 +5,14 @@ usertype memory Sol does not take ownership of raw pointers, returned from functions or set through the ``set`` functions. Return a value, a ``std::unique_ptr``, a ``std::shared_ptr`` of some kind, or hook up the :doc:`unique usertypes traits` to work for some specific handle structure you use (AKA, for ``boost::shared_ptr``). -The userdata generated by Sol has a specific layout, depending on how Sol recognizes userdata passed into it. All of the referred to metatable names are generated from :ref:`usertype_traits\`. Note that we use 1 metatable per the 3 styles listed below, plus 1 additional metatable that is used for the actual table that you bind with the name when calling ``table::new/set_(simple_)usertype``. +The userdata generated by Sol has a specific layout, depending on how Sol recognizes userdata passed into it. All of the referred to metatable names are generated from the name of the class itself. Note that we use 1 metatable per the 3 styles listed below, plus 1 additional metatable that is used for the actual table that you bind with the name when calling ``table::new/set_(simple_)usertype``. In general, we always insert a T* in the first `sizeof(T*)` bytes, so the any framework that pulls out those first bytes expecting a pointer will work. The rest of the data has some different alignments and contents based on what it's used for and how it's used. For ``T`` --------- -These are classified with the metatable name from :ref:`usertype_traits\`. +These are classified with a metatable name generally derived from the class name itself. The data layout for references is as follows:: diff --git a/examples/environment_snooping.cpp b/examples/environment_snooping.cpp index df10eb49..4002cebe 100644 --- a/examples/environment_snooping.cpp +++ b/examples/environment_snooping.cpp @@ -4,13 +4,24 @@ #include #include +// Simple sol2 version of the below +void simple(sol::this_state ts, sol::this_environment te) { + sol::state_view lua = ts; + if (te) { + sol::environment& env = te; + sol::environment freshenv = lua["freshenv"]; + bool is_same_env = freshenv == env; + std::cout << "this_environment -- env == freshenv : " << is_same_env << std::endl; + } + std::cout << "this_environment -- no environment present" << std::endl; +} + // NOTE: // THIS IS A LOW-LEVEL EXAMPLE, using pieces of sol2 // to facilitate better usage -// If you need to do this often, you can copy this code and paste it in a utility function for yourself -// so you can grab the environment whenever you need to - -void some_function_called_by_sol2(sol::this_state ts) { +// It is recommended you just do the simple version, as it is basically this code +// but it is sometimes useful to show the hoops you need to jump through to use the Lua C API +void complicated(sol::this_state ts) { lua_State* L = ts; lua_Debug info; @@ -30,14 +41,14 @@ void some_function_called_by_sol2(sol::this_state ts) { // the rest is for printing / debugging purposes if (lua_getinfo(L, "fnluS", &info) == 0) { // failure? - std::cout << "error: unable to get stack information" << std::endl; + std::cout << "manually -- error: unable to get stack information" << std::endl; lua_settop(L, pre_stack_size); return; } // Okay, so all the calls worked. // Print out some information about this "level" - std::cout << "[" << level << "] " << info.short_src << ":" << info.currentline + std::cout << "manually -- [" << level << "] " << info.short_src << ":" << info.currentline << " -- " << (info.name ? info.name : "") << "[" << info.what << "]" << std::endl; // Grab the function off the top of the stack @@ -48,14 +59,14 @@ void some_function_called_by_sol2(sol::this_state ts) { // The environment can now be ripped out of the function sol::environment env(sol::env_key, f); if (!env.valid()) { - std::cout << "error: no environment to get" << std::endl; + std::cout << "manually -- error: no environment to get" << std::endl; lua_settop(L, pre_stack_size); return; } sol::state_view lua(L); sol::environment freshenv = lua["freshenv"]; bool is_same_env = freshenv == env; - std::cout << "env == freshenv : " << is_same_env << std::endl; + std::cout << "manually -- env == freshenv : " << is_same_env << std::endl; } int main() { @@ -64,9 +75,11 @@ int main() { sol::environment freshenv(lua, sol::create, lua.globals()); lua["freshenv"] = freshenv; - lua.set_function("f", some_function_called_by_sol2); + lua.set_function("f", simple); + lua.set_function("g", complicated); lua.script("f()", freshenv); + lua.script("g()", freshenv); return 0; } diff --git a/sol/call.hpp b/sol/call.hpp index 17ddc119..f6a3bb11 100644 --- a/sol/call.hpp +++ b/sol/call.hpp @@ -597,9 +597,9 @@ namespace sol { return lua_call_wrapper, is_index, is_variable, stack::stack_detail::default_check_arguments, boost>{}.call(L, std::forward(fx), std::forward(args)...); } - template + template inline int call_user(lua_State* L) { - auto& fx = stack::get>(L, upvalue_index(1)); + auto& fx = stack::get>(L, upvalue_index(start)); return call_wrapped(L, fx); } diff --git a/sol/environment.hpp b/sol/environment.hpp index 83cb5ae7..8540277a 100644 --- a/sol/environment.hpp +++ b/sol/environment.hpp @@ -92,7 +92,8 @@ namespace sol { #else // Use upvalues as explained in Lua 5.2 and beyond's manual this->push(); - if (lua_setupvalue(L, -2, 1) == nullptr) { + const char* name = lua_setupvalue(L, -2, 1); + if (name == nullptr) { this->pop(); } #endif @@ -111,13 +112,74 @@ namespace sol { return basic_environment(L, -1); } + struct this_environment { + optional env; + + this_environment() : env(nullopt) {} + this_environment(sol::environment e) : env(std::move(e)) {} + this_environment(const this_environment&) = default; + this_environment(this_environment&&) = default; + this_environment& operator=(const this_environment&) = default; + this_environment& operator=(this_environment&&) = default; + + explicit operator bool() const { + return static_cast(env); + } + + operator optional& () { + return env; + } + + operator const optional& () const { + return env; + } + + operator environment& () { + return env.value(); + } + + operator const environment& () const { + return env.value(); + } + }; + namespace stack { template <> struct getter { - static environment get(lua_State* L, int index = -1) { + static environment get(lua_State* L, int index, record& tracking) { + tracking.use(1); return get_environment(stack_reference(L, raw_index(index))); } }; + + template <> + struct getter { + static this_environment get(lua_State* L, int index, record& tracking) { + tracking.use(0); + lua_Debug info; + // Level 0 means current function (this C function, which may or may not be useful for us?) + // Level 1 means next call frame up the stack. (Can be nothing if function called directly from C++ with lua_p/call) + int pre_stack_size = lua_gettop(L); + if (lua_getstack(L, 1, &info) != 1) { + if (lua_getstack(L, 0, &info) != 1) { + lua_settop(L, pre_stack_size); + return this_environment(); + } + } + if (lua_getinfo(L, "f", &info) == 0) { + lua_settop(L, pre_stack_size); + return this_environment(); + } + + sol::stack_reference f(L, -1); + sol::environment env(sol::env_key, f); + if (!env.valid()) { + lua_settop(L, pre_stack_size); + return this_environment(); + } + return this_environment(std::move(env)); + } + }; } // stack } // sol diff --git a/sol/function_types.hpp b/sol/function_types.hpp index 492ea3c4..058c21a2 100644 --- a/sol/function_types.hpp +++ b/sol/function_types.hpp @@ -82,7 +82,9 @@ namespace sol { auto userptr = detail::ptr(std::forward(obj), std::forward(args)...); lua_CFunction freefunc = &function_detail::upvalue_member_variable, meta::unqualified_t>::call; - int upvalues = stack::stack_detail::push_as_upvalues(L, memfxptr); + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::stack_detail::push_as_upvalues(L, memfxptr); upvalues += stack::push(L, lightuserdata_value(static_cast(userptr))); stack::push(L, c_closure(freefunc, upvalues)); } @@ -101,7 +103,9 @@ namespace sol { template static void select_member_variable(std::true_type, lua_State* L, Fx&& fx, function_detail::class_indicator) { lua_CFunction freefunc = &function_detail::upvalue_this_member_variable::call; - int upvalues = stack::stack_detail::push_as_upvalues(L, fx); + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::stack_detail::push_as_upvalues(L, fx); stack::push(L, c_closure(freefunc, upvalues)); } @@ -109,7 +113,9 @@ namespace sol { static void select_member_variable(std::true_type, lua_State* L, Fx&& fx) { typedef typename meta::bind_traits>::object_type C; lua_CFunction freefunc = &function_detail::upvalue_this_member_variable::call; - int upvalues = stack::stack_detail::push_as_upvalues(L, fx); + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::stack_detail::push_as_upvalues(L, fx); stack::push(L, c_closure(freefunc, upvalues)); } @@ -127,7 +133,9 @@ namespace sol { auto userptr = detail::ptr(std::forward(obj), std::forward(args)...); lua_CFunction freefunc = &function_detail::upvalue_member_function, meta::unqualified_t>::call; - int upvalues = stack::stack_detail::push_as_upvalues(L, memfxptr); + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::stack_detail::push_as_upvalues(L, memfxptr); upvalues += stack::push(L, lightuserdata_value(static_cast(userptr))); stack::push(L, c_closure(freefunc, upvalues)); } @@ -146,7 +154,9 @@ namespace sol { template static void select_member_function(std::true_type, lua_State* L, Fx&& fx, function_detail::class_indicator) { lua_CFunction freefunc = &function_detail::upvalue_this_member_function::call; - int upvalues = stack::stack_detail::push_as_upvalues(L, fx); + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::stack_detail::push_as_upvalues(L, fx); stack::push(L, c_closure(freefunc, upvalues)); } @@ -154,7 +164,9 @@ namespace sol { static void select_member_function(std::true_type, lua_State* L, Fx&& fx) { typedef typename meta::bind_traits>::object_type C; lua_CFunction freefunc = &function_detail::upvalue_this_member_function::call; - int upvalues = stack::stack_detail::push_as_upvalues(L, fx); + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::stack_detail::push_as_upvalues(L, fx); stack::push(L, c_closure(freefunc, upvalues)); } @@ -168,7 +180,9 @@ namespace sol { std::decay_t target(std::forward(fx), std::forward(args)...); lua_CFunction freefunc = &function_detail::upvalue_free_function::call; - int upvalues = stack::stack_detail::push_as_upvalues(L, target); + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::stack_detail::push_as_upvalues(L, target); stack::push(L, c_closure(freefunc, upvalues)); } @@ -183,10 +197,12 @@ namespace sol { template static void set_fx(lua_State* L, Args&&... args) { - lua_CFunction freefunc = function_detail::call>; + lua_CFunction freefunc = function_detail::call, 2>; - stack::push>(L, std::forward(args)...); - stack::push(L, c_closure(freefunc, 1)); + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::push>(L, std::forward(args)...); + stack::push(L, c_closure(freefunc, upvalues)); } template @@ -254,15 +270,19 @@ namespace sol { template struct pusher> { static int push(lua_State* L, protect_t&& pw) { - lua_CFunction cf = call_detail::call_user>; - int closures = stack::push>>(L, std::move(pw.value)); - return stack::push(L, c_closure(cf, closures)); + lua_CFunction cf = call_detail::call_user, 2>; + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::push>>(L, std::move(pw.value)); + return stack::push(L, c_closure(cf, upvalues)); } static int push(lua_State* L, const protect_t& pw) { - lua_CFunction cf = call_detail::call_user>; - int closures = stack::push>>(L, pw.value); - return stack::push(L, c_closure(cf, closures)); + lua_CFunction cf = call_detail::call_user, 2>; + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::push>>(L, pw.value); + return stack::push(L, c_closure(cf, upvalues)); } }; @@ -357,9 +377,11 @@ namespace sol { struct pusher>> { template static int push(lua_State* L, C&& c) { - lua_CFunction cf = call_detail::call_user>; - int closures = stack::push>>(L, std::forward(c)); - return stack::push(L, c_closure(cf, closures)); + lua_CFunction cf = call_detail::call_user, 2>; + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::push>>(L, std::forward(c)); + return stack::push(L, c_closure(cf, upvalues)); } }; @@ -374,9 +396,11 @@ namespace sol { template struct pusher>> { static int push(lua_State* L, destructor_wrapper c) { - lua_CFunction cf = call_detail::call_user>; - int closures = stack::push>(L, std::move(c)); - return stack::push(L, c_closure(cf, closures)); + lua_CFunction cf = call_detail::call_user, 2>; + int upvalues = 0; + upvalues += stack::push(L, nullptr); + upvalues += stack::push>(L, std::move(c)); + return stack::push(L, c_closure(cf, upvalues)); } }; diff --git a/sol/function_types_core.hpp b/sol/function_types_core.hpp index 24169ea7..e6682d57 100644 --- a/sol/function_types_core.hpp +++ b/sol/function_types_core.hpp @@ -28,9 +28,9 @@ namespace sol { namespace function_detail { - template + template inline int call(lua_State* L) { - Fx& fx = stack::get>(L, upvalue_index(1)); + Fx& fx = stack::get>(L, upvalue_index(start)); return fx(L); } } // function_detail diff --git a/sol/function_types_stateless.hpp b/sol/function_types_stateless.hpp index 0b6e9072..f97c99d4 100644 --- a/sol/function_types_stateless.hpp +++ b/sol/function_types_stateless.hpp @@ -57,7 +57,7 @@ namespace sol { // idx n + 1: is the object's void pointer // We don't need to store the size, because the other side is templated // with the same member function pointer type - auto memberdata = stack::stack_detail::get_as_upvalues(L, 1); + auto memberdata = stack::stack_detail::get_as_upvalues(L); auto objdata = stack::stack_detail::get_as_upvalues(L, memberdata.second); function_type& memfx = memberdata.first; auto& item = *objdata.first; @@ -84,7 +84,7 @@ namespace sol { // idx n + 1: is the object's void pointer // We don't need to store the size, because the other side is templated // with the same member function pointer type - auto memberdata = stack::stack_detail::get_as_upvalues(L, 1); + auto memberdata = stack::stack_detail::get_as_upvalues(L); auto objdata = stack::stack_detail::get_as_upvalues(L, memberdata.second); auto& mem = *objdata.first; function_type& var = memberdata.first; @@ -115,7 +115,7 @@ namespace sol { static int real_call(lua_State* L) { // Layout: // idx 1...n: verbatim data of member variable pointer - auto memberdata = stack::stack_detail::get_as_upvalues(L, 1); + auto memberdata = stack::stack_detail::get_as_upvalues(L); function_type& memfx = memberdata.first; return call_detail::call_wrapped(L, memfx); } @@ -137,7 +137,7 @@ namespace sol { static int real_call(lua_State* L) { // Layout: // idx 1...n: verbatim data of member variable pointer - auto memberdata = stack::stack_detail::get_as_upvalues(L, 1); + auto memberdata = stack::stack_detail::get_as_upvalues(L); function_type& var = memberdata.first; switch (lua_gettop(L)) { case 1: diff --git a/sol/stack.hpp b/sol/stack.hpp index ad2a73a4..c65fa30f 100644 --- a/sol/stack.hpp +++ b/sol/stack.hpp @@ -56,7 +56,7 @@ namespace sol { } template - inline std::pair get_as_upvalues(lua_State* L, int index = 1) { + inline std::pair get_as_upvalues(lua_State* L, int index = 2) { const static std::size_t data_t_count = (sizeof(T) + (sizeof(void*) - 1)) / sizeof(void*); typedef std::array data_t; data_t voiddata{ {} }; diff --git a/sol/stack_check.hpp b/sol/stack_check.hpp index ad6260dc..a4005878 100644 --- a/sol/stack_check.hpp +++ b/sol/stack_check.hpp @@ -136,6 +136,15 @@ namespace sol { } }; + template + struct checker { + template + static bool check(lua_State*, int, Handler&&, record& tracking) { + tracking.use(0); + return true; + } + }; + template struct checker { template diff --git a/sol/types.hpp b/sol/types.hpp index 742ab835..85451a37 100644 --- a/sol/types.hpp +++ b/sol/types.hpp @@ -633,6 +633,7 @@ namespace sol { class thread; struct variadic_args; struct this_state; + struct this_environment; namespace detail { template @@ -785,6 +786,9 @@ namespace sol { template <> struct lua_type_of : std::integral_constant {}; + template <> + struct lua_type_of : std::integral_constant {}; + template <> struct lua_type_of : std::integral_constant {}; @@ -901,6 +905,9 @@ namespace sol { template <> struct is_transparent_argument : std::true_type {}; + template <> + struct is_transparent_argument : std::true_type {}; + template <> struct is_transparent_argument : std::true_type {}; diff --git a/test_environments.cpp b/test_environments.cpp index b5e04534..5475d105 100644 --- a/test_environments.cpp +++ b/test_environments.cpp @@ -201,3 +201,35 @@ TEST_CASE("environments/functions", "see if environments on functions are workin REQUIRE(!gtest.valid()); } } + +TEST_CASE("environments/this_environment", "test various situations of pulling out an environment") { + static std::string code = "return f(10)"; + + sol::state lua; + + lua["f"] = [](sol::this_environment te, int x) { + if (te) { + sol::environment& env = te; + return x + static_cast(env["x"]); + } + return x; + }; + + sol::environment e(lua, sol::create, lua.globals()); + e["x"] = 20; + SECTION("from Lua script") { + int value = lua.script(code, e); + REQUIRE(value == 30); + } + SECTION("from C++") { + sol::function f = lua["f"]; + e.set_on(f); + int value = f(10); + REQUIRE(value == 30); + } + SECTION("from C++, with no env") { + sol::function f = lua["f"]; + int value = f(10); + REQUIRE(value == 10); + } +}