add more documentation, add clear() to bytecode, include additional examples and better data

This commit is contained in:
ThePhD 2019-08-15 01:26:52 -04:00
parent 09f5e862b8
commit cb3acaa97b
No known key found for this signature in database
GPG Key ID: 1509DB1C0F702BFA
13 changed files with 356 additions and 154 deletions

View File

@ -24,9 +24,9 @@
# # # Required minimum version statement
cmake_minimum_required(VERSION 3.5.0)
find_package(PythonInterp 3)
find_package(Python3 COMPONENTS Interpreter)
if (NOT PYTHONINTERP_FOUND)
if (NOT Python3_Interpreter_FOUND)
message(FATAL_ERROR "sol2 documentation cannot be generated as python 3 has not been found: install or set the python 3 interpreter for the docs to find it and be sure to pip install sphinx")
endif()

View File

@ -3,61 +3,37 @@ coroutine
*resumable/yielding functions from Lua*
A ``coroutine`` is a :doc:`reference<reference>` to a function in Lua that can be called multiple times to yield a specific result. It is run on the :doc:`lua_State<state>` that was used to create it (see :doc:`thread<thread>` for an example on how to get a coroutine that runs on a thread separate from your usual "main" :doc:`lua_State<state>`).
A ``coroutine`` is a :doc:`reference<reference>` to a function in Lua that can be called multiple times to yield a specific result. It is a cooperative function. It is run on the :doc:`lua_State<state>` that was used to create it (see :doc:`thread<thread>` for an example on how to get a coroutine that runs on a stack space separate from your usual "main" stack space :doc:`lua_State<state>`).
The ``coroutine`` object is entirely similar to the :doc:`protected_function<protected_function>` object, with additional member functions to check if a coroutine has yielded (:doc:`call_status::yielded<types>`) and is thus runnable again, whether it has completed (:ref:`call_status::ok<call-status>`) and thus cannot yield anymore values, or whether it has suffered an error (see :ref:`status()<status>` and :ref:`call_status<call-status>`'s error codes).
The ``coroutine`` object is entirely similar to the :doc:`protected_function<protected_function>` object, with additional member functions to check if a coroutine has yielded (:doc:`call_status::yielded<types>`) and is thus runnable again, whether it has completed (:ref:`call_status::ok<call-status>`) and thus cannot yield anymore values, or whether it has suffered an error (see :ref:`status()<thread-status>`'s and :ref:`call_status<call-status>`'s error codes).
For example, you can work with a coroutine like this:
.. code-block:: lua
.. literalinclude:: ../../../examples/source/docs/coroutine_main.cpp
:caption: co.lua
:name: co-lua
:lines: 8-15
:linenos:
function loop()
while counter ~= 30
do
coroutine.yield(counter);
counter = counter + 1;
end
return counter
end
This is a function that yields. We set the ``counter`` value in C++, and then use the coroutine to get a few values:
This is a function that yields:
.. literalinclude:: ../../../examples/source/docs/coroutine_main.cpp
:caption: coroutine_main.cpp
:name: coroutine_main
:lines: 1-6,18-19,21,25-
:linenos:
.. code-block:: cpp
:caption: main.cpp
:name: yield-main
Note that this code doesn't check for errors: to do so, you can call the function and assign it as ``auto result = loop_coroutine();``, then check ``result.valid()`` as is the case with :doc:`protected_function<protected_function>`.
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::coroutine);
lua.script_file("co.lua");
sol::coroutine cr = lua["loop"];
Finally, you can run this coroutine on another stack space (NOT a different computer thread: Lua uses the term 'thread' a bit strangely, as we follow its usage of the term, but it is NOT a separate thread) by doing the following:
for (int counter = 0; // start from 0
counter < 10 && cr; // we want 10 values, and we only want to run if the coroutine "cr" is valid
// Alternative: counter < 10 && cr.valid()
++counter) {
// Call the coroutine, does the computation and then suspends
int value = cr();
}
Note that this code doesn't check for errors: to do so, you can call the function and assign it as ``auto result = cr();``, then check ``result.valid()`` as is the case with :doc:`protected_function<protected_function>`. Finally, you can run this coroutine on another thread by doing the following:
.. code-block:: cpp
:caption: main_with_thread.cpp
.. literalinclude:: ../../../examples/source/docs/coroutine_thread.cpp
:caption: coroutine_thread.cpp
:name: yield-main-thread
:lines: 1-6,18-19,21,25-
:linenos:
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::coroutine);
lua.script_file("co.lua");
sol::thread runner = sol::thread::create(lua.lua_state());
sol::state_view runnerstate = runner.state();
sol::coroutine cr = runnerstate["loop"];
for (int counter = 0; counter < 10 && cr; ++counter) {
// Call the coroutine, does the computation and then suspends
int value = cr();
}
The following are the members of ``sol::coroutine``:

