diff options
Diffstat (limited to '')
-rw-r--r-- | src/linda.cpp | 510 |
1 files changed, 258 insertions, 252 deletions
diff --git a/src/linda.cpp b/src/linda.cpp index 1119d71..1f4b19d 100644 --- a/src/linda.cpp +++ b/src/linda.cpp | |||
@@ -47,7 +47,7 @@ namespace { | |||
47 | { | 47 | { |
48 | STACK_CHECK_START_REL(L_, 0); | 48 | STACK_CHECK_START_REL(L_, 0); |
49 | for (StackIndex const _i : std::ranges::iota_view{ start_, StackIndex{ end_ + 1 } }) { | 49 | for (StackIndex const _i : std::ranges::iota_view{ start_, StackIndex{ end_ + 1 } }) { |
50 | LuaType const _t{ luaG_type(L_, _i) }; | 50 | LuaType const _t{ luaW_type(L_, _i) }; |
51 | switch (_t) { | 51 | switch (_t) { |
52 | case LuaType::BOOLEAN: | 52 | case LuaType::BOOLEAN: |
53 | case LuaType::NUMBER: | 53 | case LuaType::NUMBER: |
@@ -62,7 +62,7 @@ namespace { | |||
62 | 62 | ||
63 | case LuaType::LIGHTUSERDATA: | 63 | case LuaType::LIGHTUSERDATA: |
64 | { | 64 | { |
65 | static constexpr std::array<std::reference_wrapper<UniqueKey const>, 3> kKeysToCheck{ kLindaBatched, kCancelError, kNilSentinel }; | 65 | static constexpr std::array<std::reference_wrapper<UniqueKey const>, 2> kKeysToCheck{ kCancelError, kNilSentinel }; |
66 | for (UniqueKey const& _key : kKeysToCheck) { | 66 | for (UniqueKey const& _key : kKeysToCheck) { |
67 | if (_key.equals(L_, _i)) { | 67 | if (_key.equals(L_, _i)) { |
68 | raise_luaL_error(L_, "argument #%d: can't use %s as a slot", _i, _key.debugName.data()); | 68 | raise_luaL_error(L_, "argument #%d: can't use %s as a slot", _i, _key.debugName.data()); |
@@ -109,13 +109,13 @@ namespace { | |||
109 | { | 109 | { |
110 | Linda* const _linda{ ToLinda<OPT>(L_, idx_) }; | 110 | Linda* const _linda{ ToLinda<OPT>(L_, idx_) }; |
111 | if (_linda != nullptr) { | 111 | if (_linda != nullptr) { |
112 | luaG_pushstring(L_, "Linda: "); | 112 | luaW_pushstring(L_, "Linda: "); |
113 | std::string_view const _lindaName{ _linda->getName() }; | 113 | std::string_view const _lindaName{ _linda->getName() }; |
114 | if (!_lindaName.empty()) { | 114 | if (!_lindaName.empty()) { |
115 | luaG_pushstring(L_, _lindaName); | 115 | luaW_pushstring(L_, _lindaName); |
116 | } else { | 116 | } else { |
117 | // obfuscate the pointer so that we can't read the value with our eyes out of a script | 117 | // obfuscate the pointer so that we can't read the value with our eyes out of a script |
118 | luaG_pushstring(L_, "%p", _linda->obfuscated()); | 118 | luaW_pushstring(L_, "%p", _linda->obfuscated()); |
119 | } | 119 | } |
120 | lua_concat(L_, 2); | 120 | lua_concat(L_, 2); |
121 | return 1; | 121 | return 1; |
@@ -123,6 +123,8 @@ namespace { | |||
123 | return 0; | 123 | return 0; |
124 | } | 124 | } |
125 | 125 | ||
126 | // ############################################################################################# | ||
127 | |||
126 | // a helper to process the timeout argument of linda:send() and linda:receive() | 128 | // a helper to process the timeout argument of linda:send() and linda:receive() |
127 | [[nodiscard]] | 129 | [[nodiscard]] |
128 | static auto ProcessTimeoutArg(lua_State* const L_) | 130 | static auto ProcessTimeoutArg(lua_State* const L_) |
@@ -130,7 +132,7 @@ namespace { | |||
130 | StackIndex _key_i{ 2 }; // index of first slot, if timeout not there | 132 | StackIndex _key_i{ 2 }; // index of first slot, if timeout not there |
131 | 133 | ||
132 | std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; | 134 | std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; |
133 | if (luaG_type(L_, StackIndex{ 2 }) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion | 135 | if (luaW_type(L_, StackIndex{ 2 }) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion |
134 | lua_Duration const _duration{ lua_tonumber(L_, 2) }; | 136 | lua_Duration const _duration{ lua_tonumber(L_, 2) }; |
135 | if (_duration.count() >= 0.0) { | 137 | if (_duration.count() >= 0.0) { |
136 | _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(_duration); | 138 | _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(_duration); |
@@ -145,6 +147,160 @@ namespace { | |||
145 | } | 147 | } |
146 | 148 | ||
147 | // ############################################################################################# | 149 | // ############################################################################################# |
150 | static bool WaitInternal([[maybe_unused]] lua_State* const L_, Lane* const lane_, Linda* const linda_, Keeper* const keeper_, std::condition_variable& waitingOn_, std::chrono::time_point<std::chrono::steady_clock> until_) | ||
151 | { | ||
152 | Lane::Status _prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings | ||
153 | if (lane_ != nullptr) { | ||
154 | // change status of lane to "waiting" | ||
155 | _prev_status = lane_->status.load(std::memory_order_acquire); // Running, most likely | ||
156 | LUA_ASSERT(L_, _prev_status == Lane::Running); // but check, just in case | ||
157 | LUA_ASSERT(L_, lane_->waiting_on == nullptr); | ||
158 | lane_->waiting_on = &waitingOn_; | ||
159 | lane_->status.store(Lane::Waiting, std::memory_order_release); | ||
160 | } | ||
161 | |||
162 | // wait until the final target date by small increments, interrupting regularly so that we can check for cancel requests, | ||
163 | // in case some timing issue caused a cancel request to be issued, and the condvar signalled, before we actually wait for it | ||
164 | auto const [_forceTryAgain, _until_check_cancel] = std::invoke([until_, wakePeriod = linda_->getWakePeriod()] { | ||
165 | auto _until_check_cancel{ std::chrono::time_point<std::chrono::steady_clock>::max() }; | ||
166 | if (wakePeriod.count() > 0.0f) { | ||
167 | _until_check_cancel = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(wakePeriod); | ||
168 | } | ||
169 | bool const _forceTryAgain{ _until_check_cancel < until_ }; | ||
170 | return std::make_tuple(_forceTryAgain, _forceTryAgain ? _until_check_cancel : until_); | ||
171 | }); | ||
172 | |||
173 | // operation can't complete: wake when it is signalled to be possible, or when timeout is reached | ||
174 | std::unique_lock<std::mutex> _guard{ keeper_->mutex, std::adopt_lock }; | ||
175 | std::cv_status const _status{ waitingOn_.wait_until(_guard, _until_check_cancel) }; | ||
176 | _guard.release(); // we don't want to unlock the mutex on exit! | ||
177 | bool const _try_again{ _forceTryAgain || (_status == std::cv_status::no_timeout) }; // detect spurious wakeups | ||
178 | if (lane_ != nullptr) { | ||
179 | lane_->waiting_on = nullptr; | ||
180 | lane_->status.store(_prev_status, std::memory_order_release); | ||
181 | } | ||
182 | return _try_again; | ||
183 | } | ||
184 | |||
185 | // ############################################################################################# | ||
186 | |||
187 | // the implementation for linda:receive() and linda:receive_batched() | ||
188 | static int ReceiveInternal(lua_State* const L_, bool const batched_) | ||
189 | { | ||
190 | Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) }; | ||
191 | |||
192 | auto const [_key_i, _until] = ProcessTimeoutArg(L_); | ||
193 | |||
194 | keeper_api_t _selected_keeper_receive{ nullptr }; | ||
195 | int _expected_pushed_min{ 0 }, _expected_pushed_max{ 0 }; | ||
196 | // are we in batched mode? | ||
197 | if (batched_) { | ||
198 | // make sure the keys are of a valid type | ||
199 | CheckKeyTypes(L_, _key_i, _key_i); | ||
200 | // receive multiple values from a single slot | ||
201 | _selected_keeper_receive = KEEPER_API(receive_batched); | ||
202 | // we expect a user-defined amount of return value | ||
203 | _expected_pushed_min = (int) luaL_checkinteger(L_, _key_i + 1); | ||
204 | if (_expected_pushed_min < 1) { | ||
205 | raise_luaL_argerror(L_, StackIndex{ _key_i + 1 }, "bad min count"); | ||
206 | } | ||
207 | _expected_pushed_max = (int) luaL_optinteger(L_, _key_i + 2, _expected_pushed_min); | ||
208 | // don't forget to count the slot in addition to the values | ||
209 | ++_expected_pushed_min; | ||
210 | ++_expected_pushed_max; | ||
211 | if (_expected_pushed_min > _expected_pushed_max) { | ||
212 | raise_luaL_argerror(L_, StackIndex{ _key_i + 2 }, "batched min/max error"); | ||
213 | } | ||
214 | } else { | ||
215 | // make sure the keys are of a valid type | ||
216 | CheckKeyTypes(L_, _key_i, StackIndex{ lua_gettop(L_) }); | ||
217 | // receive a single value, checking multiple slots | ||
218 | _selected_keeper_receive = KEEPER_API(receive); | ||
219 | // we expect a single (value, slot) pair of returned values | ||
220 | _expected_pushed_min = _expected_pushed_max = 2; | ||
221 | } | ||
222 | |||
223 | Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) }; | ||
224 | Keeper* const _keeper{ _linda->whichKeeper() }; | ||
225 | KeeperState const _K{ _keeper ? _keeper->K : KeeperState{ static_cast<lua_State*>(nullptr) } }; | ||
226 | if (_K == nullptr) | ||
227 | return 0; | ||
228 | |||
229 | CancelRequest _cancel{ CancelRequest::None }; | ||
230 | KeeperCallResult _pushed{}; | ||
231 | |||
232 | STACK_CHECK_START_REL(_K, 0); | ||
233 | for (bool _try_again{ true };;) { | ||
234 | if (_lane != nullptr) { | ||
235 | _cancel = _lane->cancelRequest.load(std::memory_order_relaxed); | ||
236 | } | ||
237 | _cancel = (_cancel != CancelRequest::None) | ||
238 | ? _cancel | ||
239 | : ((_linda->cancelStatus == Linda::Cancelled) ? CancelRequest::Soft : CancelRequest::None); | ||
240 | |||
241 | // if user wants to cancel, or looped because of a timeout, the call returns without sending anything | ||
242 | if (!_try_again || _cancel != CancelRequest::None) { | ||
243 | _pushed.emplace(0); | ||
244 | break; | ||
245 | } | ||
246 | |||
247 | // all arguments of receive() but the first are passed to the keeper's receive function | ||
248 | STACK_CHECK(_K, 0); | ||
249 | _pushed = keeper_call(_K, _selected_keeper_receive, L_, _linda, _key_i); | ||
250 | if (!_pushed.has_value()) { | ||
251 | break; | ||
252 | } | ||
253 | if (_pushed.value() > 0) { | ||
254 | LUA_ASSERT(L_, _pushed.value() >= _expected_pushed_min && _pushed.value() <= _expected_pushed_max); | ||
255 | if (kRestrictedChannel.equals(L_, StackIndex{ kIdxTop })) { | ||
256 | raise_luaL_error(L_, "Key is restricted"); | ||
257 | } | ||
258 | _linda->readHappened.notify_all(); | ||
259 | break; | ||
260 | } | ||
261 | |||
262 | if (std::chrono::steady_clock::now() >= _until) { | ||
263 | break; /* instant timeout */ | ||
264 | } | ||
265 | |||
266 | // nothing received, wait until timeout or signalled that we should try again | ||
267 | _try_again = WaitInternal(L_, _lane, _linda, _keeper, _linda->writeHappened, _until); | ||
268 | } | ||
269 | STACK_CHECK(_K, 0); | ||
270 | |||
271 | if (!_pushed.has_value()) { | ||
272 | raise_luaL_error(L_, "tried to copy unsupported types"); | ||
273 | } | ||
274 | |||
275 | switch (_cancel) { | ||
276 | case CancelRequest::None: | ||
277 | { | ||
278 | int const _nbPushed{ _pushed.value() }; | ||
279 | if (_nbPushed == 0) { | ||
280 | // not enough data in the linda slot to fulfill the request, return nil, "timeout" | ||
281 | lua_pushnil(L_); | ||
282 | luaW_pushstring(L_, "timeout"); | ||
283 | return 2; | ||
284 | } | ||
285 | return _nbPushed; | ||
286 | } | ||
287 | |||
288 | case CancelRequest::Soft: | ||
289 | // if user wants to soft-cancel, the call returns nil, kCancelError | ||
290 | lua_pushnil(L_); | ||
291 | kCancelError.pushKey(L_); | ||
292 | return 2; | ||
293 | |||
294 | case CancelRequest::Hard: | ||
295 | // raise an error interrupting execution only in case of hard cancel | ||
296 | raise_cancel_error(L_); // raises an error and doesn't return | ||
297 | |||
298 | default: | ||
299 | raise_luaL_error(L_, "internal error: unknown cancel request"); | ||
300 | } | ||
301 | } | ||
302 | |||
303 | // ############################################################################################# | ||
148 | // ############################################################################################# | 304 | // ############################################################################################# |
149 | } // namespace | 305 | } // namespace |
150 | // ################################################################################################# | 306 | // ################################################################################################# |
@@ -158,9 +314,10 @@ LUAG_FUNC(linda); | |||
158 | // ################################################################################################# | 314 | // ################################################################################################# |
159 | // ################################################################################################# | 315 | // ################################################################################################# |
160 | 316 | ||
161 | Linda::Linda(Universe* const U_, LindaGroup const group_, std::string_view const& name_) | 317 | Linda::Linda(Universe* const U_, std::string_view const& name_, lua_Duration const wake_period_, LindaGroup const group_) |
162 | : DeepPrelude{ LindaFactory::Instance } | 318 | : DeepPrelude{ LindaFactory::Instance } |
163 | , U{ U_ } | 319 | , U{ U_ } |
320 | , wakePeriod{ wake_period_ } | ||
164 | , keeperIndex{ group_ % U_->keepers.getNbKeepers() } | 321 | , keeperIndex{ group_ % U_->keepers.getNbKeepers() } |
165 | { | 322 | { |
166 | setName(name_); | 323 | setName(name_); |
@@ -181,6 +338,7 @@ Keeper* Linda::acquireKeeper() const | |||
181 | Keeper* const _keeper{ whichKeeper() }; | 338 | Keeper* const _keeper{ whichKeeper() }; |
182 | if (_keeper) { | 339 | if (_keeper) { |
183 | _keeper->mutex.lock(); | 340 | _keeper->mutex.lock(); |
341 | keeperOperationCount.fetch_add(1, std::memory_order_seq_cst); | ||
184 | } | 342 | } |
185 | return _keeper; | 343 | return _keeper; |
186 | } | 344 | } |
@@ -192,13 +350,17 @@ Linda* Linda::CreateTimerLinda(lua_State* const L_) | |||
192 | STACK_CHECK_START_REL(L_, 0); // L_: | 350 | STACK_CHECK_START_REL(L_, 0); // L_: |
193 | // Initialize 'timerLinda'; a common Linda object shared by all states | 351 | // Initialize 'timerLinda'; a common Linda object shared by all states |
194 | lua_pushcfunction(L_, LG_linda); // L_: lanes.linda | 352 | lua_pushcfunction(L_, LG_linda); // L_: lanes.linda |
195 | luaG_pushstring(L_, "lanes-timer"); // L_: lanes.linda "lanes-timer" | 353 | lua_createtable(L_, 0, 3); // L_: lanes.linda {} |
196 | lua_pushinteger(L_, 0); // L_: lanes.linda "lanes-timer" 0 | 354 | luaW_pushstring(L_, "lanes-timer"); // L_: lanes.linda {} "lanes-timer" |
197 | lua_call(L_, 2, 1); // L_: linda | 355 | luaW_setfield(L_, StackIndex{ -2 }, std::string_view{ "name" }); // L_: lanes.linda { .name="lanes-timer" } |
356 | lua_pushinteger(L_, 0); // L_: lanes.linda { .name="lanes-timer" } 0 | ||
357 | luaW_setfield(L_, StackIndex{ -2 }, std::string_view{ "group" }); // L_: lanes.linda { .name="lanes-timer" .group = 0 } | ||
358 | // note that wake_period is not set (will default to the value in the universe) | ||
359 | lua_call(L_, 1, 1); // L_: linda | ||
198 | STACK_CHECK(L_, 1); | 360 | STACK_CHECK(L_, 1); |
199 | 361 | ||
200 | // Proxy userdata contents is only a 'DeepPrelude*' pointer | 362 | // Proxy userdata contents is only a 'DeepPrelude*' pointer |
201 | auto const _timerLinda{ *luaG_tofulluserdata<Linda*>(L_, kIdxTop) }; | 363 | auto const _timerLinda{ *luaW_tofulluserdata<Linda*>(L_, kIdxTop) }; |
202 | // increment refcount so that this linda remains alive as long as the universe exists. | 364 | // increment refcount so that this linda remains alive as long as the universe exists. |
203 | _timerLinda->refcount.fetch_add(1, std::memory_order_relaxed); | 365 | _timerLinda->refcount.fetch_add(1, std::memory_order_relaxed); |
204 | lua_pop(L_, 1); // L_: | 366 | lua_pop(L_, 1); // L_: |
@@ -261,7 +423,6 @@ int Linda::ProtectedCall(lua_State* const L_, lua_CFunction const f_) | |||
261 | // doing LindaFactory::deleteDeepObjectInternal -> keeper_call(clear) | 423 | // doing LindaFactory::deleteDeepObjectInternal -> keeper_call(clear) |
262 | lua_gc(L_, LUA_GCSTOP, 0); | 424 | lua_gc(L_, LUA_GCSTOP, 0); |
263 | 425 | ||
264 | LUA_ASSERT_CODE(auto const _koip{ _linda->startKeeperOperation(L_) }); | ||
265 | // if we didn't do anything wrong, the keeper stack should be clean | 426 | // if we didn't do anything wrong, the keeper stack should be clean |
266 | LUA_ASSERT(L_, lua_gettop(_K) == 0); | 427 | LUA_ASSERT(L_, lua_gettop(_K) == 0); |
267 | 428 | ||
@@ -269,7 +430,7 @@ int Linda::ProtectedCall(lua_State* const L_, lua_CFunction const f_) | |||
269 | lua_pushcfunction(L_, f_); | 430 | lua_pushcfunction(L_, f_); |
270 | lua_insert(L_, 1); | 431 | lua_insert(L_, 1); |
271 | // do a protected call | 432 | // do a protected call |
272 | LuaError const _rc{ lua_pcall(L_, lua_gettop(L_) - 1, LUA_MULTRET, 0) }; | 433 | LuaError const _rc{ ToLuaError(lua_pcall(L_, lua_gettop(L_) - 1, LUA_MULTRET, 0)) }; |
273 | // whatever happens, the keeper state stack must be empty when we are done | 434 | // whatever happens, the keeper state stack must be empty when we are done |
274 | lua_settop(_K, 0); | 435 | lua_settop(_K, 0); |
275 | 436 | ||
@@ -291,7 +452,7 @@ int Linda::ProtectedCall(lua_State* const L_, lua_CFunction const f_) | |||
291 | 452 | ||
292 | void Linda::pushCancelString(lua_State* L_) const | 453 | void Linda::pushCancelString(lua_State* L_) const |
293 | { | 454 | { |
294 | luaG_pushstring(L_, cancelStatus == Status::Cancelled ? "cancelled" : "active"); | 455 | luaW_pushstring(L_, cancelStatus == Status::Cancelled ? "cancelled" : "active"); |
295 | } | 456 | } |
296 | 457 | ||
297 | // ################################################################################################# | 458 | // ################################################################################################# |
@@ -300,6 +461,7 @@ void Linda::releaseKeeper(Keeper* const keeper_) const | |||
300 | { | 461 | { |
301 | if (keeper_) { // can be nullptr if we tried to acquire during shutdown | 462 | if (keeper_) { // can be nullptr if we tried to acquire during shutdown |
302 | assert(keeper_ == whichKeeper()); | 463 | assert(keeper_ == whichKeeper()); |
464 | keeperOperationCount.fetch_sub(1, std::memory_order_seq_cst); | ||
303 | keeper_->mutex.unlock(); | 465 | keeper_->mutex.unlock(); |
304 | } | 466 | } |
305 | } | 467 | } |
@@ -343,7 +505,7 @@ void Linda::setName(std::string_view const& name_) | |||
343 | LUAG_FUNC(linda_cancel) | 505 | LUAG_FUNC(linda_cancel) |
344 | { | 506 | { |
345 | Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) }; | 507 | Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) }; |
346 | std::string_view const _who{ luaG_optstring(L_, StackIndex{ 2 }, "both") }; | 508 | std::string_view const _who{ luaW_optstring(L_, StackIndex{ 2 }, "both") }; |
347 | // make sure we got 2 arguments: the linda and the cancellation mode | 509 | // make sure we got 2 arguments: the linda and the cancellation mode |
348 | luaL_argcheck(L_, lua_gettop(L_) <= 2, 2, "wrong number of arguments"); | 510 | luaL_argcheck(L_, lua_gettop(L_) <= 2, 2, "wrong number of arguments"); |
349 | 511 | ||
@@ -431,13 +593,13 @@ static int linda_index_string(lua_State* L_) | |||
431 | Linda* const _linda{ ToLinda<false>(L_, kIdxSelf) }; | 593 | Linda* const _linda{ ToLinda<false>(L_, kIdxSelf) }; |
432 | LUA_ASSERT(L_, lua_gettop(L_) == 2); // L_: linda "key" | 594 | LUA_ASSERT(L_, lua_gettop(L_) == 2); // L_: linda "key" |
433 | 595 | ||
434 | std::string_view const _keystr{ luaG_tostring(L_, kIdxKey) }; | 596 | std::string_view const _keystr{ luaW_tostring(L_, kIdxKey) }; |
435 | lua_settop(L_, 2); // keep only our original arguments on the stack | 597 | lua_settop(L_, 2); // keep only our original arguments on the stack |
436 | 598 | ||
437 | // look in metatable first | 599 | // look in metatable first |
438 | lua_getmetatable(L_, kIdxSelf); // L_: linda "key" mt | 600 | lua_getmetatable(L_, kIdxSelf); // L_: linda "key" mt |
439 | lua_replace(L_, -3); // L_: mt "key" | 601 | lua_replace(L_, -3); // L_: mt "key" |
440 | if (luaG_rawget(L_, StackIndex{ -2 }) != LuaType::NIL) { // found something? // L_: mt value | 602 | if (luaW_rawget(L_, StackIndex{ -2 }) != LuaType::NIL) { // found something? // L_: mt value |
441 | return 1; // done | 603 | return 1; // done |
442 | } | 604 | } |
443 | 605 | ||
@@ -457,12 +619,12 @@ static LUAG_FUNC(linda_index) | |||
457 | static constexpr StackIndex kIdxKey{ 2 }; | 619 | static constexpr StackIndex kIdxKey{ 2 }; |
458 | LUA_ASSERT(L_, lua_gettop(L_) == 2); | 620 | LUA_ASSERT(L_, lua_gettop(L_) == 2); |
459 | 621 | ||
460 | switch (luaG_type(L_, kIdxKey)) { | 622 | switch (luaW_type(L_, kIdxKey)) { |
461 | case LuaType::STRING: | 623 | case LuaType::STRING: |
462 | return linda_index_string(L_); // stack modification is undefined, returned value is at the top | 624 | return linda_index_string(L_); // stack modification is undefined, returned value is at the top |
463 | 625 | ||
464 | default: // unknown key | 626 | default: // unknown key |
465 | raise_luaL_error(L_, "Unsupported linda indexing key type %s", luaG_typename(L_, kIdxKey).data()); | 627 | raise_luaL_error(L_, "Unsupported linda indexing key type %s", luaW_typename(L_, kIdxKey).data()); |
466 | } | 628 | } |
467 | } | 629 | } |
468 | 630 | ||
@@ -605,7 +767,7 @@ LUAG_FUNC(linda_limit) | |||
605 | int const _nargs{ lua_gettop(L_) }; | 767 | int const _nargs{ lua_gettop(L_) }; |
606 | luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments"); | 768 | luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments"); |
607 | // make sure we got a numeric limit, or "unlimited", (or nothing) | 769 | // make sure we got a numeric limit, or "unlimited", (or nothing) |
608 | bool const _unlimited{ luaG_tostring(L_, StackIndex{ 3 }) == "unlimited" }; | 770 | bool const _unlimited{ luaW_tostring(L_, StackIndex{ 3 }) == "unlimited" }; |
609 | LindaLimit const _val{ _unlimited ? std::numeric_limits<LindaLimit::type>::max() : static_cast<LindaLimit::type>(luaL_optinteger(L_, 3, 0)) }; | 771 | LindaLimit const _val{ _unlimited ? std::numeric_limits<LindaLimit::type>::max() : static_cast<LindaLimit::type>(luaL_optinteger(L_, 3, 0)) }; |
610 | if (_val < 0) { | 772 | if (_val < 0) { |
611 | raise_luaL_argerror(L_, StackIndex{ 3 }, "limit must be >= 0"); | 773 | raise_luaL_argerror(L_, StackIndex{ 3 }, "limit must be >= 0"); |
@@ -616,23 +778,23 @@ LUAG_FUNC(linda_limit) | |||
616 | KeeperCallResult _pushed; | 778 | KeeperCallResult _pushed; |
617 | if (_linda->cancelStatus == Linda::Active) { | 779 | if (_linda->cancelStatus == Linda::Active) { |
618 | if (_unlimited) { | 780 | if (_unlimited) { |
619 | LUA_ASSERT(L_, lua_gettop(L_) == 3 && luaG_tostring(L_, StackIndex{ 3 }) == "unlimited"); | 781 | LUA_ASSERT(L_, lua_gettop(L_) == 3 && luaW_tostring(L_, StackIndex{ 3 }) == "unlimited"); |
620 | // inside the Keeper, unlimited is signified with a -1 limit (can't use nil because of nil kNilSentinel conversions!) | 782 | // inside the Keeper, unlimited is signified with a -1 limit (can't use nil because of nil kNilSentinel conversions!) |
621 | lua_pop(L_, 1); // L_: linda slot | 783 | lua_pop(L_, 1); // L_: linda slot |
622 | lua_pushinteger(L_, -1); // L_: linda slot nil | 784 | lua_pushinteger(L_, -1); // L_: linda slot nil |
623 | } | 785 | } |
624 | Keeper* const _keeper{ _linda->whichKeeper() }; | 786 | Keeper* const _keeper{ _linda->whichKeeper() }; |
625 | _pushed = keeper_call(_keeper->K, KEEPER_API(limit), L_, _linda, StackIndex{ 2 }); | 787 | _pushed = keeper_call(_keeper->K, KEEPER_API(limit), L_, _linda, StackIndex{ 2 }); |
626 | LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 2) && luaG_type(L_, kIdxTop) == LuaType::STRING); | 788 | LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 2) && luaW_type(L_, kIdxTop) == LuaType::STRING); |
627 | if (_nargs == 3) { // 3 args: setting the limit | 789 | if (_nargs == 3) { // 3 args: setting the limit |
628 | // changing the limit: no error, boolean value saying if we should wake blocked writer threads | 790 | // changing the limit: no error, boolean value saying if we should wake blocked writer threads |
629 | LUA_ASSERT(L_, luaG_type(L_, StackIndex{ -2 }) == LuaType::BOOLEAN); // L_: bool string | 791 | LUA_ASSERT(L_, luaW_type(L_, StackIndex{ -2 }) == LuaType::BOOLEAN); // L_: bool string |
630 | if (lua_toboolean(L_, -2)) { | 792 | if (lua_toboolean(L_, -2)) { |
631 | _linda->readHappened.notify_all(); // To be done from within the 'K' locking area | 793 | _linda->readHappened.notify_all(); // To be done from within the 'K' locking area |
632 | } | 794 | } |
633 | } else { // 2 args: reading the limit | 795 | } else { // 2 args: reading the limit |
634 | // reading the limit: a number >=0 or "unlimited" | 796 | // reading the limit: a number >=0 or "unlimited" |
635 | LUA_ASSERT(L_, luaG_type(L_, StackIndex{ -2 }) == LuaType::NUMBER || luaG_tostring(L_, StackIndex{ -2 }) == "unlimited"); | 797 | LUA_ASSERT(L_, luaW_type(L_, StackIndex{ -2 }) == LuaType::NUMBER || luaW_tostring(L_, StackIndex{ -2 }) == "unlimited"); |
636 | } | 798 | } |
637 | } else { // linda is cancelled | 799 | } else { // linda is cancelled |
638 | // do nothing and return nil,lanes.cancel_error | 800 | // do nothing and return nil,lanes.cancel_error |
@@ -650,153 +812,25 @@ LUAG_FUNC(linda_limit) | |||
650 | // ################################################################################################# | 812 | // ################################################################################################# |
651 | 813 | ||
652 | /* | 814 | /* |
653 | * 2 modes of operation | 815 | * [val, slot] = linda:receive([timeout_secs_num=nil], key_num|str|bool|lightuserdata [, ...] ) |
654 | * [val, slot]= linda:receive([timeout_secs_num=nil], key_num|str|bool|lightuserdata [, ...] ) | ||
655 | * Consumes a single value from the Linda, in any slot. | 816 | * Consumes a single value from the Linda, in any slot. |
656 | * Returns: received value (which is consumed from the slot), and the slot which had it | 817 | * Returns: received value (which is consumed from the slot), and the slot which had it |
657 | |||
658 | * [val1, ... valCOUNT]= linda_receive( linda_ud, [timeout_secs_num=-1], linda.batched, key_num|str|bool|lightuserdata, min_COUNT[, max_COUNT]) | ||
659 | * Consumes between min_COUNT and max_COUNT values from the linda, from a single slot. | ||
660 | * returns the actual consumed values, or nil if there weren't enough values to consume | ||
661 | */ | 818 | */ |
662 | LUAG_FUNC(linda_receive) | 819 | LUAG_FUNC(linda_receive) |
663 | { | 820 | { |
664 | static constexpr lua_CFunction _receive{ | 821 | return Linda::ProtectedCall(L_, [](lua_State* const L_) { return ReceiveInternal(L_, false); }); |
665 | +[](lua_State* const L_) { | 822 | } |
666 | Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) }; | ||
667 | |||
668 | auto [_key_i, _until] = ProcessTimeoutArg(L_); | ||
669 | |||
670 | keeper_api_t _selected_keeper_receive{ nullptr }; | ||
671 | int _expected_pushed_min{ 0 }, _expected_pushed_max{ 0 }; | ||
672 | // are we in batched mode? | ||
673 | if (kLindaBatched.equals(L_, _key_i)) { | ||
674 | // no need to pass linda.batched in the keeper state | ||
675 | ++_key_i; | ||
676 | // make sure the keys are of a valid type | ||
677 | CheckKeyTypes(L_, _key_i, _key_i); | ||
678 | // receive multiple values from a single slot | ||
679 | _selected_keeper_receive = KEEPER_API(receive_batched); | ||
680 | // we expect a user-defined amount of return value | ||
681 | _expected_pushed_min = (int) luaL_checkinteger(L_, _key_i + 1); | ||
682 | if (_expected_pushed_min < 1) { | ||
683 | raise_luaL_argerror(L_, StackIndex{ _key_i + 1 }, "bad min count"); | ||
684 | } | ||
685 | _expected_pushed_max = (int) luaL_optinteger(L_, _key_i + 2, _expected_pushed_min); | ||
686 | // don't forget to count the slot in addition to the values | ||
687 | ++_expected_pushed_min; | ||
688 | ++_expected_pushed_max; | ||
689 | if (_expected_pushed_min > _expected_pushed_max) { | ||
690 | raise_luaL_argerror(L_, StackIndex{ _key_i + 2 }, "batched min/max error"); | ||
691 | } | ||
692 | } else { | ||
693 | // make sure the keys are of a valid type | ||
694 | CheckKeyTypes(L_, _key_i, StackIndex{ lua_gettop(L_) }); | ||
695 | // receive a single value, checking multiple slots | ||
696 | _selected_keeper_receive = KEEPER_API(receive); | ||
697 | // we expect a single (value, slot) pair of returned values | ||
698 | _expected_pushed_min = _expected_pushed_max = 2; | ||
699 | } | ||
700 | |||
701 | Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) }; | ||
702 | Keeper* const _keeper{ _linda->whichKeeper() }; | ||
703 | KeeperState const _K{ _keeper ? _keeper->K : KeeperState{ static_cast<lua_State*>(nullptr) } }; | ||
704 | if (_K == nullptr) | ||
705 | return 0; | ||
706 | |||
707 | CancelRequest _cancel{ CancelRequest::None }; | ||
708 | KeeperCallResult _pushed{}; | ||
709 | |||
710 | STACK_CHECK_START_REL(_K, 0); | ||
711 | for (bool _try_again{ true };;) { | ||
712 | if (_lane != nullptr) { | ||
713 | _cancel = _lane->cancelRequest.load(std::memory_order_relaxed); | ||
714 | } | ||
715 | _cancel = (_cancel != CancelRequest::None) | ||
716 | ? _cancel | ||
717 | : ((_linda->cancelStatus == Linda::Cancelled) ? CancelRequest::Soft : CancelRequest::None); | ||
718 | // if user wants to cancel, or looped because of a timeout, the call returns without sending anything | ||
719 | if (!_try_again || _cancel != CancelRequest::None) { | ||
720 | _pushed.emplace(0); | ||
721 | break; | ||
722 | } | ||
723 | |||
724 | // all arguments of receive() but the first are passed to the keeper's receive function | ||
725 | STACK_CHECK(_K, 0); | ||
726 | _pushed = keeper_call(_K, _selected_keeper_receive, L_, _linda, _key_i); | ||
727 | if (!_pushed.has_value()) { | ||
728 | break; | ||
729 | } | ||
730 | if (_pushed.value() > 0) { | ||
731 | LUA_ASSERT(L_, _pushed.value() >= _expected_pushed_min && _pushed.value() <= _expected_pushed_max); | ||
732 | if (kRestrictedChannel.equals(L_, StackIndex{ kIdxTop })) { | ||
733 | raise_luaL_error(L_, "Key is restricted"); | ||
734 | } | ||
735 | _linda->readHappened.notify_all(); | ||
736 | break; | ||
737 | } | ||
738 | |||
739 | if (std::chrono::steady_clock::now() >= _until) { | ||
740 | break; /* instant timeout */ | ||
741 | } | ||
742 | |||
743 | // nothing received, wait until timeout or signalled that we should try again | ||
744 | { | ||
745 | Lane::Status _prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings | ||
746 | if (_lane != nullptr) { | ||
747 | // change status of lane to "waiting" | ||
748 | _prev_status = _lane->status.load(std::memory_order_acquire); // Running, most likely | ||
749 | LUA_ASSERT(L_, _prev_status == Lane::Running); // but check, just in case | ||
750 | LUA_ASSERT(L_, _lane->waiting_on == nullptr); | ||
751 | _lane->waiting_on = &_linda->writeHappened; | ||
752 | _lane->status.store(Lane::Waiting, std::memory_order_release); | ||
753 | } | ||
754 | // not enough data to read: wakeup when data was sent, or when timeout is reached | ||
755 | std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; | ||
756 | std::cv_status const _status{ _linda->writeHappened.wait_until(_guard, _until) }; | ||
757 | _guard.release(); // we don't want to unlock the mutex on exit! | ||
758 | _try_again = (_status == std::cv_status::no_timeout); // detect spurious wakeups | ||
759 | if (_lane != nullptr) { | ||
760 | _lane->waiting_on = nullptr; | ||
761 | _lane->status.store(_prev_status, std::memory_order_release); | ||
762 | } | ||
763 | } | ||
764 | } | ||
765 | STACK_CHECK(_K, 0); | ||
766 | |||
767 | if (!_pushed.has_value()) { | ||
768 | raise_luaL_error(L_, "tried to copy unsupported types"); | ||
769 | } | ||
770 | |||
771 | switch (_cancel) { | ||
772 | case CancelRequest::None: | ||
773 | { | ||
774 | int const _nbPushed{ _pushed.value() }; | ||
775 | if (_nbPushed == 0) { | ||
776 | // not enough data in the linda slot to fulfill the request, return nil, "timeout" | ||
777 | lua_pushnil(L_); | ||
778 | luaG_pushstring(L_, "timeout"); | ||
779 | return 2; | ||
780 | } | ||
781 | return _nbPushed; | ||
782 | } | ||
783 | |||
784 | case CancelRequest::Soft: | ||
785 | // if user wants to soft-cancel, the call returns nil, kCancelError | ||
786 | lua_pushnil(L_); | ||
787 | kCancelError.pushKey(L_); | ||
788 | return 2; | ||
789 | 823 | ||
790 | case CancelRequest::Hard: | 824 | // ################################################################################################# |
791 | // raise an error interrupting execution only in case of hard cancel | ||
792 | raise_cancel_error(L_); // raises an error and doesn't return | ||
793 | 825 | ||
794 | default: | 826 | /* |
795 | raise_luaL_error(L_, "internal error: unknown cancel request"); | 827 | * [val1, ... valCOUNT] = linda_receive_batched( linda_ud, [timeout_secs_num=-1], key_num|str|bool|lightuserdata, min_COUNT[, max_COUNT]) |
796 | } | 828 | * Consumes between min_COUNT and max_COUNT values from the linda, from a single slot. |
797 | } | 829 | * returns the actual consumed values, or nil if there weren't enough values to consume |
798 | }; | 830 | */ |
799 | return Linda::ProtectedCall(L_, _receive); | 831 | LUAG_FUNC(linda_receive_batched) |
832 | { | ||
833 | return Linda::ProtectedCall(L_, [](lua_State* const L_) { return ReceiveInternal(L_, true); }); | ||
800 | } | 834 | } |
801 | 835 | ||
802 | // ################################################################################################# | 836 | // ################################################################################################# |
@@ -816,7 +850,7 @@ LUAG_FUNC(linda_restrict) | |||
816 | int const _nargs{ lua_gettop(L_) }; | 850 | int const _nargs{ lua_gettop(L_) }; |
817 | luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments"); | 851 | luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments"); |
818 | // make sure we got a known restrict mode, (or nothing) | 852 | // make sure we got a known restrict mode, (or nothing) |
819 | std::string_view const _mode{ luaG_tostring(L_, StackIndex{ 3 }) }; | 853 | std::string_view const _mode{ luaW_tostring(L_, StackIndex{ 3 }) }; |
820 | if (!_mode.empty() && (_mode != "none" && _mode != "set/get" && _mode != "send/receive")) { | 854 | if (!_mode.empty() && (_mode != "none" && _mode != "set/get" && _mode != "send/receive")) { |
821 | raise_luaL_argerror(L_, StackIndex{ 3 }, "unknown restrict mode"); | 855 | raise_luaL_argerror(L_, StackIndex{ 3 }, "unknown restrict mode"); |
822 | } | 856 | } |
@@ -828,7 +862,7 @@ LUAG_FUNC(linda_restrict) | |||
828 | Keeper* const _keeper{ _linda->whichKeeper() }; | 862 | Keeper* const _keeper{ _linda->whichKeeper() }; |
829 | _pushed = keeper_call(_keeper->K, KEEPER_API(restrict), L_, _linda, StackIndex{ 2 }); | 863 | _pushed = keeper_call(_keeper->K, KEEPER_API(restrict), L_, _linda, StackIndex{ 2 }); |
830 | // we should get a single return value: the string describing the previous restrict mode | 864 | // we should get a single return value: the string describing the previous restrict mode |
831 | LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 1) && luaG_type(L_, kIdxTop) == LuaType::STRING); | 865 | LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 1) && luaW_type(L_, kIdxTop) == LuaType::STRING); |
832 | } else { // linda is cancelled | 866 | } else { // linda is cancelled |
833 | // do nothing and return nil,lanes.cancel_error | 867 | // do nothing and return nil,lanes.cancel_error |
834 | lua_pushnil(L_); | 868 | lua_pushnil(L_); |
@@ -889,6 +923,7 @@ LUAG_FUNC(linda_send) | |||
889 | _cancel = (_cancel != CancelRequest::None) | 923 | _cancel = (_cancel != CancelRequest::None) |
890 | ? _cancel | 924 | ? _cancel |
891 | : ((_linda->cancelStatus == Linda::Cancelled) ? CancelRequest::Soft : CancelRequest::None); | 925 | : ((_linda->cancelStatus == Linda::Cancelled) ? CancelRequest::Soft : CancelRequest::None); |
926 | |||
892 | // if user wants to cancel, or looped because of a timeout, the call returns without sending anything | 927 | // if user wants to cancel, or looped because of a timeout, the call returns without sending anything |
893 | if (!_try_again || _cancel != CancelRequest::None) { | 928 | if (!_try_again || _cancel != CancelRequest::None) { |
894 | _pushed.emplace(0); | 929 | _pushed.emplace(0); |
@@ -921,26 +956,7 @@ LUAG_FUNC(linda_send) | |||
921 | } | 956 | } |
922 | 957 | ||
923 | // storage limit hit, wait until timeout or signalled that we should try again | 958 | // storage limit hit, wait until timeout or signalled that we should try again |
924 | { | 959 | _try_again = WaitInternal(L_, _lane, _linda, _keeper, _linda->readHappened, _until); |
925 | Lane::Status _prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings | ||
926 | if (_lane != nullptr) { | ||
927 | // change status of lane to "waiting" | ||
928 | _prev_status = _lane->status.load(std::memory_order_acquire); // Running, most likely | ||
929 | LUA_ASSERT(L_, _prev_status == Lane::Running); // but check, just in case | ||
930 | LUA_ASSERT(L_, _lane->waiting_on == nullptr); | ||
931 | _lane->waiting_on = &_linda->readHappened; | ||
932 | _lane->status.store(Lane::Waiting, std::memory_order_release); | ||
933 | } | ||
934 | // could not send because no room: wait until some data was read before trying again, or until timeout is reached | ||
935 | std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; | ||
936 | std::cv_status const status{ _linda->readHappened.wait_until(_guard, _until) }; | ||
937 | _guard.release(); // we don't want to unlock the mutex on exit! | ||
938 | _try_again = (status == std::cv_status::no_timeout); // detect spurious wakeups | ||
939 | if (_lane != nullptr) { | ||
940 | _lane->waiting_on = nullptr; | ||
941 | _lane->status.store(_prev_status, std::memory_order_release); | ||
942 | } | ||
943 | } | ||
944 | } | 960 | } |
945 | STACK_CHECK(_K, 0); | 961 | STACK_CHECK(_K, 0); |
946 | 962 | ||
@@ -966,7 +982,7 @@ LUAG_FUNC(linda_send) | |||
966 | } else { | 982 | } else { |
967 | // not enough room in the Linda slot to fulfill the request, return nil, "timeout" | 983 | // not enough room in the Linda slot to fulfill the request, return nil, "timeout" |
968 | lua_pushnil(L_); | 984 | lua_pushnil(L_); |
969 | luaG_pushstring(L_, "timeout"); | 985 | luaW_pushstring(L_, "timeout"); |
970 | return 2; | 986 | return 2; |
971 | } | 987 | } |
972 | } | 988 | } |
@@ -1001,7 +1017,7 @@ LUAG_FUNC(linda_set) | |||
1001 | if (kRestrictedChannel.equals(L_, kIdxTop)) { | 1017 | if (kRestrictedChannel.equals(L_, kIdxTop)) { |
1002 | raise_luaL_error(L_, "Key is restricted"); | 1018 | raise_luaL_error(L_, "Key is restricted"); |
1003 | } | 1019 | } |
1004 | LUA_ASSERT(L_, _pushed.value() == 2 && luaG_type(L_, kIdxTop) == LuaType::STRING && luaG_type(L_, StackIndex{ -2 }) == LuaType::BOOLEAN); | 1020 | LUA_ASSERT(L_, _pushed.value() == 2 && luaW_type(L_, kIdxTop) == LuaType::STRING && luaW_type(L_, StackIndex{ -2 }) == LuaType::BOOLEAN); |
1005 | 1021 | ||
1006 | if (_has_data) { | 1022 | if (_has_data) { |
1007 | // we put some data in the slot, tell readers that they should wake | 1023 | // we put some data in the slot, tell readers that they should wake |
@@ -1063,7 +1079,7 @@ LUAG_FUNC(linda_towatch) | |||
1063 | LUAG_FUNC(linda_wake) | 1079 | LUAG_FUNC(linda_wake) |
1064 | { | 1080 | { |
1065 | Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) }; | 1081 | Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) }; |
1066 | std::string_view const _who{ luaG_optstring(L_, StackIndex{ 2 }, "both") }; | 1082 | std::string_view const _who{ luaW_optstring(L_, StackIndex{ 2 }, "both") }; |
1067 | // make sure we got 2 arguments: the linda and the wake targets | 1083 | // make sure we got 2 arguments: the linda and the wake targets |
1068 | luaL_argcheck(L_, lua_gettop(L_) <= 2, 2, "wrong number of arguments"); | 1084 | luaL_argcheck(L_, lua_gettop(L_) <= 2, 2, "wrong number of arguments"); |
1069 | 1085 | ||
@@ -1102,6 +1118,7 @@ namespace { | |||
1102 | { "get", LG_linda_get }, | 1118 | { "get", LG_linda_get }, |
1103 | { "limit", LG_linda_limit }, | 1119 | { "limit", LG_linda_limit }, |
1104 | { "receive", LG_linda_receive }, | 1120 | { "receive", LG_linda_receive }, |
1121 | { "receive_batched", LG_linda_receive_batched }, | ||
1105 | { "restrict", LG_linda_restrict }, | 1122 | { "restrict", LG_linda_restrict }, |
1106 | { "send", LG_linda_send }, | 1123 | { "send", LG_linda_send }, |
1107 | { "set", LG_linda_set }, | 1124 | { "set", LG_linda_set }, |
@@ -1118,88 +1135,77 @@ namespace { | |||
1118 | // ################################################################################################# | 1135 | // ################################################################################################# |
1119 | 1136 | ||
1120 | /* | 1137 | /* |
1121 | * ud = lanes.linda( [name[,group[,close_handler]]]) | 1138 | * ud = lanes.linda{.name = <string>, .group = <number>, .close_handler = <callable>, .wake_period = <number>} |
1122 | * | 1139 | * |
1123 | * returns a linda object, or raises an error if creation failed | 1140 | * returns a linda object, or raises an error if creation failed |
1124 | */ | 1141 | */ |
1125 | LUAG_FUNC(linda) | 1142 | LUAG_FUNC(linda) |
1126 | { | 1143 | { |
1127 | static constexpr StackIndex kLastArg{ LUA_VERSION_NUM >= 504 ? 3 : 2 }; | 1144 | // unpack the received table on the stack, putting name wake_period group close_handler in that order |
1128 | StackIndex const _top{ lua_gettop(L_) }; | 1145 | StackIndex const _top{ lua_gettop(L_) }; |
1129 | luaL_argcheck(L_, _top <= kLastArg, _top, "too many arguments"); | 1146 | luaL_argcheck(L_, _top <= 1, _top, "too many arguments"); |
1130 | StackIndex _closeHandlerIdx{}; | 1147 | if (_top == 0) { |
1131 | StackIndex _nameIdx{}; | 1148 | lua_settop(L_, 3); // L_: nil nil nil |
1132 | StackIndex _groupIdx{}; | 1149 | } |
1133 | for (StackIndex const _i : std::ranges::iota_view{ StackIndex{ 1 }, StackIndex{ _top + 1 }}) { | 1150 | else if (!lua_istable(L_, kIdxTop)) { |
1134 | switch (luaG_type(L_, _i)) { | 1151 | luaL_argerror(L_, 1, "expecting a table"); |
1152 | } else { | ||
1153 | auto* const _U{ Universe::Get(L_) }; | ||
1154 | lua_getfield(L_, 1, "wake_period"); // L_: {} wake_period | ||
1155 | if (lua_isnil(L_, kIdxTop)) { | ||
1156 | lua_pop(L_, 1); | ||
1157 | lua_pushnumber(L_, _U->lindaWakePeriod.count()); | ||
1158 | } else if (luaW_type(L_, kIdxTop) == LuaType::STRING) { | ||
1159 | if (luaW_tostring(L_, kIdxTop) != "never") { | ||
1160 | luaL_argerror(L_, 1, "invalid wake_period"); | ||
1161 | } else { | ||
1162 | lua_pop(L_, 1); | ||
1163 | lua_pushnumber(L_, 0); | ||
1164 | } | ||
1165 | } | ||
1166 | else { | ||
1167 | luaL_argcheck(L_, luaL_optnumber(L_, 2, 0) > 0, 1, "wake_period must be > 0"); | ||
1168 | } | ||
1169 | |||
1170 | lua_getfield(L_, 1, "group"); // L_: {} wake_period group | ||
1171 | int const _nbKeepers{ _U->keepers.getNbKeepers() }; | ||
1172 | if (lua_isnil(L_, kIdxTop)) { | ||
1173 | luaL_argcheck(L_, _nbKeepers < 2, 0, "Group is mandatory in multiple Keeper scenarios"); | ||
1174 | } else { | ||
1175 | int const _group{ static_cast<int>(lua_tointeger(L_, kIdxTop)) }; | ||
1176 | luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, 1, "group out of range"); | ||
1177 | } | ||
1178 | |||
1135 | #if LUA_VERSION_NUM >= 504 // to-be-closed support starts with Lua 5.4 | 1179 | #if LUA_VERSION_NUM >= 504 // to-be-closed support starts with Lua 5.4 |
1136 | case LuaType::FUNCTION: | 1180 | lua_getfield(L_, 1, "close_handler"); // L_: {} wake_period group close_handler |
1137 | luaL_argcheck(L_, _closeHandlerIdx == 0, _i, "More than one __close handler"); | 1181 | LuaType const _handlerType{ luaW_type(L_, kIdxTop) }; |
1138 | _closeHandlerIdx = _i; | 1182 | if (_handlerType == LuaType::NIL) { |
1139 | break; | 1183 | lua_pop(L_, 1); // L_: {} wake_period group |
1140 | 1184 | } else if (_handlerType == LuaType::USERDATA || _handlerType == LuaType::TABLE) { | |
1141 | case LuaType::USERDATA: | 1185 | luaL_argcheck(L_, luaL_getmetafield(L_, kIdxTop, "__call") != 0, 1, "__close handler is not callable"); |
1142 | case LuaType::TABLE: | ||
1143 | luaL_argcheck(L_, _closeHandlerIdx == 0, _i, "More than one __close handler"); | ||
1144 | luaL_argcheck(L_, luaL_getmetafield(L_, _i, "__call") != 0, _i, "__close handler is not callable"); | ||
1145 | lua_pop(L_, 1); // luaL_getmetafield() pushed the field, we need to pop it | 1186 | lua_pop(L_, 1); // luaL_getmetafield() pushed the field, we need to pop it |
1146 | _closeHandlerIdx = _i; | 1187 | } else { |
1147 | break; | 1188 | luaL_argcheck(L_, _handlerType == LuaType::FUNCTION, 1, "__close handler is not a function"); |
1189 | } | ||
1148 | #endif // LUA_VERSION_NUM >= 504 | 1190 | #endif // LUA_VERSION_NUM >= 504 |
1149 | 1191 | ||
1150 | case LuaType::STRING: | 1192 | auto const _nameType{ luaW_getfield(L_, StackIndex{ 1 }, "name") }; // L_: {} wake_period group [close_handler] name |
1151 | luaL_argcheck(L_, _nameIdx == 0, _i, "More than one name"); | 1193 | luaL_argcheck(L_, _nameType == LuaType::NIL || _nameType == LuaType::STRING, 1, "name is not a string"); |
1152 | _nameIdx = _i; | 1194 | lua_replace(L_, 1); // L_: name wake_period group [close_handler] |
1153 | break; | ||
1154 | |||
1155 | case LuaType::NUMBER: | ||
1156 | luaL_argcheck(L_, _groupIdx == 0, _i, "More than one group"); | ||
1157 | _groupIdx = _i; | ||
1158 | break; | ||
1159 | |||
1160 | default: | ||
1161 | luaL_argcheck(L_, false, _i, "Bad argument type (should be a string, a number, or a callable type)"); | ||
1162 | } | ||
1163 | } | ||
1164 | |||
1165 | int const _nbKeepers{ Universe::Get(L_)->keepers.getNbKeepers() }; | ||
1166 | if (!_groupIdx) { | ||
1167 | luaL_argcheck(L_, _nbKeepers < 2, 0, "Group is mandatory in multiple Keeper scenarios"); | ||
1168 | } else { | ||
1169 | int const _group{ static_cast<int>(lua_tointeger(L_, _groupIdx)) }; | ||
1170 | luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, _groupIdx, "Group out of range"); | ||
1171 | } | 1195 | } |
1172 | 1196 | ||
1173 | // done with argument checking, let's proceed | 1197 | // done with argument checking, let's proceed |
1174 | if constexpr (LUA_VERSION_NUM >= 504) { | 1198 | if (lua_gettop(L_) == 4) { |
1175 | // make sure we have kMaxArgs arguments on the stack for processing, with name, group, and handler, in that order | ||
1176 | lua_settop(L_, kLastArg); // L_: a b c | ||
1177 | // If either index is 0, lua_settop() adjusted the stack with a nil in slot kLastArg | ||
1178 | lua_pushvalue(L_, _closeHandlerIdx ? _closeHandlerIdx : kLastArg); // L_: a b c close_handler | ||
1179 | lua_pushvalue(L_, _groupIdx ? _groupIdx : kLastArg); // L_: a b c close_handler group | ||
1180 | lua_pushvalue(L_, _nameIdx ? _nameIdx : kLastArg); // L_: a b c close_handler group name | ||
1181 | lua_replace(L_, 1); // L_: name b c close_handler group | ||
1182 | lua_replace(L_, 2); // L_: name group c close_handler | ||
1183 | lua_replace(L_, 3); // L_: name group close_handler | ||
1184 | |||
1185 | // if we have a __close handler, we need a uservalue slot to store it | 1199 | // if we have a __close handler, we need a uservalue slot to store it |
1186 | UserValueCount const _nuv{ _closeHandlerIdx ? 1 : 0 }; | 1200 | LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, UserValueCount{ 1 }); // L_: name wake_period group [close_handler] linda |
1187 | LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, _nuv); // L_: name group close_handler linda | 1201 | lua_replace(L_, 3); // L_: name wake_period linda close_handler |
1188 | if (_closeHandlerIdx != 0) { | 1202 | lua_setiuservalue(L_, StackIndex{ 3 }, UserValueIndex{ 1 }); // L_: name wake_period linda |
1189 | lua_replace(L_, 2); // L_: name linda close_handler | ||
1190 | lua_setiuservalue(L_, StackIndex{ 2 }, UserValueIndex{ 1 }); // L_: name linda | ||
1191 | } | ||
1192 | // depending on whether we have a handler or not, the stack is not in the same state at this point | 1203 | // depending on whether we have a handler or not, the stack is not in the same state at this point |
1193 | // just make sure we have our Linda at the top | 1204 | // just make sure we have our Linda at the top |
1194 | LUA_ASSERT(L_, ToLinda<true>(L_, kIdxTop)); | 1205 | LUA_ASSERT(L_, ToLinda<true>(L_, kIdxTop)); |
1195 | return 1; | 1206 | return 1; |
1196 | } else { // no to-be-closed support | 1207 | } else { // no to-be-closed support |
1197 | // ensure we have name, group in that order on the stack | 1208 | LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, UserValueCount{ 0 }); // L_: name wake_period group linda |
1198 | if (_nameIdx > _groupIdx) { | ||
1199 | lua_insert(L_, 1); // L_: name group | ||
1200 | } | ||
1201 | LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, UserValueCount{ 0 }); // L_: name group linda | ||
1202 | return 1; | 1209 | return 1; |
1203 | } | 1210 | } |
1204 | |||
1205 | } | 1211 | } |