diff options
| author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-12-02 11:53:09 +0100 |
|---|---|---|
| committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-12-02 11:53:09 +0100 |
| commit | ca7657e24549acb8a2dd45fa81c309b5bf9f61ee (patch) | |
| tree | 0124afa9a67d5f9d64fdbad4724bc715b7b502fb /src/intercopycontext.cpp | |
| parent | 0728e80bc2d3f5609f19b76f2bcf10412ce029b4 (diff) | |
| download | lanes-ca7657e24549acb8a2dd45fa81c309b5bf9f61ee.tar.gz lanes-ca7657e24549acb8a2dd45fa81c309b5bf9f61ee.tar.bz2 lanes-ca7657e24549acb8a2dd45fa81c309b5bf9f61ee.zip | |
Data transfer supports registered non-deep full userdata
Diffstat (limited to 'src/intercopycontext.cpp')
| -rw-r--r-- | src/intercopycontext.cpp | 98 |
1 files changed, 84 insertions, 14 deletions
diff --git a/src/intercopycontext.cpp b/src/intercopycontext.cpp index 568e4cb..1be4c53 100644 --- a/src/intercopycontext.cpp +++ b/src/intercopycontext.cpp | |||
| @@ -55,7 +55,7 @@ static int buf_writer(lua_State* L_, void const* b_, size_t size_, void* ud_) | |||
| 55 | 55 | ||
| 56 | // function sentinel used to transfer native functions from/to keeper states | 56 | // function sentinel used to transfer native functions from/to keeper states |
| 57 | [[nodiscard]] | 57 | [[nodiscard]] |
| 58 | static int func_lookup_sentinel(lua_State* L_) | 58 | static int func_lookup_sentinel(lua_State* const L_) |
| 59 | { | 59 | { |
| 60 | raise_luaL_error(L_, "function lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | 60 | raise_luaL_error(L_, "function lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); |
| 61 | } | 61 | } |
| @@ -64,7 +64,7 @@ static int func_lookup_sentinel(lua_State* L_) | |||
| 64 | 64 | ||
| 65 | // function sentinel used to transfer native table from/to keeper states | 65 | // function sentinel used to transfer native table from/to keeper states |
| 66 | [[nodiscard]] | 66 | [[nodiscard]] |
| 67 | static int table_lookup_sentinel(lua_State* L_) | 67 | static int table_lookup_sentinel(lua_State* const L_) |
| 68 | { | 68 | { |
| 69 | raise_luaL_error(L_, "table lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | 69 | raise_luaL_error(L_, "table lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); |
| 70 | } | 70 | } |
| @@ -73,23 +73,32 @@ static int table_lookup_sentinel(lua_State* L_) | |||
| 73 | 73 | ||
| 74 | // function sentinel used to transfer cloned full userdata from/to keeper states | 74 | // function sentinel used to transfer cloned full userdata from/to keeper states |
| 75 | [[nodiscard]] | 75 | [[nodiscard]] |
| 76 | static int userdata_clone_sentinel(lua_State* L_) | 76 | static int userdata_clone_sentinel(lua_State* const L_) |
| 77 | { | 77 | { |
| 78 | raise_luaL_error(L_, "userdata clone sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | 78 | raise_luaL_error(L_, "userdata clone sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); |
| 79 | } | 79 | } |
| 80 | 80 | ||
| 81 | // ################################################################################################# | 81 | // ################################################################################################# |
| 82 | 82 | ||
| 83 | // retrieve the name of a function/table in the lookup database | 83 | // function sentinel used to transfer native table from/to keeper states |
| 84 | [[nodiscard]] | ||
| 85 | static int userdata_lookup_sentinel(lua_State* const L_) | ||
| 86 | { | ||
| 87 | raise_luaL_error(L_, "userdata lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | ||
| 88 | } | ||
| 89 | |||
| 90 | // ################################################################################################# | ||
| 91 | |||
| 92 | // retrieve the name of a function/table/userdata in the lookup database | ||
| 84 | [[nodiscard]] | 93 | [[nodiscard]] |
| 85 | std::string_view InterCopyContext::findLookupName() const | 94 | std::string_view InterCopyContext::findLookupName() const |
| 86 | { | 95 | { |
| 87 | LUA_ASSERT(L1, lua_isfunction(L1, L1_i) || lua_istable(L1, L1_i)); // L1: ... v ... | 96 | LUA_ASSERT(L1, lua_isfunction(L1, L1_i) || lua_istable(L1, L1_i) || luaG_type(L1, L1_i) == LuaType::USERDATA); |
| 88 | STACK_CHECK_START_REL(L1, 0); | 97 | STACK_CHECK_START_REL(L1, 0); // L1: ... v ... |
| 89 | STACK_GROW(L1, 3); // up to 3 slots are necessary on error | 98 | STACK_GROW(L1, 3); // up to 3 slots are necessary on error |
| 90 | if (mode == LookupMode::FromKeeper) { | 99 | if (mode == LookupMode::FromKeeper) { |
| 91 | lua_CFunction const _f{ lua_tocfunction(L1, L1_i) }; // should *always* be one of the function sentinels | 100 | lua_CFunction const _f{ lua_tocfunction(L1, L1_i) }; // should *always* be one of the sentinel functions |
| 92 | if (_f == func_lookup_sentinel || _f == table_lookup_sentinel || _f == userdata_clone_sentinel) { | 101 | if (_f == func_lookup_sentinel || _f == table_lookup_sentinel || _f == userdata_clone_sentinel || _f == userdata_lookup_sentinel) { |
| 93 | lua_getupvalue(L1, L1_i, 1); // L1: ... v ... "f.q.n" | 102 | lua_getupvalue(L1, L1_i, 1); // L1: ... v ... "f.q.n" |
| 94 | } else { | 103 | } else { |
| 95 | // if this is not a sentinel, this is some user-created table we wanted to lookup | 104 | // if this is not a sentinel, this is some user-created table we wanted to lookup |
| @@ -110,8 +119,8 @@ std::string_view InterCopyContext::findLookupName() const | |||
| 110 | // popping doesn't invalidate the pointer since this is an interned string gotten from the lookup database | 119 | // popping doesn't invalidate the pointer since this is an interned string gotten from the lookup database |
| 111 | lua_pop(L1, (mode == LookupMode::FromKeeper) ? 1 : 2); // L1: ... v ... | 120 | lua_pop(L1, (mode == LookupMode::FromKeeper) ? 1 : 2); // L1: ... v ... |
| 112 | STACK_CHECK(L1, 0); | 121 | STACK_CHECK(L1, 0); |
| 113 | if (_fqn.empty() && !lua_istable(L1, L1_i)) { // raise an error if we try to send an unknown function (but not for tables) | 122 | if (_fqn.empty() && !lua_istable(L1, L1_i)) { // raise an error if we try to send an unknown function/userdata (but not for tables) |
| 114 | // try to discover the name of the function we want to send | 123 | // try to discover the name of the function/userdata we want to send |
| 115 | kLaneNameRegKey.pushValue(L1); // L1: ... v ... lane_name | 124 | kLaneNameRegKey.pushValue(L1); // L1: ... v ... lane_name |
| 116 | std::string_view const _from{ luaG_tostring(L1, kIdxTop) }; | 125 | std::string_view const _from{ luaG_tostring(L1, kIdxTop) }; |
| 117 | lua_pushcfunction(L1, LG_nameof); // L1: ... v ... lane_name LG_nameof | 126 | lua_pushcfunction(L1, LG_nameof); // L1: ... v ... lane_name LG_nameof |
| @@ -331,7 +340,8 @@ void InterCopyContext::lookupNativeFunction() const | |||
| 331 | lua_rawget(L2, -2); // L1: ... f ... L2: {} f | 340 | lua_rawget(L2, -2); // L1: ... f ... L2: {} f |
| 332 | // nil means we don't know how to transfer stuff: user should do something | 341 | // nil means we don't know how to transfer stuff: user should do something |
| 333 | // anything other than function or table should not happen! | 342 | // anything other than function or table should not happen! |
| 334 | if (!lua_isfunction(L2, -1) && !lua_istable(L2, -1)) { | 343 | LuaType const _objType{ luaG_type(L2, kIdxTop) }; |
| 344 | if (_objType != LuaType::FUNCTION && _objType != LuaType::TABLE && _objType != LuaType::USERDATA) { | ||
| 335 | kLaneNameRegKey.pushValue(L1); // L1: ... f ... lane_name | 345 | kLaneNameRegKey.pushValue(L1); // L1: ... f ... lane_name |
| 336 | std::string_view const _from{ luaG_tostring(L1, kIdxTop) }; | 346 | std::string_view const _from{ luaG_tostring(L1, kIdxTop) }; |
| 337 | lua_pop(L1, 1); // L1: ... f ... | 347 | lua_pop(L1, 1); // L1: ... f ... |
| @@ -340,9 +350,10 @@ void InterCopyContext::lookupNativeFunction() const | |||
| 340 | lua_pop(L2, 1); // L2: {} f | 350 | lua_pop(L2, 1); // L2: {} f |
| 341 | raise_luaL_error( | 351 | raise_luaL_error( |
| 342 | getErrL(), | 352 | getErrL(), |
| 343 | "%s%s: function '%s' not found in %s destination transfer database.", | 353 | "%s%s: %s '%s' not found in %s destination transfer database.", |
| 344 | lua_isnil(L2, -1) ? "" : "INTERNAL ERROR IN ", | 354 | lua_isnil(L2, -1) ? "" : "INTERNAL ERROR IN ", |
| 345 | _from.empty() ? "main" : _from.data(), | 355 | _from.empty() ? "main" : _from.data(), |
| 356 | luaG_typename(L2, _objType), | ||
| 346 | _fqn.data(), | 357 | _fqn.data(), |
| 347 | _to.empty() ? "main" : _to.data()); | 358 | _to.empty() ? "main" : _to.data()); |
| 348 | return; | 359 | return; |
| @@ -397,8 +408,8 @@ void InterCopyContext::copyCachedFunction() const | |||
| 397 | LUA_ASSERT(L1, lua_isfunction(L2, -1)); | 408 | LUA_ASSERT(L1, lua_isfunction(L2, -1)); |
| 398 | } else { // function is native/LuaJIT: no need to cache | 409 | } else { // function is native/LuaJIT: no need to cache |
| 399 | lookupNativeFunction(); // L2: ... {cache} ... function | 410 | lookupNativeFunction(); // L2: ... {cache} ... function |
| 400 | // if the function was in fact a lookup sentinel, we can either get a function or a table here | 411 | // if the function was in fact a lookup sentinel, we can either get a function, table or full userdata here |
| 401 | LUA_ASSERT(L1, lua_isfunction(L2, -1) || lua_istable(L2, -1)); | 412 | LUA_ASSERT(L1, lua_isfunction(L2, kIdxTop) || lua_istable(L2, kIdxTop) || luaG_type(L2, kIdxTop) == LuaType::USERDATA); |
| 402 | } | 413 | } |
| 403 | } | 414 | } |
| 404 | 415 | ||
| @@ -678,6 +689,59 @@ bool InterCopyContext::pushCachedTable() const | |||
| 678 | 689 | ||
| 679 | // ################################################################################################# | 690 | // ################################################################################################# |
| 680 | 691 | ||
| 692 | // Push a looked-up full userdata. | ||
| 693 | [[nodiscard]] | ||
| 694 | bool InterCopyContext::lookupUserdata() const | ||
| 695 | { | ||
| 696 | // get the name of the userdata we want to send | ||
| 697 | std::string_view const _fqn{ findLookupName() }; | ||
| 698 | // push the equivalent userdata in the destination's stack, retrieved from the lookup table | ||
| 699 | STACK_CHECK_START_REL(L2, 0); | ||
| 700 | STACK_GROW(L2, 3); // up to 3 slots are necessary on error | ||
| 701 | switch (mode) { | ||
| 702 | default: // shouldn't happen, in theory... | ||
| 703 | raise_luaL_error(getErrL(), "internal error: unknown lookup mode"); | ||
| 704 | break; | ||
| 705 | |||
| 706 | case LookupMode::ToKeeper: | ||
| 707 | // push a sentinel closure that holds the lookup name as upvalue | ||
| 708 | luaG_pushstring(L2, _fqn); // L1: ... f ... L2: "f.q.n" | ||
| 709 | lua_pushcclosure(L2, userdata_lookup_sentinel, 1); // L1: ... f ... L2: f | ||
| 710 | break; | ||
| 711 | |||
| 712 | case LookupMode::LaneBody: | ||
| 713 | case LookupMode::FromKeeper: | ||
| 714 | kLookupRegKey.pushValue(L2); // L1: ... f ... L2: {} | ||
| 715 | STACK_CHECK(L2, 1); | ||
| 716 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
| 717 | luaG_pushstring(L2, _fqn); // L1: ... f ... L2: {} "f.q.n" | ||
| 718 | lua_rawget(L2, -2); // L1: ... f ... L2: {} f | ||
| 719 | // nil means we don't know how to transfer stuff: user should do something | ||
| 720 | // anything other than function or table should not happen! | ||
| 721 | if (!lua_isfunction(L2, -1) && !lua_istable(L2, -1)) { | ||
| 722 | kLaneNameRegKey.pushValue(L1); // L1: ... f ... lane_name | ||
| 723 | std::string_view const _from{ luaG_tostring(L1, kIdxTop) }; | ||
| 724 | lua_pop(L1, 1); // L1: ... f ... | ||
| 725 | kLaneNameRegKey.pushValue(L2); // L1: ... f ... L2: {} f lane_name | ||
| 726 | std::string_view const _to{ luaG_tostring(L2, kIdxTop) }; | ||
| 727 | lua_pop(L2, 1); // L2: {} f | ||
| 728 | raise_luaL_error( | ||
| 729 | getErrL(), | ||
| 730 | "%s%s: userdata '%s' not found in %s destination transfer database.", | ||
| 731 | lua_isnil(L2, -1) ? "" : "INTERNAL ERROR IN ", | ||
| 732 | _from.empty() ? "main" : _from.data(), | ||
| 733 | _fqn.data(), | ||
| 734 | _to.empty() ? "main" : _to.data()); | ||
| 735 | } | ||
| 736 | lua_remove(L2, -2); // L2: f | ||
| 737 | break; | ||
| 738 | } | ||
| 739 | STACK_CHECK(L2, 1); | ||
| 740 | return true; | ||
| 741 | } | ||
| 742 | |||
| 743 | // ################################################################################################# | ||
| 744 | |||
| 681 | [[nodiscard]] | 745 | [[nodiscard]] |
| 682 | bool InterCopyContext::tryCopyClonable() const | 746 | bool InterCopyContext::tryCopyClonable() const |
| 683 | { | 747 | { |
| @@ -1099,6 +1163,12 @@ bool InterCopyContext::interCopyUserdata() const | |||
| 1099 | return true; | 1163 | return true; |
| 1100 | } | 1164 | } |
| 1101 | 1165 | ||
| 1166 | // 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?) | ||
| 1167 | if (lookupUserdata()) { | ||
| 1168 | LUA_ASSERT(L1, luaG_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 | ||
| 1169 | return true; | ||
| 1170 | } | ||
| 1171 | |||
| 1102 | raise_luaL_error(getErrL(), "can't copy non-deep full userdata across lanes"); | 1172 | raise_luaL_error(getErrL(), "can't copy non-deep full userdata across lanes"); |
| 1103 | } | 1173 | } |
| 1104 | 1174 | ||