View File

@ -38,12 +38,12 @@ free functions
template <typename T>
void set_environment( const environment& env, const T& target );
See :ref:`environment::set_on<environment-set-on>`.
See :ref:`environment::set_on<sol-environment-set_on>`.
.. code-block:: cpp
:caption: function: get_environment
:name: sol-environment-set_environment
:name: sol-environment-get_environment
template <typename E = reference, typename T>
basic_environment<E> get_environment( const T& target );

View File

@ -5,9 +5,9 @@ Lua has no thread safety. sol does not force thread safety bottlenecks anywhere.
Assume any access or any call on Lua affects the whole ``sol::state``/``lua_State*`` (because it does, in a fair bit of cases). Therefore, every call to a state should be blocked off in C++ with some kind of access control (when you're working with multiple C++ threads). When you start hitting the same state from multiple threads, race conditions (data or instruction) can happen.
Individual Lua coroutines might be able to run on separate C++-created threads without tanking the state utterly, since each Lua coroutine has the capability to run on an independent Lua execution stack (Lua confusingly calls it a ``thread`` in the C API, but it really just means a separate execution stack) as well as some other associated bits and pieces that won't quite interfere with the global state.
To handle multithreaded environments, it is encouraged to either spawn a Lua state (``sol::state``) for each thread you are working with and keep inter-state communication to synchronized serialization points. This means that 3 C++ threads should have 3 Lua states, and access between them should be controlled using some kind of synchronized C++ mechanism (actual transfer between states must be done by serializing the value into C++ and then re-pushing it into the other state).
To handle multithreaded environments, it is encouraged to either spawn a Lua state (``sol::state``) for each thread you are working with and keep inter-state communication to synchronized serialization points. This means that 3 C++ threads should each have their own Lua state, and access between them should be controlled using some kind of synchronized C++ mechanism (actual transfer between states must be done by serializing the value into C++ and then re-pushing it into the other state).
`Here is an example of a processor that explicitly serializes`_ Lua returns into C++ values, and Lua functions into state-transferrable byte code to do work.
Using coroutines and Lua's threads might also buy you some concurrency and parallelism (**unconfirmed and likely untrue, do not gamble on this**), but remember that Lua's 'threading' technique is ultimately cooperative and requires explicit yielding and resuming (simplified as function calls for :doc:`sol::coroutine<api/coroutine>`).
@ -37,3 +37,5 @@ Here's an example of explicit state transferring below:
.. literalinclude:: ../../examples/source/docs/state_transfer.cpp
:name: state-transfer
:linenos:
.. _Here is an example of a processor that explicitly serializes: https://github.com/ThePhD/sol2/blob/develop/examples/source/docs/std_thread.cpp

View File

@ -0,0 +1,51 @@
#define SOL_ALL_SAFTIES_ON 1
#include <sol/sol.hpp>
#include <iostream>
int main() {
const auto& co_lua_script = R"(
function loop()
while counter ~= 30
do
coroutine.yield(counter);
counter = counter + 1;
end
return counter
end
)";
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::coroutine);
/*
lua.script_file("co.lua");
we load string directly rather than use a file
*/
lua.script(co_lua_script);
sol::coroutine loop_coroutine = lua["loop"];
// set counter variable in C++
// (can set it to something else to
// have loop_coroutine() yield different values)
lua["counter"] = 20;
// example of using and re-using coroutine
// you do not have to use coroutines in a loop,
// this is just the example
// we start from 0;
// we want 10 values, and we only want to
// run if the coroutine "loop_coroutine" is valid
for (int counter = 0; counter < 10 && loop_coroutine; ++counter) {
// Alternative: counter < 10 && cr.valid()
// Call the coroutine, does the computation and then suspends
// once it returns, we get the value back from the return
// and then can use it
// we can either leave the coroutine like that can come to it later,
// or loop back around
int value = loop_coroutine();
std::cout << "In C++: " << value << std::endl;
}
return 0;
}

