summaryrefslogtreecommitdiffstats
path: root/ext/luawrapper/include
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--ext/luawrapper/include/LuaContext.hpp3009
1 files changed, 3009 insertions, 0 deletions
diff --git a/ext/luawrapper/include/LuaContext.hpp b/ext/luawrapper/include/LuaContext.hpp
new file mode 100644
index 0000000..ad6c86e
--- /dev/null
+++ b/ext/luawrapper/include/LuaContext.hpp
@@ -0,0 +1,3009 @@
+/*
+Copyright (c) 2013, Pierre KRIEGER
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef INCLUDE_LUACONTEXT_HPP
+#define INCLUDE_LUACONTEXT_HPP
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <functional>
+#include <limits>
+#include <list>
+#include <map>
+#include <memory>
+#include <random>
+#include <set>
+#include <stdexcept>
+#include <string>
+#include <sstream>
+#include <tuple>
+#include <type_traits>
+#include <unordered_map>
+#include <boost/any.hpp>
+#include <boost/format.hpp>
+#include <boost/mpl/distance.hpp>
+#include <boost/mpl/transform.hpp>
+#include <boost/optional.hpp>
+#include <boost/variant.hpp>
+#include <boost/type_traits.hpp>
+#include <lua.hpp>
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+# include "misc/exception.hpp"
+#endif
+
+#ifdef __GNUC__
+# define ATTR_UNUSED __attribute__((unused))
+#else
+# define ATTR_UNUSED
+#endif
+
+#define LUACONTEXT_GLOBAL_EQ "e5ddced079fc405aa4937b386ca387d2"
+#define EQ_FUNCTION_NAME "__eq"
+#define TOSTRING_FUNCTION_NAME "__tostring"
+
+/**
+ * Defines a Lua context
+ * A Lua context is used to interpret Lua code. Since everything in Lua is a variable (including functions),
+ * we only provide few functions like readVariable and writeVariable.
+ *
+ * You can also write variables with C++ functions so that they are callable by Lua. Note however that you HAVE TO convert
+ * your function to std::function (not directly std::bind or a lambda function) so the class can detect which argument types
+ * it wants. These arguments may only be of basic types (int, float, etc.) or std::string.
+ */
+
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+
+class LuaContext {
+ struct ValueInRegistry;
+ template<typename TFunctionObject, typename TFirstParamType> struct Binder;
+ template<typename T> struct IsOptional;
+ enum Globals_t { Globals }; // tag for "global variables"
+public:
+ /**
+ * @param openDefaultLibs True if luaL_openlibs should be called
+ */
+ explicit LuaContext(bool openDefaultLibs = true)
+ {
+ // luaL_newstate can return null if allocation failed
+ mState = luaL_newstate();
+ if (mState == nullptr)
+ throw std::bad_alloc();
+
+ // setting the panic function
+ lua_atpanic(mState, [](lua_State* state) -> int {
+ const std::string str = lua_tostring(state, -1);
+ lua_pop(state, 1);
+ assert(false && "lua_atpanic triggered");
+ exit(0);
+ });
+
+ // opening default library if required to do so
+ if (openDefaultLibs)
+ luaL_openlibs(mState);
+
+ writeGlobalEq();
+ }
+
+ void writeGlobalEq() {
+ const auto eqFunction = [](lua_State* lua) -> int {
+ try {
+ lua_pushstring(lua, "__eq");
+ lua_gettable(lua, -2);
+ /* if not found, return false */
+ if (lua_isnil(lua, -1)) {
+ lua_pop(lua, -2);
+ lua_pushboolean(lua, false);
+ return 1;
+ }
+ lua_insert(lua, lua_gettop(lua)-2);
+ return callRaw(lua, PushedObject{lua, 3}, 1).release();
+ } catch(...) {
+ Pusher<std::exception_ptr>::push(lua, std::current_exception()).release();
+ luaError(lua);
+ }
+ };
+ lua_pushcfunction(mState, eqFunction);
+ lua_setglobal(mState, LUACONTEXT_GLOBAL_EQ);
+ };
+
+ /**
+ * Move constructor
+ */
+ LuaContext(LuaContext&& s) :
+ mState(s.mState)
+ {
+ s.mState = luaL_newstate();
+ }
+
+ /**
+ * Move operator
+ */
+ LuaContext& operator=(LuaContext&& s) noexcept
+ {
+ std::swap(mState, s.mState);
+ return *this;
+ }
+
+ /**
+ * Copy is forbidden
+ */
+ LuaContext(const LuaContext&) = delete;
+
+ /**
+ * Copy is forbidden
+ */
+ LuaContext& operator=(const LuaContext&) = delete;
+
+ /**
+ * Destructor
+ */
+ ~LuaContext() noexcept
+ {
+ assert(mState);
+ lua_close(mState);
+ }
+
+ /**
+ * Thrown when an error happens during execution of lua code (like not enough parameters for a function)
+ */
+ class ExecutionErrorException : public std::runtime_error
+ {
+ public:
+ ExecutionErrorException(const std::string& msg) :
+ std::runtime_error(msg)
+ {
+ }
+ };
+
+ /**
+ * Thrown when a syntax error happens in a lua script
+ */
+ class SyntaxErrorException : public std::runtime_error
+ {
+ public:
+ SyntaxErrorException(const std::string& msg) :
+ std::runtime_error(msg)
+ {
+ }
+ };
+
+ /**
+ * Thrown when trying to cast a Lua variable to an unvalid type, eg. trying to read a number when the variable is a string
+ */
+ class WrongTypeException : public std::runtime_error
+ {
+ public:
+ WrongTypeException(const std::string& luaType_, const std::type_info& destination_) :
+ std::runtime_error("Trying to cast a lua variable from \"" + luaType_ + "\" to \"" + destination_.name() + "\""),
+ luaType(luaType_),
+ destination(destination_)
+ {
+ }
+
+ std::string luaType;
+ const std::type_info& destination;
+ };
+
+ /**
+ * Function object that can call a function stored by Lua
+ * This type is copiable and movable, but not constructible. It can only be created through readVariable.
+ * @tparam TFunctionType Function type (eg. "int (int, bool)")
+ */
+ template<typename TFunctionType>
+ class LuaFunctionCaller;
+
+ /**
+ * Opaque type that identifies a Lua object
+ */
+ struct LuaObject {
+ LuaObject() = default;
+ LuaObject(lua_State* state, int index=-1) {
+ this->objectInRegistry = std::make_shared<LuaContext::ValueInRegistry>(state, index);
+ }
+ std::shared_ptr<LuaContext::ValueInRegistry> objectInRegistry;
+ };
+
+ /**
+ * Opaque type that identifies a Lua thread
+ */
+ struct ThreadID {
+ ThreadID() = default;
+ ThreadID(ThreadID&& o) : state(o.state), threadInRegistry(std::move(o.threadInRegistry)) { }
+ ThreadID& operator=(ThreadID&& o) { std::swap(state, o.state); std::swap(threadInRegistry, o.threadInRegistry); return *this; }
+ public:
+ friend LuaContext;
+ lua_State* state;
+ std::unique_ptr<ValueInRegistry> threadInRegistry;
+ };
+
+ /**
+ * Type that is considered as an empty array
+ */
+ enum EmptyArray_t { EmptyArray };
+
+ /**
+ * Type for a metatable
+ */
+ enum Metatable_t { Metatable };
+
+ /**
+ * Executes lua code from the stream
+ * @param code A stream that Lua will read its code from
+ */
+ void executeCode(std::istream& code)
+ {
+ auto toCall = load(mState, code);
+ call<std::tuple<>>(mState, std::move(toCall));
+ }
+
+ /**
+ * Executes lua code from the stream and returns a value
+ * @param code A stream that Lua will read its code from
+ * @tparam TType The type that the executing code should return
+ */
+ template<typename TType>
+ auto executeCode(std::istream& code)
+ -> TType
+ {
+ auto toCall = load(mState, code);
+ return call<TType>(mState, std::move(toCall));
+ }
+
+ /**
+ * Executes lua code given as parameter
+ * @param code A string containing code that will be executed by Lua
+ */
+ void executeCode(const std::string& code)
+ {
+ executeCode(code.c_str());
+ }
+
+ /*
+ * Executes Lua code from the stream and returns a value
+ * @param code A string containing code that will be executed by Lua
+ * @tparam TType The type that the executing code should return
+ */
+ template<typename TType>
+ auto executeCode(const std::string& code)
+ -> TType
+ {
+ return executeCode<TType>(code.c_str());
+ }
+
+ /**
+ * Executes Lua code
+ * @param code A string containing code that will be executed by Lua
+ */
+ void executeCode(const char* code)
+ {
+ auto toCall = load(mState, code);
+ call<std::tuple<>>(mState, std::move(toCall));
+ }
+
+ /*
+ * Executes Lua code from the stream and returns a value
+ * @param code A string containing code that will be executed by Lua
+ * @tparam TType The type that the executing code should return
+ */
+ template<typename TType>
+ auto executeCode(const char* code)
+ -> TType
+ {
+ auto toCall = load(mState, code);
+ return call<TType>(mState, std::move(toCall));
+ }
+
+ /**
+ * Executes lua code from the stream
+ * @param code A stream that Lua will read its code from
+ */
+ void executeCode(const ThreadID& thread, std::istream& code)
+ {
+ auto toCall = load(thread.state, code);
+ call<std::tuple<>>(thread.state, std::move(toCall));
+ }
+
+ /**
+ * Executes lua code from the stream and returns a value
+ * @param code A stream that Lua will read its code from
+ * @tparam TType The type that the executing code should return
+ */
+ template<typename TType>
+ auto executeCode(const ThreadID& thread, std::istream& code)
+ -> TType
+ {
+ auto toCall = load(thread.state, code);
+ return call<TType>(thread.state, std::move(toCall));
+ }
+
+ /**
+ * Executes lua code given as parameter
+ * @param code A string containing code that will be executed by Lua
+ */
+ void executeCode(const ThreadID& thread, const std::string& code)
+ {
+ executeCode(thread, code.c_str());
+ }
+
+ /*
+ * Executes Lua code from the stream and returns a value
+ * @param code A string containing code that will be executed by Lua
+ * @tparam TType The type that the executing code should return
+ */
+ template<typename TType>
+ auto executeCode(const ThreadID& thread, const std::string& code)
+ -> TType
+ {
+ return executeCode<TType>(thread, code.c_str());
+ }
+
+ /**
+ * Executes Lua code
+ * @param code A string containing code that will be executed by Lua
+ */
+ void executeCode(const ThreadID& thread, const char* code)
+ {
+ auto toCall = load(thread.state, code);
+ call<std::tuple<>>(thread.state, std::move(toCall));
+ }
+
+ /*
+ * Executes Lua code from the stream and returns a value
+ * @param code A string containing code that will be executed by Lua
+ * @tparam TType The type that the executing code should return
+ */
+ template<typename TType>
+ auto executeCode(const ThreadID& thread, const char* code)
+ -> TType
+ {
+ auto toCall = load(thread.state, code);
+ return call<TType>(thread.state, std::move(toCall));
+ }
+
+ /**
+ * Tells that Lua will be allowed to access an object's function
+ * This is the version "registerFunction(name, &Foo::function)"
+ */
+ template<typename TPointerToMemberFunction>
+ auto registerFunction(const std::string& name, TPointerToMemberFunction pointer)
+ -> typename std::enable_if<std::is_member_function_pointer<TPointerToMemberFunction>::value>::type
+ {
+ registerFunctionImpl(name, std::mem_fn(pointer), tag<TPointerToMemberFunction>{});
+ }
+
+ /**
+ * Tells that Lua will be allowed to access an object's function
+ * This is the version with an explicit template parameter: "registerFunction<void (Foo::*)()>(name, [](Foo&) { })"
+ * @param fn Function object which takes as first parameter a reference to the object
+ * @tparam TFunctionType Pointer-to-member function type
+ */
+ template<typename TFunctionType, typename TType>
+ void registerFunction(const std::string& functionName, TType fn)
+ {
+ static_assert(std::is_member_function_pointer<TFunctionType>::value, "registerFunction must take a member function pointer type as template parameter");
+ registerFunctionImpl(functionName, std::move(fn), tag<TFunctionType>{});
+ }
+
+ /**
+ * Tells that Lua will be allowed to access an object's function
+ * This is the alternative version with an explicit template parameter: "registerFunction<Foo, void (*)()>(name, [](Foo&) { })"
+ * @param fn Function object which takes as first parameter a reference to the object
+ * @tparam TObject Object to register this function to
+ * @tparam TFunctionType Function type
+ */
+ template<typename TObject, typename TFunctionType, typename TType>
+ void registerFunction(const std::string& functionName, TType fn)
+ {
+ static_assert(std::is_function<TFunctionType>::value, "registerFunction must take a function type as template parameter");
+ registerFunctionImpl(functionName, std::move(fn), tag<TObject>{}, tag<TFunctionType>{});
+ }
+
+ /**
+ * Wrappers for registering "__eq" function in case we want to change this to something else some day
+ */
+
+ template<typename TPointerToMemberFunction>
+ auto registerEqFunction(TPointerToMemberFunction pointer)
+ -> typename std::enable_if<std::is_member_function_pointer<TPointerToMemberFunction>::value>::type
+ {
+ registerFunctionImpl(EQ_FUNCTION_NAME, std::mem_fn(pointer), tag<TPointerToMemberFunction>{});
+ }
+
+ template<typename TFunctionType, typename TType>
+ void registerEqFunction(TType fn)
+ {
+ static_assert(std::is_member_function_pointer<TFunctionType>::value, "registerFunction must take a member function pointer type as template parameter");
+ registerFunctionImpl(EQ_FUNCTION_NAME, std::move(fn), tag<TFunctionType>{});
+ }
+
+ template<typename TObject, typename TFunctionType, typename TType>
+ void registerEqFunction(TType fn)
+ {
+ static_assert(std::is_function<TFunctionType>::value, "registerFunction must take a function type as template parameter");
+ registerFunctionImpl(EQ_FUNCTION_NAME, std::move(fn), tag<TObject>{}, tag<TFunctionType>{});
+ }
+
+ /**
+ * Wrappers for registering "__tostring" function in case we want to change this to something else some day
+ */
+
+ template<typename TPointerToMemberFunction>
+ auto registerToStringFunction(TPointerToMemberFunction pointer)
+ -> typename std::enable_if<std::is_member_function_pointer<TPointerToMemberFunction>::value>::type
+ {
+ registerFunctionImpl(TOSTRING_FUNCTION_NAME, std::mem_fn(pointer), tag<TPointerToMemberFunction>{});
+ }
+
+ template<typename TFunctionType, typename TType>
+ void registerToStringFunction(TType fn)
+ {
+ static_assert(std::is_member_function_pointer<TFunctionType>::value, "registerFunction must take a member function pointer type as template parameter");
+ registerFunctionImpl(TOSTRING_FUNCTION_NAME, std::move(fn), tag<TFunctionType>{});
+ }
+
+ template<typename TObject, typename TFunctionType, typename TType>
+ void registerToStringFunction(TType fn)
+ {
+ static_assert(std::is_function<TFunctionType>::value, "registerFunction must take a function type as template parameter");
+ registerFunctionImpl(TOSTRING_FUNCTION_NAME, std::move(fn), tag<TObject>{}, tag<TFunctionType>{});
+ }
+
+ /**
+ * Inverse operation of registerFunction
+ * @tparam TType Type whose function belongs to
+ */
+ template<typename TType>
+ void unregisterFunction(const std::string& /*functionName*/)
+ {
+ lua_pushlightuserdata(mState, const_cast<std::type_info*>(&typeid(TType)));
+ lua_pushnil(mState);
+ lua_settable(mState, LUA_REGISTRYINDEX);
+ checkTypeRegistration(mState, &typeid(TType));
+
+ lua_pushlightuserdata(mState, const_cast<std::type_info*>(&typeid(TType*)));
+ lua_pushnil(mState);
+ lua_settable(mState, LUA_REGISTRYINDEX);
+ checkTypeRegistration(mState, &typeid(TType*));
+
+ lua_pushlightuserdata(mState, const_cast<std::type_info*>(&typeid(std::shared_ptr<TType>)));
+ lua_pushnil(mState);
+ lua_settable(mState, LUA_REGISTRYINDEX);
+ checkTypeRegistration(mState, &typeid(std::shared_ptr<TType>));
+ }
+
+ /**
+ * Registers a member variable
+ * This is the version "registerMember(name, &Foo::member)"
+ */
+ template<typename TObject, typename TVarType>
+ void registerMember(const std::string& name, TVarType TObject::*member)
+ {
+ // implementation simply calls the custom member with getter and setter
+ const auto getter = [=](const TObject& obj) -> TVarType { return obj.*member; };
+ const auto setter = [=](TObject& obj, const TVarType& value) { obj.*member = value; };
+ registerMember<TVarType (TObject::*)>(name, getter, setter);
+ }
+
+ /**
+ * Registers a member variable
+ * This is the version "registerMember<Foo, int>(name, getter, setter)"
+ * @tparam TObject Type to register the member to
+ * @tparam TVarType Type of the member
+ * @param name Name of the member to register
+ * @param readFunction Function of type "TVarType (const TObject&)"
+ * @param writeFunction_ Function of type "void (TObject&, const TVarType&)"
+ */
+ template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction>
+ void registerMember(const std::string& name, TReadFunction readFunction, TWriteFunction writeFunction_)
+ {
+ registerMemberImpl<TObject,TVarType>(name, std::move(readFunction), std::move(writeFunction_));
+ }
+
+ /**
+ * Registers a member variable
+ * This is the version "registerMember<int (Foo::*)>(name, getter, setter)"
+ * @tparam TMemberType Pointer to member object representing the type
+ * @param name Name of the member to register
+ * @param readFunction Function of type "TVarType (const TObject&)"
+ * @param writeFunction_ Function of type "void (TObject&, const TVarType&)"
+ */
+ template<typename TMemberType, typename TReadFunction, typename TWriteFunction>
+ void registerMember(const std::string& name, TReadFunction readFunction, TWriteFunction writeFunction_)
+ {
+ static_assert(std::is_member_object_pointer<TMemberType>::value, "registerMember must take a member object pointer type as template parameter");
+ registerMemberImpl(tag<TMemberType>{}, name, std::move(readFunction), std::move(writeFunction_));
+ }
+
+ /**
+ * Registers a non-modifiable member variable
+ * This is the version "registerMember<Foo, int>(name, getter)"
+ * @tparam TObject Type to register the member to
+ * @tparam TVarType Type of the member
+ * @param name Name of the member to register
+ * @param readFunction Function of type "TVarType (const TObject&)"
+ */
+ template<typename TObject, typename TVarType, typename TReadFunction>
+ void registerMember(const std::string& name, TReadFunction readFunction)
+ {
+ registerMemberImpl<TObject,TVarType>(name, std::move(readFunction));
+ }
+
+ /**
+ * Registers a non-modifiable member variable
+ * This is the version "registerMember<int (Foo::*)>(name, getter)"
+ * @tparam TMemberType Pointer to member object representing the type
+ * @param name Name of the member to register
+ * @param readFunction Function of type "TVarType (const TObject&)"
+ */
+ template<typename TMemberType, typename TReadFunction>
+ void registerMember(const std::string& name, TReadFunction readFunction)
+ {
+ static_assert(std::is_member_object_pointer<TMemberType>::value, "registerMember must take a member object pointer type as template parameter");
+ registerMemberImpl(tag<TMemberType>{}, name, std::move(readFunction));
+ }
+
+ /**
+ * Registers a dynamic member variable
+ * This is the version "registerMember<Foo, int>(getter, setter)"
+ * @tparam TObject Type to register the member to
+ * @tparam TVarType Type of the member
+ * @param readFunction Function of type "TVarType (const TObject&, const std::string&)"
+ * @param writeFunction_ Function of type "void (TObject&, const std::string&, const TVarType&)"
+ */
+ template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction>
+ void registerMember(TReadFunction readFunction, TWriteFunction writeFunction_)
+ {
+ registerMemberImpl<TObject,TVarType>(std::move(readFunction), std::move(writeFunction_));
+ }
+
+ /**
+ * Registers a dynamic member variable
+ * This is the version "registerMember<int (Foo::*)>(getter, setter)"
+ * @tparam TMemberType Pointer to member object representing the type
+ * @param readFunction Function of type "TVarType (const TObject&, const std::string&)"
+ * @param writeFunction_ Function of type "void (TObject&, const std::string&, const TVarType&)"
+ */
+ template<typename TMemberType, typename TReadFunction, typename TWriteFunction>
+ void registerMember(TReadFunction readFunction, TWriteFunction writeFunction_)
+ {
+ static_assert(std::is_member_object_pointer<TMemberType>::value, "registerMember must take a member object pointer type as template parameter");
+ registerMemberImpl(tag<TMemberType>{}, std::move(readFunction), std::move(writeFunction_));
+ }
+
+ /**
+ * Registers a dynamic non-modifiable member variable
+ * This is the version "registerMember<Foo, int>(getter)"
+ * @tparam TObject Type to register the member to
+ * @tparam TVarType Type of the member
+ * @param readFunction Function of type "TVarType (const TObject&, const std::string&)"
+ */
+ template<typename TObject, typename TVarType, typename TReadFunction>
+ void registerMember(TReadFunction readFunction)
+ {
+ registerMemberImpl<TObject, TVarType>(std::move(readFunction));
+ }
+
+ /**
+ * Registers a dynamic non-modifiable member variable
+ * This is the version "registerMember<int (Foo::*)>(getter)"
+ * @tparam TMemberType Pointer to member object representing the type
+ * @param readFunction Function of type "TVarType (const TObject&, const std::string&)"
+ */
+ template<typename TMemberType, typename TReadFunction>
+ void registerMember(TReadFunction readFunction)
+ {
+ static_assert(std::is_member_object_pointer<TMemberType>::value, "registerMember must take a member object pointer type as template parameter");
+ registerMemberImpl(tag<TMemberType>{}, std::move(readFunction));
+ }
+
+ /**
+ * Creates a new thread
+ * A Lua thread is not really a thread, but rather an "execution stack".
+ * You can destroy the thread by calling destroyThread
+ * @sa destroyThread
+ */
+ auto createThread()
+ -> ThreadID
+ {
+ ThreadID result;
+
+ result.state = lua_newthread(mState);
+ result.threadInRegistry = std::unique_ptr<ValueInRegistry>(new ValueInRegistry(mState));
+ lua_pop(mState, 1);
+
+ return result;
+ }
+
+ /**
+ * Destroys a thread created with createThread
+ * @sa createThread
+ */
+ void destroyThread(ThreadID& id)
+ {
+ id.threadInRegistry.reset();
+ }
+
+ /**
+ * Reads the content of a Lua variable
+ *
+ * @tparam TType Type requested for the read
+ * @throw WrongTypeException When the variable is not convertible to the requested type
+ * @sa writeVariable
+ *
+ * Readable types are all types accepted by writeVariable except nullptr, std::unique_ptr and function pointers
+ * Additionally supported:
+ * - LuaFunctionCaller<FunctionType>, which is an alternative to std::function
+ * - references to custom objects, in which case it will return the object in-place
+ *
+ * After the variable name, you can add other parameters.
+ * If the variable is an array, it will instead get the element of that array whose offset is the second parameter.
+ * Same applies for third, fourth, etc. parameters.
+ */
+ template<typename TType, typename... TTypes>
+ TType readVariable(const std::string& name, TTypes&&... elements) const
+ {
+ lua_getglobal(mState, name.c_str());
+ lookIntoStackTop(mState, std::forward<TTypes>(elements)...);
+ return readTopAndPop<TType>(mState, PushedObject{mState, 1});
+ }
+
+ /**
+ * @sa readVariable
+ */
+ template<typename TType, typename... TTypes>
+ TType readVariable(const char* name, TTypes&&... elements) const
+ {
+ lua_getglobal(mState, name);
+ lookIntoStackTop(mState, std::forward<TTypes>(elements)...);
+ return readTopAndPop<TType>(mState, PushedObject{mState, 1});
+ }
+
+ /**
+ * @sa readVariable
+ */
+ template<typename TType, typename... TTypes>
+ TType readVariable(const ThreadID& thread, const std::string& name, TTypes&&... elements) const
+ {
+ lua_getglobal(thread.state, name.c_str());
+ lookIntoStackTop(thread.state, std::forward<TTypes>(elements)...);
+ return readTopAndPop<TType>(thread.state, PushedObject{thread.state, 1});
+ }
+
+ /**
+ * @sa readVariable
+ */
+ template<typename TType, typename... TTypes>
+ TType readVariable(const ThreadID& thread, const char* name, TTypes&&... elements) const
+ {
+ lua_getglobal(thread.state, name);
+ lookIntoStackTop(thread.state, std::forward<TTypes>(elements)...);
+ return readTopAndPop<TType>(thread.state, PushedObject{thread.state, 1});
+ }
+
+ /**
+ * Changes the content of a Lua variable
+ *
+ * Accepted values are:
+ * - all base types (char, short, int, float, double, bool)
+ * - std::string
+ * - enums
+ * - std::vector<>
+ * - std::vector<std::pair<>>, std::map<> and std::unordered_map<> (the key and value must also be accepted values)
+ * - std::function<> (all parameters must be accepted values, and return type must be either an accepted value for readVariable or a tuple)
+ * - std::shared_ptr<> (std::unique_ptr<> are converted to std::shared_ptr<>)
+ * - nullptr (writes nil)
+ * - any object
+ *
+ * All objects are passed by copy and destroyed by the garbage collector if necessary.
+ */
+ template<typename... TData>
+ void writeVariable(TData&&... data) noexcept {
+ static_assert(sizeof...(TData) >= 2, "You must pass at least a variable name and a value to writeVariable");
+ typedef typename std::decay<typename std::tuple_element<sizeof...(TData) - 1,std::tuple<TData...>>::type>::type
+ RealDataType;
+ static_assert(!std::is_same<typename Tupleizer<RealDataType>::type,RealDataType>::value, "Error: you can't use LuaContext::writeVariable with a tuple");
+
+ setTable<RealDataType>(mState, Globals, std::forward<TData>(data)...);
+ }
+
+ /**
+ * Equivalent to writeVariable(varName, ..., std::function<TFunctionType>(data));
+ * This version is more efficient than writeVariable if you want to write functions
+ */
+ template<typename TFunctionType, typename... TData>
+ void writeFunction(TData&&... data) noexcept {
+ static_assert(sizeof...(TData) >= 2, "You must pass at least a variable name and a value to writeFunction");
+
+ setTable<TFunctionType>(mState, Globals, std::forward<TData>(data)...);
+ }
+
+ /**
+ * Same as the other writeFunction, except that the template parameter is automatically detected
+ * This only works if the data is either a native function pointer, or contains one operator() (this is the case for lambdas)
+ */
+ template<typename... TData>
+ void writeFunction(TData&&... data) noexcept {
+ static_assert(sizeof...(TData) >= 2, "You must pass at least a variable name and a value to writeFunction");
+ typedef typename std::decay<typename std::tuple_element<sizeof...(TData) - 1,std::tuple<TData...>>::type>::type
+ RealDataType;
+ typedef typename FunctionTypeDetector<RealDataType>::type
+ DetectedFunctionType;
+
+ return writeFunction<DetectedFunctionType>(std::forward<TData>(data)...);
+ }
+
+
+private:
+ // the state is the most important variable in the class since it is our interface with Lua
+ // - registered members and functions are stored in tables at offset &typeid(type) of the registry
+ // each table has its getter functions at offset 0, getter members at offset 1, default getter at offset 2
+ // offset 3 is unused, setter members at offset 4, default setter at offset 5
+ lua_State* mState;
+
+
+ /**************************************************/
+ /* PUSH OBJECT */
+ /**************************************************/
+ struct PushedObject {
+ PushedObject(lua_State* state_, int num_ = 1) : state(state_), num(num_) {}
+ ~PushedObject() { assert(lua_gettop(state) >= num); if (num >= 1) lua_pop(state, num); }
+
+ PushedObject& operator=(const PushedObject&) = delete;
+ PushedObject(const PushedObject&) = delete;
+ PushedObject& operator=(PushedObject&& other) { std::swap(state, other.state); std::swap(num, other.num); return *this; }
+ PushedObject(PushedObject&& other) : state(other.state), num(other.num) { other.num = 0; }
+
+ PushedObject operator+(PushedObject&& other) && { PushedObject obj(state, num + other.num); num = 0; other.num = 0; return obj; }
+ void operator+=(PushedObject&& other) { assert(state == other.state); num += other.num; other.num = 0; }
+
+ auto getState() const -> lua_State* { return state; }
+ auto getNum() const -> int { return num; }
+
+ int release() { const auto n = num; num = 0; return n; }
+ void pop() { if (num >= 1) lua_pop(state, num); num = 0; }
+ void pop(int n) { assert(num >= n); lua_pop(state, n); num -= n; }
+
+ private:
+ lua_State* state;
+ int num = 0;
+ };
+
+
+ /**************************************************/
+ /* MISC */
+ /**************************************************/
+ // type used as a tag
+ template<typename T>
+ struct tag {};
+
+ // tag for "the registry"
+ enum RegistryTag { Registry };
+
+ // this function takes a value representing the offset to look into
+ // it will look into the top element of the stack and replace the element by its content at the given index
+ template<typename OffsetType1, typename... OffsetTypeOthers>
+ static void lookIntoStackTop(lua_State* state, OffsetType1&& offset1, OffsetTypeOthers&&... offsetOthers) {
+ static_assert(Pusher<typename std::decay<OffsetType1>::type>::minSize == 1 && Pusher<typename std::decay<OffsetType1>::type>::maxSize == 1, "Impossible to have a multiple-values index");
+ auto p1 = Pusher<typename std::decay<OffsetType1>::type>::push(state, offset1);
+ lua_gettable(state, -2);
+ lua_remove(state, -2);
+ p1.release();
+
+ lookIntoStackTop(state, std::forward<OffsetTypeOthers>(offsetOthers)...);
+ }
+
+ template<typename... OffsetTypeOthers>
+ static void lookIntoStackTop(lua_State* state, Metatable_t, OffsetTypeOthers&&... offsetOthers) {
+ lua_getmetatable(state, -1);
+ lua_remove(state, -2);
+
+ lookIntoStackTop(state, std::forward<OffsetTypeOthers>(offsetOthers)...);
+ }
+
+ static void lookIntoStackTop(lua_State*) {
+ }
+
+ // equivalent of lua_settable with t[k]=n, where t is the value at the index in the template parameter, k is the second parameter, n is the last parameter, and n is pushed by the function in the first parameter
+ // if there are more than 3 parameters, parameters 3 to n-1 are considered as sub-indices into the array
+ // the dataPusher MUST push only one thing on the stack
+ // TTableIndex must be either LUA_REGISTRYINDEX, LUA_GLOBALSINDEX, LUA_ENVINDEX, or the position of the element on the stack
+ template<typename TDataType, typename TIndex, typename TData>
+ static void setTable(lua_State* state, const PushedObject&, TIndex&& index, TData&& data) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TIndex>::type>::minSize == 1 && Pusher<typename std::decay<TIndex>::type>::maxSize == 1, "Impossible to have a multiple-values index");
+ static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data");
+
+ auto p1 = Pusher<typename std::decay<TIndex>::type>::push(state, index);
+ auto p2 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data));
+
+ lua_settable(state, -3);
+ p1.release();
+ p2.release();
+ }
+
+ template<typename TDataType, typename TData>
+ static void setTable(lua_State* state, const PushedObject&, const std::string& index, TData&& data) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data");
+
+ auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data));
+ lua_setfield(state, -2, index.c_str());
+ p1.release();
+ }
+
+ template<typename TDataType, typename TData>
+ static void setTable(lua_State* state, const PushedObject&, const char* index, TData&& data) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data");
+
+ auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data));
+ lua_setfield(state, -2, index);
+ p1.release();
+ }
+
+ template<typename TDataType, typename TData>
+ static void setTable(lua_State* state, const PushedObject&, Metatable_t, TData&& data) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data");
+
+ auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data));
+ lua_setmetatable(state, -2);
+ p1.release();
+ }
+
+ template<typename TDataType, typename TIndex1, typename TIndex2, typename TIndex3, typename... TIndices>
+ static auto setTable(lua_State* state, PushedObject&, TIndex1&& index1, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept
+ -> typename std::enable_if<!std::is_same<typename std::decay<TIndex1>::type, Metatable_t>::value>::type
+ {
+ static_assert(Pusher<typename std::decay<TIndex1>::type>::minSize == 1 && Pusher<typename std::decay<TIndex1>::type>::maxSize == 1, "Impossible to have a multiple-values index");
+
+ auto p1 = Pusher<typename std::decay<TIndex1>::type>::push(state, std::forward<TIndex1>(index1));
+ lua_gettable(state, -2);
+
+ setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...);
+ }
+
+ template<typename TDataType, typename TIndex1, typename TIndex2, typename TIndex3, typename... TIndices>
+ static auto setTable(lua_State* state, PushedObject&& pushedTable, TIndex1&& index1, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept
+ -> typename std::enable_if<!std::is_same<typename std::decay<TIndex1>::type, Metatable_t>::value>::type
+ {
+ static_assert(Pusher<typename std::decay<TIndex1>::type>::minSize == 1 && Pusher<typename std::decay<TIndex1>::type>::maxSize == 1, "Impossible to have a multiple-values index");
+
+ auto p1 = Pusher<typename std::decay<TIndex1>::type>::push(state, std::forward<TIndex1>(index1)) + std::move(pushedTable);
+ lua_gettable(state, -2);
+
+ setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...);
+ }
+
+ template<typename TDataType, typename TIndex2, typename TIndex3, typename... TIndices>
+ static void setTable(lua_State* state, PushedObject& pushedObject, Metatable_t, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept
+ {
+ if (lua_getmetatable(state, -1) == 0)
+ {
+ lua_newtable(state);
+ PushedObject p1{state, 1};
+
+ setTable<TDataType>(state, p1, std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...);
+
+ lua_setmetatable(state, -2);
+ p1.release();
+ }
+ else
+ {
+ setTable<TDataType>(state, pushedObject, std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...);
+ }
+ }
+
+ template<typename TDataType, typename TIndex2, typename TIndex3, typename... TIndices>
+ static void setTable(lua_State* state, PushedObject&& pushedObject, Metatable_t, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept
+ {
+ if (lua_getmetatable(state, -1) == 0)
+ {
+ lua_newtable(state);
+ PushedObject p1{state, 1};
+
+ setTable<TDataType>(state, p1, std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...);
+
+ lua_setmetatable(state, -2);
+ p1.release();
+ }
+ else
+ {
+ setTable<TDataType>(state, std::move(pushedObject), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...);
+ }
+ }
+
+ template<typename TDataType, typename TIndex, typename TData>
+ static void setTable(lua_State* state, RegistryTag, TIndex&& index, TData&& data) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TIndex>::type>::minSize == 1 && Pusher<typename std::decay<TIndex>::type>::maxSize == 1, "Impossible to have a multiple-values index");
+ static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data");
+
+ auto p1 = Pusher<typename std::decay<TIndex>::type>::push(state, index);
+ auto p2 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data));
+
+ lua_settable(state, LUA_REGISTRYINDEX);
+ p1.release();
+ p2.release();
+ }
+
+ template<typename TDataType, typename TData>
+ static void setTable(lua_State* state, RegistryTag, const std::string& index, TData&& data) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data");
+
+ auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data));
+ lua_setfield(state, LUA_REGISTRYINDEX, index.c_str());
+ p1.release();
+ }
+
+ template<typename TDataType, typename TData>
+ static void setTable(lua_State* state, RegistryTag, const char* index, TData&& data) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data");
+
+ auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data));
+ lua_setfield(state, LUA_REGISTRYINDEX, index);
+ p1.release();
+ }
+
+ template<typename TDataType, typename TIndex1, typename TIndex2, typename TIndex3, typename... TIndices>
+ static void setTable(lua_State* state, RegistryTag, TIndex1&& index1, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TIndex1>::type>::minSize == 1 && Pusher<typename std::decay<TIndex1>::type>::maxSize == 1, "Impossible to have a multiple-values index");
+
+ auto p1 = Pusher<typename std::decay<TIndex1>::type>::push(state, std::forward<TIndex1>(index1));
+ lua_gettable(state, LUA_REGISTRYINDEX);
+
+ setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...);
+ }
+
+ template<typename TDataType, typename TIndex, typename TData>
+ static void setTable(lua_State* state, Globals_t, TIndex&& index, TData&& data) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TIndex>::type>::minSize == 1 && Pusher<typename std::decay<TIndex>::type>::maxSize == 1, "Impossible to have a multiple-values index");
+ static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data");
+
+
+# if LUA_VERSION_NUM >= 502
+
+ lua_pushglobaltable(state);
+ PushedObject p3{state, 1};
+ auto p1 = Pusher<typename std::decay<TIndex>::type>::push(state, index);
+ auto p2 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data));
+ lua_settable(state, -3);
+
+# else
+
+ auto p1 = Pusher<typename std::decay<TIndex>::type>::push(state, index);
+ auto p2 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data));
+ lua_settable(state, LUA_GLOBALSINDEX);
+
+# endif
+
+ p1.release();
+ p2.release();
+ }
+
+ template<typename TDataType, typename TData>
+ static void setTable(lua_State* state, Globals_t, const std::string& index, TData&& data) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data");
+
+ auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data));
+ lua_setglobal(state, index.c_str());
+ p1.release();
+ }
+
+ template<typename TDataType, typename TData>
+ static void setTable(lua_State* state, Globals_t, const char* index, TData&& data) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data");
+
+ auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data));
+ lua_setglobal(state, index);
+ p1.release();
+ }
+
+ template<typename TDataType, typename TIndex1, typename TIndex2, typename TIndex3, typename... TIndices>
+ static void setTable(lua_State* state, Globals_t, TIndex1&& index1, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept
+ {
+ static_assert(Pusher<typename std::decay<TIndex1>::type>::minSize == 1 && Pusher<typename std::decay<TIndex1>::type>::maxSize == 1, "Impossible to have a multiple-values index");
+
+# if LUA_VERSION_NUM >= 502
+
+ lua_pushglobaltable(state);
+ auto p1 = Pusher<typename std::decay<TIndex1>::type>::push(state, std::forward<TIndex1>(index1)) + PushedObject{state, 1};
+ lua_gettable(state, -2);
+
+# else
+
+ auto p1 = Pusher<typename std::decay<TIndex1>::type>::push(state, std::forward<TIndex1>(index1));
+ lua_gettable(state, LUA_GLOBALSINDEX);
+
+# endif
+
+ setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...);
+ }
+
+ // TODO: g++ reports "ambiguous overload"
+ /*template<typename TDataType, typename TIndex2, typename TIndex3, typename... TIndices>
+ static void setTable(lua_State* state, Globals_t, const char* index, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept
+ {
+ lua_getglobal(state, index);
+ PushedObject p1{state, 1};
+
+ setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...);
+ }
+
+ template<typename TDataType, typename TIndex2, typename TIndex3, typename... TIndices>
+ static void setTable(lua_State* state, Globals_t, const std::string& index, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept
+ {
+ lua_getglobal(state, index.c_str());
+ PushedObject p1{state, 1};
+
+ setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...);
+ }*/
+
+ // simple function that reads the "nb" first top elements of the stack, pops them, and returns the value
+ // warning: first parameter is the number of parameters, not the parameter index
+ // if read generates an exception, stack is poped anyway
+ template<typename TReturnType>
+ static auto readTopAndPop(lua_State* state, PushedObject object)
+ -> TReturnType
+ {
+ auto val = Reader<typename std::decay<TReturnType>::type>::read(state, -object.getNum());
+ if (!val.is_initialized())
+ throw WrongTypeException{lua_typename(state, lua_type(state, -object.getNum())), typeid(TReturnType)};
+ return val.get();
+ }
+
+ // checks that the offsets for a type's registrations are set in the registry
+ static void checkTypeRegistration(lua_State* state, const std::type_info* type)
+ {
+ lua_pushlightuserdata(state, const_cast<std::type_info*>(type));
+ lua_gettable(state, LUA_REGISTRYINDEX);
+ if (!lua_isnil(state, -1)) {
+ lua_pop(state, 1);
+ return;
+ }
+ lua_pop(state, 1);
+
+ lua_pushlightuserdata(state, const_cast<std::type_info*>(type));
+ lua_newtable(state);
+
+ lua_pushinteger(state, 0);
+ lua_newtable(state);
+ lua_settable(state, -3);
+
+ lua_pushinteger(state, 1);
+ lua_newtable(state);
+ lua_settable(state, -3);
+
+ lua_pushinteger(state, 3);
+ lua_newtable(state);
+ lua_settable(state, -3);
+
+ lua_pushinteger(state, 4);
+ lua_newtable(state);
+ lua_settable(state, -3);
+
+ lua_settable(state, LUA_REGISTRYINDEX);
+ }
+
+ //
+# ifdef _MSC_VER
+ __declspec(noreturn)
+# else
+ [[noreturn]]
+# endif
+ static void luaError(lua_State* state)
+ {
+ lua_error(state);
+ assert(false);
+ std::terminate(); // removes compilation warning
+ }
+
+
+ /**************************************************/
+ /* FUNCTIONS REGISTRATION */
+ /**************************************************/
+ // the "registerFunction" public functions call this one
+ template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams>
+ void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<TObject>, tag<TRetValue (TOtherParams...)>)
+ {
+ static_assert(std::is_class<TObject>::value || std::is_pointer<TObject>::value || std::is_union<TObject>::value , "registerFunction can only be used for a class a union or a pointer");
+
+ checkTypeRegistration(mState, &typeid(TObject));
+ setTable<TRetValue(TObject&, TOtherParams...)>(mState, Registry, &typeid(TObject), 0, functionName, function);
+
+ checkTypeRegistration(mState, &typeid(TObject*));
+ setTable<TRetValue(TObject*, TOtherParams...)>(mState, Registry, &typeid(TObject*), 0, functionName, [=](TObject* obj, TOtherParams... rest) { assert(obj); return function(*obj, std::forward<TOtherParams>(rest)...); });
+
+ checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject>));
+ setTable<TRetValue(std::shared_ptr<TObject>, TOtherParams...)>(mState, Registry, &typeid(std::shared_ptr<TObject>), 0, functionName, [=](const std::shared_ptr<TObject>& obj, TOtherParams... rest) { assert(obj); return function(*obj, std::forward<TOtherParams>(rest)...); });
+ }
+
+ template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams>
+ void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<const TObject>, tag<TRetValue (TOtherParams...)> fTypeTag)
+ {
+ registerFunctionImpl(functionName, function, tag<TObject>{}, fTypeTag);
+
+ checkTypeRegistration(mState, &typeid(TObject const*));
+ setTable<TRetValue(TObject const*, TOtherParams...)>(mState, Registry, &typeid(TObject const*), 0, functionName, [=](TObject const* obj, TOtherParams... rest) { assert(obj); return function(*obj, std::forward<TOtherParams>(rest)...); });
+
+ checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject const>));
+ setTable<TRetValue(std::shared_ptr<TObject const>, TOtherParams...)>(mState, Registry, &typeid(std::shared_ptr<TObject const>), 0, functionName, [=](const std::shared_ptr<TObject const>& obj, TOtherParams... rest) { assert(obj); return function(*obj, std::forward<TOtherParams>(rest)...); });
+ }
+
+ template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams>
+ void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<TRetValue (TObject::*)(TOtherParams...)>)
+ {
+ registerFunctionImpl(functionName, std::move(function), tag<TObject>{}, tag<TRetValue (TOtherParams...)>{});
+ }
+
+ template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams>
+ void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<TRetValue (TObject::*)(TOtherParams...) const>)
+ {
+ registerFunctionImpl(functionName, std::move(function), tag<const TObject>{}, tag<TRetValue (TOtherParams...)>{});
+ }
+
+ template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams>
+ void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<TRetValue (TObject::*)(TOtherParams...) volatile>)
+ {
+ registerFunctionImpl(functionName, std::move(function), tag<TObject>{}, tag<TRetValue (TOtherParams...)>{});
+ }
+
+ template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams>
+ void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<TRetValue (TObject::*)(TOtherParams...) const volatile>)
+ {
+ registerFunctionImpl(functionName, std::move(function), tag<const TObject>{}, tag<TRetValue (TOtherParams...)>{});
+ }
+
+ // the "registerMember" public functions call this one
+ template<typename TObject, typename TVarType, typename TReadFunction>
+ void registerMemberImpl(const std::string& name, TReadFunction readFunction)
+ {
+ static_assert(std::is_class<TObject>::value || std::is_pointer<TObject>::value, "registerMember can only be called on a class or a pointer");
+
+ checkTypeRegistration(mState, &typeid(TObject));
+ setTable<TVarType (TObject&)>(mState, Registry, &typeid(TObject), 1, name, [readFunction](TObject const& object) {
+ return readFunction(object);
+ });
+
+ checkTypeRegistration(mState, &typeid(TObject*));
+ setTable<TVarType (TObject*)>(mState, Registry, &typeid(TObject*), 1, name, [readFunction](TObject const* object) {
+ assert(object);
+ return readFunction(*object);
+ });
+
+ checkTypeRegistration(mState, &typeid(TObject const*));
+ setTable<TVarType (TObject const*)>(mState, Registry, &typeid(TObject const*), 1, name, [readFunction](TObject const* object) {
+ assert(object);
+ return readFunction(*object);
+ });
+
+ checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject>));
+ setTable<TVarType (std::shared_ptr<TObject>)>(mState, Registry, &typeid(std::shared_ptr<TObject>), 1, name, [readFunction](const std::shared_ptr<TObject>& object) {
+ assert(object);
+ return readFunction(*object);
+ });
+
+ checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject const>));
+ setTable<TVarType (std::shared_ptr<TObject const>)>(mState, Registry, &typeid(std::shared_ptr<TObject const>), 1, name, [readFunction](const std::shared_ptr<TObject const>& object) {
+ assert(object);
+ return readFunction(*object);
+ });
+ }
+
+ template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction>
+ void registerMemberImpl(const std::string& name, TReadFunction readFunction, TWriteFunction writeFunction_)
+ {
+ registerMemberImpl<TObject,TVarType>(name, readFunction);
+
+ setTable<void (TObject&, TVarType)>(mState, Registry, &typeid(TObject), 4, name, [writeFunction_](TObject& object, const TVarType& value) {
+ writeFunction_(object, value);
+ });
+
+ setTable<void (TObject*, TVarType)>(mState, Registry, &typeid(TObject*), 4, name, [writeFunction_](TObject* object, const TVarType& value) {
+ assert(object);
+ writeFunction_(*object, value);
+ });
+
+ setTable<void (std::shared_ptr<TObject>, TVarType)>(mState, Registry, &typeid(std::shared_ptr<TObject>), 4, name, [writeFunction_](std::shared_ptr<TObject> object, const TVarType& value) {
+ assert(object);
+ writeFunction_(*object, value);
+ });
+ }
+
+ template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction>
+ void registerMemberImpl(tag<TVarType (TObject::*)>, const std::string& name, TReadFunction readFunction, TWriteFunction writeFunction_)
+ {
+ registerMemberImpl<TObject,TVarType>(name, std::move(readFunction), std::move(writeFunction_));
+ }
+
+ template<typename TObject, typename TVarType, typename TReadFunction>
+ void registerMemberImpl(tag<TVarType(TObject::*)>, const std::string& name, TReadFunction readFunction)
+ {
+ registerMemberImpl<TObject, TVarType>(name, std::move(readFunction));
+ }
+
+ // the "registerMember" public functions call this one
+ template<typename TObject, typename TVarType, typename TReadFunction>
+ void registerMemberImpl(TReadFunction readFunction)
+ {
+ checkTypeRegistration(mState, &typeid(TObject));
+ setTable<TVarType (TObject const&, std::string)>(mState, Registry, &typeid(TObject), 2, [readFunction](TObject const& object, const std::string& name) {
+ return readFunction(object, name);
+ });
+
+ checkTypeRegistration(mState, &typeid(TObject*));
+ setTable<TVarType (TObject*, std::string)>(mState, Registry, &typeid(TObject*), 2, [readFunction](TObject const* object, const std::string& name) {
+ assert(object);
+ return readFunction(*object, name);
+ });
+
+ checkTypeRegistration(mState, &typeid(TObject const*));
+ setTable<TVarType (TObject const*, std::string)>(mState, Registry, &typeid(TObject const*), 2, [readFunction](TObject const* object, const std::string& name) {
+ assert(object);
+ return readFunction(*object, name);
+ });
+
+ checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject>));
+ setTable<TVarType (std::shared_ptr<TObject>, std::string)>(mState, Registry, &typeid(std::shared_ptr<TObject>), 2, [readFunction](const std::shared_ptr<TObject>& object, const std::string& name) {
+ assert(object);
+ return readFunction(*object, name);
+ });
+
+ checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject const>));
+ setTable<TVarType (std::shared_ptr<TObject const>, std::string)>(mState, Registry, &typeid(std::shared_ptr<TObject const>), 2, [readFunction](const std::shared_ptr<TObject const>& object, const std::string& name) {
+ assert(object);
+ return readFunction(*object, name);
+ });
+ }
+
+ template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction>
+ void registerMemberImpl(TReadFunction readFunction, TWriteFunction writeFunction_)
+ {
+ registerMemberImpl<TObject,TVarType>(readFunction);
+
+ setTable<void (TObject&, std::string, TVarType)>(mState, Registry, &typeid(TObject), 5, [writeFunction_](TObject& object, const std::string& name, const TVarType& value) {
+ writeFunction_(object, name, value);
+ });
+
+ setTable<void (TObject*, std::string, TVarType)>(mState, Registry, &typeid(TObject*), 2, [writeFunction_](TObject* object, const std::string& name, const TVarType& value) {
+ assert(object);
+ writeFunction_(*object, name, value);
+ });
+
+ setTable<void (std::shared_ptr<TObject>, std::string, TVarType)>(mState, Registry, &typeid(std::shared_ptr<TObject>), 2, [writeFunction_](const std::shared_ptr<TObject>& object, const std::string& name, const TVarType& value) {
+ assert(object);
+ writeFunction_(*object, name, value);
+ });
+ }
+
+ template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction>
+ void registerMemberImpl(tag<TVarType (TObject::*)>, TReadFunction readFunction, TWriteFunction writeFunction_)
+ {
+ registerMemberImpl<TObject,TVarType>(std::move(readFunction), std::move(writeFunction_));
+ }
+
+ template<typename TObject, typename TVarType, typename TReadFunction>
+ void registerMemberImpl(tag<TVarType(TObject::*)>, TReadFunction readFunction)
+ {
+ registerMemberImpl<TObject, TVarType>(std::move(readFunction));
+ }
+
+
+ /**************************************************/
+ /* LOADING AND CALLING */
+ /**************************************************/
+ // this function loads data from the stream and pushes a function at the top of the stack
+ // throws in case of syntax error
+ static PushedObject load(lua_State* state, std::istream& code) {
+ // since the lua_load function requires a static function, we use this structure
+ // the Reader structure is at the same time an object storing an istream and a buffer,
+ // and a static function provider
+ struct Reader {
+ Reader(std::istream& str) : stream(str) {}
+ std::istream& stream;
+ std::array<char,512> buffer;
+
+ // read function ; "data" must be an instance of Reader
+ static const char* read(lua_State* /*l*/, void* data, size_t* size) {
+ assert(size != nullptr);
+ assert(data != nullptr);
+ Reader& me = *static_cast<Reader*>(data);
+ if (me.stream.eof()) { *size = 0; return nullptr; }
+
+ me.stream.read(me.buffer.data(), me.buffer.size());
+ *size = static_cast<size_t>(me.stream.gcount()); // gcount could return a value larger than a size_t, but its maximum is me.buffer.size() so there's no problem
+ return me.buffer.data();
+ }
+ };
+
+ // we create an instance of Reader, and we call lua_load
+ Reader reader{code};
+ const auto loadReturnValue = lua_load(state, &Reader::read, &reader, "chunk"
+# if LUA_VERSION_NUM >= 502
+ , nullptr
+# endif
+ );
+
+ // now we have to check return value
+ if (loadReturnValue != 0) {
+ // there was an error during loading, an error message was pushed on the stack
+ const std::string errorMsg = lua_tostring(state, -1);
+ lua_pop(state, 1);
+ if (loadReturnValue == LUA_ERRMEM)
+ throw std::bad_alloc();
+ else if (loadReturnValue == LUA_ERRSYNTAX)
+ throw SyntaxErrorException{errorMsg};
+ throw std::runtime_error("Error while calling lua_load: " + errorMsg);
+ }
+
+ return PushedObject{state, 1};
+ }
+
+ // this function loads data and pushes a function at the top of the stack
+ // throws in case of syntax error
+ static PushedObject load(lua_State* state, const char* code) {
+ auto loadReturnValue = luaL_loadstring(state, code);
+
+ // now we have to check return value
+ if (loadReturnValue != 0) {
+ // there was an error during loading, an error message was pushed on the stack
+ const std::string errorMsg = lua_tostring(state, -1);
+ lua_pop(state, 1);
+ if (loadReturnValue == LUA_ERRMEM)
+ throw std::bad_alloc();
+ else if (loadReturnValue == LUA_ERRSYNTAX)
+ throw SyntaxErrorException{errorMsg};
+ throw std::runtime_error("Error while calling lua_load: " + errorMsg);
+ }
+
+ return PushedObject{state, 1};
+ }
+
+ // this function calls what is on the top of the stack and removes it (just like lua_call)
+ // if an exception is triggered, the top of the stack will be removed anyway
+ template<typename TReturnType, typename... TParameters>
+ static auto call(lua_State* state, PushedObject toCall, TParameters&&... input)
+ -> TReturnType
+ {
+ typedef typename Tupleizer<TReturnType>::type
+ RealReturnType;
+
+ // we push the parameters on the stack
+ auto inArguments = Pusher<std::tuple<TParameters&&...>>::push(state, std::forward_as_tuple(std::forward<TParameters>(input)...));
+
+ //
+ const int outArgumentsCount = std::tuple_size<RealReturnType>::value;
+ auto outArguments = callRaw(state, std::move(toCall) + std::move(inArguments), outArgumentsCount);
+
+ // pcall succeeded, we pop the returned values and return them
+ return readTopAndPop<TReturnType>(state, std::move(outArguments));
+ }
+
+ static int gettraceback(lua_State* L) {
+ lua_getglobal(L, "debug"); // stack: error, debug library
+ lua_getfield(L, -1, "traceback"); // stack: error, debug library, debug.traceback function
+ lua_remove(L, -2); // stack: error, debug.traceback function
+ lua_pushstring(L, ""); // stack: error, debug.traceback, ""
+ lua_pushinteger(L, 2); // stack: error, debug.traceback, "", 2
+ lua_call(L, 2, 1); // stack: error, traceback
+ lua_createtable(L, 2, 0); // stack: error, traceback, {}
+ lua_insert(L, 1); // stack: {}, error, traceback
+ lua_rawseti(L, 1, 2); // stack: {[2]=traceback}, error
+ lua_rawseti(L, 1, 1); // stack: {[1]=error,[2]=traceback}
+ return 1; // return the table
+ }
+
+ // this function just calls lua_pcall and checks for errors
+ static PushedObject callRaw(lua_State* state, PushedObject functionAndArguments, const int outArguments)
+ {
+ // provide traceback handler
+ int tbindex = lua_gettop(state) - (functionAndArguments.getNum() - 1);
+ lua_pushcfunction(state, gettraceback);
+
+ // move it back up, before our function and arguments
+ lua_insert(state, tbindex);
+
+ // calling pcall automatically pops the parameters and pushes output
+ const auto pcallReturnValue = lua_pcall(state, functionAndArguments.getNum() - 1, outArguments, tbindex);
+ functionAndArguments.release();
+
+ lua_remove(state, tbindex); // remove traceback function
+
+
+ // if pcall failed, analyzing the problem and throwing
+ if (pcallReturnValue != 0) {
+
+ // stack top: {error, traceback}
+ lua_rawgeti(state, -1, 1); // stack top: {error, traceback}, error
+ lua_rawgeti(state, -2, 2); // stack top: {error, traceback}, error, traceback
+ lua_remove(state, -3); // stack top: error, traceback
+
+ PushedObject traceBackRef{state, 1};
+ const auto traceBack = readTopAndPop<std::string>(state, std::move(traceBackRef)); // stack top: error
+ PushedObject errorCode{state, 1};
+
+ // an error occurred during execution, either an error message or a std::exception_ptr was pushed on the stack
+ if (pcallReturnValue == LUA_ERRMEM) {
+ throw std::bad_alloc{};
+
+ } else if (pcallReturnValue == LUA_ERRRUN) {
+ if (lua_isstring(state, 1)) {
+ // the error is a string
+ const auto str = readTopAndPop<std::string>(state, std::move(errorCode));
+ throw ExecutionErrorException{str+traceBack};
+
+ } else {
+ // an exception_ptr was pushed on the stack
+ // rethrowing it with an additional ExecutionErrorException
+ try {
+ if (const auto exp = readTopAndPop<std::exception_ptr>(state, std::move(errorCode))) {
+ std::rethrow_exception(exp);
+ }
+ } catch(const std::exception& e) {
+ std::throw_with_nested(ExecutionErrorException{std::string{"Exception thrown by a callback function: "} + e.what()});
+ } catch(...) {
+ std::throw_with_nested(ExecutionErrorException{"Exception thrown by a callback function called by Lua. "+traceBack});
+ }
+ throw ExecutionErrorException{"Unknown Lua error"};
+ }
+ }
+ }
+
+ return PushedObject{state, outArguments};
+ }
+
+
+ /**************************************************/
+ /* PUSH FUNCTIONS */
+ /**************************************************/
+ template<typename T>
+ static PushedObject push(lua_State* state, T&& value)
+ {
+ return Pusher<typename std::decay<T>::type>::push(state, std::forward<T>(value));
+ }
+
+ // the Pusher structures allow you to push a value on the stack
+ // - static const int minSize : minimum size on the stack that the value can have
+ // - static const int maxSize : maximum size on the stack that the value can have
+ // - static int push(const LuaContext&, ValueType) : pushes the value on the stack and returns the size on the stack
+
+ // implementation for custom objects
+ template<typename TType, typename = void>
+ struct Pusher {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ template<typename TType2>
+ static PushedObject push(lua_State* state, TType2&& value) noexcept {
+ // this function is called when lua's garbage collector wants to destroy our object
+ // we simply call its destructor
+ const auto garbageCallbackFunction = [](lua_State* lua) -> int {
+ assert(lua_gettop(lua) == 1);
+ TType* ptr = static_cast<TType*>(lua_touserdata(lua, 1));
+ assert(ptr);
+ ptr->~TType();
+ return 0;
+ };
+
+ // this function will be stored in __index in the metatable
+ const auto indexFunction = [](lua_State* lua) -> int {
+ try {
+ assert(lua_gettop(lua) == 2);
+ assert(lua_isuserdata(lua, 1));
+
+ // searching for a handler
+ lua_pushlightuserdata(lua, const_cast<std::type_info*>(&typeid(TType)));
+ lua_gettable(lua, LUA_REGISTRYINDEX);
+ assert(!lua_isnil(lua, -1));
+
+ // looking into getter functions
+ lua_pushinteger(lua, 0);
+ lua_gettable(lua, -2);
+ lua_pushvalue(lua, 2);
+ lua_gettable(lua, -2);
+ if (!lua_isnil(lua, -1))
+ return 1;
+ lua_pop(lua, 2);
+
+ // looking into getter members
+ lua_pushinteger(lua, 1);
+ lua_gettable(lua, -2);
+ lua_pushvalue(lua, 2);
+ lua_gettable(lua, -2);
+ if (!lua_isnil(lua, -1)) {
+ lua_pushvalue(lua, 1);
+ return callRaw(lua, PushedObject{lua, 2}, 1).release();
+ }
+ lua_pop(lua, 2);
+
+ // looking into default getter
+ lua_pushinteger(lua, 2);
+ lua_gettable(lua, -2);
+ if (lua_isnil(lua, -1))
+ return 1;
+ lua_pushvalue(lua, 1);
+ lua_pushvalue(lua, 2);
+ return callRaw(lua, PushedObject{lua, 3}, 1).release();
+
+ } catch (...) {
+ Pusher<std::exception_ptr>::push(lua, std::current_exception()).release();
+ luaError(lua);
+ }
+ };
+
+ // this function will be stored in __newindex in the metatable
+ const auto newIndexFunction = [](lua_State* lua) -> int {
+ try {
+ assert(lua_gettop(lua) == 3);
+ assert(lua_isuserdata(lua, 1));
+
+ // searching for a handler
+ lua_pushlightuserdata(lua, const_cast<std::type_info*>(&typeid(TType)));
+ lua_rawget(lua, LUA_REGISTRYINDEX);
+ assert(!lua_isnil(lua, -1));
+
+ // looking into setter members
+ lua_pushinteger(lua, 4);
+ lua_rawget(lua, -2);
+ lua_pushvalue(lua, 2);
+ lua_rawget(lua, -2);
+ if (!lua_isnil(lua, -1)) {
+ lua_pushvalue(lua, 1);
+ lua_pushvalue(lua, 3);
+ callRaw(lua, PushedObject{lua, 3}, 0);
+ lua_pop(lua, 2);
+ return 0;
+ }
+ lua_pop(lua, 2);
+
+ // looking into default setter
+ lua_pushinteger(lua, 5);
+ lua_rawget(lua, -2);
+ if (lua_isnil(lua, -1))
+ {
+ lua_pop(lua, 2);
+ lua_pushstring(lua, "No setter found");
+ luaError(lua);
+ }
+ lua_pushvalue(lua, 1);
+ lua_pushvalue(lua, 2);
+ lua_pushvalue(lua, 3);
+ callRaw(lua, PushedObject{lua, 4}, 0);
+ lua_pop(lua, 1);
+ return 0;
+
+ } catch (...) {
+ Pusher<std::exception_ptr>::push(lua, std::current_exception()).release();
+ luaError(lua);
+ }
+ };
+
+ const auto toStringFunction = [](lua_State* lua) -> int {
+ try {
+ assert(lua_gettop(lua) == 1);
+ assert(lua_isuserdata(lua, 1));
+ lua_pushstring(lua, "__tostring");
+ lua_gettable(lua, 1);
+ if (lua_isnil(lua, -1))
+ {
+ const void *ptr = lua_topointer(lua, -2);
+ lua_pop(lua, 1);
+ lua_pushstring(lua, (boost::format("userdata 0x%08x") % reinterpret_cast<intptr_t>(ptr)).str().c_str());
+ return 1;
+ }
+ lua_pushvalue(lua, 1);
+ return callRaw(lua, PushedObject{lua, 2}, 1).release();
+ } catch (...) {
+ Pusher<std::exception_ptr>::push(lua, std::current_exception()).release();
+ luaError(lua);
+ }
+ };
+
+
+ // writing structure for this type into the registry
+ checkTypeRegistration(state, &typeid(TType));
+ try {
+ // creating the object
+ // lua_newuserdata allocates memory in the internals of the lua library and returns it so we can fill it
+ // and that's what we do with placement-new
+ const auto pointerLocation = static_cast<TType*>(lua_newuserdata(state, sizeof(TType)));
+ new (pointerLocation) TType(std::forward<TType2>(value));
+ }
+ catch (...) {
+ Pusher<std::exception_ptr>::push(state, std::current_exception()).release();
+ luaError(state);
+ }
+
+ PushedObject obj{state, 1};
+
+ // creating the metatable (over the object on the stack)
+ // lua_settable pops the key and value we just pushed, so stack management is easy
+ // all that remains on the stack after these function calls is the metatable
+ lua_newtable(state);
+ PushedObject pushedTable{state, 1};
+
+ // using the garbage collecting function we created above
+ if (!boost::has_trivial_destructor<TType>::value)
+ {
+ lua_pushstring(state, "__gc");
+ lua_pushcfunction(state, garbageCallbackFunction);
+ lua_settable(state, -3);
+ }
+
+ // the _typeid index of the metatable will store the type_info*
+ lua_pushstring(state, "_typeid");
+ lua_pushlightuserdata(state, const_cast<std::type_info*>(&typeid(TType)));
+ lua_settable(state, -3);
+
+ // using the index function we created above
+ lua_pushstring(state, "__index");
+ lua_pushcfunction(state, indexFunction);
+ lua_settable(state, -3);
+
+ // using the newindex function we created above
+ lua_pushstring(state, "__newindex");
+ lua_pushcfunction(state, newIndexFunction);
+ lua_settable(state, -3);
+
+ lua_pushstring(state, "__tostring");
+ lua_pushcfunction(state, toStringFunction);
+ lua_settable(state, -3);
+
+ lua_pushstring(state, "__eq");
+ lua_getglobal(state, LUACONTEXT_GLOBAL_EQ);
+ lua_settable(state, -3);
+
+
+ // at this point, the stack contains the object at offset -2 and the metatable at offset -1
+ // lua_setmetatable will bind the two together and pop the metatable
+ // our custom type remains on the stack (and that's what we want since this is a push function)
+ lua_setmetatable(state, -2);
+ pushedTable.release();
+
+ return obj;
+ }
+ };
+
+ // this structure has a "size" int static member which is equal to the total of the push min size of all the types
+ template<typename... TTypes>
+ struct PusherTotalMinSize;
+
+ // this structure has a "size" int static member which is equal to the total of the push max size of all the types
+ template<typename... TTypes>
+ struct PusherTotalMaxSize;
+
+ // this structure has a "size" int static member which is equal to the maximum size of the push of all the types
+ template<typename... TTypes>
+ struct PusherMinSize;
+
+ // this structure has a "size" int static member which is equal to the maximum size of the push of all the types
+ template<typename... TTypes>
+ struct PusherMaxSize;
+
+
+ /**************************************************/
+ /* READ FUNCTIONS */
+ /**************************************************/
+ // the "Reader" structures allow to read data from the stack
+ // - the "ReturnType" type is what is returned by the reader, and can be different than the template parameter (especially with references and constness)
+ // - the "read" static function will check and read at the same time, returning an empty optional if it is the wrong type
+
+ template<typename TType, typename = void>
+ struct Reader {
+ typedef typename std::conditional<std::is_pointer<TType>::value, TType, TType&>::type
+ ReturnType;
+
+ static auto read(lua_State* state, int index)
+ -> boost::optional<ReturnType>
+ {
+ if (!test(state, index))
+ return boost::none;
+ return boost::optional<ReturnType>(*static_cast<TType*>(lua_touserdata(state, index)));
+ }
+
+ private:
+ static bool test(lua_State* state, int index)
+ {
+ if (!lua_isuserdata(state, index))
+ return false;
+ if (!lua_getmetatable(state, index))
+ return false;
+
+ // now we have our metatable on the top of the stack
+ // retrieving its _typeid member
+ lua_pushstring(state, "_typeid");
+ lua_gettable(state, -2);
+ const auto storedTypeID = static_cast<const std::type_info*>(lua_touserdata(state, -1));
+ const auto typeIDToCompare = &typeid(TType);
+
+ // if wrong typeid, returning false
+ lua_pop(state, 2);
+ if (storedTypeID != typeIDToCompare)
+ return false;
+
+ return true;
+ }
+ };
+
+ /**
+ * This functions reads multiple values starting at "index" and passes them to the callback
+ */
+ template<typename TRetValue, typename TCallback>
+ static auto readIntoFunction(lua_State* /*state*/, tag<TRetValue>, TCallback&& callback, int /*index*/)
+ -> TRetValue
+ {
+ return callback();
+ }
+ template<typename TRetValue, typename TCallback, typename TFirstType, typename... TTypes>
+ static auto readIntoFunction(lua_State* state, tag<TRetValue> retValueTag, TCallback&& callback, int index, tag<TFirstType>, tag<TTypes>... othersTags)
+ -> typename std::enable_if<IsOptional<TFirstType>::value, TRetValue>::type
+ {
+ if (index >= 0) {
+ static const TFirstType empty{};
+ Binder<TCallback, const TFirstType&> binder{ callback, empty };
+ return readIntoFunction(state, retValueTag, binder, index + 1, othersTags...);
+ }
+
+ const auto& firstElem = Reader<typename std::decay<TFirstType>::type>::read(state, index);
+ if (!firstElem)
+ throw WrongTypeException(lua_typename(state, lua_type(state, index)), typeid(TFirstType));
+
+ Binder<TCallback, const TFirstType&> binder{ callback, *firstElem };
+ return readIntoFunction(state, retValueTag, binder, index + 1, othersTags...);
+ }
+ template<typename TRetValue, typename TCallback, typename TFirstType, typename... TTypes>
+ static auto readIntoFunction(lua_State* state, tag<TRetValue> retValueTag, TCallback&& callback, int index, tag<TFirstType>, tag<TTypes>... othersTags)
+ -> typename std::enable_if<!IsOptional<TFirstType>::value, TRetValue>::type
+ {
+ if (index >= 0)
+ throw std::logic_error("Wrong number of parameters");
+
+ const auto& firstElem = Reader<typename std::decay<TFirstType>::type>::read(state, index);
+ if (!firstElem)
+ throw WrongTypeException(lua_typename(state, lua_type(state, index)), typeid(TFirstType));
+
+ Binder<TCallback, const TFirstType&> binder{ callback, *firstElem };
+ return readIntoFunction(state, retValueTag, binder, index + 1, othersTags...);
+ }
+
+
+ /**************************************************/
+ /* UTILITIES */
+ /**************************************************/
+ // structure that will ensure that a certain value is stored somewhere in the registry
+ struct ValueInRegistry {
+ // this constructor will clone and hold the value at the specified index (or by default at the top of the stack) in the registry
+ ValueInRegistry(lua_State* lua_, int index=-1) : lua{lua_}
+ {
+ lua_pushlightuserdata(lua, this);
+ lua_pushvalue(lua, -1 + index);
+ lua_settable(lua, LUA_REGISTRYINDEX);
+ }
+
+ // removing the function from the registry
+ ~ValueInRegistry()
+ {
+ lua_pushlightuserdata(lua, this);
+ lua_pushnil(lua);
+ lua_settable(lua, LUA_REGISTRYINDEX);
+ }
+
+ // loads the value and puts it at the top of the stack
+ PushedObject pop()
+ {
+ lua_pushlightuserdata(lua, this);
+ lua_gettable(lua, LUA_REGISTRYINDEX);
+ return PushedObject{lua, 1};
+ }
+
+ ValueInRegistry(const ValueInRegistry&) = delete;
+ ValueInRegistry& operator=(const ValueInRegistry&) = delete;
+
+ private:
+ lua_State* lua;
+ };
+
+ // binds the first parameter of a function object
+ template<typename TFunctionObject, typename TFirstParamType>
+ struct Binder {
+ TFunctionObject function;
+ TFirstParamType param;
+
+ template<typename... TParams>
+ auto operator()(TParams&&... params)
+ -> decltype(function(param, std::forward<TParams>(params)...))
+ {
+ return function(param, std::forward<TParams>(params)...);
+ }
+ };
+
+ // turns a type into a tuple
+ // void is turned into std::tuple<>
+ // existing tuples are untouched
+ template<typename T>
+ struct Tupleizer;
+
+ // this structure takes a pointer to a member function type and returns the base function type
+ template<typename TType>
+ struct RemoveMemberPointerFunction { typedef void type; }; // required because of a compiler bug
+
+ // this structure takes any object and detects its function type
+ template<typename TObjectType>
+ struct FunctionTypeDetector { typedef typename RemoveMemberPointerFunction<decltype(&std::decay<TObjectType>::type::operator())>::type type; };
+
+ // this structure takes a function arguments list and has the "min" and the "max" static const member variables, whose value equal to the min and max number of parameters for the function
+ // the only case where "min != max" is with boost::optional at the end of the list
+ template<typename... TArgumentsList>
+ struct FunctionArgumentsCounter {};
+
+ // true is the template parameter is a boost::optional
+ template<typename T>
+ struct IsOptional : public std::false_type {};
+};
+
+/// @deprecated
+static LuaContext::EmptyArray_t ATTR_UNUSED
+ LuaEmptyArray {};
+/// @deprecated
+static LuaContext::Metatable_t ATTR_UNUSED
+ LuaMetatable {};
+
+/**************************************************/
+/* PARTIAL IMPLEMENTATIONS */
+/**************************************************/
+template<>
+inline auto LuaContext::readTopAndPop<void>(lua_State* /*state*/, PushedObject /*obj*/)
+ -> void
+{
+}
+
+// this structure takes a template parameter T
+// if T is a tuple, it returns T ; if T is not a tuple, it returns std::tuple<T>
+// we have to use this structure because std::tuple<std::tuple<...>> triggers a bug in both MSVC++ and GCC
+template<typename T>
+struct LuaContext::Tupleizer { typedef std::tuple<T> type; };
+template<typename... Args>
+struct LuaContext::Tupleizer<std::tuple<Args...>> { typedef std::tuple<Args...> type; };
+template<>
+struct LuaContext::Tupleizer<void> { typedef std::tuple<> type; };
+
+// this structure takes any object and detects its function type
+template<typename TRetValue, typename... TParameters>
+struct LuaContext::FunctionTypeDetector<TRetValue (TParameters...)> { typedef TRetValue type(TParameters...); };
+template<typename TObjectType>
+struct LuaContext::FunctionTypeDetector<TObjectType*> { typedef typename FunctionTypeDetector<TObjectType>::type type; };
+
+// this structure takes a pointer to a member function type and returns the base function type
+template<typename TType, typename TRetValue, typename... TParameters>
+struct LuaContext::RemoveMemberPointerFunction<TRetValue (TType::*)(TParameters...)> { typedef TRetValue type(TParameters...); };
+template<typename TType, typename TRetValue, typename... TParameters>
+struct LuaContext::RemoveMemberPointerFunction<TRetValue (TType::*)(TParameters...) const> { typedef TRetValue type(TParameters...); };
+template<typename TType, typename TRetValue, typename... TParameters>
+struct LuaContext::RemoveMemberPointerFunction<TRetValue (TType::*)(TParameters...) volatile> { typedef TRetValue type(TParameters...); };
+template<typename TType, typename TRetValue, typename... TParameters>
+struct LuaContext::RemoveMemberPointerFunction<TRetValue (TType::*)(TParameters...) const volatile> { typedef TRetValue type(TParameters...); };
+
+// implementation of PusherTotalMinSize
+template<typename TFirst, typename... TTypes>
+struct LuaContext::PusherTotalMinSize<TFirst, TTypes...> { static const int size = Pusher<typename std::decay<TFirst>::type>::minSize + PusherTotalMinSize<TTypes...>::size; };
+template<>
+struct LuaContext::PusherTotalMinSize<> { static const int size = 0; };
+
+// implementation of PusherTotalMaxSize
+template<typename TFirst, typename... TTypes>
+struct LuaContext::PusherTotalMaxSize<TFirst, TTypes...> { static const int size = Pusher<typename std::decay<TFirst>::type>::maxSize + PusherTotalMaxSize<TTypes...>::size; };
+template<>
+struct LuaContext::PusherTotalMaxSize<> { static const int size = 0; };
+
+// implementation of PusherMinSize
+template<typename TFirst, typename TSecond, typename... TTypes>
+struct LuaContext::PusherMinSize<TFirst, TSecond, TTypes...>
+{
+ static const int size = Pusher<typename std::decay<TFirst>::type>::minSize < Pusher<typename std::decay<TSecond>::type>::minSize
+ ?
+ PusherMinSize<typename std::decay<TFirst>::type, TTypes...>::size
+ :
+ PusherMinSize<typename std::decay<TSecond>::type, TTypes...>::size;
+};
+
+template<typename TFirst>
+struct LuaContext::PusherMinSize<TFirst> { static const int size = Pusher<typename std::decay<TFirst>::type>::minSize; };
+
+// implementation of PusherMaxSize
+template<typename TFirst, typename... TTypes>
+struct LuaContext::PusherMaxSize<TFirst, TTypes...> { static const int size = Pusher<typename std::decay<TFirst>::type>::maxSize > PusherTotalMaxSize<TTypes...>::size ? Pusher<typename std::decay<TFirst>::type>::maxSize : PusherMaxSize<TTypes...>::size; };
+template<>
+struct LuaContext::PusherMaxSize<> { static const int size = 0; };
+
+// implementation of FunctionArgumentsCounter
+template<typename TFirst, typename... TParams>
+struct LuaContext::FunctionArgumentsCounter<TFirst, TParams...> {
+ typedef FunctionArgumentsCounter<TParams...>
+ SubType;
+ static const int min = (IsOptional<TFirst>::value && SubType::min == 0) ? 0 : 1 + SubType::min;
+ static const int max = 1 + SubType::max;
+};
+template<>
+struct LuaContext::FunctionArgumentsCounter<> {
+ static const int min = 0;
+ static const int max = 0;
+};
+
+// implementation of IsOptional
+template<typename T>
+struct LuaContext::IsOptional<boost::optional<T>> : public std::true_type {};
+
+// implementation of LuaFunctionCaller
+template<typename TFunctionType>
+class LuaContext::LuaFunctionCaller { static_assert(std::is_function<TFunctionType>::value, "Template parameter of LuaFunctionCaller must be a function type"); };
+template<typename TRetValue, typename... TParams>
+class LuaContext::LuaFunctionCaller<TRetValue (TParams...)>
+{
+public:
+ TRetValue operator()(TParams&&... params) const
+ {
+ auto obj = valueHolder->pop();
+ return call<TRetValue>(state, std::move(obj), std::forward<TParams>(params)...);
+ }
+
+private:
+ std::shared_ptr<ValueInRegistry> valueHolder;
+ lua_State* state;
+
+private:
+ friend LuaContext;
+ explicit LuaFunctionCaller(lua_State* state_, int index) :
+ valueHolder(std::make_shared<ValueInRegistry>(state_, index)),
+ state(state_)
+ {}
+};
+
+
+/**************************************************/
+/* PUSH FUNCTIONS */
+/**************************************************/
+// specializations of the Pusher structure
+
+// opaque Lua references
+template<>
+struct LuaContext::Pusher<LuaContext::LuaObject> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, const LuaContext::LuaObject& value) noexcept {
+ if (value.objectInRegistry.get()) {
+ PushedObject obj = value.objectInRegistry->pop();
+ return obj;
+ } else {
+ lua_pushnil(state);
+ return PushedObject{state, 1};
+ }
+ }
+};
+
+// boolean
+template<>
+struct LuaContext::Pusher<bool> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, bool value) noexcept {
+ lua_pushboolean(state, value);
+ return PushedObject{state, 1};
+ }
+};
+
+// string
+template<>
+struct LuaContext::Pusher<std::string> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, const std::string& value) noexcept {
+ lua_pushlstring(state, value.c_str(), value.length());
+ return PushedObject{state, 1};
+ }
+};
+
+// const char*
+template<>
+struct LuaContext::Pusher<const char*> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, const char* value) noexcept {
+ lua_pushstring(state, value);
+ return PushedObject{state, 1};
+ }
+};
+
+// const char[N]
+template<int N>
+struct LuaContext::Pusher<const char[N]> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, const char* value) noexcept {
+ lua_pushstring(state, value);
+ return PushedObject{state, 1};
+ }
+};
+
+// floating numbers
+template<typename T>
+struct LuaContext::Pusher<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, T value) noexcept {
+ lua_pushnumber(state, value);
+ return PushedObject{state, 1};
+ }
+};
+
+// integers
+template<typename T>
+struct LuaContext::Pusher<T, typename std::enable_if<std::is_integral<T>::value>::type> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, T value) noexcept {
+ lua_pushinteger(state, value);
+ return PushedObject{state, 1};
+ }
+};
+
+// nil
+template<>
+struct LuaContext::Pusher<std::nullptr_t> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, std::nullptr_t) noexcept {
+ lua_pushnil(state);
+ return PushedObject{state, 1};
+ }
+};
+
+// empty arrays
+template<>
+struct LuaContext::Pusher<LuaContext::EmptyArray_t> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, EmptyArray_t) noexcept {
+ lua_newtable(state);
+ return PushedObject{state, 1};
+ }
+};
+
+// std::type_info* is a lightuserdata
+template<>
+struct LuaContext::Pusher<const std::type_info*> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, const std::type_info* ptr) noexcept {
+ lua_pushlightuserdata(state, const_cast<std::type_info*>(ptr));
+ return PushedObject{state, 1};
+ }
+};
+
+// thread
+template<>
+struct LuaContext::Pusher<LuaContext::ThreadID> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, const LuaContext::ThreadID& value) noexcept {
+ lua_pushthread(value.state);
+ return PushedObject{state, 1};
+ }
+};
+
+// maps
+template<typename TKey, typename TValue>
+struct LuaContext::Pusher<std::map<TKey,TValue>> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, const std::map<TKey,TValue>& value) noexcept {
+ static_assert(Pusher<typename std::decay<TKey>::type>::minSize == 1 && Pusher<typename std::decay<TKey>::type>::maxSize == 1, "Can't push multiple elements for a table key");
+ static_assert(Pusher<typename std::decay<TValue>::type>::minSize == 1 && Pusher<typename std::decay<TValue>::type>::maxSize == 1, "Can't push multiple elements for a table value");
+
+ auto obj = Pusher<EmptyArray_t>::push(state, EmptyArray);
+
+ for (auto i = value.begin(), e = value.end(); i != e; ++i)
+ setTable<TValue>(state, obj, i->first, i->second);
+
+ return obj;
+ }
+};
+
+// unordered_maps
+template<typename TKey, typename TValue, typename THash, typename TKeyEqual>
+struct LuaContext::Pusher<std::unordered_map<TKey,TValue,THash,TKeyEqual>> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, const std::unordered_map<TKey,TValue,THash,TKeyEqual>& value) noexcept {
+ static_assert(Pusher<typename std::decay<TKey>::type>::minSize == 1 && Pusher<typename std::decay<TKey>::type>::maxSize == 1, "Can't push multiple elements for a table key");
+ static_assert(Pusher<typename std::decay<TValue>::type>::minSize == 1 && Pusher<typename std::decay<TValue>::type>::maxSize == 1, "Can't push multiple elements for a table value");
+
+ auto obj = Pusher<EmptyArray_t>::push(state, EmptyArray);
+
+ for (auto i = value.begin(), e = value.end(); i != e; ++i)
+ setTable<TValue>(state, obj, i->first, i->second);
+
+ return obj;
+ }
+};
+
+// vectors of pairs
+template<typename TType1, typename TType2>
+struct LuaContext::Pusher<std::vector<std::pair<TType1,TType2>>> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, const std::vector<std::pair<TType1,TType2>>& value) noexcept {
+ static_assert(Pusher<typename std::decay<TType1>::type>::minSize == 1 && Pusher<typename std::decay<TType1>::type>::maxSize == 1, "Can't push multiple elements for a table key");
+ static_assert(Pusher<typename std::decay<TType2>::type>::minSize == 1 && Pusher<typename std::decay<TType2>::type>::maxSize == 1, "Can't push multiple elements for a table value");
+
+ auto obj = Pusher<EmptyArray_t>::push(state, EmptyArray);
+
+ for (auto i = value.begin(), e = value.end(); i != e; ++i)
+ setTable<TType2>(state, obj, i->first, i->second);
+
+ return obj;
+ }
+};
+
+// vectors
+template<typename TType>
+struct LuaContext::Pusher<std::vector<TType>> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, const std::vector<TType>& value) noexcept {
+ static_assert(Pusher<typename std::decay<TType>::type>::minSize == 1 && Pusher<typename std::decay<TType>::type>::maxSize == 1, "Can't push multiple elements for a table value");
+
+ auto obj = Pusher<EmptyArray_t>::push(state, EmptyArray);
+
+ for (unsigned int i = 0; i < value.size(); ++i)
+ setTable<TType>(state, obj, i + 1, value[i]);
+
+ return obj;
+ }
+};
+
+// unique_ptr
+template<typename TType>
+struct LuaContext::Pusher<std::unique_ptr<TType>> {
+ static const int minSize = Pusher<std::shared_ptr<TType>>::minSize;
+ static const int maxSize = Pusher<std::shared_ptr<TType>>::maxSize;
+
+ static PushedObject push(lua_State* state, std::unique_ptr<TType> value) noexcept {
+ return Pusher<std::shared_ptr<TType>>::push(state, std::move(value));
+ }
+};
+
+// enum
+template<typename TEnum>
+struct LuaContext::Pusher<TEnum, typename std::enable_if<std::is_enum<TEnum>::value>::type> {
+ #if !defined(__clang__) || __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 3)
+ typedef typename std::underlying_type<TEnum>::type
+ RealType;
+ #else
+ // implementation when std::underlying_type is not supported
+ typedef unsigned long
+ RealType;
+ #endif
+
+ static const int minSize = Pusher<RealType>::minSize;
+ static const int maxSize = Pusher<RealType>::maxSize;
+
+ static PushedObject push(lua_State* state, TEnum value) noexcept {
+ return Pusher<RealType>::push(state, static_cast<RealType>(value));
+ }
+};
+
+// any function
+// this specialization is not directly called, but is called by other specializations
+template<typename TReturnType, typename... TParameters>
+struct LuaContext::Pusher<TReturnType (TParameters...)>
+{
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ // counts the number of arguments
+ typedef FunctionArgumentsCounter<TParameters...>
+ LocalFunctionArgumentsCounter;
+
+ // this is the version of "push" for non-trivially destructible function objects
+ template<typename TFunctionObject>
+ static auto push(lua_State* state, TFunctionObject fn) noexcept
+ -> typename std::enable_if<!boost::has_trivial_destructor<TFunctionObject>::value, PushedObject>::type
+ {
+ // TODO: is_move_constructible not supported by some compilers
+ //static_assert(std::is_move_constructible<TFunctionObject>::value, "The function object must be move-constructible");
+
+ // when the lua script calls the thing we will push on the stack, we want "fn" to be executed
+ // if we used lua's cfunctions system, we could not detect when the function is no longer in use, which could cause problems
+ // so we use userdata instead
+
+ // this function is called when the lua script tries to call our custom data type
+ // we transfer execution to the "callback" function below
+ const auto callCallback = [](lua_State* lua) -> int {
+ assert(lua_gettop(lua) >= 1);
+ assert(lua_isuserdata(lua, 1));
+ auto function = static_cast<TFunctionObject*>(lua_touserdata(lua, 1));
+ assert(function);
+
+ return callback(lua, function, lua_gettop(lua) - 1).release();
+ };
+
+ // this one is called when lua's garbage collector no longer needs our custom data type
+ // we call the function object's destructor
+ const auto garbageCallback = [](lua_State* lua) -> int {
+ assert(lua_gettop(lua) == 1);
+ auto function = static_cast<TFunctionObject*>(lua_touserdata(lua, 1));
+ assert(function);
+ function->~TFunctionObject();
+ return 0;
+ };
+
+ // creating the object
+ // lua_newuserdata allocates memory in the internals of the lua library and returns it so we can fill it
+ // and that's what we do with placement-new
+ const auto functionLocation = static_cast<TFunctionObject*>(lua_newuserdata(state, sizeof(TFunctionObject)));
+ new (functionLocation) TFunctionObject(std::move(fn));
+
+ // creating the metatable (over the object on the stack)
+ // lua_settable pops the key and value we just pushed, so stack management is easy
+ // all that remains on the stack after these function calls is the metatable
+ lua_newtable(state);
+ lua_pushstring(state, "__call");
+ lua_pushcfunction(state, callCallback);
+ lua_settable(state, -3);
+
+ lua_pushstring(state, "__gc");
+ lua_pushcfunction(state, garbageCallback);
+ lua_settable(state, -3);
+
+ // at this point, the stack contains the object at offset -2 and the metatable at offset -1
+ // lua_setmetatable will bind the two together and pop the metatable
+ // our custom function remains on the stack (and that's what we want)
+ lua_setmetatable(state, -2);
+
+ return PushedObject{state, 1};
+ }
+
+ // this is the version of "push" for trivially destructible objects
+ template<typename TFunctionObject>
+ static auto push(lua_State* state, TFunctionObject fn) noexcept
+ -> typename std::enable_if<boost::has_trivial_destructor<TFunctionObject>::value, PushedObject>::type
+ {
+ // TODO: is_move_constructible not supported by some compilers
+ //static_assert(std::is_move_constructible<TFunctionObject>::value, "The function object must be move-constructible");
+
+ // when the lua script calls the thing we will push on the stack, we want "fn" to be executed
+ // since "fn" doesn't need to be destroyed, we simply push it on the stack
+
+ // this is the cfunction that is the callback
+ const auto function = [](lua_State* state_) -> int
+ {
+ // the function object is an upvalue
+ const auto toCall = static_cast<TFunctionObject*>(lua_touserdata(state_, lua_upvalueindex(1)));
+ return callback(state_, toCall, lua_gettop(state_)).release();
+ };
+
+ // we copy the function object onto the stack
+ const auto functionObjectLocation = static_cast<TFunctionObject*>(lua_newuserdata(state, sizeof(TFunctionObject)));
+ new (functionObjectLocation) TFunctionObject(std::move(fn));
+
+ // pushing the function with the function object as upvalue
+ lua_pushcclosure(state, function, 1);
+ return PushedObject{state, 1};
+ }
+
+ // this is the version of "push" for pointer to functions
+ static auto push(lua_State* state, TReturnType (*fn)(TParameters...)) noexcept
+ -> PushedObject
+ {
+ // when the lua script calls the thing we will push on the stack, we want "fn" to be executed
+ // since "fn" doesn't need to be destroyed, we simply push it on the stack
+
+ // this is the cfunction that is the callback
+ const auto function = [](lua_State* state_) -> int
+ {
+ // the function object is an upvalue
+ const auto toCall = reinterpret_cast<TReturnType (*)(TParameters...)>(lua_touserdata(state_, lua_upvalueindex(1)));
+ return callback(state_, toCall, lua_gettop(state_)).release();
+ };
+
+ // we copy the function object onto the stack
+ lua_pushlightuserdata(state, reinterpret_cast<void*>(fn));
+
+ // pushing the function with the function object as upvalue
+ lua_pushcclosure(state, function, 1);
+ return PushedObject{state, 1};
+ }
+
+ // this is the version of "push" for references to functions
+ static auto push(lua_State* state, TReturnType (&fn)(TParameters...)) noexcept
+ -> PushedObject
+ {
+ return push(state, &fn);
+ }
+
+private:
+ // callback that calls the function object
+ // this function is used by the callbacks and handles loading arguments from the stack and pushing the return value back
+ template<typename TFunctionObject>
+ static auto callback(lua_State* state, TFunctionObject* toCall, int argumentsCount)
+ -> PushedObject
+ {
+ // checking if number of parameters is correct
+ if (argumentsCount < LocalFunctionArgumentsCounter::min) {
+ // if not, using lua_error to return an error
+ luaL_where(state, 1);
+ lua_pushstring(state, "This function requires at least ");
+ lua_pushnumber(state, LocalFunctionArgumentsCounter::min);
+ lua_pushstring(state, " parameter(s)");
+ lua_concat(state, 4);
+ luaError(state);
+
+ } else if (argumentsCount > LocalFunctionArgumentsCounter::max) {
+ // if not, using lua_error to return an error
+ luaL_where(state, 1);
+ lua_pushstring(state, "This function requires at most ");
+ lua_pushnumber(state, LocalFunctionArgumentsCounter::max);
+ lua_pushstring(state, " parameter(s)");
+ lua_concat(state, 4);
+ luaError(state);
+ }
+
+ // calling the function
+ try {
+ return callback2(state, *toCall, argumentsCount);
+
+ } catch (const WrongTypeException& ex) {
+ // wrong parameter type, using lua_error to return an error
+ luaL_where(state, 1);
+ lua_pushstring(state, "Unable to convert parameter from ");
+ lua_pushstring(state, ex.luaType.c_str());
+ lua_pushstring(state, " to ");
+ lua_pushstring(state, ex.destination.name());
+ lua_concat(state, 5);
+ luaError(state);
+
+ } catch (const std::exception& e) {
+ luaL_where(state, 1);
+ lua_pushstring(state, "Caught exception: ");
+ lua_pushstring(state, e.what());
+ lua_concat(state, 3);
+ luaError(state);
+ } catch (...) {
+ Pusher<std::exception_ptr>::push(state, std::current_exception()).release();
+ luaError(state);
+ }
+ }
+
+ template<typename TFunctionObject>
+ static auto callback2(lua_State* state, TFunctionObject&& toCall, int argumentsCount)
+ -> typename std::enable_if<!std::is_void<TReturnType>::value && !std::is_void<TFunctionObject>::value, PushedObject>::type
+ {
+ // pushing the result on the stack and returning number of pushed elements
+ typedef Pusher<typename std::decay<TReturnType>::type>
+ P;
+ return P::push(state, readIntoFunction(state, tag<TReturnType>{}, toCall, -argumentsCount, tag<TParameters>{}...));
+ }
+
+ template<typename TFunctionObject>
+ static auto callback2(lua_State* state, TFunctionObject&& toCall, int argumentsCount)
+ -> typename std::enable_if<std::is_void<TReturnType>::value && !std::is_void<TFunctionObject>::value, PushedObject>::type
+ {
+ readIntoFunction(state, tag<TReturnType>{}, toCall, -argumentsCount, tag<TParameters>{}...);
+ return PushedObject{state, 0};
+ }
+};
+
+// C function pointers
+template<typename TReturnType, typename... TParameters>
+struct LuaContext::Pusher<TReturnType (*)(TParameters...)>
+{
+ // using the function-pushing implementation
+ typedef Pusher<TReturnType (TParameters...)>
+ SubPusher;
+ static const int minSize = SubPusher::minSize;
+ static const int maxSize = SubPusher::maxSize;
+
+ template<typename TType>
+ static PushedObject push(lua_State* state, TType value) noexcept {
+ return SubPusher::push(state, value);
+ }
+};
+
+// C function references
+template<typename TReturnType, typename... TParameters>
+struct LuaContext::Pusher<TReturnType (&)(TParameters...)>
+{
+ // using the function-pushing implementation
+ typedef Pusher<TReturnType(TParameters...)>
+ SubPusher;
+ static const int minSize = SubPusher::minSize;
+ static const int maxSize = SubPusher::maxSize;
+
+ template<typename TType>
+ static PushedObject push(lua_State* state, TType value) noexcept {
+ return SubPusher::push(state, value);
+ }
+};
+
+// std::function
+template<typename TReturnType, typename... TParameters>
+struct LuaContext::Pusher<std::function<TReturnType (TParameters...)>>
+{
+ // using the function-pushing implementation
+ typedef Pusher<TReturnType (TParameters...)>
+ SubPusher;
+ static const int minSize = SubPusher::minSize;
+ static const int maxSize = SubPusher::maxSize;
+
+ static PushedObject push(lua_State* state, const std::function<TReturnType (TParameters...)>& value) noexcept {
+ return SubPusher::push(state, value);
+ }
+};
+
+// boost::variant
+template<typename... TTypes>
+struct LuaContext::Pusher<boost::variant<TTypes...>>
+{
+ static const int minSize = PusherMinSize<TTypes...>::size;
+ static const int maxSize = PusherMaxSize<TTypes...>::size;
+
+ static PushedObject push(lua_State* state, const boost::variant<TTypes...>& value) noexcept {
+ PushedObject obj{state, 0};
+ VariantWriter writer{state, obj};
+ value.apply_visitor(writer);
+ return obj;
+ }
+
+private:
+ struct VariantWriter : public boost::static_visitor<> {
+ template<typename TType>
+ void operator()(TType value) noexcept
+ {
+ obj = Pusher<typename std::decay<TType>::type>::push(state, std::move(value));
+ }
+
+ VariantWriter(lua_State* state_, PushedObject& obj_) : state(state_), obj(obj_) {}
+ lua_State* state;
+ PushedObject& obj;
+ };
+};
+
+// boost::optional
+template<typename TType>
+struct LuaContext::Pusher<boost::optional<TType>> {
+ typedef Pusher<typename std::decay<TType>::type>
+ UnderlyingPusher;
+
+ static const int minSize = UnderlyingPusher::minSize < 1 ? UnderlyingPusher::minSize : 1;
+ static const int maxSize = UnderlyingPusher::maxSize > 1 ? UnderlyingPusher::maxSize : 1;
+
+ static PushedObject push(lua_State* state, const boost::optional<TType>& value) noexcept {
+ if (value) {
+ return UnderlyingPusher::push(state, value.get());
+ } else {
+ lua_pushnil(state);
+ return PushedObject{state, 1};
+ }
+ }
+};
+
+// tuple
+template<typename... TTypes>
+struct LuaContext::Pusher<std::tuple<TTypes...>> {
+ // TODO: NOT EXCEPTION SAFE /!\ //
+ static const int minSize = PusherTotalMinSize<TTypes...>::size;
+ static const int maxSize = PusherTotalMaxSize<TTypes...>::size;
+
+ static PushedObject push(lua_State* state, const std::tuple<TTypes...>& value) noexcept {
+ return PushedObject{state, push2(state, value, std::integral_constant<int,0>{})};
+ }
+
+ static PushedObject push(lua_State* state, std::tuple<TTypes...>&& value) noexcept {
+ return PushedObject{state, push2(state, std::move(value), std::integral_constant<int,0>{})};
+ }
+
+private:
+ template<int N>
+ static int push2(lua_State* state, const std::tuple<TTypes...>& value, std::integral_constant<int,N>) noexcept {
+ typedef typename std::tuple_element<N,std::tuple<TTypes...>>::type ElemType;
+
+ return Pusher<typename std::decay<ElemType>::type>::push(state, std::get<N>(value)).release() +
+ push2(state, value, std::integral_constant<int,N+1>{});
+ }
+
+ template<int N>
+ static int push2(lua_State* state, std::tuple<TTypes...>&& value, std::integral_constant<int,N>) noexcept {
+ typedef typename std::tuple_element<N,std::tuple<TTypes...>>::type ElemType;
+
+ return Pusher<typename std::decay<ElemType>::type>::push(state, std::move(std::get<N>(value))).release() +
+ push2(state, std::move(value), std::integral_constant<int,N+1>{});
+ }
+
+ static int push2(lua_State* /*state*/, const std::tuple<TTypes...>&, std::integral_constant<int,sizeof...(TTypes)>) noexcept {
+ return 0;
+ }
+
+ static int push2(lua_State* /*state*/, std::tuple<TTypes...>&&, std::integral_constant<int,sizeof...(TTypes)>) noexcept {
+ return 0;
+ }
+};
+
+/**************************************************/
+/* READ FUNCTIONS */
+/**************************************************/
+// specializations of the Reader structures
+
+// opaque Lua references
+template<>
+struct LuaContext::Reader<LuaContext::LuaObject>
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<LuaContext::LuaObject>
+ {
+ LuaContext::LuaObject obj(state, index);
+ return obj;
+ }
+};
+
+// reading null
+template<>
+struct LuaContext::Reader<std::nullptr_t>
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<std::nullptr_t>
+ {
+ if (!lua_isnil(state, index))
+ return boost::none;
+ return nullptr;
+ }
+};
+
+// integrals
+template<typename TType>
+struct LuaContext::Reader<
+ TType,
+ typename std::enable_if<std::is_integral<TType>::value>::type
+ >
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<TType>
+ {
+# if LUA_VERSION_NUM >= 502
+
+ int success;
+ auto value = lua_tointegerx(state, index, &success);
+ if (success == 0)
+ return boost::none;
+ return static_cast<TType>(value);
+
+# else
+
+ if (!lua_isnumber(state, index))
+ return boost::none;
+ return static_cast<TType>(lua_tointeger(state, index));
+
+# endif
+ }
+};
+
+// floating points
+template<typename TType>
+struct LuaContext::Reader<
+ TType,
+ typename std::enable_if<std::is_floating_point<TType>::value>::type
+ >
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<TType>
+ {
+# if LUA_VERSION_NUM >= 502
+
+ int success;
+ auto value = lua_tonumberx(state, index, &success);
+ if (success == 0)
+ return boost::none;
+ return static_cast<TType>(value);
+
+# else
+
+ if (!lua_isnumber(state, index))
+ return boost::none;
+ return static_cast<TType>(lua_tonumber(state, index));
+
+# endif
+ }
+};
+
+// boolean
+template<>
+struct LuaContext::Reader<bool>
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<bool>
+ {
+ if (!lua_isboolean(state, index))
+ return boost::none;
+ return lua_toboolean(state, index) != 0;
+ }
+};
+
+// string
+// lua_tostring returns a temporary pointer, but that's not a problem since we copy
+// the data into a std::string
+template<>
+struct LuaContext::Reader<std::string>
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<std::string>
+ {
+ std::string result;
+
+ // lua_tolstring might convert the variable that would confuse lua_next, so we
+ // make a copy of the variable.
+ lua_pushvalue(state, index);
+
+ size_t len;
+ const auto val = lua_tolstring(state, -1, &len);
+
+ if (val != nullptr)
+ result.assign(val, len);
+
+ lua_pop(state, 1);
+
+ return val != nullptr ? boost::optional<std::string>{ std::move(result) } : boost::none;
+ }
+};
+
+// enums
+template<typename TType>
+struct LuaContext::Reader<
+ TType,
+ typename std::enable_if<std::is_enum<TType>::value>::type
+ >
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<TType>
+ {
+ if (!lua_isnumber(state, index) || fmod(lua_tonumber(state, index), 1.) != 0)
+ return boost::none;
+ return static_cast<TType>(lua_tointeger(state, index));
+ }
+};
+
+// LuaFunctionCaller
+template<typename TRetValue, typename... TParameters>
+struct LuaContext::Reader<LuaContext::LuaFunctionCaller<TRetValue (TParameters...)>>
+{
+ typedef LuaFunctionCaller<TRetValue (TParameters...)>
+ ReturnType;
+
+ static auto read(lua_State* state, int index)
+ -> boost::optional<ReturnType>
+ {
+ if (lua_isfunction(state, index) == 0 && lua_isuserdata(state, index) == 0)
+ return boost::none;
+ return ReturnType(state, index);
+ }
+};
+
+// function
+template<typename TRetValue, typename... TParameters>
+struct LuaContext::Reader<std::function<TRetValue (TParameters...)>>
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<std::function<TRetValue (TParameters...)>>
+ {
+ if (auto val = Reader<LuaContext::LuaFunctionCaller<TRetValue (TParameters...)>>::read(state, index))
+ {
+ std::function<TRetValue (TParameters...)> f{*val};
+ return boost::optional<std::function<TRetValue (TParameters...)>>{std::move(f)};
+ }
+
+ return boost::none;
+ }
+};
+
+// vector of pairs
+template<typename TType1, typename TType2>
+struct LuaContext::Reader<std::vector<std::pair<TType1,TType2>>>
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<std::vector<std::pair<TType1, TType2>>>
+ {
+ if (!lua_istable(state, index))
+ return boost::none;
+
+ std::vector<std::pair<TType1, TType2>> result;
+
+ // we traverse the table at the top of the stack
+ lua_pushnil(state); // first key
+ while (lua_next(state, (index > 0) ? index : (index - 1)) != 0) {
+ // now a key and its value are pushed on the stack
+ try {
+ auto val1 = Reader<TType1>::read(state, -2);
+ auto val2 = Reader<TType2>::read(state, -1);
+
+ if (!val1.is_initialized() || !val2.is_initialized()) {
+ lua_pop(state, 2); // we remove the value and the key
+ return {};
+ }
+
+ result.push_back({ val1.get(), val2.get() });
+ lua_pop(state, 1); // we remove the value but keep the key for the next iteration
+
+ } catch(...) {
+ lua_pop(state, 2); // we remove the value and the key
+ return {};
+ }
+ }
+
+ return { std::move(result) };
+ }
+};
+
+// map
+template<typename TKey, typename TValue>
+struct LuaContext::Reader<std::map<TKey,TValue>>
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<std::map<TKey,TValue>>
+ {
+ if (!lua_istable(state, index))
+ return boost::none;
+
+ std::map<TKey,TValue> result;
+
+ // we traverse the table at the top of the stack
+ lua_pushnil(state); // first key
+ while (lua_next(state, (index > 0) ? index : (index - 1)) != 0) {
+ // now a key and its value are pushed on the stack
+ try {
+ auto key = Reader<TKey>::read(state, -2);
+ auto value = Reader<TValue>::read(state, -1);
+
+ if (!key.is_initialized() || !value.is_initialized()) {
+ lua_pop(state, 2); // we remove the value and the key
+ return {};
+ }
+
+ result.insert({ key.get(), value.get() });
+ lua_pop(state, 1); // we remove the value but keep the key for the next iteration
+
+ } catch(...) {
+ lua_pop(state, 2); // we remove the value and the key
+ return {};
+ }
+ }
+
+ return { std::move(result) };
+ }
+};
+
+// unordered_map
+template<typename TKey, typename TValue, typename THash, typename TKeyEqual>
+struct LuaContext::Reader<std::unordered_map<TKey,TValue,THash,TKeyEqual>>
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<std::unordered_map<TKey,TValue,THash,TKeyEqual>>
+ {
+ if (!lua_istable(state, index))
+ return boost::none;
+
+ std::unordered_map<TKey,TValue,THash,TKeyEqual> result;
+
+ // we traverse the table at the top of the stack
+ lua_pushnil(state); // first key
+ while (lua_next(state, (index > 0) ? index : (index - 1)) != 0) {
+ // now a key and its value are pushed on the stack
+ try {
+ auto key = Reader<TKey>::read(state, -2);
+ auto value = Reader<TValue>::read(state, -1);
+
+ if (!key.is_initialized() || !value.is_initialized()) {
+ lua_pop(state, 2); // we remove the value and the key
+ return {};
+ }
+
+ result.insert({ key.get(), value.get() });
+ lua_pop(state, 1); // we remove the value but keep the key for the next iteration
+
+ } catch(...) {
+ lua_pop(state, 2); // we remove the value and the key
+ return {};
+ }
+ }
+
+ return { std::move(result) };
+ }
+};
+
+// optional
+// IMPORTANT: optional means "either nil or the value of the right type"
+// * if the value is nil, then an optional containing an empty optional is returned
+// * if the value is of the right type, then an optional containing an optional containing the value is returned
+// * if the value is of the wrong type, then an empty optional is returned
+template<typename TType>
+struct LuaContext::Reader<boost::optional<TType>>
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<boost::optional<TType>>
+ {
+ if (lua_isnil(state, index))
+ return boost::optional<TType>{boost::none};
+ if (auto&& other = Reader<TType>::read(state, index))
+ return std::move(other);
+ return boost::none;
+ }
+};
+
+// variant
+template<typename... TTypes>
+struct LuaContext::Reader<boost::variant<TTypes...>>
+{
+ typedef boost::variant<TTypes...>
+ ReturnType;
+
+private:
+ // class doing operations for a range of types from TIterBegin to TIterEnd
+ template<typename TIterBegin, typename TIterEnd, typename = void>
+ struct VariantReader
+ {
+ using SubReader = Reader<typename std::decay<typename boost::mpl::deref<TIterBegin>::type>::type>;
+
+ static auto read(lua_State* state, int index)
+ -> boost::optional<ReturnType>
+ {
+ // note: using SubReader::read triggers a compilation error when used with a reference
+ if (const auto val = SubReader::read(state, index))
+ return boost::variant<TTypes...>{*val};
+ return VariantReader<typename boost::mpl::next<TIterBegin>::type, TIterEnd>::read(state, index);
+ }
+ };
+
+ // specialization of class above being called when list of remaining types is empty
+ template<typename TIterBegin, typename TIterEnd>
+ struct VariantReader<TIterBegin, TIterEnd, typename std::enable_if<boost::mpl::distance<TIterBegin, TIterEnd>::type::value == 0>::type>
+ {
+ static auto read(lua_State* /*state*/, int /*index*/)
+ -> boost::optional<ReturnType>
+ {
+ return boost::none;
+ }
+ };
+
+ // this is the main type
+ typedef VariantReader<typename boost::mpl::begin<typename ReturnType::types>::type, typename boost::mpl::end<typename ReturnType::types>::type>
+ MainVariantReader;
+
+public:
+ static auto read(lua_State* state, int index)
+ -> boost::optional<ReturnType>
+ {
+ return MainVariantReader::read(state, index);
+ }
+};
+
+// reading a tuple
+// tuple have an additional argument for their functions, that is the maximum size to read
+// if maxSize is smaller than the tuple size, then the remaining parameters will be left to default value
+template<>
+struct LuaContext::Reader<std::tuple<>>
+{
+ static auto read(lua_State* /*state*/, int /*index*/, int /*maxSize*/ = 0)
+ -> boost::optional<std::tuple<>>
+ {
+ return std::tuple<>{};
+ }
+};
+
+template<typename TFirst, typename... TOthers>
+struct LuaContext::Reader<std::tuple<TFirst, TOthers...>,
+ typename std::enable_if<!LuaContext::IsOptional<TFirst>::value>::type // TODO: replace by std::is_default_constructible when it works on every compiler
+ >
+{
+ // this is the "TFirst is NOT default constructible" version
+
+ typedef std::tuple<TFirst, TOthers...>
+ ReturnType;
+
+ static auto read(lua_State* state, int index, int maxSize = std::tuple_size<ReturnType>::value)
+ -> boost::optional<ReturnType>
+ {
+ if (maxSize <= 0)
+ return boost::none;
+
+ auto firstVal = Reader<TFirst>::read(state, index);
+ auto othersVal = Reader<std::tuple<TOthers...>>::read(state, index + 1, maxSize - 1);
+
+ if (!firstVal || !othersVal)
+ return boost::none;
+
+ return std::tuple_cat(std::tuple<TFirst>(*firstVal), std::move(*othersVal));
+ }
+};
+
+template<typename TFirst, typename... TOthers>
+struct LuaContext::Reader<std::tuple<TFirst, TOthers...>,
+ typename std::enable_if<LuaContext::IsOptional<TFirst>::value>::type // TODO: replace by std::is_default_constructible when it works on every compiler
+ >
+{
+ // this is the "TFirst is default-constructible" version
+
+ typedef std::tuple<TFirst, TOthers...>
+ ReturnType;
+
+ static auto read(lua_State* state, int index, int maxSize = std::tuple_size<ReturnType>::value)
+ -> boost::optional<ReturnType>
+ {
+ auto othersVal = Reader<std::tuple<TOthers...>>::read(state, index + 1, maxSize - 1);
+ if (!othersVal)
+ return boost::none;
+
+ if (maxSize <= 0)
+ return std::tuple_cat(std::tuple<TFirst>(), std::move(*othersVal));
+
+ auto firstVal = Reader<TFirst>::read(state, index);
+ if (!firstVal)
+ return boost::none;
+
+ return std::tuple_cat(std::tuple<TFirst>(*firstVal), std::move(*othersVal));
+ }
+};
+
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+#endif