/* 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 "functions.hpp" #include #include #include #include LUWRA_NS_BEGIN namespace internal { static std::atomic_size_t user_type_counter; template std::string user_type_name = ""; template int construct_user_type(State* state) { return internal::Layout::direct( state, 1, &Value::template push, state ); } template int destruct_user_type(State* state) { if (!lua_islightuserdata(state, 1)) Value::read(state, 1).~T(); return 0; } template int stringify_user_type(State* state) { return push(state, internal::user_type_name); } template int access_user_type_property(State* state) { if (lua_gettop(state) > 1) { // Setter (Value::read(state, 1).*property_pointer) = Value::read(state, 2); return 0; } else { // Getter return push(state, Value::read(state, 1).*property_pointer); } } template struct MethodWrapper { static_assert( sizeof(T) == -1, "The MethodWrapper template expects a type name and a function signature as parameter" ); }; template struct MethodWrapper { using MethodPointerType = R (T::*)(A...); using FunctionSignature = R (T&, A...); template static R delegate(T& parent, A... args) { return (parent.*method_pointer)(std::forward(args)...); } }; } /** * User type T. * 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. */ template struct Value { static inline T& read(State* state, int n) { assert(!internal::user_type_name.empty()); return *static_cast( luaL_checkudata(state, n, internal::user_type_name.c_str()) ); } template static inline int push(State* state, A&&... args) { assert(!internal::user_type_name.empty()); void* mem = lua_newuserdata(state, sizeof(T)); if (!mem) { luaL_error(state, "Failed to allocate user type"); return -1; } // Call constructor on instance new (mem) T(std::forward(args)...); // Set metatable for this type luaL_getmetatable(state, internal::user_type_name.c_str()); lua_setmetatable(state, -2); return 1; } }; /** * User type T. * Instances created using this specialization are allocated as light user data in Lua. * The default garbage-collector does not destruct light user data types. */ template struct Value { static inline T* read(State* state, int n) { assert(!internal::user_type_name.empty()); return static_cast( luaL_checkudata(state, n, internal::user_type_name.c_str()) ); } static inline int push(State* state, T* instance) { assert(!internal::user_type_name.empty()); // push instance as light user data lua_pushlightuserdata(state, instance); // Set metatable for this type luaL_getmetatable(state, internal::user_type_name.c_str()); lua_setmetatable(state, -2); return 1; } }; /** * Register the metatable for user type `T`. This function allows you to register methods * which are shared across all instances of this type. A garbage-collector hook is also inserted. * Meta-methods can be added and/or overwritten aswell. */ template static inline void register_user_type( State* state, const std::map& methods, const std::map& meta_methods = {} ) { // Setup an appropriate meta table name if (internal::user_type_name.empty()) internal::user_type_name = "UD#" + std::to_string(internal::user_type_counter++); luaL_newmetatable(state, internal::user_type_name.c_str()); // Register methods if (methods.size() > 0 && meta_methods.count("__index") == 0) { lua_pushstring(state, "__index"); lua_newtable(state); for (auto& method: methods) { lua_pushstring(state, method.first); lua_pushcfunction(state, method.second); lua_rawset(state, -3); } lua_rawset(state, -3); } // Register garbage-collection hook if (meta_methods.count("__gc") == 0) { lua_pushstring(state, "__gc"); lua_pushcfunction(state, &internal::destruct_user_type); lua_rawset(state, -3); } // Register string representation function if (meta_methods.count("__tostring") == 0) { lua_pushstring(state, "__tostring"); lua_pushcfunction(state, &internal::stringify_user_type); lua_rawset(state, -3); } // Insert meta methods for (const auto& metamethod: meta_methods) { lua_pushstring(state, metamethod.first); lua_pushcfunction(state, metamethod.second); lua_rawset(state, -3); } // Pop meta table off the stack lua_pop(state, -1); } /** * Constructor function for a type `T`. Variadic arguments must be used to specify which parameters * to use during construction. */ template constexpr CFunction wrap_constructor = &internal::construct_user_type; /** * Works similiar to `wrap_function`. Given a class or struct declaration as follows: * * struct T { * R my_method(A0, A1 ... An); * }; * * You might wrap this method easily: * * CFunction wrapped_meth = wrap_method; * * In Lua, assuming `instance` is a userdata instance of type `T`, x0, x1 ... xn are instances * of A0, A1 ... An, and the method has been bound as `my_method`; it is possible to invoke the * method like so: * * instance:my_method(x0, x1 ... xn) */ template < typename T, typename S, typename internal::MethodWrapper::MethodPointerType method_pointer > constexpr CFunction wrap_method = wrap_function< typename internal::MethodWrapper::FunctionSignature, internal::MethodWrapper::template delegate >; /** * Property accessor method * * struct T { * R my_property; * }; * * The wrapped property accessor is also a function: * * CFunction wrapped_property = wrap_property; */ template < typename T, typename R, R T::* property_pointer > constexpr CFunction wrap_property = &internal::access_user_type_property; LUWRA_NS_END #endif