View File

@ -0,0 +1,36 @@
#define SOL_ALL_SAFTIES_ON 1
#include <sol/sol.hpp>
#include <iostream>
int main() {
const auto& co_lua_script = R"(
function loop()
while counter ~= 30
do
coroutine.yield(counter);
counter = counter + 1;
end
return counter
end
)";
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::coroutine);
/*
lua.script_file("co.lua");
we load string directly rather than use a file
*/
lua.script(co_lua_script);
sol::thread runner = sol::thread::create(lua.lua_state());
sol::state_view runnerstate = runner.state();
sol::coroutine loop_coroutine = lua["loop"];
lua["counter"] = 20;
for (int counter = 0; counter < 10 && loop_coroutine; ++counter) {
// Call the coroutine, does the computation and then suspends
int value = loop_coroutine();
}
return 0;
}

View File

@ -0,0 +1,134 @@
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <variant>
#include <cstddef>
#include <iostream>
struct worker_data {
std::mutex until_ready_mutex;
std::condition_variable until_ready_condition;
bool is_ready = false;
bool is_processed = false;
sol::state worker_lua;
sol::bytecode payload;
std::variant<double, std::vector<double>> return_payload;
worker_data() {
worker_lua.open_libraries(sol::lib::base);
}
};
void worker_thread(worker_data& data) {
for (std::uint64_t loops = 0; true; ++loops) {
// Wait until main() sends data
std::unique_lock<std::mutex> data_lock(data.until_ready_mutex);
data.until_ready_condition.wait(data_lock, [&data] { return data.is_ready; });
if (data.payload.size() == 0) {
// signaling we are done
return;
}
// just for easier typing
sol::state& lua = data.worker_lua;
// we own the lock now, do the work
std::variant<double, std::vector<double>> result = lua.safe_script(data.payload.as_string_view());
// store returning payload,
// clear current payload
data.return_payload = std::move(result);
data.payload.clear();
// Send result back to main
std::cout << "worker_thread data processing is completed: signaling & unlocking\n";
data.is_processed = true;
data.is_ready = false;
data_lock.unlock();
data.until_ready_condition.notify_one();
}
}
int main() {
// main lua state
sol::state lua;
lua.open_libraries(sol::lib::base);
// set up functions, etc. etc.
lua.script("function f () return 4.5 end");
lua.script("function g () return { 1.1, 2.2, 3.3 } end");
// kick off worker
worker_data data;
std::thread worker(worker_thread, std::ref(data));
// main Lua state
bool done_working = false;
for (std::uint64_t loops = 0; !done_working; ++loops) {
// finished working? send nothing
// even loop? use f
// otherwise, use g
if (loops >= 3) {
data.payload.clear();
done_working = true;
}
else if ((loops % 2) == 0) {
sol::function target = lua["f"];
data.payload = target.dump();
}
else {
sol::function target = lua["g"];
data.payload = target.dump();
}
// send data to the worker thread
{
std::lock_guard<std::mutex> lk(data.until_ready_mutex);
data.is_ready = true;
std::cout << "function serialized: sending to worker thread to execute on Lua state...\n";
}
data.until_ready_condition.notify_one();
if (done_working) {
break;
}
// wait for the worker
{
std::unique_lock<std::mutex> lock_waiting_for_worker(data.until_ready_mutex);
data.until_ready_condition.wait(lock_waiting_for_worker, [&data] { return data.is_processed; });
data.is_processed = false;
}
auto data_processor = [](auto& returned_data) {
using option_type = std::remove_cv_t<std::remove_reference_t<decltype(returned_data)>>;
if constexpr (std::is_same_v<option_type, double>) {
std::cout << "received a double: " << returned_data << "\n";
}
else if constexpr (std::is_same_v<option_type, std::vector<double>>) {
std::cout << "received a std::vector<double>: { ";
for (std::size_t i = 0; i < returned_data.size(); ++i) {
std::cout << returned_data[i];
if (i != static_cast<std::size_t>(returned_data.size() - 1)) {
std::cout << ", ";
}
}
std::cout << " }\n";
}
else {
std::cerr << "OH MY GOD YOU FORGOT TO HANDLE A TYPE OF DATA FROM A WORKER ABORT ABORT ABORT\n";
std::abort();
}
};
std::visit(data_processor, data.return_payload);
}
// join and wait for workers to come back
worker.join();
// workers are back, exit program
return 0;
}

