diff options
| author | Benoit Germain <bnt.germain@gmail.com> | 2025-09-20 14:30:57 +0200 |
|---|---|---|
| committer | Benoit Germain <bnt.germain@gmail.com> | 2025-09-20 14:30:57 +0200 |
| commit | c52571736d852d2636bd285d19c613be5c706cff (patch) | |
| tree | 50a1fc9b867197faa695c728008c8d0c7498b756 | |
| parent | dc3de6ef8d4bb1a8ce7b2932515a0f6287ab1e76 (diff) | |
| download | lanes-c52571736d852d2636bd285d19c613be5c706cff.tar.gz lanes-c52571736d852d2636bd285d19c613be5c706cff.tar.bz2 lanes-c52571736d852d2636bd285d19c613be5c706cff.zip | |
Improve table and userdata conversions
* add convert_fallback and convert_max_attempts to global settings
* if no __lanesconvert is available, use convert_fallback (can be useful for externally provided full userdata with fixed metatables)
* only try conversion on non-deep and non-clonable userdata
* conversion can be applied recursively, up to convert_max_attempts times
* plus all the relevant unit tests of course
| -rw-r--r-- | docs/index.html | 33 | ||||
| -rw-r--r-- | src/intercopycontext.cpp | 298 | ||||
| -rw-r--r-- | src/intercopycontext.hpp | 32 | ||||
| -rw-r--r-- | src/lanes.cpp | 3 | ||||
| -rw-r--r-- | src/lanes.lua | 28 | ||||
| -rw-r--r-- | src/universe.cpp | 16 | ||||
| -rw-r--r-- | src/universe.hpp | 14 | ||||
| -rw-r--r-- | unit_tests/UnitTests.vcxproj | 1 | ||||
| -rw-r--r-- | unit_tests/UnitTests.vcxproj.filters | 1 | ||||
| -rw-r--r-- | unit_tests/init_and_shutdown.cpp | 161 | ||||
| -rw-r--r-- | unit_tests/misc_tests.cpp | 193 | ||||
| -rw-r--r-- | unit_tests/shared.cpp | 8 |
12 files changed, 656 insertions, 132 deletions
diff --git a/docs/index.html b/docs/index.html index 2e74495..0d24d55 100644 --- a/docs/index.html +++ b/docs/index.html | |||
| @@ -71,7 +71,7 @@ | |||
| 71 | </p> | 71 | </p> |
| 72 | 72 | ||
| 73 | <p> | 73 | <p> |
| 74 | This document was revised on 03-Jul-25, and applies to version <tt>4.0.0</tt>. | 74 | This document was revised on 19-Sep-25, and applies to version <tt>4.0.0</tt>. |
| 75 | </p> | 75 | </p> |
| 76 | </font> | 76 | </font> |
| 77 | </center> | 77 | </center> |
| @@ -388,6 +388,30 @@ | |||
| 388 | </tr> | 388 | </tr> |
| 389 | 389 | ||
| 390 | <tr valign=top> | 390 | <tr valign=top> |
| 391 | <td id="convert_fallback"> | ||
| 392 | <code>.convert_fallback</code> | ||
| 393 | </td> | ||
| 394 | <td> | ||
| 395 | <tt>nil</tt>, <tt>lanes.null</tt> or <tt>'decay'</tt> | ||
| 396 | </td> | ||
| 397 | <td> | ||
| 398 | Same as <tt>__lanesconvert</tt> (see <a href="#other">Limitations on data passing</a>), but cannot be a function, because this would have to be transferred to all newly created lanes. | ||
| 399 | </td> | ||
| 400 | </tr> | ||
| 401 | |||
| 402 | <tr valign=top> | ||
| 403 | <td id="convert_max_attempts"> | ||
| 404 | <code>.convert_max_attempts</code> | ||
| 405 | </td> | ||
| 406 | <td> | ||
| 407 | 1 ≥ number ≥ 9 | ||
| 408 | </td> | ||
| 409 | <td> | ||
| 410 | When a table or userdata conversion is performed with a function, it can happen recursively up to this number of attempts before triggering an error. Default is 1. | ||
| 411 | </td> | ||
| 412 | </tr> | ||
| 413 | |||
| 414 | <tr valign=top> | ||
| 391 | <td id="internal_allocator"> | 415 | <td id="internal_allocator"> |
| 392 | <code>.internal_allocator</code> | 416 | <code>.internal_allocator</code> |
| 393 | </td> | 417 | </td> |
| @@ -1801,11 +1825,14 @@ | |||
| 1801 | Using the same source table in multiple <a href="#lindas">linda</a> messages keeps no ties between the tables (this is the same reason why tables can't be used as slots). | 1825 | Using the same source table in multiple <a href="#lindas">linda</a> messages keeps no ties between the tables (this is the same reason why tables can't be used as slots). |
| 1802 | </li> | 1826 | </li> |
| 1803 | <li> | 1827 | <li> |
| 1804 | For tables and full userdata: before anything else, the metatable is searched for a <tt>__lanesconvert</tt> field. If found, the source object is converted as follows depending on <tt>__lanesconvert</tt>'s value: | 1828 | For tables and full userdata that are neither deep nor clonable: before anything else, a converter is searched for in the <tt>__lanesconvert</tt> field of its metatable. |
| 1829 | If there is no metatable, or no <tt>__lanesconvert</tt>, <a href="#convert_fallback"><tt>convert_fallback</tt></a> is used instead. | ||
| 1830 | The source object is then converted depending on the converter value: | ||
| 1805 | <ul> | 1831 | <ul> |
| 1832 | <li><tt>nil</tt>: The value is not converted. If it is not a clonable or deep userdata, the transfer will eventually fail with an error.</li> | ||
| 1806 | <li><tt>lanes.null</tt>: The value is converted to <tt>nil</tt>.</li> | 1833 | <li><tt>lanes.null</tt>: The value is converted to <tt>nil</tt>.</li> |
| 1807 | <li><tt>"decay"</tt>: The value is converted to a light userdata obtained from <tt>lua_topointer()</tt>.</li> | 1834 | <li><tt>"decay"</tt>: The value is converted to a light userdata obtained from <tt>lua_topointer()</tt>.</li> |
| 1808 | <li>A function: The function is called as <tt>o:__lanesconvert(string)</tt>, where the argument is either <tt>"keeper"</tt> or <tt>"regular"</tt>, depending on the type of destination. Its (single) return value is the result of the conversion.</li> | 1835 | <li>A function: The function is called as <tt>o:__lanesconvert(string)</tt>, where the argument is either <tt>"keeper"</tt> (sending data in a Linda) or <tt>"regular"</tt> (when creating or calling a Lane generator). Its first return value is the result of the conversion, the rest is ignored. Transfer is then attempted on the new value. If this also requires a conversion, it will be done, up to a depth of 3, after which an error will be raised.</li> |
| 1809 | <li>Any other value raises an error.</li> | 1836 | <li>Any other value raises an error.</li> |
| 1810 | </ul> | 1837 | </ul> |
| 1811 | </li> | 1838 | </li> |
diff --git a/src/intercopycontext.cpp b/src/intercopycontext.cpp index 6e9b66c..a8b3374 100644 --- a/src/intercopycontext.cpp +++ b/src/intercopycontext.cpp | |||
| @@ -513,72 +513,130 @@ void InterCopyContext::interCopyKeyValuePair() const | |||
| 513 | 513 | ||
| 514 | // ################################################################################################# | 514 | // ################################################################################################# |
| 515 | 515 | ||
| 516 | LuaType InterCopyContext::processConversion() const | 516 | ConvertMode InterCopyContext::lookupConverter() const |
| 517 | { | 517 | { |
| 518 | static constexpr int kPODmask = (1 << LUA_TNIL) | (1 << LUA_TBOOLEAN) | (1 << LUA_TLIGHTUSERDATA) | (1 << LUA_TNUMBER) | (1 << LUA_TSTRING); | 518 | static constexpr std::string_view kConvertField{ "__lanesconvert" }; |
| 519 | |||
| 520 | LuaType _val_type{ luaW_type(L1, L1_i) }; | ||
| 521 | |||
| 522 | STACK_CHECK_START_REL(L1, 0); | 519 | STACK_CHECK_START_REL(L1, 0); |
| 523 | 520 | ||
| 524 | // it's a POD: nothing to do | 521 | // lookup and push a converter on the stack |
| 525 | if (((1 << static_cast<int>(_val_type)) & kPODmask) != 0) { | 522 | LuaType const _convertType{ |
| 526 | return _val_type; | 523 | std::invoke([this]() { |
| 527 | } | 524 | // never convert from inside a keeper |
| 525 | if (mode == LookupMode::FromKeeper) { | ||
| 526 | lua_pushnil(L1); | ||
| 527 | return LuaType::NIL; | ||
| 528 | } | ||
| 529 | if (lua_getmetatable(L1, L1_i)) { // L1: ... mt | ||
| 530 | // we have a metatable: grab the converter inside it | ||
| 531 | LuaType const _convertType{ luaW_getfield(L1, kIdxTop, kConvertField) }; // L1: ... mt <converter> | ||
| 532 | lua_remove(L1, -2); // L1: ... <converter> | ||
| 533 | return _convertType; | ||
| 534 | } | ||
| 535 | // no metatable: setup converter from the global settings | ||
| 536 | switch (U->convertMode) { | ||
| 537 | case ConvertMode::DoNothing: | ||
| 538 | lua_pushnil(L1); | ||
| 539 | return LuaType::NIL; | ||
| 540 | |||
| 541 | case ConvertMode::ConvertToNil: | ||
| 542 | kNilSentinel.pushKey(L1); | ||
| 543 | return LuaType::LIGHTUSERDATA; | ||
| 544 | |||
| 545 | case ConvertMode::Decay: | ||
| 546 | luaW_pushstring(L1, "decay"); | ||
| 547 | return LuaType::STRING; | ||
| 548 | |||
| 549 | case ConvertMode::UserConversion: | ||
| 550 | raise_luaL_error(getErrL(), "INTERNAL ERROR: function-based conversion should have been prevented at configure settings validation"); | ||
| 551 | } | ||
| 552 | // technically unreachable, since all cases are handled and raise_luaL_error() is [[noreturn]], but MSVC raises C4715 without that: | ||
| 553 | return LuaType::NIL; | ||
| 554 | }) | ||
| 555 | }; | ||
| 556 | STACK_CHECK(L1, 1); | ||
| 528 | 557 | ||
| 529 | // no metatable: nothing to do | 558 | ConvertMode _convertMode{ ConvertMode::DoNothing }; |
| 530 | if (!lua_getmetatable(L1, L1_i)) { // L1: ... | 559 | LUA_ASSERT(getErrL(), luaW_type(L1, kIdxTop) == _convertType); |
| 531 | STACK_CHECK(L1, 0); | 560 | switch (_convertType) { |
| 532 | return _val_type; | 561 | case LuaType::NIL: // L1: ... nil |
| 533 | } | 562 | // no converter, nothing to do |
| 534 | // we have a metatable // L1: ... mt | ||
| 535 | static constexpr std::string_view kConvertField{ "__lanesconvert" }; | ||
| 536 | LuaType const _converterType{ luaW_getfield(L1, kIdxTop, kConvertField) }; // L1: ... mt kConvertField | ||
| 537 | switch (_converterType) { | ||
| 538 | case LuaType::NIL: | ||
| 539 | // no __lanesconvert, nothing to do | ||
| 540 | lua_pop(L1, 2); // L1: ... | ||
| 541 | break; | 563 | break; |
| 542 | 564 | ||
| 543 | case LuaType::LIGHTUSERDATA: | 565 | case LuaType::LIGHTUSERDATA: |
| 544 | if (kNilSentinel.equals(L1, kIdxTop)) { | 566 | if (!kNilSentinel.equals(L1, kIdxTop)) { |
| 545 | DEBUGSPEW_CODE(DebugSpew(U) << "converted " << luaW_typename(L1, _val_type) << " to nil" << std::endl); | 567 | raise_luaL_error(getErrL(), "Invalid %s type %s", kConvertField.data(), luaW_typename(L1, _convertType).data()); |
| 546 | lua_replace(L1, L1_i); // L1: ... mt | ||
| 547 | lua_pop(L1, 1); // L1: ... | ||
| 548 | _val_type = _converterType; | ||
| 549 | } else { | ||
| 550 | raise_luaL_error(getErrL(), "Invalid %s type %s", kConvertField.data(), luaW_typename(L1, _converterType).data()); | ||
| 551 | } | 568 | } |
| 569 | _convertMode = ConvertMode::ConvertToNil; | ||
| 552 | break; | 570 | break; |
| 553 | 571 | ||
| 554 | case LuaType::STRING: | 572 | case LuaType::STRING: |
| 555 | // kConvertField == "decay" -> replace source value with it's pointer | 573 | if (std::string_view const _mode{ luaW_tostring(L1, kIdxTop) }; _mode != "decay") { // L1: ... "<some string>" |
| 556 | if (std::string_view const _mode{ luaW_tostring(L1, kIdxTop) }; _mode == "decay") { | ||
| 557 | lua_pop(L1, 1); // L1: ... mt | ||
| 558 | lua_pushlightuserdata(L1, const_cast<void*>(lua_topointer(L1, L1_i))); // L1: ... mt decayed | ||
| 559 | lua_replace(L1, L1_i); // L1: ... mt | ||
| 560 | lua_pop(L1, 1); // L1: ... | ||
| 561 | _val_type = LuaType::LIGHTUSERDATA; | ||
| 562 | } else { | ||
| 563 | raise_luaL_error(getErrL(), "Invalid %s mode '%s'", kConvertField.data(), _mode.data()); | 574 | raise_luaL_error(getErrL(), "Invalid %s mode '%s'", kConvertField.data(), _mode.data()); |
| 564 | } | 575 | } |
| 576 | _convertMode = ConvertMode::Decay; | ||
| 565 | break; | 577 | break; |
| 566 | 578 | ||
| 567 | case LuaType::FUNCTION: | 579 | case LuaType::FUNCTION: // L1: ... <some_function> |
| 568 | lua_pushvalue(L1, L1_i); // L1: ... mt kConvertField val | 580 | _convertMode = ConvertMode::UserConversion; |
| 569 | luaW_pushstring(L1, mode == LookupMode::ToKeeper ? "keeper" : "regular"); // L1: ... mt kConvertField val string | ||
| 570 | lua_call(L1, 2, 1); // val:kConvertField(str) -> result // L1: ... mt kConvertField converted | ||
| 571 | lua_replace(L1, L1_i); // L1: ... mt | ||
| 572 | lua_pop(L1, 1); // L1: ... mt | ||
| 573 | _val_type = luaW_type(L1, L1_i); | ||
| 574 | break; | 581 | break; |
| 575 | 582 | ||
| 576 | default: | 583 | default: |
| 577 | raise_luaL_error(getErrL(), "Invalid %s type %s", kConvertField.data(), luaW_typename(L1, _converterType).data()); | 584 | raise_luaL_error(getErrL(), "Invalid %s type %s", kConvertField.data(), luaW_typename(L1, _convertType).data()); |
| 585 | } | ||
| 586 | STACK_CHECK(L1, 1); // L1: ... <converter> | ||
| 587 | return _convertMode; | ||
| 588 | } | ||
| 589 | |||
| 590 | // ################################################################################################# | ||
| 591 | |||
| 592 | bool InterCopyContext::processConversion() const | ||
| 593 | { | ||
| 594 | #if HAVE_LUA_ASSERT() || USE_DEBUG_SPEW() | ||
| 595 | LuaType const _val_type{ luaW_type(L1, L1_i) }; | ||
| 596 | #endif // HAVE_LUA_ASSERT() || USE_DEBUG_SPEW() | ||
| 597 | LUA_ASSERT(getErrL(), _val_type == LuaType::TABLE || _val_type == LuaType::USERDATA); | ||
| 598 | |||
| 599 | STACK_CHECK_START_REL(L1, 0); | ||
| 600 | |||
| 601 | ConvertMode const _convertMode{ lookupConverter() }; // L1: ... <converter> | ||
| 602 | STACK_CHECK(L1, 1); | ||
| 603 | |||
| 604 | bool _converted{ true }; | ||
| 605 | switch (_convertMode) { | ||
| 606 | case ConvertMode::DoNothing: | ||
| 607 | LUA_ASSERT(getErrL(), luaW_type(L1, kIdxTop) == LuaType::NIL); // L1: ... nil | ||
| 608 | lua_pop(L1, 1); // L1: ... | ||
| 609 | _converted = false; | ||
| 610 | break; | ||
| 611 | |||
| 612 | case ConvertMode::ConvertToNil: | ||
| 613 | LUA_ASSERT(getErrL(), kNilSentinel.equals(L1, kIdxTop)); // L1: ... kNilSentinel | ||
| 614 | DEBUGSPEW_CODE(DebugSpew(U) << "converted " << luaW_typename(L1, _val_type) << " to nil" << std::endl); | ||
| 615 | lua_replace(L1, L1_i); // L1: ... | ||
| 616 | break; | ||
| 617 | |||
| 618 | case ConvertMode::Decay: | ||
| 619 | // kConvertField == "decay" -> replace source value with its pointer | ||
| 620 | LUA_ASSERT(getErrL(), luaW_tostring(L1, kIdxTop) == "decay"); | ||
| 621 | DEBUGSPEW_CODE(DebugSpew(U) << "converted " << luaW_typename(L1, _val_type) << " to a pointer" << std::endl); | ||
| 622 | lua_pop(L1, 1); // L1: ... | ||
| 623 | lua_pushlightuserdata(L1, const_cast<void*>(lua_topointer(L1, L1_i))); // L1: ... decayed | ||
| 624 | lua_replace(L1, L1_i); // L1: ... | ||
| 625 | break; | ||
| 626 | |||
| 627 | case ConvertMode::UserConversion: | ||
| 628 | lua_pushvalue(L1, L1_i); // L1: ... converter() val | ||
| 629 | luaW_pushstring(L1, mode == LookupMode::ToKeeper ? "keeper" : "regular"); // L1: ... converter() val string | ||
| 630 | lua_call(L1, 2, 1); // val:converter(str) -> result // L1: ... converted | ||
| 631 | lua_replace(L1, L1_i); // L1: ... | ||
| 632 | DEBUGSPEW_CODE(DebugSpew(U) << "converted " << luaW_typename(L1, _val_type) << " to a " << luaW_typename(L1, L1_i) << std::endl); | ||
| 633 | break; | ||
| 634 | |||
| 635 | default: | ||
| 636 | raise_luaL_error(getErrL(), "INTERNAL ERROR: SHOULD NEVER GET HERE"); | ||
| 578 | } | 637 | } |
| 579 | STACK_CHECK(L1, 0); | 638 | STACK_CHECK(L1, 0); |
| 580 | LUA_ASSERT(getErrL(), luaW_type(L1, L1_i) == _val_type); | 639 | return _converted; |
| 581 | return _val_type; | ||
| 582 | } | 640 | } |
| 583 | 641 | ||
| 584 | // ################################################################################################# | 642 | // ################################################################################################# |
| @@ -867,13 +925,11 @@ bool InterCopyContext::tryCopyDeep() const | |||
| 867 | 925 | ||
| 868 | // ################################################################################################# | 926 | // ################################################################################################# |
| 869 | 927 | ||
| 870 | [[nodiscard]] | 928 | void InterCopyContext::interCopyBoolean() const |
| 871 | bool InterCopyContext::interCopyBoolean() const | ||
| 872 | { | 929 | { |
| 873 | int const _v{ lua_toboolean(L1, L1_i) }; | 930 | int const _v{ lua_toboolean(L1, L1_i) }; |
| 874 | DEBUGSPEW_CODE(DebugSpew(nullptr) << (_v ? "true" : "false") << std::endl); | 931 | DEBUGSPEW_CODE(DebugSpew(nullptr) << (_v ? "true" : "false") << std::endl); |
| 875 | lua_pushboolean(L2, _v); | 932 | lua_pushboolean(L2, _v); |
| 876 | return true; | ||
| 877 | } | 933 | } |
| 878 | 934 | ||
| 879 | // ################################################################################################# | 935 | // ################################################################################################# |
| @@ -972,8 +1028,7 @@ bool InterCopyContext::interCopyFunction() const | |||
| 972 | 1028 | ||
| 973 | // ################################################################################################# | 1029 | // ################################################################################################# |
| 974 | 1030 | ||
| 975 | [[nodiscard]] | 1031 | void InterCopyContext::interCopyLightuserdata() const |
| 976 | bool InterCopyContext::interCopyLightuserdata() const | ||
| 977 | { | 1032 | { |
| 978 | void* const _p{ lua_touserdata(L1, L1_i) }; | 1033 | void* const _p{ lua_touserdata(L1, L1_i) }; |
| 979 | // recognize and print known UniqueKey names here | 1034 | // recognize and print known UniqueKey names here |
| @@ -999,7 +1054,6 @@ bool InterCopyContext::interCopyLightuserdata() const | |||
| 999 | lua_pushlightuserdata(L2, _p); | 1054 | lua_pushlightuserdata(L2, _p); |
| 1000 | DEBUGSPEW_CODE(DebugSpew(nullptr) << std::endl); | 1055 | DEBUGSPEW_CODE(DebugSpew(nullptr) << std::endl); |
| 1001 | } | 1056 | } |
| 1002 | return true; | ||
| 1003 | } | 1057 | } |
| 1004 | 1058 | ||
| 1005 | // ################################################################################################# | 1059 | // ################################################################################################# |
| @@ -1021,8 +1075,7 @@ bool InterCopyContext::interCopyNil() const | |||
| 1021 | 1075 | ||
| 1022 | // ################################################################################################# | 1076 | // ################################################################################################# |
| 1023 | 1077 | ||
| 1024 | [[nodiscard]] | 1078 | void InterCopyContext::interCopyNumber() const |
| 1025 | bool InterCopyContext::interCopyNumber() const | ||
| 1026 | { | 1079 | { |
| 1027 | // LNUM patch support (keeping integer accuracy) | 1080 | // LNUM patch support (keeping integer accuracy) |
| 1028 | #if defined LUA_LNUM || LUA_VERSION_NUM >= 503 | 1081 | #if defined LUA_LNUM || LUA_VERSION_NUM >= 503 |
| @@ -1037,40 +1090,44 @@ bool InterCopyContext::interCopyNumber() const | |||
| 1037 | DEBUGSPEW_CODE(DebugSpew(nullptr) << _v << std::endl); | 1090 | DEBUGSPEW_CODE(DebugSpew(nullptr) << _v << std::endl); |
| 1038 | lua_pushnumber(L2, _v); | 1091 | lua_pushnumber(L2, _v); |
| 1039 | } | 1092 | } |
| 1040 | return true; | ||
| 1041 | } | 1093 | } |
| 1042 | 1094 | ||
| 1043 | // ################################################################################################# | 1095 | // ################################################################################################# |
| 1044 | 1096 | ||
| 1045 | [[nodiscard]] | 1097 | void InterCopyContext::interCopyString() const |
| 1046 | bool InterCopyContext::interCopyString() const | ||
| 1047 | { | 1098 | { |
| 1048 | std::string_view const _s{ luaW_tostring(L1, L1_i) }; | 1099 | std::string_view const _s{ luaW_tostring(L1, L1_i) }; |
| 1049 | DEBUGSPEW_CODE(DebugSpew(nullptr) << "'" << _s << "'" << std::endl); | 1100 | DEBUGSPEW_CODE(DebugSpew(nullptr) << "'" << _s << "'" << std::endl); |
| 1050 | luaW_pushstring(L2, _s); | 1101 | luaW_pushstring(L2, _s); |
| 1051 | return true; | ||
| 1052 | } | 1102 | } |
| 1053 | 1103 | ||
| 1054 | // ################################################################################################# | 1104 | // ################################################################################################# |
| 1055 | 1105 | ||
| 1056 | [[nodiscard]] | 1106 | [[nodiscard]] |
| 1057 | bool InterCopyContext::interCopyTable() const | 1107 | InterCopyOneResult InterCopyContext::interCopyTable() const |
| 1058 | { | 1108 | { |
| 1059 | if (vt == VT::KEY) { | 1109 | if (vt == VT::KEY) { |
| 1060 | return false; | 1110 | return InterCopyOneResult::NotCopied; |
| 1061 | } | 1111 | } |
| 1062 | 1112 | ||
| 1063 | STACK_CHECK_START_REL(L1, 0); | 1113 | STACK_CHECK_START_REL(L1, 0); |
| 1064 | STACK_CHECK_START_REL(L2, 0); | 1114 | STACK_CHECK_START_REL(L2, 0); |
| 1065 | DEBUGSPEW_CODE(DebugSpew(nullptr) << "TABLE " << name << std::endl); | 1115 | DEBUGSPEW_CODE(DebugSpew(nullptr) << "TABLE " << name << std::endl); |
| 1066 | 1116 | ||
| 1117 | // replace the value at L1_i with the result of a conversion if required | ||
| 1118 | bool const _converted{ processConversion() }; | ||
| 1119 | if (_converted) { | ||
| 1120 | return InterCopyOneResult::RetryAfterConversion; | ||
| 1121 | } | ||
| 1122 | STACK_CHECK(L1, 0); | ||
| 1123 | |||
| 1067 | /* | 1124 | /* |
| 1068 | * First, let's try to see if this table is special (aka is it some table that we registered in our lookup databases during module registration?) | 1125 | * First, let's try to see if this table is special (aka is it some table that we registered in our lookup databases during module registration?) |
| 1069 | * Note that this table CAN be a module table, but we just didn't register it, in which case we'll send it through the table cloning mechanism | 1126 | * Note that this table CAN be a module table, but we just didn't register it, in which case we'll send it through the table cloning mechanism |
| 1070 | */ | 1127 | */ |
| 1071 | if (lookupTable()) { | 1128 | if (lookupTable()) { |
| 1072 | LUA_ASSERT(L1, lua_istable(L2, -1) || (lua_tocfunction(L2, -1) == table_lookup_sentinel)); // from lookup data. can also be table_lookup_sentinel if this is a table we know | 1129 | LUA_ASSERT(L1, lua_istable(L2, -1) || (lua_tocfunction(L2, -1) == table_lookup_sentinel)); // from lookup data. can also be table_lookup_sentinel if this is a table we know |
| 1073 | return true; | 1130 | return InterCopyOneResult::Copied; |
| 1074 | } | 1131 | } |
| 1075 | 1132 | ||
| 1076 | /* Check if we've already copied the same table from 'L1' (during this transmission), and | 1133 | /* Check if we've already copied the same table from 'L1' (during this transmission), and |
| @@ -1084,7 +1141,7 @@ bool InterCopyContext::interCopyTable() const | |||
| 1084 | */ | 1141 | */ |
| 1085 | if (pushCachedTable()) { // L2: ... t | 1142 | if (pushCachedTable()) { // L2: ... t |
| 1086 | LUA_ASSERT(L1, lua_istable(L2, -1)); // from cache | 1143 | LUA_ASSERT(L1, lua_istable(L2, -1)); // from cache |
| 1087 | return true; | 1144 | return InterCopyOneResult::Copied; |
| 1088 | } | 1145 | } |
| 1089 | LUA_ASSERT(L1, lua_istable(L2, -1)); | 1146 | LUA_ASSERT(L1, lua_istable(L2, -1)); |
| 1090 | 1147 | ||
| @@ -1106,25 +1163,25 @@ bool InterCopyContext::interCopyTable() const | |||
| 1106 | } | 1163 | } |
| 1107 | STACK_CHECK(L2, 1); | 1164 | STACK_CHECK(L2, 1); |
| 1108 | STACK_CHECK(L1, 0); | 1165 | STACK_CHECK(L1, 0); |
| 1109 | return true; | 1166 | return InterCopyOneResult::Copied; |
| 1110 | } | 1167 | } |
| 1111 | 1168 | ||
| 1112 | // ################################################################################################# | 1169 | // ################################################################################################# |
| 1113 | 1170 | ||
| 1114 | [[nodiscard]] | 1171 | [[nodiscard]] |
| 1115 | bool InterCopyContext::interCopyUserdata() const | 1172 | InterCopyOneResult InterCopyContext::interCopyUserdata() const |
| 1116 | { | 1173 | { |
| 1117 | STACK_CHECK_START_REL(L1, 0); | 1174 | STACK_CHECK_START_REL(L1, 0); |
| 1118 | STACK_CHECK_START_REL(L2, 0); | 1175 | STACK_CHECK_START_REL(L2, 0); |
| 1119 | if (vt == VT::KEY) { | 1176 | if (vt == VT::KEY) { |
| 1120 | return false; | 1177 | return InterCopyOneResult::NotCopied; |
| 1121 | } | 1178 | } |
| 1122 | 1179 | ||
| 1123 | // try clonable userdata first | 1180 | // try clonable userdata first |
| 1124 | if (tryCopyClonable()) { | 1181 | if (tryCopyClonable()) { |
| 1125 | STACK_CHECK(L1, 0); | 1182 | STACK_CHECK(L1, 0); |
| 1126 | STACK_CHECK(L2, 1); | 1183 | STACK_CHECK(L2, 1); |
| 1127 | return true; | 1184 | return InterCopyOneResult::Copied; |
| 1128 | } | 1185 | } |
| 1129 | 1186 | ||
| 1130 | STACK_CHECK(L1, 0); | 1187 | STACK_CHECK(L1, 0); |
| @@ -1134,13 +1191,20 @@ bool InterCopyContext::interCopyUserdata() const | |||
| 1134 | if (tryCopyDeep()) { | 1191 | if (tryCopyDeep()) { |
| 1135 | STACK_CHECK(L1, 0); | 1192 | STACK_CHECK(L1, 0); |
| 1136 | STACK_CHECK(L2, 1); | 1193 | STACK_CHECK(L2, 1); |
| 1137 | return true; | 1194 | return InterCopyOneResult::Copied; |
| 1195 | } | ||
| 1196 | |||
| 1197 | // replace the value at L1_i with the result of a conversion if required | ||
| 1198 | bool const _converted{ processConversion() }; | ||
| 1199 | if (_converted) { | ||
| 1200 | return InterCopyOneResult::RetryAfterConversion; | ||
| 1138 | } | 1201 | } |
| 1202 | STACK_CHECK(L1, 0); | ||
| 1139 | 1203 | ||
| 1140 | // Last, let's try to see if this userdata is special (aka is it some userdata that we registered in our lookup databases during module registration?) | 1204 | // Last, let's try to see if this userdata is special (aka is it some userdata that we registered in our lookup databases during module registration?) |
| 1141 | if (lookupUserdata()) { | 1205 | if (lookupUserdata()) { |
| 1142 | LUA_ASSERT(L1, luaW_type(L2, kIdxTop) == LuaType::USERDATA || (lua_tocfunction(L2, kIdxTop) == userdata_lookup_sentinel)); // from lookup data. can also be userdata_lookup_sentinel if this is a userdata we know | 1206 | LUA_ASSERT(L1, luaW_type(L2, kIdxTop) == LuaType::USERDATA || (lua_tocfunction(L2, kIdxTop) == userdata_lookup_sentinel)); // from lookup data. can also be userdata_lookup_sentinel if this is a userdata we know |
| 1143 | return true; | 1207 | return InterCopyOneResult::Copied; |
| 1144 | } | 1208 | } |
| 1145 | 1209 | ||
| 1146 | raise_luaL_error(getErrL(), "can't copy non-deep full userdata across lanes"); | 1210 | raise_luaL_error(getErrL(), "can't copy non-deep full userdata across lanes"); |
| @@ -1193,54 +1257,64 @@ InterCopyResult InterCopyContext::interCopyOne() const | |||
| 1193 | DEBUGSPEW_CODE(DebugSpew(U) << "interCopyOne()" << std::endl); | 1257 | DEBUGSPEW_CODE(DebugSpew(U) << "interCopyOne()" << std::endl); |
| 1194 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ U }); | 1258 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ U }); |
| 1195 | 1259 | ||
| 1196 | // replace the value at L1_i with the result of a conversion if required | 1260 | DEBUGSPEW_CODE(DebugSpew(U) << local::sLuaTypeNames[static_cast<int>(luaW_type(L1, L1_i))] << " " << local::sValueTypeNames[static_cast<int>(vt)] << ": "); |
| 1197 | LuaType const _val_type{ processConversion() }; | ||
| 1198 | STACK_CHECK(L1, 0); | ||
| 1199 | DEBUGSPEW_CODE(DebugSpew(U) << local::sLuaTypeNames[static_cast<int>(_val_type)] << " " << local::sValueTypeNames[static_cast<int>(vt)] << ": "); | ||
| 1200 | |||
| 1201 | // Lets push nil to L2 if the object should be ignored | ||
| 1202 | bool _ret{ true }; | ||
| 1203 | switch (_val_type) { | ||
| 1204 | // Basic types allowed both as values, and as table keys | ||
| 1205 | case LuaType::BOOLEAN: | ||
| 1206 | _ret = interCopyBoolean(); | ||
| 1207 | break; | ||
| 1208 | case LuaType::NUMBER: | ||
| 1209 | _ret = interCopyNumber(); | ||
| 1210 | break; | ||
| 1211 | case LuaType::STRING: | ||
| 1212 | _ret = interCopyString(); | ||
| 1213 | break; | ||
| 1214 | case LuaType::LIGHTUSERDATA: | ||
| 1215 | _ret = interCopyLightuserdata(); | ||
| 1216 | break; | ||
| 1217 | 1261 | ||
| 1218 | // The following types are not allowed as table keys | 1262 | auto _tryCopy = [this]() { |
| 1219 | case LuaType::USERDATA: | 1263 | LuaType const _val_type{ luaW_type(L1, L1_i) }; |
| 1220 | _ret = interCopyUserdata(); | 1264 | InterCopyOneResult _result{ InterCopyOneResult::Copied }; |
| 1221 | break; | 1265 | switch (_val_type) { |
| 1222 | case LuaType::NIL: | 1266 | // Basic types allowed both as values, and as table keys |
| 1223 | _ret = interCopyNil(); | 1267 | case LuaType::BOOLEAN: |
| 1224 | break; | 1268 | interCopyBoolean(); |
| 1225 | case LuaType::FUNCTION: | 1269 | break; |
| 1226 | _ret = interCopyFunction(); | 1270 | case LuaType::NUMBER: |
| 1227 | break; | 1271 | interCopyNumber(); |
| 1228 | case LuaType::TABLE: | 1272 | break; |
| 1229 | _ret = interCopyTable(); | 1273 | case LuaType::STRING: |
| 1230 | break; | 1274 | interCopyString(); |
| 1275 | break; | ||
| 1276 | case LuaType::LIGHTUSERDATA: | ||
| 1277 | interCopyLightuserdata(); | ||
| 1278 | break; | ||
| 1231 | 1279 | ||
| 1232 | // The following types cannot be copied | 1280 | // The following types are not allowed as table keys |
| 1233 | case LuaType::NONE: | 1281 | case LuaType::USERDATA: |
| 1234 | case LuaType::CDATA: | 1282 | _result = interCopyUserdata(); |
| 1235 | [[fallthrough]]; | 1283 | break; |
| 1236 | case LuaType::THREAD: | 1284 | case LuaType::NIL: |
| 1237 | _ret = false; | 1285 | _result = interCopyNil() ? InterCopyOneResult::Copied : InterCopyOneResult::NotCopied; |
| 1238 | break; | 1286 | break; |
| 1239 | } | 1287 | case LuaType::FUNCTION: |
| 1288 | _result = interCopyFunction() ? InterCopyOneResult::Copied : InterCopyOneResult::NotCopied; | ||
| 1289 | break; | ||
| 1290 | case LuaType::TABLE: | ||
| 1291 | _result = interCopyTable(); | ||
| 1292 | break; | ||
| 1293 | |||
| 1294 | // The following types cannot be copied | ||
| 1295 | case LuaType::NONE: | ||
| 1296 | case LuaType::CDATA: | ||
| 1297 | [[fallthrough]]; | ||
| 1298 | |||
| 1299 | case LuaType::THREAD: | ||
| 1300 | _result = InterCopyOneResult::NotCopied; | ||
| 1301 | break; | ||
| 1302 | } | ||
| 1303 | return _result; | ||
| 1304 | }; | ||
| 1305 | |||
| 1306 | uint32_t _conversionCount{ 0 }; | ||
| 1307 | InterCopyOneResult _result{ InterCopyOneResult::Copied }; | ||
| 1308 | do { | ||
| 1309 | if (_conversionCount++ > U->convertMaxAttempts) { | ||
| 1310 | raise_luaL_error(getErrL(), "more than %d conversion attempts", U->convertMaxAttempts); | ||
| 1311 | } | ||
| 1312 | _result = _tryCopy(); | ||
| 1313 | } while (_result == InterCopyOneResult::RetryAfterConversion); | ||
| 1240 | 1314 | ||
| 1241 | STACK_CHECK(L2, _ret ? 1 : 0); | 1315 | STACK_CHECK(L2, (_result == InterCopyOneResult::Copied) ? 1 : 0); |
| 1242 | STACK_CHECK(L1, 0); | 1316 | STACK_CHECK(L1, 0); |
| 1243 | return _ret ? InterCopyResult::Success : InterCopyResult::Error; | 1317 | return (_result == InterCopyOneResult::Copied) ? InterCopyResult::Success : InterCopyResult::Error; |
| 1244 | } | 1318 | } |
| 1245 | 1319 | ||
| 1246 | // ################################################################################################# | 1320 | // ################################################################################################# |
diff --git a/src/intercopycontext.hpp b/src/intercopycontext.hpp index ece4674..0b88666 100644 --- a/src/intercopycontext.hpp +++ b/src/intercopycontext.hpp | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | #pragma once | 1 | #pragma once |
| 2 | 2 | ||
| 3 | #include "tools.hpp" | 3 | #include "tools.hpp" |
| 4 | #include "universe.hpp" | ||
| 4 | 5 | ||
| 5 | // forwards | 6 | // forwards |
| 6 | class Universe; | 7 | class Universe; |
| @@ -21,6 +22,13 @@ enum class [[nodiscard]] InterCopyResult | |||
| 21 | Error | 22 | Error |
| 22 | }; | 23 | }; |
| 23 | 24 | ||
| 25 | enum class [[nodiscard]] InterCopyOneResult | ||
| 26 | { | ||
| 27 | NotCopied, | ||
| 28 | Copied, | ||
| 29 | RetryAfterConversion | ||
| 30 | }; | ||
| 31 | |||
| 24 | // ################################################################################################# | 32 | // ################################################################################################# |
| 25 | 33 | ||
| 26 | DECLARE_UNIQUE_TYPE(CacheIndex, StackIndex); | 34 | DECLARE_UNIQUE_TYPE(CacheIndex, StackIndex); |
| @@ -43,8 +51,14 @@ class InterCopyContext final | |||
| 43 | // when mode == LookupMode::FromKeeper, L1 is a keeper state and L2 is not, therefore L2 is the state where we want to raise the error | 51 | // when mode == LookupMode::FromKeeper, L1 is a keeper state and L2 is not, therefore L2 is the state where we want to raise the error |
| 44 | // whon mode != LookupMode::FromKeeper, L1 is not a keeper state, therefore L1 is the state where we want to raise the error | 52 | // whon mode != LookupMode::FromKeeper, L1 is not a keeper state, therefore L1 is the state where we want to raise the error |
| 45 | lua_State* getErrL() const { return (mode == LookupMode::FromKeeper) ? L2.value() : L1.value(); } | 53 | lua_State* getErrL() const { return (mode == LookupMode::FromKeeper) ? L2.value() : L1.value(); } |
| 54 | |||
| 55 | // for use in processConversion | ||
| 56 | [[nodiscard]] | ||
| 57 | ConvertMode lookupConverter() const; | ||
| 58 | |||
| 59 | // for use in interCopyTable and interCopyUserdata | ||
| 46 | [[nodiscard]] | 60 | [[nodiscard]] |
| 47 | LuaType processConversion() const; | 61 | bool processConversion() const; |
| 48 | 62 | ||
| 49 | // for use in copyCachedFunction | 63 | // for use in copyCachedFunction |
| 50 | void copyFunction() const; | 64 | void copyFunction() const; |
| @@ -71,22 +85,18 @@ class InterCopyContext final | |||
| 71 | bool tryCopyDeep() const; | 85 | bool tryCopyDeep() const; |
| 72 | 86 | ||
| 73 | // copying a single Lua stack item | 87 | // copying a single Lua stack item |
| 74 | [[nodiscard]] | 88 | void interCopyBoolean() const; |
| 75 | bool interCopyBoolean() const; | ||
| 76 | [[nodiscard]] | 89 | [[nodiscard]] |
| 77 | bool interCopyFunction() const; | 90 | bool interCopyFunction() const; |
| 78 | [[nodiscard]] | 91 | void interCopyLightuserdata() const; |
| 79 | bool interCopyLightuserdata() const; | ||
| 80 | [[nodiscard]] | 92 | [[nodiscard]] |
| 81 | bool interCopyNil() const; | 93 | bool interCopyNil() const; |
| 94 | void interCopyNumber() const; | ||
| 95 | void interCopyString() const; | ||
| 82 | [[nodiscard]] | 96 | [[nodiscard]] |
| 83 | bool interCopyNumber() const; | 97 | InterCopyOneResult interCopyTable() const; |
| 84 | [[nodiscard]] | ||
| 85 | bool interCopyString() const; | ||
| 86 | [[nodiscard]] | ||
| 87 | bool interCopyTable() const; | ||
| 88 | [[nodiscard]] | 98 | [[nodiscard]] |
| 89 | bool interCopyUserdata() const; | 99 | InterCopyOneResult interCopyUserdata() const; |
| 90 | 100 | ||
| 91 | public: | 101 | public: |
| 92 | [[nodiscard]] | 102 | [[nodiscard]] |
diff --git a/src/lanes.cpp b/src/lanes.cpp index 4373aee..024ac67 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp | |||
| @@ -907,6 +907,9 @@ LANES_API int luaopen_lanes_core(lua_State* const L_) | |||
| 907 | // will do nothing on first invocation, as we haven't stored settings in the registry yet | 907 | // will do nothing on first invocation, as we haven't stored settings in the registry yet |
| 908 | lua_setfield(L_, -3, "settings"); // L_: M LG_configure() | 908 | lua_setfield(L_, -3, "settings"); // L_: M LG_configure() |
| 909 | lua_setfield(L_, -2, "configure"); // L_: M | 909 | lua_setfield(L_, -2, "configure"); // L_: M |
| 910 | // lanes.null can be used for some configure settings, expose it now | ||
| 911 | kNilSentinel.pushKey(L_); // L_: M kNilSentinel | ||
| 912 | lua_setfield(L_, -2, "null"); // L_: M | ||
| 910 | } | 913 | } |
| 911 | 914 | ||
| 912 | STACK_CHECK(L_, 1); | 915 | STACK_CHECK(L_, 1); |
diff --git a/src/lanes.lua b/src/lanes.lua index c5b3315..43ebfd5 100644 --- a/src/lanes.lua +++ b/src/lanes.lua | |||
| @@ -93,6 +93,8 @@ local default_params = | |||
| 93 | { | 93 | { |
| 94 | -- LuaJIT provides a thread-unsafe allocator by default, so we need to protect it when used in parallel lanes | 94 | -- LuaJIT provides a thread-unsafe allocator by default, so we need to protect it when used in parallel lanes |
| 95 | allocator = isLuaJIT and "protected" or nil, | 95 | allocator = isLuaJIT and "protected" or nil, |
| 96 | convert_fallback = nil, | ||
| 97 | convert_max_attempts = 1, | ||
| 96 | -- it looks also like LuaJIT allocator may not appreciate direct use of its allocator for other purposes than the VM operation | 98 | -- it looks also like LuaJIT allocator may not appreciate direct use of its allocator for other purposes than the VM operation |
| 97 | internal_allocator = isLuaJIT and "libc" or "allocator", | 99 | internal_allocator = isLuaJIT and "libc" or "allocator", |
| 98 | keepers_gc_threshold = -1, | 100 | keepers_gc_threshold = -1, |
| @@ -125,6 +127,23 @@ local param_checkers = | |||
| 125 | end | 127 | end |
| 126 | return true | 128 | return true |
| 127 | end, | 129 | end, |
| 130 | convert_fallback = function(val_) | ||
| 131 | -- convert_fallback should be nil, lanes.null or 'decay' | ||
| 132 | if val_ == nil or val_ == core.null or val_ == 'decay' then | ||
| 133 | return true | ||
| 134 | end | ||
| 135 | return nil, "must be nil, lanes.null or 'decay'" | ||
| 136 | end, -- convert_fallback | ||
| 137 | convert_max_attempts = function(val_) | ||
| 138 | -- convert_max_attempts should be a number | ||
| 139 | if type(val_) ~= "number" then | ||
| 140 | return nil, "not a number" | ||
| 141 | end | ||
| 142 | if val_ <= 0 or val_ >= 10 then | ||
| 143 | return nil, "value out of range" | ||
| 144 | end | ||
| 145 | return true | ||
| 146 | end, -- convert_fallback | ||
| 128 | internal_allocator = function(val_) | 147 | internal_allocator = function(val_) |
| 129 | -- can be "libc" or "allocator" | 148 | -- can be "libc" or "allocator" |
| 130 | if type(val_) ~= "string" then | 149 | if type(val_) ~= "string" then |
| @@ -850,12 +869,8 @@ local configure = function(settings_) | |||
| 850 | lanes.configure = function() return lanes end -- no need to configure anything again | 869 | lanes.configure = function() return lanes end -- no need to configure anything again |
| 851 | 870 | ||
| 852 | -- now we can configure Lanes core | 871 | -- now we can configure Lanes core |
| 853 | |||
| 854 | |||
| 855 | |||
| 856 | |||
| 857 | |||
| 858 | local settings = core.configure and core.configure(params_checker(settings_)) or core.settings | 872 | local settings = core.configure and core.configure(params_checker(settings_)) or core.settings |
| 873 | assert(type(settings) == 'table') | ||
| 859 | 874 | ||
| 860 | -- | 875 | -- |
| 861 | lanes.ABOUT = | 876 | lanes.ABOUT = |
| @@ -912,7 +927,10 @@ lanesMeta.__index = function(lanes_, k_) | |||
| 912 | -- Access the required key | 927 | -- Access the required key |
| 913 | return lanes_[k_] | 928 | return lanes_[k_] |
| 914 | end | 929 | end |
| 930 | |||
| 915 | lanes.configure = configure | 931 | lanes.configure = configure |
| 932 | -- lanes.null can be used for some configure settings, expose it now | ||
| 933 | lanes.null = assert(core.null) | ||
| 916 | setmetatable(lanes, lanesMeta) | 934 | setmetatable(lanes, lanesMeta) |
| 917 | 935 | ||
| 918 | -- ################################################################################################# | 936 | -- ################################################################################################# |
diff --git a/src/universe.cpp b/src/universe.cpp index 4db036b..934db2c 100644 --- a/src/universe.cpp +++ b/src/universe.cpp | |||
| @@ -163,6 +163,22 @@ Universe* Universe::Create(lua_State* const L_) | |||
| 163 | lua_setmetatable(L_, -2); // L_: settings universe | 163 | lua_setmetatable(L_, -2); // L_: settings universe |
| 164 | lua_pop(L_, 1); // L_: settings | 164 | lua_pop(L_, 1); // L_: settings |
| 165 | 165 | ||
| 166 | std::ignore = luaW_getfield(L_, kIdxSettings, "convert_fallback"); // L_: settings convert_fallback | ||
| 167 | if (kNilSentinel.equals(L_, kIdxTop)) { | ||
| 168 | _U->convertMode = ConvertMode::ConvertToNil; | ||
| 169 | } else if (luaW_type(L_, kIdxTop) == LuaType::STRING) { | ||
| 170 | LUA_ASSERT(L_, luaW_tostring(L_, kIdxTop) == "decay"); | ||
| 171 | _U->convertMode = ConvertMode::Decay; | ||
| 172 | } else { | ||
| 173 | LUA_ASSERT(L_, lua_isnil(L_, kIdxTop)); | ||
| 174 | } | ||
| 175 | lua_pop(L_, 1); // L_: settings | ||
| 176 | |||
| 177 | std::ignore = luaW_getfield(L_, kIdxSettings, "convert_max_attempts"); // L_: settings convert_max_attempts | ||
| 178 | _U->convertMaxAttempts = static_cast<decltype(_U->convertMaxAttempts)>(lua_tointeger(L_, kIdxTop)); | ||
| 179 | lua_pop(L_, 1); // L_: settings | ||
| 180 | STACK_CHECK(L_, 0); | ||
| 181 | |||
| 166 | std::ignore = luaW_getfield(L_, kIdxSettings, "linda_wake_period"); // L_: settings linda_wake_period | 182 | std::ignore = luaW_getfield(L_, kIdxSettings, "linda_wake_period"); // L_: settings linda_wake_period |
| 167 | if (luaW_type(L_, kIdxTop) == LuaType::NUMBER) { | 183 | if (luaW_type(L_, kIdxTop) == LuaType::NUMBER) { |
| 168 | _U->lindaWakePeriod = lua_Duration{ lua_tonumber(L_, kIdxTop) }; | 184 | _U->lindaWakePeriod = lua_Duration{ lua_tonumber(L_, kIdxTop) }; |
diff --git a/src/universe.hpp b/src/universe.hpp index f781e92..1f3ffaf 100644 --- a/src/universe.hpp +++ b/src/universe.hpp | |||
| @@ -17,6 +17,17 @@ enum class LookupMode; | |||
| 17 | 17 | ||
| 18 | // ################################################################################################# | 18 | // ################################################################################################# |
| 19 | 19 | ||
| 20 | // having this enum declared here is not ideal, but it will do for now | ||
| 21 | enum class [[nodiscard]] ConvertMode | ||
| 22 | { | ||
| 23 | DoNothing, // no conversion | ||
| 24 | ConvertToNil, // value is converted to nil | ||
| 25 | Decay, // value is converted to a light userdata pointer | ||
| 26 | UserConversion // value is converted by calling user-provided function | ||
| 27 | }; | ||
| 28 | |||
| 29 | // ################################################################################################# | ||
| 30 | |||
| 20 | // mutex-protected allocator for use with Lua states that share a non-threadsafe allocator | 31 | // mutex-protected allocator for use with Lua states that share a non-threadsafe allocator |
| 21 | class ProtectedAllocator final | 32 | class ProtectedAllocator final |
| 22 | : public lanes::AllocatorDefinition | 33 | : public lanes::AllocatorDefinition |
| @@ -95,6 +106,9 @@ class Universe final | |||
| 95 | 106 | ||
| 96 | Keepers keepers; | 107 | Keepers keepers; |
| 97 | 108 | ||
| 109 | ConvertMode convertMode{ ConvertMode::DoNothing }; | ||
| 110 | uint32_t convertMaxAttempts{ 1 }; | ||
| 111 | |||
| 98 | lua_Duration lindaWakePeriod{}; | 112 | lua_Duration lindaWakePeriod{}; |
| 99 | 113 | ||
| 100 | // Initialized by 'init_once_LOCKED()': the deep userdata Linda object | 114 | // Initialized by 'init_once_LOCKED()': the deep userdata Linda object |
diff --git a/unit_tests/UnitTests.vcxproj b/unit_tests/UnitTests.vcxproj index 2093063..d5edef9 100644 --- a/unit_tests/UnitTests.vcxproj +++ b/unit_tests/UnitTests.vcxproj | |||
| @@ -1106,6 +1106,7 @@ | |||
| 1106 | <ClCompile Include="lane_tests.cpp" /> | 1106 | <ClCompile Include="lane_tests.cpp" /> |
| 1107 | <ClCompile Include="legacy_tests.cpp" /> | 1107 | <ClCompile Include="legacy_tests.cpp" /> |
| 1108 | <ClCompile Include="linda_tests.cpp" /> | 1108 | <ClCompile Include="linda_tests.cpp" /> |
| 1109 | <ClCompile Include="misc_tests.cpp" /> | ||
| 1109 | <ClCompile Include="shared.cpp" /> | 1110 | <ClCompile Include="shared.cpp" /> |
| 1110 | <ClCompile Include="init_and_shutdown.cpp" /> | 1111 | <ClCompile Include="init_and_shutdown.cpp" /> |
| 1111 | <ClCompile Include="_pch.cpp"> | 1112 | <ClCompile Include="_pch.cpp"> |
diff --git a/unit_tests/UnitTests.vcxproj.filters b/unit_tests/UnitTests.vcxproj.filters index df82447..5d2dad8 100644 --- a/unit_tests/UnitTests.vcxproj.filters +++ b/unit_tests/UnitTests.vcxproj.filters | |||
| @@ -18,6 +18,7 @@ | |||
| 18 | <ClCompile Include="catch_amalgamated.cpp"> | 18 | <ClCompile Include="catch_amalgamated.cpp"> |
| 19 | <Filter>Catch2</Filter> | 19 | <Filter>Catch2</Filter> |
| 20 | </ClCompile> | 20 | </ClCompile> |
| 21 | <ClCompile Include="misc_tests.cpp" /> | ||
| 21 | </ItemGroup> | 22 | </ItemGroup> |
| 22 | <ItemGroup> | 23 | <ItemGroup> |
| 23 | <ClInclude Include="_pch.hpp" /> | 24 | <ClInclude Include="_pch.hpp" /> |
diff --git a/unit_tests/init_and_shutdown.cpp b/unit_tests/init_and_shutdown.cpp index 69e4f1b..e7e99a0 100644 --- a/unit_tests/init_and_shutdown.cpp +++ b/unit_tests/init_and_shutdown.cpp | |||
| @@ -205,6 +205,141 @@ TEST_CASE(("lanes.configure.allocator/protected")) | |||
| 205 | L.requireSuccess("require 'lanes'.configure{allocator = 'protected'}"); | 205 | L.requireSuccess("require 'lanes'.configure{allocator = 'protected'}"); |
| 206 | } | 206 | } |
| 207 | 207 | ||
| 208 | // ################################################################################################# | ||
| 209 | |||
| 210 | TEST_CASE("lanes.configure.convert_fallback") | ||
| 211 | { | ||
| 212 | LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; | ||
| 213 | |||
| 214 | SECTION("convert_fallback = <light userdata>") | ||
| 215 | { | ||
| 216 | SECTION("bad value") | ||
| 217 | { | ||
| 218 | // make sure our fixture works | ||
| 219 | L.requireSuccess("fixture = require 'fixture'; assert(type(fixture.newlightuserdata() == 'userdata'))"); | ||
| 220 | // and that providing a bad userdata fails | ||
| 221 | L.requireFailure("lanes = require 'lanes'; lanes.configure{convert_fallback = fixture.newlightuserdata()}"); | ||
| 222 | } | ||
| 223 | |||
| 224 | SECTION("lanes.null") | ||
| 225 | { | ||
| 226 | // lanes.null should be accessible even if we don't lanes.configure() immediately | ||
| 227 | L.requireSuccess("lanes = require 'lanes'; lanes_null = lanes.null; assert(type(lanes_null) == 'userdata')"); | ||
| 228 | // and providing it should work fine | ||
| 229 | L.requireSuccess("lanes.configure{convert_fallback = lanes_null}"); | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | // --------------------------------------------------------------------------------------------- | ||
| 234 | |||
| 235 | SECTION("convert_fallback = <string>") | ||
| 236 | { | ||
| 237 | SECTION("bad value") | ||
| 238 | { | ||
| 239 | L.requireFailure("require 'lanes'.configure{convert_fallback = 'some other string'}"); | ||
| 240 | } | ||
| 241 | SECTION("'decay'") | ||
| 242 | { | ||
| 243 | L.requireSuccess("require 'lanes'.configure{convert_fallback = 'decay'}"); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | // --------------------------------------------------------------------------------------------- | ||
| 248 | |||
| 249 | SECTION("convert_fallback = <function>") | ||
| 250 | { | ||
| 251 | // cannot use functions in the global settings, like we can in __lanesconvert metatable entries | ||
| 252 | L.requireFailure("require 'lanes'.configure{convert_fallback = print}"); | ||
| 253 | } | ||
| 254 | |||
| 255 | // --------------------------------------------------------------------------------------------- | ||
| 256 | |||
| 257 | SECTION("convert_fallback = <number>") | ||
| 258 | { | ||
| 259 | L.requireFailure("require 'lanes'.configure{convert_fallback = 42}"); | ||
| 260 | } | ||
| 261 | |||
| 262 | // --------------------------------------------------------------------------------------------- | ||
| 263 | |||
| 264 | SECTION("convert_fallback = <boolean>") | ||
| 265 | { | ||
| 266 | L.requireFailure("require 'lanes'.configure{convert_fallback = true}"); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | // ################################################################################################# | ||
| 271 | |||
| 272 | TEST_CASE("lanes.configure.convert_max_attempts") | ||
| 273 | { | ||
| 274 | LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; | ||
| 275 | |||
| 276 | // --------------------------------------------------------------------------------------------- | ||
| 277 | |||
| 278 | SECTION("convert_max_attempts = <boolean>") | ||
| 279 | { | ||
| 280 | L.requireFailure("require 'lanes'.configure{convert_max_attempts = true}"); | ||
| 281 | } | ||
| 282 | |||
| 283 | // --------------------------------------------------------------------------------------------- | ||
| 284 | |||
| 285 | SECTION("convert_max_attempts = <string>") | ||
| 286 | { | ||
| 287 | L.requireFailure("require 'lanes'.configure{convert_max_attempts = 'bob'}"); | ||
| 288 | } | ||
| 289 | |||
| 290 | // --------------------------------------------------------------------------------------------- | ||
| 291 | |||
| 292 | SECTION("convert_max_attempts = <function>") | ||
| 293 | { | ||
| 294 | L.requireFailure("require 'lanes'.configure{convert_max_attempts = print}"); | ||
| 295 | } | ||
| 296 | |||
| 297 | // --------------------------------------------------------------------------------------------- | ||
| 298 | |||
| 299 | SECTION("convert_max_attempts = <light userdata>") | ||
| 300 | { | ||
| 301 | // make sure our fixture works | ||
| 302 | L.requireSuccess("fixture = require 'fixture'; assert(type(fixture.newlightuserdata() == 'userdata'))"); | ||
| 303 | L.requireFailure("require 'lanes'.configure{convert_max_attempts = fixture.newlightuserdata()}"); | ||
| 304 | } | ||
| 305 | |||
| 306 | // --------------------------------------------------------------------------------------------- | ||
| 307 | |||
| 308 | SECTION("convert_max_attempts = <full userdata>") | ||
| 309 | { | ||
| 310 | // make sure our fixture works | ||
| 311 | L.requireSuccess("fixture = require 'fixture'; assert(type(fixture.newuserdata() == 'userdata'))"); | ||
| 312 | L.requireFailure("require 'lanes'.configure{convert_max_attempts = fixture.newuserdata()}"); | ||
| 313 | } | ||
| 314 | |||
| 315 | // --------------------------------------------------------------------------------------------- | ||
| 316 | |||
| 317 | SECTION("convert_max_attempts = 0") | ||
| 318 | { | ||
| 319 | L.requireFailure("require 'lanes'.configure{convert_max_attempts = 0}"); | ||
| 320 | } | ||
| 321 | |||
| 322 | // --------------------------------------------------------------------------------------------- | ||
| 323 | |||
| 324 | SECTION("convert_max_attempts = 1") | ||
| 325 | { | ||
| 326 | L.requireSuccess("require 'lanes'.configure{convert_max_attempts = 1}"); | ||
| 327 | } | ||
| 328 | |||
| 329 | // --------------------------------------------------------------------------------------------- | ||
| 330 | |||
| 331 | SECTION("convert_max_attempts = 9") | ||
| 332 | { | ||
| 333 | L.requireSuccess("require 'lanes'.configure{convert_max_attempts = 9}"); | ||
| 334 | } | ||
| 335 | |||
| 336 | // --------------------------------------------------------------------------------------------- | ||
| 337 | |||
| 338 | SECTION("convert_max_attempts = 9") | ||
| 339 | { | ||
| 340 | L.requireFailure("require 'lanes'.configure{convert_max_attempts = 10}"); | ||
| 341 | } | ||
| 342 | } | ||
| 208 | 343 | ||
| 209 | // ################################################################################################# | 344 | // ################################################################################################# |
| 210 | 345 | ||
| @@ -478,6 +613,32 @@ TEST_CASE("lanes.configure.on_state_create/configuration") | |||
| 478 | 613 | ||
| 479 | // ################################################################################################# | 614 | // ################################################################################################# |
| 480 | 615 | ||
| 616 | TEST_CASE("lanes.configure.returns_lanes") | ||
| 617 | { | ||
| 618 | LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | ||
| 619 | |||
| 620 | // when lanes is required, it should contain a 'configure' function and a 'null' userdata | ||
| 621 | L.requireSuccess("lanes = require 'lanes'; lanes_configure1 = lanes.configure; assert(type(lanes.configure) == 'function')"); | ||
| 622 | L.requireSuccess("assert(type(lanes.null) == 'userdata')"); | ||
| 623 | |||
| 624 | // make sure we didn't automatically call lanes.configure() when indexing lanes to retrieve lanes.null | ||
| 625 | L.requireSuccess("lanes_configure2 = lanes.configure; assert(lanes_configure2 == lanes_configure1)"); | ||
| 626 | |||
| 627 | // calling lanes.configure() should return lanes | ||
| 628 | L.requireSuccess("assert(lanes.configure() == lanes)"); | ||
| 629 | |||
| 630 | // and the configure function should have been changed to another function | ||
| 631 | L.requireSuccess("lanes_configure3 = lanes.configure; assert(lanes_configure3 ~= lanes_configure2)"); | ||
| 632 | |||
| 633 | // which returns lanes too | ||
| 634 | L.requireSuccess("assert(lanes.configure() == lanes)"); | ||
| 635 | |||
| 636 | // and the configure function should not change again | ||
| 637 | L.requireSuccess("assert(lanes.configure == lanes_configure3)"); | ||
| 638 | } | ||
| 639 | |||
| 640 | // ################################################################################################# | ||
| 641 | |||
| 481 | TEST_CASE("lanes.configure.shutdown_timeout") | 642 | TEST_CASE("lanes.configure.shutdown_timeout") |
| 482 | { | 643 | { |
| 483 | LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | 644 | LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; |
diff --git a/unit_tests/misc_tests.cpp b/unit_tests/misc_tests.cpp new file mode 100644 index 0000000..a2199aa --- /dev/null +++ b/unit_tests/misc_tests.cpp | |||
| @@ -0,0 +1,193 @@ | |||
| 1 | #include "_pch.hpp" | ||
| 2 | |||
| 3 | #include "shared.h" | ||
| 4 | |||
| 5 | // ################################################################################################# | ||
| 6 | // ################################################################################################# | ||
| 7 | |||
| 8 | TEST_CASE("misc.__lanesconvert.for_tables") | ||
| 9 | { | ||
| 10 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; | ||
| 11 | S.requireSuccess("lanes = require 'lanes'.configure()"); | ||
| 12 | |||
| 13 | S.requireSuccess( | ||
| 14 | " l = lanes.linda()" | ||
| 15 | " t = setmetatable({}, {__lanesconvert = lanes.null})" // table with a nil-converter | ||
| 16 | " l:send('k', t)" // send the table | ||
| 17 | " key, out = l:receive('k')" // read it back | ||
| 18 | " assert(key == 'k' and type(out) == 'nil', 'got ' .. key .. ' ' .. tostring(out))" // should have become nil | ||
| 19 | ); | ||
| 20 | |||
| 21 | S.requireSuccess( | ||
| 22 | " l = lanes.linda()" | ||
| 23 | " t = setmetatable({}, {__lanesconvert = 'decay'})" // table with a decay-converter | ||
| 24 | " l:send('k', t)" // send the table | ||
| 25 | " key, out = l:receive('k')" // read it back | ||
| 26 | " assert(key == 'k' and type(out) == 'userdata', 'got ' .. key .. ' ' .. tostring(out))" // should have become a light userdata | ||
| 27 | ); | ||
| 28 | |||
| 29 | S.requireSuccess( | ||
| 30 | " l = lanes.linda()" | ||
| 31 | " t = setmetatable({}, {__lanesconvert = function(t, hint) return 'keeper' end})" // table with a string-converter | ||
| 32 | " l:send('k', t)" // send the table | ||
| 33 | " key, out = l:receive('k')" // read it back | ||
| 34 | " assert(key == 'k' and out == 'keeper')" // should become 'keeper', the hint that the function received | ||
| 35 | ); | ||
| 36 | |||
| 37 | // make sure that a function that returns the original object causes an error (we don't want infinite loops during conversion) | ||
| 38 | S.requireFailure( | ||
| 39 | " l = lanes.linda()" | ||
| 40 | " t = setmetatable({}, {__lanesconvert = function(t, hint) return t end})" // table with a string-converter | ||
| 41 | " l:send('k', t)" // send the table, it should raise an error because the converter triggers an infinite loop | ||
| 42 | ); | ||
| 43 | } | ||
| 44 | |||
| 45 | // ################################################################################################# | ||
| 46 | // ################################################################################################# | ||
| 47 | |||
| 48 | TEST_CASE("misc.__lanesconvert.for_userdata") | ||
| 49 | { | ||
| 50 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; | ||
| 51 | S.requireSuccess("lanes = require 'lanes'.configure()"); | ||
| 52 | S.requireSuccess("fixture = require 'fixture'"); | ||
| 53 | |||
| 54 | S.requireSuccess("u_tonil = fixture.newuserdata{__lanesconvert = lanes.null}; assert(type(u_tonil) == 'userdata')"); | ||
| 55 | S.requireSuccess( | ||
| 56 | " l = lanes.linda()" | ||
| 57 | " l:send('k', u_tonil)" // send a full userdata with a nil-converter | ||
| 58 | " key, out = l:receive('k')" // read it back | ||
| 59 | " assert(key == 'k' and type(out) == 'nil')" // should become nil | ||
| 60 | ); | ||
| 61 | |||
| 62 | S.requireSuccess("u_tolud = fixture.newuserdata{__lanesconvert = 'decay'}; assert(type(u_tolud) == 'userdata')"); | ||
| 63 | S.requireSuccess( | ||
| 64 | " l = lanes.linda()" | ||
| 65 | " l:send('k', u_tolud)" // send a full userdata with a decay-converter | ||
| 66 | " key, out = l:receive('k')" // read it back | ||
| 67 | " assert(key == 'k' and type(out) == 'userdata' and getmetatable(out) == nil)" // should become a light userdata | ||
| 68 | ); | ||
| 69 | |||
| 70 | S.requireSuccess("u_tostr = fixture.newuserdata{__lanesconvert = function() return 'yo' end}; assert(type(u_tostr) == 'userdata')"); | ||
| 71 | S.requireSuccess( | ||
| 72 | " l = lanes.linda()" | ||
| 73 | " l:send('k', u_tostr)" // send a full userdata with a string-converter | ||
| 74 | " key, out = l:receive('k')" // read it back | ||
| 75 | " assert(key == 'k' and out == 'yo')" // should become 'yo' | ||
| 76 | ); | ||
| 77 | |||
| 78 | // make sure that a function that returns the original object causes an error (we don't want infinite loops during conversion) | ||
| 79 | S.requireSuccess("u_toself = fixture.newuserdata{__lanesconvert = function(u) return u end}; assert(type(u_toself) == 'userdata')"); | ||
| 80 | S.requireFailure( | ||
| 81 | " l = lanes.linda()" | ||
| 82 | " l:send('k', u_toself)" // send the userdata, it should raise an error because the converter triggers an infinite loop | ||
| 83 | ); | ||
| 84 | |||
| 85 | // TODO: make sure that a deep userdata with a __lanesconvert isn't converted (because deep copy takes precedence) | ||
| 86 | } | ||
| 87 | |||
| 88 | // ################################################################################################# | ||
| 89 | // ################################################################################################# | ||
| 90 | |||
| 91 | TEST_CASE("misc.convert_fallback.unset") | ||
| 92 | { | ||
| 93 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; | ||
| 94 | S.requireSuccess("lanes = require 'lanes'.configure()"); | ||
| 95 | |||
| 96 | S.requireSuccess( | ||
| 97 | " l = lanes.linda()" | ||
| 98 | " l:send('k', {})" // send a table without a metatable | ||
| 99 | " key, out = l:receive('k')" // read it back | ||
| 100 | " assert(key == 'k' and type(out) == 'table')" // should not change | ||
| 101 | ); | ||
| 102 | |||
| 103 | S.requireSuccess("fixture = require 'fixture'; u = fixture.newuserdata(); assert(type(u) == 'userdata')"); | ||
| 104 | S.requireFailure( | ||
| 105 | " l = lanes.linda()" | ||
| 106 | " l:send('k', u)" // send a full userdata without a metatable, should fail | ||
| 107 | ); | ||
| 108 | } | ||
| 109 | |||
| 110 | // ################################################################################################# | ||
| 111 | // ################################################################################################# | ||
| 112 | |||
| 113 | TEST_CASE("misc.convert_fallback.decay") | ||
| 114 | { | ||
| 115 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; | ||
| 116 | S.requireSuccess("lanes = require 'lanes'.configure{convert_fallback = 'decay'}"); | ||
| 117 | S.requireSuccess("fixture = require 'fixture'"); | ||
| 118 | |||
| 119 | S.requireSuccess( | ||
| 120 | " l = lanes.linda()" | ||
| 121 | " l:send('k', {})" // send a table without a metatable | ||
| 122 | " key, out = l:receive('k')" // read it back | ||
| 123 | " assert(key == 'k' and type(out) == 'userdata' and getmetatable(out) == nil)" // should have become a light userdata | ||
| 124 | ); | ||
| 125 | |||
| 126 | S.requireSuccess("u = fixture.newuserdata(); assert(type(u) == 'userdata')"); | ||
| 127 | S.requireSuccess( | ||
| 128 | " l = lanes.linda()" | ||
| 129 | " l:send('k', u)" // send a non-copyable non-deep full userdata | ||
| 130 | " key, out = l:receive('k')" // read it back | ||
| 131 | " assert(key == 'k' and type(out) == 'userdata' and getmetatable(out) == nil)" // should have become a light userdata | ||
| 132 | ); | ||
| 133 | } | ||
| 134 | |||
| 135 | // ################################################################################################# | ||
| 136 | // ################################################################################################# | ||
| 137 | |||
| 138 | TEST_CASE("misc.convert_fallback.convert_no_nil") | ||
| 139 | { | ||
| 140 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | ||
| 141 | S.requireSuccess("lanes = require 'lanes'; lanes.configure{convert_fallback = lanes.null}"); | ||
| 142 | |||
| 143 | S.requireSuccess( | ||
| 144 | " l = lanes.linda()" | ||
| 145 | " l:send('k', {})" // send a table without a metatable | ||
| 146 | " key, out = l:receive('k')" // read it back | ||
| 147 | " assert(key == 'k' and type(out) == 'nil')" // should have become nil | ||
| 148 | ); | ||
| 149 | |||
| 150 | S.requireSuccess( | ||
| 151 | " l = lanes.linda()" | ||
| 152 | " t = setmetatable({}, {__lanesconvert = 'decay'})" // override global converter with our own | ||
| 153 | " l:send('k', t)" // send the table | ||
| 154 | " key, out = l:receive('k')" // read it back | ||
| 155 | " assert(key == 'k' and type(out) == 'userdata', 'got ' .. key .. ' ' .. tostring(out))" // should have become a light userdata | ||
| 156 | ); | ||
| 157 | } | ||
| 158 | |||
| 159 | // ################################################################################################# | ||
| 160 | // ################################################################################################# | ||
| 161 | |||
| 162 | TEST_CASE("misc.convert_max_attempts.is_respected") | ||
| 163 | { | ||
| 164 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | ||
| 165 | S.requireSuccess("lanes = require 'lanes'; lanes.configure{convert_max_attempts = 3}"); | ||
| 166 | S.requireSuccess("l = lanes.linda()"); | ||
| 167 | |||
| 168 | S.requireSuccess( | ||
| 169 | " t = setmetatable({n=1}, {__lanesconvert = function(t, hint) t.n = t.n - 1 return t.n > 0 and t or 'done' end})" // table with a string-converter | ||
| 170 | " l:send('k', t)" // send the table | ||
| 171 | " key, out = l:receive('k')" // read it back | ||
| 172 | " assert(key == 'k' and out == 'done', 'got ' .. key .. ' ' .. tostring(out))" // should have stayed a table | ||
| 173 | ); | ||
| 174 | |||
| 175 | S.requireSuccess( | ||
| 176 | " t = setmetatable({n=2}, {__lanesconvert = function(t, hint) t.n = t.n - 1 return t.n > 0 and t or 'done' end})" // table with a string-converter | ||
| 177 | " l:send('k', t)" // send the table | ||
| 178 | " key, out = l:receive('k')" // read it back | ||
| 179 | " assert(key == 'k' and out == 'done', 'got ' .. key .. ' ' .. tostring(out))" // should have stayed a table | ||
| 180 | ); | ||
| 181 | |||
| 182 | S.requireSuccess( | ||
| 183 | " t = setmetatable({n=3}, {__lanesconvert = function(t, hint) t.n = t.n - 1 return t.n > 0 and t or 'done' end})" // table with a string-converter | ||
| 184 | " l:send('k', t)" // send the table | ||
| 185 | " key, out = l:receive('k')" // read it back | ||
| 186 | " assert(key == 'k' and out == 'done', 'got ' .. key .. ' ' .. tostring(out))" // should have stayed a table | ||
| 187 | ); | ||
| 188 | |||
| 189 | S.requireFailure( | ||
| 190 | " t = setmetatable({n=4}, {__lanesconvert = function(t, hint) t.n = t.n - 1 return t.n > 0 and t or 'done' end})" // table with a string-converter | ||
| 191 | " l:send('k', t)" // send the table, it should raise an error because the converter retries too many times | ||
| 192 | ); | ||
| 193 | } | ||
diff --git a/unit_tests/shared.cpp b/unit_tests/shared.cpp index 9f3b08e..825cd48 100644 --- a/unit_tests/shared.cpp +++ b/unit_tests/shared.cpp | |||
| @@ -46,8 +46,14 @@ namespace | |||
| 46 | return 1; | 46 | return 1; |
| 47 | }; | 47 | }; |
| 48 | 48 | ||
| 49 | // a function that creates a full userdata, using first argument as its metatable | ||
| 49 | lua_CFunction sNewUserData = +[](lua_State* const L_) { | 50 | lua_CFunction sNewUserData = +[](lua_State* const L_) { |
| 50 | std::ignore = luaW_newuserdatauv<int>(L_, UserValueCount{ 0 }); | 51 | lua_settop(L_, 1); // L_: {}|nil |
| 52 | STACK_CHECK_START_ABS(L_, 1); | ||
| 53 | std::ignore = luaW_newuserdatauv<int>(L_, UserValueCount{ 0 }); // L_: {}|nil u | ||
| 54 | lua_insert(L_, 1); // L_: u {}|nil | ||
| 55 | lua_setmetatable(L_, 1); // L_: u | ||
| 56 | STACK_CHECK(L_, 1); | ||
| 51 | return 1; | 57 | return 1; |
| 52 | }; | 58 | }; |
| 53 | 59 | ||
