#pragma once #include #include #include #define ASSERT(...) #define UNUSED(x) (void)(x) #ifndef alignof #define alignof(T) std::alignment_of::value #endif namespace lua { /** * Base macro for declaring a Lua/C function. Any function declared will need to be exported via * the exportedFunctions() function in BaseLuaObject. See BaseLuaObject below for more * information. This macro declares a static "thunk" which checks the user data, optionally checks * for object death (again see BaseLuaObject below for more info), and then invokes a normal * object method. The actual object method needs to be implemented by the class. * @param Class supplies the owning class name. * @param Name supplies the function name. * @param Index supplies the stack index where "this" (Lua/C userdata) is found. */ #define DECLARE_LUA_FUNCTION_EX(Class, Name, Index) \ static int static_##Name(lua_State* state) { \ Class* object = lua::alignAndCast( \ luaL_checkudata(state, Index, typeid(Class).name())); \ object->checkDead(state); \ return object->Name(state); \ } \ int Name(lua_State* state); /** * Declare a Lua function in which userdata is in stack slot 1. See DECLARE_LUA_FUNCTION_EX() */ #define DECLARE_LUA_FUNCTION(Class, Name) DECLARE_LUA_FUNCTION_EX(Class, Name, 1) /** * Declare a Lua function in which userdata is in upvalue slot 1. See DECLARE_LUA_FUNCTION_EX() */ #define DECLARE_LUA_CLOSURE(Class, Name) DECLARE_LUA_FUNCTION_EX(Class, Name, lua_upvalueindex(1)) /** * Calculate the maximum space needed to be aligned. */ template size_t maximumSpaceNeededToAlign() { // The allocated memory can be misaligned up to `alignof(T) - 1` bytes. Adding it to the size to // allocate. return sizeof(T) + alignof(T) - 1; } template inline T* alignAndCast(void* mem) { size_t size = maximumSpaceNeededToAlign(); return static_cast(std::align(alignof(T), sizeof(T), mem, size)); } /** * Create a new user data and assign its metatable. */ template inline T* allocateLuaUserData(lua_State* state) { void* mem = lua_newuserdata(state, maximumSpaceNeededToAlign()); luaL_getmetatable(state, typeid(T).name()); ASSERT(lua_istable(state, -1)); lua_setmetatable(state, -2); return alignAndCast(mem); } /** * This is the base class for all C++ objects that we expose out to Lua. The goal is to hide as * much ugliness as possible. In general, to use this, do the following: * 1) Make your class derive from BaseLuaObject * 2) Define methods using DECLARE_LUA_FUNCTION* macros * 3) Export your functions by declaring a static exportedFunctions() method in your class. * 4) Optionally manage "death" status on your object. (See checkDead() and markDead() below). * 5) Generally you will want to hold your objects inside a LuaRef or a LuaDeathRef. See below * for more information on those containers. * * It's very important to understand the Lua memory model: Once an object is created, *it is * owned by Lua*. Lua can GC it at any time. If you want to make sure that does not happen, you * must hold a ref to it in C++, generally via LuaRef or LuaDeathRef. */ template class BaseLuaObject { public: using ExportedFunctions = std::vector>; virtual ~BaseLuaObject() = default; /** * Create a new object of this type, owned by Lua. This type must have previously been registered * via the registerType() routine below. * @param state supplies the owning Lua state. * @param args supplies the variadic constructor arguments for the object. * @return a pair containing a pointer to the new object and the state it was created with. (This * is done for convenience when passing a created object to a LuaRef or a LuaDeathRef. */ template static std::pair create(lua_State* state, ConstructorArgs&&... args) { // Memory is allocated via Lua and it is raw. We use placement new to run the constructor. T* mem = allocateLuaUserData(state); return {new (mem) T(std::forward(args)...), state}; } /** * Register a type with Lua. * @param state supplies the state to register with. */ static void registerType(lua_State* state) { std::vector to_register; // Fetch all of the functions to be exported to Lua so that we can register them in the // metatable. ExportedFunctions functions = T::exportedFunctions(); for (auto function : functions) { to_register.push_back({function.first, function.second}); } // Always register a __gc method so that we can run the object's destructor. We do this // manually because the memory is raw and was allocated by Lua. to_register.push_back( {"__gc", [](lua_State* state) { T* object = alignAndCast(luaL_checkudata(state, 1, typeid(T).name())); object->~T(); return 0; }}); // Add the sentinel. to_register.push_back({nullptr, nullptr}); // Register the type by creating a new metatable, setting __index to itself, and then // performing the register. luaL_newmetatable(state, typeid(T).name()); lua_pushvalue(state, -1); lua_setfield(state, -2, "__index"); luaL_register(state, nullptr, to_register.data()); } /** * This function is called as part of the DECLARE_LUA_FUNCTION* macros. The idea here is that * we cannot control when Lua destroys things. However, we may expose wrappers to a script that * should not be used after some event. This allows us to mark objects as dead so that if they * are used again they will throw a Lua error and not reach our code. * @param state supplies the calling LuaState. */ int checkDead(lua_State* state) { if (dead_) { return luaL_error(state, "object used outside of proper scope"); } return 0; } /** * Mark an object as dead so that a checkDead() call will throw an error. See checkDead(). */ void markDead() { dead_ = true; onMarkDead(); } /** * Mark an object as live so that a checkDead() call will not throw an error. See checkDead(). */ void markLive() { dead_ = false; onMarkLive(); } protected: /** * Called from markDead() when an object is marked dead. This is effectively a C++ destructor for * Lua/C objects. Objects can perform inline cleanup or mark other objects as dead if needed. It * can also be used to protect objects from use if they get assigned to a global variable and * used across coroutines. */ virtual void onMarkDead() {} /** * Called from markLive() when an object is marked live. This is a companion to onMarkDead(). See * the comments there. */ virtual void onMarkLive() {} private: bool dead_{}; }; /** * This is basically a Lua smart pointer. The idea is that given a Lua object, if we want to * guarantee that Lua won't destroy it, we need to reference it. This wraps the reference * functionality. While a LuaRef owns an object it's guaranteed that Lua will not GC it. * TODO(mattklein123): Add dedicated unit tests. This will require mocking a Lua state. */ template class LuaRef { public: /** * Create an empty LuaRef. */ LuaRef() { reset(); } /** * Create a LuaRef from an object. * @param object supplies the object. Generally this is the return value from a Object::create() * call. The object must be at the top of the Lua stack. * @param leave_on_stack supplies whether to leave the object on the stack or not when the ref * is constructed. */ LuaRef(const std::pair& object, bool leave_on_stack) { reset(object, leave_on_stack); } ~LuaRef() { unref(); } T* get() { return object_.first; } /** * Same as the LuaRef non-default constructor, but post-construction. */ void reset(const std::pair& object, bool leave_on_stack) { unref(); if (leave_on_stack) { lua_pushvalue(object.second, -1); } object_ = object; ref_ = luaL_ref(object_.second, LUA_REGISTRYINDEX); ASSERT(ref_ != LUA_REFNIL); } /** * Return a LuaRef to its default/empty state. */ void reset() { unref(); object_ = std::pair{}; ref_ = LUA_NOREF; } /** * Push the referenced object back onto the stack. */ void pushStack() { ASSERT(object_.first); lua_rawgeti(object_.second, LUA_REGISTRYINDEX, ref_); } protected: void unref() { if (object_.second != nullptr) { luaL_unref(object_.second, LUA_REGISTRYINDEX, ref_); } } std::pair object_; int ref_; }; /** * This is a variant of LuaRef which also marks an object as dead during destruction. This is * useful if an object should not be used after the scope of the pcall() or resume(). * TODO(mattklein123): Add dedicated unit tests. This will require mocking a Lua state. */ template class LuaDeathRef : public LuaRef { public: ~LuaDeathRef() { markDead(); } void markDead() { if (this->object_.first) { this->object_.first->markDead(); } } void markLive() { if (this->object_.first) { this->object_.first->markLive(); } } void reset(const std::pair& object, bool leave_on_stack) { markDead(); LuaRef::reset(object, leave_on_stack); } void reset() { markDead(); LuaRef::reset(); } }; }