aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lanes.lua14
-rw-r--r--src/linda.cpp152
-rw-r--r--src/linda.hpp10
-rw-r--r--src/lindafactory.cpp9
-rw-r--r--src/universe.cpp8
-rw-r--r--src/universe.hpp2
6 files changed, 118 insertions, 77 deletions
diff --git a/src/lanes.lua b/src/lanes.lua
index bd94a14..3ee959c 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -96,6 +96,7 @@ local default_params =
96 -- it looks also like LuaJIT allocator may not appreciate direct use of its allocator for other purposes than the VM operation 96 -- it looks also like LuaJIT allocator may not appreciate direct use of its allocator for other purposes than the VM operation
97 internal_allocator = isLuaJIT and "libc" or "allocator", 97 internal_allocator = isLuaJIT and "libc" or "allocator",
98 keepers_gc_threshold = -1, 98 keepers_gc_threshold = -1,
99 linda_wake_period = 'never',
99 nb_user_keepers = 0, 100 nb_user_keepers = 0,
100 on_state_create = nil, 101 on_state_create = nil,
101 shutdown_timeout = 0.25, 102 shutdown_timeout = 0.25,
@@ -141,6 +142,19 @@ local param_checkers =
141 end 142 end
142 return true 143 return true
143 end, 144 end,
145 linda_wake_period = function(val_)
146 -- linda_wake_period should be a number > 0, or the string 'never'
147 if val_ == 'never' then
148 return true
149 end
150 if type(val_) ~= "number" then
151 return nil, "not a number"
152 end
153 if val_ <= 0 then
154 return nil, "value out of range"
155 end
156 return true
157 end,
144 nb_user_keepers = function(val_) 158 nb_user_keepers = function(val_)
145 -- nb_user_keepers should be a number in [0,100] (so that nobody tries to run OOM by specifying a huge amount) 159 -- nb_user_keepers should be a number in [0,100] (so that nobody tries to run OOM by specifying a huge amount)
146 if type(val_) ~= "number" then 160 if type(val_) ~= "number" then
diff --git a/src/linda.cpp b/src/linda.cpp
index 0cdacfa..a9ae61c 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -237,11 +237,23 @@ namespace {
237 _lane->waiting_on = &_linda->writeHappened; 237 _lane->waiting_on = &_linda->writeHappened;
238 _lane->status.store(Lane::Waiting, std::memory_order_release); 238 _lane->status.store(Lane::Waiting, std::memory_order_release);
239 } 239 }
240
241 // wait until the final target date by small increments, interrupting regularly so that we can check for cancel requests,
242 // in case some timing issue caused a cancel request to be issued, and the condvar signalled, before we actually wait for it
243 auto const [_forceTryAgain, _until_check_cancel] = std::invoke([_until, wakePeriod = _linda->getWakePeriod()] {
244 auto _until_check_cancel{ std::chrono::time_point<std::chrono::steady_clock>::max() };
245 if (wakePeriod.count() > 0.0f) {
246 _until_check_cancel = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(wakePeriod);
247 }
248 bool const _forceTryAgain{ _until_check_cancel < _until };
249 return std::make_tuple(_forceTryAgain, _forceTryAgain ? _until_check_cancel : _until);
250 });
251
240 // not enough data to read: wakeup when data was sent, or when timeout is reached 252 // not enough data to read: wakeup when data was sent, or when timeout is reached
241 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; 253 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock };
242 std::cv_status const _status{ _linda->writeHappened.wait_until(_guard, _until) }; 254 std::cv_status const _status{ _linda->writeHappened.wait_until(_guard, _until_check_cancel) };
243 _guard.release(); // we don't want to unlock the mutex on exit! 255 _guard.release(); // we don't want to unlock the mutex on exit!
244 _try_again = (_status == std::cv_status::no_timeout); // detect spurious wakeups 256 _try_again = _forceTryAgain || (_status == std::cv_status::no_timeout); // detect spurious wakeups
245 if (_lane != nullptr) { 257 if (_lane != nullptr) {
246 _lane->waiting_on = nullptr; 258 _lane->waiting_on = nullptr;
247 _lane->status.store(_prev_status, std::memory_order_release); 259 _lane->status.store(_prev_status, std::memory_order_release);
@@ -296,9 +308,10 @@ LUAG_FUNC(linda);
296// ################################################################################################# 308// #################################################################################################
297// ################################################################################################# 309// #################################################################################################
298 310
299Linda::Linda(Universe* const U_, LindaGroup const group_, std::string_view const& name_) 311Linda::Linda(Universe* const U_, std::string_view const& name_, lua_Duration const wake_period_, LindaGroup const group_)
300: DeepPrelude{ LindaFactory::Instance } 312: DeepPrelude{ LindaFactory::Instance }
301, U{ U_ } 313, U{ U_ }
314, wakePeriod{ wake_period_ }
302, keeperIndex{ group_ % U_->keepers.getNbKeepers() } 315, keeperIndex{ group_ % U_->keepers.getNbKeepers() }
303{ 316{
304 setName(name_); 317 setName(name_);
@@ -330,9 +343,13 @@ Linda* Linda::CreateTimerLinda(lua_State* const L_)
330 STACK_CHECK_START_REL(L_, 0); // L_: 343 STACK_CHECK_START_REL(L_, 0); // L_:
331 // Initialize 'timerLinda'; a common Linda object shared by all states 344 // Initialize 'timerLinda'; a common Linda object shared by all states
332 lua_pushcfunction(L_, LG_linda); // L_: lanes.linda 345 lua_pushcfunction(L_, LG_linda); // L_: lanes.linda
333 luaG_pushstring(L_, "lanes-timer"); // L_: lanes.linda "lanes-timer" 346 lua_createtable(L_, 0, 3); // L_: lanes.linda {}
334 lua_pushinteger(L_, 0); // L_: lanes.linda "lanes-timer" 0 347 luaG_pushstring(L_, "lanes-timer"); // L_: lanes.linda {} "lanes-timer"
335 lua_call(L_, 2, 1); // L_: linda 348 luaG_setfield(L_, StackIndex{ -2 }, std::string_view{ "name" }); // L_: lanes.linda { .name="lanes-timer" }
349 lua_pushinteger(L_, 0); // L_: lanes.linda { .name="lanes-timer" } 0
350 luaG_setfield(L_, StackIndex{ -2 }, std::string_view{ "group" }); // L_: lanes.linda { .name="lanes-timer" .group = 0 }
351 // note that wake_period is not set (will default to the value in the universe)
352 lua_call(L_, 1, 1); // L_: linda
336 STACK_CHECK(L_, 1); 353 STACK_CHECK(L_, 1);
337 354
338 // Proxy userdata contents is only a 'DeepPrelude*' pointer 355 // Proxy userdata contents is only a 'DeepPrelude*' pointer
@@ -941,11 +958,23 @@ LUAG_FUNC(linda_send)
941 _lane->waiting_on = &_linda->readHappened; 958 _lane->waiting_on = &_linda->readHappened;
942 _lane->status.store(Lane::Waiting, std::memory_order_release); 959 _lane->status.store(Lane::Waiting, std::memory_order_release);
943 } 960 }
961
962 // wait until the final target date by small increments, interrupting regularly so that we can check for cancel requests,
963 // in case some timing issue caused a cancel request to be issued, and the condvar signalled, before we actually wait for it
964 auto const [_forceTryAgain, _until_check_cancel] = std::invoke([_until, wakePeriod = _linda->getWakePeriod()] {
965 auto _until_check_cancel{ std::chrono::time_point<std::chrono::steady_clock>::max() };
966 if (wakePeriod.count() > 0.0f) {
967 _until_check_cancel = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(wakePeriod);
968 }
969 bool const _forceTryAgain{ _until_check_cancel < _until };
970 return std::make_tuple(_forceTryAgain, _forceTryAgain ? _until_check_cancel : _until);
971 });
972
944 // could not send because no room: wait until some data was read before trying again, or until timeout is reached 973 // could not send because no room: wait until some data was read before trying again, or until timeout is reached
945 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; 974 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock };
946 std::cv_status const status{ _linda->readHappened.wait_until(_guard, _until) }; 975 std::cv_status const status{ _linda->readHappened.wait_until(_guard, _until_check_cancel) };
947 _guard.release(); // we don't want to unlock the mutex on exit! 976 _guard.release(); // we don't want to unlock the mutex on exit!
948 _try_again = (status == std::cv_status::no_timeout); // detect spurious wakeups 977 _try_again = _forceTryAgain || (status == std::cv_status::no_timeout); // detect spurious wakeups
949 if (_lane != nullptr) { 978 if (_lane != nullptr) {
950 _lane->waiting_on = nullptr; 979 _lane->waiting_on = nullptr;
951 _lane->status.store(_prev_status, std::memory_order_release); 980 _lane->status.store(_prev_status, std::memory_order_release);
@@ -1129,88 +1158,69 @@ namespace {
1129// ################################################################################################# 1158// #################################################################################################
1130 1159
1131/* 1160/*
1132 * ud = lanes.linda( [name[,group[,close_handler]]]) 1161 * ud = lanes.linda{.name = <string>, .group = <number>, .close_handler = <callable>, .wake_period = <number>}
1133 * 1162 *
1134 * returns a linda object, or raises an error if creation failed 1163 * returns a linda object, or raises an error if creation failed
1135 */ 1164 */
1136LUAG_FUNC(linda) 1165LUAG_FUNC(linda)
1137{ 1166{
1138 static constexpr StackIndex kLastArg{ LUA_VERSION_NUM >= 504 ? 3 : 2 }; 1167 // unpack the received table on the stack, putting name wake_period group close_handler in that order
1139 StackIndex const _top{ lua_gettop(L_) }; 1168 StackIndex const _top{ lua_gettop(L_) };
1140 luaL_argcheck(L_, _top <= kLastArg, _top, "too many arguments"); 1169 luaL_argcheck(L_, _top <= 1, _top, "too many arguments");
1141 StackIndex _closeHandlerIdx{}; 1170 if (_top == 0) {
1142 StackIndex _nameIdx{}; 1171 lua_settop(L_, 3); // L_: nil nil nil
1143 StackIndex _groupIdx{}; 1172 }
1144 for (StackIndex const _i : std::ranges::iota_view{ StackIndex{ 1 }, StackIndex{ _top + 1 }}) { 1173 else if (!lua_istable(L_, kIdxTop)) {
1145 switch (luaG_type(L_, _i)) { 1174 luaL_argerror(L_, 1, "expecting a table");
1175 } else {
1176 auto* const _U{ Universe::Get(L_) };
1177 lua_getfield(L_, 1, "wake_period"); // L_: {} wake_period
1178 if (lua_isnil(L_, kIdxTop)) {
1179 lua_pop(L_, 1);
1180 lua_pushnumber(L_, _U->lindaWakePeriod.count());
1181 } else {
1182 luaL_argcheck(L_, luaL_optnumber(L_, 2, 0) > 0, 1, "wake_period must be > 0");
1183 }
1184
1185 lua_getfield(L_, 1, "group"); // L_: {} wake_period group
1186 int const _nbKeepers{ _U->keepers.getNbKeepers() };
1187 if (lua_isnil(L_, kIdxTop)) {
1188 luaL_argcheck(L_, _nbKeepers < 2, 0, "Group is mandatory in multiple Keeper scenarios");
1189 } else {
1190 int const _group{ static_cast<int>(lua_tointeger(L_, kIdxTop)) };
1191 luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, 1, "group out of range");
1192 }
1193
1146#if LUA_VERSION_NUM >= 504 // to-be-closed support starts with Lua 5.4 1194#if LUA_VERSION_NUM >= 504 // to-be-closed support starts with Lua 5.4
1147 case LuaType::FUNCTION: 1195 lua_getfield(L_, 1, "close_handler"); // L_: {} wake_period group close_handler
1148 luaL_argcheck(L_, _closeHandlerIdx == 0, _i, "More than one __close handler"); 1196 LuaType const _handlerType{ luaG_type(L_, kIdxTop) };
1149 _closeHandlerIdx = _i; 1197 if (_handlerType == LuaType::NIL) {
1150 break; 1198 lua_pop(L_, 1); // L_: {} wake_period group
1151 1199 } else if (_handlerType == LuaType::USERDATA || _handlerType == LuaType::TABLE) {
1152 case LuaType::USERDATA: 1200 luaL_argcheck(L_, luaL_getmetafield(L_, kIdxTop, "__call") != 0, 1, "__close handler is not callable");
1153 case LuaType::TABLE:
1154 luaL_argcheck(L_, _closeHandlerIdx == 0, _i, "More than one __close handler");
1155 luaL_argcheck(L_, luaL_getmetafield(L_, _i, "__call") != 0, _i, "__close handler is not callable");
1156 lua_pop(L_, 1); // luaL_getmetafield() pushed the field, we need to pop it 1201 lua_pop(L_, 1); // luaL_getmetafield() pushed the field, we need to pop it
1157 _closeHandlerIdx = _i; 1202 } else {
1158 break; 1203 luaL_argcheck(L_, _handlerType == LuaType::FUNCTION, 1, "__close handler is not a function");
1204 }
1159#endif // LUA_VERSION_NUM >= 504 1205#endif // LUA_VERSION_NUM >= 504
1160 1206
1161 case LuaType::STRING: 1207 auto const _nameType{ luaG_getfield(L_, StackIndex{ 1 }, "name") }; // L_: {} wake_period group [close_handler] name
1162 luaL_argcheck(L_, _nameIdx == 0, _i, "More than one name"); 1208 luaL_argcheck(L_, _nameType == LuaType::NIL || _nameType == LuaType::STRING, 1, "name is not a string");
1163 _nameIdx = _i; 1209 lua_replace(L_, 1); // L_: name wake_period group [close_handler]
1164 break;
1165
1166 case LuaType::NUMBER:
1167 luaL_argcheck(L_, _groupIdx == 0, _i, "More than one group");
1168 _groupIdx = _i;
1169 break;
1170
1171 default:
1172 luaL_argcheck(L_, false, _i, "Bad argument type (should be a string, a number, or a callable type)");
1173 }
1174 }
1175
1176 int const _nbKeepers{ Universe::Get(L_)->keepers.getNbKeepers() };
1177 if (!_groupIdx) {
1178 luaL_argcheck(L_, _nbKeepers < 2, 0, "Group is mandatory in multiple Keeper scenarios");
1179 } else {
1180 int const _group{ static_cast<int>(lua_tointeger(L_, _groupIdx)) };
1181 luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, _groupIdx, "Group out of range");
1182 } 1210 }
1183 1211
1184 // done with argument checking, let's proceed 1212 // done with argument checking, let's proceed
1185 if constexpr (LUA_VERSION_NUM >= 504) { 1213 if (lua_gettop(L_) == 4) {
1186 // make sure we have kMaxArgs arguments on the stack for processing, with name, group, and handler, in that order
1187 lua_settop(L_, kLastArg); // L_: a b c
1188 // If either index is 0, lua_settop() adjusted the stack with a nil in slot kLastArg
1189 lua_pushvalue(L_, _closeHandlerIdx ? _closeHandlerIdx : kLastArg); // L_: a b c close_handler
1190 lua_pushvalue(L_, _groupIdx ? _groupIdx : kLastArg); // L_: a b c close_handler group
1191 lua_pushvalue(L_, _nameIdx ? _nameIdx : kLastArg); // L_: a b c close_handler group name
1192 lua_replace(L_, 1); // L_: name b c close_handler group
1193 lua_replace(L_, 2); // L_: name group c close_handler
1194 lua_replace(L_, 3); // L_: name group close_handler
1195
1196 // if we have a __close handler, we need a uservalue slot to store it 1214 // if we have a __close handler, we need a uservalue slot to store it
1197 UserValueCount const _nuv{ _closeHandlerIdx ? 1 : 0 }; 1215 LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, UserValueCount{ 1 }); // L_: name wake_period group [close_handler] linda
1198 LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, _nuv); // L_: name group close_handler linda 1216 lua_replace(L_, 3); // L_: name wake_period linda close_handler
1199 if (_closeHandlerIdx != 0) { 1217 lua_setiuservalue(L_, StackIndex{ 3 }, UserValueIndex{ 1 }); // L_: name wake_period linda
1200 lua_replace(L_, 2); // L_: name linda close_handler
1201 lua_setiuservalue(L_, StackIndex{ 2 }, UserValueIndex{ 1 }); // L_: name linda
1202 }
1203 // depending on whether we have a handler or not, the stack is not in the same state at this point 1218 // depending on whether we have a handler or not, the stack is not in the same state at this point
1204 // just make sure we have our Linda at the top 1219 // just make sure we have our Linda at the top
1205 LUA_ASSERT(L_, ToLinda<true>(L_, kIdxTop)); 1220 LUA_ASSERT(L_, ToLinda<true>(L_, kIdxTop));
1206 return 1; 1221 return 1;
1207 } else { // no to-be-closed support 1222 } else { // no to-be-closed support
1208 // ensure we have name, group in that order on the stack 1223 LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, UserValueCount{ 0 }); // L_: name wake_period group linda
1209 if (_nameIdx > _groupIdx) {
1210 lua_insert(L_, 1); // L_: name group
1211 }
1212 LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, UserValueCount{ 0 }); // L_: name group linda
1213 return 1; 1224 return 1;
1214 } 1225 }
1215
1216} 1226}
diff --git a/src/linda.hpp b/src/linda.hpp
index 2d5c9dc..7874db3 100644
--- a/src/linda.hpp
+++ b/src/linda.hpp
@@ -42,19 +42,21 @@ class Linda final
42 }; 42 };
43 using enum Status; 43 using enum Status;
44 44
45 private: 45 public:
46 Universe* const U{ nullptr }; // the universe this linda belongs to
46 47
48 private:
47 static constexpr size_t kEmbeddedNameLength = 24; 49 static constexpr size_t kEmbeddedNameLength = 24;
48 using EmbeddedName = std::array<char, kEmbeddedNameLength>; 50 using EmbeddedName = std::array<char, kEmbeddedNameLength>;
49 // depending on the name length, it is either embedded inside the Linda, or allocated separately 51 // depending on the name length, it is either embedded inside the Linda, or allocated separately
50 std::variant<std::string_view, EmbeddedName> nameVariant{}; 52 std::variant<std::string_view, EmbeddedName> nameVariant{};
51 // counts the keeper operations in progress 53 // counts the keeper operations in progress
52 std::atomic<int> keeperOperationCount{}; 54 std::atomic<int> keeperOperationCount{};
55 lua_Duration wakePeriod{};
53 56
54 public: 57 public:
55 std::condition_variable readHappened{}; 58 std::condition_variable readHappened{};
56 std::condition_variable writeHappened{}; 59 std::condition_variable writeHappened{};
57 Universe* const U{ nullptr }; // the universe this linda belongs to
58 KeeperIndex const keeperIndex{ -1 }; // the keeper associated to this linda 60 KeeperIndex const keeperIndex{ -1 }; // the keeper associated to this linda
59 Status cancelStatus{ Status::Active }; 61 Status cancelStatus{ Status::Active };
60 62
@@ -68,7 +70,7 @@ class Linda final
68 static void operator delete(void* p_) { static_cast<Linda*>(p_)->U->internalAllocator.free(p_, sizeof(Linda)); } 70 static void operator delete(void* p_) { static_cast<Linda*>(p_)->U->internalAllocator.free(p_, sizeof(Linda)); }
69 71
70 ~Linda(); 72 ~Linda();
71 Linda(Universe* U_, LindaGroup group_, std::string_view const& name_); 73 Linda(Universe* U_, std::string_view const& name_, lua_Duration wake_period_, LindaGroup group_);
72 Linda() = delete; 74 Linda() = delete;
73 // non-copyable, non-movable 75 // non-copyable, non-movable
74 Linda(Linda const&) = delete; 76 Linda(Linda const&) = delete;
@@ -92,6 +94,8 @@ class Linda final
92 [[nodiscard]] 94 [[nodiscard]]
93 std::string_view getName() const; 95 std::string_view getName() const;
94 [[nodiscard]] 96 [[nodiscard]]
97 auto getWakePeriod() const { return wakePeriod; }
98 [[nodiscard]]
95 bool inKeeperOperation() const { return keeperOperationCount.load(std::memory_order_seq_cst) != 0; } 99 bool inKeeperOperation() const { return keeperOperationCount.load(std::memory_order_seq_cst) != 0; }
96 template <typename T = uintptr_t> 100 template <typename T = uintptr_t>
97 [[nodiscard]] 101 [[nodiscard]]
diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp
index 42d0984..4eab0c1 100644
--- a/src/lindafactory.cpp
+++ b/src/lindafactory.cpp
@@ -108,9 +108,11 @@ std::string_view LindaFactory::moduleName() const
108 108
109DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* const L_) const 109DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* const L_) const
110{ 110{
111 // we always expect name and group at the bottom of the stack (either can be nil). any extra stuff we ignore and keep unmodified 111 STACK_CHECK_START_REL(L_, 0);
112 // we always expect name, wake_period, group at the bottom of the stack (either can be nil). any extra stuff we ignore and keep unmodified
112 std::string_view _linda_name{ luaG_tostring(L_, StackIndex{ 1 }) }; 113 std::string_view _linda_name{ luaG_tostring(L_, StackIndex{ 1 }) };
113 LindaGroup _linda_group{ static_cast<int>(lua_tointeger(L_, 2)) }; 114 auto const _wake_period{ static_cast<lua_Duration>(lua_tonumber(L_, 2)) };
115 LindaGroup const _linda_group{ static_cast<int>(lua_tointeger(L_, 3)) };
114 116
115 // store in the linda the location of the script that created it 117 // store in the linda the location of the script that created it
116 if (_linda_name == "auto") { 118 if (_linda_name == "auto") {
@@ -129,6 +131,7 @@ DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* const L_) const
129 // The deep data is allocated separately of Lua stack; we might no longer be around when last reference to it is being released. 131 // The deep data is allocated separately of Lua stack; we might no longer be around when last reference to it is being released.
130 // One can use any memory allocation scheme. Just don't use L's allocF because we don't know which state will get the honor of GCing the linda 132 // One can use any memory allocation scheme. Just don't use L's allocF because we don't know which state will get the honor of GCing the linda
131 Universe* const _U{ Universe::Get(L_) }; 133 Universe* const _U{ Universe::Get(L_) };
132 Linda* const _linda{ new (_U) Linda{ _U, _linda_group, _linda_name } }; 134 Linda* const _linda{ new (_U) Linda{ _U, _linda_name, _wake_period, _linda_group } };
135 STACK_CHECK(L_, 0);
133 return _linda; 136 return _linda;
134} 137}
diff --git a/src/universe.cpp b/src/universe.cpp
index 89ad02a..335f056 100644
--- a/src/universe.cpp
+++ b/src/universe.cpp
@@ -153,6 +153,14 @@ Universe* Universe::Create(lua_State* const L_)
153 lua_setmetatable(L_, -2); // L_: settings universe 153 lua_setmetatable(L_, -2); // L_: settings universe
154 lua_pop(L_, 1); // L_: settings 154 lua_pop(L_, 1); // L_: settings
155 155
156 std::ignore = luaG_getfield(L_, kIdxSettings, "linda_wake_period"); // L_: settings linda_wake_period
157 if (luaG_type(L_, kIdxTop) == LuaType::NUMBER) {
158 _U->lindaWakePeriod = lua_Duration{ lua_tonumber(L_, kIdxTop) };
159 } else {
160 LUA_ASSERT(L_, luaG_tostring(L_, kIdxTop) == "never");
161 }
162 lua_pop(L_, 1); // L_: settings
163
156 std::ignore = luaG_getfield(L_, kIdxSettings, "strip_functions"); // L_: settings strip_functions 164 std::ignore = luaG_getfield(L_, kIdxSettings, "strip_functions"); // L_: settings strip_functions
157 _U->stripFunctions = lua_toboolean(L_, -1) ? true : false; 165 _U->stripFunctions = lua_toboolean(L_, -1) ? true : false;
158 lua_pop(L_, 1); // L_: settings 166 lua_pop(L_, 1); // L_: settings
diff --git a/src/universe.hpp b/src/universe.hpp
index 42a3d83..0c5e659 100644
--- a/src/universe.hpp
+++ b/src/universe.hpp
@@ -99,6 +99,8 @@ class Universe final
99 99
100 Keepers keepers; 100 Keepers keepers;
101 101
102 lua_Duration lindaWakePeriod{};
103
102 // Initialized by 'init_once_LOCKED()': the deep userdata Linda object 104 // Initialized by 'init_once_LOCKED()': the deep userdata Linda object
103 // used for timers (each lane will get a proxy to this) 105 // used for timers (each lane will get a proxy to this)
104 Linda* timerLinda{ nullptr }; 106 Linda* timerLinda{ nullptr };