aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/linda.cpp741
-rw-r--r--src/lindafactory.cpp3
-rw-r--r--src/nameof.cpp1
-rw-r--r--src/state.cpp14
-rw-r--r--src/tools.cpp12
-rw-r--r--src/tools.h2
6 files changed, 398 insertions, 375 deletions
diff --git a/src/linda.cpp b/src/linda.cpp
index 1933b06..4edc029 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -38,75 +38,86 @@ THE SOFTWARE.
38#include "tools.h" 38#include "tools.h"
39 39
40// ################################################################################################# 40// #################################################################################################
41// #################################################################################################
42namespace {
43 // #############################################################################################
44 // #############################################################################################
41 45
42static void check_key_types(lua_State* const L_, int const start_, int const end_)
43{
44 for (int const _i : std::ranges::iota_view{ start_, end_ + 1 }) {
45 switch (LuaType const _t{ luaG_type(L_, _i) }) {
46 case LuaType::BOOLEAN:
47 case LuaType::NUMBER:
48 case LuaType::STRING:
49 break;
50 46
51 case LuaType::LIGHTUSERDATA: 47 static void CheckKeyTypes(lua_State* const L_, int const start_, int const end_)
52 { 48 {
53 static constexpr std::array<std::reference_wrapper<UniqueKey const>, 3> kKeysToCheck{ kLindaBatched, kCancelError, kNilSentinel }; 49 for (int const _i : std::ranges::iota_view{ start_, end_ + 1 }) {
54 for (UniqueKey const& _key : kKeysToCheck) { 50 switch (LuaType const _t{ luaG_type(L_, _i) }) {
55 if (_key.equals(L_, _i)) { 51 case LuaType::BOOLEAN:
56 raise_luaL_error(L_, "argument #%d: can't use %s as a key", _i, _key.debugName.data()); 52 case LuaType::NUMBER:
57 break; 53 case LuaType::STRING:
54 break;
55
56 case LuaType::LIGHTUSERDATA:
57 {
58 static constexpr std::array<std::reference_wrapper<UniqueKey const>, 3> kKeysToCheck{ kLindaBatched, kCancelError, kNilSentinel };
59 for (UniqueKey const& _key : kKeysToCheck) {
60 if (_key.equals(L_, _i)) {
61 raise_luaL_error(L_, "argument #%d: can't use %s as a key", _i, _key.debugName.data());
62 break;
63 }
58 } 64 }
59 } 65 }
66 break;
67
68 default:
69 raise_luaL_error(L_, "argument #%d: invalid key type (not a boolean, string, number or light userdata)", _i);
60 } 70 }
61 break; 71 }
72 }
62 73
63 default: 74 // #############################################################################################
64 raise_luaL_error(L_, "argument #%d: invalid key type (not a boolean, string, number or light userdata)", _i); 75
76 /*
77 * string = linda:__tostring( linda_ud)
78 *
79 * Return the stringification of a linda
80 *
81 * Useful for concatenation or debugging purposes
82 */
83
84 template <bool OPT>
85 [[nodiscard]] static int LindaToString(lua_State* const L_, int const idx_)
86 {
87 Linda* const _linda{ ToLinda<OPT>(L_, idx_) };
88 if (_linda != nullptr) {
89 luaG_pushstring(L_, "Linda: ");
90 std::string_view const _lindaName{ _linda->getName() };
91 if (!_lindaName.empty()) {
92 luaG_pushstring(L_, _lindaName);
93 } else {
94 // obfuscate the pointer so that we can't read the value with our eyes out of a script
95 luaG_pushstring(L_, "%p", _linda->obfuscated());
96 }
97 lua_concat(L_, 2);
98 return 1;
65 } 99 }
100 return 0;
66 } 101 }
67}
68 102
69// ################################################################################################# 103 // #############################################################################################
70 104
71/* 105 template <bool OPT>
72 * string = linda:__tostring( linda_ud) 106 [[nodiscard]] static inline Linda* ToLinda(lua_State* const L_, int const idx_)
73 * 107 {
74 * Return the stringification of a linda 108 Linda* const _linda{ static_cast<Linda*>(LindaFactory::Instance.toDeep(L_, idx_)) };
75 * 109 if constexpr (!OPT) {
76 * Useful for concatenation or debugging purposes 110 luaL_argcheck(L_, _linda != nullptr, idx_, "expecting a linda object"); // doesn't return if linda is nullptr
77 */ 111 LUA_ASSERT(L_, _linda->U == Universe::Get(L_));
78
79template <bool OPT>
80[[nodiscard]] static int LindaToString(lua_State* L_, int idx_)
81{
82 Linda* const _linda{ ToLinda<OPT>(L_, idx_) };
83 if (_linda != nullptr) {
84 luaG_pushstring(L_, "Linda: ");
85 std::string_view const _lindaName{ _linda->getName() };
86 if (!_lindaName.empty()) {
87 luaG_pushstring(L_, _lindaName);
88 } else {
89 // obfuscate the pointer so that we can't read the value with our eyes out of a script
90 luaG_pushstring(L_, "%p", _linda->obfuscated());
91 } 112 }
92 lua_concat(L_, 2); 113 return _linda;
93 return 1;
94 } 114 }
95 return 0;
96}
97 115
116 // #############################################################################################
117 // #############################################################################################
118} // namespace
119// #################################################################################################
98// ################################################################################################# 120// #################################################################################################
99
100template <bool OPT>
101[[nodiscard]] static inline Linda* ToLinda(lua_State* L_, int idx_)
102{
103 Linda* const _linda{ static_cast<Linda*>(LindaFactory::Instance.toDeep(L_, idx_)) };
104 if constexpr (!OPT) {
105 luaL_argcheck(L_, _linda != nullptr, idx_, "expecting a linda object"); // doesn't return if linda is nullptr
106 LUA_ASSERT(L_, _linda->U == Universe::Get(L_));
107 }
108 return _linda;
109}
110 121
111// ################################################################################################# 122// #################################################################################################
112// ################################################################################################# 123// #################################################################################################
@@ -114,7 +125,7 @@ template <bool OPT>
114// ################################################################################################# 125// #################################################################################################
115// ################################################################################################# 126// #################################################################################################
116 127
117Linda::Linda(Universe* U_, LindaGroup group_, std::string_view const& name_) 128Linda::Linda(Universe* const U_, LindaGroup const group_, std::string_view const& name_)
118: DeepPrelude{ LindaFactory::Instance } 129: DeepPrelude{ LindaFactory::Instance }
119, U{ U_ } 130, U{ U_ }
120, keeperIndex{ group_ % U_->keepers.getNbKeepers() } 131, keeperIndex{ group_ % U_->keepers.getNbKeepers() }
@@ -170,7 +181,7 @@ std::string_view Linda::getName() const
170// ################################################################################################# 181// #################################################################################################
171 182
172// used to perform all linda operations that access keepers 183// used to perform all linda operations that access keepers
173int Linda::ProtectedCall(lua_State* L_, lua_CFunction f_) 184int Linda::ProtectedCall(lua_State* const L_, lua_CFunction const f_)
174{ 185{
175 Linda* const _linda{ ToLinda<false>(L_, 1) }; 186 Linda* const _linda{ ToLinda<false>(L_, 1) };
176 187
@@ -344,14 +355,16 @@ LUAG_FUNC(linda_concat)
344 */ 355 */
345LUAG_FUNC(linda_count) 356LUAG_FUNC(linda_count)
346{ 357{
347 auto _count = [](lua_State* L_) { 358 static constexpr lua_CFunction _count{
348 Linda* const _linda{ ToLinda<false>(L_, 1) }; 359 +[](lua_State* const L_) {
349 // make sure the keys are of a valid type 360 Linda* const _linda{ ToLinda<false>(L_, 1) };
350 check_key_types(L_, 2, lua_gettop(L_)); 361 // make sure the keys are of a valid type
351 362 CheckKeyTypes(L_, 2, lua_gettop(L_));
352 Keeper* const _keeper{ _linda->whichKeeper() }; 363
353 KeeperCallResult const _pushed{ keeper_call(_keeper->K, KEEPER_API(count), L_, _linda, 2) }; 364 Keeper* const _keeper{ _linda->whichKeeper() };
354 return OptionalValue(_pushed, L_, "tried to count an invalid key"); 365 KeeperCallResult const _pushed{ keeper_call(_keeper->K, KEEPER_API(count), L_, _linda, 2) };
366 return OptionalValue(_pushed, L_, "tried to count an invalid key");
367 }
355 }; 368 };
356 return Linda::ProtectedCall(L_, _count); 369 return Linda::ProtectedCall(L_, _count);
357} 370}
@@ -383,9 +396,11 @@ LUAG_FUNC(linda_deep)
383 */ 396 */
384LUAG_FUNC(linda_dump) 397LUAG_FUNC(linda_dump)
385{ 398{
386 auto _dump = [](lua_State* L_) { 399 static constexpr lua_CFunction _dump{
387 Linda* const _linda{ ToLinda<false>(L_, 1) }; 400 +[](lua_State* const L_) {
388 return Keeper::PushLindaStorage(*_linda, DestState{ L_ }); 401 Linda* const _linda{ ToLinda<false>(L_, 1) };
402 return Keeper::PushLindaStorage(*_linda, DestState{ L_ });
403 }
389 }; 404 };
390 return Linda::ProtectedCall(L_, _dump); 405 return Linda::ProtectedCall(L_, _dump);
391} 406}
@@ -399,28 +414,30 @@ LUAG_FUNC(linda_dump)
399 */ 414 */
400LUAG_FUNC(linda_get) 415LUAG_FUNC(linda_get)
401{ 416{
402 auto get = [](lua_State* L_) { 417 static constexpr lua_CFunction _get{
403 Linda* const _linda{ ToLinda<false>(L_, 1) }; 418 +[](lua_State* const L_) {
404 lua_Integer const _count{ luaL_optinteger(L_, 3, 1) }; 419 Linda* const _linda{ ToLinda<false>(L_, 1) };
405 luaL_argcheck(L_, _count >= 1, 3, "count should be >= 1"); 420 lua_Integer const _count{ luaL_optinteger(L_, 3, 1) };
406 luaL_argcheck(L_, lua_gettop(L_) <= 3, 4, "too many arguments"); 421 luaL_argcheck(L_, _count >= 1, 3, "count should be >= 1");
407 // make sure the key is of a valid type (throws an error if not the case) 422 luaL_argcheck(L_, lua_gettop(L_) <= 3, 4, "too many arguments");
408 check_key_types(L_, 2, 2); 423 // make sure the key is of a valid type (throws an error if not the case)
409 424 CheckKeyTypes(L_, 2, 2);
410 KeeperCallResult _pushed; 425
411 if (_linda->cancelRequest == CancelRequest::None) { 426 KeeperCallResult _pushed;
412 Keeper* const _keeper{ _linda->whichKeeper() }; 427 if (_linda->cancelRequest == CancelRequest::None) {
413 _pushed = keeper_call(_keeper->K, KEEPER_API(get), L_, _linda, 2); 428 Keeper* const _keeper{ _linda->whichKeeper() };
414 } else { // linda is cancelled 429 _pushed = keeper_call(_keeper->K, KEEPER_API(get), L_, _linda, 2);
415 // do nothing and return nil,lanes.cancel_error 430 } else { // linda is cancelled
416 lua_pushnil(L_); 431 // do nothing and return nil,lanes.cancel_error
417 kCancelError.pushKey(L_); 432 lua_pushnil(L_);
418 _pushed.emplace(2); 433 kCancelError.pushKey(L_);
434 _pushed.emplace(2);
435 }
436 // an error can be raised if we attempt to read an unregistered function
437 return OptionalValue(_pushed, L_, "tried to copy unsupported types");
419 } 438 }
420 // an error can be raised if we attempt to read an unregistered function
421 return OptionalValue(_pushed, L_, "tried to copy unsupported types");
422 }; 439 };
423 return Linda::ProtectedCall(L_, get); 440 return Linda::ProtectedCall(L_, _get);
424} 441}
425 442
426// ################################################################################################# 443// #################################################################################################
@@ -434,35 +451,37 @@ LUAG_FUNC(linda_get)
434 */ 451 */
435LUAG_FUNC(linda_limit) 452LUAG_FUNC(linda_limit)
436{ 453{
437 auto _limit = [](lua_State* L_) { 454 static constexpr lua_CFunction _limit{
438 Linda* const _linda{ ToLinda<false>(L_, 1) }; 455 +[](lua_State* const L_) {
439 // make sure we got 3 arguments: the linda, a key and a limit 456 Linda* const _linda{ ToLinda<false>(L_, 1) };
440 int const _nargs{ lua_gettop(L_) }; 457 // make sure we got 3 arguments: the linda, a key and a limit
441 luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments"); 458 int const _nargs{ lua_gettop(L_) };
442 // make sure we got a numeric limit 459 luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments");
443 lua_Integer const _limit{ luaL_optinteger(L_, 3, 0) }; 460 // make sure we got a numeric limit
444 if (_limit < 0) { 461 lua_Integer const _val{ luaL_optinteger(L_, 3, 0) };
445 raise_luaL_argerror(L_, 3, "limit must be >= 0"); 462 if (_val < 0) {
446 } 463 raise_luaL_argerror(L_, 3, "limit must be >= 0");
447 // make sure the key is of a valid type
448 check_key_types(L_, 2, 2);
449
450 KeeperCallResult _pushed;
451 if (_linda->cancelRequest == CancelRequest::None) {
452 Keeper* const _keeper{ _linda->whichKeeper() };
453 _pushed = keeper_call(_keeper->K, KEEPER_API(limit), L_, _linda, 2);
454 LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 1) && luaG_type(L_, -1) == LuaType::BOOLEAN); // no error, boolean value saying if we should wake blocked writer threads
455 if (lua_toboolean(L_, -1)) {
456 _linda->readHappened.notify_all(); // To be done from within the 'K' locking area
457 } 464 }
458 } else { // linda is cancelled 465 // make sure the key is of a valid type
459 // do nothing and return nil,lanes.cancel_error 466 CheckKeyTypes(L_, 2, 2);
460 lua_pushnil(L_); 467
461 kCancelError.pushKey(L_); 468 KeeperCallResult _pushed;
462 _pushed.emplace(2); 469 if (_linda->cancelRequest == CancelRequest::None) {
470 Keeper* const _keeper{ _linda->whichKeeper() };
471 _pushed = keeper_call(_keeper->K, KEEPER_API(limit), L_, _linda, 2);
472 LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 1) && luaG_type(L_, -1) == LuaType::BOOLEAN); // no error, boolean value saying if we should wake blocked writer threads
473 if (lua_toboolean(L_, -1)) {
474 _linda->readHappened.notify_all(); // To be done from within the 'K' locking area
475 }
476 } else { // linda is cancelled
477 // do nothing and return nil,lanes.cancel_error
478 lua_pushnil(L_);
479 kCancelError.pushKey(L_);
480 _pushed.emplace(2);
481 }
482 // propagate pushed boolean if any
483 return _pushed.value();
463 } 484 }
464 // propagate pushed boolean if any
465 return _pushed.value();
466 }; 485 };
467 return Linda::ProtectedCall(L_, _limit); 486 return Linda::ProtectedCall(L_, _limit);
468} 487}
@@ -481,197 +500,63 @@ LUAG_FUNC(linda_limit)
481 */ 500 */
482LUAG_FUNC(linda_receive) 501LUAG_FUNC(linda_receive)
483{ 502{
484 auto _receive = [](lua_State* L_) { 503 static constexpr lua_CFunction _receive{
485 Linda* const _linda{ ToLinda<false>(L_, 1) }; 504 +[](lua_State* const L_) {
486 int _key_i{ 2 }; // index of first key, if timeout not there 505 Linda* const _linda{ ToLinda<false>(L_, 1) };
487 506 int _key_i{ 2 }; // index of first key, if timeout not there
488 std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; 507
489 if (luaG_type(L_, 2) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion 508 std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
490 lua_Duration const _duration{ lua_tonumber(L_, 2) }; 509 if (luaG_type(L_, 2) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion
491 if (_duration.count() >= 0.0) { 510 lua_Duration const _duration{ lua_tonumber(L_, 2) };
492 _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(_duration); 511 if (_duration.count() >= 0.0) {
493 } else { 512 _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(_duration);
494 raise_luaL_argerror(L_, 2, "duration cannot be < 0"); 513 } else {
495 } 514 raise_luaL_argerror(L_, 2, "duration cannot be < 0");
496 ++_key_i;
497 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
498 ++_key_i;
499 }
500
501 keeper_api_t _selected_keeper_receive{ nullptr };
502 int _expected_pushed_min{ 0 }, _expected_pushed_max{ 0 };
503 // are we in batched mode?
504 if (kLindaBatched.equals(L_, _key_i)) {
505 // no need to pass linda.batched in the keeper state
506 ++_key_i;
507 // make sure the keys are of a valid type
508 check_key_types(L_, _key_i, _key_i);
509 // receive multiple values from a single slot
510 _selected_keeper_receive = KEEPER_API(receive_batched);
511 // we expect a user-defined amount of return value
512 _expected_pushed_min = (int) luaL_checkinteger(L_, _key_i + 1);
513 if (_expected_pushed_min < 1) {
514 raise_luaL_argerror(L_, _key_i + 1, "bad min count");
515 }
516 _expected_pushed_max = (int) luaL_optinteger(L_, _key_i + 2, _expected_pushed_min);
517 // don't forget to count the key in addition to the values
518 ++_expected_pushed_min;
519 ++_expected_pushed_max;
520 if (_expected_pushed_min > _expected_pushed_max) {
521 raise_luaL_argerror(L_, _key_i + 2, "batched min/max error");
522 }
523 } else {
524 // make sure the keys are of a valid type
525 check_key_types(L_, _key_i, lua_gettop(L_));
526 // receive a single value, checking multiple slots
527 _selected_keeper_receive = KEEPER_API(receive);
528 // we expect a single (value, key) pair of returned values
529 _expected_pushed_min = _expected_pushed_max = 2;
530 }
531
532 Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) };
533 Keeper* const _keeper{ _linda->whichKeeper() };
534 KeeperState const _K{ _keeper ? _keeper->K : nullptr };
535 if (_K == nullptr)
536 return 0;
537
538 CancelRequest _cancel{ CancelRequest::None };
539 KeeperCallResult _pushed{};
540 STACK_CHECK_START_REL(_K, 0);
541 for (bool _try_again{ true };;) {
542 if (_lane != nullptr) {
543 _cancel = _lane->cancelRequest;
544 }
545 _cancel = (_cancel != CancelRequest::None) ? _cancel : _linda->cancelRequest;
546 // if user wants to cancel, or looped because of a timeout, the call returns without sending anything
547 if (!_try_again || _cancel != CancelRequest::None) {
548 _pushed.emplace(0);
549 break;
550 }
551
552 // all arguments of receive() but the first are passed to the keeper's receive function
553 _pushed = keeper_call(_K, _selected_keeper_receive, L_, _linda, _key_i);
554 if (!_pushed.has_value()) {
555 break;
556 }
557 if (_pushed.value() > 0) {
558 LUA_ASSERT(L_, _pushed.value() >= _expected_pushed_min && _pushed.value() <= _expected_pushed_max);
559 _linda->readHappened.notify_all();
560 break;
561 }
562
563 if (std::chrono::steady_clock::now() >= _until) {
564 break; /* instant timeout */
565 }
566
567 // nothing received, wait until timeout or signalled that we should try again
568 {
569 Lane::Status _prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings
570 if (_lane != nullptr) {
571 // change status of lane to "waiting"
572 _prev_status = _lane->status; // Running, most likely
573 LUA_ASSERT(L_, _prev_status == Lane::Running); // but check, just in case
574 _lane->status = Lane::Waiting;
575 LUA_ASSERT(L_, _lane->waiting_on == nullptr);
576 _lane->waiting_on = &_linda->writeHappened;
577 }
578 // not enough data to read: wakeup when data was sent, or when timeout is reached
579 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock };
580 std::cv_status const _status{ _linda->writeHappened.wait_until(_guard, _until) };
581 _guard.release(); // we don't want to unlock the mutex on exit!
582 _try_again = (_status == std::cv_status::no_timeout); // detect spurious wakeups
583 if (_lane != nullptr) {
584 _lane->waiting_on = nullptr;
585 _lane->status = _prev_status;
586 } 515 }
516 ++_key_i;
517 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
518 ++_key_i;
587 } 519 }
588 }
589 STACK_CHECK(_K, 0);
590
591 if (!_pushed.has_value()) {
592 raise_luaL_error(L_, "tried to copy unsupported types");
593 }
594 520
595 switch (_cancel) { 521 keeper_api_t _selected_keeper_receive{ nullptr };
596 case CancelRequest::None: 522 int _expected_pushed_min{ 0 }, _expected_pushed_max{ 0 };
597 { 523 // are we in batched mode?
598 int const _nbPushed{ _pushed.value() }; 524 if (kLindaBatched.equals(L_, _key_i)) {
599 if (_nbPushed == 0) { 525 // no need to pass linda.batched in the keeper state
600 // not enough data in the linda slot to fulfill the request, return nil, "timeout" 526 ++_key_i;
601 lua_pushnil(L_); 527 // make sure the keys are of a valid type
602 luaG_pushstring(L_, "timeout"); 528 CheckKeyTypes(L_, _key_i, _key_i);
603 return 2; 529 // receive multiple values from a single slot
530 _selected_keeper_receive = KEEPER_API(receive_batched);
531 // we expect a user-defined amount of return value
532 _expected_pushed_min = (int) luaL_checkinteger(L_, _key_i + 1);
533 if (_expected_pushed_min < 1) {
534 raise_luaL_argerror(L_, _key_i + 1, "bad min count");
535 }
536 _expected_pushed_max = (int) luaL_optinteger(L_, _key_i + 2, _expected_pushed_min);
537 // don't forget to count the key in addition to the values
538 ++_expected_pushed_min;
539 ++_expected_pushed_max;
540 if (_expected_pushed_min > _expected_pushed_max) {
541 raise_luaL_argerror(L_, _key_i + 2, "batched min/max error");
604 } 542 }
605 return _nbPushed;
606 }
607
608 case CancelRequest::Soft:
609 // if user wants to soft-cancel, the call returns nil, kCancelError
610 lua_pushnil(L_);
611 kCancelError.pushKey(L_);
612 return 2;
613
614 case CancelRequest::Hard:
615 // raise an error interrupting execution only in case of hard cancel
616 raise_cancel_error(L_); // raises an error and doesn't return
617
618 default:
619 raise_luaL_error(L_, "internal error: unknown cancel request");
620 }
621 };
622 return Linda::ProtectedCall(L_, _receive);
623}
624
625// #################################################################################################
626
627/*
628 * bool= linda:linda_send([timeout_secs=nil,] key_num|str|bool|lightuserdata, ...)
629 *
630 * Send one or more values to a Linda. If there is a limit, all values must fit.
631 *
632 * Returns: 'true' if the value was queued
633 * 'false' for timeout (only happens when the queue size is limited)
634 * nil, kCancelError if cancelled
635 */
636LUAG_FUNC(linda_send)
637{
638 auto _send = [](lua_State* L_) {
639 Linda* const _linda{ ToLinda<false>(L_, 1) };
640 int _key_i{ 2 }; // index of first key, if timeout not there
641
642 std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
643 if (luaG_type(L_, 2) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion
644 lua_Duration const _duration{ lua_tonumber(L_, 2) };
645 if (_duration.count() >= 0.0) {
646 _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(_duration);
647 } else { 543 } else {
648 raise_luaL_argerror(L_, 2, "duration cannot be < 0"); 544 // make sure the keys are of a valid type
545 CheckKeyTypes(L_, _key_i, lua_gettop(L_));
546 // receive a single value, checking multiple slots
547 _selected_keeper_receive = KEEPER_API(receive);
548 // we expect a single (value, key) pair of returned values
549 _expected_pushed_min = _expected_pushed_max = 2;
649 } 550 }
650 ++_key_i;
651 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
652 ++_key_i;
653 }
654
655 // make sure the key is of a valid type
656 check_key_types(L_, _key_i, _key_i);
657
658 STACK_GROW(L_, 1);
659 551
660 // make sure there is something to send
661 if (lua_gettop(L_) == _key_i) {
662 raise_luaL_error(L_, "no data to send");
663 }
664
665 bool _ret{ false };
666 CancelRequest _cancel{ CancelRequest::None };
667 KeeperCallResult _pushed;
668 {
669 Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) }; 552 Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) };
670 Keeper* const _keeper{ _linda->whichKeeper() }; 553 Keeper* const _keeper{ _linda->whichKeeper() };
671 KeeperState const _K{ _keeper ? _keeper->K : nullptr }; 554 KeeperState const _K{ _keeper ? _keeper->K : nullptr };
672 if (_K == nullptr) 555 if (_K == nullptr)
673 return 0; 556 return 0;
674 557
558 CancelRequest _cancel{ CancelRequest::None };
559 KeeperCallResult _pushed{};
675 STACK_CHECK_START_REL(_K, 0); 560 STACK_CHECK_START_REL(_K, 0);
676 for (bool _try_again{ true };;) { 561 for (bool _try_again{ true };;) {
677 if (_lane != nullptr) { 562 if (_lane != nullptr) {
@@ -684,28 +569,22 @@ LUAG_FUNC(linda_send)
684 break; 569 break;
685 } 570 }
686 571
687 STACK_CHECK(_K, 0); 572 // all arguments of receive() but the first are passed to the keeper's receive function
688 _pushed = keeper_call(_K, KEEPER_API(send), L_, _linda, _key_i); 573 _pushed = keeper_call(_K, _selected_keeper_receive, L_, _linda, _key_i);
689 if (!_pushed.has_value()) { 574 if (!_pushed.has_value()) {
690 break; 575 break;
691 } 576 }
692 LUA_ASSERT(L_, _pushed.value() == 1); 577 if (_pushed.value() > 0) {
693 578 LUA_ASSERT(L_, _pushed.value() >= _expected_pushed_min && _pushed.value() <= _expected_pushed_max);
694 _ret = lua_toboolean(L_, -1) ? true : false; 579 _linda->readHappened.notify_all();
695 lua_pop(L_, 1);
696
697 if (_ret) {
698 // Wake up ALL waiting threads
699 _linda->writeHappened.notify_all();
700 break; 580 break;
701 } 581 }
702 582
703 // instant timout to bypass the wait syscall
704 if (std::chrono::steady_clock::now() >= _until) { 583 if (std::chrono::steady_clock::now() >= _until) {
705 break; /* no wait; instant timeout */ 584 break; /* instant timeout */
706 } 585 }
707 586
708 // storage limit hit, wait until timeout or signalled that we should try again 587 // nothing received, wait until timeout or signalled that we should try again
709 { 588 {
710 Lane::Status _prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings 589 Lane::Status _prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings
711 if (_lane != nullptr) { 590 if (_lane != nullptr) {
@@ -714,13 +593,13 @@ LUAG_FUNC(linda_send)
714 LUA_ASSERT(L_, _prev_status == Lane::Running); // but check, just in case 593 LUA_ASSERT(L_, _prev_status == Lane::Running); // but check, just in case
715 _lane->status = Lane::Waiting; 594 _lane->status = Lane::Waiting;
716 LUA_ASSERT(L_, _lane->waiting_on == nullptr); 595 LUA_ASSERT(L_, _lane->waiting_on == nullptr);
717 _lane->waiting_on = &_linda->readHappened; 596 _lane->waiting_on = &_linda->writeHappened;
718 } 597 }
719 // could not send because no room: wait until some data was read before trying again, or until timeout is reached 598 // not enough data to read: wakeup when data was sent, or when timeout is reached
720 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; 599 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock };
721 std::cv_status const status{ _linda->readHappened.wait_until(_guard, _until) }; 600 std::cv_status const _status{ _linda->writeHappened.wait_until(_guard, _until) };
722 _guard.release(); // we don't want to unlock the mutex on exit! 601 _guard.release(); // we don't want to unlock the mutex on exit!
723 _try_again = (status == std::cv_status::no_timeout); // detect spurious wakeups 602 _try_again = (_status == std::cv_status::no_timeout); // detect spurious wakeups
724 if (_lane != nullptr) { 603 if (_lane != nullptr) {
725 _lane->waiting_on = nullptr; 604 _lane->waiting_on = nullptr;
726 _lane->status = _prev_status; 605 _lane->status = _prev_status;
@@ -728,32 +607,176 @@ LUAG_FUNC(linda_send)
728 } 607 }
729 } 608 }
730 STACK_CHECK(_K, 0); 609 STACK_CHECK(_K, 0);
731 }
732 610
733 if (!_pushed.has_value()) { 611 if (!_pushed.has_value()) {
734 raise_luaL_error(L_, "tried to copy unsupported types"); 612 raise_luaL_error(L_, "tried to copy unsupported types");
613 }
614
615 switch (_cancel) {
616 case CancelRequest::None:
617 {
618 int const _nbPushed{ _pushed.value() };
619 if (_nbPushed == 0) {
620 // not enough data in the linda slot to fulfill the request, return nil, "timeout"
621 lua_pushnil(L_);
622 luaG_pushstring(L_, "timeout");
623 return 2;
624 }
625 return _nbPushed;
626 }
627
628 case CancelRequest::Soft:
629 // if user wants to soft-cancel, the call returns nil, kCancelError
630 lua_pushnil(L_);
631 kCancelError.pushKey(L_);
632 return 2;
633
634 case CancelRequest::Hard:
635 // raise an error interrupting execution only in case of hard cancel
636 raise_cancel_error(L_); // raises an error and doesn't return
637
638 default:
639 raise_luaL_error(L_, "internal error: unknown cancel request");
640 }
735 } 641 }
642 };
643 return Linda::ProtectedCall(L_, _receive);
644}
645
646// #################################################################################################
647
648/*
649 * bool= linda:linda_send([timeout_secs=nil,] key_num|str|bool|lightuserdata, ...)
650 *
651 * Send one or more values to a Linda. If there is a limit, all values must fit.
652 *
653 * Returns: 'true' if the value was queued
654 * 'false' for timeout (only happens when the queue size is limited)
655 * nil, kCancelError if cancelled
656 */
657LUAG_FUNC(linda_send)
658{
659 static constexpr lua_CFunction _send{
660 +[](lua_State* const L_) {
661 Linda* const _linda{ ToLinda<false>(L_, 1) };
662 int _key_i{ 2 }; // index of first key, if timeout not there
663
664 std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
665 if (luaG_type(L_, 2) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion
666 lua_Duration const _duration{ lua_tonumber(L_, 2) };
667 if (_duration.count() >= 0.0) {
668 _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(_duration);
669 } else {
670 raise_luaL_argerror(L_, 2, "duration cannot be < 0");
671 }
672 ++_key_i;
673 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
674 ++_key_i;
675 }
736 676
737 switch (_cancel) { 677 // make sure the key is of a valid type
738 case CancelRequest::Soft: 678 CheckKeyTypes(L_, _key_i, _key_i);
739 // if user wants to soft-cancel, the call returns nil, kCancelError
740 lua_pushnil(L_);
741 kCancelError.pushKey(L_);
742 return 2;
743 679
744 case CancelRequest::Hard: 680 STACK_GROW(L_, 1);
745 // raise an error interrupting execution only in case of hard cancel
746 raise_cancel_error(L_); // raises an error and doesn't return
747 681
748 default: 682 // make sure there is something to send
749 if (_ret) { 683 if (lua_gettop(L_) == _key_i) {
750 lua_pushboolean(L_, _ret); // true (success) 684 raise_luaL_error(L_, "no data to send");
751 return 1; 685 }
752 } else { 686
753 // not enough room in the Linda slot to fulfill the request, return nil, "timeout" 687 bool _ret{ false };
688 CancelRequest _cancel{ CancelRequest::None };
689 KeeperCallResult _pushed;
690 {
691 Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) };
692 Keeper* const _keeper{ _linda->whichKeeper() };
693 KeeperState const _K{ _keeper ? _keeper->K : nullptr };
694 if (_K == nullptr)
695 return 0;
696
697 STACK_CHECK_START_REL(_K, 0);
698 for (bool _try_again{ true };;) {
699 if (_lane != nullptr) {
700 _cancel = _lane->cancelRequest;
701 }
702 _cancel = (_cancel != CancelRequest::None) ? _cancel : _linda->cancelRequest;
703 // if user wants to cancel, or looped because of a timeout, the call returns without sending anything
704 if (!_try_again || _cancel != CancelRequest::None) {
705 _pushed.emplace(0);
706 break;
707 }
708
709 STACK_CHECK(_K, 0);
710 _pushed = keeper_call(_K, KEEPER_API(send), L_, _linda, _key_i);
711 if (!_pushed.has_value()) {
712 break;
713 }
714 LUA_ASSERT(L_, _pushed.value() == 1);
715
716 _ret = lua_toboolean(L_, -1) ? true : false;
717 lua_pop(L_, 1);
718
719 if (_ret) {
720 // Wake up ALL waiting threads
721 _linda->writeHappened.notify_all();
722 break;
723 }
724
725 // instant timout to bypass the wait syscall
726 if (std::chrono::steady_clock::now() >= _until) {
727 break; /* no wait; instant timeout */
728 }
729
730 // storage limit hit, wait until timeout or signalled that we should try again
731 {
732 Lane::Status _prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings
733 if (_lane != nullptr) {
734 // change status of lane to "waiting"
735 _prev_status = _lane->status; // Running, most likely
736 LUA_ASSERT(L_, _prev_status == Lane::Running); // but check, just in case
737 _lane->status = Lane::Waiting;
738 LUA_ASSERT(L_, _lane->waiting_on == nullptr);
739 _lane->waiting_on = &_linda->readHappened;
740 }
741 // could not send because no room: wait until some data was read before trying again, or until timeout is reached
742 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock };
743 std::cv_status const status{ _linda->readHappened.wait_until(_guard, _until) };
744 _guard.release(); // we don't want to unlock the mutex on exit!
745 _try_again = (status == std::cv_status::no_timeout); // detect spurious wakeups
746 if (_lane != nullptr) {
747 _lane->waiting_on = nullptr;
748 _lane->status = _prev_status;
749 }
750 }
751 }
752 STACK_CHECK(_K, 0);
753 }
754
755 if (!_pushed.has_value()) {
756 raise_luaL_error(L_, "tried to copy unsupported types");
757 }
758
759 switch (_cancel) {
760 case CancelRequest::Soft:
761 // if user wants to soft-cancel, the call returns nil, kCancelError
754 lua_pushnil(L_); 762 lua_pushnil(L_);
755 luaG_pushstring(L_, "timeout"); 763 kCancelError.pushKey(L_);
756 return 2; 764 return 2;
765
766 case CancelRequest::Hard:
767 // raise an error interrupting execution only in case of hard cancel
768 raise_cancel_error(L_); // raises an error and doesn't return
769
770 default:
771 if (_ret) {
772 lua_pushboolean(L_, _ret); // true (success)
773 return 1;
774 } else {
775 // not enough room in the Linda slot to fulfill the request, return nil, "timeout"
776 lua_pushnil(L_);
777 luaG_pushstring(L_, "timeout");
778 return 2;
779 }
757 } 780 }
758 } 781 }
759 }; 782 };
@@ -771,39 +794,41 @@ LUAG_FUNC(linda_send)
771 */ 794 */
772LUAG_FUNC(linda_set) 795LUAG_FUNC(linda_set)
773{ 796{
774 auto set = [](lua_State* L_) { 797 static constexpr lua_CFunction _set{
775 Linda* const _linda{ ToLinda<false>(L_, 1) }; 798 +[](lua_State* const L_) {
776 bool const _has_data{ lua_gettop(L_) > 2 }; 799 Linda* const _linda{ ToLinda<false>(L_, 1) };
777 // make sure the key is of a valid type (throws an error if not the case) 800 bool const _has_data{ lua_gettop(L_) > 2 };
778 check_key_types(L_, 2, 2); 801 // make sure the key is of a valid type (throws an error if not the case)
779 802 CheckKeyTypes(L_, 2, 2);
780 KeeperCallResult _pushed; 803
781 if (_linda->cancelRequest == CancelRequest::None) { 804 KeeperCallResult _pushed;
782 Keeper* const _keeper{ _linda->whichKeeper() }; 805 if (_linda->cancelRequest == CancelRequest::None) {
783 _pushed = keeper_call(_keeper->K, KEEPER_API(set), L_, _linda, 2); 806 Keeper* const _keeper{ _linda->whichKeeper() };
784 if (_pushed.has_value()) { // no error? 807 _pushed = keeper_call(_keeper->K, KEEPER_API(set), L_, _linda, 2);
785 LUA_ASSERT(L_, _pushed.value() == 1 && luaG_type(L_, -1) == LuaType::BOOLEAN); 808 if (_pushed.has_value()) { // no error?
786 809 LUA_ASSERT(L_, _pushed.value() == 1 && luaG_type(L_, -1) == LuaType::BOOLEAN);
787 if (_has_data) { 810
788 // we put some data in the slot, tell readers that they should wake 811 if (_has_data) {
789 _linda->writeHappened.notify_all(); // To be done from within the 'K' locking area 812 // we put some data in the slot, tell readers that they should wake
790 } 813 _linda->writeHappened.notify_all(); // To be done from within the 'K' locking area
791 if (lua_toboolean(L_, -1)) { 814 }
792 // the key was full, but it is no longer the case, tell writers they should wake 815 if (lua_toboolean(L_, -1)) {
793 _linda->readHappened.notify_all(); // To be done from within the 'K' locking area 816 // the key was full, but it is no longer the case, tell writers they should wake
817 _linda->readHappened.notify_all(); // To be done from within the 'K' locking area
818 }
794 } 819 }
820 } else { // linda is cancelled
821 // do nothing and return nil,lanes.cancel_error
822 lua_pushnil(L_);
823 kCancelError.pushKey(L_);
824 _pushed.emplace(2);
795 } 825 }
796 } else { // linda is cancelled
797 // do nothing and return nil,lanes.cancel_error
798 lua_pushnil(L_);
799 kCancelError.pushKey(L_);
800 _pushed.emplace(2);
801 }
802 826
803 // must trigger any error after keeper state has been released 827 // must trigger any error after keeper state has been released
804 return OptionalValue(_pushed, L_, "tried to copy unsupported types"); 828 return OptionalValue(_pushed, L_, "tried to copy unsupported types");
829 }
805 }; 830 };
806 return Linda::ProtectedCall(L_, set); 831 return Linda::ProtectedCall(L_, _set);
807} 832}
808 833
809// ################################################################################################# 834// #################################################################################################
diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp
index 9ae2611..d5ebc9b 100644
--- a/src/lindafactory.cpp
+++ b/src/lindafactory.cpp
@@ -35,8 +35,7 @@ THE SOFTWARE.
35 35
36#include "linda.h" 36#include "linda.h"
37 37
38// must be a #define instead of a constexpr to work with lua_pushliteral (until I templatize it) 38static constexpr std::string_view kLindaMetatableName{ "Linda" };
39#define kLindaMetatableName "Linda"
40 39
41// ################################################################################################# 40// #################################################################################################
42 41
diff --git a/src/nameof.cpp b/src/nameof.cpp
index a33c2e5..3c82603 100644
--- a/src/nameof.cpp
+++ b/src/nameof.cpp
@@ -206,4 +206,3 @@ LUAG_FUNC(nameof)
206 lua_replace(L_, -3); // L_: "type" "result" 206 lua_replace(L_, -3); // L_: "type" "result"
207 return 2; 207 return 2;
208} 208}
209
diff --git a/src/state.cpp b/src/state.cpp
index 267554e..3f6b3d7 100644
--- a/src/state.cpp
+++ b/src/state.cpp
@@ -50,7 +50,7 @@ namespace {
50 // ############################################################################################# 50 // #############################################################################################
51 // ############################################################################################# 51 // #############################################################################################
52 52
53 [[nodiscard]] static int require_lanes_core(lua_State* L_) 53 [[nodiscard]] static int require_lanes_core(lua_State* const L_)
54 { 54 {
55 // leaves a copy of 'lanes.core' module table on the stack 55 // leaves a copy of 'lanes.core' module table on the stack
56 luaL_requiref(L_, kLanesCoreLibName, luaopen_lanes_core, 0); 56 luaL_requiref(L_, kLanesCoreLibName, luaopen_lanes_core, 0);
@@ -95,7 +95,7 @@ namespace {
95 95
96 // ############################################################################################# 96 // #############################################################################################
97 97
98 static void Open1Lib(lua_State* L_, std::string_view const& name_) 98 static void Open1Lib(lua_State* const L_, std::string_view const& name_)
99 { 99 {
100 for (luaL_Reg const& _entry : local::sLibs) { 100 for (luaL_Reg const& _entry : local::sLibs) {
101 if (name_ == _entry.name) { 101 if (name_ == _entry.name) {
@@ -123,7 +123,7 @@ namespace {
123 // ############################################################################################# 123 // #############################################################################################
124 124
125 // just like lua_xmove, args are (from, to) 125 // just like lua_xmove, args are (from, to)
126 static void CopyOneTimeSettings(Universe* U_, SourceState L1_, DestState L2_) 126 static void CopyOneTimeSettings(Universe* const U_, SourceState const L1_, DestState const L2_)
127 { 127 {
128 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ U_ }); 128 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ U_ });
129 129
@@ -157,7 +157,7 @@ namespace state {
157 // ############################################################################################# 157 // #############################################################################################
158 // ############################################################################################# 158 // #############################################################################################
159 159
160 void CallOnStateCreate(Universe* U_, lua_State* L_, lua_State* from_, LookupMode mode_) 160 void CallOnStateCreate(Universe* const U_, lua_State* const L_, lua_State* const from_, LookupMode const mode_)
161 { 161 {
162 if (U_->onStateCreateFunc == nullptr) { 162 if (U_->onStateCreateFunc == nullptr) {
163 return; 163 return;
@@ -195,7 +195,7 @@ namespace state {
195 195
196 // ############################################################################################# 196 // #############################################################################################
197 197
198 lua_State* CreateState([[maybe_unused]] Universe* U_, lua_State* from_) 198 lua_State* CreateState([[maybe_unused]] Universe* const U_, lua_State* const from_)
199 { 199 {
200 lua_State* const _L { 200 lua_State* const _L {
201 std::invoke( 201 std::invoke(
@@ -228,7 +228,7 @@ namespace state {
228 228
229 // ############################################################################################# 229 // #############################################################################################
230 230
231 void InitializeOnStateCreate(Universe* U_, lua_State* L_) 231 void InitializeOnStateCreate(Universe* const U_, lua_State* const L_)
232 { 232 {
233 STACK_CHECK_START_REL(L_, 1); // L_: settings 233 STACK_CHECK_START_REL(L_, 1); // L_: settings
234 if (luaG_getfield(L_, -1, kOnStateCreate) != LuaType::NIL) { // L_: settings on_state_create|nil 234 if (luaG_getfield(L_, -1, kOnStateCreate) != LuaType::NIL) { // L_: settings on_state_create|nil
@@ -266,7 +266,7 @@ namespace state {
266 * Base ("unpack", "print" etc.) is always added, unless 'libs' is nullptr. 266 * Base ("unpack", "print" etc.) is always added, unless 'libs' is nullptr.
267 * 267 *
268 */ 268 */
269 lua_State* NewLaneState(Universe* U_, SourceState from_, std::optional<std::string_view> const& libs_) 269 lua_State* NewLaneState(Universe* const U_, SourceState const from_, std::optional<std::string_view> const& libs_)
270 { 270 {
271 DestState const _L{ CreateState(U_, from_) }; 271 DestState const _L{ CreateState(U_, from_) };
272 272
diff --git a/src/tools.cpp b/src/tools.cpp
index 9fc1e35..14786b2 100644
--- a/src/tools.cpp
+++ b/src/tools.cpp
@@ -65,15 +65,15 @@ static constexpr int kWriterReturnCode{ 666 };
65 * +-----------------+-------------------+------------+----------+ 65 * +-----------------+-------------------+------------+----------+
66 */ 66 */
67 67
68[[nodiscard]] FuncSubType luaG_getfuncsubtype(lua_State* L_, int _i) 68FuncSubType luaG_getfuncsubtype(lua_State* const L_, int const i_)
69{ 69{
70 if (lua_tocfunction(L_, _i)) { // nullptr for LuaJIT-fast && bytecode functions 70 if (lua_tocfunction(L_, i_)) { // nullptr for LuaJIT-fast && bytecode functions
71 return FuncSubType::Native; 71 return FuncSubType::Native;
72 } 72 }
73 { 73 {
74 int _mustpush{ 0 }; 74 int _mustpush{ 0 };
75 if (luaG_absindex(L_, _i) != lua_gettop(L_)) { 75 if (luaG_absindex(L_, i_) != lua_gettop(L_)) {
76 lua_pushvalue(L_, _i); 76 lua_pushvalue(L_, i_);
77 _mustpush = 1; 77 _mustpush = 1;
78 } 78 }
79 // the provided writer fails with code kWriterReturnCode 79 // the provided writer fails with code kWriterReturnCode
@@ -93,11 +93,11 @@ static constexpr int kWriterReturnCode{ 666 };
93namespace tools { 93namespace tools {
94 94
95 // inspired from tconcat() in ltablib.c 95 // inspired from tconcat() in ltablib.c
96 [[nodiscard]] std::string_view PushFQN(lua_State* L_, int t_, int last_) 96 std::string_view PushFQN(lua_State* const L_, int const t_, int const last_)
97 { 97 {
98 luaL_Buffer _b;
99 STACK_CHECK_START_REL(L_, 0); 98 STACK_CHECK_START_REL(L_, 0);
100 // Lua 5.4 pushes &b as light userdata on the stack. be aware of it... 99 // Lua 5.4 pushes &b as light userdata on the stack. be aware of it...
100 luaL_Buffer _b;
101 luaL_buffinit(L_, &_b); // L_: ... {} ... &b? 101 luaL_buffinit(L_, &_b); // L_: ... {} ... &b?
102 int _i{ 1 }; 102 int _i{ 1 };
103 for (; _i < last_; ++_i) { 103 for (; _i < last_; ++_i) {
diff --git a/src/tools.h b/src/tools.h
index e240fdb..5127ea0 100644
--- a/src/tools.h
+++ b/src/tools.h
@@ -18,7 +18,7 @@ enum class FuncSubType
18 FastJIT 18 FastJIT
19}; 19};
20 20
21[[nodiscard]] FuncSubType luaG_getfuncsubtype(lua_State* L_, int _i); 21[[nodiscard]] FuncSubType luaG_getfuncsubtype(lua_State* L_, int i_);
22 22
23// ################################################################################################# 23// #################################################################################################
24 24