// https://github.com/kunitoki/LuaBridge3 // Copyright 2020, kunitoki // Copyright 2019, Dmitry Tarakanov // Copyright 2012, Vinnie Falco // Copyright 2007, Nathan Reed // SPDX-License-Identifier: MIT #pragma once #include "Config.h" #include "ClassInfo.h" #include "FlagSet.h" #include "LuaHelpers.h" #include "LuaException.h" #include "Options.h" #include "TypeTraits.h" #include #include #include #include #include #include namespace luabridge { //================================================================================================= /** * @brief Base for class and namespace registration. * * Maintains Lua stack in the proper state. Once beginNamespace, beginClass or deriveClass is called the parent object upon its destruction * may no longer clear the Lua stack. * * Then endNamespace or endClass is called, a new parent is created and the child transfers the responsibility for clearing stack to it. * * So there can be maximum one "active" registrar object. */ namespace detail { struct BaseClassInfo { const void* staticKey; const void* classKey; lua_Integer castOffset; }; template lua_Integer computeCastOffset() noexcept { static_assert(std::is_base_of_v); alignas(Derived) std::byte buf[sizeof(Derived)] = {}; auto* derived = reinterpret_cast(buf); auto* base = static_cast(derived); // implicit upcast, purely pointer arithmetic return static_cast(reinterpret_cast(base) - reinterpret_cast(derived)); } class Registrar { protected: Registrar(lua_State* L) : L(L) , m_stackSize(0) { } Registrar(lua_State* L, int skipStackPops) : L(L) , m_stackSize(0) , m_skipStackPops(skipStackPops) { } Registrar(const Registrar& rhs) = delete; Registrar(Registrar&& rhs) : L(rhs.L) , m_stackSize(std::exchange(rhs.m_stackSize, 0)) , m_skipStackPops(std::exchange(rhs.m_skipStackPops, 0)) { } Registrar& operator=(const Registrar& rhs) = delete; Registrar& operator=(Registrar&& rhs) { m_stackSize = std::exchange(rhs.m_stackSize, 0); m_skipStackPops = std::exchange(rhs.m_skipStackPops, 0); return *this; } ~Registrar() { const int popsCount = m_stackSize - m_skipStackPops; if (popsCount > 0) { LUABRIDGE_ASSERT(popsCount <= lua_gettop(L)); lua_pop(L, popsCount); } } void assertIsActive() const { if (m_stackSize == 0) { throw_or_assert("Unable to continue registration"); } } lua_State* const L = nullptr; int m_stackSize = 0; int m_skipStackPops = 0; }; } // namespace detail //================================================================================================= /** * @brief Provides C++ to Lua registration capabilities. * * This class is not instantiated directly, call `getGlobalNamespace` to start the registration process. */ class Namespace : public detail::Registrar { //============================================================================================= #if 0 /** * @brief Error reporting. * * This function looks handy, why aren't we using it? */ static int luaError(lua_State* L, std::string message) { LUABRIDGE_ASSERT(lua_isstring(L, lua_upvalueindex(1))); std::string s; // Get information on the caller's caller to format the message, // so the error appears to originate from the Lua source. lua_Debug ar; int result = lua_getstack(L, 2, &ar); if (result != 0) { lua_getinfo(L, "Sl", &ar); s = ar.short_src; if (ar.currentline != -1) { // poor mans int to string to avoid . lua_pushnumber(L, ar.currentline); s = s + ":" + lua_tostring(L, -1) + ": "; lua_pop(L, 1); } } s = s + message; luaL_error(L, "%s", s.c_str()); } #endif //============================================================================================= /** * @brief Factored base to reduce template instantiations. */ class ClassBase : public detail::Registrar { public: explicit ClassBase(const char* name, Namespace parent) : Registrar(std::move(parent)) , className(name == nullptr ? "" : name) { } using Registrar::operator=; protected: static void appendParentList(lua_State* L, int parentsIndex, int visitedIndex, int baseMetatableIndex) { parentsIndex = lua_absindex(L, parentsIndex); visitedIndex = lua_absindex(L, visitedIndex); baseMetatableIndex = lua_absindex(L, baseMetatableIndex); LUABRIDGE_ASSERT(lua_istable(L, parentsIndex)); LUABRIDGE_ASSERT(lua_istable(L, visitedIndex)); LUABRIDGE_ASSERT(lua_istable(L, baseMetatableIndex)); const auto appendUnique = [L, parentsIndex, visitedIndex](int metatableIndex) { metatableIndex = lua_absindex(L, metatableIndex); if (! lua_istable(L, metatableIndex)) return; auto* metatablePtr = const_cast(lua_topointer(L, metatableIndex)); LUABRIDGE_ASSERT(metatablePtr != nullptr); lua_pushlightuserdata(L, metatablePtr); lua_rawget(L, visitedIndex); const bool alreadyVisited = ! lua_isnil(L, -1); lua_pop(L, 1); if (alreadyVisited) return; lua_pushlightuserdata(L, metatablePtr); lua_pushboolean(L, 1); lua_rawset(L, visitedIndex); lua_pushvalue(L, metatableIndex); lua_rawseti(L, parentsIndex, static_cast(static_cast(get_length(L, parentsIndex)) + 1)); }; appendUnique(baseMetatableIndex); lua_rawgetp_x(L, baseMetatableIndex, detail::getParentKey()); // Stack: ..., parent list | nil if (! lua_istable(L, -1)) { lua_pop(L, 1); return; } const int parentListIndex = lua_absindex(L, -1); const int count = get_length(L, parentListIndex); for (int i = 1; i <= count; ++i) { lua_rawgeti(L, parentListIndex, i); appendUnique(-1); lua_pop(L, 1); } lua_pop(L, 1); } void setObjectMetaMethods(int tableIndex, bool simple) { tableIndex = lua_absindex(L, tableIndex); if (simple) { lua_rawgetp_x(L, tableIndex, detail::getPropgetKey()); // Stack: ..., pg | nil lua_pushvalue(L, tableIndex); // Stack: ..., pg | nil, mt lua_pushcclosure_x(L, &detail::index_metamethod_simple, "__index", 2); rawsetfield(L, tableIndex, "__index"); lua_rawgetp_x(L, tableIndex, detail::getPropsetKey()); // Stack: ..., ps | nil lua_pushcclosure_x(L, &detail::newindex_metamethod_simple, "__newindex", 1); rawsetfield(L, tableIndex, "__newindex"); } else { lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); rawsetfield(L, tableIndex, "__index"); lua_pushcfunction_x(L, &detail::newindex_metamethod, "__newindex"); rawsetfield(L, tableIndex, "__newindex"); } } void setStaticMetaMethods(int tableIndex, bool simple) { tableIndex = lua_absindex(L, tableIndex); if (simple) { lua_rawgetp_x(L, tableIndex, detail::getPropgetKey()); // Stack: ..., pg | nil lua_rawgetp_x(L, tableIndex, detail::getClassKey()); // Stack: ..., pg | nil, cl | nil lua_pushvalue(L, tableIndex); // Stack: ..., pg | nil, cl | nil, mt lua_pushcclosure_x(L, &detail::index_metamethod_simple, "__index", 3); rawsetfield(L, tableIndex, "__index"); lua_rawgetp_x(L, tableIndex, detail::getPropsetKey()); // Stack: ..., ps | nil lua_pushcclosure_x(L, &detail::newindex_metamethod_simple, "__newindex", 1); rawsetfield(L, tableIndex, "__newindex"); } else { lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); rawsetfield(L, tableIndex, "__index"); lua_pushcfunction_x(L, &detail::newindex_metamethod, "__newindex"); rawsetfield(L, tableIndex, "__newindex"); } } //========================================================================================= /** * @brief Create the const table. */ void createConstTable(const char* name, bool trueConst, Options options) { LUABRIDGE_ASSERT(name != nullptr); std::string type_name = std::string(trueConst ? "const " : "") + name; // Stack: namespace table (ns) lua_newtable(L); // Stack: ns, const table (co) lua_pushvalue(L, -1); // Stack: ns, co, co lua_setmetatable(L, -2); // co.__metatable = co. Stack: ns, co pushunsigned(L, options.toUnderlying()); // Stack: ns, co, options lua_rawsetp_x(L, -2, detail::getClassOptionsKey()); // co [classOptionsKey] = options. Stack: ns, co lua_pushstring(L, type_name.c_str()); // Stack: ns, co, name lua_rawsetp_x(L, -2, detail::getTypeKey()); // co [typeKey] = name. Stack: ns, co lua_newtable(L); // Stack: ns, co, propget table (pg) lua_rawsetp_x(L, -2, detail::getPropgetKey()); // Stack: ns, co setObjectMetaMethods(-1, ! options.test(extensibleClass)); // Stack: ns, co if (! options.test(visibleMetatables)) { lua_pushboolean(L, 0); rawsetfield(L, -2, "__metatable"); } } //========================================================================================= /** * @brief Create the class table. * * The Lua stack should have the const table on top. */ void createClassTable(const char* name, Options options) { LUABRIDGE_ASSERT(name != nullptr); // Stack: namespace table (ns), const table (co) // Class table is the same as const table except the propset table createConstTable(name, false, options); // Stack: ns, co, cl lua_newtable(L); // Stack: ns, co, cl, propset table (ps) lua_rawsetp_x(L, -2, detail::getPropsetKey()); // cl [propsetKey] = ps. Stack: ns, co, cl lua_pushvalue(L, -2); // Stack: ns, co, cl, co lua_rawsetp_x(L, -2, detail::getConstKey()); // cl [constKey] = co. Stack: ns, co, cl lua_pushvalue(L, -1); // Stack: ns, co, cl, cl lua_rawsetp_x(L, -3, detail::getClassKey()); // co [classKey] = cl. Stack: ns, co, cl if (! options.test(extensibleClass)) setObjectMetaMethods(-1, true); } //========================================================================================= /** * @brief Create the static table. */ void createStaticTable(const char* name, Options options) { LUABRIDGE_ASSERT(name != nullptr); // Stack: namespace table (ns), const table (co), class table (cl) lua_newtable(L); // Stack: ns, co, cl, static table (st) lua_newtable(L); // Stack: ns, co, cl, st, static metatable (mt) lua_pushvalue(L, -1); // Stack: ns, co, cl, st, mt, mt lua_setmetatable(L, -3); // st.__metatable = mt. Stack: ns, co, cl, st, mt lua_insert(L, -2); // Stack: ns, co, cl, st, mt, st rawsetfield(L, -5, name); // ns [name] = st. Stack: ns, co, cl, st, mt pushunsigned(L, options.toUnderlying()); // Stack: ns, co, cl, st, mt, options lua_rawsetp_x(L, -2, detail::getClassOptionsKey()); // st [classOptionsKey] = options. Stack: ns, co, cl, st, mt lua_newtable(L); // Stack: ns, co, cl, st, proget table (pg) lua_rawsetp_x(L, -2, detail::getPropgetKey()); // st [propgetKey] = pg. Stack: ns, co, cl, st lua_newtable(L); // Stack: ns, co, cl, st, propset table (ps) lua_rawsetp_x(L, -2, detail::getPropsetKey()); // st [propsetKey] = pg. Stack: ns, co, cl, st lua_pushvalue(L, -2); // Stack: ns, co, cl, st, cl lua_rawsetp_x(L, -2, detail::getClassKey()); // st [classKey] = cl. Stack: ns, co, cl, st setStaticMetaMethods(-1, ! options.test(extensibleClass)); if (! options.test(visibleMetatables)) { lua_pushboolean(L, 0); rawsetfield(L, -2, "__metatable"); } } //========================================================================================= /** * @brief Asserts on stack state. */ void assertStackState() const { // Stack: const table (co), class table (cl), static table (st) LUABRIDGE_ASSERT(lua_istable(L, -3)); LUABRIDGE_ASSERT(lua_istable(L, -2)); LUABRIDGE_ASSERT(lua_istable(L, -1)); } //========================================================================================= const char* className = ""; }; //============================================================================================= /** * @brief Provides a class registration in a lua_State. * * After construction the Lua stack holds these objects: * -1 static table * -2 class table * -3 const table * -4 enclosing namespace table */ template class Class : public ClassBase { public: //========================================================================================= /** * @brief Register a new class or add to an existing class registration. * * @param name The new class name. * @param parent A parent namespace object. * @param options Class options. */ Class(const char* name, Namespace parent, Options options) : ClassBase(name, std::move(parent)) { LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) rawgetfield(L, -1, name); // Stack: ns, static table (st) | nil if (lua_isnil(L, -1)) // Stack: ns, nil { lua_pop(L, 1); // Stack: ns createConstTable(name, true, options); // Stack: ns, const table (co) ++m_stackSize; #if !defined(LUABRIDGE_ON_LUAU) lua_pushcfunction_x(L, &detail::gc_metamethod, "__gc"); // Stack: ns, co, function rawsetfield(L, -2, "__gc"); // co ["__gc"] = function. Stack: ns, co #endif lua_pushcfunction_x(L, &detail::tostring_metamethod, "__tostring"); rawsetfield(L, -2, "__tostring"); createClassTable(name, options); // Stack: ns, co, class table (cl) ++m_stackSize; lua_pushlightuserdata(L, const_cast(detail::getConstRegistryKey())); // Stack: ns, co, cl, id lua_rawsetp_x(L, -3, detail::getTypeIdentityKey()); // co[typeIdentityKey] = const id. Stack: ns, co, cl lua_pushlightuserdata(L, const_cast(detail::getClassRegistryKey())); // Stack: ns, co, cl, id lua_rawsetp_x(L, -2, detail::getTypeIdentityKey()); // cl[typeIdentityKey] = class id. Stack: ns, co, cl #if !defined(LUABRIDGE_ON_LUAU) lua_pushcfunction_x(L, &detail::gc_metamethod, "__gc"); // Stack: ns, co, cl, function rawsetfield(L, -2, "__gc"); // cl ["__gc"] = function. Stack: ns, co, cl #endif lua_pushcfunction_x(L, &detail::tostring_metamethod, "__tostring"); rawsetfield(L, -2, "__tostring"); createStaticTable(name, options); // Stack: ns, co, cl, st ++m_stackSize; lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st lua_rawsetp_x(L, -2, detail::getStaticKey()); // cl [staticKey] = st. Stack: ns, co, cl, st lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st lua_rawsetp_x(L, -3, detail::getStaticKey()); // co [staticKey] = st. Stack: ns, co, cl, st // Map T back to its tables. lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getStaticRegistryKey()); // Stack: ns, co, cl, st lua_pushvalue(L, -2); // Stack: ns, co, cl, st, cl lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); // Stack: ns, co, cl, st lua_pushvalue(L, -3); // Stack: ns, co, cl, st, co lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); // Stack: ns, co, cl, st } else { LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: ns, vst ++m_stackSize; lua_getmetatable(L, -1); // Stack: ns, vst, st lua_insert(L, -2); // Stack: ns, st, vst lua_pop(L, 1); // Stack: ns, st // Map T back from its stored tables lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); // Stack: ns, st, co LUABRIDGE_ASSERT(lua_istable(L, -1)); // Class was previously registered as table or namespace ? lua_insert(L, -2); // Stack: ns, co, st ++m_stackSize; lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); // Stack: ns, co, st, cl LUABRIDGE_ASSERT(lua_istable(L, -1)); // Class was previously registered as table or namespace ? lua_insert(L, -2); // Stack: ns, co, cl, st ++m_stackSize; } } //========================================================================================= /** * @brief Derive a new class. * * @param name The class name. * @param parent A parent namespace object. * @param staticKey Key where the class is stored. */ Class(const char* name, Namespace parent, std::initializer_list bases, Options options) : ClassBase(name, std::move(parent)) { LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) createConstTable(name, true, options); // Stack: ns, const table (co) ++m_stackSize; #if !defined(LUABRIDGE_ON_LUAU) lua_pushcfunction_x(L, &detail::gc_metamethod, "__gc"); // Stack: ns, co, function rawsetfield(L, -2, "__gc"); // co ["__gc"] = function. Stack: ns, co #endif lua_pushcfunction_x(L, &detail::tostring_metamethod, "__tostring"); rawsetfield(L, -2, "__tostring"); createClassTable(name, options); // Stack: ns, co, class table (cl) ++m_stackSize; lua_pushlightuserdata(L, const_cast(detail::getConstRegistryKey())); // Stack: ns, co, cl, id lua_rawsetp_x(L, -3, detail::getTypeIdentityKey()); // co[typeIdentityKey] = const id. Stack: ns, co, cl lua_pushlightuserdata(L, const_cast(detail::getClassRegistryKey())); // Stack: ns, co, cl, id lua_rawsetp_x(L, -2, detail::getTypeIdentityKey()); // cl[typeIdentityKey] = class id. Stack: ns, co, cl #if !defined(LUABRIDGE_ON_LUAU) lua_pushcfunction_x(L, &detail::gc_metamethod, "__gc"); // Stack: ns, co, cl, function rawsetfield(L, -2, "__gc"); // cl ["__gc"] = function. Stack: ns, co, cl #endif lua_pushcfunction_x(L, &detail::tostring_metamethod, "__tostring"); rawsetfield(L, -2, "__tostring"); createStaticTable(name, options); // Stack: ns, co, cl, st ++m_stackSize; lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st lua_rawsetp_x(L, -2, detail::getStaticKey()); // cl [staticKey] = st. Stack: ns, co, cl, st lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st lua_rawsetp_x(L, -3, detail::getStaticKey()); // co [staticKey] = st. Stack: ns, co, cl, st const int coIndex = lua_absindex(L, -3); const int clIndex = lua_absindex(L, -2); const int stIndex = lua_absindex(L, -1); lua_newtable(L); // Stack: ns, co, cl, st, cl parents const int clParentsIndex = lua_absindex(L, -1); lua_newtable(L); // Stack: ns, co, cl, st, cl parents, cast table const int castTableIndex = lua_absindex(L, -1); lua_newtable(L); // Stack: ns, co, cl, st, cl parents, cast table, visited const int visitedIndex = lua_absindex(L, -1); for (const detail::BaseClassInfo& base : bases) { lua_rawgetp_x(L, LUA_REGISTRYINDEX, base.staticKey); // Stack: ..., visited, base st | nil if (! lua_istable(L, -1)) { lua_pop(L, 4); // pop (nil), visited, cast table, cl parents. Stack: ns, co, cl, st throw_or_assert("Base class is not registered"); return; } lua_rawgetp_x(L, -1, detail::getClassKey()); // Stack: ..., visited, base st, base cl | nil if (! lua_istable(L, -1)) { lua_pop(L, 5); // pop (nil), base st, visited, cast table, cl parents. Stack: ns, co, cl, st throw_or_assert("Base class is not registered"); return; } appendParentList(L, clParentsIndex, visitedIndex, -1); // Store the direct pointer adjustment offset for this base class lua_pushlightuserdata(L, const_cast(base.classKey)); lua_pushinteger(L, base.castOffset); lua_rawset(L, castTableIndex); // Compose and propagate ancestor offsets from the base's own cast table. // offset(T → ancestor) = offset(T → base) + offset(base → ancestor) lua_rawgetp_x(L, -1, detail::getCastTableKey()); // Stack: ..., base st, base cl, base cast table | nil if (lua_istable(L, -1)) { lua_pushnil(L); while (lua_next(L, -2) != 0) { // Stack: ..., base cast table, ancestor key, ancestor offset const lua_Integer ancestorOffset = lua_tointeger(L, -1); lua_pushvalue(L, -2); // duplicate ancestor key to check presence lua_rawget(L, castTableIndex); const bool alreadyPresent = ! lua_isnil(L, -1); lua_pop(L, 1); if (! alreadyPresent) { lua_pushvalue(L, -2); // push ancestor key lua_pushinteger(L, base.castOffset + ancestorOffset); lua_rawset(L, castTableIndex); } lua_pop(L, 1); // pop ancestor offset; ancestor key stays for next() } lua_pop(L, 1); // pop base cast table } else { lua_pop(L, 1); // pop nil } lua_pop(L, 2); // pop base cl and base st. Stack: ..., cast table, visited } lua_pop(L, 1); // pop visited. Stack: ns, co, cl, st, cl parents, cast table // Store the cast table in both class and const metatables lua_pushvalue(L, castTableIndex); lua_rawsetp_x(L, clIndex, detail::getCastTableKey()); // cl[castTableKey] = cast table lua_pushvalue(L, castTableIndex); lua_rawsetp_x(L, coIndex, detail::getCastTableKey()); // co[castTableKey] = cast table lua_pop(L, 1); // pop cast table. Stack: ns, co, cl, st, cl parents lua_createtable(L, get_length(L, clParentsIndex), 0); // Stack: ns, co, cl, st, cl parents, co parents const int coParentsIndex = lua_absindex(L, -1); lua_createtable(L, get_length(L, clParentsIndex), 0); // Stack: ns, co, cl, st, cl parents, co parents, st parents const int stParentsIndex = lua_absindex(L, -1); const int parentCount = get_length(L, clParentsIndex); for (int i = 1; i <= parentCount; ++i) { lua_rawgeti(L, clParentsIndex, i); // Stack: ..., st parents, parent cl LUABRIDGE_ASSERT(lua_istable(L, -1)); lua_rawgetp_x(L, -1, detail::getConstKey()); // Stack: ..., parent cl, parent co LUABRIDGE_ASSERT(lua_istable(L, -1)); lua_rawseti(L, coParentsIndex, i); // Stack: ..., parent cl lua_rawgetp_x(L, -1, detail::getStaticKey()); // Stack: ..., parent cl, parent st LUABRIDGE_ASSERT(lua_istable(L, -1)); lua_rawseti(L, stParentsIndex, i); // Stack: ..., parent cl lua_pop(L, 1); // Stack: ..., st parents } lua_pushvalue(L, coParentsIndex); lua_rawsetp_x(L, coIndex, detail::getParentKey()); lua_pushvalue(L, clParentsIndex); lua_rawsetp_x(L, clIndex, detail::getParentKey()); lua_pushvalue(L, stParentsIndex); lua_rawsetp_x(L, stIndex, detail::getParentKey()); lua_pop(L, 3); // Stack: ns, co, cl, st setObjectMetaMethods(-3, false); // co setObjectMetaMethods(-2, false); // cl setStaticMetaMethods(-1, false); // st lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getStaticRegistryKey()); // Stack: ns, co, cl, st lua_pushvalue(L, -2); // Stack: ns, co, cl, st, cl lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); // Stack: ns, co, cl, st lua_pushvalue(L, -3); // Stack: ns, co, cl, st, co lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); // Stack: ns, co, cl, st } //========================================================================================= /** * @brief Continue registration in the enclosing namespace. * * @returns A parent registration object. */ Namespace endClass() { LUABRIDGE_ASSERT(m_stackSize > 3); m_stackSize -= 3; lua_pop(L, 3); return Namespace(std::move(*this)); } //========================================================================================= /** * @brief Add or replace a static property. */ template Class& addStaticProperty(const char* name, Getter get, bool) = delete; template Class& addStaticProperty(const char* name, Getter get) { LUABRIDGE_ASSERT(name != nullptr); assertStackState(); // Stack: const table (co), class table (cl), static table (st) detail::push_property_getter(L, std::move(get), name); // Stack: co, cl, st, function detail::add_property_getter(L, name, -2); // Stack: co, cl, st detail::push_property_readonly(L, name); // Stack: co, cl, st, function detail::add_property_setter(L, name, -2); // Stack: co, cl, st return *this; } template Class& addStaticProperty(const char* name, Getter get, Setter set) { LUABRIDGE_ASSERT(name != nullptr); assertStackState(); // Stack: const table (co), class table (cl), static table (st) detail::push_property_getter(L, std::move(get), name); // Stack: co, cl, st, function detail::add_property_getter(L, name, -2); // Stack: co, cl, st detail::push_property_setter(L, std::move(set), name); // Stack: co, cl, st, function detail::add_property_setter(L, name, -2); // Stack: co, cl, st return *this; } template Class& addStaticPropertyReadWrite(const char* name, Field member) { return addStaticProperty(name, member, member); } //========================================================================================= /** * @brief Add or replace a single static function or multiple overloaded functions. * * @param name The overload name. * @param functions A single or set of static functions that will be invoked. * * @returns This class registration object. */ template auto addStaticFunction(const char* name, Functions... functions) -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> { LUABRIDGE_ASSERT(name != nullptr); assertStackState(); // Stack: const table (co), class table (cl), static table (st) if constexpr (sizeof...(Functions) == 1) { ([&] { detail::push_function(L, std::move(functions), name); } (), ...); } else { // upvalue 1: OverloadSet (C++ struct with arity + type checker per overload) auto* overload_set_unaligned = lua_newuserdata_aligned(L); auto* overload_set = align(overload_set_unaligned); ([&] { detail::OverloadEntry entry; if constexpr (detail::is_any_cfunction_pointer_v) { entry.arity = -1; entry.checker = nullptr; } else { using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; } overload_set->entries.push_back(entry); } (), ...); // upvalue 2: flat table of function closures indexed 1..N lua_createtable(L, static_cast(sizeof...(Functions)), 0); int idx = 1; ([&] { detail::push_function(L, std::move(functions), name); lua_rawseti(L, -2, idx++); } (), ...); lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); } rawsetfield(L, -2, name); return *this; } //========================================================================================= /** * @brief Add a static index metamethod function fallback that is triggered when no result is found in static functions, properties or any other static members. * * Let the user define a fallback index (__index) metamethod for the static class table. */ template auto addStaticIndexMetaMethod(Function function) -> std::enable_if_t && std::is_invocable_v, Class&> { using FnType = decltype(function); assertStackState(); // Stack: const table (co), class table (cl), static table (st) lua_newuserdata_aligned(L, std::move(function)); // Stack: co, cl, st, function userdata (ud) lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__index", 1); // Stack: co, cl, st, function lua_rawsetp_x(L, -2, detail::getStaticIndexFallbackKey()); setStaticMetaMethods(-1, false); return *this; } Class& addStaticIndexMetaMethod(LuaRef (*idxf)(const LuaRef&, lua_State*)) { using FnType = decltype(idxf); assertStackState(); // Stack: const table (co), class table (cl), static table (st) lua_pushlightuserdata(L, reinterpret_cast(idxf)); // Stack: co, cl, st, function ptr lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__index", 1); // Stack: co, cl, st, function lua_rawsetp_x(L, -2, detail::getStaticIndexFallbackKey()); setStaticMetaMethods(-1, false); return *this; } //========================================================================================= /** * @brief Add a static new index metamethod function fallback that is triggered when no result is found in static functions, properties or any other static members. * * Let the user define a fallback new index (__newindex) metamethod for the static class table. */ template auto addStaticNewIndexMetaMethod(Function function) -> std::enable_if_t && std::is_invocable_v, Class&> { using FnType = decltype(function); assertStackState(); // Stack: const table (co), class table (cl), static table (st) lua_newuserdata_aligned(L, std::move(function)); // Stack: co, cl, st, function userdata (ud) lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__newindex", 1); // Stack: co, cl, st, function lua_rawsetp_x(L, -2, detail::getStaticNewIndexFallbackKey()); setStaticMetaMethods(-1, false); return *this; } Class& addStaticNewIndexMetaMethod(LuaRef (*idxf)(const LuaRef&, const LuaRef&, lua_State*)) { using FnType = decltype(idxf); assertStackState(); // Stack: const table (co), class table (cl), static table (st) lua_pushlightuserdata(L, reinterpret_cast(idxf)); // Stack: co, cl, st, function ptr lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__newindex", 1); // Stack: co, cl, st, function lua_rawsetp_x(L, -2, detail::getStaticNewIndexFallbackKey()); setStaticMetaMethods(-1, false); return *this; } //========================================================================================= /** * @brief Add or replace a property member, by constructible by std::function. */ template Class& addProperty(const char* name, Getter getter, bool) = delete; template Class& addProperty(const char* name, Getter getter) { LUABRIDGE_ASSERT(name != nullptr); assertStackState(); // Stack: const table (co), class table (cl), static table (st) detail::push_class_property_getter(L, std::move(getter), name); // Stack: co, cl, st, getter lua_pushvalue(L, -1); // Stack: co, cl, st, getter, getter detail::add_property_getter(L, name, -4); // Stack: co, cl, st, getter detail::add_property_getter(L, name, -4); // Stack: co, cl, st detail::push_property_readonly(L, name); // Stack: co, cl, st, function detail::add_property_setter(L, name, -3); // Stack: co, cl, st return *this; } template Class& addProperty(const char* name, Getter getter, Setter setter) { LUABRIDGE_ASSERT(name != nullptr); assertStackState(); // Stack: const table (co), class table (cl), static table (st) detail::push_class_property_getter(L, std::move(getter), name); // Stack: co, cl, st, getter lua_pushvalue(L, -1); // Stack: co, cl, st, getter, getter detail::add_property_getter(L, name, -4); // Stack: co, cl, st, getter detail::add_property_getter(L, name, -4); // Stack: co, cl, st detail::push_class_property_setter(L, std::move(setter), name); // Stack: co, cl, st, setter detail::add_property_setter(L, name, -3); // Stack: co, cl, st return *this; } template Class& addPropertyReadWrite(const char* name, Field T::*member) { return addProperty(name, member, member); } //========================================================================================= /** * @brief Add or replace a function that can operate on the class. * * @param name The function or overloaded functions name. * @param functions A single or set of functions that will be invoked. * * @returns This class registration object. */ template auto addFunction(const char* name, Functions... functions) -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> { LUABRIDGE_ASSERT(name != nullptr); assertStackState(); // Stack: const table (co), class table (cl), static table (st) if (name == std::string_view("__gc")) { throw_or_assert("__gc metamethod registration is forbidden"); return *this; } if constexpr (sizeof...(Functions) == 1) { ([&] { detail::push_member_function(L, std::move(functions), name); } (), ...); if constexpr (detail::const_functions_count == 1) { lua_pushvalue(L, -1); // Stack: co, cl, st, function, function rawsetfield(L, -4, name); // Stack: co, cl, st, function rawsetfield(L, -4, name); // Stack: co, cl, st } else { rawsetfield(L, -3, name); // Stack: co, cl, st } } else { // create new closure of const try_overload_functions with new table if constexpr (detail::const_functions_count > 0) { // upvalue 1: OverloadSet auto* overload_set_const_unaligned = lua_newuserdata_aligned(L); auto* overload_set_const = align(overload_set_const_unaligned); ([&] { if (!detail::is_const_function) return; detail::OverloadEntry entry; if constexpr (detail::is_any_cfunction_pointer_v) { entry.arity = -1; entry.checker = nullptr; } else if constexpr (detail::is_proxy_member_function_v) { using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; } else { using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; } overload_set_const->entries.push_back(entry); } (), ...); LUABRIDGE_ASSERT(!overload_set_const->entries.empty()); // upvalue 2: flat table of function closures lua_createtable(L, static_cast(detail::const_functions_count), 0); int idx = 1; ([&] { if (!detail::is_const_function) return; detail::push_member_function(L, std::move(functions), name); lua_rawseti(L, -2, idx++); } (), ...); lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); lua_pushvalue(L, -1); // Stack: co, cl, st, function, function rawsetfield(L, -4, name); // Stack: co, cl, st, function rawsetfield(L, -4, name); // Stack: co, cl, st } // create new closure of non const try_overload_functions with new table if constexpr (detail::non_const_functions_count > 0) { // upvalue 1: OverloadSet auto* overload_set_nonconst_unaligned = lua_newuserdata_aligned(L); auto* overload_set_nonconst = align(overload_set_nonconst_unaligned); ([&] { if (detail::is_const_function) return; detail::OverloadEntry entry; if constexpr (detail::is_any_cfunction_pointer_v) { entry.arity = -1; entry.checker = nullptr; } else if constexpr (detail::is_proxy_member_function_v) { using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; } else { using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; } overload_set_nonconst->entries.push_back(entry); } (), ...); LUABRIDGE_ASSERT(!overload_set_nonconst->entries.empty()); // upvalue 2: flat table of function closures lua_createtable(L, static_cast(detail::non_const_functions_count), 0); int idx = 1; ([&] { if (detail::is_const_function) return; detail::push_member_function(L, std::move(functions), name); lua_rawseti(L, -2, idx++); } (), ...); lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); rawsetfield(L, -3, name); // Stack: co, cl, st } } return *this; } #if LUABRIDGE_HAS_CXX20_COROUTINES //========================================================================================= /** * @brief Add a C++20 coroutine as a static function to this class. * * The factory must be a callable returning CppCoroutine. When Lua calls the registered * function, a new C++ coroutine is created and run; co_yield sends values to the Lua caller * and co_return sends the final return value. * * @param name The function name to register. * @param factory A callable returning CppCoroutine. * * @returns This class registration object. */ template auto addStaticCoroutine(const char* name, F factory) -> std::enable_if_t, Class&> { LUABRIDGE_ASSERT(name != nullptr); assertStackState(); // Stack: const table (co), class table (cl), static table (st) detail::push_coroutine_function(L, std::move(factory), name); rawsetfield(L, -2, name); // Stack: co, cl, st (into st) return *this; } //========================================================================================= /** * @brief Add a C++20 coroutine as a member function to this class. * * The factory must be a callable whose first argument is T* or const T*, followed by any * additional arguments, and returning CppCoroutine. When Lua calls the method on an * object, a new C++ coroutine is created with the object as the first argument; co_yield * sends values to the Lua caller and co_return sends the final return value. * * If the factory takes const T* as the first argument it is registered as a const method * (accessible on both const and non-const objects); otherwise it is registered as a * non-const method (accessible on non-const objects only). * * @param name The method name to register. * @param factory A callable taking T* or const T* as the first argument (the object * pointer), plus optional further arguments, returning CppCoroutine. * * @returns This class registration object. */ template auto addCoroutine(const char* name, F factory) -> std::enable_if_t< detail::is_cpp_coroutine_factory_v && detail::is_proxy_member_function_v, Class&> { LUABRIDGE_ASSERT(name != nullptr); assertStackState(); // Stack: const table (co), class table (cl), static table (st) detail::push_coroutine_function(L, std::move(factory), name); if constexpr (detail::is_const_function) { lua_pushvalue(L, -1); // Stack: co, cl, st, func, func rawsetfield(L, -4, name); // Stack: co, cl, st, func (sets in cl) rawsetfield(L, -4, name); // Stack: co, cl, st (sets in co) } else { rawsetfield(L, -3, name); // Stack: co, cl, st (sets in cl) } return *this; } #endif // LUABRIDGE_HAS_CXX20_COROUTINES //========================================================================================= /** * @brief Add or replace a primary Constructor. * * The primary Constructor is invoked when calling the class type table like a function. * * The template parameter should be a function pointer type that matches the desired Constructor (since you can't take the * address of a Constructor and pass it as an argument). */ template auto addConstructor() -> std::enable_if_t<(sizeof...(Functions) > 0), Class&> { assertStackState(); // Stack: const table (co), class table (cl), static table (st) if constexpr (sizeof...(Functions) == 1) { ([&] { lua_pushcclosure_x(L, &detail::constructor_placement_proxy>, className, 0); } (), ...); } else { // upvalue 1: OverloadSet auto* overload_set_unaligned = lua_newuserdata_aligned(L); auto* overload_set = align(overload_set_unaligned); ([&] { using ArgsPack = detail::function_arguments_t; detail::OverloadEntry entry; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; overload_set->entries.push_back(entry); } (), ...); // upvalue 2: flat table of function closures lua_createtable(L, static_cast(sizeof...(Functions)), 0); int idx = 1; ([&] { lua_pushcclosure_x(L, &detail::constructor_placement_proxy>, className, 0); lua_rawseti(L, -2, idx++); } (), ...); lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); } rawsetfield(L, -2, "__call"); return *this; } //========================================================================================= /** * @brief Add or replace a placement constructor. * * The primary placement constructor is invoked when calling the class type table like a function. * * The provider of the Function argument is responsible of doing placement new of the type T over the void* pointer provided to * the method as first argument. */ template auto addConstructor(Functions... functions) -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> { static_assert(((detail::function_arity_excluding_v >= 1) && ...)); static_assert(((std::is_same_v, void*>) && ...)); assertStackState(); // Stack: const table (co), class table (cl), static table (st) if constexpr (sizeof...(Functions) == 1) { ([&] { using F = detail::constructor_forwarder; lua_newuserdata_aligned(L, F(std::move(functions))); // Stack: co, cl, st, upvalue lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); // Stack: co, cl, st, function } (), ...); } else { // upvalue 1: OverloadSet auto* overload_set_unaligned = lua_newuserdata_aligned(L); auto* overload_set = align(overload_set_unaligned); ([&] { detail::OverloadEntry entry; if constexpr (detail::is_any_cfunction_pointer_v) { entry.arity = -1; entry.checker = nullptr; } else { // skip void* first arg (placement new destination, not a Lua argument) using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::function_arity_excluding_v) - 1; entry.checker = &detail::overload_type_checker; } overload_set->entries.push_back(entry); } (), ...); // upvalue 2: flat table of function closures lua_createtable(L, static_cast(sizeof...(Functions)), 0); int idx = 1; ([&] { using F = detail::constructor_forwarder; lua_newuserdata_aligned(L, F(std::move(functions))); lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); lua_rawseti(L, -2, idx++); } (), ...); lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); } rawsetfield(L, -2, "__call"); // Stack: co, cl, st return *this; } //========================================================================================= /** * @brief Add or replace a primary Constructor when the type is used from an intrusive container C. */ template auto addConstructorFrom() -> std::enable_if_t<(sizeof...(Functions) > 0), Class&> { assertStackState(); // Stack: const table (co), class table (cl), static table (st) if constexpr (sizeof...(Functions) == 1) { ([&] { lua_pushcclosure_x(L, &detail::constructor_container_proxy>, className, 0); } (), ...); } else { // upvalue 1: OverloadSet auto* overload_set_unaligned = lua_newuserdata_aligned(L); auto* overload_set = align(overload_set_unaligned); ([&] { using ArgsPack = detail::function_arguments_t; detail::OverloadEntry entry; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; overload_set->entries.push_back(entry); } (), ...); // upvalue 2: flat table of function closures lua_createtable(L, static_cast(sizeof...(Functions)), 0); int idx = 1; ([&] { lua_pushcclosure_x(L, &detail::constructor_container_proxy>, className, 0); lua_rawseti(L, -2, idx++); } (), ...); lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); } rawsetfield(L, -2, "__call"); return *this; } //========================================================================================= /** * @brief Add or replace a primary Constructor when the type is used from an intrusive container C. * * The provider of the Function argument is responsible of constructing the container C forwarding arguments in the callable Functions passed in. */ template auto addConstructorFrom(Functions... functions) -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> { static_assert(((std::is_same_v, C>) && ...)); assertStackState(); // Stack: const table (co), class table (cl), static table (st) if constexpr (sizeof...(Functions) == 1) { ([&] { using F = detail::container_forwarder; lua_newuserdata_aligned(L, F(std::move(functions))); // Stack: co, cl, st, upvalue lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); // Stack: co, cl, st, function } (), ...); } else { // upvalue 1: OverloadSet auto* overload_set_unaligned = lua_newuserdata_aligned(L); auto* overload_set = align(overload_set_unaligned); ([&] { detail::OverloadEntry entry; if constexpr (detail::is_any_cfunction_pointer_v) { entry.arity = -1; entry.checker = nullptr; } else { using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; } overload_set->entries.push_back(entry); } (), ...); // upvalue 2: flat table of function closures lua_createtable(L, static_cast(sizeof...(Functions)), 0); int idx = 1; ([&] { using F = detail::container_forwarder; lua_newuserdata_aligned(L, F(std::move(functions))); lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); lua_rawseti(L, -2, idx++); } (), ...); lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); } rawsetfield(L, -2, "__call"); // Stack: co, cl, st return *this; } //========================================================================================= template auto addDestructor(Function function) -> std::enable_if_t, Class&> { static_assert(detail::function_arity_excluding_v == 1); static_assert(std::is_same_v, T*>); assertStackState(); // Stack: const table (co), class table (cl), static table (st) using F = detail::destructor_forwarder; lua_newuserdata_aligned(L, F(std::move(function))); // Stack: co, cl, st, upvalue lua_pushcclosure_x(L, &detail::invoke_proxy_destructor, className, 1); // Stack: co, cl, st, function rawsetfield(L, -3, "__destruct"); // Stack: co, cl, st return *this; } //========================================================================================= /** * @brief Add or replace a factory. * * The primary Constructor is invoked when calling the class type table like a function. * * The template parameter should be a function pointer type that matches the desired Constructor (since you can't take the * address of a Constructor and pass it as an argument). */ template Class& addFactory(Allocator allocator, Deallocator deallocator) { assertStackState(); // Stack: const table (co), class table (cl), static table (st) using F = detail::factory_forwarder; lua_newuserdata_aligned(L, F(std::move(allocator), std::move(deallocator))); // Stack: co, cl, st, upvalue lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); // Stack: co, cl, st, function rawsetfield(L, -2, "__call"); // Stack: co, cl, st return *this; } //========================================================================================= /** * @brief Add an index metamethod function fallback that is triggered when no result is found in functions, properties or any other members. * * Let the user define a fallback index (__index) metamethod at its level. */ template auto addIndexMetaMethod(Function function) -> std::enable_if_t && std::is_invocable_v, Class&> { using FnType = decltype(function); assertStackState(); // Stack: const table (co), class table (cl), static table (st) lua_newuserdata_aligned(L, std::move(function)); // Stack: co, cl, st, function userdata (ud) lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__index", 1); // Stack: co, cl, st, function lua_rawsetp_x(L, -3, detail::getIndexFallbackKey()); setObjectMetaMethods(-2, false); return *this; } Class& addIndexMetaMethod(LuaRef (*idxf)(T&, const LuaRef&, lua_State*)) { using FnType = decltype(idxf); assertStackState(); // Stack: const table (co), class table (cl), static table (st) lua_pushlightuserdata(L, reinterpret_cast(idxf)); // Stack: co, cl, st, function ptr lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__index", 1); // Stack: co, cl, st, function lua_rawsetp_x(L, -3, detail::getIndexFallbackKey()); setObjectMetaMethods(-2, false); return *this; } Class& addIndexMetaMethod(LuaRef (T::* idxf)(const LuaRef&, lua_State*)) { using MemFnPtr = decltype(idxf); assertStackState(); // Stack: const table (co), class table (cl), static table (st) new (lua_newuserdata_x(L, sizeof(MemFnPtr))) MemFnPtr(idxf); lua_pushcclosure_x(L, &detail::invoke_member_function, "__index", 1); lua_rawsetp_x(L, -3, detail::getIndexFallbackKey()); setObjectMetaMethods(-2, false); return *this; } //========================================================================================= /** * @brief Add an insert index metamethod function fallback that is triggered when no result is found in functions, properties or any other members. * * Let the user define a fallback insert index (___newindex) metamethod at its level. */ template auto addNewIndexMetaMethod(Function function) -> std::enable_if_t && std::is_invocable_v, Class&> { using FnType = decltype(function); assertStackState(); // Stack: const table (co), class table (cl), static table (st) lua_newuserdata_aligned(L, std::move(function)); // Stack: co, cl, st, function userdata (ud) lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__newindex", 1); // Stack: co, cl, st, function lua_rawsetp_x(L, -3, detail::getNewIndexFallbackKey()); setObjectMetaMethods(-2, false); return *this; } Class& addNewIndexMetaMethod(LuaRef (*idxf)(T&, const LuaRef&, const LuaRef&, lua_State*)) { using FnType = decltype(idxf); assertStackState(); // Stack: const table (co), class table (cl), static table (st) lua_pushlightuserdata(L, reinterpret_cast(idxf)); // Stack: co, cl, st, function ptr lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__newindex", 1); // Stack: co, cl, st, function lua_rawsetp_x(L, -3, detail::getNewIndexFallbackKey()); setObjectMetaMethods(-2, false); return *this; } Class& addNewIndexMetaMethod(LuaRef (T::* idxf)(const LuaRef&, const LuaRef&, lua_State*)) { using MemFnPtr = decltype(idxf); assertStackState(); // Stack: const table (co), class table (cl), static table (st) new (lua_newuserdata_x(L, sizeof(MemFnPtr))) MemFnPtr(idxf); lua_pushcclosure_x(L, &detail::invoke_member_function, "__newindex", 1); lua_rawsetp_x(L, -3, detail::getNewIndexFallbackKey()); setObjectMetaMethods(-2, false); return *this; } //========================================================================================= /** * @brief Register a custom type converter from this class (T) to a target type (To). * * Stores a function pointer in the FROM class's (T's) Lua metatable under * getConvertersKey() → getClassRegistryKey(), for both the class table and * the const table. Phase 3 in Stack::get reads it back during extraction. * * Requirements: * - StackConversion::enabled must be true (user specializes to opt in). * - StackConverter must provide: static To convert(const T&). * * @tparam To Target type. Stack and Stack gain Phase 3 fallback. */ template Class& addConverter() { static_assert(StackConversion::enabled, "Specialize StackConversion with enabled=true before calling addConverter()"); using FnType = TypeResult(*)(lua_State*, int); static const FnType fn = &detail::convertFromStack; // Stack during Class methods: ns, co, cl, st // Store into both cl (-2) and co (-3) so both mutable and const userdatas work. detail::getOrCreateConverterRegistry(L, lua_absindex(L, -2))->converters[detail::getClassRegistryKey()] = &fn; // cl detail::getOrCreateConverterRegistry(L, lua_absindex(L, -3))->converters[detail::getClassRegistryKey()] = &fn; // co return *this; } }; class Table : public detail::Registrar { public: explicit Table(const char* name, Namespace parent) : Registrar(std::move(parent)) { lua_newtable(L); // Stack: ns, table (tb) lua_pushvalue(L, -1); // Stack: ns, tb, tb rawsetfield(L, -3, name); ++m_stackSize; lua_newtable(L); // Stack: ns, table (tb) lua_pushvalue(L, -1); // Stack: ns, tb, tb lua_setmetatable(L, -3); // tb.__metatable = tb. Stack: ns, tb ++m_stackSize; } using Registrar::operator=; template Table& addFunction(const char* name, Function function) { using FnType = decltype(function); LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) lua_newuserdata_aligned(L, std::move(function)); // Stack: ns, function userdata (ud) lua_pushcclosure_x(L, &detail::invoke_proxy_functor, name, 1); // Stack: ns, function rawsetfield(L, -3, name); // Stack: ns return *this; } template Table& addMetaFunction(const char* name, Function function) { using FnType = decltype(function); LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) lua_newuserdata_aligned(L, std::move(function)); // Stack: ns, function userdata (ud) lua_pushcclosure_x(L, &detail::invoke_proxy_functor, name, 1); // Stack: ns, function rawsetfield(L, -2, name); // Stack: ns return *this; } Namespace endTable() { LUABRIDGE_ASSERT(m_stackSize > 2); m_stackSize -= 2; lua_pop(L, 2); return Namespace(std::move(*this)); } }; private: struct FromStack {}; //============================================================================================= /** * @brief Open the global namespace for registrations. * * @param L A Lua state. */ explicit Namespace(lua_State* L) : Registrar(L) { lua_getglobal(L, "_G"); ++m_stackSize; } //============================================================================================= /** * @brief Open the a namespace for registrations from a table on top of the stack. * * @param L A Lua state. */ Namespace(lua_State* L, Options options, FromStack) : Registrar(L, 1) { LUABRIDGE_ASSERT(lua_istable(L, -1)); lua_pushvalue(L, -1); // Stack: ns, ns // ns.__metatable = ns lua_setmetatable(L, -2); // Stack: ns // ns.__index = index_static_metamethod lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); rawsetfield(L, -2, "__index"); // Stack: ns // ns.__newindex = newindex_static_metamethod //lua_pushcfunction_x(L, &detail::newindex_metamethod, "__newindex"); //rawsetfield(L, -2, "__newindex"); // Stack: pns, ns lua_newtable(L); // Stack: ns, mt, propget table (pg) lua_rawsetp_x(L, -2, detail::getPropgetKey()); // ns [propgetKey] = pg. Stack: ns lua_newtable(L); // Stack: ns, mt, propset table (ps) lua_rawsetp_x(L, -2, detail::getPropsetKey()); // ns [propsetKey] = ps. Stack: ns if (! options.test(visibleMetatables)) { lua_pushboolean(L, 0); rawsetfield(L, -2, "__metatable"); } ++m_stackSize; } //============================================================================================= /** * @brief Open a namespace for registrations. * * The namespace is created if it doesn't already exist. * * @param name The namespace name. * @param parent The parent namespace object. * * @pre The parent namespace is at the top of the Lua stack. */ Namespace(const char* name, Namespace parent, Options options) : Registrar(std::move(parent)) { LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: parent namespace (pns) rawgetfield(L, -1, name); // Stack: pns, namespace (ns) | nil if (lua_isnil(L, -1)) // Stack: pns, nil { lua_pop(L, 1); // Stack: pns lua_newtable(L); // Stack: pns, ns lua_pushvalue(L, -1); // Stack: pns, ns, mt // ns.__metatable = ns lua_setmetatable(L, -2); // Stack: pns, ns // ns.__index = index_static_metamethod lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); rawsetfield(L, -2, "__index"); // Stack: pns, ns // ns.__newindex = newindex_static_metamethod lua_pushcfunction_x(L, &detail::newindex_metamethod, "__newindex"); rawsetfield(L, -2, "__newindex"); // Stack: pns, ns lua_newtable(L); // Stack: pns, ns, propget table (pg) lua_rawsetp_x(L, -2, detail::getPropgetKey()); // ns [propgetKey] = pg. Stack: pns, ns lua_newtable(L); // Stack: pns, ns, propset table (ps) lua_rawsetp_x(L, -2, detail::getPropsetKey()); // ns [propsetKey] = ps. Stack: pns, ns if (! options.test(visibleMetatables)) { lua_pushboolean(L, 0); rawsetfield(L, -2, "__metatable"); } // pns [name] = ns lua_pushvalue(L, -1); // Stack: pns, ns, ns rawsetfield(L, -3, name); // Stack: pns, ns } ++m_stackSize; } //============================================================================================= /** * @brief Close the class and continue the namespace registrations. * * @param child A child class registration object. */ explicit Namespace(ClassBase child) : Registrar(std::move(child)) { } explicit Namespace(Table child) : Registrar(std::move(child)) { } using Registrar::operator=; public: //============================================================================================= /** * @brief Retrieve the global namespace. * * It is recommended to put your namespace inside the global namespace, and then add your classes and functions to it, rather than * adding many classes and functions directly to the global namespace. * * @param L A Lua state. * * @returns A namespace registration object. */ static Namespace getGlobalNamespace(lua_State* L) { return Namespace(L); } /** * @brief Retrieve the namespace on top of the stack. * * You should have a table on top of the stack before calling this function. It will then use the table there as destination for registrations. * * @param L A Lua state. * * @returns A namespace registration object. */ static Namespace getNamespaceFromStack(lua_State* L, Options options = defaultOptions) { return Namespace(L, options, FromStack{}); } //============================================================================================= /** * @brief Open a new or existing namespace for registrations. * * @param name The namespace name. * * @returns A namespace registration object. */ Namespace beginNamespace(const char* name, Options options = defaultOptions) { assertIsActive(); return Namespace(name, std::move(*this), options); } //============================================================================================= /** * @brief Continue namespace registration in the parent. * * Do not use this on the global namespace. * * @returns A parent namespace registration object. */ Namespace endNamespace() { if (m_stackSize == 1) { throw_or_assert("endNamespace() called on global namespace"); return Namespace(std::move(*this)); } LUABRIDGE_ASSERT(m_stackSize > 1); --m_stackSize; lua_pop(L, 1); return Namespace(std::move(*this)); } //============================================================================================= /** * @brief Add or replace a variable that will be added in the namespace by copy of the passed value. * * @param name The variable name. * @param value A value object. * * @returns This namespace registration object. */ template Namespace& addVariable(const char* name, const T& value) { LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) if constexpr (std::is_enum_v) { using U = std::underlying_type_t; if (auto result = Stack::push(L, static_cast(value)); ! result) throw_or_assert(result.message().c_str()); } else { if (auto result = Stack::push(L, value); ! result) throw_or_assert(result.message().c_str()); } rawsetfield(L, -2, name); // Stack: ns return *this; } //============================================================================================= /** * @brief Add or replace a readonly property. * * @param name The property name. * @param get A pointer to a property getter function. * * @returns This namespace registration object. */ template Namespace& addProperty(const char* name, Getter getter) { LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) if (! checkTableHasPropertyGetter()) { throw_or_assert("addProperty() called on global namespace"); return *this; } detail::push_property_getter(L, std::move(getter), name); // Stack: ns, getter detail::add_property_getter(L, name, -2); // Stack: ns, getter detail::push_property_readonly(L, name); // Stack: ns, function detail::add_property_setter(L, name, -2); // Stack: ns return *this; } /** * @brief Add or replace a mutable property. * * @param name The property name. * @param get A pointer to a property getter function. * @param set A pointer to a property setter function. * * @returns This namespace registration object. */ template Namespace& addProperty(const char* name, Getter getter, Setter setter) { LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) if (! checkTableHasPropertyGetter()) { throw_or_assert("addProperty() called on global namespace"); return *this; } detail::push_property_getter(L, std::move(getter), name); // Stack: ns, getter detail::add_property_getter(L, name, -2); // Stack: ns detail::push_property_setter(L, std::move(setter), name); // Stack: ns, setter detail::add_property_setter(L, name, -2); // Stack: ns return *this; } //============================================================================================= /** * @brief Add or replace a single function or multiple overloaded functions. * * @param name The overload name. * @param functions A single or set of functions that will be invoked. * * @returns This namespace registration object. */ template auto addFunction(const char* name, Functions... functions) -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Namespace&> { LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) if constexpr (sizeof...(Functions) == 1) { ([&] { detail::push_function(L, std::move(functions), name); } (), ...); } else { // upvalue 1: OverloadSet (C++ struct with arity + type checker per overload) auto* overload_set_unaligned = lua_newuserdata_aligned(L); auto* overload_set = align(overload_set_unaligned); ([&] { detail::OverloadEntry entry; if constexpr (detail::is_any_cfunction_pointer_v) { entry.arity = -1; entry.checker = nullptr; } else { using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; } overload_set->entries.push_back(entry); } (), ...); // upvalue 2: flat table of function closures indexed 1..N lua_createtable(L, static_cast(sizeof...(Functions)), 0); int idx = 1; ([&] { detail::push_function(L, std::move(functions), name); lua_rawseti(L, -2, idx++); } (), ...); lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); } rawsetfield(L, -2, name); return *this; } #if LUABRIDGE_HAS_CXX20_COROUTINES //============================================================================================= /** * @brief Add a C++20 coroutine function to this namespace. * * The factory must be a callable returning CppCoroutine. When Lua calls the registered * function, a new C++ coroutine is created and run; co_yield sends values to the Lua caller * and co_return sends the final return value. * * @param name The function name to register. * @param factory A callable returning CppCoroutine. * * @returns This namespace registration object. */ template auto addCoroutine(const char* name, F factory) -> std::enable_if_t, Namespace&> { LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) detail::push_coroutine_function(L, std::move(factory), name); rawsetfield(L, -2, name); return *this; } #endif // LUABRIDGE_HAS_CXX20_COROUTINES //============================================================================================= /** * @brief Open a new or existing table as namespace for registrations. * * @param name The table name. * * @returns A table registration object. */ Table beginTable(const char* name) { assertIsActive(); return Table(name, std::move(*this)); } //============================================================================================= /** * @brief Open a new or existing class for registrations. * * @param name The class name. * @param options The class options. * * @returns A class registration object. */ template Class beginClass(const char* name, Options options = defaultOptions) { assertIsActive(); return Class(name, std::move(*this), options); } //============================================================================================= /** * @brief Derive a new class for registrations. * * Call deriveClass() only once. To continue registrations for the class later, use beginClass(). * * @param name The class name. * @param options The class options. * * @returns A class registration object. */ template Class deriveClass(const char* name, Options options = defaultOptions) { static_assert(std::is_base_of_v, "Derived must inherit from Base1"); static_assert((std::is_base_of_v && ...), "Derived must inherit from all specified base classes"); assertIsActive(); return Class(name, std::move(*this), { detail::BaseClassInfo{ detail::getStaticRegistryKey(), detail::getClassRegistryKey(), detail::computeCastOffset() }, detail::BaseClassInfo{ detail::getStaticRegistryKey(), detail::getClassRegistryKey(), detail::computeCastOffset() }... }, options); } private: /** * @brief Checks if the table has a property getter metatable. */ bool checkTableHasPropertyGetter() const { if (m_stackSize == 1 && lua_istable(L, -1)) { lua_rawgetp_x(L, -1, detail::getPropgetKey()); const bool propertyGetterTableIsValid = lua_istable(L, -1); lua_pop(L, 1); return propertyGetterTableIsValid; } return true; } }; //================================================================================================= /** * @brief Retrieve the global namespace. * * It is recommended to put your namespace inside the global namespace, and then add your classes and functions to it, rather than adding * many classes and functions directly to the global namespace. * * @param L A Lua state. * * @returns A namespace registration object. */ inline Namespace getGlobalNamespace(lua_State* L) { return Namespace::getGlobalNamespace(L); } //================================================================================================= /** * @brief Retrieve the namespace on top of the stack. * * You should have a table on top of the stack before calling this function. It will then use the table there as destination for registrations. * * @param L A Lua state. * * @returns A namespace registration object. */ inline Namespace getNamespaceFromStack(lua_State* L) { return Namespace::getNamespaceFromStack(L); } //================================================================================================= /** * @brief Registers main thread. * * This is a backward compatibility mitigation for lua 5.1 not supporting LUA_RIDX_MAINTHREAD. * * @param L The main Lua state that will be registered as main thread. * * @returns A namespace registration object. */ inline void registerMainThread(lua_State* L) { register_main_thread(L); } } // namespace luabridge