View File

@ -5,23 +5,22 @@
#include <memory>
#include <iostream>
int main(int, char* []) {
std::cout << "=== optional with iteration ===" << std::endl;
struct thing {
struct thing {
int a = 20;
thing() = default;
thing(int a) : a(a) {}
};
thing(int a) : a(a) {
}
};
struct super_thing : thing {
struct super_thing : thing {
int b = 40;
};
};
struct unrelated {
struct unrelated {};
};
int main(int, char* []) {
std::cout << "=== optional with iteration ===" << std::endl;
sol::state lua;
@ -29,20 +28,14 @@ int main(int, char* []) {
// to prevent derived class "super_thing"
// from being picked up and cast to its base
// class
lua.new_usertype<super_thing>("super_thing",
sol::base_classes, sol::bases<thing>()
);
lua.new_usertype<super_thing>("super_thing", sol::base_classes, sol::bases<thing>());
// Make a few things
lua["t1"] = thing{};
lua["t2"] = super_thing{};
lua["t3"] = unrelated{};
// And a table
lua["container"] = lua.create_table_with(
0, thing{50},
1, unrelated{},
4, super_thing{}
);
lua["container"] = lua.create_table_with(0, thing{ 50 }, 1, unrelated{}, 4, super_thing{});
std::vector<std::reference_wrapper<thing>> things;
@ -66,8 +59,7 @@ int main(int, char* []) {
case sol::type::table: {
sol::table inner = value.as<sol::table>();
f(f, inner);
}
break;
} break;
case sol::type::userdata: {
// This allows us to check if a userdata is
// a specific class type
@ -83,8 +75,7 @@ int main(int, char* []) {
std::cout << "thing.a ==" << the_thing.a << std::endl;
things.push_back(the_thing);
}
}
break;
} break;
default:
break;
}

View File

@ -75,11 +75,13 @@ namespace sol {
using base_t::rend;
using base_t::swap;
using base_t::get_allocator;
using base_t::swap;
using base_t::clear;
using base_t::emplace;
using base_t::emplace_back;
using base_t::erase;
using base_t::insert;
using base_t::pop_back;
using base_t::push_back;
@ -87,7 +89,7 @@ namespace sol {
using base_t::resize;
using base_t::shrink_to_fit;
string_view as_string_view () const {
string_view as_string_view() const {
return string_view(reinterpret_cast<const char*>(this->data()), this->size());
}
};
@ -100,7 +102,7 @@ namespace sol {
try {
bc.insert(bc.cend(), p_code, p_code + memory_size);
}
catch ( ... ) {
catch (...) {
return -1;
}
return 0;

View File

@ -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 2019-08-12 18:06:54.421648 UTC
// This header was generated with sol v3.0.3 (revision 5799084)
// Generated 2019-08-15 05:26:10.792424 UTC
// This header was generated with sol v3.0.3 (revision 09f5e86)
// https://github.com/ThePhD/sol2
#ifndef SOL_SINGLE_INCLUDE_FORWARD_HPP

View File

@ -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 2019-08-12 18:06:53.939935 UTC
// This header was generated with sol v3.0.3 (revision 5799084)
// Generated 2019-08-15 05:26:10.095950 UTC
// This header was generated with sol v3.0.3 (revision 09f5e86)
// https://github.com/ThePhD/sol2
#ifndef SOL_SINGLE_INCLUDE_HPP
@ -3507,11 +3507,13 @@ namespace sol {
using base_t::rbegin;
using base_t::rend;
using base_t::swap;
using base_t::get_allocator;
using base_t::swap;
using base_t::clear;
using base_t::emplace;
using base_t::emplace_back;
using base_t::erase;
using base_t::insert;
using base_t::pop_back;
using base_t::push_back;
@ -3519,7 +3521,7 @@ namespace sol {
using base_t::resize;
using base_t::shrink_to_fit;
string_view as_string_view () const {
string_view as_string_view() const {
return string_view(reinterpret_cast<const char*>(this->data()), this->size());
}
};
@ -3532,7 +3534,7 @@ namespace sol {
try {
bc.insert(bc.cend(), p_code, p_code + memory_size);
}
catch ( ... ) {
catch (...) {
return -1;
}
return 0;

View File

@ -40,9 +40,11 @@
TEST_CASE("containers/returns", "make sure that even references to vectors are being serialized as tables") {
sol::state lua;
std::vector<int> v{ 1, 2, 3 };
lua.set_function("f", [&]() -> std::vector<int>& {
auto f = [&]() -> std::vector<int>& {
REQUIRE(v.size() == 3);
return v;
});
};
lua.set_function("f", f);
auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error);
REQUIRE(result1.valid());
sol::object x = lua["x"];
@ -64,10 +66,14 @@ TEST_CASE("containers/custom usertype", "make sure container usertype metatables
sol::state lua;
lua.open_libraries();
lua.new_usertype<bark>("bark",
"something", [](const bark& b) {
INFO("It works: " << b.at(24));
},
"size", &bark::size, "at", sol::resolve<const int&>(&bark::at), "clear", &bark::clear);
"something",
[](const bark& b) { INFO("It works: " << b.at(24)); },
"size",
&bark::size,
"at",
sol::resolve<const int&>(&bark::at),
"clear",
&bark::clear);
bark obj{ { 24, 50 } };
lua.set("a", &obj);
{
@ -175,7 +181,8 @@ func = function(vecs)
print(i, ":", vec.x, vec.y, vec.z)
end
end
)", sol::script_pass_on_error);
)",
sol::script_pass_on_error);
REQUIRE(result0.valid());
sol::protected_function f(lua["func"]);
@ -185,12 +192,12 @@ end
REQUIRE(pfr2.valid());
}
TEST_CASE("containers/usertype transparency", "Make sure containers pass their arguments through transparently and push the results as references, not new values") {
TEST_CASE(
"containers/usertype transparency", "Make sure containers pass their arguments through transparently and push the results as references, not new values") {
class A {
public:
int a;
A(int b = 2)
: a(b){};
A(int b = 2) : a(b){};
void func() {
}
@ -208,13 +215,13 @@ TEST_CASE("containers/usertype transparency", "Make sure containers pass their a
};
sol::state lua;
lua.new_usertype<B>("B",
"a_list", &B::a_list);
lua.new_usertype<B>("B", "a_list", &B::a_list);
auto result = lua.safe_script(R"(
b = B.new()
a_ref = b.a_list[2]
)", sol::script_pass_on_error);
)",
sol::script_pass_on_error);
REQUIRE(result.valid());
B& b = lua["b"];
@ -265,13 +272,10 @@ TEST_CASE("containers/is container", "make sure the is_container trait behaves p
sol::state lua;
lua.open_libraries();
lua.new_usertype<options>("options_type",
"output_help", &options::output_help);
lua.new_usertype<options>("options_type", "output_help", &options::output_help);
lua.new_usertype<machine>("machine_type",
"new", sol::no_constructor,
"opt", [](machine& m) { return &m.opt; },
"copy_opt", [](machine& m) { return m.opt; });
lua.new_usertype<machine>(
"machine_type", "new", sol::no_constructor, "opt", [](machine& m) { return &m.opt; }, "copy_opt", [](machine& m) { return m.opt; });
{
machine m;
@ -279,7 +283,8 @@ TEST_CASE("containers/is container", "make sure the is_container trait behaves p
auto result0 = lua.safe_script(R"(
machine:opt():output_help()
)", sol::script_pass_on_error);
)",
sol::script_pass_on_error);
REQUIRE(result0.valid());
REQUIRE(options::last == &m.opt);
@ -300,10 +305,11 @@ TEST_CASE("containers/readonly", "make sure readonly members are stored appropri
std::list<bar> seq;
};
lua.new_usertype<foo>(
"foo",
"seq", &foo::seq, // this one works
"readonly_seq", sol::readonly(&foo::seq) // this one does not work
lua.new_usertype<foo>("foo",
"seq",
&foo::seq, // this one works
"readonly_seq",
sol::readonly(&foo::seq) // this one does not work
);
lua["value"] = std::list<bar>{ {}, {}, {} };
@ -312,7 +318,8 @@ a = foo.new()
x = a.seq
a.seq = value
y = a.readonly_seq
)", sol::script_pass_on_error);
)",
sol::script_pass_on_error);
REQUIRE(result0.valid());
std::list<bar>& seqrefx = lua["x"];
std::list<bar>& seqrefy = lua["y"];
@ -373,7 +380,8 @@ function f_append(vec)
vec[#vec + 1] = -54
print("#vec in lua: " .. #vec)
end
)", sol::script_pass_on_error);
)",
sol::script_pass_on_error);
REQUIRE(result1.valid());
std::vector<int> fill_cmp{ 1, 2, 3 };
@ -397,8 +405,7 @@ TEST_CASE("containers/non_copyable", "make sure non-copyable types in containers
struct test {
std::vector<non_copyable> b;
test()
: b() {
test() : b() {
}
test(test&&) = default;
test& operator=(test&&) = default;
@ -408,8 +415,7 @@ TEST_CASE("containers/non_copyable", "make sure non-copyable types in containers
SECTION("normal") {
sol::state lua;
lua.new_usertype<test>("test",
"b", sol::readonly(&test::b));
lua.new_usertype<test>("test", "b", sol::readonly(&test::b));
lua["v"] = std::vector<non_copyable>{};
@ -487,7 +493,8 @@ TEST_CASE("containers/pairs", "test how well pairs work with the underlying syst
TEST_CASE("containers/pointer types", "check that containers with unique usertypes and pointers or something") {
struct base_t {
virtual int get() const = 0;
virtual ~base_t(){}
virtual ~base_t() {
}
};
struct derived_1_t : base_t {

View File

@ -137,12 +137,9 @@ TEST_CASE("tables/create-with-local", "Check if creating a table is kosher") {
TEST_CASE("tables/function variables", "Check if tables and function calls work as intended") {
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::os);
auto run_script = [](sol::state& lua) -> void {
lua.safe_script("assert(os.fun() == \"test\")");
};
auto run_script = [](sol::state& lua) -> void { lua.safe_script("assert(os.fun() == \"test\")"); };
lua.get<sol::table>("os").set_function("fun",
[]() {
lua.get<sol::table>("os").set_function("fun", []() {
INFO("stateless lambda()");
return "test";
});
@ -165,8 +162,7 @@ TEST_CASE("tables/function variables", "Check if tables and function calls work
// stateful lambda: non-convertible, cannot be optimised
int breakit = 50;
lua.get<sol::table>("os").set_function("fun",
[&breakit]() {
lua.get<sol::table>("os").set_function("fun", [&breakit]() {
INFO("stateful lambda() with breakit:" << breakit);
return "test";
});
@ -203,9 +199,10 @@ TEST_CASE("tables/add", "Basic test to make sure the 'add' feature works") {
TEST_CASE("tables/raw set and raw get", "ensure raw setting and getting works through metatables") {
sol::state lua;
sol::table t = lua.create_table();
t[sol::metatable_key] = lua.create_table_with(
sol::meta_function::new_index, [](lua_State* L) { return luaL_error(L, "nay"); },
sol::meta_function::index, [](lua_State* L) { return luaL_error(L, "nay"); });
t[sol::metatable_key] = lua.create_table_with(sol::meta_function::new_index,
[](lua_State* L) { return luaL_error(L, "nay"); },
sol::meta_function::index,
[](lua_State* L) { return luaL_error(L, "nay"); });
t.raw_set("a", 2.5);
double la = t.raw_get<double>("a");
REQUIRE(la == 2.5);
@ -241,9 +238,13 @@ TEST_CASE("tables/optional move", "ensure pushing a sol::optional<T> rvalue corr
sol::state sol_state;
struct move_only {
int secret_code;
move_only(int sc) : secret_code(sc) {}
move_only(const move_only&) = delete;
move_only(move_only&&) = default;
move_only& operator=(const move_only&) = delete;
move_only& operator=(move_only&&) = default;
};
sol_state["requires_move"] = sol::optional<move_only>{ move_only{ 0x4D } };
sol_state["requires_move"] = sol::optional<move_only>(move_only(0x4D));
REQUIRE(sol_state["requires_move"].get<move_only>().secret_code == 0x4D);
}