/* Luwra * Minimal-overhead Lua wrapper for C++ * * Copyright (C) 2015, Ole Krüger */ #ifndef LUWRA_USERTYPES_H_ #define LUWRA_USERTYPES_H_ #include "common.hpp" #include "types.hpp" #include "stack.hpp" #include #include LUWRA_NS_BEGIN namespace internal { template using StripUserType = typename std::remove_cv::type; /** * User type registry identifiers */ template struct UserTypeReg { /** * Dummy field which is used because it has a seperate address for each instance of T */ static const int id; /** * Registry name for a metatable which is associated with a user type */ static const std::string name; }; template const int UserTypeReg::id = INT_MAX; template const std::string UserTypeReg::name = "UD#" + std::to_string(uintptr_t(&id)); /** * Register a new metatable for a user type T. */ template static inline void new_user_type_metatable(State* state) { using T = StripUserType; luaL_newmetatable(state, UserTypeReg::name.c_str()); } /** * Check if the value at the given index if a user type T. */ template static inline StripUserType* check_user_type(State* state, int index) { using T = StripUserType; return static_cast(luaL_checkudata(state, index, UserTypeReg::name.c_str())); } /** * Apply U's metatable for the value at the top of the stack. */ template static inline void apply_user_type_meta_table(State* state) { setMetatable(state, UserTypeReg>::name.c_str()); } /** * Lua C function to construct a user type T with parameters A */ template static inline int construct_user_type(State* state) { return direct( state, &Value&>::template push, state ); } /** * Lua C function to destruct a user type T */ template static inline int destruct_user_type(State* state) { using T = StripUserType; read(state, 1).~T(); return 0; } /** * Create a string representation for user type T. */ template static int stringify_user_type(State* state) { using T = StripUserType; return push( state, internal::UserTypeReg::name + "@" + std::to_string(uintptr_t(Value::read(state, 1))) ); } } /** * User type */ template struct Value { using T = internal::StripUserType; /** * Reference a user type value on the stack. * \param state Lua state * \param n Stack index * \returns Reference to the user type value */ static inline U& read(State* state, int n) { // T is unqualified, therefore conversion from T& to U& is allowed return *internal::check_user_type(state, n); } /** * Construct a user type value on the stack. * \note Instances created using this specialization are allocated and constructed as full user * data types in Lua. The default garbage-collecting hook will destruct the user type, * once it has been marked. * \param state Lua state * \param args Constructor arguments * \returns Number of values that have been pushed onto the stack */ template static inline size_t push(State* state, A&&... args) { void* mem = lua_newuserdata(state, sizeof(T)); if (!mem) { luaL_error(state, "Failed to allocate user type"); return -1; } // Construct new (mem) T {std::forward(args)...}; // Apply metatable for unqualified type T internal::apply_user_type_meta_table(state); return 1; } }; /** * Construct a user type value on the stack. * \note Instances created using this specialization are allocated and constructed as full user * data types in Lua. The default garbage-collecting hook will destruct the user type, * once it has been marked. * \param state Lua state * \param args Constructor arguments * \returns Reference to the constructed value */ template static inline internal::StripUserType& construct(State* state, A&&... args) { using T = internal::StripUserType; void* mem = lua_newuserdata(state, sizeof(T)); if (!mem) { luaL_error(state, "Failed to allocate user type"); // 'luaL_error' will not return } // Construct T* value = new (mem) T {std::forward(args)...}; // Apply metatable for unqualified type T internal::apply_user_type_meta_table(state); return *value; } /** * User type */ template struct Value { using T = internal::StripUserType; /** * Reference a user type value on the stack. * \param state Lua state * \param n Stack index * \returns Pointer to the user type value. */ static inline U* read(State* state, int n) { // T is unqualified, therefore conversion from T* to U* is allowed return internal::check_user_type(state, n); } /** * Copy a value onto the stack. This function behaves exactly as if you would call * `Value::push(state, *ptr)`. * \param state Lua state * \param ptr Pointer to the user type value * \returns Number of values that have been pushed */ static inline size_t push(State* state, const T* ptr) { return Value::push(state, *ptr); } }; using MemberMap = std::map; /** * Register the metatable for user type `T`. This function allows you to register methods * which are shared across all instances of this type. * * By default a garbage-collector hook and string representation function are added as meta methods. * Both can be overwritten. * * \tparam U User type struct or class * * \param state Lua state * \param methods Map of methods * \param meta_methods Map of meta methods */ template static inline void registerUserType( State* state, const MemberMap& methods = MemberMap(), const MemberMap& meta_methods = MemberMap() ) { using T = internal::StripUserType; // Setup an appropriate metatable name internal::new_user_type_metatable(state); // Register methods if (methods.size() > 0 && meta_methods.count("__index") == 0) { push(state, "__index"); lua_newtable(state); for (auto& method: methods) { setFields(state, -1, method.first, method.second); } lua_rawset(state, -3); } // Register garbage-collection hook if (meta_methods.count("__gc") == 0) { setFields(state, -1, "__gc", &internal::destruct_user_type); } // Register string representation function if (meta_methods.count("__tostring") == 0) { setFields(state, -1, "__tostring", &internal::stringify_user_type); } // Insert meta methods for (const auto& metamethod: meta_methods) { setFields(state, -1, metamethod.first, metamethod.second); } // Pop metatable off the stack lua_pop(state, -1); } namespace internal { template struct UserTypeSignature { static_assert( sizeof(T) == -1, "Parameter to UserTypeSignature is not a valid signature" ); }; template struct UserTypeSignature { using UserType = StripUserType; static inline void registerConstructor(State* state, const std::string& name) { setGlobal(state, name, &construct_user_type); } }; } /** * Same as the other `registerUserType` but registers the construtor as well. The template parameter * is a signature `U(A...)` where `U` is the user type and `A...` its constructor parameters. */ template static inline void registerUserType( State* state, const std::string& ctor_name, const MemberMap& methods = MemberMap(), const MemberMap& meta_methods = MemberMap() ) { using U = typename internal::UserTypeSignature::UserType; registerUserType(state, methods, meta_methods); internal::UserTypeSignature::registerConstructor(state, ctor_name); } LUWRA_NS_END /** * Generate a `lua_CFunction` wrapper for a constructor. * \param type Type to instantiate * \param ... Constructor parameter types * \return Wrapped function as `lua_CFunction` */ #define LUWRA_WRAP_CONSTRUCTOR(type, ...) \ (&luwra::internal::construct_user_type, __VA_ARGS__>) #define LUWRA_FIELD(type, name) {__STRING(name), LUWRA_WRAP_FIELD(type::name)} #define LUWRA_METHOD(type, name) {__STRING(name), LUWRA_WRAP_METHOD(type::name)} #define LUWRA_FUNCTION(type, name) {__STRING(name), LUWRA_WRAP_FUNCTION(type::name)} #endif