diff options
| author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-12-09 17:39:30 +0100 |
|---|---|---|
| committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-12-09 17:39:30 +0100 |
| commit | 5f9ef9e1b75adc27a4ae4129cc33137aa7aaa565 (patch) | |
| tree | 2500bc537a045a66ae68288f01bff569decdd80c /src | |
| parent | a334c7662a1a7be5b79efe72076c49ab7c714070 (diff) | |
| download | lanes-5f9ef9e1b75adc27a4ae4129cc33137aa7aaa565.tar.gz lanes-5f9ef9e1b75adc27a4ae4129cc33137aa7aaa565.tar.bz2 lanes-5f9ef9e1b75adc27a4ae4129cc33137aa7aaa565.zip | |
Improved DeepPrelude architecture
Diffstat (limited to 'src')
| -rw-r--r-- | src/deep.cpp | 66 | ||||
| -rw-r--r-- | src/deep.hpp | 11 | ||||
| -rw-r--r-- | src/lanes.cpp | 1 | ||||
| -rw-r--r-- | src/linda.cpp | 34 | ||||
| -rw-r--r-- | src/linda.hpp | 6 | ||||
| -rw-r--r-- | src/macros_and_utils.hpp | 13 | ||||
| -rw-r--r-- | src/universe.cpp | 25 | ||||
| -rw-r--r-- | src/universe.hpp | 11 |
8 files changed, 108 insertions, 59 deletions
diff --git a/src/deep.cpp b/src/deep.cpp index 6f4da9f..7b287f5 100644 --- a/src/deep.cpp +++ b/src/deep.cpp | |||
| @@ -62,37 +62,6 @@ namespace { | |||
| 62 | // ############################################################################################# | 62 | // ############################################################################################# |
| 63 | // ############################################################################################# | 63 | // ############################################################################################# |
| 64 | 64 | ||
| 65 | /* | ||
| 66 | * void= mt.__gc( proxy_ud ) | ||
| 67 | * | ||
| 68 | * End of life for a proxy object; reduce the deep reference count and clean it up if reaches 0. | ||
| 69 | * | ||
| 70 | */ | ||
| 71 | [[nodiscard]] | ||
| 72 | static int DeepGC(lua_State* const L_) | ||
| 73 | { | ||
| 74 | DeepPrelude* const* const _proxy{ luaG_tofulluserdata<DeepPrelude*>(L_, StackIndex{ 1 }) }; | ||
| 75 | DeepPrelude* const _p{ *_proxy }; | ||
| 76 | |||
| 77 | // can work without a universe if creating a deep userdata from some external C module when Lanes isn't loaded | ||
| 78 | // in that case, we are not multithreaded and locking isn't necessary anyway | ||
| 79 | bool const _isLastRef{ _p->refcount.fetch_sub(1, std::memory_order_relaxed) == 1 }; | ||
| 80 | |||
| 81 | if (_isLastRef) { | ||
| 82 | // retrieve wrapped __gc, if any | ||
| 83 | lua_pushvalue(L_, lua_upvalueindex(1)); // L_: self __gc? | ||
| 84 | if (!lua_isnil(L_, -1)) { | ||
| 85 | lua_insert(L_, -2); // L_: __gc self | ||
| 86 | lua_call(L_, 1, 0); // L_: | ||
| 87 | } else { | ||
| 88 | // need an empty stack in case we are GC_ing from a Keeper, so that empty stack checks aren't triggered | ||
| 89 | lua_pop(L_, 2); // L_: | ||
| 90 | } | ||
| 91 | DeepFactory::DeleteDeepObject(L_, _p); | ||
| 92 | } | ||
| 93 | return 0; | ||
| 94 | } | ||
| 95 | |||
| 96 | // ############################################################################################# | 65 | // ############################################################################################# |
| 97 | 66 | ||
| 98 | // Pops the key (metatable or factory) off the stack, and replaces with the deep lookup value (factory/metatable/nil). | 67 | // Pops the key (metatable or factory) off the stack, and replaces with the deep lookup value (factory/metatable/nil). |
| @@ -121,6 +90,39 @@ namespace { | |||
| 121 | // ################################################################################################# | 90 | // ################################################################################################# |
| 122 | // ################################################################################################# | 91 | // ################################################################################################# |
| 123 | 92 | ||
| 93 | /* | ||
| 94 | * void= mt.__gc( proxy_ud ) | ||
| 95 | * | ||
| 96 | * End of life for a proxy object; reduce the deep reference count and clean it up if reaches 0. | ||
| 97 | * | ||
| 98 | */ | ||
| 99 | [[nodiscard]] | ||
| 100 | int DeepFactory::DeepGC(lua_State* const L_) | ||
| 101 | { | ||
| 102 | DeepPrelude* const* const _proxy{ luaG_tofulluserdata<DeepPrelude*>(L_, StackIndex{ 1 }) }; | ||
| 103 | DeepPrelude* const _p{ *_proxy }; | ||
| 104 | |||
| 105 | // can work without a universe if creating a deep userdata from some external C module when Lanes isn't loaded | ||
| 106 | // in that case, we are not multithreaded and locking isn't necessary anyway | ||
| 107 | bool const _isLastRef{ _p->refcount.fetch_sub(1, std::memory_order_relaxed) == 1 }; | ||
| 108 | |||
| 109 | if (_isLastRef) { | ||
| 110 | // retrieve wrapped __gc, if any | ||
| 111 | lua_pushvalue(L_, lua_upvalueindex(1)); // L_: self __gc? | ||
| 112 | if (!lua_isnil(L_, -1)) { | ||
| 113 | lua_insert(L_, -2); // L_: __gc self | ||
| 114 | lua_call(L_, 1, 0); // L_: | ||
| 115 | } else { | ||
| 116 | // need an empty stack in case we are GC_ing from a Keeper, so that empty stack checks aren't triggered | ||
| 117 | lua_pop(L_, 2); // L_: | ||
| 118 | } | ||
| 119 | DeepFactory::DeleteDeepObject(L_, _p); | ||
| 120 | } | ||
| 121 | return 0; | ||
| 122 | } | ||
| 123 | |||
| 124 | // ################################################################################################# | ||
| 125 | |||
| 124 | // NEVER call deleteDeepObjectInternal by itself, ALWAYS go through DeleteDeepObject() | 126 | // NEVER call deleteDeepObjectInternal by itself, ALWAYS go through DeleteDeepObject() |
| 125 | void DeepFactory::DeleteDeepObject(lua_State* const L_, DeepPrelude* const o_) | 127 | void DeepFactory::DeleteDeepObject(lua_State* const L_, DeepPrelude* const o_) |
| 126 | { | 128 | { |
| @@ -381,7 +383,7 @@ DeepPrelude* DeepFactory::toDeep(lua_State* const L_, StackIndex const index_) c | |||
| 381 | 383 | ||
| 382 | // ################################################################################################# | 384 | // ################################################################################################# |
| 383 | 385 | ||
| 384 | void DeepPrelude::push(lua_State* L_) const | 386 | void DeepPrelude::push(lua_State* const L_) const |
| 385 | { | 387 | { |
| 386 | STACK_CHECK_START_REL(L_, 0); | 388 | STACK_CHECK_START_REL(L_, 0); |
| 387 | kDeepProxyCacheRegKey.getSubTableMode(L_, "v"); // L_: DPC | 389 | kDeepProxyCacheRegKey.getSubTableMode(L_, "v"); // L_: DPC |
diff --git a/src/deep.hpp b/src/deep.hpp index 6fa12b1..e4bdaca 100644 --- a/src/deep.hpp +++ b/src/deep.hpp | |||
| @@ -20,19 +20,26 @@ static constexpr UniqueKey kDeepVersion{ 0x91171AEC6641E9DBull, "kDeepVersion" } | |||
| 20 | 20 | ||
| 21 | // should be used as header for deep userdata | 21 | // should be used as header for deep userdata |
| 22 | // a deep userdata is a full userdata that stores a single pointer to the actual DeepPrelude-derived object | 22 | // a deep userdata is a full userdata that stores a single pointer to the actual DeepPrelude-derived object |
| 23 | struct DeepPrelude | 23 | class DeepPrelude |
| 24 | { | 24 | { |
| 25 | friend class DeepFactory; | ||
| 26 | |||
| 27 | private: | ||
| 25 | UniqueKey const magic{ kDeepVersion }; | 28 | UniqueKey const magic{ kDeepVersion }; |
| 26 | // when stored in a keeper state, the full userdata doesn't have a metatable, so we need direct access to the factory | 29 | // when stored in a keeper state, the full userdata doesn't have a metatable, so we need direct access to the factory |
| 27 | DeepFactory& factory; | 30 | DeepFactory& factory; |
| 31 | |||
| 32 | protected: | ||
| 28 | // data is destroyed when refcount is 0 | 33 | // data is destroyed when refcount is 0 |
| 29 | std::atomic<int> refcount{ 0 }; | 34 | std::atomic<int> refcount{ 0 }; |
| 30 | 35 | ||
| 36 | protected: | ||
| 31 | DeepPrelude(DeepFactory& factory_) | 37 | DeepPrelude(DeepFactory& factory_) |
| 32 | : factory{ factory_ } | 38 | : factory{ factory_ } |
| 33 | { | 39 | { |
| 34 | } | 40 | } |
| 35 | 41 | ||
| 42 | public: | ||
| 36 | void push(lua_State* L_) const; | 43 | void push(lua_State* L_) const; |
| 37 | }; | 44 | }; |
| 38 | 45 | ||
| @@ -63,6 +70,8 @@ class DeepFactory | |||
| 63 | virtual std::string_view moduleName() const = 0; | 70 | virtual std::string_view moduleName() const = 0; |
| 64 | 71 | ||
| 65 | private: | 72 | private: |
| 73 | [[nodiscard]] | ||
| 74 | static int DeepGC(lua_State* L_); | ||
| 66 | void storeDeepLookup(lua_State* L_) const; | 75 | void storeDeepLookup(lua_State* L_) const; |
| 67 | 76 | ||
| 68 | public: | 77 | public: |
diff --git a/src/lanes.cpp b/src/lanes.cpp index 0b29a8f..83d01fb 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp | |||
| @@ -86,6 +86,7 @@ THE SOFTWARE. | |||
| 86 | #include "intercopycontext.hpp" | 86 | #include "intercopycontext.hpp" |
| 87 | #include "keeper.hpp" | 87 | #include "keeper.hpp" |
| 88 | #include "lane.hpp" | 88 | #include "lane.hpp" |
| 89 | #include "linda.hpp" | ||
| 89 | #include "nameof.hpp" | 90 | #include "nameof.hpp" |
| 90 | #include "state.hpp" | 91 | #include "state.hpp" |
| 91 | #include "threading.hpp" | 92 | #include "threading.hpp" |
diff --git a/src/linda.cpp b/src/linda.cpp index 6ebbc1f..7af387f 100644 --- a/src/linda.cpp +++ b/src/linda.cpp | |||
| @@ -130,6 +130,8 @@ namespace { | |||
| 130 | // ################################################################################################# | 130 | // ################################################################################################# |
| 131 | // ################################################################################################# | 131 | // ################################################################################################# |
| 132 | 132 | ||
| 133 | LUAG_FUNC(linda); | ||
| 134 | |||
| 133 | // ################################################################################################# | 135 | // ################################################################################################# |
| 134 | // ################################################################################################# | 136 | // ################################################################################################# |
| 135 | // #################################### Linda implementation ####################################### | 137 | // #################################### Linda implementation ####################################### |
| @@ -165,6 +167,38 @@ Keeper* Linda::acquireKeeper() const | |||
| 165 | 167 | ||
| 166 | // ################################################################################################# | 168 | // ################################################################################################# |
| 167 | 169 | ||
| 170 | Linda* Linda::CreateTimerLinda(lua_State* const L_) | ||
| 171 | { | ||
| 172 | STACK_CHECK_START_REL(L_, 0); // L_: | ||
| 173 | // Initialize 'timerLinda'; a common Linda object shared by all states | ||
| 174 | lua_pushcfunction(L_, LG_linda); // L_: lanes.linda | ||
| 175 | luaG_pushstring(L_, "lanes-timer"); // L_: lanes.linda "lanes-timer" | ||
| 176 | lua_pushinteger(L_, 0); // L_: lanes.linda "lanes-timer" 0 | ||
| 177 | lua_call(L_, 2, 1); // L_: linda | ||
| 178 | STACK_CHECK(L_, 1); | ||
| 179 | |||
| 180 | // Proxy userdata contents is only a 'DeepPrelude*' pointer | ||
| 181 | auto const _timerLinda{ *luaG_tofulluserdata<Linda*>(L_, kIdxTop) }; | ||
| 182 | // increment refcount so that this linda remains alive as long as the universe exists. | ||
| 183 | _timerLinda->refcount.fetch_add(1, std::memory_order_relaxed); | ||
| 184 | lua_pop(L_, 1); // L_: | ||
| 185 | STACK_CHECK(L_, 0); | ||
| 186 | return _timerLinda; | ||
| 187 | } | ||
| 188 | |||
| 189 | // ################################################################################################# | ||
| 190 | |||
| 191 | void Linda::DeleteTimerLinda(lua_State* const L_, Linda* const linda_) | ||
| 192 | { | ||
| 193 | if (linda_ != nullptr) { // test in case some early internal error prevented Lanes from creating the deep timer | ||
| 194 | [[maybe_unused]] auto const _prev_ref_count{ linda_->refcount.fetch_sub(1, std::memory_order_relaxed) }; | ||
| 195 | LUA_ASSERT(L_, _prev_ref_count == 1); // this should be the last reference | ||
| 196 | DeepFactory::DeleteDeepObject(L_, linda_); | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | // ################################################################################################# | ||
| 201 | |||
| 168 | void Linda::freeAllocatedName() | 202 | void Linda::freeAllocatedName() |
| 169 | { | 203 | { |
| 170 | if (std::holds_alternative<std::string_view>(nameVariant)) { | 204 | if (std::holds_alternative<std::string_view>(nameVariant)) { |
diff --git a/src/linda.hpp b/src/linda.hpp index aa63316..920db1b 100644 --- a/src/linda.hpp +++ b/src/linda.hpp | |||
| @@ -82,6 +82,9 @@ class Linda | |||
| 82 | Linda& operator=(Linda const&&) = delete; | 82 | Linda& operator=(Linda const&&) = delete; |
| 83 | 83 | ||
| 84 | private: | 84 | private: |
| 85 | [[nodiscard]] | ||
| 86 | static Linda* CreateTimerLinda(lua_State* L_); | ||
| 87 | static void DeleteTimerLinda(lua_State* L_, Linda* linda_); | ||
| 85 | void freeAllocatedName(); | 88 | void freeAllocatedName(); |
| 86 | void setName(std::string_view const& name_); | 89 | void setName(std::string_view const& name_); |
| 87 | 90 | ||
| @@ -89,6 +92,9 @@ class Linda | |||
| 89 | [[nodiscard]] | 92 | [[nodiscard]] |
| 90 | Keeper* acquireKeeper() const; | 93 | Keeper* acquireKeeper() const; |
| 91 | [[nodiscard]] | 94 | [[nodiscard]] |
| 95 | static Linda* CreateTimerLinda(lua_State* const L_, Passkey<Universe>) { return CreateTimerLinda(L_); } | ||
| 96 | static void DeleteTimerLinda(lua_State* const L_, Linda* const linda_, Passkey<Universe>) { DeleteTimerLinda(L_, linda_); } | ||
| 97 | [[nodiscard]] | ||
| 92 | std::string_view getName() const; | 98 | std::string_view getName() const; |
| 93 | [[nodiscard]] | 99 | [[nodiscard]] |
| 94 | bool inKeeperOperation() const { return keeperOperationCount.load(std::memory_order_seq_cst) != 0; } | 100 | bool inKeeperOperation() const { return keeperOperationCount.load(std::memory_order_seq_cst) != 0; } |
diff --git a/src/macros_and_utils.hpp b/src/macros_and_utils.hpp index 6850ddf..0897367 100644 --- a/src/macros_and_utils.hpp +++ b/src/macros_and_utils.hpp | |||
| @@ -47,3 +47,16 @@ typename T::value_type const& OptionalValue(T const& x_, Ts... args_) | |||
| 47 | } | 47 | } |
| 48 | return x_.value(); | 48 | return x_.value(); |
| 49 | } | 49 | } |
| 50 | |||
| 51 | // ################################################################################################# | ||
| 52 | |||
| 53 | struct PasskeyToken {}; | ||
| 54 | constexpr PasskeyToken PK{}; | ||
| 55 | template <typename T> | ||
| 56 | class Passkey | ||
| 57 | { | ||
| 58 | private: | ||
| 59 | friend T; | ||
| 60 | Passkey(PasskeyToken) {} | ||
| 61 | // rule of 5 ignored out of laziness here | ||
| 62 | }; | ||
diff --git a/src/universe.cpp b/src/universe.cpp index ec7d043..b7d11d8 100644 --- a/src/universe.cpp +++ b/src/universe.cpp | |||
| @@ -35,6 +35,7 @@ THE SOFTWARE. | |||
| 35 | #include "intercopycontext.hpp" | 35 | #include "intercopycontext.hpp" |
| 36 | #include "keeper.hpp" | 36 | #include "keeper.hpp" |
| 37 | #include "lane.hpp" | 37 | #include "lane.hpp" |
| 38 | #include "linda.hpp" | ||
| 38 | #include "state.hpp" | 39 | #include "state.hpp" |
| 39 | 40 | ||
| 40 | extern LUAG_FUNC(linda); | 41 | extern LUAG_FUNC(linda); |
| @@ -147,7 +148,7 @@ Universe* Universe::Create(lua_State* const L_) | |||
| 147 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); | 148 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); |
| 148 | lua_createtable(L_, 0, 1); // L_: settings universe {mt} | 149 | lua_createtable(L_, 0, 1); // L_: settings universe {mt} |
| 149 | std::ignore = luaG_getfield(L_, kIdxSettings, "shutdown_timeout"); // L_: settings universe {mt} shutdown_timeout | 150 | std::ignore = luaG_getfield(L_, kIdxSettings, "shutdown_timeout"); // L_: settings universe {mt} shutdown_timeout |
| 150 | lua_pushcclosure(L_, LG_universe_gc, 1); // L_: settings universe {mt} LG_universe_gc | 151 | lua_pushcclosure(L_, UniverseGC, 1); // L_: settings universe {mt} UniverseGC |
| 151 | lua_setfield(L_, -2, "__gc"); // L_: settings universe {mt} | 152 | lua_setfield(L_, -2, "__gc"); // L_: settings universe {mt} |
| 152 | lua_setmetatable(L_, -2); // L_: settings universe | 153 | lua_setmetatable(L_, -2); // L_: settings universe |
| 153 | lua_pop(L_, 1); // L_: settings | 154 | lua_pop(L_, 1); // L_: settings |
| @@ -175,18 +176,7 @@ Universe* Universe::Create(lua_State* const L_) | |||
| 175 | STACK_CHECK(L_, 0); | 176 | STACK_CHECK(L_, 0); |
| 176 | 177 | ||
| 177 | // Initialize 'timerLinda'; a common Linda object shared by all states | 178 | // Initialize 'timerLinda'; a common Linda object shared by all states |
| 178 | lua_pushcfunction(L_, LG_linda); // L_: settings lanes.linda | 179 | _U->timerLinda = Linda::CreateTimerLinda(L_, PK); |
| 179 | luaG_pushstring(L_, "lanes-timer"); // L_: settings lanes.linda "lanes-timer" | ||
| 180 | lua_pushinteger(L_, 0); // L_: settings lanes.linda "lanes-timer" 0 | ||
| 181 | lua_call(L_, 2, 1); // L_: settings linda | ||
| 182 | STACK_CHECK(L_, 1); | ||
| 183 | |||
| 184 | // Proxy userdata contents is only a 'DeepPrelude*' pointer | ||
| 185 | _U->timerLinda = *luaG_tofulluserdata<DeepPrelude*>(L_, kIdxTop); | ||
| 186 | // increment refcount so that this linda remains alive as long as the universe exists. | ||
| 187 | _U->timerLinda->refcount.fetch_add(1, std::memory_order_relaxed); | ||
| 188 | lua_pop(L_, 1); // L_: settings | ||
| 189 | STACK_CHECK(L_, 0); | ||
| 190 | return _U; | 180 | return _U; |
| 191 | } | 181 | } |
| 192 | 182 | ||
| @@ -409,7 +399,7 @@ bool Universe::terminateFreeRunningLanes(lua_Duration const shutdownTimeout_, Ca | |||
| 409 | // ################################################################################################# | 399 | // ################################################################################################# |
| 410 | 400 | ||
| 411 | // process end: cancel any still free-running threads | 401 | // process end: cancel any still free-running threads |
| 412 | LUAG_FUNC(universe_gc) | 402 | int Universe::UniverseGC(lua_State* const L_) |
| 413 | { | 403 | { |
| 414 | lua_Duration const _shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) }; | 404 | lua_Duration const _shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) }; |
| 415 | STACK_CHECK_START_ABS(L_, 1); | 405 | STACK_CHECK_START_ABS(L_, 1); |
| @@ -444,12 +434,7 @@ LUAG_FUNC(universe_gc) | |||
| 444 | } | 434 | } |
| 445 | 435 | ||
| 446 | // no need to mutex-protect this as all lanes in the universe are gone at that point | 436 | // no need to mutex-protect this as all lanes in the universe are gone at that point |
| 447 | if (_U->timerLinda != nullptr) { // test in case some early internal error prevented Lanes from creating the deep timer | 437 | Linda::DeleteTimerLinda(L_, std::exchange(_U->timerLinda, nullptr), PK); |
| 448 | [[maybe_unused]] int const _prev_ref_count{ _U->timerLinda->refcount.fetch_sub(1, std::memory_order_relaxed) }; | ||
| 449 | LUA_ASSERT(L_, _prev_ref_count == 1); // this should be the last reference | ||
| 450 | DeepFactory::DeleteDeepObject(L_, _U->timerLinda); | ||
| 451 | _U->timerLinda = nullptr; | ||
| 452 | } | ||
| 453 | 438 | ||
| 454 | _U->keepers.close(); | 439 | _U->keepers.close(); |
| 455 | 440 | ||
diff --git a/src/universe.hpp b/src/universe.hpp index 639dcdb..dbf0ece 100644 --- a/src/universe.hpp +++ b/src/universe.hpp | |||
| @@ -10,8 +10,8 @@ | |||
| 10 | // ################################################################################################# | 10 | // ################################################################################################# |
| 11 | 11 | ||
| 12 | // forwards | 12 | // forwards |
| 13 | struct DeepPrelude; | ||
| 14 | class Lane; | 13 | class Lane; |
| 14 | class Linda; | ||
| 15 | 15 | ||
| 16 | // ################################################################################################# | 16 | // ################################################################################################# |
| 17 | 17 | ||
| @@ -99,7 +99,7 @@ class Universe | |||
| 99 | 99 | ||
| 100 | // Initialized by 'init_once_LOCKED()': the deep userdata Linda object | 100 | // Initialized by 'init_once_LOCKED()': the deep userdata Linda object |
| 101 | // used for timers (each lane will get a proxy to this) | 101 | // used for timers (each lane will get a proxy to this) |
| 102 | DeepPrelude* timerLinda{ nullptr }; | 102 | Linda* timerLinda{ nullptr }; |
| 103 | 103 | ||
| 104 | LaneTracker tracker; | 104 | LaneTracker tracker; |
| 105 | 105 | ||
| @@ -122,6 +122,9 @@ class Universe | |||
| 122 | // The terminal desinit sequence should wait for all such processing to terminate before force-killing threads | 122 | // The terminal desinit sequence should wait for all such processing to terminate before force-killing threads |
| 123 | std::atomic<int> selfdestructingCount{ 0 }; | 123 | std::atomic<int> selfdestructingCount{ 0 }; |
| 124 | 124 | ||
| 125 | private: | ||
| 126 | static int UniverseGC(lua_State* L_); | ||
| 127 | |||
| 125 | public: | 128 | public: |
| 126 | [[nodiscard]] | 129 | [[nodiscard]] |
| 127 | static void* operator new([[maybe_unused]] size_t size_, lua_State* L_) noexcept { return luaG_newuserdatauv<Universe>(L_, UserValueCount{ 0 }); }; | 130 | static void* operator new([[maybe_unused]] size_t size_, lua_State* L_) noexcept { return luaG_newuserdatauv<Universe>(L_, UserValueCount{ 0 }); }; |
| @@ -171,7 +174,3 @@ inline void Universe::Store(lua_State* L_, Universe* U_) | |||
| 171 | kUniverseLightRegKey.setValue(L_, [U = U_](lua_State* L_) { U ? lua_pushlightuserdata(L_, U) : lua_pushnil(L_); }); | 174 | kUniverseLightRegKey.setValue(L_, [U = U_](lua_State* L_) { U ? lua_pushlightuserdata(L_, U) : lua_pushnil(L_); }); |
| 172 | STACK_CHECK(L_, 0); | 175 | STACK_CHECK(L_, 0); |
| 173 | } | 176 | } |
| 174 | |||
| 175 | // ################################################################################################# | ||
| 176 | |||
| 177 | LUAG_FUNC(universe_gc); | ||
