From d406c877f29aaa032bd1f7dc0fbf1e05dc1e1ea0 Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 20 Nov 2025 18:25:52 +0100 Subject: [PATCH 01/24] bump(UE) add version UE5.7, no changes necessary --- .github/workflows/build.yml | 8 ++++++++ .github/workflows/release.yml | 6 +++--- Documentation/unreal/InkCPP_DEMO.zip | Bin 227917 -> 227917 bytes 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c7b7cae..f5a695b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -117,12 +117,20 @@ jobs: working-directory: ${{github.workspace}}/build shell: bash run: | + cmake $GITHUB_WORKSPACE -DINKCPP_UNREAL_TARGET_VERSION="5.7" -DINKCPP_UNREAL=ON + cmake --install . --config $BUILD_TYPE --prefix comp_unreal_5_7 --component unreal cmake $GITHUB_WORKSPACE -DINKCPP_UNREAL_TARGET_VERSION="5.6" -DINKCPP_UNREAL=ON cmake --install . --config $BUILD_TYPE --prefix comp_unreal_5_6 --component unreal cmake $GITHUB_WORKSPACE -DINKCPP_UNREAL_TARGET_VERSION="5.5" -DINKCPP_UNREAL=ON cmake --install . --config $BUILD_TYPE --prefix comp_unreal_5_5 --component unreal cmake $GITHUB_WORKSPACE -DINKCPP_UNREAL_TARGET_VERSION="5.4" -DINKCPP_UNREAL=ON cmake --install . --config $BUILD_TYPE --prefix comp_unreal_5_4 --component unreal + - name: Upload UE 5.7 + if: ${{ matrix.unreal }} + uses: actions/upload-artifact@v4 + with: + name: unreal_5_7 + path: build/comp_unreal_5_7/ - name: Upload UE 5.6 if: ${{ matrix.unreal }} uses: actions/upload-artifact@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63696585..8c82eed1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,12 +26,12 @@ jobs: run: | mkdir artifacts ID=$(gh run list -b master --limit 1 --json databaseId | jq '.[0].databaseId') - gh run download $ID -D artifacts -n linux-cl -n linux-lib -n linux-clib -n unreal_5_6 -n unreal_5_5 -n unreal_5_4 -n macos-cl -n macos-lib -n macos-clib -n macos-arm-cl -n macos-arm-lib -n macos-arm-clib -n win64-cl -n win64-lib -n win64-clib -n python-package-distribution + gh run download $ID -D artifacts -n linux-cl -n linux-lib -n linux-clib -n unreal_5_7 -n unreal_5_6 -n unreal_5_5 -n unreal_5_4 -n macos-cl -n macos-lib -n macos-clib -n macos-arm-cl -n macos-arm-lib -n macos-arm-clib -n win64-cl -n win64-lib -n win64-clib -n python-package-distribution mv artifacts/python-package-distribution dist - name: Zip working-directory: ${{github.workspace}}/artifacts run: | - for f in linux-cl linux-lib linux-clib unreal_5_6 unreal_5_5 unreal_5_4 macos-cl macos-lib macos-clib macos-arm-cl macos-arm-lib macos-arm-clib win64-cl win64-lib win64-clib; do zip -r ../$f.zip $f; done + for f in linux-cl linux-lib linux-clib unreal_5_7 unreal_5_6 unreal_5_5 unreal_5_4 macos-cl macos-lib macos-clib macos-arm-cl macos-arm-lib macos-arm-clib win64-cl win64-lib win64-clib; do zip -r ../$f.zip $f; done - name: List run: tree - name: Publish to PyPI @@ -45,5 +45,5 @@ jobs: --repo="$GITHUB_REPOSITORY" \ --title="${GITHUB_REPOSITORY#*/} ${tag#v}" \ --generate-notes \ - "$tag" "linux-cl.zip" "linux-lib.zip" "linux-clib.zip" "unreal_5_6.zip" "unreal_5_5.zip" "unreal_5_4.zip" "macos-cl.zip" "macos-lib.zip" "macos-clib.zip" "win64-cl.zip" "macos-arm-cl.zip" "macos-arm-lib.zip" "macos-arm-clib.zip" "win64-lib.zip" "win64-clib.zip" + "$tag" "linux-cl.zip" "linux-lib.zip" "linux-clib.zip" "unreal_5_7.zip" "unreal_5_6.zip" "unreal_5_5.zip" "unreal_5_4.zip" "macos-cl.zip" "macos-lib.zip" "macos-clib.zip" "win64-cl.zip" "macos-arm-cl.zip" "macos-arm-lib.zip" "macos-arm-clib.zip" "win64-lib.zip" "win64-clib.zip" diff --git a/Documentation/unreal/InkCPP_DEMO.zip b/Documentation/unreal/InkCPP_DEMO.zip index fa2009764da79fbd21792457b30dafcc12f8baa3..6ae55b556a6a460bb5c9940e7c3dbc85db277308 100644 GIT binary patch delta 2240 zcmX^6h4<_iUcLZtW)=|!5XhQT5XW1**V<2P6SAeRCn>OD>319FH4{)Z`63g%BB4UUw9k z%@w@8%q$?sPUaM};6ZlnWM9EBF__^Hr^6k$Q_vCB7&f8B+z=bq%6g(oG09zf0+Z61 zURA&>2lEol@a=}ojMjBvS4=NxWOT+7EU+M*ezuX(6UCX+m6{mS(cB4FJ-><34vT3p ze@y@0#OROW+U-Hjj9RQ<=T2|vVDv(jyw<_k!2*%|{)JIxdRs4}2C9l(y^OaZDkiU$ zRhhnb5~C`rf_IY`k0L3sD`8f`2o9bZj7EWwAd41Mna*Fz%szcX39}f6!bvX~6CuGq z9q1k(RGa+2GWM&(9jiT^SC&Z;RfUx-(|)rhRn-#;dXeVO5TWP24=!wP=1OQl$p+5#jIKn%)t3b8Q6EiX}`k;0 z&}{DEo0;?P-ntdBhwotK;)*v*#BYXLm56t3Ec0AI@*Ilt@|ZbkS{R+=R#Xm-u}9J8233s>Am@4&3^kX!J7P zY47$3uV&3hB6*9xFY10ed6Cr44AIkfcPj_F#r5t@vYD^oxhw3~kNrkwbxWN$&aKFH zwNAO;_2~2CRnsr5JAFYqwmalRl<3;L3C}9!4B!7-R$TeZDC?a1OS$jLr@y7B&5sZ8 zX6Kj^S##Ksk%8ep5C?cOGKnxF3fk#uIm~tG_4s&5%s>t&rIVMK#>+up;JI>~E2K}{BagnS`wzQba=(k#Z`x#xn0NY_aybMks8ZUzXW=% zqsfT`|L$J0bB!TOLZNJ35%z~8Ss&lXg$KiG3&$v`T&{u@RiM~cw8h?1)>VNLMwH0{ zO(^$;Ivvqpno#sBo(4@W-L21Jk4z|;E`N62S;~@1G}(nbJ`<*48>>+wt#fO19Hp(h z8dVeW2;ie36vQ^wqEKq)BdoRPDj{u0BA^jL?2H)&Q=b}VH`)fw=tn|Y%rk8P(K;Lm z?f87jQD+)jhaysOzel(w%PW|TDaCHAMTfm1&RfNmW9~;Ed6GUFDVMW|HlO7ZB@d(R zlL%4T;1s6x69VX6K4PAq@I0E_at+r&*Mr!-Qmld;n%B4-HOquJO0MQe<6BPOVD;J9 zmHo3?deq{3+yFz?2yeTSY0LPi4!v#C`rBOKL9Gz$LhgSh-8bmZSc4Eu{rPg2 z2*}fR72-`o)c*`Gr#V8y_L_vn^quY8nTJgRi6Cw;*@|pDi7hR|0dS&iPq_)Fof4+b zBR@Nacz93;ka=cVHHx>q#SBmFu--|N;a0>!4f#$ zQZE(rT!(`F6Vdt z9#pJCdmi4<&Y#sD_h9Xva~9PhOVF9w%I^YQZs%Dyp5HqCo0B__myI`Fihpo1;aKk9 zt%K7itmwPj=$Ub4Uh=^BnX&!u{`bpw-$4a?t2ca6vnpX?Q<~4_&M9z))zht#sY~I%}+d8f(Y;9VrH9l)) z=Gi09_Wsg)Z|mW?z6YbOrS9s{6+d-JQMhaXh+t2ifIC@Skwie6cH&5ItO%!$IHH4yTAU%LW`$Z@ zX?v+2YVo2G(GC};7vm2_aG^Z|5Ltvj6=RJEBIJo|!N?MvC4vBX0`_AKB~ra$KAwQ~ z7}ZGxe8&?>b{b~T;Uyv%jwiC-dUR4A&>K%=x2a1d0$SsV?6i(jsW0F%o`A@hzg`+6 z_=_hXFSZE?2#Y6RD>k5)ss>ZpiKEMz2AuFxM`b~sHsEC<2+F<(Ix5>Kqzq?@U?rZ& zO0twm=K&k>L{;QM4DPsrc$9u J&%@Hce*rr Date: Thu, 20 Nov 2025 22:45:28 +0100 Subject: [PATCH 02/24] WIP --- CMakeLists.txt | 6 ++++- inkcpp/array.h | 6 ++--- inkcpp/include/choice.h | 10 +++++--- inkcpp/include/list.h | 19 ++++++++++++-- inkcpp/include/story_ptr.h | 7 ++++-- inkcpp/include/traits.h | 2 +- inkcpp/include/types.h | 4 +++ inkcpp/list_table.cpp | 2 +- inkcpp/list_table.h | 50 ++++++++++++++++++++++--------------- inkcpp/snapshot_interface.h | 7 ++++++ inkcpp_compiler/json.hpp | 2 ++ shared/public/config.h | 2 +- shared/public/system.h | 2 +- 13 files changed, 84 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 490af3e7..2f73c0ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,7 +46,11 @@ SET(CMAKE_CXX_STANDARD 17) SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(CMAKE_INSTALL_LIBRARY_DIR lib) SET(CMAKE_INSTALL_INCLUDE_DIR include) - +if (MSVC) + add_compile_options(/Wall /wd4820) +else() + add_compile_options(-Wall -Wextra -Wpedantic) +endif() # Add subdirectories include(CMakeDependentOption) option(INKCPP_PY "Build python bindings" OFF) diff --git a/inkcpp/array.h b/inkcpp/array.h index a28a8b83..c4d10bcc 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -75,9 +75,9 @@ class managed_array : public snapshot_interface T& back() { return end()[-1]; } - const size_t size() const { return _size; } + size_t size() const { return _size; } - const size_t capacity() const { return _capacity; } + size_t capacity() const { return _capacity; } T& push() { @@ -170,10 +170,10 @@ class managed_array : public snapshot_interface } private: - if_t _static_data[dynamic ? 1 : initialCapacity]; T* _dynamic_data = nullptr; size_t _capacity; size_t _size; + if_t _static_data[dynamic ? 1 : initialCapacity]; }; template diff --git a/inkcpp/include/choice.h b/inkcpp/include/choice.h index 60b9c700..8748dee1 100644 --- a/inkcpp/include/choice.h +++ b/inkcpp/include/choice.h @@ -74,12 +74,16 @@ namespace runtime ); protected: - int _index = -1; ///< @private const char* _text = nullptr; ///< @private - uint32_t _path = ~0; ///< @private - thread_t _thread = ~0; ///< @private const internal::snap_tag* _tags_start = nullptr; ///< @private const internal::snap_tag* _tags_end = nullptr; ///< @private + uint32_t _path = ~0U; ///< @private + thread_t _thread = ~0U; ///< @private + int _index = -1; ///< @private +#pragma warning(push) +#pragma warning(disable : 4820, justification : "4 byte aligment free on 64-bit systems") }; + +#pragma warning(pop) } // namespace runtime } // namespace ink diff --git a/inkcpp/include/list.h b/inkcpp/include/list.h index 1b2cdd93..2d7d23a5 100644 --- a/inkcpp/include/list.h +++ b/inkcpp/include/list.h @@ -58,6 +58,8 @@ class list_interface const list_interface& _list; int _i; bool _one_list_iterator; ///< iterates only though values of one list +#pragma warning(push) +#pragma warning(disable : 4820, justification : "3 byte aligment free on 64-bit systems") friend list_interface; #ifdef INK_BUILD_CLIB friend int ::ink_list_flags(const HInkList*, InkListIter*); @@ -68,7 +70,7 @@ class list_interface protected: /** @private */ iterator( - const char* flag_name, const list_interface& list, size_t i, bool one_list_only = false + const char* flag_name, const list_interface& list, int i, bool one_list_only = false ) : _flag_name(flag_name) , _list_name(nullptr) @@ -115,8 +117,16 @@ class list_interface * @param itr other iterator */ bool operator==(const iterator& itr) const { return itr._i == _i; } + + iterator& operator=(const iterator&) = delete; }; +#pragma warning(pop) + + virtual ~list_interface() {} + +#pragma warning(push) +#pragma warning(disable : 4100, justification : "non functional prototypes do not need the argument.") /** checks if a flag is contained in the list */ virtual bool contains(const char* flag) const { @@ -166,6 +176,8 @@ class list_interface inkAssert(false, "Not implemented funciton from interface is called!"); }; +#pragma warning(pop) + protected: /** @private */ iterator new_iterator(const char* flag_name, int i, bool one_list_only = false) const @@ -182,7 +194,10 @@ class list_interface /** @private */ internal::list_table* _list_table; - /** @private */ int _list; +#pragma warning(push) +#pragma warning(disable : 4820, justification : "4 byte aligment free on 64-bit systems") }; + +#pragma warning(pop) } // namespace ink::runtime diff --git a/inkcpp/include/story_ptr.h b/inkcpp/include/story_ptr.h index ad4b4290..be67ba4a 100644 --- a/inkcpp/include/story_ptr.h +++ b/inkcpp/include/story_ptr.h @@ -19,13 +19,16 @@ namespace internal , valid(true) { } + static void remove_reference(ref_block*&); size_t references; bool valid; - - static void remove_reference(ref_block*&); +#pragma warning(push) +#pragma warning(disable : 4820, justification : "3 byte aligment free on 64-bit systems") }; +#pragma warning(pop) + /** @private */ class story_ptr_base { diff --git a/inkcpp/include/traits.h b/inkcpp/include/traits.h index e119a640..242e9cb2 100644 --- a/inkcpp/include/traits.h +++ b/inkcpp/include/traits.h @@ -162,7 +162,7 @@ inline size_t c_str_len(const char* c) const char* i = c; while (*i != 0) i++; - return i - c; + return static_cast(i - c); } MARK_AS_STRING(char*, c_str_len(x), x); diff --git a/inkcpp/include/types.h b/inkcpp/include/types.h index 8183e6c2..08b25453 100644 --- a/inkcpp/include/types.h +++ b/inkcpp/include/types.h @@ -54,6 +54,8 @@ struct value { Float, ///< containing a float List ///< containing a @ref list_interface } type; ///< Label of type currently contained in @ref value +#pragma warning(push) +#pragma warning(disable : 4820, justification : "4 byte aligment free on 64-bit systems") value() : v_int32{0} @@ -126,6 +128,8 @@ struct value { } }; +#pragma warning(pop) + /** access #value::Type::Bool value */ template<> inline const auto& value::get() const diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index 85a756ea..b4e16228 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -112,7 +112,7 @@ void list_table::gc() _list_handouts.clear(); } -int list_table::toFid(list_flag e) const { return listBegin(e.list_id) + e.flag; } +size_t list_table::toFid(list_flag e) const { return listBegin(e.list_id) + e.flag; } size_t list_table::stringLen(const list_flag& e) const { return c_str_len(toString(e)); } diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index 9139f6a6..9baca261 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -28,16 +28,16 @@ class prng; // memory segments // @param bits size in bits // @param size segment size in bytes -constexpr int segmentsFromBits(int bits, int size) +constexpr size_t segmentsFromBits(size_t bits, size_t size) { - size *= 8; - return bits / size + (bits % size ? 1 : 0); + size *= 8U; + return bits / size + (bits % size ? 1U : 0); } /// managed all list entries and list metadata class list_table : public snapshot_interface { - using data_t = int; + using data_t = unsigned; enum class state : char { unused, used, @@ -77,9 +77,9 @@ class list_table : public snapshot_interface flag.flag = -1; return flag; } - for (int i = listBegin(flag.list_id); i < _list_end[flag.list_id]; ++i) { - if (_flag_values[i] == flag.flag) { - flag.flag = i - listBegin(flag.list_id); + for (int i = listBegin(flag.list_id); i < _list_end[static_cast(flag.list_id)]; ++i) { + if (_flag_values[static_cast(i)] == flag.flag) { + flag.flag = static_cast(i - listBegin(flag.list_id)); return flag; } } @@ -89,7 +89,7 @@ class list_table : public snapshot_interface int get_flag_value(list_flag flag) const { - return _flag_values[listBegin(flag.list_id) + flag.flag]; + return _flag_values[static_cast(listBegin(flag.list_id) + flag.flag)]; } /// zeros all usage values @@ -257,9 +257,9 @@ class list_table : public snapshot_interface private: void copy_lists(const data_t* src, data_t* dst); - static constexpr int bits_per_data = sizeof(data_t) * 8; + static constexpr size_t bits_per_data = sizeof(data_t) * 8U; - int listBegin(int lid) const { return lid == 0 ? 0 : _list_end[lid - 1]; } + size_t listBegin(int lid) const { return lid == 0 ? 0 : _list_end[static_cast(lid - 1)]; } const data_t* getPtr(int eid) const { @@ -273,13 +273,13 @@ class list_table : public snapshot_interface + static_cast(_entrySize) * static_cast(eid); } - int numFlags() const + size_t numFlags() const { return _flag_names.size(); // return _list_end.end()[-1]; TODO: } - int numLists() const { return _list_end.size(); } + size_t numLists() const { return _list_end.size(); } bool getBit(const data_t* data, int id) const { @@ -314,12 +314,12 @@ class list_table : public snapshot_interface } } - int toFid(list_flag e) const; + size_t toFid(list_flag e) const; auto flagStartMask() const { struct { - int segment; + size_t segment; data_t mask; } res{numLists() / bits_per_data, ~static_cast(0) >> (numLists() % bits_per_data)}; @@ -330,10 +330,10 @@ class list_table : public snapshot_interface using managed_array = managed_array < T, config<0, abs(config)>; - static constexpr int maxMemorySize + static constexpr long maxMemorySize = (config::maxListTypes < 0 || config::maxFlags < 0 || config::maxLists < 0 ? -1 : 1) - * segmentsFromBits(abs(config::maxListTypes) + abs(config::maxFlags), sizeof(data_t)) - * static_cast(abs(config::maxLists)); + * static_cast(segmentsFromBits(abs(config::maxListTypes) + abs(config::maxFlags), sizeof(data_t)) + * static_cast(abs(config::maxLists))); int _entrySize; ///< entry size in data_t // entries (created lists) @@ -341,7 +341,7 @@ class list_table : public snapshot_interface managed_array _entry_state; // defined list (meta data) - managed_array _list_end; + managed_array _list_end; managed_array _flag_names; managed_array _flag_values; managed_array _list_names; @@ -351,7 +351,7 @@ class list_table : public snapshot_interface bool _valid; public: - friend class name_flag_itr; + friend class named_flag_itr; friend class list_impl; class named_flag_itr @@ -364,10 +364,14 @@ class list_table : public snapshot_interface const char* name; } _pos; + /** carry list change. + * if the iterator incremented to the next flag, also increment the list if necessary + * @pre _pos.flag.list_id >= 0 + */ void carry() { if (_pos.flag.flag - == _list._list_end[_pos.flag.list_id] - _list.listBegin(_pos.flag.list_id)) { + == _list._list_end[static_cast(_pos.flag.list_id)] - _list.listBegin(static_cast(_pos.flag.list_id))) { _pos.flag.flag = 0; ++_pos.flag.list_id; } @@ -401,6 +405,12 @@ class list_table : public snapshot_interface } public: + named_flag_itr(const named_flag_itr& o) + : _list{o._list} + , _data{o._data} + , _pos{o._pos} + {} + named_flag_itr& operator=(const named_flag_itr&) = delete; bool operator!=(const named_flag_itr& o) const { return _pos.flag != o._pos.flag; } named_flag_itr(const list_table& list, const data_t* filter) diff --git a/inkcpp/snapshot_interface.h b/inkcpp/snapshot_interface.h index 88c103a8..0bf8855d 100644 --- a/inkcpp/snapshot_interface.h +++ b/inkcpp/snapshot_interface.h @@ -53,14 +53,20 @@ class snapshot_interface const string_table& strings; const char* story_string_table; const snap_tag* runner_tags = nullptr; + snapper() = delete; + snapper& operator=(const snapper&) = delete; }; struct loader { managed_array& string_table; /// FIXME: make configurable const char* story_string_table; const snap_tag* runner_tags = nullptr; + loader() = delete; + loader& operator=(const loader&) = delete; }; +#pragma warning(push) +#pragma warning(disable : 4100, justification : "non functional prototypes do not need the argument." ) size_t snap(unsigned char* data, snapper&) const { inkFail("Snap function not implemented"); @@ -72,5 +78,6 @@ class snapshot_interface inkFail("Snap function not implemented"); return nullptr; }; +#pragma warning(pop) }; } // namespace ink::runtime::internal diff --git a/inkcpp_compiler/json.hpp b/inkcpp_compiler/json.hpp index 4d1a37ad..b289fecd 100644 --- a/inkcpp_compiler/json.hpp +++ b/inkcpp_compiler/json.hpp @@ -1,3 +1,4 @@ +#pragma warning(push, 1) // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.11.2 @@ -24594,3 +24595,4 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC #endif // INCLUDE_NLOHMANN_JSON_HPP_ +#pragma warning(pop) diff --git a/shared/public/config.h b/shared/public/config.h index 4bb0877f..c89fff51 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -8,7 +8,7 @@ #ifdef INKCPP_API # define INK_ENABLE_UNREAL -#elif INKCPP_BUILD_CLIB +#elif defined(INKCPP_BUILD_CLIB) # define INK_ENABLE_CSTD #else # define INK_ENABLE_STL diff --git a/shared/public/system.h b/shared/public/system.h index d1b70816..56ba3cf7 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -201,7 +201,7 @@ template namespace runtime::internal { - constexpr unsigned abs(int i) { return i < 0 ? -i : i; } + constexpr unsigned abs(int i) { return static_cast(i < 0 ? -i : i); } template struct always_false { From 507c8655e310d259d5a1fa1299f2ccbc343de849 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 21 Nov 2025 17:36:45 +0100 Subject: [PATCH 03/24] refactor: Removed memory leaks in test scenarios refactor: add simple flag to `managad_array` because msvc hicks on implace overwrite of abstract classes --- Documentation/cmake_example/main.cpp | 3 +- README.md | 7 +- inkcpp/array.h | 49 ++++++-- inkcpp/avl_array.h | 3 + inkcpp/include/list.h | 14 ++- inkcpp/list_impl.h | 64 ++++++----- inkcpp/list_table.cpp | 2 +- inkcpp/list_table.h | 32 +++--- inkcpp/snapshot_interface.h | 15 ++- inkcpp_cl/test.cpp | 4 +- inkcpp_test/EmptyStringForDivert.cpp | 4 +- .../ExternalFunctionsExecuteProperly.cpp | 5 +- inkcpp_test/FallbackFunction.cpp | 4 +- inkcpp_test/Fixes.cpp | 32 +++--- inkcpp_test/Globals.cpp | 24 ++-- inkcpp_test/InkyJson.cpp | 4 +- inkcpp_test/LabelCondition.cpp | 58 +++++----- inkcpp_test/Lists.cpp | 32 +++--- inkcpp_test/LookaheadSafe.cpp | 2 +- inkcpp_test/MoveTo.cpp | 8 +- inkcpp_test/NewLines.cpp | 8 +- inkcpp_test/NoEarlyTags.cpp | 4 +- inkcpp_test/Observer.cpp | 105 +++++++++--------- inkcpp_test/SpaceAfterBracketChoice.cpp | 9 +- inkcpp_test/Tags.cpp | 10 +- inkcpp_test/ThirdTierChoiceAfterBrackets.cpp | 5 +- inkcpp_test/UTF8.cpp | 28 +++-- 27 files changed, 294 insertions(+), 241 deletions(-) diff --git a/Documentation/cmake_example/main.cpp b/Documentation/cmake_example/main.cpp index bf661cbf..4ab30388 100644 --- a/Documentation/cmake_example/main.cpp +++ b/Documentation/cmake_example/main.cpp @@ -4,6 +4,7 @@ #include #include +#include #include using namespace ink::runtime; @@ -14,7 +15,7 @@ int main() { ink::compiler::run("test.ink.json", "test.bin"); // Load ink binary story, generated from the inkCPP compiler - story* myInk = story::from_file("test.bin"); + std::unique_ptr myInk{story::from_file("test.bin")}; // Create a new thread runner thread = myInk->new_runner(); diff --git a/README.md b/README.md index fa9a2602..64478826 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Alternativly is the latest version of the UE plugin can be downloaded from the [ Place the content of this file at your plugin folder of your UE project and at the next start up it will be intigrated. -A example project can be found [here](https://jbenda.github.io/inkcpp/unreal/InkCPP_DEMO.zip). And here the [Documentation](https://jbenda.github.io/inkcpp/html/group__unreal.html). +A example project can be found [here](https://jbenda.github.io/inkcpp/unreal/InkCPP_DEMO.zip). And here the [Documentation](https://jbenda.github.io/inkcpp/html/group__unreal.html). Code for the Unreal plugin is located in the `unreal` directory. In order to install it, run ```sh @@ -104,6 +104,7 @@ Instructions: #include #include #include +#include using namespace ink::runtime; @@ -112,7 +113,7 @@ int MyInkFunction(int a, int b) { return a + b; } ... // Load ink binary story, generated from the inkCPP compiler -story* myInk = story::from_file("test.bin"); +std::unique_ptr myInk{story::from_file("test.bin")}; // Create a new thread runner thread = myInk->new_runner(); @@ -208,6 +209,6 @@ The python bindnigs are defined in `inkcpp_python` subfolder. ## Dependencies The compiler depends on Nlohmann's JSON library and the C++ STL. -The runtime does not depend on either. If `INK_ENABLE_STL` is defined then STL extensions are added such as stream operators and `std::string` support. If `INK_ENABLE_UNREAL`, then FStrings, Delegates and other Unreal classes will be supported. +The runtime does not depend on either. If `INK_ENABLE_STL` is defined then STL extensions are added such as stream operators and `std::string` support. If `INK_ENABLE_UNREAL`, then FStrings, Delegates and other Unreal classes will be supported. NOTE: There is still some lingering C standard library calls in the runtime. I will be guarding them with an `INK_ENABLE_CSTD` or something soon. diff --git a/inkcpp/array.h b/inkcpp/array.h index c4d10bcc..d1a38587 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -15,7 +15,15 @@ namespace ink::runtime::internal { -template +/** Managed array of objects. + * + * @tparam simple if the object has a trivial destructor, so delete[](char*) can be used instead of + * calling the constructor. + * @tparam dynamic if the memory should be allocated on the heap and grow if needed + * @tparam initialCapacitiy number of elements to allocate at construction, if !dynamic, this is + * allocated in place and can not be changed. + */ +template class managed_array : public snapshot_interface { public: @@ -25,7 +33,11 @@ class managed_array : public snapshot_interface , _size{0} { if constexpr (dynamic) { - _dynamic_data = new T[initialCapacity]; + if constexpr (simple) { + _dynamic_data = reinterpret_cast(new char[sizeof(T) * initialCapacity]); + } else { + _dynamic_data = new T[initialCapacity]; + } } } @@ -37,7 +49,11 @@ class managed_array : public snapshot_interface virtual ~managed_array() { if constexpr (dynamic) { - delete[] _dynamic_data; + if constexpr (simple) { + delete[] reinterpret_cast(_dynamic_data); + } else { + delete[] _dynamic_data; + } } } @@ -233,21 +249,30 @@ class managed_restorable_array : public managed_array -void managed_array::extend(size_t capacity) +template +void managed_array::extend(size_t capacity) { static_assert(dynamic, "Can only extend if array is dynamic!"); size_t new_capacity = capacity > _capacity ? capacity : 1.5f * _capacity; if (new_capacity < 5) { new_capacity = 5; } - T* new_data = new T[new_capacity]; + T* new_data = nullptr; + if constexpr (simple) { + new_data = reinterpret_cast(new char[sizeof(T) * new_capacity]); + } else { + new_data = new T[new_capacity]; + } for (size_t i = 0; i < _capacity; ++i) { new_data[i] = _dynamic_data[i]; } - delete[] _dynamic_data; + if constexpr (simple) { + delete[] reinterpret_cast(_dynamic_data); + } else { + delete[] _dynamic_data; + } _dynamic_data = new_data; _capacity = new_capacity; } @@ -459,15 +484,15 @@ class allocated_restorable_array final : public basic_restorable_array T* new_buffer = new T[new_capacity]; if (_buffer) { for (size_t i = 0; i < base::capacity(); ++i) { - new_buffer[i] = _buffer[i]; + new_buffer[i] = _buffer[i]; // copy temp - new_buffer[i + base::capacity()] = _buffer[i + base::capacity()]; + new_buffer[i + n] = _buffer[i + base::capacity()]; } delete[] _buffer; } - for (size_t i = base::capacity(); i < new_capacity / 2; ++i) { - new_buffer[i] = _initialValue; - new_buffer[i + base::capacity()] = _nullValue; + for (size_t i = base::capacity(); i < n; ++i) { + new_buffer[i] = _initialValue; + new_buffer[i + n] = _nullValue; } _buffer = new_buffer; diff --git a/inkcpp/avl_array.h b/inkcpp/avl_array.h index 30cd36fd..2e3cd698 100644 --- a/inkcpp/avl_array.h +++ b/inkcpp/avl_array.h @@ -208,6 +208,9 @@ class avl_array delete[] val_; delete[] balance_; delete[] child_; + if (parent_) { + delete[] parent_; + } } } diff --git a/inkcpp/include/list.h b/inkcpp/include/list.h index 2d7d23a5..3f4ddd35 100644 --- a/inkcpp/include/list.h +++ b/inkcpp/include/list.h @@ -48,6 +48,8 @@ class list_interface { } + virtual ~list_interface() {} + /** iterater for flags in a list * @todo implement `operator->` */ @@ -69,9 +71,7 @@ class list_interface protected: /** @private */ - iterator( - const char* flag_name, const list_interface& list, int i, bool one_list_only = false - ) + iterator(const char* flag_name, const list_interface& list, int i, bool one_list_only = false) : _flag_name(flag_name) , _list_name(nullptr) , _list(list) @@ -99,7 +99,7 @@ class list_interface }; /** access value the iterator is pointing to */ - Flag operator*() const { return Flag{ _flag_name, _list_name }; }; + Flag operator*() const { return Flag{_flag_name, _list_name}; }; /** continue iterator to next value */ iterator& operator++() @@ -123,10 +123,12 @@ class list_interface #pragma warning(pop) - virtual ~list_interface() {} #pragma warning(push) -#pragma warning(disable : 4100, justification : "non functional prototypes do not need the argument.") +#pragma warning( \ + disable : 4100, justification : "non functional prototypes do not need the argument." \ +) + /** checks if a flag is contained in the list */ virtual bool contains(const char* flag) const { diff --git a/inkcpp/list_impl.h b/inkcpp/list_impl.h index ea7b1dd6..ab8f2ca8 100644 --- a/inkcpp/list_impl.h +++ b/inkcpp/list_impl.h @@ -8,32 +8,38 @@ #include "list.h" -namespace ink::runtime::internal { - class list_table; - class value; - class list_impl final : public list_interface { - public: - list_impl(list_table& table, int lid) : list_interface(table, lid) {} - int get_lid() const { return _list; } - - bool contains(const char* flag_name) const override; - void add(const char* flag_name) override; - void remove(const char* flag_name) override; - - list_interface::iterator begin() const override { - return ++new_iterator(nullptr, 0); - } - - list_interface::iterator begin(const char* list_name) const override; - list_interface::iterator end() const override { - return new_iterator(nullptr, -1); - } - - private: - friend ink::runtime::internal::value; - - /// @todo wrong iteration order, first lists then flags - void next(const char*& flag_name, const char*& list_name, int& i, bool one_list_only) - const override; - }; -} +namespace ink::runtime::internal +{ +class list_table; +class value; + +class list_impl final : public list_interface +{ +public: + list_impl(list_table& table, int lid) + : list_interface(table, lid) + { + } + + ~list_impl() {} + + int get_lid() const { return _list; } + + bool contains(const char* flag_name) const override; + void add(const char* flag_name) override; + void remove(const char* flag_name) override; + + list_interface::iterator begin() const override { return ++new_iterator(nullptr, 0); } + + list_interface::iterator begin(const char* list_name) const override; + + list_interface::iterator end() const override { return new_iterator(nullptr, -1); } + +private: + friend ink::runtime::internal::value; + + /// @todo wrong iteration order, first lists then flags + void next(const char*& flag_name, const char*& list_name, int& i, bool one_list_only) + const override; +}; +} // namespace ink::runtime::internal diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index b4e16228..73c00b93 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -431,7 +431,7 @@ list_table::list list_table::sub(list arg, int n) for (int j = listBegin(i); j < _list_end[i]; ++j) { if (hasFlag(l, j)) { int value = _flag_values[j] - n; - for (int k = j - 1; k >= listBegin(i); --k) { + for (size_t k = j - 1; k >= listBegin(i) && k != ~0U; --k) { if (_flag_values[k] == value) { setFlag(o, k); has_flag = true; diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index 9baca261..3ad069e3 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -256,7 +256,7 @@ class list_table : public snapshot_interface list_interface* handout_list(list); private: - void copy_lists(const data_t* src, data_t* dst); + void copy_lists(const data_t* src, data_t* dst); static constexpr size_t bits_per_data = sizeof(data_t) * 8U; size_t listBegin(int lid) const { return lid == 0 ? 0 : _list_end[static_cast(lid - 1)]; } @@ -319,21 +319,23 @@ class list_table : public snapshot_interface auto flagStartMask() const { struct { - size_t segment; + size_t segment; data_t mask; } res{numLists() / bits_per_data, ~static_cast(0) >> (numLists() % bits_per_data)}; return res; } - template + template using managed_array = managed_array < T, - config<0, abs(config)>; + config<0, abs(config), simple>; static constexpr long maxMemorySize = (config::maxListTypes < 0 || config::maxFlags < 0 || config::maxLists < 0 ? -1 : 1) - * static_cast(segmentsFromBits(abs(config::maxListTypes) + abs(config::maxFlags), sizeof(data_t)) - * static_cast(abs(config::maxLists))); + * static_cast( + segmentsFromBits(abs(config::maxListTypes) + abs(config::maxFlags), sizeof(data_t)) + * static_cast(abs(config::maxLists)) + ); int _entrySize; ///< entry size in data_t // entries (created lists) @@ -341,12 +343,12 @@ class list_table : public snapshot_interface managed_array _entry_state; // defined list (meta data) - managed_array _list_end; - managed_array _flag_names; - managed_array _flag_values; - managed_array _list_names; + managed_array _list_end; + managed_array _flag_names; + managed_array _flag_values; + managed_array _list_names; /// keep track over lists accessed with get_var, and clear then at gc time - managed_array _list_handouts; + managed_array _list_handouts; bool _valid; @@ -371,7 +373,8 @@ class list_table : public snapshot_interface void carry() { if (_pos.flag.flag - == _list._list_end[static_cast(_pos.flag.list_id)] - _list.listBegin(static_cast(_pos.flag.list_id))) { + == _list._list_end[static_cast(_pos.flag.list_id)] + - _list.listBegin(static_cast(_pos.flag.list_id))) { _pos.flag.flag = 0; ++_pos.flag.list_id; } @@ -409,8 +412,11 @@ class list_table : public snapshot_interface : _list{o._list} , _data{o._data} , _pos{o._pos} - {} + { + } + named_flag_itr& operator=(const named_flag_itr&) = delete; + bool operator!=(const named_flag_itr& o) const { return _pos.flag != o._pos.flag; } named_flag_itr(const list_table& list, const data_t* filter) diff --git a/inkcpp/snapshot_interface.h b/inkcpp/snapshot_interface.h index 0bf8855d..997600f3 100644 --- a/inkcpp/snapshot_interface.h +++ b/inkcpp/snapshot_interface.h @@ -6,13 +6,12 @@ */ #pragma once -#include "snapshot.h" #include namespace ink::runtime::internal { class globals_impl; -template +template class managed_array; class snap_tag; class string_table; @@ -52,9 +51,9 @@ class snapshot_interface struct snapper { const string_table& strings; const char* story_string_table; - const snap_tag* runner_tags = nullptr; - snapper() = delete; - snapper& operator=(const snapper&) = delete; + const snap_tag* runner_tags = nullptr; + snapper() = delete; + snapper& operator=(const snapper&) = delete; }; struct loader { @@ -66,7 +65,10 @@ class snapshot_interface }; #pragma warning(push) -#pragma warning(disable : 4100, justification : "non functional prototypes do not need the argument." ) +#pragma warning( \ + disable : 4100, justification : "non functional prototypes do not need the argument." \ +) + size_t snap(unsigned char* data, snapper&) const { inkFail("Snap function not implemented"); @@ -78,6 +80,7 @@ class snapshot_interface inkFail("Snap function not implemented"); return nullptr; }; + #pragma warning(pop) }; } // namespace ink::runtime::internal diff --git a/inkcpp_cl/test.cpp b/inkcpp_cl/test.cpp index b5b48a79..3982e938 100644 --- a/inkcpp_cl/test.cpp +++ b/inkcpp_cl/test.cpp @@ -124,8 +124,8 @@ bool test(const std::string& inkFilename) } // Load story - auto file = story::from_file("test.bin"); - auto runner = file->new_runner(); + std::unique_ptr file{story::from_file("test.bin")}; + auto runner = file->new_runner(); while (true) { // Run continuously diff --git a/inkcpp_test/EmptyStringForDivert.cpp b/inkcpp_test/EmptyStringForDivert.cpp index 341f0754..cd40b0cb 100644 --- a/inkcpp_test/EmptyStringForDivert.cpp +++ b/inkcpp_test/EmptyStringForDivert.cpp @@ -12,8 +12,8 @@ SCENARIO("a story with a white space infront of an conditional Divert", "[Output // based on https://github.com/JBenda/inkcpp/issues/71 GIVEN("A story") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "EmptyStringForDivert.bin"); - runner thread = ink->new_runner(); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "EmptyStringForDivert.bin")}; + runner thread = ink->new_runner(); WHEN("run") { diff --git a/inkcpp_test/ExternalFunctionsExecuteProperly.cpp b/inkcpp_test/ExternalFunctionsExecuteProperly.cpp index 747c8fde..57ed8a78 100644 --- a/inkcpp_test/ExternalFunctionsExecuteProperly.cpp +++ b/inkcpp_test/ExternalFunctionsExecuteProperly.cpp @@ -12,8 +12,9 @@ SCENARIO("a story with an external function evaluates the function at the right { GIVEN("a story with an external function") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ExternalFunctionsExecuteProperly.bin"); - auto thread = ink->new_runner().cast(); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR + "ExternalFunctionsExecuteProperly.bin")}; + auto thread = ink->new_runner().cast(); std::stringstream debug; thread->set_debug_enabled(&debug); diff --git a/inkcpp_test/FallbackFunction.cpp b/inkcpp_test/FallbackFunction.cpp index 35990ac1..2d18cd9d 100644 --- a/inkcpp_test/FallbackFunction.cpp +++ b/inkcpp_test/FallbackFunction.cpp @@ -14,8 +14,8 @@ SCENARIO("run a story with external function and fallback function", "[external { GIVEN("story with two external functions, one with fallback") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "FallBack.bin"); - runner thread = ink->new_runner(); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "FallBack.bin")}; + runner thread = ink->new_runner(); WHEN("bind both external functions") { diff --git a/inkcpp_test/Fixes.cpp b/inkcpp_test/Fixes.cpp index 33134408..9dd52aa9 100644 --- a/inkcpp_test/Fixes.cpp +++ b/inkcpp_test/Fixes.cpp @@ -1,4 +1,5 @@ #include "catch.hpp" +#include "snapshot.h" #include "system.h" #include @@ -15,9 +16,9 @@ SCENARIO("string_table fill up #97", "[fixes]") { GIVEN("story murder_scene") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "murder_scene.bin"); - globals globStore = ink->new_globals(); - runner main = ink->new_runner(globStore); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "murder_scene.bin")}; + globals globStore = ink->new_globals(); + runner main = ink->new_runner(globStore); WHEN("Run first choice 50 times") { @@ -62,10 +63,10 @@ SCENARIO("unknown command _ #109", "[fixes]") for (size_t i = 0; i < out_str.size(); ++i) { data[i] = out_str[i]; } - auto ink = story::from_binary(data, out_str.size()); - globals globStore = ink->new_globals(); - runner main = ink->new_runner(globStore); - std::string story = main->getall(); + std::unique_ptr ink{story::from_binary(data, out_str.size())}; + globals globStore = ink->new_globals(); + runner main = ink->new_runner(globStore); + std::string story = main->getall(); THEN("expect correct output") { REQUIRE(res.warnings.size() == 0); @@ -87,16 +88,16 @@ SCENARIO("snapshot failed inside execution _ #111", "[fixes]") { GIVEN("story with multiline output with a knot") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "111_crash.bin"); - auto ink2 = story::from_file(INK_TEST_RESOURCE_DIR "111_crash.bin"); - runner thread = ink->new_runner(); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "111_crash.bin")}; + std::unique_ptr ink2{story::from_file(INK_TEST_RESOURCE_DIR "111_crash.bin")}; + runner thread = ink->new_runner(); WHEN("run store and reload") { auto line = thread->getline(); THEN("outputs first line") { REQUIRE(line == "First line of text\n"); } - auto snapshot = thread->create_snapshot(); - runner thread2 = ink2->new_runner_from_snapshot(*snapshot); - line = thread->getline(); + std::unique_ptr snapshot{thread->create_snapshot()}; + runner thread2 = ink2->new_runner_from_snapshot(*snapshot); + line = thread->getline(); THEN("outputs second line") { REQUIRE(line == "Second line of test\n"); } } } @@ -106,8 +107,9 @@ SCENARIO("missing leading whitespace inside choice-only text and glued text _ #1 { GIVEN("story with problematic text") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "130_131_missing_whitespace.bin"); - runner thread = ink->new_runner(); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR + "130_131_missing_whitespace.bin")}; + runner thread = ink->new_runner(); WHEN("run story") { auto line = thread->getline(); diff --git a/inkcpp_test/Globals.cpp b/inkcpp_test/Globals.cpp index c2a6e8a1..2a9cc3f0 100644 --- a/inkcpp_test/Globals.cpp +++ b/inkcpp_test/Globals.cpp @@ -9,13 +9,13 @@ using namespace ink::runtime; SCENARIO("run story with global variable", "[global variables]") { - GIVEN ("a story with global variables") + GIVEN("a story with global variables") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "GlobalStory.bin"); - globals globStore = ink->new_globals(); - runner thread = ink->new_runner(globStore); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "GlobalStory.bin")}; + globals globStore = ink->new_globals(); + runner thread = ink->new_runner(globStore); - WHEN( "just runs") + WHEN("just runs") { THEN("variables should contain values as in inkScript") { @@ -24,12 +24,10 @@ SCENARIO("run story with global variable", "[global variables]") REQUIRE(*globStore->get("friendly_name_of_player") == std::string{"Jackie"}); } } - WHEN ("edit number") + WHEN("edit number") { - bool resi - = globStore->set("age", 30); - bool resc - = globStore->set("friendly_name_of_player", "Freddy"); + bool resi = globStore->set("age", 30); + bool resc = globStore->set("friendly_name_of_player", "Freddy"); THEN("execution should success") { REQUIRE(resi == true); @@ -41,7 +39,7 @@ SCENARIO("run story with global variable", "[global variables]") REQUIRE(*globStore->get("age") == 30); REQUIRE(*globStore->get("friendly_name_of_player") == std::string{"Freddy"}); } - WHEN ("something added to string") + WHEN("something added to string") { // concat in GlobalsStory.ink thread->getall(); @@ -51,9 +49,9 @@ SCENARIO("run story with global variable", "[global variables]") } } } - WHEN ("name or type not exist") + WHEN("name or type not exist") { - auto wrongType = globStore->get("age"); + auto wrongType = globStore->get("age"); auto notExistingName = globStore->get("foo"); THEN("should return nullptr") { diff --git a/inkcpp_test/InkyJson.cpp b/inkcpp_test/InkyJson.cpp index 1de37df4..f97ab32e 100644 --- a/inkcpp_test/InkyJson.cpp +++ b/inkcpp_test/InkyJson.cpp @@ -19,8 +19,8 @@ SCENARIO("run inklecate 1.1.1 story") { auto input_file = std::string(INK_TEST_RESOURCE_DIR "simple-1.1.1-") + compiler + ".json"; ink::compiler::run(input_file.c_str(), "simple.bin"); - auto ink = story::from_file("simple.bin"); - runner thread = ink->new_runner(); + std::unique_ptr ink{story::from_file("simple.bin")}; + runner thread = ink->new_runner(); THEN("Expect normal output") { diff --git a/inkcpp_test/LabelCondition.cpp b/inkcpp_test/LabelCondition.cpp index 3b6744d6..77737741 100644 --- a/inkcpp_test/LabelCondition.cpp +++ b/inkcpp_test/LabelCondition.cpp @@ -8,52 +8,52 @@ using namespace ink::runtime; -SCENARIO( "run story with hidden choice" ) +SCENARIO("run story with hidden choice") { GIVEN("a story with choice visible by second visit") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "LabelConditionStory.bin"); - globals globals = ink->new_globals(); - runner thread = ink->new_runner( globals ); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "LabelConditionStory.bin")}; + globals globals = ink->new_globals(); + runner thread = ink->new_runner(globals); - WHEN( "normal run" ) + WHEN("normal run") { std::string out = thread->getall(); - REQUIRE( thread->num_choices() == 1 ); - std::string choice1a = thread->get_choice( 0 )->text(); - thread->choose( 0 ); + REQUIRE(thread->num_choices() == 1); + std::string choice1a = thread->get_choice(0)->text(); + thread->choose(0); std::string out2 = thread->getall(); - REQUIRE( thread->num_choices() == 1 ); - std::string choice2a = thread->get_choice( 0 )->text(); + REQUIRE(thread->num_choices() == 1); + std::string choice2a = thread->get_choice(0)->text(); - THEN( "second choice keeps hidden" ) + THEN("second choice keeps hidden") { - REQUIRE( out == "" ); - REQUIRE( choice1a == "labeled choice" ); - REQUIRE( out2 == "" ); - REQUIRE( choice2a == "labeled choice" ); + REQUIRE(out == ""); + REQUIRE(choice1a == "labeled choice"); + REQUIRE(out2 == ""); + REQUIRE(choice2a == "labeled choice"); } } - WHEN( "set global variable to enable hidden choice" ) + WHEN("set global variable to enable hidden choice") { - globals->set( "bool_variable", false ); + globals->set("bool_variable", false); std::string out = thread->getall(); - REQUIRE( thread->num_choices() == 1 ); - std::string choice1a = thread->get_choice( 0 )->text(); - thread->choose( 0 ); + REQUIRE(thread->num_choices() == 1); + std::string choice1a = thread->get_choice(0)->text(); + thread->choose(0); std::string out2 = thread->getall(); - REQUIRE( thread->num_choices() == 2 ); - std::string choice2a = thread->get_choice( 0 )->text(); - std::string choice2b = thread->get_choice( 1 )->text(); + REQUIRE(thread->num_choices() == 2); + std::string choice2a = thread->get_choice(0)->text(); + std::string choice2b = thread->get_choice(1)->text(); - THEN( "second choice is visible" ) + THEN("second choice is visible") { - REQUIRE( out == "" ); - REQUIRE( choice1a == "labeled choice" ); - REQUIRE( out2 == "" ); - REQUIRE( choice2a == "labeled choice" ); - REQUIRE( choice2b == "hidden choice" ); + REQUIRE(out == ""); + REQUIRE(choice1a == "labeled choice"); + REQUIRE(out2 == ""); + REQUIRE(choice2a == "labeled choice"); + REQUIRE(choice2b == "hidden choice"); } } } diff --git a/inkcpp_test/Lists.cpp b/inkcpp_test/Lists.cpp index 51be2d8a..d5c86526 100644 --- a/inkcpp_test/Lists.cpp +++ b/inkcpp_test/Lists.cpp @@ -14,8 +14,8 @@ SCENARIO("List logic operations", "[lists]") { GIVEN("a demo story") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ListLogicStory.bin"); - runner thread = ink->new_runner(); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "ListLogicStory.bin")}; + runner thread = ink->new_runner(); WHEN("just run") { std::string out = thread->getall(); @@ -45,17 +45,18 @@ Hey } } } + SCENARIO("run a story with lists", "[lists]") { GIVEN("a story with multi lists") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ListStory.bin"); - globals globals = ink->new_globals(); - runner thread = ink->new_runner(globals); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "ListStory.bin")}; + globals globals = ink->new_globals(); + runner thread = ink->new_runner(globals); WHEN("just run") { - std::string out = thread->getall(); + std::string out = thread->getall(); std::string choice1 = thread->get_choice(0)->text(); thread->choose(0); std::string out2 = thread->getall(); @@ -72,8 +73,8 @@ SCENARIO("run a story with lists", "[lists]") std::string out = thread->getall(); std::string choice1 = thread->get_choice(0)->text(); thread->choose(0); - - list l1 = *globals->get("list"); + auto x = globals->get("list"); + list l1 = x.value(); l1->add("animals.dog"); l1->remove("colors.red"); REQUIRE(globals->set("list", l1)); @@ -88,7 +89,7 @@ SCENARIO("run a story with lists", "[lists]") WHEN("modify") { - std::string out = thread->getall(); + std::string out = thread->getall(); std::string choice1 = thread->get_choice(0)->text(); thread->choose(0); @@ -130,13 +131,14 @@ SCENARIO("run a story with lists", "[lists]") THEN("should iterate all contained flags") { l1 = *globals->get("list"); - for(auto flag : *l1) { + for (auto flag : *l1) { INFO(flag); - REQUIRE((strcmp(flag.list_name, "colors")==0 || strcmp(flag.list_name, "animals") == 0)); - REQUIRE((strcmp(flag.flag_name, "bird") == 0 - || strcmp(flag.flag_name, "dog") == 0 - || strcmp(flag.flag_name, "yellow") == 0 - )); + REQUIRE((strcmp(flag.list_name, "colors") == 0 || strcmp(flag.list_name, "animals") == 0) + ); + REQUIRE( + (strcmp(flag.flag_name, "bird") == 0 || strcmp(flag.flag_name, "dog") == 0 + || strcmp(flag.flag_name, "yellow") == 0) + ); } } } diff --git a/inkcpp_test/LookaheadSafe.cpp b/inkcpp_test/LookaheadSafe.cpp index 7ae878e0..1dd5c1db 100644 --- a/inkcpp_test/LookaheadSafe.cpp +++ b/inkcpp_test/LookaheadSafe.cpp @@ -12,7 +12,7 @@ SCENARIO("a story with external functions and glue", "[external]") { GIVEN("the story") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "LookaheadSafe.bin"); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "LookaheadSafe.bin")}; int cnt = 0; auto foo = [&cnt]() { diff --git a/inkcpp_test/MoveTo.cpp b/inkcpp_test/MoveTo.cpp index 87803bf3..fa3c3f2b 100644 --- a/inkcpp_test/MoveTo.cpp +++ b/inkcpp_test/MoveTo.cpp @@ -12,10 +12,10 @@ SCENARIO("run a story, but jump around manually", "[move_to]") { GIVEN("a story with side talking points") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "MoveTo.bin"); - globals globStore = ink->new_globals(); - runner main = ink->new_runner(globStore); - runner side = ink->new_runner(globStore); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "MoveTo.bin")}; + globals globStore = ink->new_globals(); + runner main = ink->new_runner(globStore); + runner side = ink->new_runner(globStore); WHEN("just run main story") { diff --git a/inkcpp_test/NewLines.cpp b/inkcpp_test/NewLines.cpp index 35350ba9..fb3664a6 100644 --- a/inkcpp_test/NewLines.cpp +++ b/inkcpp_test/NewLines.cpp @@ -7,8 +7,8 @@ using namespace ink::runtime; -auto lines_ink = story::from_file(INK_TEST_RESOURCE_DIR "LinesStory.bin"); -runner lines_thread = lines_ink->new_runner(); +std::unique_ptr lines_ink{story::from_file(INK_TEST_RESOURCE_DIR "LinesStory.bin")}; +runner lines_thread = lines_ink->new_runner(); SCENARIO("a story has the proper line breaks", "[lines]") { @@ -52,8 +52,8 @@ SCENARIO("a story has the proper line breaks", "[lines]") } GIVEN("a complex story") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "TheIntercept.bin"); - runner thread = ink->new_runner(); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "TheIntercept.bin")}; + runner thread = ink->new_runner(); // based on issue #82 WHEN("run sequence 1 3 3 3 2 3") { diff --git a/inkcpp_test/NoEarlyTags.cpp b/inkcpp_test/NoEarlyTags.cpp index 5ca5cf3a..16ab40f3 100644 --- a/inkcpp_test/NoEarlyTags.cpp +++ b/inkcpp_test/NoEarlyTags.cpp @@ -7,8 +7,8 @@ using namespace ink::runtime; -auto tg_ink = story::from_file(INK_TEST_RESOURCE_DIR "NoEarlyTags.bin"); -auto tg_thread = tg_ink->new_runner(); +std::unique_ptr tg_ink{story::from_file(INK_TEST_RESOURCE_DIR "NoEarlyTags.bin")}; +auto tg_thread = tg_ink->new_runner(); SCENARIO("Story with tags and glues", "[tags][glue]") { diff --git a/inkcpp_test/Observer.cpp b/inkcpp_test/Observer.cpp index 77286fe4..8bb22fda 100644 --- a/inkcpp_test/Observer.cpp +++ b/inkcpp_test/Observer.cpp @@ -1,4 +1,5 @@ #include "catch.hpp" +#include "system.h" #include <../runner_impl.h> #include @@ -13,9 +14,9 @@ SCENARIO("Observer", "[variables][observer]") { GIVEN("a story which changes variables") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ObserverStory.bin"); - auto globals = ink->new_globals(); - auto thread = ink->new_runner(globals).cast(); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "ObserverStory.bin")}; + auto globals = ink->new_globals(); + auto thread = ink->new_runner(globals).cast(); std::stringstream debug; thread->set_debug_enabled(&debug); @@ -29,19 +30,19 @@ SCENARIO("Observer", "[variables][observer]") int var1_cnt = 0; auto var1 = [&var1_cnt](int32_t i) { if (var1_cnt++ == 0) { - CHECK(i == 1); - } else { - CHECK(i == 5); - } + CHECK(i == 1); + } else { + CHECK(i == 5); + } }; int var2_cnt = 0; auto var2 = [&var2_cnt](const char* s) { std::string str(s); if (var2_cnt++ == 0) { - CHECK(str == "hello"); - } else { - CHECK(str == "test"); - } + CHECK(str == "hello"); + } else { + CHECK(str == "test"); + } }; globals->observe("var1", var1); @@ -87,10 +88,10 @@ SCENARIO("Observer", "[variables][observer]") int var1_cnt = 0; auto var1 = [&var1_cnt](int32_t i) { if (var1_cnt++ < 2) { - CHECK(i == 1); - } else { - CHECK(i == 5); - } + CHECK(i == 1); + } else { + CHECK(i == 5); + } }; globals->observe("var1", var1); globals->observe("var1", var1); @@ -103,7 +104,7 @@ SCENARIO("Observer", "[variables][observer]") { auto var1 = [](uint32_t i) { }; - CHECK_THROWS(globals->observe("var1", var1)); + CHECK_THROWS_AS(globals->observe("var1", var1), ink::ink_exception); } WHEN("Just get pinged") { @@ -122,13 +123,13 @@ SCENARIO("Observer", "[variables][observer]") int var1_cnt = 0; auto var1 = [&var1_cnt](int32_t i, ink::optional o_i) { if (var1_cnt++ == 0) { - CHECK(i == 1); - CHECK_FALSE(o_i.has_value()); - } else { - CHECK(i == 5); - CHECK(o_i.has_value()); - CHECK(o_i.value() == 1); - } + CHECK(i == 1); + CHECK_FALSE(o_i.has_value()); + } else { + CHECK(i == 5); + CHECK(o_i.has_value()); + CHECK(o_i.value() == 1); + } }; int var2_cnt = 0; @@ -161,13 +162,13 @@ SCENARIO("Observer", "[variables][observer]") auto var1 = [&var1_cnt, &globals](int32_t i) { ++var1_cnt; if (var1_cnt == 1) { - CHECK(i == 1); - } else if (var1_cnt == 2) { - CHECK(i == 5); - globals->set("var1", 8); - } else if (var1_cnt == 3) { - CHECK(i == 8); - } + CHECK(i == 1); + } else if (var1_cnt == 2) { + CHECK(i == 5); + globals->set("var1", 8); + } else if (var1_cnt == 3) { + CHECK(i == 8); + } }; globals->observe("var1", var1); std::string out = thread->getall(); @@ -182,13 +183,13 @@ SCENARIO("Observer", "[variables][observer]") auto var1 = [&var1_cnt, &globals](int32_t i) { ++var1_cnt; if (var1_cnt == 1) { - CHECK(i == 1); - globals->set("var1", 8); - } else if (var1_cnt == 2) { - CHECK(i == 8); - } else if (var1_cnt == 3) { - CHECK(i == 5); - } + CHECK(i == 1); + globals->set("var1", 8); + } else if (var1_cnt == 2) { + CHECK(i == 8); + } else if (var1_cnt == 3) { + CHECK(i == 5); + } }; globals->observe("var1", var1); std::string out = thread->getall(); @@ -203,16 +204,16 @@ SCENARIO("Observer", "[variables][observer]") auto var1 = [&var1_cnt, &globals](int32_t i) { ++var1_cnt; if (var1_cnt == 1) { - CHECK(i == 1); - globals->set("var1", 8); - } else if (var1_cnt == 2) { - CHECK(i == 8); - globals->set("var1", 10); - } else if (var1_cnt == 3) { - CHECK(i == 10); - } else if (var1_cnt == 4) { - CHECK(i == 5); - } + CHECK(i == 1); + globals->set("var1", 8); + } else if (var1_cnt == 2) { + CHECK(i == 8); + globals->set("var1", 10); + } else if (var1_cnt == 3) { + CHECK(i == 10); + } else if (var1_cnt == 4) { + CHECK(i == 5); + } }; globals->observe("var1", var1); std::string out = thread->getall(); @@ -226,11 +227,11 @@ SCENARIO("Observer", "[variables][observer]") int var1_cnt = 0; auto var1 = [&var1_cnt, &globals](int32_t i) { if (var1_cnt++ == 0) { - CHECK(i == 1); - } else { - CHECK(i == 5); - globals->set("var2", "didum"); - } + CHECK(i == 1); + } else { + CHECK(i == 5); + globals->set("var2", "didum"); + } }; int var2_cnt = 0; auto var2 = [&var2_cnt]() { diff --git a/inkcpp_test/SpaceAfterBracketChoice.cpp b/inkcpp_test/SpaceAfterBracketChoice.cpp index b11cb2e6..c4e692c6 100644 --- a/inkcpp_test/SpaceAfterBracketChoice.cpp +++ b/inkcpp_test/SpaceAfterBracketChoice.cpp @@ -11,8 +11,8 @@ SCENARIO("a story with bracketed choices and spaces can choose correctly", "[cho { GIVEN("a story with line breaks") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ChoiceBracketStory.bin"); - runner thread = ink->new_runner(); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "ChoiceBracketStory.bin")}; + runner thread = ink->new_runner(); thread->getall(); WHEN("start thread") { @@ -35,10 +35,7 @@ SCENARIO("a story with bracketed choices and spaces can choose correctly", "[cho { thread->choose(1); thread->getall(); - THEN("still has choices") - { - REQUIRE(thread->has_choices()); - } + THEN("still has choices") { REQUIRE(thread->has_choices()); } } } } diff --git a/inkcpp_test/Tags.cpp b/inkcpp_test/Tags.cpp index e30a5d1c..17ffb06d 100644 --- a/inkcpp_test/Tags.cpp +++ b/inkcpp_test/Tags.cpp @@ -12,10 +12,10 @@ using namespace ink::runtime; SCENARIO("tags", "[ahf]") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "AHF.bin"); - runner thread = ink->new_runner(); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR "AHF.bin")}; + runner thread = ink->new_runner(); thread->move_to(ink::hash_string("test_knot")); - while(thread->can_continue()) { + while (thread->can_continue()) { auto line = thread->getline(); } REQUIRE(thread->can_continue() == false); @@ -25,8 +25,8 @@ SCENARIO("run story with tags", "[tags][story]") { GIVEN("a story with tags") { - story* _ink = story::from_file(INK_TEST_RESOURCE_DIR "TagsStory.bin"); - runner _thread = _ink->new_runner(); + std::unique_ptr _ink{story::from_file(INK_TEST_RESOURCE_DIR "TagsStory.bin")}; + runner _thread = _ink->new_runner(); WHEN("starting the thread") { CHECK_FALSE(_thread->has_tags()); diff --git a/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp b/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp index 5aeef107..207baae1 100644 --- a/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp +++ b/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp @@ -14,8 +14,9 @@ SCENARIO( { GIVEN("a story with brackets and nested choices") { - auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ThirdTierChoiceAfterBracketsStory.bin"); - runner thread = ink->new_runner(); + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR + "ThirdTierChoiceAfterBracketsStory.bin")}; + runner thread = ink->new_runner(); WHEN("start thread") { diff --git a/inkcpp_test/UTF8.cpp b/inkcpp_test/UTF8.cpp index 1a03c55d..31fe6091 100644 --- a/inkcpp_test/UTF8.cpp +++ b/inkcpp_test/UTF8.cpp @@ -14,39 +14,43 @@ SCENARIO("a story supports UTF-8", "[utf-8]") GIVEN("a story with UTF8 characters") { ink::compiler::run(INK_TEST_RESOURCE_DIR "UTF8Story.ink.json", "UTF8Story.bin"); - auto ink = story::from_file("UTF8Story.bin"); - runner thread = ink->new_runner(); + std::unique_ptr ink{story::from_file("UTF8Story.bin")}; + runner thread = ink->new_runner(); std::ifstream demoFile("ink/UTF-8-demo.txt"); - if (!demoFile.is_open()) { + if (! demoFile.is_open()) { throw std::runtime_error("cannot open UTF-8 demo file"); } - char byte; + char byte; std::stringstream demo; std::stringstream current; while (demoFile.get(byte)) { - if (byte == '\r') continue; // skip windows carriage-return newlines + if (byte == '\r') + continue; // skip windows carriage-return newlines else if (byte == '\n' || byte == 0) { // newline or null byte std::string s = current.str(); - if (s.empty()) continue; + if (s.empty()) + continue; demo << s << '\n'; current.str(""); - } - else { // normal char + } else { // normal char current << byte; } } std::string s = current.str(); - if (!s.empty()) demo << s << '\n'; - + if (! s.empty()) + demo << s << '\n'; + - WHEN("consume lines") { + WHEN("consume lines") + { std::stringstream content; while (thread->can_continue()) { content << thread->getline(); } - THEN("lines match test file") { + THEN("lines match test file") + { std::string c = content.str(); std::string d = demo.str(); REQUIRE(c == d); From 5e4462af6cad91255c8fcec61014fe75a432bac3 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 21 Nov 2025 22:36:22 +0100 Subject: [PATCH 04/24] refactor improve code quality --- CMakeLists.txt | 5 +- inkcpp/array.h | 16 ++-- inkcpp/avl_array.h | 8 +- inkcpp/choice.cpp | 2 +- inkcpp/collections/restorable.cpp | 8 +- inkcpp/collections/restorable.h | 12 +-- inkcpp/container_operations.cpp | 2 +- inkcpp/executioner.h | 4 +- inkcpp/functional.cpp | 4 +- inkcpp/globals_impl.cpp | 2 +- inkcpp/include/list.h | 8 +- inkcpp/list_impl.cpp | 4 +- inkcpp/list_impl.h | 3 +- inkcpp/list_operations.cpp | 2 +- inkcpp/list_table.cpp | 130 +++++++++++++++-------------- inkcpp/list_table.h | 44 ++++++---- inkcpp/output.cpp | 11 ++- inkcpp/output.h | 2 +- inkcpp/runner_impl.cpp | 28 ++++--- inkcpp/runner_impl.h | 8 +- inkcpp/simple_restorable_stack.h | 8 +- inkcpp/snapshot_impl.cpp | 6 +- inkcpp/snapshot_interface.h | 2 + inkcpp/stack.cpp | 28 +++---- inkcpp/stack.h | 2 +- inkcpp/string_table.cpp | 5 +- inkcpp/string_utils.h | 23 +++-- inkcpp/value.cpp | 8 +- inkcpp/value.h | 2 +- inkcpp_cl/inkcpp_cl.cpp | 6 +- inkcpp_compiler/binary_emitter.cpp | 10 +-- inkcpp_compiler/binary_stream.cpp | 6 +- inkcpp_compiler/json_compiler.cpp | 4 +- shared/public/system.h | 10 ++- 34 files changed, 226 insertions(+), 197 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f73c0ef..23dc4632 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,10 @@ SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(CMAKE_INSTALL_LIBRARY_DIR lib) SET(CMAKE_INSTALL_INCLUDE_DIR include) if (MSVC) - add_compile_options(/Wall /wd4820) + # disable: + # aligment problems + # non explicit switch case + add_compile_options(/W4 /wd4820 /wd4061) else() add_compile_options(-Wall -Wextra -Wpedantic) endif() diff --git a/inkcpp/array.h b/inkcpp/array.h index d1a38587..38d1135b 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -28,9 +28,9 @@ class managed_array : public snapshot_interface { public: managed_array() - : _static_data{} - , _capacity{initialCapacity} + : _capacity{initialCapacity} , _size{0} + , _static_data{} { if constexpr (dynamic) { if constexpr (simple) { @@ -162,7 +162,7 @@ class managed_array : public snapshot_interface ptr = snap_write(ptr, e, should_write); } } - return ptr - data; + return static_cast(ptr - data); } const unsigned char* snap_load(const unsigned char* ptr, const loader& loader) @@ -235,7 +235,7 @@ class managed_restorable_array : public managed_array(ptr - data); } const unsigned char* snap_load(const unsigned char* ptr, const snapshot_interface::loader& loader) @@ -253,7 +253,7 @@ template void managed_array::extend(size_t capacity) { static_assert(dynamic, "Can only extend if array is dynamic!"); - size_t new_capacity = capacity > _capacity ? capacity : 1.5f * _capacity; + size_t new_capacity = capacity > _capacity ? capacity : _capacity + _capacity / 2U; if (new_capacity < 5) { new_capacity = 5; } @@ -514,7 +514,7 @@ class allocated_restorable_array final : public basic_restorable_array }; template -inline size_t basic_restorable_array::snap(unsigned char* data, const snapper& snapper) const +inline size_t basic_restorable_array::snap(unsigned char* data, const snapper&) const { unsigned char* ptr = data; bool should_write = data != nullptr; @@ -525,12 +525,12 @@ inline size_t basic_restorable_array::snap(unsigned char* data, const snapper ptr = snap_write(ptr, _array[i], should_write); ptr = snap_write(ptr, _temp[i], should_write); } - return ptr - data; + return static_cast(ptr - data); } template inline const unsigned char* - basic_restorable_array::snap_load(const unsigned char* data, const loader& loader) + basic_restorable_array::snap_load(const unsigned char* data, const loader&) { auto ptr = data; ptr = snap_read(ptr, _saved); diff --git a/inkcpp/avl_array.h b/inkcpp/avl_array.h index 2e3cd698..50fa36ce 100644 --- a/inkcpp/avl_array.h +++ b/inkcpp/avl_array.h @@ -44,8 +44,6 @@ #include "system.h" -#include - /** * \param Key The key type. The type (class) must provide a 'less than' and 'equal to' operator * \param T The Data type @@ -72,7 +70,7 @@ class avl_array ink::runtime::internal::if_t balance_; ink::runtime::internal::if_t child_; size_type size_; // actual size - size_t _capacity; + size_type _capacity; size_type root_; // root node ink::runtime::internal::if_t parent_; @@ -130,7 +128,7 @@ class avl_array // returns unique number for each entry // the numbers are unique as long no operation are executed // on the avl - inline size_t temp_identifier() const { return instance_->size() - idx_ - 1; } + inline size_type temp_identifier() const { return instance_->size() - idx_ - 1; } // preincrement tag_avl_array_iterator& operator++() @@ -260,7 +258,7 @@ class avl_array void extend() { - size_t new_size = _capacity * 1.5; + size_type new_size = _capacity + _capacity / 2; if (new_size < 5) { new_size = 5; } diff --git a/inkcpp/choice.cpp b/inkcpp/choice.cpp index b1ffbc7c..6e15f296 100644 --- a/inkcpp/choice.cpp +++ b/inkcpp/choice.cpp @@ -16,7 +16,7 @@ namespace ink namespace runtime { - size_t choice::num_tags() const { return std::distance(_tags_start, _tags_end); } + size_t choice::num_tags() const { return static_cast(std::distance(_tags_start, _tags_end)); } const char* choice::get_tag(size_t index) const { diff --git a/inkcpp/collections/restorable.cpp b/inkcpp/collections/restorable.cpp index b2b0abc8..6bbe98a1 100644 --- a/inkcpp/collections/restorable.cpp +++ b/inkcpp/collections/restorable.cpp @@ -51,7 +51,7 @@ size_t restorable::snap(unsigned char* data, const snapper& snapper) cons ptr = snap_write(ptr, _buffer[i].name, data != nullptr); ptr += _buffer[i].data.snap(data ? ptr : nullptr, snapper); } - return ptr - data; + return static_cast(ptr - data); } template<> @@ -63,7 +63,7 @@ size_t restorable::snap(unsigned char* data, const snapper& snapper) cons for (size_t i = 0; i < max; ++i) { ptr += _buffer[i].snap(data ? ptr : nullptr, snapper); } - return ptr - data; + return static_cast(ptr - data); } template<> @@ -75,7 +75,7 @@ size_t restorable::snap(unsigned char* data, const snapper&) const for (size_t i = 0; i < max; ++i) { ptr = snap_write(ptr, _buffer[i], data != nullptr); } - return ptr - data; + return static_cast(ptr - data); } template<> @@ -108,7 +108,7 @@ const unsigned char* restorable::snap_load(const unsigned char* ptr, cons } template<> -const unsigned char* restorable::snap_load(const unsigned char* ptr, const loader& loader) +const unsigned char* restorable::snap_load(const unsigned char* ptr, const loader&) { size_t max; ptr = snap_load_base(ptr, _pos, _jump, _save, max); diff --git a/inkcpp/collections/restorable.h b/inkcpp/collections/restorable.h index 4d7f38d1..4478b905 100644 --- a/inkcpp/collections/restorable.h +++ b/inkcpp/collections/restorable.h @@ -91,8 +91,8 @@ class restorable : public snapshot_interface : _buffer(buffer) , _size(size) , _pos(0) - , _jump(~0) - , _save(~0) + , _jump(~0U) + , _save(~0U) { } @@ -130,7 +130,7 @@ class restorable : public snapshot_interface _pos = _save; // Clear save point - _save = _jump = ~0; + _save = _jump = ~0U; } // Forget the save point and continue with the current data @@ -150,7 +150,7 @@ class restorable : public snapshot_interface } // Reset save position - _save = _jump = ~0; + _save = _jump = ~0U; } using iterator = restorable_iter; @@ -223,7 +223,7 @@ class restorable : public snapshot_interface void clear() { _pos = 0; - _save = _jump = ~0; + _save = _jump = ~0U; } // Forward iterate @@ -358,7 +358,7 @@ class restorable : public snapshot_interface protected: // Called when we run out of space in buffer. - virtual void overflow(ElementType*& buffer, size_t& size) + virtual void overflow(ElementType*&, size_t&) { inkFail("Restorable run out of memory!"); } diff --git a/inkcpp/container_operations.cpp b/inkcpp/container_operations.cpp index 64575b1d..04550cca 100644 --- a/inkcpp/container_operations.cpp +++ b/inkcpp/container_operations.cpp @@ -42,7 +42,7 @@ namespace ink::runtime::internal { } void operation::operator() - (basic_eval_stack& stack, value* vals) + (basic_eval_stack& stack, value*) { stack.push(value{}.set(static_cast( _runner.num_choices() diff --git a/inkcpp/executioner.h b/inkcpp/executioner.h index 154d2d70..fbedc7a7 100644 --- a/inkcpp/executioner.h +++ b/inkcpp/executioner.h @@ -82,7 +82,7 @@ namespace ink::runtime::internal { public: static constexpr bool enabled = false; template - typed_executer(const T& t) {} + typed_executer(const T&) {} void operator()(value_type, basic_eval_stack&, value*) { inkFail("Operation for value not supported!"); @@ -144,7 +144,7 @@ namespace ink::runtime::internal { class executer_imp { public: template - executer_imp(const T& t) {} + executer_imp(const T&) {} void operator()(Command, basic_eval_stack&) { inkFail("requested command was not found!"); } diff --git a/inkcpp/functional.cpp b/inkcpp/functional.cpp index 81289436..f10dd1b2 100644 --- a/inkcpp/functional.cpp +++ b/inkcpp/functional.cpp @@ -25,7 +25,7 @@ ink::runtime::value } template<> -int32_t function_base::pop(basic_eval_stack* stack, list_table& lists) +int32_t function_base::pop(basic_eval_stack* stack, list_table&) { value val = stack->pop(); inkAssert(val.type() == value_type::int32, "Type mismatch!"); @@ -33,7 +33,7 @@ int32_t function_base::pop(basic_eval_stack* stack, list_table& lists) } template<> -const char* function_base::pop(basic_eval_stack* stack, list_table& lists) +const char* function_base::pop(basic_eval_stack* stack, list_table&) { value val = stack->pop(); inkAssert(val.type() == value_type::string, "Type mismatch!"); diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 6daed149..38a68c59 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -267,7 +267,7 @@ size_t globals_impl::snap(unsigned char* data, const snapper& snapper) const ptr += _strings.snap(data ? ptr : nullptr, snapper); ptr += _lists.snap(data ? ptr : nullptr, snapper); ptr += _variables.snap(data ? ptr : nullptr, snapper); - return ptr - data; + return static_cast(ptr - data); } const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loader& loader) diff --git a/inkcpp/include/list.h b/inkcpp/include/list.h index 3f4ddd35..110372de 100644 --- a/inkcpp/include/list.h +++ b/inkcpp/include/list.h @@ -48,6 +48,7 @@ class list_interface { } + virtual list_interface& operator=(const list_interface&) = default; virtual ~list_interface() {} /** iterater for flags in a list @@ -121,8 +122,6 @@ class list_interface iterator& operator=(const iterator&) = delete; }; -#pragma warning(pop) - #pragma warning(push) #pragma warning( \ @@ -197,9 +196,8 @@ class list_interface /** @private */ internal::list_table* _list_table; int _list; -#pragma warning(push) -#pragma warning(disable : 4820, justification : "4 byte aligment free on 64-bit systems") }; -#pragma warning(pop) } // namespace ink::runtime + +#pragma warning(pop) diff --git a/inkcpp/list_impl.cpp b/inkcpp/list_impl.cpp index a9d2bccd..f61f47a6 100644 --- a/inkcpp/list_impl.cpp +++ b/inkcpp/list_impl.cpp @@ -44,7 +44,7 @@ void list_impl::next(const char*& flag_name, const char*& list_name, int& i, boo if (flag_name != nullptr) { ++flag.flag; } - if (flag.flag >= _list_table->_list_end[flag.list_id]) { + if (static_cast(flag.flag) >= _list_table->_list_end[flag.list_id]) { next_list: if (one_list_only) { i = -1; @@ -61,7 +61,7 @@ void list_impl::next(const char*& flag_name, const char*& list_name, int& i, boo } while (! _list_table->has(list_table::list{_list}, flag)) { ++flag.flag; - if (flag.flag >= _list_table->_list_end[flag.list_id] - _list_table->listBegin(flag.list_id)) { + if (static_cast(flag.flag) >= _list_table->_list_end[flag.list_id] - _list_table->listBegin(flag.list_id)) { goto next_list; } } diff --git a/inkcpp/list_impl.h b/inkcpp/list_impl.h index ab8f2ca8..5d1c6cff 100644 --- a/inkcpp/list_impl.h +++ b/inkcpp/list_impl.h @@ -21,7 +21,8 @@ class list_impl final : public list_interface { } - ~list_impl() {} + list_impl& operator=(const list_impl&) = default; + ~list_impl() override {} int get_lid() const { return _list; } diff --git a/inkcpp/list_operations.cpp b/inkcpp/list_operations.cpp index 45023bfc..091d2ed5 100644 --- a/inkcpp/list_operations.cpp +++ b/inkcpp/list_operations.cpp @@ -199,7 +199,7 @@ void operation::operator()( "list_flag construction needs the flag numeric value as second argument!" ); list_flag entry = _list_table.get_list_id(vals[0].get()); - entry.flag = vals[1].get(); + entry.flag = static_cast(vals[1].get()); entry = _list_table.external_fvalue_to_internal(entry); stack.push(value{}.set(entry)); } diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index 73c00b93..fd0195b9 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -43,7 +43,7 @@ list_table::list_table(const char* data, const ink::internal::header& header) int start = 0; while ((flag = header.read_list_flag(ptr)) != null_flag) { // start of new list - if (_list_end.size() == flag.list_id) { + if (static_cast(_list_end.size()) == flag.list_id) { start = _list_end.size() == 0 ? 0 : _list_end.back(); _list_end.push() = start; _list_names.push() = ptr; @@ -130,9 +130,9 @@ size_t list_table::stringLen(const list& l) const size_t len = 0; const data_t* entry = getPtr(l.lid); bool first = true; - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(entry, i)) { - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { if (hasFlag(entry, j) && _flag_names[j]) { if (! first) { len += 2; // ', ' @@ -163,14 +163,17 @@ char* list_table::toString(char* out, const list& l) const while (1) { bool change = false; - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(entry, i)) { - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { if (! hasFlag(entry, j)) { continue; } int value = _flag_values[j]; - if (first || value > last_value || (value == last_value && i > last_list)) { + // the cast is ok, since if we are in the + // first round, `first` is true and we do not evaluate + // second round, `last_list` is >= 0 + if (first || value > last_value || (value == last_value && i > static_cast(last_list))) { if (min_id == -1 || value < min_value) { change = true; min_list = i; @@ -206,10 +209,10 @@ list_table::list list_table::range(list_table::list l, int min, int max) data_t* in = getPtr(l.lid); data_t* out = getPtr(res.lid); bool has_any_list = false; - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(in, i)) { bool has_flag = false; - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { int value = _flag_values[j]; if (value < min) { continue; @@ -301,10 +304,10 @@ list_table::list list_table::sub(list lh, list rh) o[i] = (l[i] & r[i]) ^ l[i]; } - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(r, i)) { if (hasList(l, i)) { - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { if (hasFlag(o, j)) { setList(o, i); active_flag = true; @@ -317,7 +320,7 @@ list_table::list list_table::sub(list lh, list rh) if (active_flag) { return res; } - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(o, i)) { return res; } @@ -335,13 +338,13 @@ list_table::list list_table::sub(list lh, list_flag rh) o[i] = l[i]; } setFlag(o, toFid(rh), false); - for (int i = listBegin(rh.list_id); i < _list_end[rh.list_id]; ++i) { + for (size_t i = listBegin(rh.list_id); i < _list_end[rh.list_id]; ++i) { if (hasFlag(o, i)) { return res; } } setList(l, rh.list_id, false); - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(o, i)) { return res; } @@ -371,13 +374,13 @@ list_table::list list_table::add(list arg, int n) data_t* o = getPtr(res.lid); bool active_flag = false; ; - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(l, i)) { bool has_flag = false; - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { if (hasFlag(l, j)) { int value = _flag_values[j] + n; - for (int k = j + 1; k < _list_end[i]; ++k) { + for (size_t k = j + 1; k < _list_end[i]; ++k) { if (value == _flag_values[k]) { setFlag(o, k); has_flag = true; @@ -404,9 +407,9 @@ list_flag list_table::add(list_flag arg, int n) return arg; } int value = _flag_values[arg.flag] + n; - for (int i = listBegin(arg.list_id); i < _list_end[arg.list_id]; ++i) { + for (size_t i = listBegin(arg.list_id); i < _list_end[arg.list_id]; ++i) { if (_flag_values[i] == value) { - arg.flag = i; + arg.flag = static_cast(i); return arg; } } @@ -425,13 +428,13 @@ list_table::list list_table::sub(list arg, int n) data_t* l = getPtr(arg.lid); data_t* o = getPtr(res.lid); bool active_flag = false; - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(l, i)) { bool has_flag = false; - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { if (hasFlag(l, j)) { int value = _flag_values[j] - n; - for (size_t k = j - 1; k >= listBegin(i) && k != ~0U; --k) { + for (size_t k = j - 1; k != ~0U && k >= listBegin(i) && k != ~0U; --k) { if (_flag_values[k] == value) { setFlag(o, k); has_flag = true; @@ -469,9 +472,9 @@ int32_t list_table::count(list l) const { int count = 0; const data_t* data = getPtr(l.lid); - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(data, i)) { - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { if (_flag_names[j] != nullptr && hasFlag(data, j)) { ++count; } @@ -485,14 +488,14 @@ list_flag list_table::min(list l) const { list_flag res{-1, -1}; const data_t* data = getPtr(l.lid); - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(data, i)) { - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { if (hasFlag(data, j)) { int value = _flag_values[j]; if (res.flag < 0 || value < res.flag) { - res.flag = value; - res.list_id = i; + res.flag = static_cast(value); + res.list_id = static_cast(i); } break; } @@ -506,14 +509,14 @@ list_flag list_table::max(list l) const { list_flag res{-1, -1}; const data_t* data = getPtr(l.lid); - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(data, i)) { - for (int j = _list_end[i] - 1; j >= listBegin(i); --j) { + for (size_t j = _list_end[i] - 1; j != ~0U && j >= listBegin(i); --j) { if (hasFlag(data, j)) { int value = _flag_values[j]; if (value > res.flag) { - res.flag = value; - res.list_id = i; + res.flag = static_cast(value); + res.list_id = static_cast(i); } break; } @@ -527,12 +530,12 @@ bool list_table::equal(list lh, list rh) const { const data_t* l = getPtr(lh.lid); const data_t* r = getPtr(rh.lid); - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(l, i) != hasList(r, i)) { return false; } if (hasList(l, i)) { - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { if (hasFlag(l, j) != hasFlag(r, j)) { return false; } @@ -545,13 +548,13 @@ bool list_table::equal(list lh, list rh) const bool list_table::equal(list lh, list_flag rh) const { const data_t* l = getPtr(lh.lid); - for (int i = 0; i < numLists(); ++i) { - if (hasList(l, i) != (rh.list_id == i)) { + for (size_t i = 0; i < numLists(); ++i) { + if (hasList(l, i) != (rh.list_id == static_cast(i))) { return false; } } - for (int i = listBegin(rh.list_id); i < _list_end[rh.list_id]; ++i) { - if (hasFlag(l, i) != (rh.flag == i - listBegin(rh.list_id))) { + for (size_t i = listBegin(rh.list_id); i < _list_end[rh.list_id]; ++i) { + if (hasFlag(l, i) != (rh.flag == static_cast(i - listBegin(rh.list_id)))) { return false; } } @@ -563,10 +566,10 @@ list_table::list list_table::all(list arg) list res = create(); data_t* l = getPtr(arg.lid); data_t* o = getPtr(res.lid); - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(l, i)) { setList(o, i); - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { setFlag(o, j); } } @@ -580,7 +583,7 @@ list_table::list list_table::all(list_flag arg) if (arg != null_flag) { data_t* o = getPtr(res.lid); setList(o, arg.list_id); - for (int i = listBegin(arg.list_id); i < _list_end[arg.list_id]; ++i) { + for (size_t i = listBegin(arg.list_id); i < _list_end[arg.list_id]; ++i) { setFlag(o, i); } } @@ -593,10 +596,10 @@ list_table::list list_table::invert(list arg) list res = create(); data_t* l = getPtr(arg.lid); data_t* o = getPtr(res.lid); - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(l, i)) { bool hasList = false; - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { bool have = hasFlag(l, j); if (! have) { hasList = true; @@ -616,8 +619,8 @@ list_table::list list_table::invert(list_flag arg) list res = create(); if (arg != null_flag) { data_t* o = getPtr(res.lid); - for (int i = listBegin(arg.list_id); i < _list_end[arg.list_id]; ++i) { - setFlag(o, i, i - listBegin(arg.list_id) != arg.flag); + for (size_t i = listBegin(arg.list_id); i < _list_end[arg.list_id]; ++i) { + setFlag(o, i, arg.flag != static_cast(i - listBegin(arg.list_id))); } } return res; @@ -656,9 +659,9 @@ list_flag list_table::lrnd(list lh, prng& rng) const int n = count(lh); n = rng.rand(n); int count = 0; - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(l, i)) { - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { if (hasFlag(l, j)) { if (count++ == n) { return list_flag{ @@ -677,12 +680,12 @@ bool list_table::has(list lh, list rh) const { const data_t* r = getPtr(rh.lid); const data_t* l = getPtr(lh.lid); - for (int i = 0; i < numLists(); ++i) { + for (size_t i = 0; i < numLists(); ++i) { if (hasList(r, i)) { if (! hasList(l, i)) { return false; } - for (int j = listBegin(i); j < _list_end[i]; ++j) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { if (hasFlag(r, j) && ! hasFlag(l, j)) { return false; } @@ -700,7 +703,7 @@ optional list_table::toFlag(const char* flag_name) const list_flag list = get_list_id(flag_name); // since flag_name is `list_name.flag_name` flag_name = periode + 1; int list_begin = list.list_id == 0 ? 0 : _list_end[list.list_id - 1]; - for (int i = list_begin; i != _list_end[list.list_id]; ++i) { + for (size_t i = list_begin; i != _list_end[list.list_id]; ++i) { if (str_equal(flag_name, _flag_names[i])) { return { list_flag{list.list_id, static_cast(i - list_begin)} @@ -710,12 +713,12 @@ optional list_table::toFlag(const char* flag_name) const } else { for (auto flag_itr = _flag_names.begin(); flag_itr != _flag_names.end(); ++flag_itr) { if (str_equal(*flag_itr, flag_name)) { - int fid = flag_itr - _flag_names.begin(); - int lid = 0; + size_t fid = static_cast(flag_itr - _flag_names.begin()); + size_t lid = 0; int begin = 0; - for (auto list_itr = _list_end.begin(); list_itr != _list_end.end(); ++list_itr) { + for (auto* list_itr = _list_end.begin(); list_itr != _list_end.end(); ++list_itr) { if (*list_itr > fid) { - lid = list_itr - _list_end.begin(); + lid = static_cast(list_itr - _list_end.begin()); break; } begin = *list_itr; @@ -733,7 +736,7 @@ list_flag list_table::get_list_id(const char* list_name) const { using int_t = decltype(list_flag::list_id); const char* period = str_find(list_name, '.'); - size_t len = period ? period - list_name : c_str_len(list_name); + size_t len = period ? static_cast(period - list_name) : c_str_len(list_name); for (int_t i = 0; i < static_cast(_list_names.size()); ++i) { if (str_equal_len(list_name, _list_names[i], len)) { return list_flag{i, -1}; @@ -752,8 +755,8 @@ list_table::list list_table::redefine(list lh, list rh) // if the new list has no origin: give it the origin of the old value bool has_origin = false; - for (int i = 0; i < numLists(); ++i) { - if (hasList(r, i)) { + for (size_t i = 0; i < numLists(); ++i) { + if (hasList(r, static_cast(i))) { has_origin = true; break; } @@ -790,14 +793,17 @@ std::ostream& list_table::write(std::ostream& os, list l) const while (1) { bool change = false; - for (int i = 0; i < numLists(); ++i) { - if (hasList(entry, i)) { - for (int j = listBegin(i); j < _list_end[i]; ++j) { - if (! hasFlag(entry, j)) { + for (size_t i = 0; i < numLists(); ++i) { + if (hasList(entry, static_cast(i))) { + for (size_t j = listBegin(i); j < _list_end[i]; ++j) { + if (! hasFlag(entry, static_cast(j))) { continue; } int value = _flag_values[j]; - if (first || value > last_value || (value == last_value && i > last_list)) { + // the cast is ok, since if we are in the + // first round, `first` is true and we do not evaluate + // second round, `last_list` is >= 0 + if (first || value > last_value || (value == last_value && i > static_cast(last_list))) { if (min_id == -1 || value < min_value) { min_value = value; min_id = j; @@ -830,7 +836,7 @@ size_t list_table::snap(unsigned char* data, const snapper& snapper) const unsigned char* ptr = data; ptr += _data.snap(data ? ptr : nullptr, snapper); ptr += _entry_state.snap(data ? ptr : nullptr, snapper); - return ptr - data; + return static_cast(ptr - data); } const unsigned char* list_table::snap_load(const unsigned char* ptr, const loader& loader) diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index 3ad069e3..c13e372c 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -77,9 +77,10 @@ class list_table : public snapshot_interface flag.flag = -1; return flag; } - for (int i = listBegin(flag.list_id); i < _list_end[static_cast(flag.list_id)]; ++i) { - if (_flag_values[static_cast(i)] == flag.flag) { - flag.flag = static_cast(i - listBegin(flag.list_id)); + inkAssert(flag.list_id >= 0); + for (size_t i = listBegin(static_cast(flag.list_id)); i < _list_end[static_cast(flag.list_id)]; ++i) { + if (_flag_values[i] == flag.flag) { + flag.flag = static_cast(i - listBegin(static_cast(flag.list_id))); return flag; } } @@ -89,7 +90,8 @@ class list_table : public snapshot_interface int get_flag_value(list_flag flag) const { - return _flag_values[static_cast(listBegin(flag.list_id) + flag.flag)]; + inkAssert(flag.list_id >= 0 && flag.flag >= 0); + return _flag_values[listBegin(static_cast(flag.list_id)) + static_cast(flag.flag)]; } /// zeros all usage values @@ -259,7 +261,7 @@ class list_table : public snapshot_interface void copy_lists(const data_t* src, data_t* dst); static constexpr size_t bits_per_data = sizeof(data_t) * 8U; - size_t listBegin(int lid) const { return lid == 0 ? 0 : _list_end[static_cast(lid - 1)]; } + size_t listBegin(size_t lid) const { return lid == 0 ? 0 : _list_end[static_cast(lid - 1)]; } const data_t* getPtr(int eid) const { @@ -281,14 +283,14 @@ class list_table : public snapshot_interface size_t numLists() const { return _list_end.size(); } - bool getBit(const data_t* data, int id) const + bool getBit(const data_t* data, size_t id) const { return data[id / bits_per_data] & (0x01 << (bits_per_data - 1 - (id % bits_per_data))); } - void setBit(data_t* data, int id, bool value = true) + void setBit(data_t* data, size_t id, bool value = true) { - data_t mask = 0x01 << (bits_per_data - 1 - (id % bits_per_data)); + data_t mask = 0x01U << (bits_per_data - 1U - (id % bits_per_data)); if (value) { data[id / bits_per_data] |= mask; } else { @@ -296,21 +298,30 @@ class list_table : public snapshot_interface } } - bool hasList(const data_t* data, int lid) const { return getBit(data, lid); } + bool hasList(const data_t* data, int lid) const { + if (lid < 0) { + return false; + } + return getBit(data, static_cast(lid)); + } void setList(data_t* data, int lid, bool value = true) { if (lid >= 0) { - setBit(data, lid, value); + setBit(data, static_cast(lid), value); } } - bool hasFlag(const data_t* data, int fid) const { return getBit(data, fid + numLists()); } + bool hasFlag(const data_t* data, int fid) const { + if (fid < 0) { + return false; + } + return getBit(data, static_cast(fid) + numLists()); } void setFlag(data_t* data, int fid, bool value = true) { if (fid >= 0) { - setBit(data, fid + numLists(), value); + setBit(data, static_cast(fid) + numLists(), value); } } @@ -372,13 +383,14 @@ class list_table : public snapshot_interface */ void carry() { - if (_pos.flag.flag + if (_pos.flag.flag < 0 || _pos.flag.list_id < 0) { return; } + if (static_cast(_pos.flag.flag) == _list._list_end[static_cast(_pos.flag.list_id)] - _list.listBegin(static_cast(_pos.flag.list_id))) { _pos.flag.flag = 0; ++_pos.flag.list_id; } - if (_pos.flag.list_id == _list.numLists()) { + if (static_cast(_pos.flag.list_id) == _list.numLists()) { _pos.flag = null_flag; } else { _pos.name = _list._flag_names[_list.toFid(_pos.flag)]; @@ -390,7 +402,7 @@ class list_table : public snapshot_interface bool valid; do { valid = true; - int fid = _list.toFid(_pos.flag); + size_t fid = _list.toFid(_pos.flag); if (_data == nullptr) { if (_list._flag_names[fid] == nullptr) { valid = false; @@ -399,7 +411,7 @@ class list_table : public snapshot_interface } else if (! _list.hasList(_data, _pos.flag.list_id)) { valid = false; ++_pos.flag.list_id; - } else if (! _list.hasFlag(_data, fid) || _list._flag_names[fid] == nullptr) { + } else if (! _list.hasFlag(_data, static_cast(fid)) || _list._flag_names[fid] == nullptr) { valid = false; ++_pos.flag.flag; } diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 6162db2c..c00fd4c8 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -33,7 +33,7 @@ void basic_stream::initelize_data(value* buffer, size_t size) _max = size; } -void basic_stream::overflow(value*& buffer, size_t& size, size_t target) +void basic_stream::overflow(value*&, size_t&, size_t) { inkFail("Stack overflow!"); } @@ -146,7 +146,6 @@ inline bool get_next(const value* list, size_t i, size_t size, const value** nex { while (i + 1 < size) { *next = &list[i + 1]; - value_type type = (*next)->type(); if ((*next)->printable()) { return true; } @@ -157,7 +156,7 @@ inline bool get_next(const value* list, size_t i, size_t size, const value** nex } template -void basic_stream::copy_string(const char* str, size_t& dataIter, T& output) +void basic_stream::copy_string(const char* str, size_t&, T& output) { while (*str != 0) { write_char(output, *str++); @@ -274,7 +273,7 @@ size_t basic_stream::find_first_of(value_type type, size_t offset /*= 0*/) const size_t basic_stream::find_last_of(value_type type, size_t offset /*= 0*/) const { if (_size == 0) - return -1; + return ~0U; // Special case to make the reverse loop easier if (_size == 1 && offset == 0) @@ -363,7 +362,7 @@ char* basic_stream::get_alloc(string_table& strings, list_table& lists) case value_type::float32: case value_type::uint32: // Convert to string and advance - toStr(ptr, end - ptr, _data[i]); + toStr(ptr, static_cast(end - ptr), _data[i]); while (*ptr != 0) ptr++; @@ -529,7 +528,7 @@ size_t basic_stream::snap(unsigned char* data, const snapper& snapper) const for (auto itr = _data; itr != _data + _size; ++itr) { ptr += itr->snap(data ? ptr : nullptr, snapper); } - return ptr - data; + return static_cast(ptr - data); } const unsigned char* basic_stream::snap_load(const unsigned char* ptr, const loader& loader) diff --git a/inkcpp/output.h b/inkcpp/output.h index 493bdcfe..eb9b2e8e 100644 --- a/inkcpp/output.h +++ b/inkcpp/output.h @@ -30,7 +30,7 @@ namespace runtime public: // Constant to identify an invalid position in the stream - static constexpr size_t npos = ~0; + static constexpr size_t npos = ~0U; // Append data to stream void append(const value&); diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 05ab406b..148b7bfb 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -34,7 +34,7 @@ const choice* runner_interface::get_choice(size_t index) const return begin() + index; } -size_t runner_interface::num_choices() const { return end() - begin(); } +size_t runner_interface::num_choices() const { return static_cast(end() - begin()); } } // namespace ink::runtime namespace ink::runtime::internal @@ -409,7 +409,7 @@ void runner_impl::start_frame(uint32_t target) } // Push next address onto the callstack { - size_t address = _ptr - _story->instructions(); + offset_t address = static_cast(_ptr - _story->instructions()); _stack.push_frame(address, _evaluation_mode); _ref_stack.push_frame(address, _evaluation_mode); } @@ -477,7 +477,7 @@ runner_impl::runner_impl(const story_impl* data, globals global) , _choices() , _tags_begin(0, ~0) , _container(ContainerData{}) - , _rng(time(NULL)) + , _rng(static_cast(time(NULL))) { @@ -520,7 +520,7 @@ runner_impl::line_type runner_impl::getline() // Fall through the fallback choice, if available if (! has_choices() && _fallback_choice) { - choose(~0); + choose(~0U); } inkAssert(_output.is_empty(), "Output should be empty after getline!"); @@ -666,7 +666,7 @@ size_t runner_impl::snap(unsigned char* data, snapper& snapper) const ptr += _fallback_choice.value().snap(data ? ptr : nullptr, snapper); } ptr += _choices.snap(data ? ptr : nullptr, snapper); - return ptr - data; + return static_cast(ptr - data); } const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& loader) @@ -716,7 +716,7 @@ const char* runner_impl::getline_alloc() advance_line(); const char* res = _output.get_alloc(_globals->strings(), _globals->lists()); if (! has_choices() && _fallback_choice) { - choose(~0); + choose(~0U); } inkAssert(_output.is_empty(), "Output should be empty after getline!"); return res; @@ -779,7 +779,7 @@ bool runner_impl::line_step() // Step the interpreter until we've parsed all tags for the line _entered_global = true; - _current_knot_id = ~0; + _current_knot_id = ~0U; _entered_knot = false; } // Step the interpreter @@ -1159,10 +1159,10 @@ void runner_impl::step() // TODO We push ahead of a single divert. Is that correct in all cases....????? auto returnTo = _ptr + CommandSize; _stack.push_frame( - returnTo - _story->instructions(), _evaluation_mode + static_cast(returnTo - _story->instructions()), _evaluation_mode ); _ref_stack.push_frame( - returnTo - _story->instructions(), _evaluation_mode + static_cast(returnTo - _story->instructions()), _evaluation_mode ); // Fork a new thread on the callstack @@ -1330,7 +1330,7 @@ void runner_impl::step() // been visited if (flag & CommandFlag::CHOICE_IS_ONCE_ONLY) { // Need to convert offset to container index - container_t destination = -1; + container_t destination = ~0U; if (_story->get_container_id(_story->instructions() + path, destination)) { // Ignore the choice if we've visited the destination before if (_globals->visits(destination) > 0) { @@ -1457,7 +1457,9 @@ void runner_impl::step() // iteration loop. I don't feel like replicating that right now. // So, let's just return a random number and *shrug* int sequenceLength = _eval.pop().get(); - int index = _eval.pop().get(); + /* shuffel index */ + _eval.pop(); + _eval.push(value{}.set(static_cast(_rng.rand(sequenceLength))) ); @@ -1621,7 +1623,7 @@ void runner_impl::restore() _tags_begin.restore(); _evaluation_mode = _saved_evaluation_mode; _current_knot_id = _current_knot_id_backup; - _current_knot_id_backup = ~0; + _current_knot_id_backup = ~0U; // Not doing this anymore. There can be lingering stack entries from function returns // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty"); @@ -1645,7 +1647,7 @@ void runner_impl::forget() _choices.forgett(); _tags.forgett(); _tags_begin.forget(); - _current_knot_id_backup = ~0; + _current_knot_id_backup = ~0U; // Nothing to do for eval stack. It should just stay as it is _saved = false; diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 26f4e44a..f4089a29 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -211,8 +211,8 @@ class runner_impl // Special code for jumping from the current IP to another void jump(ip_t, bool record_visits, bool track_knot_visit); - uint32_t _current_knot_id = ~0; // id to detect knot changes from the outside - uint32_t _current_knot_id_backup = ~0; + uint32_t _current_knot_id = ~0U; // id to detect knot changes from the outside + uint32_t _current_knot_id_backup = ~0U; uint32_t _entered_knot = false; // if we are in the first action after a jump to an snitch/knot bool _entered_global = false; // if we are in the first action after a jump to an snitch/knot @@ -234,7 +234,7 @@ class runner_impl public: template = true> threads() - : base(~0) + : base(~0U) , _threadDone(nullptr, reinterpret_cast(~0)) { static_assert(sizeof...(D) == 0, "Don't use explicit template arguments!"); @@ -376,7 +376,7 @@ size_t runner_impl::threads::snap(unsigned char* data, const snapper unsigned char* ptr = data; ptr += base::snap(data ? ptr : nullptr, snapper); ptr += _threadDone.snap(data ? ptr : nullptr, snapper); - return ptr - data; + return static_cast(ptr - data); } template diff --git a/inkcpp/simple_restorable_stack.h b/inkcpp/simple_restorable_stack.h index db20ef93..4e4d8692 100644 --- a/inkcpp/simple_restorable_stack.h +++ b/inkcpp/simple_restorable_stack.h @@ -53,7 +53,7 @@ class simple_restorable_stack : public snapshot_interface virtual const unsigned char* snap_load(const unsigned char* data, const loader&); protected: - virtual void overflow(T*& buffer, size_t& size) { inkFail("Stack overflow!"); } + virtual void overflow(T*&, size_t&) { inkFail("Stack overflow!"); } void initialize_data(T* buffer, size_t size) { @@ -71,7 +71,7 @@ class simple_restorable_stack : public snapshot_interface size_t _size; const T _null; - const static size_t InvalidIndex = ~0; + const static size_t InvalidIndex = ~0U; size_t _pos = 0; size_t _save = InvalidIndex, _jump = InvalidIndex; @@ -309,12 +309,12 @@ size_t simple_restorable_stack::snap(unsigned char* data, const snapper&) con for (size_t i = 0; i < max; ++i) { ptr = snap_write(ptr, _buffer[i], should_write); } - return ptr - data; + return static_cast(ptr - data); } template const unsigned char* - simple_restorable_stack::snap_load(const unsigned char* ptr, const loader& loader) + simple_restorable_stack::snap_load(const unsigned char* ptr, const loader&) { T null; ptr = snap_read(ptr, null); diff --git a/inkcpp/snapshot_impl.cpp b/inkcpp/snapshot_impl.cpp index f18bc9c5..83f537c7 100644 --- a/inkcpp/snapshot_impl.cpp +++ b/inkcpp/snapshot_impl.cpp @@ -83,7 +83,7 @@ snapshot_impl::snapshot_impl(const globals_impl& globals) // write lookup table ptr += sizeof(header); { - size_t offset = (ptr - data) + (_header.num_runners + 1) * sizeof(size_t); + size_t offset = static_cast((ptr - data) + (_header.num_runners + 1) * sizeof(size_t)); memcpy(ptr, &offset, sizeof(offset)); ptr += sizeof(offset); offset += globals.snap(nullptr, snapper); @@ -128,7 +128,7 @@ size_t snap_choice::snap(unsigned char* data, const snapper& snapper) const ptr = snap_write(ptr, offset_end, should_write); } ptr = snap_write(ptr, snapper.strings.get_id(_text), should_write); - return ptr - data; + return static_cast(ptr - data); } const unsigned char* snap_choice::snap_load(const unsigned char* data, const loader& loader) @@ -167,7 +167,7 @@ size_t snap_tag::snap(unsigned char* data, const snapper& snapper) const ptr = snap_write(ptr, true, should_write); ptr = snap_write(ptr, id, should_write); } - return ptr - data; + return static_cast(ptr - data); } const unsigned char* snap_tag::snap_load(const unsigned char* data, const loader& loader) diff --git a/inkcpp/snapshot_interface.h b/inkcpp/snapshot_interface.h index 997600f3..8693bbe4 100644 --- a/inkcpp/snapshot_interface.h +++ b/inkcpp/snapshot_interface.h @@ -6,6 +6,8 @@ */ #pragma once +#include "system.h" + #include namespace ink::runtime::internal diff --git a/inkcpp/stack.cpp b/inkcpp/stack.cpp index 58783e2e..44dcb4d9 100644 --- a/inkcpp/stack.cpp +++ b/inkcpp/stack.cpp @@ -47,7 +47,7 @@ namespace ink::runtime::internal if (skip != ~0) { // Stop if we get to the start of the thread block if (e.data.type() == value_type::thread_start && skip == e.data.get().jump) { - skip = ~0; + skip = ~0U; } // Don't return anything in the hidden thread block @@ -77,7 +77,7 @@ namespace ink::runtime::internal } private: hash_t _name; - thread_t _skip = ~0; + thread_t _skip = ~0U; uint32_t _jumping = 0; }; class reverse_find_from_frame_predicat_operator { @@ -96,7 +96,7 @@ namespace ink::runtime::internal int _ci; int _current_frame = 0; hash_t _name; - thread_t _skip = ~0; + thread_t _skip = ~0U; uint32_t _jumping = 0; }; @@ -270,7 +270,7 @@ namespace ink::runtime::internal if (frame->data.type() == value_type::thread_end) { // Push a new jump marker after the thread end - entry& jump = push({ InvalidHash, value{}.set(0u,0u) }); + push({ InvalidHash, value{}.set(0u,0u) }); // Do a pop back returnedFrame = do_thread_jump_pop(base::begin()); @@ -326,7 +326,7 @@ namespace ink::runtime::internal return false; uint32_t jumping = 0; - uint32_t thread = ~0; + uint32_t thread = ~0U; // Search in reverse for a stack frame const entry* frame = base::reverse_find([&jumping, &thread](const entry& elem) { // If we're jumping over data, just keep returning false until we're done @@ -340,9 +340,9 @@ namespace ink::runtime::internal return false; // If we're skipping over a thread, wait until we hit its start before checking - if (thread != ~0) { + if (thread != ~0U) { if (elem.data.type() == value_type::thread_start && elem.data.get().jump == thread) - thread = ~0; + thread = ~0U; return false; } @@ -393,7 +393,7 @@ namespace ink::runtime::internal thread_t new_thread = _next_thread++; // Push a thread start marker here - entry& thread_entry = add(InvalidHash, value{}.set(new_thread, 0u)); + add(InvalidHash, value{}.set(new_thread, 0u)); // Set stack jump counter for thread to 0. This number is used if the thread ever // tries to pop past its origin. It keeps track of how much of the preceeding stack it's popped back @@ -427,7 +427,7 @@ namespace ink::runtime::internal } // Now, start iterating backwards - thread_t nulling = ~0; + thread_t nulling = ~0U; uint32_t jumping = 0; base::reverse_for_each([&nulling, &jumping](entry& elem) { if (jumping > 0) { @@ -445,10 +445,10 @@ namespace ink::runtime::internal } // If we're deleting a useless thread block - if (nulling != ~0) { + if (nulling != ~0U) { // If this is the start of the block, stop deleting if (elem.name == InvalidHash && elem.data.type() == value_type::thread_start && elem.data.get().jump == nulling) { - nulling = ~0; + nulling = ~0U; } // delete data @@ -496,7 +496,7 @@ namespace ink::runtime::internal void basic_stack::forget() { - base::forget([](entry& elem) { elem.name = ~0; }); + base::forget([](entry& elem) { elem.name = ~0U; }); } entry& basic_stack::add(hash_t name, const value& val) @@ -522,7 +522,7 @@ namespace ink::runtime::internal const value& basic_eval_stack::top() const { - return base::top([](const value& v){ return false; }); + return base::top([](const value&){ return false; }); } const value& basic_eval_stack::top_value() const @@ -605,7 +605,7 @@ namespace ink::runtime::internal ptr = snap_write(ptr, _next_thread, should_write ); ptr = snap_write(ptr, _backup_next_thread, should_write ); ptr += base::snap(data ? ptr : nullptr, snapper); - return ptr - data; + return static_cast(ptr - data); } const unsigned char* basic_stack::snap_load(const unsigned char* ptr, const loader& loader) diff --git a/inkcpp/stack.h b/inkcpp/stack.h index 66dd0ae2..eb06cac2 100644 --- a/inkcpp/stack.h +++ b/inkcpp/stack.h @@ -105,7 +105,7 @@ namespace runtime thread_t _next_thread = 0; thread_t _backup_next_thread = 0; - static const hash_t NulledHashId = ~0; + static const hash_t NulledHashId = ~0U; }; template<> diff --git a/inkcpp/string_table.cpp b/inkcpp/string_table.cpp index 40dda321..e54df9c7 100644 --- a/inkcpp/string_table.cpp +++ b/inkcpp/string_table.cpp @@ -107,7 +107,7 @@ size_t string_table::snap(unsigned char* data, const snapper&) const for (size_t i = 0; i < _table.size(); ++i) { for (auto itr = _table.begin(); itr != _table.end(); ++itr) { if (itr.temp_identifier() == i) { - size_t length = strlen(itr.key()) + 1; + size_t length = static_cast(strlen(itr.key())) + 1; if (length == 1) { ptr = snap_write(ptr, EMPTY_STRING, 2, should_write); } else { @@ -118,13 +118,12 @@ size_t string_table::snap(unsigned char* data, const snapper&) const } } ptr = snap_write(ptr, "\0", 1, should_write); - return ptr - data; + return static_cast(ptr - data); } const unsigned char* string_table::snap_load(const unsigned char* data, const loader& loader) { auto* ptr = data; - int i = 0; while (*ptr) { size_t len = 0; for (; ptr[len]; ++len) diff --git a/inkcpp/string_utils.h b/inkcpp/string_utils.h index 00df3111..bd8724ba 100644 --- a/inkcpp/string_utils.h +++ b/inkcpp/string_utils.h @@ -23,7 +23,7 @@ namespace ink::runtime::internal inline int toStr(char* buffer, size_t size, uint32_t value) { #ifdef WIN32 - return _itoa_s(value, buffer, size, 10); + return _itoa_s(static_cast(value), buffer, size, 10); #else if (buffer == nullptr || size < 1) { return EINVAL; @@ -143,7 +143,7 @@ inline constexpr size_t value_length(const value& v) case value_type::float32: return decimal_digits(v.get()); case value_type::string: return c_str_len(v.get()); case value_type::newline: return 1; - default: inkFail("Can't determine length of this value type"); return -1; + default: inkFail("Can't determine length of this value type"); return ~0U; } } @@ -188,15 +188,22 @@ inline constexpr ITR clean_string(ITR begin, ITR end) auto dst = begin; for (auto src = begin; src != end; ++src) { if (dst == begin) { - if (LEADING_SPACES && isspace(static_cast(src[0]))) { - continue; + if constexpr (LEADING_SPACES) { + if (isspace(static_cast(src[0]))) { + continue; + } } } else if (src[-1] == '\n' && isspace(static_cast(src[0]))) { continue; - } else if ((isspace(static_cast(src[0])) && src[0] != '\n') - && ((src + 1 == end && TAILING_SPACES) - || ((src + 1 != end) && isspace(static_cast(src[1]))))) { - continue; + } else if (isspace(static_cast(src[0])) && src[0] != '\n') { + if constexpr (TAILING_SPACES) { + if (src + 1 == end) { + continue; + } + } + if (src + 1 != end && isspace(static_cast(src[1]))) { + continue; + } } else if (src[0] == '\n' && dst != begin && dst[-1] == '\n') { continue; } diff --git a/inkcpp/value.cpp b/inkcpp/value.cpp index d16c4452..b0d1d06e 100644 --- a/inkcpp/value.cpp +++ b/inkcpp/value.cpp @@ -20,7 +20,7 @@ template bool truthy_impl(const value& v, const list_table& lists); template<> -bool truthy_impl(const value& v, const list_table& lists) +bool truthy_impl(const value&, const list_table&) { inkFail("Type was not found in operational types or it has no conversion to boolean"); return false; @@ -260,7 +260,7 @@ size_t value::snap(unsigned char* data, const snapper& snapper) const // TODO more space efficent? ptr = snap_write(ptr, &bool_value, max_value_size, should_write); } - return ptr - data; + return static_cast(ptr - data); } const unsigned char* value::snap_load(const unsigned char* ptr, const loader& loader) @@ -269,9 +269,9 @@ const unsigned char* value::snap_load(const unsigned char* ptr, const loader& lo ptr = snap_read(ptr, &bool_value, max_value_size); if (_type == value_type::string) { if (string_value.allocated) { - string_value.str = loader.string_table[(std::uintptr_t)(string_value.str)]; + string_value.str = loader.string_table[static_cast(reinterpret_cast(string_value.str))]; } else { - string_value.str = loader.story_string_table + (std::uintptr_t)(string_value.str); + string_value.str = loader.story_string_table + static_cast(reinterpret_cast(string_value.str)); } } return ptr; diff --git a/inkcpp/value.h b/inkcpp/value.h index 70d70ee9..aef705da 100644 --- a/inkcpp/value.h +++ b/inkcpp/value.h @@ -225,7 +225,7 @@ class value : public snapshot_interface }; template -value redefine::operator()(const T& lh, const T& rh) +value redefine::operator()(const T&, const T& rh) { return value{}.set(rh); } diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index b9bd4813..0809c620 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -152,7 +152,7 @@ int main(int argc, const char** argv) } // If input filename is an .ink file - int val = inputFilename.find(".ink"); + size_t val = inputFilename.find(".ink"); bool json_file_is_tmp_file = false; if (val == inputFilename.length() - 4) { // Create temporary filename @@ -229,7 +229,7 @@ int main(int argc, const char** argv) std::cout << thread->getline(); if (thread->has_tags()) { std::cout << "# tags: "; - for (int i = 0; i < thread->num_tags(); ++i) { + for (ink::size_t i = 0; i < thread->num_tags(); ++i) { if (i != 0) { std::cout << ", "; } @@ -247,7 +247,7 @@ int main(int argc, const char** argv) std::cout << index++ << ": " << c.text(); if (! ommit_choice_tags && c.has_tags()) { std::cout << "\n\t"; - for (size_t i = 0; i < c.num_tags(); ++i) { + for (ink::size_t i = 0; i < c.num_tags(); ++i) { std::cout << "# " << c.get_tag(i) << " "; } } diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index 4f281843..d53e868d 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -59,7 +59,7 @@ struct container_data { uint32_t end_offset = 0; // Index used in CNT? operations - container_t counter_index = ~0; + container_t counter_index = ~0U; ~container_data() { @@ -240,7 +240,7 @@ void binary_emitter::output(std::ostream& out) write_container_map(out, _container_map, _max_container_index); // Write a separator - uint32_t END_MARKER = ~0; + uint32_t END_MARKER = ~0U; out.write(( const char* ) &END_MARKER, sizeof(uint32_t)); // Write container hash list @@ -320,7 +320,7 @@ void binary_emitter::process_paths() bool firstParent = true; // We need to parse the path - offset_t noop_offset = ~0; + offset_t noop_offset = ~0U; char* _context = nullptr; const char* token = ink::compiler::internal::strtok_s(const_cast(path_cstr), ".", &_context); @@ -435,11 +435,11 @@ void binary_emitter::set_list_meta(const list_data& list_defs) _lists.write(flag.flag); if (flag.flag.list_id != list_id) { list_id = flag.flag.list_id; - _lists.write(reinterpret_cast(list_names->data()), list_names->size()); + _lists.write(reinterpret_cast(list_names->data()), static_cast(list_names->size())); ++list_names; _lists.write('\0'); } - _lists.write(reinterpret_cast(flag.name->c_str()), flag.name->size() + 1); + _lists.write(reinterpret_cast(flag.name->c_str()), static_cast(flag.name->size()) + 1); } _lists.write(null_flag); } diff --git a/inkcpp_compiler/binary_stream.cpp b/inkcpp_compiler/binary_stream.cpp index d601a08e..f02529bb 100644 --- a/inkcpp_compiler/binary_stream.cpp +++ b/inkcpp_compiler/binary_stream.cpp @@ -20,7 +20,7 @@ namespace ink constexpr byte_t ZERO = 0; size_t len; if(value.length()) { - len = write((const byte_t*)value.c_str(), value.length()); + len = write(reinterpret_cast(value.c_str()), static_cast(value.length())); } else { len = write(' '); } @@ -48,7 +48,7 @@ namespace ink } // Check how much space we have left - size_t slab_remaining = _currentSlab + DATA_SIZE - _ptr; + size_t slab_remaining = static_cast(_currentSlab + DATA_SIZE - _ptr); // If we're out of space... if (slab_remaining < len) @@ -93,7 +93,7 @@ namespace ink return 0; // Each slabs is size DATA_SIZE then add the position in the current slab - return _slabs.size() * DATA_SIZE + (_ptr - _currentSlab); + return static_cast(_slabs.size() * DATA_SIZE + (_ptr - _currentSlab)); } void binary_stream::set(size_t offset, const byte_t* data, size_t len) diff --git a/inkcpp_compiler/json_compiler.cpp b/inkcpp_compiler/json_compiler.cpp index d7c7dc82..2bbf83c6 100644 --- a/inkcpp_compiler/json_compiler.cpp +++ b/inkcpp_compiler/json_compiler.cpp @@ -58,7 +58,7 @@ void json_compiler::compile( struct container_meta { std::string name; - container_t indexToReturn = ~0; + container_t indexToReturn = ~0U; bool recordInContainerMap = false; vector deferred; CommandFlag cmd_flags = CommandFlag::NO_FLAGS; @@ -412,7 +412,7 @@ void json_compiler::compile_complex_command(const nlohmann::json& command) else if (get(command, "#", val)) { if (_ink_version > 20) { - ink_exception("with inkVerison 21 the tag system chages, and the '#: ' is deprecated now" + throw ink_exception("with inkVerison 21 the tag system chages, and the '#: ' is deprecated now" ); } _emitter->write_string(Command::TAG, CommandFlag::NO_FLAGS, val); diff --git a/shared/public/system.h b/shared/public/system.h index 56ba3cf7..2ec2c914 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -97,8 +97,10 @@ constexpr list_flag empty_flag{-1, 0}; namespace internal { +#pragma warning(push) +#pragma warning(disable : 4514, justification : "functions are defined in header file, they do not need to be used.") /** Checks if a string starts with a given prefix*/ - static bool starts_with(const char* string, const char* prefix) + static inline constexpr bool starts_with(const char* string, const char* prefix) { while (*prefix) { if (*string != *prefix) { @@ -111,7 +113,7 @@ namespace internal } /** Checks if a string is only whitespace*/ - static bool is_whitespace(const char* string, bool includeNewline = true) + static inline constexpr bool is_whitespace(const char* string, bool includeNewline = true) { // Iterate string while (true) { @@ -130,9 +132,9 @@ namespace internal /** check if character can be only part of a word, when two part of word characters put together * the will be a space inserted I049 */ - inline bool is_part_of_word(char character) { return isalpha(character) || isdigit(character); } + static inline bool is_part_of_word(char character) { return isalpha(character) || isdigit(character); } - inline constexpr bool is_whitespace(char character, bool includeNewline = true) + static inline constexpr bool is_whitespace(char character, bool includeNewline = true) { switch (character) { case '\n': From 315662aa97b76912785c3ea0d09f0044cc2abf14 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 26 Nov 2025 14:23:13 +0100 Subject: [PATCH 05/24] fix(snapshots): Store tags for choices with index tags for choices where stord as pointer not as offset in array --- inkcpp/globals_impl.cpp | 1 + inkcpp/runner_impl.cpp | 46 ++++++++++--------- inkcpp/snapshot_impl.cpp | 4 +- inkcpp_test/Fixes.cpp | 28 +++++++++++ .../ink/116_story_with_choice_tags.ink | 5 ++ unreal/UE_example.ink | 18 +++++--- 6 files changed, 71 insertions(+), 31 deletions(-) create mode 100644 inkcpp_test/ink/116_story_with_choice_tags.ink diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 38a68c59..ebc44a4a 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -204,6 +204,7 @@ void globals_impl::initialize_globals(runner_impl* run) { // If no way to move there, then there are no globals. if (! run->move_to(hash_string("global decl"))) { + _globals_initialized = true; return; } diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 148b7bfb..ccc265e2 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -655,10 +655,11 @@ size_t runner_impl::snap(unsigned char* data, snapper& snapper) const ptr += _eval.snap(data ? ptr : nullptr, snapper); ptr += _tags_begin.snap(data ? ptr : nullptr, snapper); ptr += _tags.snap(data ? ptr : nullptr, snapper); - ptr = snap_write(ptr, _entered_global, should_write); - ptr = snap_write(ptr, _entered_knot, should_write); - ptr = snap_write(ptr, _current_knot_id, should_write); - ptr = snap_write(ptr, _current_knot_id_backup, should_write); + snapper.runner_tags = _tags.data(); + ptr = snap_write(ptr, _entered_global, should_write); + ptr = snap_write(ptr, _entered_knot, should_write); + ptr = snap_write(ptr, _current_knot_id, should_write); + ptr = snap_write(ptr, _current_knot_id_backup, should_write); ptr += _container.snap(data ? ptr : nullptr, snapper); ptr += _threads.snap(data ? ptr : nullptr, snapper); ptr = snap_write(ptr, _fallback_choice.has_value(), should_write); @@ -682,23 +683,24 @@ const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& l int32_t seed; ptr = snap_read(ptr, seed); _rng.srand(seed); - ptr = snap_read(ptr, _evaluation_mode); - ptr = snap_read(ptr, _string_mode); - ptr = snap_read(ptr, _saved_evaluation_mode); - ptr = snap_read(ptr, _saved); - ptr = snap_read(ptr, _is_falling); - ptr = _output.snap_load(ptr, loader); - ptr = _stack.snap_load(ptr, loader); - ptr = _ref_stack.snap_load(ptr, loader); - ptr = _eval.snap_load(ptr, loader); - ptr = _tags_begin.snap_load(ptr, loader); - ptr = _tags.snap_load(ptr, loader); - ptr = snap_read(ptr, _entered_global); - ptr = snap_read(ptr, _entered_knot); - ptr = snap_read(ptr, _current_knot_id); - ptr = snap_read(ptr, _current_knot_id_backup); - ptr = _container.snap_load(ptr, loader); - ptr = _threads.snap_load(ptr, loader); + ptr = snap_read(ptr, _evaluation_mode); + ptr = snap_read(ptr, _string_mode); + ptr = snap_read(ptr, _saved_evaluation_mode); + ptr = snap_read(ptr, _saved); + ptr = snap_read(ptr, _is_falling); + ptr = _output.snap_load(ptr, loader); + ptr = _stack.snap_load(ptr, loader); + ptr = _ref_stack.snap_load(ptr, loader); + ptr = _eval.snap_load(ptr, loader); + ptr = _tags_begin.snap_load(ptr, loader); + ptr = _tags.snap_load(ptr, loader); + loader.runner_tags = _tags.data(); + ptr = snap_read(ptr, _entered_global); + ptr = snap_read(ptr, _entered_knot); + ptr = snap_read(ptr, _current_knot_id); + ptr = snap_read(ptr, _current_knot_id_backup); + ptr = _container.snap_load(ptr, loader); + ptr = _threads.snap_load(ptr, loader); bool has_fallback_choice; ptr = snap_read(ptr, has_fallback_choice); _fallback_choice = nullopt; @@ -1459,7 +1461,7 @@ void runner_impl::step() int sequenceLength = _eval.pop().get(); /* shuffel index */ _eval.pop(); - + _eval.push(value{}.set(static_cast(_rng.rand(sequenceLength))) ); diff --git a/inkcpp/snapshot_impl.cpp b/inkcpp/snapshot_impl.cpp index 83f537c7..12d55f6a 100644 --- a/inkcpp/snapshot_impl.cpp +++ b/inkcpp/snapshot_impl.cpp @@ -122,9 +122,9 @@ size_t snap_choice::snap(unsigned char* data, const snapper& snapper) const ptr = snap_write(ptr, false, should_write); } else { ptr = snap_write(ptr, true, should_write); - std::uintptr_t offset_start = _tags_start != nullptr ? _tags_start - snapper.runner_tags : 0; + std::uintptr_t offset_start = _tags_start - snapper.runner_tags; ptr = snap_write(ptr, offset_start, should_write); - std::uintptr_t offset_end = _tags_end != nullptr ? _tags_end - snapper.runner_tags : 0; + std::uintptr_t offset_end = _tags_end - snapper.runner_tags; ptr = snap_write(ptr, offset_end, should_write); } ptr = snap_write(ptr, snapper.strings.get_id(_text), should_write); diff --git a/inkcpp_test/Fixes.cpp b/inkcpp_test/Fixes.cpp index 9dd52aa9..2fd44385 100644 --- a/inkcpp_test/Fixes.cpp +++ b/inkcpp_test/Fixes.cpp @@ -128,3 +128,31 @@ SCENARIO("missing leading whitespace inside choice-only text and glued text _ #1 } } } + +SCENARIO( + "choice tag references are not correctly stored (as pointer instead of index) _ #116", "[fixes]" +) +{ + GIVEN("story with choice tag") + { + std::unique_ptr ink{story::from_file(INK_TEST_RESOURCE_DIR + "116_story_with_choice_tags.bin")}; + runner thread = ink->new_runner(); + WHEN("run story, store, and reload") + { + thread->getall(); + REQUIRE(thread->num_choices() == 1); + REQUIRE(thread->get_choice(0)->num_tags() == 1); + REQUIRE(thread->get_choice(0)->get_tag(0) == std::string("Type:Idle")); + auto* snapshot = thread->create_snapshot(); + THEN("snapshot loaded works") + { + runner loaded = ink->new_runner_from_snapshot(*snapshot); + loaded->getall(); + REQUIRE(loaded->num_choices() == 1); + REQUIRE(loaded->get_choice(0)->num_tags() == 1); + REQUIRE(loaded->get_choice(0)->get_tag(0) == std::string("Type:Idle")); + } + } + } +} diff --git a/inkcpp_test/ink/116_story_with_choice_tags.ink b/inkcpp_test/ink/116_story_with_choice_tags.ink new file mode 100644 index 00000000..0d649927 --- /dev/null +++ b/inkcpp_test/ink/116_story_with_choice_tags.ink @@ -0,0 +1,5 @@ +-> Entrance_cycle += Entrance_cycle ++ (look_around) [look around # Type:Idle ] + While watching around you, <> + -> Entrance_cycle diff --git a/unreal/UE_example.ink b/unreal/UE_example.ink index 77cc9e9c..14ff0249 100644 --- a/unreal/UE_example.ink +++ b/unreal/UE_example.ink @@ -1,3 +1,4 @@ +# date:2025.03.22 LIST Potions = TalkWithAnimals, Invisibility LIST Clues = Skull, Feather VAR Inventory = (Skull, TalkWithAnimals) @@ -51,28 +52,31 @@ You startk walking to {to}. -> END === Faint +# background:Unconscious You collapse, the next thing you can remember is how you are given to the ambulance. No further action for today ... -> DONE === Mansion = Car +# background:Car You step outside your car. Its a wired feeling beehing here again. -> Car_cycle = Car_cycle -+ (look_around)[look around] - It is a strange day. Despite it beeing spring, the sky is one massive gray soup. ++ (look_around)[look around # Type:Idle] + It is a strange day. Despite it beeing spring, the sky is one massive gray soup. # style:Gray -> Car_cycle + [go to the mension] ~ walking("Mansion.Entrance") -> Entrance = Entrance +# background:Mansion {not Mansion.look_around: Just in time you are able to see the door, someone with with a yellow summer dress enters it.} You climbing the 56 steps up to the door, high water is a dump thing. -> Entrance_cycle = Entrance_cycle -+ (look_around) [look around] ++ (look_around) [look around # Type:Idle ] While watching around you, <> {Inventory hasnt Invisibility: see a small bottle in the Pot next to the door. @@ -82,11 +86,11 @@ You climbing the 56 steps up to the door, high water is a dump thing. -> Entrance_cycle * {look_around} [Pick up the bottle] ~ Inventory += Invisibility - You pick up the bottle and inspect it more. It is labeld "Invisible", just this one word written with and edding. + You pick up the bottle and inspect it more. It is labeld "Invisible, just this one word written with and edding. -> Entrance_cycle -+ [Knock] - "Ahh", you cry while reaching for the door bell. Saying it was charched would be an understatement. ++ (knock)[Knock {knock: again?} # {knock: Type:Danger} ] + "Ahh", you cry while reaching for the door bell. Saying it was charched would be an understatement. ~ Health -= 20 { Health <= 0: -> Faint} -> Entrance_cycle --> DONE \ No newline at end of file +-> DONE From ee4333631928006ad2f02706ef1a299c28671c01 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 26 Nov 2025 17:18:06 +0100 Subject: [PATCH 06/24] fix(snapshot): cleare temporary data used for reconstruction reconstructing globals from a snapshot leaves some state behind, when reconstruction again globals from the same snapshot this data need to be cleared first. refactor: fixes clang compiler warnings --- .clang-format-ignore | 1 + .pre-commit-config.yaml | 3 +- inkcpp/array.h | 12 +- inkcpp/avl_array.h | 8 +- inkcpp/choice.cpp | 5 +- inkcpp/collections/restorable.cpp | 8 +- inkcpp/collections/restorable.h | 25 +- inkcpp/container_operations.cpp | 61 +- inkcpp/executioner.h | 288 ++++---- inkcpp/globals_impl.cpp | 1 + inkcpp/include/choice.h | 3 - inkcpp/include/list.h | 24 +- inkcpp/include/story_ptr.h | 5 +- inkcpp/include/types.h | 4 - inkcpp/list_impl.cpp | 3 +- inkcpp/list_impl.h | 1 + inkcpp/list_table.cpp | 10 +- inkcpp/list_table.h | 35 +- inkcpp/output.cpp | 7 +- inkcpp/runner_impl.cpp | 6 +- inkcpp/simple_restorable_stack.h | 3 +- inkcpp/snapshot_interface.h | 19 +- inkcpp/stack.cpp | 1028 ++++++++++++++-------------- inkcpp/story_impl.cpp | 19 +- inkcpp/story_impl.h | 6 +- inkcpp/string_table.cpp | 1 - inkcpp/string_utils.h | 6 +- inkcpp/value.cpp | 11 +- inkcpp_cl/inkcpp_cl.cpp | 23 +- inkcpp_compiler/binary_emitter.cpp | 15 +- inkcpp_compiler/binary_stream.cpp | 264 ++++--- inkcpp_compiler/json.hpp | 2 - inkcpp_compiler/json_compiler.cpp | 5 +- inkcpp_test/Callstack.cpp | 20 +- inkcpp_test/Fixes.cpp | 19 +- inkcpp_test/Observer.cpp | 100 +-- inkcpp_test/Restorable.cpp | 162 +++-- shared/private/header.h | 70 +- shared/public/system.h | 20 +- 39 files changed, 1212 insertions(+), 1091 deletions(-) create mode 100644 .clang-format-ignore diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 00000000..faac041c --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1 @@ +inkcpp_compiler/json.hpp diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 38ab6a8d..cb068f80 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,10 @@ repos: - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v18.1.0 + rev: v18.1.8 hooks: - id: clang-format types_or: [c++, c, cuda] + exclude: inkcpp_compiler/json.hpp - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 diff --git a/inkcpp/array.h b/inkcpp/array.h index 38d1135b..eb009f54 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -57,9 +57,17 @@ class managed_array : public snapshot_interface } } - const T& operator[](size_t i) const { return data()[i]; } + const T& operator[](size_t i) const + { + inkAssert(i < _size, "Access array out of bounds, index %u in array of size %u", i, _size); + return data()[i]; + } - T& operator[](size_t i) { return data()[i]; } + T& operator[](size_t i) + { + inkAssert(i < _size, "Access array out of bounds, index %u in array of size %u", i, _size); + return data()[i]; + } const T* data() const { diff --git a/inkcpp/avl_array.h b/inkcpp/avl_array.h index 50fa36ce..d3847a51 100644 --- a/inkcpp/avl_array.h +++ b/inkcpp/avl_array.h @@ -70,7 +70,7 @@ class avl_array ink::runtime::internal::if_t balance_; ink::runtime::internal::if_t child_; size_type size_; // actual size - size_type _capacity; + size_type _capacity; size_type root_; // root node ink::runtime::internal::if_t parent_; @@ -112,6 +112,12 @@ class avl_array return *this; } + tag_avl_array_iterator(const tag_avl_array_iterator& other) + : instance_{other.instance_} + , idx_{other.idx_} + { + } + inline bool operator==(const tag_avl_array_iterator& rhs) const { return idx_ == rhs.idx_; } inline bool operator!=(const tag_avl_array_iterator& rhs) const { return ! (*this == rhs); } diff --git a/inkcpp/choice.cpp b/inkcpp/choice.cpp index 6e15f296..97e4b1dd 100644 --- a/inkcpp/choice.cpp +++ b/inkcpp/choice.cpp @@ -16,7 +16,10 @@ namespace ink namespace runtime { - size_t choice::num_tags() const { return static_cast(std::distance(_tags_start, _tags_end)); } + size_t choice::num_tags() const + { + return static_cast(std::distance(_tags_start, _tags_end)); + } const char* choice::get_tag(size_t index) const { diff --git a/inkcpp/collections/restorable.cpp b/inkcpp/collections/restorable.cpp index 6bbe98a1..643d390a 100644 --- a/inkcpp/collections/restorable.cpp +++ b/inkcpp/collections/restorable.cpp @@ -16,10 +16,10 @@ unsigned char* ptr = snapshot_interface::snap_write(ptr, jump, write); ptr = snapshot_interface::snap_write(ptr, save, write); max = pos; - if (jump != ~0 && jump > max) { + if (jump != ~0U && jump > max) { max = jump; } - if (save != ~0 && save > max) { + if (save != ~0U && save > max) { max = save; } return ptr; @@ -32,10 +32,10 @@ const unsigned char* ptr = snapshot_interface::snap_read(ptr, jump); ptr = snapshot_interface::snap_read(ptr, save); max = pos; - if (jump != ~0 && jump > max) { + if (jump != ~0U && jump > max) { max = jump; } - if (save != ~0 && save > max) { + if (save != ~0U && save > max) { max = save; } return ptr; diff --git a/inkcpp/collections/restorable.h b/inkcpp/collections/restorable.h index 4478b905..7f8b9b7a 100644 --- a/inkcpp/collections/restorable.h +++ b/inkcpp/collections/restorable.h @@ -6,7 +6,7 @@ */ #pragma once -#include "../snapshot_impl.h" +#include "../snapshot_interface.h" #include #include @@ -102,15 +102,15 @@ class restorable : public snapshot_interface } // Checks if we have a save state - bool is_saved() const { return _save != ~0; } + bool is_saved() const { return _save != ~0U; } // Creates a save point which can later be restored to or forgotten void save() { inkAssert( - _save == ~0, "Collection is already saved. You should never call save twice. Ignoring." + _save == ~0U, "Collection is already saved. You should never call save twice. Ignoring." ); - if (_save != ~0) { + if (_save != ~0U) { return; } @@ -121,8 +121,8 @@ class restorable : public snapshot_interface // Restore to the last save point void restore() { - inkAssert(_save != ~0, "Collection can't be restored because it's not saved. Ignoring."); - if (_save == ~0) { + inkAssert(_save != ~0U, "Collection can't be restored because it's not saved. Ignoring."); + if (_save == ~0U) { return; } @@ -137,8 +137,8 @@ class restorable : public snapshot_interface template void forget(NullifyMethod nullify) { - inkAssert(_save != ~0, "Can't forget save point because there is none. Ignoring."); - if (_save == ~0) { + inkAssert(_save != ~0U, "Can't forget save point because there is none. Ignoring."); + if (_save == ~0U) { return; } @@ -170,7 +170,7 @@ class restorable : public snapshot_interface ElementType& push(const ElementType& elem) { // Don't destroy saved data. Jump over it - if (_pos < _save && _save != ~0) { + if (_pos < _save && _save != ~0U) { _jump = _pos; _pos = _save; } @@ -279,7 +279,7 @@ class restorable : public snapshot_interface void for_each_all(CallbackMethod callback) const { // no matter if we're saved or not, we iterate everything - int len = (_save == ~0 || _pos > _save) ? _pos : _save; + int len = (_save == ~0U || _pos > _save) ? _pos : _save; // Iterate for (int i = 0; i < len; i++) @@ -358,10 +358,7 @@ class restorable : public snapshot_interface protected: // Called when we run out of space in buffer. - virtual void overflow(ElementType*&, size_t&) - { - inkFail("Restorable run out of memory!"); - } + virtual void overflow(ElementType*&, size_t&) { inkFail("Restorable run out of memory!"); } private: template diff --git a/inkcpp/container_operations.cpp b/inkcpp/container_operations.cpp index 04550cca..a724baa2 100644 --- a/inkcpp/container_operations.cpp +++ b/inkcpp/container_operations.cpp @@ -13,40 +13,35 @@ #include -namespace ink::runtime::internal { +namespace ink::runtime::internal +{ - void operation::operator()( - basic_eval_stack& stack, value* vals) - { - container_t id; - bool success = _story.get_container_id( - _story.instructions() + vals[0].get(), - id); - inkAssert(success, "failed to find container to read visit count!"); - stack.push(value{}.set( - static_cast(_visit_counts.visits( id ) - ))); - } - - void operation::operator()( - basic_eval_stack& stack, value* vals) - { - container_t id; - bool success = _story.get_container_id( - _story.instructions() + vals[0].get(), - id); - inkAssert(success, "failed to find container to read turn count!"); - stack.push(value{}.set( - static_cast(_visit_counts.turns(id) - ))); - } +void operation::operator()( + basic_eval_stack& stack, value* vals +) +{ + container_t id; + bool success + = _story.get_container_id(_story.instructions() + vals[0].get(), id); + inkAssert(success, "failed to find container to read visit count!"); + stack.push(value{}.set(static_cast(_visit_counts.visits(id)))); +} - void operation::operator() - (basic_eval_stack& stack, value*) - { - stack.push(value{}.set(static_cast( - _runner.num_choices() - ))); - } +void operation::operator()( + basic_eval_stack& stack, value* vals +) +{ + container_t id; + bool success + = _story.get_container_id(_story.instructions() + vals[0].get(), id); + inkAssert(success, "failed to find container to read turn count!"); + stack.push(value{}.set(static_cast(_visit_counts.turns(id)))); +} +void operation< + Command::CHOICE_COUNT, value_type::none, void>::operator()(basic_eval_stack& stack, value*) +{ + stack.push(value{}.set(static_cast(_runner.num_choices()))); } + +} // namespace ink::runtime::internal diff --git a/inkcpp/executioner.h b/inkcpp/executioner.h index fbedc7a7..fdcdec62 100644 --- a/inkcpp/executioner.h +++ b/inkcpp/executioner.h @@ -31,149 +31,179 @@ #include "stack.h" #include "operations.h" +namespace ink::runtime::internal +{ + +/** + * @brief iterates through value_types until it found a matching operator. + * Matching means a operator which implements the command for the type. + * @tparam cmd Command to search operation for. + * @tparam t value type to start search + * @tparam Offset t + Offset is real start, used because trouble with mscv + * @return value_type::OP_END, if no "next operation" found + * @return type which is greater t + Offset and implement the command + */ +template +constexpr value_type next_operatable_type() +{ + constexpr value_type ty = t + Offset; + if constexpr (operation::enabled) { + return ty; + } else if constexpr (ty >= value_type::OP_END) { + return value_type::OP_END; + } else { + return next_operatable_type(); + } +} +/** + * @brief Iterates through all existing operations for this Command. + */ +template()> +class typed_executer +{ +public: + static constexpr bool enabled = true; + + template + typed_executer(const T& t) + : _typed_exe{t} + , _op{t} + { + } -namespace ink::runtime::internal { - - /** - * @brief iterates through value_types until it found a matching operator. - * Matching means a operator which implements the command for the type. - * @tparam cmd Command to search operation for. - * @tparam t value type to start search - * @tparam Offset t + Offset is real start, used because trouble with mscv - * @return value_type::OP_END, if no "next operation" found - * @return type which is greater t + Offset and implement the command - */ - template - constexpr value_type next_operatable_type() { - constexpr value_type ty = t + Offset; - if constexpr (operation::enabled) { - return ty; - } else if constexpr (ty >= value_type::OP_END){ - return value_type::OP_END; + void operator()(value_type t, basic_eval_stack& s, value* v) + { + if (t == ty) { + _op(s, v); } else { - return next_operatable_type(); + _typed_exe(t, s, v); } } - /** - * @brief Iterates through all existing operations for this Command. - */ - template()> - class typed_executer { - public: - static constexpr bool enabled = true; - template - typed_executer(const T& t) : _typed_exe{t}, _op{t} {} - - void operator()(value_type t, basic_eval_stack& s, value* v) { - if (t == ty) { _op(s, v); } - else { _typed_exe(t, s, v); } - } - private: - // skip command for not implemented types - typed_executer(ty),1>()> _typed_exe; - operation _op; - }; - - // end of recursion (has no operation attached to it) - template - class typed_executer { - public: - static constexpr bool enabled = false; - template - typed_executer(const T&) {} - - void operator()(value_type, basic_eval_stack&, value*) { - inkFail("Operation for value not supported!"); - } - }; +private: + // skip command for not implemented types + typed_executer(ty), 1>()> _typed_exe; + operation _op; +}; + +// end of recursion (has no operation attached to it) +template +class typed_executer +{ +public: + static constexpr bool enabled = false; + + template + typed_executer(const T&) + { + } - /** - * @brief Find next command which is at least for one type implemented. - * @tparam c command to start search - * @tparam Offset offset to start search, used because of trouble with mscv - * @return Command::OP_END if no next operation is found - * @return next command witch at least of implementation. - */ - template - constexpr Command next_operatable_command() { - constexpr Command cmd = c + Offset; - if constexpr (typed_executer::enabled) { - return cmd; - } else if constexpr (cmd >= Command::OP_END){ - return Command::OP_END; + void operator()(value_type, basic_eval_stack&, value*) + { + inkFail("Operation for value not supported!"); + } +}; + +/** + * @brief Find next command which is at least for one type implemented. + * @tparam c command to start search + * @tparam Offset offset to start search, used because of trouble with mscv + * @return Command::OP_END if no next operation is found + * @return next command witch at least of implementation. + */ +template +constexpr Command next_operatable_command() +{ + constexpr Command cmd = c + Offset; + if constexpr (typed_executer::enabled) { + return cmd; + } else if constexpr (cmd >= Command::OP_END) { + return Command::OP_END; + } else { + return next_operatable_command(); + } +} + +/** + * @brief Iterate through all commands to find correct command. + * Also instantiates all typed_executer and with them the operations. + */ +template()> +class executer_imp +{ +public: + template + executer_imp(const T& t) + : _exe{t} + , _typed_exe{t} + { + } + + void operator()(Command c, basic_eval_stack& s) + { + if (c == cmd) { + static constexpr size_t N = command_num_args(cmd); + if constexpr (N == 0) { + value_type ty = casting::common_base<0>(nullptr); + _typed_exe(ty, s, nullptr); + } else { + value args[N]; + for (int i = command_num_args(cmd) - 1; i >= 0; --i) { + args[i] = s.pop(); + } + value_type ty = casting::common_base(args); + _typed_exe(ty, s, args); + } } else { - return next_operatable_command(); + _exe(c, s); } } +private: + executer_imp()> _exe; + typed_executer _typed_exe; +}; + +/// end of recursion +template<> +class executer_imp +{ +public: + template + executer_imp(const T&) + { + } + + void operator()(Command, basic_eval_stack&) { inkFail("requested command was not found!"); } +}; + +/** + * @brief Class which instantiates all operations and give access to them. + */ +class executer +{ +public: /** - * @brief Iterate through all commands to find correct command. - * Also instantiates all typed_executer and with them the operations. + * @brief pass all arguments to operations who need them. + * @attention each type need to be unique for the look up later! + * @tparam Args argument types + * @param args arguments */ - template()> - class executer_imp { - public: - template - executer_imp(const T& t) : _exe{t}, _typed_exe{t}{} - - void operator()(Command c, basic_eval_stack& s) { - if (c == cmd) { - static constexpr size_t N = command_num_args(cmd); - if constexpr (N == 0) { - value_type ty = casting::common_base<0>(nullptr); - _typed_exe(ty, s, nullptr); - } else { - value args[N]; - for (int i = command_num_args(cmd)-1; i >= 0 ; --i) { - args[i] = s.pop(); - } - value_type ty = casting::common_base(args); - _typed_exe(ty, s, args); - } - } else { _exe(c, s); } - } - private: - executer_imp()> _exe; - typed_executer _typed_exe; - }; - - /// end of recursion - template<> - class executer_imp { - public: - template - executer_imp(const T&) {} - void operator()(Command, basic_eval_stack&) { - inkFail("requested command was not found!"); - } - }; + template + executer(Args&... args) + : _executer{tuple(&args...)} + { + } /** - * @brief Class which instantiates all operations and give access to them. + * @brief execute command on stack. + * @param cmd command to execute + * @param stack stack to operate on */ - class executer { - public: - /** - * @brief pass all arguments to operations who need them. - * @attention each type need to be unique for the look up later! - * @tparam Args argument types - * @param args arguments - */ - template - executer(Args& ... args) : _executer{tuple(&args...)} {} - - /** - * @brief execute command on stack. - * @param cmd command to execute - * @param stack stack to operate on - */ - void operator()(Command cmd, basic_eval_stack& stack) { - _executer(cmd, stack); - } - private: - executer_imp _executer; - }; -} + void operator()(Command cmd, basic_eval_stack& stack) { _executer(cmd, stack); } +private: + executer_imp _executer; +}; +} // namespace ink::runtime::internal diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index ebc44a4a..6003b107 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -10,6 +10,7 @@ #include "snapshot_impl.h" #include "system.h" #include "types.h" +#include "value.h" namespace ink::runtime::internal { diff --git a/inkcpp/include/choice.h b/inkcpp/include/choice.h index 8748dee1..489ac305 100644 --- a/inkcpp/include/choice.h +++ b/inkcpp/include/choice.h @@ -80,10 +80,7 @@ namespace runtime uint32_t _path = ~0U; ///< @private thread_t _thread = ~0U; ///< @private int _index = -1; ///< @private -#pragma warning(push) -#pragma warning(disable : 4820, justification : "4 byte aligment free on 64-bit systems") }; -#pragma warning(pop) } // namespace runtime } // namespace ink diff --git a/inkcpp/include/list.h b/inkcpp/include/list.h index 110372de..72dc1f9a 100644 --- a/inkcpp/include/list.h +++ b/inkcpp/include/list.h @@ -49,6 +49,7 @@ class list_interface } virtual list_interface& operator=(const list_interface&) = default; + virtual ~list_interface() {} /** iterater for flags in a list @@ -61,8 +62,6 @@ class list_interface const list_interface& _list; int _i; bool _one_list_iterator; ///< iterates only though values of one list -#pragma warning(push) -#pragma warning(disable : 4820, justification : "3 byte aligment free on 64-bit systems") friend list_interface; #ifdef INK_BUILD_CLIB friend int ::ink_list_flags(const HInkList*, InkListIter*); @@ -123,10 +122,15 @@ class list_interface }; -#pragma warning(push) -#pragma warning( \ - disable : 4100, justification : "non functional prototypes do not need the argument." \ -) +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-parameter" +#else +# pragma warning(push) +# pragma warning( \ + disable : 4100, justification : "non functional prototypes do not need the argument." \ + ) +#endif /** checks if a flag is contained in the list */ virtual bool contains(const char* flag) const @@ -177,7 +181,11 @@ class list_interface inkAssert(false, "Not implemented funciton from interface is called!"); }; -#pragma warning(pop) +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#else +# pragma warning(pop) +#endif protected: /** @private */ @@ -199,5 +207,3 @@ class list_interface }; } // namespace ink::runtime - -#pragma warning(pop) diff --git a/inkcpp/include/story_ptr.h b/inkcpp/include/story_ptr.h index be67ba4a..0c33c46a 100644 --- a/inkcpp/include/story_ptr.h +++ b/inkcpp/include/story_ptr.h @@ -19,16 +19,13 @@ namespace internal , valid(true) { } + static void remove_reference(ref_block*&); size_t references; bool valid; -#pragma warning(push) -#pragma warning(disable : 4820, justification : "3 byte aligment free on 64-bit systems") }; -#pragma warning(pop) - /** @private */ class story_ptr_base { diff --git a/inkcpp/include/types.h b/inkcpp/include/types.h index 08b25453..8183e6c2 100644 --- a/inkcpp/include/types.h +++ b/inkcpp/include/types.h @@ -54,8 +54,6 @@ struct value { Float, ///< containing a float List ///< containing a @ref list_interface } type; ///< Label of type currently contained in @ref value -#pragma warning(push) -#pragma warning(disable : 4820, justification : "4 byte aligment free on 64-bit systems") value() : v_int32{0} @@ -128,8 +126,6 @@ struct value { } }; -#pragma warning(pop) - /** access #value::Type::Bool value */ template<> inline const auto& value::get() const diff --git a/inkcpp/list_impl.cpp b/inkcpp/list_impl.cpp index f61f47a6..030c4684 100644 --- a/inkcpp/list_impl.cpp +++ b/inkcpp/list_impl.cpp @@ -61,7 +61,8 @@ void list_impl::next(const char*& flag_name, const char*& list_name, int& i, boo } while (! _list_table->has(list_table::list{_list}, flag)) { ++flag.flag; - if (static_cast(flag.flag) >= _list_table->_list_end[flag.list_id] - _list_table->listBegin(flag.list_id)) { + if (static_cast(flag.flag) + >= _list_table->_list_end[flag.list_id] - _list_table->listBegin(flag.list_id)) { goto next_list; } } diff --git a/inkcpp/list_impl.h b/inkcpp/list_impl.h index 5d1c6cff..1aefb2be 100644 --- a/inkcpp/list_impl.h +++ b/inkcpp/list_impl.h @@ -22,6 +22,7 @@ class list_impl final : public list_interface } list_impl& operator=(const list_impl&) = default; + ~list_impl() override {} int get_lid() const { return _list; } diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index fd0195b9..703de027 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -170,10 +170,11 @@ char* list_table::toString(char* out, const list& l) const continue; } int value = _flag_values[j]; - // the cast is ok, since if we are in the + // the cast is ok, since if we are in the // first round, `first` is true and we do not evaluate // second round, `last_list` is >= 0 - if (first || value > last_value || (value == last_value && i > static_cast(last_list))) { + if (first || value > last_value + || (value == last_value && i > static_cast(last_list))) { if (min_id == -1 || value < min_value) { change = true; min_list = i; @@ -715,7 +716,7 @@ optional list_table::toFlag(const char* flag_name) const if (str_equal(*flag_itr, flag_name)) { size_t fid = static_cast(flag_itr - _flag_names.begin()); size_t lid = 0; - int begin = 0; + int begin = 0; for (auto* list_itr = _list_end.begin(); list_itr != _list_end.end(); ++list_itr) { if (*list_itr > fid) { lid = static_cast(list_itr - _list_end.begin()); @@ -803,7 +804,8 @@ std::ostream& list_table::write(std::ostream& os, list l) const // the cast is ok, since if we are in the // first round, `first` is true and we do not evaluate // second round, `last_list` is >= 0 - if (first || value > last_value || (value == last_value && i > static_cast(last_list))) { + if (first || value > last_value + || (value == last_value && i > static_cast(last_list))) { if (min_id == -1 || value < min_value) { min_value = value; min_id = j; diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index c13e372c..b0d12ab1 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -78,7 +78,8 @@ class list_table : public snapshot_interface return flag; } inkAssert(flag.list_id >= 0); - for (size_t i = listBegin(static_cast(flag.list_id)); i < _list_end[static_cast(flag.list_id)]; ++i) { + for (size_t i = listBegin(static_cast(flag.list_id)); + i < _list_end[static_cast(flag.list_id)]; ++i) { if (_flag_values[i] == flag.flag) { flag.flag = static_cast(i - listBegin(static_cast(flag.list_id))); return flag; @@ -91,7 +92,8 @@ class list_table : public snapshot_interface int get_flag_value(list_flag flag) const { inkAssert(flag.list_id >= 0 && flag.flag >= 0); - return _flag_values[listBegin(static_cast(flag.list_id)) + static_cast(flag.flag)]; + return _flag_values + [listBegin(static_cast(flag.list_id)) + static_cast(flag.flag)]; } /// zeros all usage values @@ -261,7 +263,10 @@ class list_table : public snapshot_interface void copy_lists(const data_t* src, data_t* dst); static constexpr size_t bits_per_data = sizeof(data_t) * 8U; - size_t listBegin(size_t lid) const { return lid == 0 ? 0 : _list_end[static_cast(lid - 1)]; } + size_t listBegin(size_t lid) const + { + return lid == 0 ? 0 : _list_end[static_cast(lid - 1)]; + } const data_t* getPtr(int eid) const { @@ -298,11 +303,12 @@ class list_table : public snapshot_interface } } - bool hasList(const data_t* data, int lid) const { + bool hasList(const data_t* data, int lid) const + { if (lid < 0) { return false; - } - return getBit(data, static_cast(lid)); + } + return getBit(data, static_cast(lid)); } void setList(data_t* data, int lid, bool value = true) @@ -312,11 +318,13 @@ class list_table : public snapshot_interface } } - bool hasFlag(const data_t* data, int fid) const { + bool hasFlag(const data_t* data, int fid) const + { if (fid < 0) { return false; - } - return getBit(data, static_cast(fid) + numLists()); } + } + return getBit(data, static_cast(fid) + numLists()); + } void setFlag(data_t* data, int fid, bool value = true) { @@ -383,7 +391,9 @@ class list_table : public snapshot_interface */ void carry() { - if (_pos.flag.flag < 0 || _pos.flag.list_id < 0) { return; } + if (_pos.flag.flag < 0 || _pos.flag.list_id < 0) { + return; + } if (static_cast(_pos.flag.flag) == _list._list_end[static_cast(_pos.flag.list_id)] - _list.listBegin(static_cast(_pos.flag.list_id))) { @@ -401,7 +411,7 @@ class list_table : public snapshot_interface { bool valid; do { - valid = true; + valid = true; size_t fid = _list.toFid(_pos.flag); if (_data == nullptr) { if (_list._flag_names[fid] == nullptr) { @@ -411,7 +421,8 @@ class list_table : public snapshot_interface } else if (! _list.hasList(_data, _pos.flag.list_id)) { valid = false; ++_pos.flag.list_id; - } else if (! _list.hasFlag(_data, static_cast(fid)) || _list._flag_names[fid] == nullptr) { + } else if (! _list.hasFlag(_data, static_cast(fid)) + || _list._flag_names[fid] == nullptr) { valid = false; ++_pos.flag.flag; } diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index c00fd4c8..6cb7eee6 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -33,10 +33,7 @@ void basic_stream::initelize_data(value* buffer, size_t size) _max = size; } -void basic_stream::overflow(value*&, size_t&, size_t) -{ - inkFail("Stack overflow!"); -} +void basic_stream::overflow(value*&, size_t&, size_t) { inkFail("Stack overflow!"); } void basic_stream::append(const value& in) { @@ -145,7 +142,7 @@ inline void write_char(std::stringstream& output, char c) inline bool get_next(const value* list, size_t i, size_t size, const value** next) { while (i + 1 < size) { - *next = &list[i + 1]; + *next = &list[i + 1]; if ((*next)->printable()) { return true; } diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index ccc265e2..9fad54e7 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -42,7 +42,7 @@ namespace ink::runtime::internal hash_t runner_impl::get_current_knot() const { - return _current_knot_id == ~0 ? 0 : _story->container_hash(_current_knot_id); + return _current_knot_id == ~0U ? 0 : _story->container_hash(_current_knot_id); } template<> @@ -599,7 +599,7 @@ void runner_impl::choose(size_t index) // Figure out where our previous pointer was for that thread ip_t prev = nullptr; - if (choiceThread == ~0) { + if (choiceThread == ~0U) { prev = _done; } else { prev = _threads.get(choiceThread); @@ -1543,7 +1543,7 @@ void runner_impl::on_done(bool setDone) void runner_impl::set_done_ptr(ip_t ptr) { thread_t curr = current_thread(); - if (curr == ~0) { + if (curr == ~0U) { _done = ptr; } else { _threads.set(curr, ptr); diff --git a/inkcpp/simple_restorable_stack.h b/inkcpp/simple_restorable_stack.h index 4e4d8692..12ce3d40 100644 --- a/inkcpp/simple_restorable_stack.h +++ b/inkcpp/simple_restorable_stack.h @@ -313,8 +313,7 @@ size_t simple_restorable_stack::snap(unsigned char* data, const snapper&) con } template -const unsigned char* - simple_restorable_stack::snap_load(const unsigned char* ptr, const loader&) +const unsigned char* simple_restorable_stack::snap_load(const unsigned char* ptr, const loader&) { T null; ptr = snap_read(ptr, null); diff --git a/inkcpp/snapshot_interface.h b/inkcpp/snapshot_interface.h index 8693bbe4..42c4eb50 100644 --- a/inkcpp/snapshot_interface.h +++ b/inkcpp/snapshot_interface.h @@ -66,10 +66,15 @@ class snapshot_interface loader& operator=(const loader&) = delete; }; -#pragma warning(push) -#pragma warning( \ - disable : 4100, justification : "non functional prototypes do not need the argument." \ -) +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-parameter" +#else +# pragma warning(push) +# pragma warning( \ + disable : 4100, justification : "non functional prototypes do not need the argument." \ + ) +#endif size_t snap(unsigned char* data, snapper&) const { @@ -83,6 +88,10 @@ class snapshot_interface return nullptr; }; -#pragma warning(pop) +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#else +# pragma warning(pop) +#endif }; } // namespace ink::runtime::internal diff --git a/inkcpp/stack.cpp b/inkcpp/stack.cpp index 44dcb4d9..dd9c562f 100644 --- a/inkcpp/stack.cpp +++ b/inkcpp/stack.cpp @@ -9,610 +9,620 @@ namespace ink::runtime::internal { - basic_stack::basic_stack(entry* data, size_t size) - : base(data, size) - { +basic_stack::basic_stack(entry* data, size_t size) + : base(data, size) +{ +} + +void basic_stack::set(hash_t name, const value& val) +{ + // If we have a save point, always add no matter what + if (base::is_saved()) { + add(name, val); + return; + } + + // Either set an existing variable or add it to the stack + value* existing = const_cast(get(name)); + if (existing == nullptr) + add(name, val); + else + *existing = val; +} + +bool reverse_find_predicat(hash_t name, thread_t& skip, uint32_t& jumping, entry& e) +{ + // Jumping + if (jumping > 0) { + jumping--; + return false; } - void basic_stack::set(hash_t name, const value& val) - { - // If we have a save point, always add no matter what - if (base::is_saved()) - { - add(name, val); - return; + // If this is an end thread marker, skip over it + if (skip == ~0U && e.data.type() == value_type::thread_end) { + skip = e.data.get(); + } + + // If we're skipping + if (skip != ~0U) { + // Stop if we get to the start of the thread block + if (e.data.type() == value_type::thread_start + && skip == e.data.get().jump) { + skip = ~0U; } - // Either set an existing variable or add it to the stack - value* existing = const_cast(get(name)); - if (existing == nullptr) - add(name, val); - else - *existing = val; + // Don't return anything in the hidden thread block + return false; } - bool reverse_find_predicat(hash_t name, thread_t& skip, uint32_t& jumping, entry& e) { - // Jumping - if (jumping > 0) { - jumping--; - return false; - } + // Is it a thread start or a jump marker + if (e.name == InvalidHash + && (e.data.type() == value_type::thread_start || e.data.type() == value_type::jump_marker)) { + // If this thread start has a jump value + uint32_t jump = e.data.get().thread_id; - // If this is an end thread marker, skip over it - if (skip == ~0 && e.data.type() == value_type::thread_end) { - skip = e.data.get(); - } + // Then we need to do some jumping. Skip + if (jump > 0) { + jumping = jump; + return false; + } + } - // If we're skipping - if (skip != ~0) { - // Stop if we get to the start of the thread block - if (e.data.type() == value_type::thread_start && skip == e.data.get().jump) { - skip = ~0U; - } + return e.name == name || e.name == InvalidHash; +} - // Don't return anything in the hidden thread block - return false; - } +class reverse_find_predicat_operator +{ +public: + explicit reverse_find_predicat_operator(hash_t name) + : _name{name} + { + } - // Is it a thread start or a jump marker - if (e.name == InvalidHash && (e.data.type() == value_type::thread_start || e.data.type() == value_type::jump_marker)) - { - // If this thread start has a jump value - uint32_t jump = e.data.get().thread_id; - - // Then we need to do some jumping. Skip - if (jump > 0) { - jumping = jump; - return false; - } - } + bool operator()(entry& e) { return reverse_find_predicat(_name, _skip, _jumping, e); } - return e.name == name || e.name == InvalidHash; +private: + hash_t _name; + thread_t _skip = ~0U; + uint32_t _jumping = 0; +}; + +class reverse_find_from_frame_predicat_operator +{ +public: + reverse_find_from_frame_predicat_operator(int ci, hash_t name) + : _ci{ci} + , _name{name} + { + inkAssert(ci == -1 || ci == 0, "only support ci == -1, for now!"); } - class reverse_find_predicat_operator { - public: - explicit reverse_find_predicat_operator(hash_t name) : _name{name} {} - bool operator()(entry& e) { - return reverse_find_predicat(_name, _skip, _jumping, e); - } - private: - hash_t _name; - thread_t _skip = ~0U; - uint32_t _jumping = 0; - }; - class reverse_find_from_frame_predicat_operator { - public: - reverse_find_from_frame_predicat_operator(int ci, hash_t name) : _ci{ci}, _name{name} { - inkAssert(ci == -1 || ci == 0, "only support ci == -1, for now!"); - } - bool operator()(entry& e) { - if(reverse_find_predicat(_name, _skip, _jumping, e)) { - if(_ci == _current_frame) { return true; } - _current_frame -= 1; + + bool operator()(entry& e) + { + if (reverse_find_predicat(_name, _skip, _jumping, e)) { + if (_ci == _current_frame) { + return true; } - return false; + _current_frame -= 1; } - private: - int _ci; - int _current_frame = 0; - hash_t _name; - thread_t _skip = ~0U; - uint32_t _jumping = 0; - }; - - const value* basic_stack::get(hash_t name) const { - // Find whatever comes first: a matching entry or a stack frame entry - const entry* found = base::reverse_find(reverse_find_predicat_operator(name)); + return false; + } - // If nothing found, no value - if (found == nullptr) - return nullptr; +private: + int _ci; + int _current_frame = 0; + hash_t _name; + thread_t _skip = ~0U; + uint32_t _jumping = 0; +}; - // If we found something of that name, return the value - if (found->name == name) - return &found->data; +const value* basic_stack::get(hash_t name) const +{ + // Find whatever comes first: a matching entry or a stack frame entry + const entry* found = base::reverse_find(reverse_find_predicat_operator(name)); - // Otherwise, nothing in this stack frame + // If nothing found, no value + if (found == nullptr) return nullptr; - } - value* basic_stack::get(hash_t name) { - // Find whatever comes first: a matching entry or a stack frame entry - entry* found = base::reverse_find(reverse_find_predicat_operator(name)); - // If nothing found, no value - if (found == nullptr) - return nullptr; + // If we found something of that name, return the value + if (found->name == name) + return &found->data; - // If we found something of that name, return the value - if (found->name == name) - return &found->data; + // Otherwise, nothing in this stack frame + return nullptr; +} - // Otherwise, nothing in this stack frame +value* basic_stack::get(hash_t name) +{ + // Find whatever comes first: a matching entry or a stack frame entry + entry* found = base::reverse_find(reverse_find_predicat_operator(name)); + + // If nothing found, no value + if (found == nullptr) return nullptr; + + // If we found something of that name, return the value + if (found->name == name) + return &found->data; + + // Otherwise, nothing in this stack frame + return nullptr; +} + +value* basic_stack::get_from_frame(int ci, hash_t name) +{ + entry* found = base::reverse_find(reverse_find_from_frame_predicat_operator(ci, name)); + if (found == nullptr && ci == -1) { + found = base::reverse_find(reverse_find_from_frame_predicat_operator(0, name)); } - - value* basic_stack::get_from_frame(int ci, hash_t name) { - entry* found = base::reverse_find(reverse_find_from_frame_predicat_operator(ci, name)); - if(found == nullptr && ci == -1) { - found = base::reverse_find(reverse_find_from_frame_predicat_operator(0, name)); - } - if(found == nullptr) { return nullptr; } - if(found->name == name) { return &found->data; } + if (found == nullptr) { return nullptr; } - - template<> - void basic_stack::push_frame(offset_t return_to, bool eval) - { - add(InvalidHash, value{}.set(return_to, eval)); - } - template<> - void basic_stack::push_frame(offset_t return_to, bool eval) - { - add(InvalidHash, value{}.set(return_to, eval)); - } - template<> - void basic_stack::push_frame(offset_t return_to, bool eval) - { - add(InvalidHash, value{}.set(return_to, eval)); + if (found->name == name) { + return &found->data; } + return nullptr; +} - const entry* basic_stack::pop() - { - return &base::pop([](const entry& elem) { return elem.name == ~0; }); - } +template<> +void basic_stack::push_frame(offset_t return_to, bool eval) +{ + add(InvalidHash, value{}.set(return_to, eval)); +} - entry* basic_stack::do_thread_jump_pop(const basic_stack::iterator& jumpStart) - { - // Start an iterator right after the jumping marker (might be a thread_start or a jump_marker) - iterator threadIter = jumpStart; +template<> +void basic_stack::push_frame(offset_t return_to, bool eval) +{ + add(InvalidHash, value{}.set(return_to, eval)); +} - // Get a reference to its jump count - value& start = threadIter.get()->data; - value_type vt = start.type(); - auto jump = start.get(); +template<> +void basic_stack::push_frame(offset_t return_to, bool eval) +{ + add(InvalidHash, value{}.set(return_to, eval)); +} - // Move over it +const entry* basic_stack::pop() +{ + return &base::pop([](const entry& elem) { return elem.name == ~0U; }); +} + +entry* basic_stack::do_thread_jump_pop(const basic_stack::iterator& jumpStart) +{ + // Start an iterator right after the jumping marker (might be a thread_start or a jump_marker) + iterator threadIter = jumpStart; + + // Get a reference to its jump count + value& start = threadIter.get()->data; + value_type vt = start.type(); + auto jump = start.get(); + + // Move over it + threadIter.next(); + + // Move back over the current jump value + for (uint32_t i = 0; i < jump.thread_id; i++) threadIter.next(); - // Move back over the current jump value - for (uint32_t i = 0; i < jump.thread_id; i++) - threadIter.next(); - - // Now keep iterating back until we get to a frame marker - // FIXME: meta types or subtypes? - while (!threadIter.done() && (threadIter.get()->name != InvalidHash - || threadIter.get()->data.type() == value_type::thread_start - || threadIter.get()->data.type() == value_type::thread_end)) - { - // If we've hit an end of thread marker - auto e = threadIter.get(); - if (e->data.type() == value_type::thread_end) - { - // We basically want to skip until we get to the start of this thread (leave the block alone) - thread_t tid = e->data.get(); - while (threadIter.get()->data.type() != value_type::thread_start - || threadIter.get()->data.get().jump != tid) - { - jump.thread_id++; - threadIter.next(); - } - - // Now let us skip over the thread start + // Now keep iterating back until we get to a frame marker + // FIXME: meta types or subtypes? + while (! threadIter.done() + && (threadIter.get()->name != InvalidHash + || threadIter.get()->data.type() == value_type::thread_start + || threadIter.get()->data.type() == value_type::thread_end)) { + // If we've hit an end of thread marker + auto e = threadIter.get(); + if (e->data.type() == value_type::thread_end) { + // We basically want to skip until we get to the start of this thread (leave the block alone) + thread_t tid = e->data.get(); + while (threadIter.get()->data.type() != value_type::thread_start + || threadIter.get()->data.get().jump != tid) { + jump.thread_id++; + threadIter.next(); } - threadIter.next(); - jump.thread_id++; + // Now let us skip over the thread start } - // Move us over the frame marker + threadIter.next(); jump.thread_id++; + } - // Now that thread marker is set to the correct jump value. - if (vt == value_type::jump_marker) { - start.set(jump); - } else if (vt == value_type::thread_start) { - start.set(jump); - } else { - inkFail("unknown jump type"); - } - return threadIter.get(); + // Move us over the frame marker + jump.thread_id++; + + // Now that thread marker is set to the correct jump value. + if (vt == value_type::jump_marker) { + start.set(jump); + } else if (vt == value_type::thread_start) { + start.set(jump); + } else { + inkFail("unknown jump type"); } + return threadIter.get(); +} - frame_type get_frame_type(value_type type) - { - switch (type) - { - case value_type::tunnel_frame: - return frame_type::tunnel; - case value_type::function_frame: - return frame_type::function; - case value_type::thread_frame: - return frame_type::thread; - default: - inkAssert(false, "Unknown frame type detected"); - return (frame_type)-1; - } +frame_type get_frame_type(value_type type) +{ + switch (type) { + case value_type::tunnel_frame: return frame_type::tunnel; + case value_type::function_frame: return frame_type::function; + case value_type::thread_frame: return frame_type::thread; + default: inkAssert(false, "Unknown frame type detected"); return ( frame_type ) -1; } +} - offset_t basic_stack::pop_frame(frame_type* type, bool& eval) - { - inkAssert(!base::is_empty(), "Can not pop frame from empty callstack."); - - const entry* returnedFrame = nullptr; - auto isNull = [](const entry& e) { return e.name == ~0; }; - - // Start iterating backwards - iterator iter = base::begin(); - if(isNull(*iter.get())) { iter.next(isNull); } - while (!iter.done()) - { - // Keep popping if it's not a frame marker or thread marker of some kind - entry* frame = iter.get(); - if (frame->name != InvalidHash) - { - pop(); - iter = base::begin(); - if(isNull(*iter.get())) { iter.next(isNull); } - continue; +offset_t basic_stack::pop_frame(frame_type* type, bool& eval) +{ + inkAssert(! base::is_empty(), "Can not pop frame from empty callstack."); + + const entry* returnedFrame = nullptr; + auto isNull = [](const entry& e) { + return e.name == ~0U; + }; + + // Start iterating backwards + iterator iter = base::begin(); + if (isNull(*iter.get())) { + iter.next(isNull); + } + while (! iter.done()) { + // Keep popping if it's not a frame marker or thread marker of some kind + entry* frame = iter.get(); + if (frame->name != InvalidHash) { + pop(); + iter = base::begin(); + if (isNull(*iter.get())) { + iter.next(isNull); } + continue; + } - // We now have a frame marker. Check if it's a thread - // Thread handling - if ( + // We now have a frame marker. Check if it's a thread + // Thread handling + if ( // FIXME: is_tghead_marker, is_jump_marker frame->data.type() == value_type::thread_start || frame->data.type() == value_type::thread_end || frame->data.type() == value_type::jump_marker ) { - // End of thread marker, we need to create a jump marker - if (frame->data.type() == value_type::thread_end) - { - // Push a new jump marker after the thread end - push({ InvalidHash, value{}.set(0u,0u) }); - - // Do a pop back - returnedFrame = do_thread_jump_pop(base::begin()); - break; - } - - // If this is a jump marker, we actually want to extend it to the next frame - if (frame->data.type() == value_type::jump_marker) - { - // Use the thread jump pop method using this jump marker - returnedFrame = do_thread_jump_pop(iter); - break; - } - - // Popping past thread start - if (frame->data.type() == value_type::thread_start) - { - returnedFrame = do_thread_jump_pop(iter); - break; - } - } - - // Otherwise, pop the frame marker off and return it - returnedFrame = pop(); - break; - } - - // If we didn't find a frame entry, we never had a frame to return from - inkAssert(returnedFrame, "Attempting to pop_frame when no frames exist! Stack reset."); - - // Make sure we're not somehow trying to "return" from a thread - inkAssert(returnedFrame->data.type() != value_type::thread_start - && returnedFrame->data.type() != value_type::thread_end, - "Can not return from a thread! How did this happen?"); - - // Store frame type - if (type != nullptr) - { - *type = get_frame_type(returnedFrame->data.type()); - } - - // Return the offset stored in the frame record - // FIXME: correct type? - const auto& frame = returnedFrame->data.get(); - eval = frame.eval; - return frame.addr; - } - - bool basic_stack::has_frame(frame_type* returnType) const - { - // Empty case - if (base::is_empty()) - return false; - - uint32_t jumping = 0; - uint32_t thread = ~0U; - // Search in reverse for a stack frame - const entry* frame = base::reverse_find([&jumping, &thread](const entry& elem) { - // If we're jumping over data, just keep returning false until we're done - if (jumping > 0) { - jumping--; - return false; - } - - // We only care about elements with InvalidHash - if (elem.name != InvalidHash) - return false; - - // If we're skipping over a thread, wait until we hit its start before checking - if (thread != ~0U) { - if (elem.data.type() == value_type::thread_start && elem.data.get().jump == thread) - thread = ~0U; - - return false; + // End of thread marker, we need to create a jump marker + if (frame->data.type() == value_type::thread_end) { + // Push a new jump marker after the thread end + push({InvalidHash, value{}.set(0u, 0u)}); + + // Do a pop back + returnedFrame = do_thread_jump_pop(base::begin()); + break; } - // If it's a jump marker or a thread start - if (elem.data.type() == value_type::jump_marker || elem.data.type() == value_type::thread_start) { - jumping = elem.data.get().thread_id; - return false; + // If this is a jump marker, we actually want to extend it to the next frame + if (frame->data.type() == value_type::jump_marker) { + // Use the thread jump pop method using this jump marker + returnedFrame = do_thread_jump_pop(iter); + break; } - // If it's a thread end, we need to skip to the matching thread start - if (elem.data.type() == value_type::thread_end) { - thread = elem.data.get(); - return false; + // Popping past thread start + if (frame->data.type() == value_type::thread_start) { + returnedFrame = do_thread_jump_pop(iter); + break; } + } - return elem.name == InvalidHash; - }); - - if (frame != nullptr && returnType != nullptr) - *returnType = get_frame_type(frame->data.type()); - - // Return true if a frame was found - return frame != nullptr; + // Otherwise, pop the frame marker off and return it + returnedFrame = pop(); + break; } - void basic_stack::clear() - { - base::clear(); - } + // If we didn't find a frame entry, we never had a frame to return from + inkAssert(returnedFrame, "Attempting to pop_frame when no frames exist! Stack reset."); - void basic_stack::mark_used(string_table& strings, list_table& lists) const - { - // Mark all strings - base::for_each_all( - [&strings, &lists](const entry& elem) { - if (elem.data.type() == value_type::string) { - strings.mark_used(elem.data.get()); - } else if (elem.data.type() == value_type::list) { - lists.mark_used(elem.data.get()); - } - }); - } - - thread_t basic_stack::fork_thread() - { - // TODO create unique thread ID - thread_t new_thread = _next_thread++; + // Make sure we're not somehow trying to "return" from a thread + inkAssert( + returnedFrame->data.type() != value_type::thread_start + && returnedFrame->data.type() != value_type::thread_end, + "Can not return from a thread! How did this happen?" + ); - // Push a thread start marker here - add(InvalidHash, value{}.set(new_thread, 0u)); + // Store frame type + if (type != nullptr) { + *type = get_frame_type(returnedFrame->data.type()); + } - // Set stack jump counter for thread to 0. This number is used if the thread ever - // tries to pop past its origin. It keeps track of how much of the preceeding stack it's popped back + // Return the offset stored in the frame record + // FIXME: correct type? + const auto& frame = returnedFrame->data.get(); + eval = frame.eval; + return frame.addr; +} - return new_thread; - } +bool basic_stack::has_frame(frame_type* returnType) const +{ + // Empty case + if (base::is_empty()) + return false; + + uint32_t jumping = 0; + uint32_t thread = ~0U; + // Search in reverse for a stack frame + const entry* frame = base::reverse_find([&jumping, &thread](const entry& elem) { + // If we're jumping over data, just keep returning false until we're done + if (jumping > 0) { + jumping--; + return false; + } + + // We only care about elements with InvalidHash + if (elem.name != InvalidHash) + return false; + + // If we're skipping over a thread, wait until we hit its start before checking + if (thread != ~0U) { + if (elem.data.type() == value_type::thread_start + && elem.data.get().jump == thread) + thread = ~0U; + + return false; + } + + // If it's a jump marker or a thread start + if (elem.data.type() == value_type::jump_marker + || elem.data.type() == value_type::thread_start) { + jumping = elem.data.get().thread_id; + return false; + } + + // If it's a thread end, we need to skip to the matching thread start + if (elem.data.type() == value_type::thread_end) { + thread = elem.data.get(); + return false; + } + + return elem.name == InvalidHash; + }); + + if (frame != nullptr && returnType != nullptr) + *returnType = get_frame_type(frame->data.type()); + + // Return true if a frame was found + return frame != nullptr; +} - void basic_stack::complete_thread(thread_t thread) - { - // Add a thread complete marker - add(InvalidHash, value{}.set(thread)); - } +void basic_stack::clear() { base::clear(); } - void basic_stack::collapse_to_thread(thread_t thread) - { - // Reset thread counter - _next_thread = 0; - - // If we're restoring a specific thread (and not the main thread) - if (thread != ~0) - { - // Keep popping until we find the requested thread's end marker - const entry* top = pop(); - while (!( - top->data.type() == value_type::thread_end && - top->data.get() == thread)) - { - inkAssert(!is_empty(), "Ran out of stack while searching for end of thread marker. Did you call complete_thread?"); - top = pop(); - } +void basic_stack::mark_used(string_table& strings, list_table& lists) const +{ + // Mark all strings + base::for_each_all([&strings, &lists](const entry& elem) { + if (elem.data.type() == value_type::string) { + strings.mark_used(elem.data.get()); + } else if (elem.data.type() == value_type::list) { + lists.mark_used(elem.data.get()); } + }); +} - // Now, start iterating backwards - thread_t nulling = ~0U; - uint32_t jumping = 0; - base::reverse_for_each([&nulling, &jumping](entry& elem) { - if (jumping > 0) { - // delete data - elem.name = NulledHashId; - - // Move on - jumping--; - return; - } +thread_t basic_stack::fork_thread() +{ + // TODO create unique thread ID + thread_t new_thread = _next_thread++; - // Thread end. We just need to delete this whole block - if (nulling == ~0 && elem.data.type() == value_type::thread_end && elem.name == InvalidHash) { - nulling = elem.data.get(); - } + // Push a thread start marker here + add(InvalidHash, value{}.set(new_thread, 0u)); - // If we're deleting a useless thread block - if (nulling != ~0U) { - // If this is the start of the block, stop deleting - if (elem.name == InvalidHash && elem.data.type() == value_type::thread_start && elem.data.get().jump == nulling) { - nulling = ~0U; - } + // Set stack jump counter for thread to 0. This number is used if the thread ever + // tries to pop past its origin. It keeps track of how much of the preceeding stack it's popped + // back - // delete data - elem.name = NulledHashId; - } - else - { - // Clear thread start markers. We don't need or want them anymore - if (elem.name == InvalidHash && - (elem.data.type() == value_type::thread_start || elem.data.type() == value_type::jump_marker)) { - // Clear it out - elem.name = NulledHashId; - - // Check if this is a jump, if so we need to ignore even more data - jumping = elem.data.get().thread_id; - } - - // Clear thread frame markers. We can't use them anymore - if (elem.name == InvalidHash && elem.data.type() == value_type::thread_frame) { - elem.name = NulledHashId; - } - } + return new_thread; +} - }, [](entry& elem) { return elem.name == NulledHashId; }); +void basic_stack::complete_thread(thread_t thread) +{ + // Add a thread complete marker + add(InvalidHash, value{}.set(thread)); +} - // No more threads. Clear next thread counter - _next_thread = 0; +void basic_stack::collapse_to_thread(thread_t thread) +{ + // Reset thread counter + _next_thread = 0; + + // If we're restoring a specific thread (and not the main thread) + if (thread != ~0U) { + // Keep popping until we find the requested thread's end marker + const entry* top = pop(); + while ( + ! (top->data.type() == value_type::thread_end + && top->data.get() == thread) + ) { + inkAssert( + ! is_empty(), + "Ran out of stack while searching for end of thread marker. Did you call complete_thread?" + ); + top = pop(); + } } - void basic_stack::save() - { - base::save(); + // Now, start iterating backwards + thread_t nulling = ~0U; + uint32_t jumping = 0; + base::reverse_for_each( + [&nulling, &jumping](entry& elem) { + if (jumping > 0) { + // delete data + elem.name = NulledHashId; + + // Move on + jumping--; + return; + } + + // Thread end. We just need to delete this whole block + if (nulling == ~0U && elem.data.type() == value_type::thread_end + && elem.name == InvalidHash) { + nulling = elem.data.get(); + } + + // If we're deleting a useless thread block + if (nulling != ~0U) { + // If this is the start of the block, stop deleting + if (elem.name == InvalidHash && elem.data.type() == value_type::thread_start + && elem.data.get().jump == nulling) { + nulling = ~0U; + } + + // delete data + elem.name = NulledHashId; + } else { + // Clear thread start markers. We don't need or want them anymore + if (elem.name == InvalidHash + && (elem.data.type() == value_type::thread_start + || elem.data.type() == value_type::jump_marker)) { + // Clear it out + elem.name = NulledHashId; + + // Check if this is a jump, if so we need to ignore even more data + jumping = elem.data.get().thread_id; + } + + // Clear thread frame markers. We can't use them anymore + if (elem.name == InvalidHash && elem.data.type() == value_type::thread_frame) { + elem.name = NulledHashId; + } + } + }, + [](entry& elem) { return elem.name == NulledHashId; } + ); + + // No more threads. Clear next thread counter + _next_thread = 0; +} - // Save thread counter - _backup_next_thread = _next_thread; - } +void basic_stack::save() +{ + base::save(); - void basic_stack::restore() - { - base::restore(); + // Save thread counter + _backup_next_thread = _next_thread; +} - // Restore thread counter - _next_thread = _backup_next_thread; - } +void basic_stack::restore() +{ + base::restore(); - void basic_stack::forget() - { - base::forget([](entry& elem) { elem.name = ~0U; }); - } + // Restore thread counter + _next_thread = _backup_next_thread; +} - entry& basic_stack::add(hash_t name, const value& val) - { - return base::push({ name, val }); - } +void basic_stack::forget() +{ + base::forget([](entry& elem) { elem.name = ~0U; }); +} - basic_eval_stack::basic_eval_stack(value* data, size_t size) - : base(data, size) - { +entry& basic_stack::add(hash_t name, const value& val) { return base::push({name, val}); } - } +basic_eval_stack::basic_eval_stack(value* data, size_t size) + : base(data, size) +{ +} - void basic_eval_stack::push(const value& val) - { - base::push(val); - } +void basic_eval_stack::push(const value& val) { base::push(val); } - value basic_eval_stack::pop() - { - return base::pop([](const value& v) { return v.type() == value_type::none; }); - } +value basic_eval_stack::pop() +{ + return base::pop([](const value& v) { return v.type() == value_type::none; }); +} - const value& basic_eval_stack::top() const - { - return base::top([](const value&){ return false; }); - } +const value& basic_eval_stack::top() const +{ + return base::top([](const value&) { return false; }); +} - const value& basic_eval_stack::top_value() const - { - return base::top([](const value& v){ return v.type() == value_type::none; }); - } +const value& basic_eval_stack::top_value() const +{ + return base::top([](const value& v) { return v.type() == value_type::none; }); +} - bool basic_eval_stack::is_empty() const - { - return base::is_empty(); - } +bool basic_eval_stack::is_empty() const { return base::is_empty(); } - void basic_eval_stack::clear() - { - base::clear(); - } +void basic_eval_stack::clear() { base::clear(); } - void basic_eval_stack::mark_used(string_table& strings, list_table& lists) const - { - // Iterate everything (including what we have saved) and mark strings - base::for_each_all([&strings,&lists](const value& elem) { - if (elem.type() == value_type::string) { - string_type str = elem.get(); - if (str.allocated) { - strings.mark_used(str.str); - } - } else if (elem.type() == value_type::list) { - lists.mark_used(elem.get()); - } - }); - } +void basic_eval_stack::mark_used(string_table& strings, list_table& lists) const +{ + // Iterate everything (including what we have saved) and mark strings + base::for_each_all([&strings, &lists](const value& elem) { + if (elem.type() == value_type::string) { + string_type str = elem.get(); + if (str.allocated) { + strings.mark_used(str.str); + } + } else if (elem.type() == value_type::list) { + lists.mark_used(elem.get()); + } + }); +} - void basic_eval_stack::save() - { - base::save(); - } +void basic_eval_stack::save() { base::save(); } - void basic_eval_stack::restore() - { - base::restore(); - } +void basic_eval_stack::restore() { base::restore(); } - void basic_eval_stack::forget() - { - // Clear out - value x; x.set(); - value none = value(x); - base::forget([&none](value& elem) { elem = none; }); - } +void basic_eval_stack::forget() +{ + // Clear out + value x; + x.set(); + value none = value(x); + base::forget([&none](value& elem) { elem = none; }); +} - void basic_stack::fetch_values(basic_stack& stack) { - auto itr = base::begin(); - auto predicat = [](entry& e) - { return !(e.name == InvalidHash || e.data.type() == value_type::value_pointer); }; - - if(!itr.done() && predicat(*itr.get())) { itr.next(predicat); } - for(; !itr.done() && itr.get()->name != InvalidHash; itr.next(predicat)) { - auto [name, ci] = itr.get()->data.get(); - inkAssert(ci != 0, "Global refs should not exists on ref stack!"); - inkAssert(ci == -1, "only support ci = -1 for now!"); - if(ci == -1) { - set(name, *stack.get(itr.get()->name)); - } - } - } +void basic_stack::fetch_values(basic_stack& stack) +{ + auto itr = base::begin(); + auto predicat = [](entry& e) { + return ! (e.name == InvalidHash || e.data.type() == value_type::value_pointer); + }; - void basic_stack::push_values(basic_stack& stack) { - for(auto itr = base::begin(); - itr.get()->name != InvalidHash && itr.get()->data.type() != value_type::value_pointer; - itr.next()) - { - stack.set(itr.get()->name, itr.get()->data); + if (! itr.done() && predicat(*itr.get())) { + itr.next(predicat); + } + for (; ! itr.done() && itr.get()->name != InvalidHash; itr.next(predicat)) { + auto [name, ci] = itr.get()->data.get(); + inkAssert(ci != 0, "Global refs should not exists on ref stack!"); + inkAssert(ci == -1, "only support ci = -1 for now!"); + if (ci == -1) { + set(name, *stack.get(itr.get()->name)); } } +} - size_t basic_stack::snap(unsigned char* data, const snapper& snapper) const - { - unsigned char* ptr = data; - bool should_write = data != nullptr; - ptr = snap_write(ptr, _next_thread, should_write ); - ptr = snap_write(ptr, _backup_next_thread, should_write ); - ptr += base::snap(data ? ptr : nullptr, snapper); - return static_cast(ptr - data); +void basic_stack::push_values(basic_stack& stack) +{ + for (auto itr = base::begin(); + itr.get()->name != InvalidHash && itr.get()->data.type() != value_type::value_pointer; + itr.next()) { + stack.set(itr.get()->name, itr.get()->data); } +} - const unsigned char* basic_stack::snap_load(const unsigned char* ptr, const loader& loader) - { - ptr = snap_read(ptr, _next_thread); - ptr = snap_read(ptr, _backup_next_thread); - ptr = base::snap_load(ptr, loader); - return ptr; - } +size_t basic_stack::snap(unsigned char* data, const snapper& snapper) const +{ + unsigned char* ptr = data; + bool should_write = data != nullptr; + ptr = snap_write(ptr, _next_thread, should_write); + ptr = snap_write(ptr, _backup_next_thread, should_write); + ptr += base::snap(data ? ptr : nullptr, snapper); + return static_cast(ptr - data); +} + +const unsigned char* basic_stack::snap_load(const unsigned char* ptr, const loader& loader) +{ + ptr = snap_read(ptr, _next_thread); + ptr = snap_read(ptr, _backup_next_thread); + ptr = base::snap_load(ptr, loader); + return ptr; } +} // namespace ink::runtime::internal diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index f072d95a..48288f57 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -227,13 +227,14 @@ globals story_impl::new_globals_from_snapshot(const snapshot& data) { const snapshot_impl& snapshot = reinterpret_cast(data); auto* globs = new globals_impl(this); - auto end = globs->snap_load( - snapshot.get_globals_snap(), - snapshot_interface::loader{ - snapshot.strings(), - _string_table, - } - ); + snapshot.strings().clear(); + auto end = globs->snap_load( + snapshot.get_globals_snap(), + snapshot_interface::loader{ + snapshot.strings(), + _string_table, + } + ); inkAssert(end == snapshot.get_runner_snap(0), "not all data were used for global reconstruction"); return globals(globs, _block); } @@ -347,7 +348,7 @@ void story_impl::setup_pointers() _container_list = ( uint32_t* ) (ptr); while (true) { uint32_t val = *( uint32_t* ) ptr; - if (val == ~0) { + if (val == ~0U) { ptr += sizeof(uint32_t); break; } else { @@ -360,7 +361,7 @@ void story_impl::setup_pointers() _container_hash_start = ( hash_t* ) (ptr); while (true) { uint32_t val = *( uint32_t* ) ptr; - if (val == ~0) { + if (val == ~0U) { _container_hash_end = ( hash_t* ) (ptr); ptr += sizeof(uint32_t); break; diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index 02d0fca3..e8cc1217 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -30,9 +30,9 @@ class story_impl : public story const char* string(uint32_t index) const; - inline const ip_t instructions() const { return _instruction_data; } + inline ip_t instructions() const { return _instruction_data; } - inline const ip_t end() const { return _file + _length; } + inline ip_t end() const { return _file + _length; } inline uint32_t num_containers() const { return _num_containers; } @@ -88,7 +88,7 @@ class story_impl : public story // instruction info ip_t _instruction_data; - // story block used to creat various weak pointers + // story block used to create various weak pointers ref_block* _block; // whether we need to delete our binary data after we destruct diff --git a/inkcpp/string_table.cpp b/inkcpp/string_table.cpp index e54df9c7..2389ec00 100644 --- a/inkcpp/string_table.cpp +++ b/inkcpp/string_table.cpp @@ -135,7 +135,6 @@ const unsigned char* string_table::snap_load(const unsigned char* data, const lo if (len == 2 && str[0] == EMPTY_STRING[0]) { str[0] = 0; } - mark_used(str); } return ptr + 1; } diff --git a/inkcpp/string_utils.h b/inkcpp/string_utils.h index bd8724ba..1ca406df 100644 --- a/inkcpp/string_utils.h +++ b/inkcpp/string_utils.h @@ -29,7 +29,7 @@ inline int toStr(char* buffer, size_t size, uint32_t value) return EINVAL; } int res = snprintf(buffer, size, "%d", value); - if (res > 0 && res < size) { + if (res > 0 && static_cast(res) < size) { return 0; } return EINVAL; @@ -47,7 +47,7 @@ inline int toStr(char* buffer, size_t size, int32_t value) return EINVAL; } int res = snprintf(buffer, size, "%d", value); - if (res > 0 && res < size) { + if (res > 0 && static_cast(res) < size) { return 0; } return EINVAL; @@ -69,7 +69,7 @@ inline int toStr(char* buffer, size_t size, float value) return EINVAL; } int res = snprintf(buffer, size, "%.7f", value); - if (res < 0 || res >= size) { + if (res < 0 || static_cast(res) >= size) { return EINVAL; } // trunc cat zeros B007 diff --git a/inkcpp/value.cpp b/inkcpp/value.cpp index b0d1d06e..6152cab2 100644 --- a/inkcpp/value.cpp +++ b/inkcpp/value.cpp @@ -197,8 +197,9 @@ value::value(const ink::runtime::value& val) case types::String: set(val.get()); break; case types::Float: set(val.get()); break; case types::List: - set(list_table::list{ - static_cast(val.get())->get_lid()}); + set( + list_table::list{static_cast(val.get())->get_lid()} + ); } } @@ -269,9 +270,11 @@ const unsigned char* value::snap_load(const unsigned char* ptr, const loader& lo ptr = snap_read(ptr, &bool_value, max_value_size); if (_type == value_type::string) { if (string_value.allocated) { - string_value.str = loader.string_table[static_cast(reinterpret_cast(string_value.str))]; + string_value.str + = loader.string_table[static_cast(reinterpret_cast(string_value.str))]; } else { - string_value.str = loader.story_string_table + static_cast(reinterpret_cast(string_value.str)); + string_value.str = loader.story_string_table + + static_cast(reinterpret_cast(string_value.str)); } } return ptr; diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index 0809c620..1d9bbdde 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -97,7 +97,7 @@ int main(int argc, const char** argv) // Parse options std::string outputFilename; - bool playMode = false, testMode = false, testDirectory = false, ommit_choice_tags = false; + bool playMode = false, ommit_choice_tags = false; std::string snapshotFile; bool show_statistics = false; const char* inklecateOverwrite = nullptr; @@ -114,11 +114,6 @@ int main(int argc, const char** argv) } } else if (option == "--ommit-choice-tags") { ommit_choice_tags = true; - } else if (option == "-t") { - testMode = true; - } else if (option == "-td") { - testMode = true; - testDirectory = true; } else if (option == "--inklecate") { if (i + 1 < argc - 1 && argv[i + 1][0] != '-') { ++i; @@ -134,26 +129,14 @@ int main(int argc, const char** argv) // Get input filename std::string inputFilename = argv[argc - 1]; - // Test mode - // if (testMode) { - // bool result; - // if (testDirectory) { - // result = test_directory(inputFilename); - // } else { - // result = test(inputFilename); - // } - - // return result ? 0 : -1; - // } - // If output filename not specified, use input filename as guideline if (outputFilename.empty()) { outputFilename = std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".bin"); } // If input filename is an .ink file - size_t val = inputFilename.find(".ink"); - bool json_file_is_tmp_file = false; + size_t val = inputFilename.find(".ink"); + bool json_file_is_tmp_file = false; if (val == inputFilename.length() - 4) { // Create temporary filename std::string jsonFile = std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".tmp"); diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index d53e868d..aec80eea 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -12,7 +12,6 @@ #include #include -#include #ifndef _MSC_VER # include @@ -361,13 +360,13 @@ void binary_emitter::process_paths() token = ink::compiler::internal::strtok_s(nullptr, ".", &_context); } - if (noop_offset != ~0) { + if (noop_offset != ~0U) { inkAssert(! useCountIndex, "Can't count visits to a noop!"); _containers.set(position, noop_offset); } else { // If we want the count index, write that out if (useCountIndex) { - inkAssert(container->counter_index != ~0, "No count index available for this container!"); + inkAssert(container->counter_index != ~0U, "No count index available for this container!"); _containers.set(position, container->counter_index); } else { // Otherwise, write container address @@ -435,11 +434,17 @@ void binary_emitter::set_list_meta(const list_data& list_defs) _lists.write(flag.flag); if (flag.flag.list_id != list_id) { list_id = flag.flag.list_id; - _lists.write(reinterpret_cast(list_names->data()), static_cast(list_names->size())); + _lists.write( + reinterpret_cast(list_names->data()), + static_cast(list_names->size()) + ); ++list_names; _lists.write('\0'); } - _lists.write(reinterpret_cast(flag.name->c_str()), static_cast(flag.name->size()) + 1); + _lists.write( + reinterpret_cast(flag.name->c_str()), + static_cast(flag.name->size()) + 1 + ); } _lists.write(null_flag); } diff --git a/inkcpp_compiler/binary_stream.cpp b/inkcpp_compiler/binary_stream.cpp index f02529bb..b22d5caf 100644 --- a/inkcpp_compiler/binary_stream.cpp +++ b/inkcpp_compiler/binary_stream.cpp @@ -10,157 +10,151 @@ namespace ink { - namespace compiler +namespace compiler +{ + namespace internal { - namespace internal + template<> + size_t binary_stream::write(const std::string& value) { - template<> - size_t binary_stream::write(const std::string& value) - { - constexpr byte_t ZERO = 0; - size_t len; - if(value.length()) { - len = write(reinterpret_cast(value.c_str()), static_cast(value.length())); - } else { - len = write(' '); - } - len += write(ZERO); - return len; + constexpr byte_t ZERO = 0; + size_t len; + if (value.length()) { + len = write( + reinterpret_cast(value.c_str()), static_cast(value.length()) + ); + } else { + len = write(' '); } + len += write(ZERO); + return len; + } - binary_stream::binary_stream() - : _currentSlab(nullptr) - , _ptr(nullptr) - { } + binary_stream::binary_stream() + : _currentSlab(nullptr) + , _ptr(nullptr) + { + } - binary_stream::~binary_stream() - { - reset(); - } + binary_stream::~binary_stream() { reset(); } - size_t binary_stream::write(const byte_t* data, size_t len) - { - // Create first slab if none exist - if (_currentSlab == nullptr) - { - _currentSlab = new byte_t[DATA_SIZE]; - _ptr = _currentSlab; - } - - // Check how much space we have left - size_t slab_remaining = static_cast(_currentSlab + DATA_SIZE - _ptr); - - // If we're out of space... - if (slab_remaining < len) - { - // Write what we can into the slab - memcpy(_ptr, data, slab_remaining); - - // Create a new slab - _slabs.push_back(_currentSlab); - _currentSlab = new byte_t[DATA_SIZE]; - _ptr = _currentSlab; - - // Recurse - return slab_remaining + write(data + slab_remaining, len - slab_remaining); - } - - // We have the space - memcpy(_ptr, data, len); - _ptr += len; - return len; + size_t binary_stream::write(const byte_t* data, size_t len) + { + // Create first slab if none exist + if (_currentSlab == nullptr) { + _currentSlab = new byte_t[DATA_SIZE]; + _ptr = _currentSlab; } - void binary_stream::write_to(std::ostream& out) const - { - // Write previous slabs - for (byte_t* slab : _slabs) - { - out.write(reinterpret_cast(slab), DATA_SIZE); - } - - // Write current slab (if it exists) - if (_currentSlab != nullptr && _ptr != _currentSlab) - { - out.write(reinterpret_cast(_currentSlab), _ptr - _currentSlab); - } + // Check how much space we have left + size_t slab_remaining = static_cast(_currentSlab + DATA_SIZE - _ptr); + + // If we're out of space... + if (slab_remaining < len) { + // Write what we can into the slab + memcpy(_ptr, data, slab_remaining); + + // Create a new slab + _slabs.push_back(_currentSlab); + _currentSlab = new byte_t[DATA_SIZE]; + _ptr = _currentSlab; + + // Recurse + return slab_remaining + write(data + slab_remaining, len - slab_remaining); } - size_t binary_stream::pos() const - { - // If we have no data, we're at position 0 - if (_currentSlab == nullptr) - return 0; + // We have the space + memcpy(_ptr, data, len); + _ptr += len; + return len; + } - // Each slabs is size DATA_SIZE then add the position in the current slab - return static_cast(_slabs.size() * DATA_SIZE + (_ptr - _currentSlab)); + void binary_stream::write_to(std::ostream& out) const + { + // Write previous slabs + for (byte_t* slab : _slabs) { + out.write(reinterpret_cast(slab), DATA_SIZE); } - void binary_stream::set(size_t offset, const byte_t* data, size_t len) - { - // Find slab for offset - unsigned int slab_index = offset / DATA_SIZE; - size_t pos = offset % DATA_SIZE; - - // Get slab and ptr - byte_t* slab = nullptr; - if (slab_index < _slabs.size()) - slab = _slabs[slab_index]; - else if (slab_index == _slabs.size()) - slab = _currentSlab; - - if (slab == nullptr) - return; // TODO: Error? - - byte_t* ptr = slab + pos; - - // Check if data will fit into slab - if (pos + len > DATA_SIZE) - { - // Write only what we can fit - size_t new_length = DATA_SIZE - pos; - memcpy(ptr, data, new_length); - - // Recurse - set(offset + new_length, data + new_length, len - new_length); - return; - } - - // Otherwise write the whole data - memcpy(ptr, data, len); + // Write current slab (if it exists) + if (_currentSlab != nullptr && _ptr != _currentSlab) { + out.write(reinterpret_cast(_currentSlab), _ptr - _currentSlab); } + } - byte_t binary_stream::get(size_t offset) const - { - // Find slab for offset - unsigned int slab_index = offset / DATA_SIZE; - size_t pos = offset % DATA_SIZE; - - // Get slab and ptr - byte_t* slab = nullptr; - if (slab_index < _slabs.size()) - slab = _slabs[slab_index]; - else if (slab_index == _slabs.size()) - slab = _currentSlab; - - inkAssert(slab != nullptr, "try to access invalid slab in binary stream"); - return slab[pos]; + size_t binary_stream::pos() const + { + // If we have no data, we're at position 0 + if (_currentSlab == nullptr) + return 0; + + // Each slabs is size DATA_SIZE then add the position in the current slab + return static_cast(_slabs.size() * DATA_SIZE + (_ptr - _currentSlab)); + } + + void binary_stream::set(size_t offset, const byte_t* data, size_t len) + { + // Find slab for offset + unsigned int slab_index = offset / DATA_SIZE; + size_t pos = offset % DATA_SIZE; + + // Get slab and ptr + byte_t* slab = nullptr; + if (slab_index < _slabs.size()) + slab = _slabs[slab_index]; + else if (slab_index == _slabs.size()) + slab = _currentSlab; + + if (slab == nullptr) + return; // TODO: Error? + + byte_t* ptr = slab + pos; + + // Check if data will fit into slab + if (pos + len > DATA_SIZE) { + // Write only what we can fit + size_t new_length = DATA_SIZE - pos; + memcpy(ptr, data, new_length); + + // Recurse + set(offset + new_length, data + new_length, len - new_length); + return; } - void binary_stream::reset() - { - // Delete all slabs - for (byte_t* slab : _slabs) - { - delete[] slab; - } - _slabs.clear(); - - // Delete active slab (if it exists) - if (_currentSlab != nullptr) - delete[] _currentSlab; - _currentSlab = _ptr = nullptr; + // Otherwise write the whole data + memcpy(ptr, data, len); + } + + byte_t binary_stream::get(size_t offset) const + { + // Find slab for offset + unsigned int slab_index = offset / DATA_SIZE; + size_t pos = offset % DATA_SIZE; + + // Get slab and ptr + byte_t* slab = nullptr; + if (slab_index < _slabs.size()) + slab = _slabs[slab_index]; + else if (slab_index == _slabs.size()) + slab = _currentSlab; + + inkAssert(slab != nullptr, "try to access invalid slab in binary stream"); + return slab[pos]; + } + + void binary_stream::reset() + { + // Delete all slabs + for (byte_t* slab : _slabs) { + delete[] slab; } + _slabs.clear(); + + // Delete active slab (if it exists) + if (_currentSlab != nullptr) + delete[] _currentSlab; + _currentSlab = _ptr = nullptr; } - } -} + } // namespace internal +} // namespace compiler +} // namespace ink diff --git a/inkcpp_compiler/json.hpp b/inkcpp_compiler/json.hpp index b289fecd..4d1a37ad 100644 --- a/inkcpp_compiler/json.hpp +++ b/inkcpp_compiler/json.hpp @@ -1,4 +1,3 @@ -#pragma warning(push, 1) // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.11.2 @@ -24595,4 +24594,3 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC #endif // INCLUDE_NLOHMANN_JSON_HPP_ -#pragma warning(pop) diff --git a/inkcpp_compiler/json_compiler.cpp b/inkcpp_compiler/json_compiler.cpp index 2bbf83c6..e3c8632e 100644 --- a/inkcpp_compiler/json_compiler.cpp +++ b/inkcpp_compiler/json_compiler.cpp @@ -247,7 +247,7 @@ void json_compiler::compile_container( // Write end container marker, End pointer should point to End command (form symetry with START // command) - if (meta.indexToReturn != ~0) + if (meta.indexToReturn != ~0U) _emitter->write(Command::END_CONTAINER_MARKER, meta.indexToReturn, meta.cmd_flags); // Record end position in map @@ -412,7 +412,8 @@ void json_compiler::compile_complex_command(const nlohmann::json& command) else if (get(command, "#", val)) { if (_ink_version > 20) { - throw ink_exception("with inkVerison 21 the tag system chages, and the '#: ' is deprecated now" + throw ink_exception( + "with inkVerison 21 the tag system chages, and the '#: ' is deprecated now" ); } _emitter->write_string(Command::TAG, CommandFlag::NO_FLAGS, val); diff --git a/inkcpp_test/Callstack.cpp b/inkcpp_test/Callstack.cpp index 23edb36f..4dbeef80 100644 --- a/inkcpp_test/Callstack.cpp +++ b/inkcpp_test/Callstack.cpp @@ -12,7 +12,8 @@ const hash_t X = ink::hash_string("X"); const hash_t Y = ink::hash_string("Y"); const hash_t Z = ink::hash_string("Z"); -value operator "" _v(unsigned long long i) { +value operator""_v(unsigned long long i) +{ return value{}.set(static_cast(i)); } @@ -157,10 +158,7 @@ SCENARIO("threading with the callstack", "[callstack]") stack.push_frame(555, false); stack.complete_thread(thread); - THEN("there should be no frames on the stack") - { - REQUIRE(stack.has_frame() == false); - } + THEN("there should be no frames on the stack") { REQUIRE(stack.has_frame() == false); } } } @@ -189,8 +187,8 @@ SCENARIO("threading with the callstack", "[callstack]") WHEN("that thread does a tunnel return") { frame_type type; - auto offset = stack.pop_frame(&type, eval_mode); - + auto offset = stack.pop_frame(&type, eval_mode); + THEN("that thread should be outside the tunnel") { REQUIRE(type == frame_type::tunnel); @@ -228,7 +226,7 @@ SCENARIO("threading with the callstack", "[callstack]") WHEN("we do a tunnel return") { frame_type type; - auto offset = stack.pop_frame(&type, eval_mode); + auto offset = stack.pop_frame(&type, eval_mode); THEN("we should be back outside") { @@ -247,7 +245,7 @@ SCENARIO("threading with the callstack", "[callstack]") { stack.complete_thread(thread); frame_type type; - auto offset = stack.pop_frame(&type, eval_mode); + stack.pop_frame(&type, eval_mode); THEN("we should be outside the tunnel") { @@ -274,7 +272,7 @@ SCENARIO("threading with the callstack", "[callstack]") { // Create the stack auto stack = ink::runtime::internal::stack<50>(); - + // Create the thread thread_t thread = stack.fork_thread(); @@ -365,7 +363,7 @@ SCENARIO("threading with the callstack", "[callstack]") WHEN("we then try to pop both frames") { frame_type _ignore; - bool eval_mode; + bool eval_mode; stack.pop_frame(&_ignore, eval_mode); stack.pop_frame(&_ignore, eval_mode); diff --git a/inkcpp_test/Fixes.cpp b/inkcpp_test/Fixes.cpp index 2fd44385..7c5209c7 100644 --- a/inkcpp_test/Fixes.cpp +++ b/inkcpp_test/Fixes.cpp @@ -1,6 +1,6 @@ #include "catch.hpp" #include "snapshot.h" -#include "system.h" +#include "../snapshot_impl.h" #include #include @@ -144,15 +144,28 @@ SCENARIO( REQUIRE(thread->num_choices() == 1); REQUIRE(thread->get_choice(0)->num_tags() == 1); REQUIRE(thread->get_choice(0)->get_tag(0) == std::string("Type:Idle")); - auto* snapshot = thread->create_snapshot(); + std::unique_ptr snap{thread->create_snapshot()}; THEN("snapshot loaded works") { - runner loaded = ink->new_runner_from_snapshot(*snapshot); + runner loaded = ink->new_runner_from_snapshot(*snap); loaded->getall(); REQUIRE(loaded->num_choices() == 1); REQUIRE(loaded->get_choice(0)->num_tags() == 1); REQUIRE(loaded->get_choice(0)->get_tag(0) == std::string("Type:Idle")); } } + WHEN("loading a snipshot multiple times") + { + thread->getall(); + std::unique_ptr snap{thread->create_snapshot()}; + runner thread = ink->new_runner_from_snapshot(*snap); + const size_t s = reinterpret_cast(snap.get())->strings().size(); + THEN("loading it again will not change the string_table size") + { + runner thread2 = ink->new_runner_from_snapshot(*snap); + const size_t s2 = reinterpret_cast(snap.get())->strings().size(); + REQUIRE(s == s2); + } + } } } diff --git a/inkcpp_test/Observer.cpp b/inkcpp_test/Observer.cpp index 8bb22fda..234bb817 100644 --- a/inkcpp_test/Observer.cpp +++ b/inkcpp_test/Observer.cpp @@ -1,7 +1,7 @@ #include "catch.hpp" #include "system.h" +#include "../runner_impl.h" -#include <../runner_impl.h> #include #include #include @@ -30,19 +30,19 @@ SCENARIO("Observer", "[variables][observer]") int var1_cnt = 0; auto var1 = [&var1_cnt](int32_t i) { if (var1_cnt++ == 0) { - CHECK(i == 1); - } else { - CHECK(i == 5); - } + CHECK(i == 1); + } else { + CHECK(i == 5); + } }; int var2_cnt = 0; auto var2 = [&var2_cnt](const char* s) { std::string str(s); if (var2_cnt++ == 0) { - CHECK(str == "hello"); - } else { - CHECK(str == "test"); - } + CHECK(str == "hello"); + } else { + CHECK(str == "test"); + } }; globals->observe("var1", var1); @@ -88,10 +88,10 @@ SCENARIO("Observer", "[variables][observer]") int var1_cnt = 0; auto var1 = [&var1_cnt](int32_t i) { if (var1_cnt++ < 2) { - CHECK(i == 1); - } else { - CHECK(i == 5); - } + CHECK(i == 1); + } else { + CHECK(i == 5); + } }; globals->observe("var1", var1); globals->observe("var1", var1); @@ -102,7 +102,7 @@ SCENARIO("Observer", "[variables][observer]") } WHEN("Run with missmatching type") { - auto var1 = [](uint32_t i) { + auto var1 = [](uint32_t) { }; CHECK_THROWS_AS(globals->observe("var1", var1), ink::ink_exception); } @@ -123,13 +123,13 @@ SCENARIO("Observer", "[variables][observer]") int var1_cnt = 0; auto var1 = [&var1_cnt](int32_t i, ink::optional o_i) { if (var1_cnt++ == 0) { - CHECK(i == 1); - CHECK_FALSE(o_i.has_value()); - } else { - CHECK(i == 5); - CHECK(o_i.has_value()); - CHECK(o_i.value() == 1); - } + CHECK(i == 1); + CHECK_FALSE(o_i.has_value()); + } else { + CHECK(i == 5); + CHECK(o_i.has_value()); + CHECK(o_i.value() == 1); + } }; int var2_cnt = 0; @@ -162,13 +162,13 @@ SCENARIO("Observer", "[variables][observer]") auto var1 = [&var1_cnt, &globals](int32_t i) { ++var1_cnt; if (var1_cnt == 1) { - CHECK(i == 1); - } else if (var1_cnt == 2) { - CHECK(i == 5); - globals->set("var1", 8); - } else if (var1_cnt == 3) { - CHECK(i == 8); - } + CHECK(i == 1); + } else if (var1_cnt == 2) { + CHECK(i == 5); + globals->set("var1", 8); + } else if (var1_cnt == 3) { + CHECK(i == 8); + } }; globals->observe("var1", var1); std::string out = thread->getall(); @@ -183,13 +183,13 @@ SCENARIO("Observer", "[variables][observer]") auto var1 = [&var1_cnt, &globals](int32_t i) { ++var1_cnt; if (var1_cnt == 1) { - CHECK(i == 1); - globals->set("var1", 8); - } else if (var1_cnt == 2) { - CHECK(i == 8); - } else if (var1_cnt == 3) { - CHECK(i == 5); - } + CHECK(i == 1); + globals->set("var1", 8); + } else if (var1_cnt == 2) { + CHECK(i == 8); + } else if (var1_cnt == 3) { + CHECK(i == 5); + } }; globals->observe("var1", var1); std::string out = thread->getall(); @@ -204,16 +204,16 @@ SCENARIO("Observer", "[variables][observer]") auto var1 = [&var1_cnt, &globals](int32_t i) { ++var1_cnt; if (var1_cnt == 1) { - CHECK(i == 1); - globals->set("var1", 8); - } else if (var1_cnt == 2) { - CHECK(i == 8); - globals->set("var1", 10); - } else if (var1_cnt == 3) { - CHECK(i == 10); - } else if (var1_cnt == 4) { - CHECK(i == 5); - } + CHECK(i == 1); + globals->set("var1", 8); + } else if (var1_cnt == 2) { + CHECK(i == 8); + globals->set("var1", 10); + } else if (var1_cnt == 3) { + CHECK(i == 10); + } else if (var1_cnt == 4) { + CHECK(i == 5); + } }; globals->observe("var1", var1); std::string out = thread->getall(); @@ -227,11 +227,11 @@ SCENARIO("Observer", "[variables][observer]") int var1_cnt = 0; auto var1 = [&var1_cnt, &globals](int32_t i) { if (var1_cnt++ == 0) { - CHECK(i == 1); - } else { - CHECK(i == 5); - globals->set("var2", "didum"); - } + CHECK(i == 1); + } else { + CHECK(i == 5); + globals->set("var2", "didum"); + } }; int var2_cnt = 0; auto var2 = [&var2_cnt]() { diff --git a/inkcpp_test/Restorable.cpp b/inkcpp_test/Restorable.cpp index 2da96f97..5cb43c6c 100644 --- a/inkcpp_test/Restorable.cpp +++ b/inkcpp_test/Restorable.cpp @@ -10,11 +10,13 @@ SCENARIO("a restorable collection can operate like a stack", "[restorable]") { // Create the collection constexpr size_t size = 128; - int buffer[size]; - auto collection = restorable(buffer, size); + int buffer[size]; + auto collection = restorable(buffer, size); // Lambdas - auto isNull = [](const int&) { return false; }; + auto isNull = [](const int&) { + return false; + }; THEN("it should have zero size") REQUIRE(collection.size(isNull) == 0); @@ -56,29 +58,37 @@ void VerifyStack(restorable& stack, const std::vector& expected) { THEN("it should match the expected array in both directions") { - auto isNull = [](const int& e) { return e == -1; }; + auto isNull = [](const int& e) { + return e == -1; + }; // Check REQUIRE(stack.size(isNull) == expected.size()); // Iterate and make sure each element matches int counter = 0; - stack.for_each([&counter, expected](const int& elem) { - REQUIRE(counter < expected.size()); - REQUIRE(counter >= 0); - REQUIRE(expected[counter] == elem); - counter++; - }, isNull); + stack.for_each( + [&counter, expected](const int& elem) { + REQUIRE(counter < static_cast(expected.size())); + REQUIRE(counter >= 0); + REQUIRE(expected[counter] == elem); + counter++; + }, + isNull + ); // Make sure we hit every element in the expected vector - REQUIRE(counter == expected.size()); + REQUIRE(counter == static_cast(expected.size())); // Try the other direction - stack.reverse_for_each([&counter, expected](const int& elem) { - counter--; - REQUIRE(counter >= 0); - REQUIRE(expected[counter] == elem); - }, isNull); + stack.reverse_for_each( + [&counter, expected](const int& elem) { + counter--; + REQUIRE(counter >= 0); + REQUIRE(expected[counter] == elem); + }, + isNull + ); REQUIRE(counter == 0); } } @@ -96,7 +106,10 @@ void RestoreAndVerifyStack(restorable& stack, const std::vector -void ForgetAndVerifyStack(restorable& stack, const std::vector& expected, NullifyCallback nullify) +void ForgetAndVerifyStack( + restorable& stack, const std::vector& expected, + NullifyCallback nullify +) { WHEN("the save state is forgotten") { @@ -111,19 +124,19 @@ SCENARIO("a collection can be restored no matter how many times you push or pop" { // Create the collection constexpr size_t size = 128; - int buffer[size]; - auto collection = restorable(buffer, size); + int buffer[size]; + auto collection = restorable(buffer, size); // Lambdas (we'll use negative one for null) - auto isNull = [](const int& elem) { return elem == -1; }; - auto nullify = [](int& elem) { elem = -1; }; + auto isNull = [](const int& elem) { + return elem == -1; + }; GIVEN("a stack with five items that has been saved") { // Create five elements std::vector expected; - for (int i = 0; i < 5; i++) - { + for (int i = 0; i < 5; i++) { collection.push(i); expected.push_back(i); } @@ -140,7 +153,7 @@ SCENARIO("a collection can be restored no matter how many times you push or pop" WHEN("elements are popped") { - collection.pop(isNull); + collection.pop(isNull); collection.pop(isNull); RestoreAndVerifyStack(collection, expected); } @@ -165,8 +178,9 @@ SCENARIO("a collection can be restored no matter how many times you push or pop" THEN("More are pushed") { - collection.push(100); collection.push(200); - VerifyStack(collection, { 100, 200 }); + collection.push(100); + collection.push(200); + VerifyStack(collection, {100, 200}); } } } @@ -176,19 +190,19 @@ SCENARIO("saving does not disrupt iteration", "[restorable]") { // Create the collection constexpr size_t size = 128; - int buffer[size]; - auto collection = restorable(buffer, size); + int buffer[size]; + auto collection = restorable(buffer, size); // Lambdas (we'll use negative one for null) - auto isNull = [](const int& elem) { return elem == -1; }; - auto nullify = [](int& elem) { elem = -1; }; + auto isNull = [](const int& elem) { + return elem == -1; + }; GIVEN("a stack with five items that has been saved") { // Create five elements std::vector expected; - for (int i = 0; i < 5; i++) - { + for (int i = 0; i < 5; i++) { collection.push(i); expected.push_back(i); } @@ -198,24 +212,32 @@ SCENARIO("saving does not disrupt iteration", "[restorable]") WHEN("elements are pushed") { - collection.push(10); expected.push_back(10); - collection.push(20); expected.push_back(20); + collection.push(10); + expected.push_back(10); + collection.push(20); + expected.push_back(20); VerifyStack(collection, expected); } WHEN("elements are popped") { - collection.pop(isNull); expected.pop_back(); - collection.pop(isNull); expected.pop_back(); + collection.pop(isNull); + expected.pop_back(); + collection.pop(isNull); + expected.pop_back(); VerifyStack(collection, expected); } WHEN("elements are popped and pushed") { - collection.pop(isNull); expected.pop_back(); - collection.pop(isNull); expected.pop_back(); - collection.push(10); expected.push_back(10); - collection.push(20); expected.push_back(20); + collection.pop(isNull); + expected.pop_back(); + collection.pop(isNull); + expected.pop_back(); + collection.push(10); + expected.push_back(10); + collection.push(20); + expected.push_back(20); VerifyStack(collection, expected); } } @@ -227,17 +249,23 @@ SCENARIO("saving does not disrupt iteration", "[restorable]") WHEN("elements are pushed") { - collection.push(10); expected.push_back(10); - collection.push(20); expected.push_back(20); + collection.push(10); + expected.push_back(10); + collection.push(20); + expected.push_back(20); VerifyStack(collection, expected); } WHEN("elements are pushed then popped") { - collection.push(10); expected.push_back(10); - collection.push(20); expected.push_back(20); - collection.pop(isNull); expected.pop_back(); - collection.pop(isNull); expected.pop_back(); + collection.push(10); + expected.push_back(10); + collection.push(20); + expected.push_back(20); + collection.pop(isNull); + expected.pop_back(); + collection.pop(isNull); + expected.pop_back(); VerifyStack(collection, expected); } } @@ -247,19 +275,22 @@ SCENARIO("save points can be forgotten", "[restorable]") { // Create the collection constexpr size_t size = 128; - int buffer[size]; - auto collection = restorable(buffer, size); + int buffer[size]; + auto collection = restorable(buffer, size); // Lambdas (we'll use negative one for null) - auto isNull = [](const int& elem) { return elem == -1; }; - auto nullify = [](int& elem) { elem = -1; }; + auto isNull = [](const int& elem) { + return elem == -1; + }; + auto nullify = [](int& elem) { + elem = -1; + }; GIVEN("a stack with five items that has been saved") { // Create five elements std::vector expected; - for (int i = 0; i < 5; i++) - { + for (int i = 0; i < 5; i++) { collection.push(i); expected.push_back(i); } @@ -269,25 +300,34 @@ SCENARIO("save points can be forgotten", "[restorable]") WHEN("elements are pushed") { - collection.push(10); expected.push_back(10); - collection.push(20); expected.push_back(20); + collection.push(10); + expected.push_back(10); + collection.push(20); + expected.push_back(20); ForgetAndVerifyStack(collection, expected, nullify); } WHEN("elements are popped") { - collection.pop(isNull); expected.pop_back(); - collection.pop(isNull); expected.pop_back(); + collection.pop(isNull); + expected.pop_back(); + collection.pop(isNull); + expected.pop_back(); ForgetAndVerifyStack(collection, expected, nullify); } WHEN("elements are popped and pushed") { - collection.pop(isNull); expected.pop_back(); - collection.pop(isNull); expected.pop_back(); - collection.push(10); expected.push_back(10); - collection.push(20); expected.push_back(20); - collection.push(30); expected.push_back(30); + collection.pop(isNull); + expected.pop_back(); + collection.pop(isNull); + expected.pop_back(); + collection.push(10); + expected.push_back(10); + collection.push(20); + expected.push_back(20); + collection.push(30); + expected.push_back(30); ForgetAndVerifyStack(collection, expected, nullify); } } diff --git a/shared/private/header.h b/shared/private/header.h index aac8a541..81e151d1 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -8,39 +8,43 @@ #include "system.h" -namespace ink::internal { +namespace ink::internal +{ - struct header { - static header parse_header(const char* data); +struct header { + static header parse_header(const char* data); - template - static T swap_bytes(const T& value) { - char data[sizeof(T)]; - for (int i = 0; i < sizeof(T); ++i) { - data[i] = reinterpret_cast(&value)[sizeof(T)-1-i]; - } - return *reinterpret_cast(data); - } - list_flag read_list_flag(const char*& ptr) const { - list_flag result = *reinterpret_cast(ptr); - ptr += sizeof(list_flag); - if (endien == ink::internal::header::endian_types::differ) { - result.flag = swap_bytes(result.flag); - result.list_id = swap_bytes(result.list_id); - } - return result; - } + template + static T swap_bytes(const T& value) + { + char data[sizeof(T)]; + for (size_t i = 0; i < sizeof(T); ++i) { + data[i] = reinterpret_cast(&value)[sizeof(T) - 1 - i]; + } + return *reinterpret_cast(data); + } - enum class endian_types: uint16_t { - none = 0, - same = 0x0001, - differ = 0x0100 - } endien = endian_types::none; - uint32_t ink_version_number = 0; - uint32_t ink_bin_version_number = 0; - static constexpr size_t Size = ///< actual data size of Header, - /// because padding of struct may - /// differ between platforms - sizeof(uint16_t) + 2 * sizeof(uint32_t); - }; -} + list_flag read_list_flag(const char*& ptr) const + { + list_flag result = *reinterpret_cast(ptr); + ptr += sizeof(list_flag); + if (endien == ink::internal::header::endian_types::differ) { + result.flag = swap_bytes(result.flag); + result.list_id = swap_bytes(result.list_id); + } + return result; + } + + enum class endian_types : uint16_t { + none = 0, + same = 0x0001, + differ = 0x0100 + } endien = endian_types::none; + uint32_t ink_version_number = 0; + uint32_t ink_bin_version_number = 0; + static constexpr size_t Size = ///< actual data size of Header, + /// because padding of struct may + /// differ between platforms + sizeof(uint16_t) + 2 * sizeof(uint32_t); +}; +} // namespace ink::internal diff --git a/shared/public/system.h b/shared/public/system.h index 2ec2c914..7b996272 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -97,8 +97,14 @@ constexpr list_flag empty_flag{-1, 0}; namespace internal { -#pragma warning(push) -#pragma warning(disable : 4514, justification : "functions are defined in header file, they do not need to be used.") +#ifdef __GNUC__ +#else +# pragma warning(push) +# pragma warning( \ + disable : 4514, \ + justification : "functions are defined in header file, they do not need to be used." \ + ) +#endif /** Checks if a string starts with a given prefix*/ static inline constexpr bool starts_with(const char* string, const char* prefix) { @@ -122,6 +128,7 @@ namespace internal case '\n': if (! includeNewline) return false; + [[fallthrough]]; case '\t': [[fallthrough]]; case ' ': continue; default: return false; @@ -132,7 +139,10 @@ namespace internal /** check if character can be only part of a word, when two part of word characters put together * the will be a space inserted I049 */ - static inline bool is_part_of_word(char character) { return isalpha(character) || isdigit(character); } + static inline bool is_part_of_word(char character) + { + return isalpha(character) || isdigit(character); + } static inline constexpr bool is_whitespace(char character, bool includeNewline = true) { @@ -150,6 +160,10 @@ namespace internal /** populate memory with Zero */ void zero_memory(void* buffer, size_t length); #endif +#ifdef __GNUC__ +#else +# pragma warning(pop) +#endif } // namespace internal #ifdef INK_ENABLE_STL From 01e57276dfec3db93a397c03ad9e7669c54cdcf5 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 28 Nov 2025 16:11:33 +0100 Subject: [PATCH 07/24] WIP + globals variabe migration + globals visit count migration + runner temp_var migration: store current_knot_id as hash + runner jump to current knot + runner load new static global/knot tags, discard old ones --- .cmake-format.json | 5 + inkcpp/array.h | 25 ++- inkcpp/collections/restorable.h | 2 + inkcpp/globals_impl.cpp | 74 +++++--- inkcpp/globals_impl.h | 15 +- inkcpp/include/runner.h | 2 +- inkcpp/include/snapshot.h | 10 +- inkcpp/include/story.h | 6 + inkcpp/list_table.h | 8 +- inkcpp/output.cpp | 12 ++ inkcpp/output.h | 1 + inkcpp/runner_impl.cpp | 172 ++++++++++++++++--- inkcpp/runner_impl.h | 10 +- inkcpp/simple_restorable_stack.h | 9 + inkcpp/snapshot_impl.cpp | 15 +- inkcpp/snapshot_impl.h | 18 +- inkcpp/snapshot_interface.h | 13 +- inkcpp/stack.cpp | 38 ++++- inkcpp/stack.h | 6 + inkcpp/story_impl.cpp | 46 +++-- inkcpp/story_impl.h | 3 + inkcpp/string_table.h | 2 + inkcpp/system.cpp | 53 +++--- inkcpp/value.cpp | 8 + inkcpp/value.h | 6 +- inkcpp_cl/inkcpp_cl.cpp | 9 + inkcpp_compiler/binary_emitter.cpp | 4 + inkcpp_test/CMakeLists.txt | 89 ++++++---- inkcpp_test/Migration.cpp | 187 +++++++++++++++++++++ inkcpp_test/ink/MigrationBase.ink | 26 +++ inkcpp_test/ink/MigrationChangeGlobals.ink | 18 ++ inkcpp_test/ink/MigrationChangeNodes.ink | 18 ++ shared/public/system.h | 20 ++- 33 files changed, 786 insertions(+), 144 deletions(-) create mode 100644 .cmake-format.json create mode 100644 inkcpp_test/Migration.cpp create mode 100644 inkcpp_test/ink/MigrationBase.ink create mode 100644 inkcpp_test/ink/MigrationChangeGlobals.ink create mode 100644 inkcpp_test/ink/MigrationChangeNodes.ink diff --git a/.cmake-format.json b/.cmake-format.json new file mode 100644 index 00000000..f5da5d8d --- /dev/null +++ b/.cmake-format.json @@ -0,0 +1,5 @@ +{ + "format": { + "line_width": 100 + } +} diff --git a/inkcpp/array.h b/inkcpp/array.h index eb009f54..9a7537be 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -157,6 +157,8 @@ class managed_array : public snapshot_interface void extend(size_t capacity = 0); + bool can_be_migrated() const { return true; } + size_t snap(unsigned char* data, const snapper& snapper) const { inkAssert(! is_pointer{}(), "here is a special case oversight"); @@ -229,6 +231,8 @@ class managed_restorable_array : public managed_arraysize(); } void forgett() { _last_size = 0; } @@ -237,6 +241,8 @@ class managed_restorable_array : public managed_array::get(size_t index) const return _array[index]; } +template +inline const T& basic_restorable_array::get_old(size_t index) const +{ + check_index(index); + inkAssert(_saved, "Use old only on saved arrays."); + + return _array[index]; +} + template inline void basic_restorable_array::save() { @@ -464,7 +481,7 @@ class fixed_restorable_array : public basic_restorable_array }; template -class allocated_restorable_array final : public basic_restorable_array +class allocated_restorable_array : public basic_restorable_array { using base = basic_restorable_array; @@ -521,6 +538,12 @@ class allocated_restorable_array final : public basic_restorable_array T* _buffer; }; +template +inline bool basic_restorable_array::can_be_migrated() const +{ + return ! _saved; +} + template inline size_t basic_restorable_array::snap(unsigned char* data, const snapper&) const { diff --git a/inkcpp/collections/restorable.h b/inkcpp/collections/restorable.h index 7f8b9b7a..9dda466a 100644 --- a/inkcpp/collections/restorable.h +++ b/inkcpp/collections/restorable.h @@ -356,6 +356,8 @@ class restorable : public snapshot_interface virtual size_t snap(unsigned char* data, const snapper&) const; const unsigned char* snap_load(const unsigned char* data, const loader&); + bool can_be_migrated() const { return ! is_saved(); } + protected: // Called when we run out of space in buffer. virtual void overflow(ElementType*&, size_t&) { inkFail("Restorable run out of memory!"); } diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 6003b107..9d29f11a 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -17,15 +17,13 @@ namespace ink::runtime::internal globals_impl::globals_impl(const story_impl* story) : _num_containers(story->num_containers()) , _turn_cnt{0} - , _visit_counts() - , _visit_counts_backup() + , _visit_counts(visit_count(), visit_count_null_value) , _owner(story) , _runners_start(nullptr) , _lists(story->list_meta(), story->get_header()) , _globals_initialized(false) { _visit_counts.resize(_num_containers); - _visit_counts_backup.resize(_num_containers); if (_lists) { // initialize static lists const list_flag* flags = story->lists(); @@ -51,8 +49,7 @@ void globals_impl::visit(uint32_t container_id, bool entering_at_start) { if ((! (_owner->container_flag(container_id) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) || entering_at_start) { - _visit_counts[container_id].visits += 1; - _visit_counts[container_id].turns = 0; + _visit_counts.set(container_id, {_visit_counts[container_id].visits + 1, 0}); } } @@ -66,9 +63,11 @@ uint32_t globals_impl::turns() const { return _turn_cnt; } void globals_impl::turn() { ++_turn_cnt; - for (size_t i = 0; i < _visit_counts.size(); ++i) { - if (_visit_counts[i].turns != -1) { - _visit_counts[i].turns += 1; + for (size_t i = 0; i < _visit_counts.capacity(); ++i) { + visit_count visits = _visit_counts[i]; + if (visits.turns != -1) { + visits.turns += 1; + _visit_counts.set(i, visits); } } } @@ -237,35 +236,43 @@ void globals_impl::gc() void globals_impl::save() { - for (uint32_t i = 0; i < _num_containers; ++i) { - _visit_counts_backup[i] = _visit_counts[i]; - } + _visit_counts.save(); _variables.save(); } void globals_impl::restore() { - for (uint32_t i = 0; i < _num_containers; ++i) { - _visit_counts[i] = _visit_counts_backup[i]; - } + _visit_counts.restore(); _variables.restore(); } -void globals_impl::forget() { _variables.forget(); } +void globals_impl::forget() +{ + _visit_counts.forget(); + _variables.forget(); +} snapshot* globals_impl::create_snapshot() const { return new snapshot_impl(*this); } +bool globals_impl::can_be_migrated() const +{ + return _visit_counts.can_be_migrated() && _strings.can_be_migrated() && _lists.can_be_migrated() + && _variables.can_be_migrated(); +} + size_t globals_impl::snap(unsigned char* data, const snapper& snapper) const { unsigned char* ptr = data; - inkAssert(_num_containers == _visit_counts.size(), "Should be equal!"); + inkAssert(_num_containers == _visit_counts.capacity(), "Should be equal!"); inkAssert( _globals_initialized, "Only support snapshot of globals with runner! or you don't need a snapshot for this state" ); ptr = snap_write(ptr, _turn_cnt, data != nullptr); ptr += _visit_counts.snap(data ? ptr : nullptr, snapper); - ptr += _visit_counts_backup.snap(data ? ptr : nullptr, snapper); + for (unsigned i = 0; i < _visit_counts.capacity(); ++i) { + ptr = snap_write(ptr, _owner->container_hash(i), data != nullptr); + } ptr += _strings.snap(data ? ptr : nullptr, snapper); ptr += _lists.snap(data ? ptr : nullptr, snapper); ptr += _variables.snap(data ? ptr : nullptr, snapper); @@ -277,10 +284,32 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa _globals_initialized = true; ptr = snap_read(ptr, _turn_cnt); ptr = _visit_counts.snap_load(ptr, loader); - ptr = _visit_counts_backup.snap_load(ptr, loader); - inkAssert(_visit_counts.size() == _visit_counts_backup.size(), "Data inconsitency"); + size_t old_capacity = _visit_counts.capacity(); + // shuffle values if needed + if (loader.migratable) { + _visit_counts.resize(_owner->num_containers()); + _visit_counts.save(); + } + inkAssert(old_capacity == _visit_counts.capacity(), "Missmatching number of tracked containers."); + for (size_t i = 0; i < old_capacity; ++i) { + hash_t path; + ptr = snap_read(ptr, path); + container_t c_id; + bool found = _owner->get_container_id(_owner->find_offset_for(path), c_id); + if (! loader.migratable) { + inkAssert(found, "Invalid container id reference."); + inkAssert(c_id == i, "tracked containere are not allowed to move, expect we migrate"); + } else { + if (found) { + _visit_counts.set(c_id, _visit_counts.get_old(i)); + } + } + } + if (loader.migratable) { + _visit_counts.forget(); + } inkAssert( - _num_containers == _visit_counts.size(), + _num_containers == _visit_counts.capacity(), "errer when loading visit counts, story file dont match snapshot!" ); ptr = _strings.snap_load(ptr, loader); @@ -289,6 +318,11 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa return ptr; } +bool globals_impl::migrate_new_globals(globals_impl& new_globals) +{ + return _variables.migrate(new_globals._variables); +} + config::statistics::global globals_impl::statistics() const { return { diff --git a/inkcpp/globals_impl.h b/inkcpp/globals_impl.h index 06c787d6..b5d3bfae 100644 --- a/inkcpp/globals_impl.h +++ b/inkcpp/globals_impl.h @@ -33,6 +33,16 @@ class globals_impl final public: size_t snap(unsigned char* data, const snapper&) const; const unsigned char* snap_load(const unsigned char* data, const loader&); + bool can_be_migrated() const; + /** Merges a global snapshot with new global definition. + * new global variables are taken from new_global. + * already existing ones are ignored + * no longer existing ones are deleted. + * @retval true on success + * @param[in] new_globals to read current relevant variables from. It is modified to be equal to + * the globals stored inside. + */ + bool migrate_new_globals(globals_impl& new_globals); // Initializes a new global store from the given story globals_impl(const story_impl*); @@ -121,8 +131,9 @@ class globals_impl final bool operator!=(const visit_count& vc) const { return ! (*this == vc); } }; - managed_array _visit_counts; - managed_array _visit_counts_backup; + static constexpr visit_count visit_count_null_value{~0U, -2}; + + internal::allocated_restorable_array _visit_counts; // Pointer back to owner story. const story_impl* const _owner; diff --git a/inkcpp/include/runner.h b/inkcpp/include/runner.h index 68400d49..da4a2651 100644 --- a/inkcpp/include/runner.h +++ b/inkcpp/include/runner.h @@ -36,7 +36,7 @@ class choice; class runner_interface { public: - virtual ~runner_interface(){}; + virtual ~runner_interface() {}; // String type to simplify interfaces working with strings #ifdef INK_ENABLE_STL diff --git a/inkcpp/include/snapshot.h b/inkcpp/include/snapshot.h index 6cede405..16a1c1e4 100644 --- a/inkcpp/include/snapshot.h +++ b/inkcpp/include/snapshot.h @@ -26,7 +26,7 @@ namespace ink::runtime class snapshot { public: - virtual ~snapshot(){}; + virtual ~snapshot() {}; /** Construct snapshot from blob. * Memory must be kept valid until the snapshot is deconstructed. @@ -39,11 +39,13 @@ class snapshot static snapshot* from_binary(const unsigned char* data, size_t length, bool freeOnDestroy = true); /** access blob inside snapshot */ - virtual const unsigned char* get_data() const = 0; + virtual const unsigned char* get_data() const = 0; /** size of blob inside snapshot */ - virtual size_t get_data_len() const = 0; + virtual size_t get_data_len() const = 0; /** number of runners which are stored inside this snapshot */ - virtual size_t num_runners() const = 0; + virtual size_t num_runners() const = 0; + /** if this snapshot can be migrated, if the story file changes (slightly). */ + virtual bool can_be_migrated() const = 0; #ifdef INK_ENABLE_STL /** deserialize snapshot from file. diff --git a/inkcpp/include/story.h b/inkcpp/include/story.h index cc696fb7..7678502b 100644 --- a/inkcpp/include/story.h +++ b/inkcpp/include/story.h @@ -69,6 +69,12 @@ class story virtual runner new_runner_from_snapshot( const snapshot& obj, globals store = nullptr, unsigned runner_id = 0 ) = 0; + + /** + * @brief hash of binary/story. + * used to check for story changes. + */ + virtual hash_t hash() const = 0; #pragma endregion #pragma region Factory Methods diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index b0d12ab1..c077ffb9 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -144,6 +144,8 @@ class list_table : public snapshot_interface size_t snap(unsigned char* data, const snapper&) const; const unsigned char* snap_load(const unsigned char* data, const loader&); + bool can_be_migrated() const { return true; } + /** special traitment when a list get assignet again * when a list get assigned and would have no origin, it gets the origin of the base with origin * eg. I072 @@ -448,7 +450,11 @@ class list_table : public snapshot_interface , _pos{null_flag, nullptr} {}; named_flag_itr(const list_table& list, const data_t* filter, int) - : _list{list}, _data{filter}, _pos{{0,0},list._flag_names[0]} + : _list{ + list + } + , _data{filter} + , _pos{{0, 0}, list._flag_names[0]} { goToValid(); } diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 6cb7eee6..94af3950 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -515,6 +515,18 @@ basic_stream& operator>>(basic_stream& in, FString& out) return in; } #endif +bool basic_stream::can_be_migrated() const +{ + if (saved()) { + return false; + } + for (size_t i = 0; i < _size; ++i) { + if (! _data[i].can_be_migrated()) { + return false; + } + } + return true; +} size_t basic_stream::snap(unsigned char* data, const snapper& snapper) const { diff --git a/inkcpp/output.h b/inkcpp/output.h index eb9b2e8e..ae2f58a9 100644 --- a/inkcpp/output.h +++ b/inkcpp/output.h @@ -127,6 +127,7 @@ namespace runtime char last_char() const { return _last_char; } // snapshot interface + bool can_be_migrated() const; size_t snap(unsigned char* data, const snapper&) const; const unsigned char* snap_load(const unsigned char* data, const loader&); diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 9fad54e7..755742ed 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -133,6 +133,7 @@ void runner_impl::set_var( } else { if (is_redef) { value* src = _stack.get(variableName); + inkAssert(src != nullptr, "Tried to redefine a non existing local variable."); if (src->type() == value_type::value_pointer) { auto [name, ci] = src->get(); inkAssert(ci == 0, "Only global pointer are allowed on _stack!"); @@ -162,29 +163,32 @@ void runner_impl::set_var( } template -inline T runner_impl::read() +inline T runner_impl::read(optional pos) { using header = ink::internal::header; + ip_t ptr = pos.value_or(_ptr); // Sanity - inkAssert(_ptr + sizeof(T) <= _story->end(), "Unexpected EOF in Ink execution"); + inkAssert(ptr + sizeof(T) <= _story->end(), "Unexpected EOF in Ink execution"); // Read memory - T val = *( const T* ) _ptr; + T val = *( const T* ) ptr; if (_story->get_header().endien == header::endian_types::differ) { val = header::swap_bytes(val); } // Advance ip - _ptr += sizeof(T); + if (! pos.has_value()) { + _ptr += sizeof(T); + } // Return return val; } template<> -inline const char* runner_impl::read() +inline const char* runner_impl::read(optional pos) { - offset_t str = read(); + offset_t str = read(pos); return _story->string(str); } @@ -292,6 +296,26 @@ void runner_impl::clear_tags(tags_clear_level which) } } +void runner_impl::fetch_tags(ip_t begin) +{ + ip_t iter = begin; + if (read(iter) == Command::START_CONTAINER_MARKER) { + iter += 6; + } + while (read(iter) == Command::START_TAG) { + // skip non-trivial tags (constant string only) + if (read(iter + 6) != Command::STR || read(iter + 12) != Command::END_TAG) { + while (read(iter) != Command::END_TAG) { + iter += 6; + } + iter += 6; + continue; + } + add_tag(read(iter + 6 + 2), tags_level::UNKNOWN); + iter += 18; + } +} + void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) { // Optimization: if we are _is_falling, then we can @@ -386,7 +410,7 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) const ContainerData* iData = nullptr; size_t level = _container.size(); if (_container.iter(iData) - && (level > comm_end + && (level >= comm_end || _story->container_flag(iData->offset + _story->instructions()) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) { auto parrent_offset = _story->instructions() + iData->offset; @@ -633,11 +657,30 @@ void runner_impl::getline_silent() snapshot* runner_impl::create_snapshot() const { return _globals->create_snapshot(); } +bool runner_impl::can_be_migrated() const +{ + if (_choices.size()) { + return false; + } + if (_entered_knot) { + return false; + } + // TODO(JBe): next store _ptr as path + hash_t c_hash = _story->container_hash(_ptr - 6); + if (c_hash == 0) { + return false; + } + return _output.can_be_migrated() && _stack.can_be_migrated() && _ref_stack.can_be_migrated() + && _eval.can_be_migrated() && _tags_begin.can_be_migrated() && _tags.can_be_migrated() + && _container.can_be_migrated() && _threads.can_be_migrated() && _choices.can_be_migrated(); +} + size_t runner_impl::snap(unsigned char* data, snapper& snapper) const { unsigned char* ptr = data; bool should_write = data != nullptr; std::uintptr_t offset = _ptr != nullptr ? _ptr - _story->instructions() : 0; + ptr = snap_write(ptr, _story->container_hash(_ptr - 6), should_write); ptr = snap_write(ptr, offset, should_write); offset = _backup - _story->instructions(); ptr = snap_write(ptr, offset, should_write); @@ -658,8 +701,13 @@ size_t runner_impl::snap(unsigned char* data, snapper& snapper) const snapper.runner_tags = _tags.data(); ptr = snap_write(ptr, _entered_global, should_write); ptr = snap_write(ptr, _entered_knot, should_write); - ptr = snap_write(ptr, _current_knot_id, should_write); - ptr = snap_write(ptr, _current_knot_id_backup, should_write); + ptr = snap_write(ptr, get_current_knot(), should_write); + if (_current_knot_id_backup != ~0U) { + ptr = snap_write(ptr, _story->container_hash(_current_knot_id_backup), should_write); + } else { + hash_t none = 0; + ptr = snap_write(ptr, none, should_write); + } ptr += _container.snap(data ? ptr : nullptr, snapper); ptr += _threads.snap(data ? ptr : nullptr, snapper); ptr = snap_write(ptr, _fallback_choice.has_value(), should_write); @@ -674,6 +722,8 @@ const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& l { auto ptr = data; std::uintptr_t offset; + hash_t current_knot_name; + ptr = snap_read(ptr, current_knot_name); ptr = snap_read(ptr, offset); _ptr = offset == 0 ? nullptr : _story->instructions() + offset; ptr = snap_read(ptr, offset); @@ -697,10 +747,23 @@ const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& l loader.runner_tags = _tags.data(); ptr = snap_read(ptr, _entered_global); ptr = snap_read(ptr, _entered_knot); - ptr = snap_read(ptr, _current_knot_id); - ptr = snap_read(ptr, _current_knot_id_backup); - ptr = _container.snap_load(ptr, loader); - ptr = _threads.snap_load(ptr, loader); + _current_knot_id = ~0U; + ptr = snap_read(ptr, current_knot_name); + if (current_knot_name) { + bool found + = _story->get_container_id(_story->find_offset_for(current_knot_name), _current_knot_id); + inkAssert(found, "Unable to find current knot in migrated story."); + } + _current_knot_id_backup = ~0U; + ptr = snap_read(ptr, current_knot_name); + if (current_knot_name) { + bool found = _story->get_container_id( + _story->find_offset_for(current_knot_name), _current_knot_id_backup + ); + inkAssert(found, "Unable to find current knot backup in migration %u", current_knot_name); + } + ptr = _container.snap_load(ptr, loader); + ptr = _threads.snap_load(ptr, loader); bool has_fallback_choice; ptr = snap_read(ptr, has_fallback_choice); _fallback_choice = nullopt; @@ -737,11 +800,50 @@ bool runner_impl::move_to(hash_t path) // Clear state and move to destination reset(); _ptr = _story->instructions(); - jump(destination, false, true); + jump(destination, false, false); return true; } +bool runner_impl::migrate_to(hash_t path) +{ + ip_t destination = _story->find_offset_for(path); + if (destination == nullptr) { + return false; + } + clear_tags(tags_clear_level::KEEP_NONE); + fetch_tags(_story->instructions()); + assign_tags({tags_level::GLOBAL}); + if (_current_knot_id != ~0U) { + ip_t start_of_knot = _story->find_offset_for(_story->container_hash(_current_knot_id)); + fetch_tags(start_of_knot); + assign_tags({tags_level::KNOT}); + if (start_of_knot != destination) { + for (ip_t iter = start_of_knot; iter != destination; iter += 6) { + if (read(iter) == Command::DEFINE_TEMP) { + hash_t temp_name = read(iter + 2); + if (get_var(temp_name) == nullptr) { + ip_t eval_start = iter - 6; + inkAssert( + read(eval_start) == Command::END_EVAL, + "expected an evaluation segment before defininng a temporary variable" + ); + while (read(eval_start) != Command::START_EVAL) { + eval_start -= 6; + } + jump(eval_start, false, false); + while (_ptr != iter + 6) { + step(); + } + } + } + } + } + } + jump(destination, false, true); + return true; +} + void runner_impl::internal_bind(hash_t name, internal::function_base* function) { _functions.add(name, function); @@ -892,6 +994,7 @@ void runner_impl::step() set_done_ptr(nullptr); } if (cmd >= Command::OP_BEGIN && cmd < Command::OP_END) { + read(); _operations(cmd, _eval); } else { switch (cmd) { @@ -995,6 +1098,7 @@ void runner_impl::step() _eval.push(value{}.set(target)); } break; case Command::NEWLINE: { + read(); if (_evaluation_mode) { _eval.push(values::newline); } else { @@ -1004,6 +1108,7 @@ void runner_impl::step() } } break; case Command::GLUE: { + read(); if (_evaluation_mode) { _eval.push(values::glue); } else { @@ -1011,6 +1116,7 @@ void runner_impl::step() } } break; case Command::VOID: { + read(); if (_evaluation_mode) { _eval.push(values::null); // TODO: void type? } @@ -1094,9 +1200,15 @@ void runner_impl::step() } break; // == Terminal commands - case Command::DONE: on_done(true); break; + case Command::DONE: + read(); + on_done(true); + break; - case Command::END: _ptr = nullptr; break; + case Command::END: + read(); + _ptr = nullptr; + break; // == Tunneling case Command::TUNNEL: { @@ -1153,10 +1265,12 @@ void runner_impl::step() } break; case Command::TUNNEL_RETURN: case Command::FUNCTION_RETURN: { + read(); execute_return(); } break; case Command::THREAD: { + read(); // Push a thread frame so we can return easily // TODO We push ahead of a single divert. Is that correct in all cases....????? auto returnTo = _ptr + CommandSize; @@ -1258,18 +1372,29 @@ void runner_impl::step() } break; // == Evaluation stack - case Command::START_EVAL: _evaluation_mode = true; break; + case Command::START_EVAL: + read(); + _evaluation_mode = true; + break; case Command::END_EVAL: + read(); _evaluation_mode = false; // Assert stack is empty? Is that necessary? break; case Command::OUTPUT: { + read(); value v = _eval.pop(); _output << v; } break; - case Command::POP: _eval.pop(); break; - case Command::DUPLICATE: _eval.push(_eval.top_value()); break; + case Command::POP: + read(); + _eval.pop(); + break; + case Command::DUPLICATE: + read(); + _eval.push(_eval.top_value()); + break; case Command::PUSH_VARIABLE_VALUE: { // Try to find in local stack hash_t variableName = read(); @@ -1288,12 +1413,14 @@ void runner_impl::step() break; } case Command::START_STR: { + read(); inkAssert(_evaluation_mode, "Can not enter string mode while not in evaluation mode!"); _string_mode = true; _evaluation_mode = false; _output << values::marker; } break; case Command::END_STR: { + read(); // TODO: Assert we really had a marker on there? inkAssert(! _evaluation_mode, "Must be in evaluation mode"); _string_mode = false; @@ -1308,11 +1435,13 @@ void runner_impl::step() // == Tag commands case Command::START_TAG: { + read(); _output << values::marker; } break; case Command::END_TAG: { + read(); auto tag = _output.get_alloc(_globals->strings(), _globals->lists()); add_tag(tag, tags_level::UNKNOWN); } break; @@ -1444,6 +1573,7 @@ void runner_impl::step() } } break; case Command::VISIT: { + read(); // Push the visit count for the current container to the top // is 0-indexed for some reason. idk why but this is what ink expects _eval.push(value{}.set( @@ -1451,9 +1581,11 @@ void runner_impl::step() )); } break; case Command::TURN: { + read(); _eval.push(value{}.set(static_cast(_globals->turns()))); } break; case Command::SEQUENCE: { + read(); // TODO: The C# ink runtime does a bunch of fancy logic // to make sure each element is picked at least once in every // iteration loop. I don't feel like replicating that right now. @@ -1467,6 +1599,7 @@ void runner_impl::step() ); } break; case Command::SEED: { + read(); int32_t seed = _eval.pop().get(); _rng.srand(seed); @@ -1488,6 +1621,7 @@ void runner_impl::step() ))); } break; case Command::TAG: { + read(); add_tag(read(), tags_level::UNKNOWN); } break; default: inkAssert(false, "Unrecognized command!"); break; diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index f4089a29..e19a7700 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -123,6 +123,7 @@ class runner_impl size_t snap(unsigned char* data, snapper&) const; const unsigned char* snap_load(const unsigned char* data, loader&); + bool can_be_migrated() const; #ifdef INK_ENABLE_CSTD // c-style getline @@ -132,6 +133,9 @@ class runner_impl // move to path virtual bool move_to(hash_t path) override; + // move to path but keep as much state as possible + bool migrate_to(hash_t path); + // Gets a single line of output virtual line_type getline() override; @@ -193,7 +197,7 @@ class runner_impl private: template - inline T read(); + inline T read(optional pos = nullopt); choice& add_choice(); void clear_choices(); @@ -208,6 +212,8 @@ class runner_impl KEEP_KNOT, ///< keep knot and global tags }; void clear_tags(tags_clear_level which); + // Fetch string only tags at Tag/Global level + void fetch_tags(ip_t begin); // Special code for jumping from the current IP to another void jump(ip_t, bool record_visits, bool track_knot_visit); @@ -389,7 +395,7 @@ const unsigned char* } template<> -inline const char* runner_impl::read(); +inline const char* runner_impl::read(optional); template bool runner_impl::has_tags() const diff --git a/inkcpp/simple_restorable_stack.h b/inkcpp/simple_restorable_stack.h index 12ce3d40..db7f5138 100644 --- a/inkcpp/simple_restorable_stack.h +++ b/inkcpp/simple_restorable_stack.h @@ -45,10 +45,13 @@ class simple_restorable_stack : public snapshot_interface bool rev_iter(const T*& iterator) const; // == Save/Restore == + bool is_saved() const { return _save != InvalidIndex; } + void save(); void restore(); void forget(); + virtual bool can_be_migrated() const; virtual size_t snap(unsigned char* data, const snapper&) const; virtual const unsigned char* snap_load(const unsigned char* data, const loader&); @@ -290,6 +293,12 @@ inline void simple_restorable_stack::forget() _save = _jump = InvalidIndex; } +template +bool simple_restorable_stack::can_be_migrated() const +{ + return ! is_saved(); +} + template size_t simple_restorable_stack::snap(unsigned char* data, const snapper&) const { diff --git a/inkcpp/snapshot_impl.cpp b/inkcpp/snapshot_impl.cpp index 12d55f6a..046b5de5 100644 --- a/inkcpp/snapshot_impl.cpp +++ b/inkcpp/snapshot_impl.cpp @@ -57,6 +57,11 @@ size_t snapshot_impl::file_size(size_t serialization_length, size_t runner_cnt) return serialization_length + sizeof(header) + (runner_cnt + 1) * sizeof(size_t); } +bool snapshot_impl::can_be_migrated(const story& story) const +{ + return can_be_migrated() || (story.hash() == _header.hash); +} + const unsigned char* snapshot_impl::get_data() const { return _file; } size_t snapshot_impl::get_data_len() const { return _length; } @@ -65,16 +70,21 @@ snapshot_impl::snapshot_impl(const globals_impl& globals) : _managed{true} { snapshot_interface::snapper snapper{globals.strings(), globals._owner->string(0)}; - _length = globals.snap(nullptr, snapper); - size_t runner_cnt = 0; + bool migratable = globals.can_be_migrated(); + size_t runner_cnt = 0; + + _length = globals.snap(nullptr, snapper); for (auto node = globals._runners_start; node; node = node->next) { _length += node->object->snap(nullptr, snapper); + migratable = migratable && node->object->can_be_migrated(); ++runner_cnt; } _length = file_size(_length, runner_cnt); _header.length = _length; _header.num_runners = runner_cnt; + _header.hash = globals._owner->hash(); + _header.migratable = migratable; unsigned char* data = new unsigned char[_length]; _file = data; unsigned char* ptr = data; @@ -108,6 +118,7 @@ snapshot_impl::snapshot_impl(const unsigned char* data, size_t length, bool mana const unsigned char* ptr = data; memcpy(&_header, ptr, sizeof(_header)); inkAssert(_header.length == _length, "Corrupted file length"); + inkAssert(_header.version == decltype(_header){}.version, "Snapshot version missmatch"); } size_t snap_choice::snap(unsigned char* data, const snapper& snapper) const diff --git a/inkcpp/snapshot_impl.h b/inkcpp/snapshot_impl.h index 049fe3eb..9078c1a1 100644 --- a/inkcpp/snapshot_impl.h +++ b/inkcpp/snapshot_impl.h @@ -11,6 +11,11 @@ #include "array.h" #include "choice.h" +namespace ink::runtime +{ +class story; +} // namespace ink::runtime + namespace ink::runtime::internal { class snap_choice @@ -67,7 +72,7 @@ class snap_tag : public snapshot_interface static_assert(sizeof(snap_tag) == sizeof(const char*)); -class snapshot_impl : public snapshot +class snapshot_impl final : public snapshot { public: ~snapshot_impl() override @@ -95,6 +100,13 @@ class snapshot_impl : public snapshot size_t num_runners() const override { return _header.num_runners; } + bool can_be_migrated() const override { return _header.migratable; } + + bool can_be_migrated(const story&) const; + + hash_t hash() const { return _header.hash; } + + private: // file information // only populated when loading snapshots @@ -107,7 +119,9 @@ class snapshot_impl : public snapshot struct header { size_t num_runners; size_t length; - + hash_t hash; + bool migratable; + size_t version = 1; } _header; size_t get_offset(size_t idx) const diff --git a/inkcpp/snapshot_interface.h b/inkcpp/snapshot_interface.h index 42c4eb50..f597ed1c 100644 --- a/inkcpp/snapshot_interface.h +++ b/inkcpp/snapshot_interface.h @@ -22,7 +22,7 @@ class value; class snapshot_interface { public: - constexpr snapshot_interface(){}; + constexpr snapshot_interface() {}; static unsigned char* snap_write(unsigned char* ptr, const void* data, size_t length, bool write) { @@ -61,6 +61,7 @@ class snapshot_interface struct loader { managed_array& string_table; /// FIXME: make configurable const char* story_string_table; + const bool migratable; const snap_tag* runner_tags = nullptr; loader() = delete; loader& operator=(const loader&) = delete; @@ -88,6 +89,16 @@ class snapshot_interface return nullptr; }; + /** Check if the snappable component is in a state which could be migrated to a/new different + * story. + * @attention a migration can still fail, even if @c can_be_migrated() was true. + */ + bool can_be_migrated() const + { + inkFail("Snap function not implementd"); + return false; + }; + #ifdef __GNUC__ # pragma GCC diagnostic pop #else diff --git a/inkcpp/stack.cpp b/inkcpp/stack.cpp index dd9c562f..97192b87 100644 --- a/inkcpp/stack.cpp +++ b/inkcpp/stack.cpp @@ -282,12 +282,11 @@ offset_t basic_stack::pop_frame(frame_type* type, bool& eval) // We now have a frame marker. Check if it's a thread // Thread handling if ( - // FIXME: is_tghead_marker, is_jump_marker - frame->data.type() == value_type::thread_start - || frame->data.type() == value_type::thread_end - || frame->data.type() == value_type::jump_marker - ) - { + // FIXME: is_tghead_marker, is_jump_marker + frame->data.type() == value_type::thread_start + || frame->data.type() == value_type::thread_end + || frame->data.type() == value_type::jump_marker + ) { // End of thread marker, we need to create a jump marker if (frame->data.type() == value_type::thread_end) { // Push a new jump marker after the thread end @@ -579,6 +578,33 @@ void basic_eval_stack::forget() base::forget([&none](value& elem) { elem = none; }); } +bool basic_stack::can_be_migrated() const +{ + bool values_migratable = true; + for_each_all([&values_migratable](const entry& e) { + if (! e.data.can_be_migrated()) { + values_migratable = false; + } + }); + return base::can_be_migrated() && _next_thread == 0 && values_migratable; +} + +bool basic_stack::migrate(basic_stack& new_stack) +{ + inkAssert(can_be_migrated() && new_stack.can_be_migrated()); + // move existing values to new_stack, iff there the variable is also in the new stack + for_each_all([&new_stack](const entry& e) { + const value* oth = new_stack.get(e.name); + if (oth) { + new_stack.set(e.name, e.data); + } + }); + // set stack to correct new values + clear(); + new_stack.for_each_all([this](const entry& e) { set(e.name, e.data); }); + return true; +} + void basic_stack::fetch_values(basic_stack& stack) { auto itr = base::begin(); diff --git a/inkcpp/stack.h b/inkcpp/stack.h index eb06cac2..1308b71e 100644 --- a/inkcpp/stack.h +++ b/inkcpp/stack.h @@ -86,6 +86,9 @@ namespace runtime void restore(); void forget(); + // copy new elements from _new, and delete elements now longer existing + bool migrate(basic_stack& _new); + // replace all pointer in current frame with values from _stack void fetch_values(basic_stack& _stack); // push all values to other _stack @@ -94,6 +97,7 @@ namespace runtime // snapshot interface size_t snap(unsigned char* data, const snapper&) const; const unsigned char* snap_load(const unsigned char* data, const loader&); + bool can_be_migrated() const; private: entry& add(hash_t name, const value& val); @@ -208,6 +212,8 @@ namespace runtime { return base::snap_load(data, loader); } + + bool can_be_migrated() const { return base::can_be_migrated(); } }; template diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index 48288f57..e1181bb6 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -191,14 +191,20 @@ hash_t story_impl::container_hash(container_t id) const } } inkAssert(hit, "Unable to find container for id!"); + hash_t hash = container_hash(offset); + inkAssert(hash, "Did not find hash entry for container!"); + return hash; +} + +hash_t story_impl::container_hash(ip_t offset) const +{ hash_t* h_iter = _container_hash_start; - while (iter != _container_hash_end) { + while (h_iter != _container_hash_end) { if (instructions() + *( offset_t* ) (h_iter + 1) == offset) { return *h_iter; } h_iter += 2; } - inkAssert(false, "Did not find hash entry for container!"); return 0; } @@ -226,16 +232,24 @@ globals story_impl::new_globals() globals story_impl::new_globals_from_snapshot(const snapshot& data) { const snapshot_impl& snapshot = reinterpret_cast(data); - auto* globs = new globals_impl(this); + if (! snapshot.can_be_migrated(*this)) { + return globals(); + } + auto* globs = new globals_impl(this); snapshot.strings().clear(); auto end = globs->snap_load( snapshot.get_globals_snap(), - snapshot_interface::loader{ - snapshot.strings(), - _string_table, - } + snapshot_interface::loader{snapshot.strings(), _string_table, snapshot.can_be_migrated()} ); inkAssert(end == snapshot.get_runner_snap(0), "not all data were used for global reconstruction"); + if (hash() != snapshot.hash()) { + globals new_globs = new_globals(); + runner thread = new_runner(new_globs); + if (! globs->migrate_new_globals(*new_globs.cast().get())) { + delete globs; + return globals(); + } + } return globals(globs, _block); } @@ -251,20 +265,26 @@ runner story_impl::new_runner_from_snapshot(const snapshot& data, globals store, const snapshot_impl& snapshot = reinterpret_cast(data); if (store == nullptr) store = new_globals_from_snapshot(snapshot); - auto* run = new runner_impl(this, store); - auto loader = snapshot_interface::loader{ - snapshot.strings(), - _string_table, - }; + auto* run = new runner_impl(this, store); // snapshot id is inverso of creation time, but creation time is the more intouitve numbering to // use - idx = (data.num_runners() - idx - 1); + idx = (data.num_runners() - idx - 1); + auto loader = snapshot_interface::loader{ + snapshot.strings(), + _string_table, + snapshot.can_be_migrated(), + }; auto end = run->snap_load(snapshot.get_runner_snap(idx), loader); inkAssert( (idx + 1 < snapshot.num_runners() && end == snapshot.get_runner_snap(idx + 1)) || end == snapshot.get_data() + snapshot.get_data_len(), "not all data were used for runner reconstruction" ); + if (hash() != snapshot.hash()) { + if (! run->migrate_to(*reinterpret_cast(snapshot.get_runner_snap(idx)))) { + return runner(); + } + } return runner(run, _block); } diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index e8cc1217..d4417ec5 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -48,6 +48,7 @@ class story_impl : public story CommandFlag container_flag(ip_t offset) const; CommandFlag container_flag(container_t id) const; hash_t container_hash(container_t id) const; + hash_t container_hash(ip_t offset) const; ip_t find_offset_for(hash_t path) const; @@ -60,6 +61,8 @@ class story_impl : public story const ink::internal::header& get_header() const { return _header; } + hash_t hash() const override { return hash_data(_file, _length); } + private: void setup_pointers(); diff --git a/inkcpp/string_table.h b/inkcpp/string_table.h index ff6752e6..c7768a4d 100644 --- a/inkcpp/string_table.h +++ b/inkcpp/string_table.h @@ -34,6 +34,8 @@ class string_table final : public snapshot_interface size_t snap(unsigned char* data, const snapper&) const; const unsigned char* snap_load(const unsigned char* data, const loader&); + bool can_be_migrated() const { return true; } + // get position of string when iterate through data // used to enable storing a string table references size_t get_id(const char* string) const; diff --git a/inkcpp/system.cpp b/inkcpp/system.cpp index 6b5990c1..7410cab0 100644 --- a/inkcpp/system.cpp +++ b/inkcpp/system.cpp @@ -10,30 +10,39 @@ namespace ink { -#define A 54059 /* a prime */ -#define B 76963 /* another prime */ -#define C 86969 /* yet another prime */ -#define FIRSTH 37 /* also prime */ +# define A 54059 /* a prime */ +# define B 76963 /* another prime */ +# define C 86969 /* yet another prime */ +# define FIRSTH 37 /* also prime */ - hash_t hash_string(const char* string) - { - hash_t h = FIRSTH; - while (*string) { - h = (h * A) ^ (string[0] * B); - string++; - } - return h; // or return h % C; +hash_t hash_string(const char* string) +{ + hash_t h = FIRSTH; + while (*string) { + h = (h * A) ^ (string[0] * B); + string++; + } + return h; // or return h % C; +} + +hash_t hash_data(const unsigned char* data, size_t len) +{ + hash_t h = FIRSTH; + for (size_t i = 0; i < len; ++i) { + h = (h * A) ^ (data[i] * B); } + return h; // or return h % C; +} - namespace internal - { - void zero_memory(void* buffer, size_t length) - { - char* buf = static_cast(buffer); - for (size_t i = 0; i < length; i++) - *(buf++) = 0; - } - } // namespace internal - } // namespace ink +namespace internal +{ + void zero_memory(void* buffer, size_t length) + { + char* buf = static_cast(buffer); + for (size_t i = 0; i < length; i++) + *(buf++) = 0; + } +} // namespace internal +} // namespace ink #endif diff --git a/inkcpp/value.cpp b/inkcpp/value.cpp index 6152cab2..b4383be0 100644 --- a/inkcpp/value.cpp +++ b/inkcpp/value.cpp @@ -237,6 +237,14 @@ ink::runtime::value value::to_interface_value(list_table& table) const return val(); } +bool value::can_be_migrated() const +{ + if (_type == value_type::string && ! string_value.allocated) { + return false; + } + return true; +} + size_t value::snap(unsigned char* data, const snapper& snapper) const { unsigned char* ptr = data; diff --git a/inkcpp/value.h b/inkcpp/value.h index aef705da..36b1b335 100644 --- a/inkcpp/value.h +++ b/inkcpp/value.h @@ -108,6 +108,7 @@ class value : public snapshot_interface { public: // snapshot interface + bool can_be_migrated() const; size_t snap(unsigned char* data, const snapper&) const; const unsigned char* snap_load(const unsigned char* data, const loader&); @@ -185,8 +186,9 @@ class value : public snapshot_interface } else if (ty != type()) { return redefine(oth, env); } else { - return internal::redefine::type, tuple>(env - )(get(), oth.get()); + return internal::redefine::type, tuple>(env)( + get(), oth.get() + ); } } diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index 1d9bbdde..221713e1 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -246,10 +246,19 @@ int main(int argc, const char** argv) std::cout << "?> "; std::cin >> c; if (c == -1) { + std::cout << "To create a migratable snapshot please enter a choice in addition, or `-1` " + "to snap right now:\nsnap after\n?>"; + std::cin >> c; + if (c != -1) { + thread->choose(c - 1); + } snapshot* snap = thread->create_snapshot(); snap->write_to_file( std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".snap").c_str() ); + if (snap->can_be_migrated()) { + std::cout << "Migratable snapshot." << std::endl; + } delete snap; break; } diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index aec80eea..3425201c 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -156,8 +156,12 @@ void binary_emitter::write_raw( { _containers.write(command); _containers.write(flag); + constexpr size_t MAX_PAYLOAD_SIZE = 4; + ink_assert(payload_size <= MAX_PAYLOAD_SIZE, "enforce constant instruction size"); if (payload_size > 0) _containers.write(( const byte_t* ) payload, payload_size); + constexpr const byte_t empty[MAX_PAYLOAD_SIZE] = {}; + _containers.write(empty, MAX_PAYLOAD_SIZE - payload_size); } void binary_emitter::write_path( diff --git a/inkcpp_test/CMakeLists.txt b/inkcpp_test/CMakeLists.txt index 2f292f46..88fde8a4 100644 --- a/inkcpp_test/CMakeLists.txt +++ b/inkcpp_test/CMakeLists.txt @@ -1,4 +1,7 @@ -add_executable(inkcpp_test catch.hpp Main.cpp +add_executable( + inkcpp_test + catch.hpp + Main.cpp Array.cpp Pointer.cpp Stack.cpp @@ -21,7 +24,7 @@ add_executable(inkcpp_test catch.hpp Main.cpp EmptyStringForDivert.cpp MoveTo.cpp Fixes.cpp -) + Migration.cpp) target_link_libraries(inkcpp_test PUBLIC inkcpp inkcpp_compiler inkcpp_shared) target_include_directories(inkcpp_test PRIVATE ../shared/private/) @@ -39,7 +42,7 @@ endif() add_test(NAME UnitTests COMMAND $) -if ($ENV{INKLECATE}) +if($ENV{INKLECATE}) set(INKLECATE_CMD $ENV{INKLECATE}) else() set(INKLECATE_CMD "inklecate") @@ -52,67 +55,79 @@ if((inkcpp_inklecate_upper STREQUAL "ALL") OR (inkcpp_inklecate_upper STREQUAL " if(inklecate_linux_POPULATED) set(INKLECATE_CMD "${inklecate_linux_SOURCE_DIR}/inklecate") else() - message(FATAL_ERROR "inklecate download is not provided, please check if the download failed. - You may set INKCPP_INKLECATE=NONE and provide inkcleate via your PATH or the INKLECATE enviroment variable. - You can also disable tests altogether by setting INKCPP_TEST=OFF") + message( + FATAL_ERROR + "inklecate download is not provided, please check if the download failed. \ + You may set INKCPP_INKLECATE=NONE and provide inkcleate \ + via your PATH or the INKLECATE enviroment variable. \ + You can also disable tests altogether by setting INKCPP_TEST=OFF") endif(inklecate_linux_POPULATED) elseif(APPLE) if(inklecate_mac_POPULATED) set(INKLECATE_CMD "${inklecate_mac_SOURCE_DIR}/inklecate") else() - message(FATAL_ERROR "inklecate download is not provided, please check if the download failed. - You may set INKCPP_INKLECATE=NONE and provide inkcleate via your PATH or the INKLECATE enviroment variable. - You can also disable tests altogether by setting INKCPP_TEST=OFF") + message( + FATAL_ERROR + "inklecate download is not provided, please check if the download failed. \ + You may set INKCPP_INKLECATE=NONE and provide inkcleate \ + via your PATH or the INKLECATE enviroment variable. + You can also disable tests altogether by setting INKCPP_TEST=OFF") endif(inklecate_mac_POPULATED) - elseif(MSYS OR MINGW OR WIN32 OR CYGWIN) + elseif( + MSYS + OR MINGW + OR WIN32 + OR CYGWIN) if(inklecate_windows_POPULATED) set(INKLECATE_CMD "${inklecate_windows_SOURCE_DIR}/inklecate") else() - message(FATAL_ERROR "inklecate download is not provided, please check if the download failed. - You may set INKCPP_INKLECATE=NONE and provide inkcleate via your PATH or the INKLECATE enviroment variable. - You can also disable tests altogether by setting INKCPP_TEST=OFF") + message( + FATAL_ERROR + "inklecate download is not provided, please check if the download failed. \ + You may set INKCPP_INKLECATE=NONE and provide inkcleate \ + via your PATH or the INKLECATE enviroment variable. \ + You can also disable tests altogether by setting INKCPP_TEST=OFF") endif(inklecate_windows_POPULATED) else() - message(FATAL_ERROR "Current os could not be identified, therfore inklecate must be provided explicit for the tests to work - please set INKCPP_INKLECATE=NONE and provide inklecate via your PATH or the INKLECATE enviroment variables. - Alternatily disable tests by setting INKCPP_TEST=OFF.") + message( + FATAL_ERROR + "Current os could not be identified, \ + therfore inklecate must be provided explicit for the tests to work \ + please set INKCPP_INKLECATE=NONE and provide inklecate \ + via your PATH or the INKLECATE enviroment variables. + Alternatily disable tests by setting INKCPP_TEST=OFF.") endif() endif((inkcpp_inklecate_upper STREQUAL "ALL") OR (inkcpp_inklecate_upper STREQUAL "OS")) set(INK_TEST_RESOURCE_DIR "${PROJECT_BINARY_DIR}/ink") file(MAKE_DIRECTORY "${INK_TEST_RESOURCE_DIR}") -target_compile_definitions(inkcpp_test PRIVATE - INK_TEST_RESOURCE_DIR="${INK_TEST_RESOURCE_DIR}/") +target_compile_definitions(inkcpp_test PRIVATE INK_TEST_RESOURCE_DIR="${INK_TEST_RESOURCE_DIR}/") file(GLOB JSON_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ink/*.json") -file(COPY ${JSON_FILES} DESTINATION ${INK_TEST_RESOURCE_DIR}) +file(COPY ${JSON_FILES} DESTINATION ${INK_TEST_RESOURCE_DIR}) file(GLOB INK_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ink/*.ink") -foreach(INK_FILE IN LISTS INK_FILES) - get_filename_component(INK_FILENAME ${INK_FILE} NAME_WE) - set(output "${INK_TEST_RESOURCE_DIR}/${INK_FILENAME}.bin") +foreach(ink_file IN LISTS INK_FILES) + get_filename_component(INK_FILENAME ${ink_file} NAME_WE) + set(OUTPUT "${INK_TEST_RESOURCE_DIR}/${INK_FILENAME}.bin") add_custom_command( - OUTPUT ${output} - COMMAND $ -o "${output}" --inklecate "${INKLECATE_CMD}" "${INK_FILE}" - DEPENDS ${INK_FILE} inkcpp_compiler - COMMENT "Compile test ink file '${INK_FILENAME}.ink' -> '${output}'" - ) - list(APPEND INK_OUT_FILES ${output}) + OUTPUT ${OUTPUT} + COMMAND $ -o "${OUTPUT}" --inklecate "${INKLECATE_CMD}" "${ink_file}" + DEPENDS ${ink_file} inkcpp_compiler + COMMENT "Compile test ink file '${INK_FILENAME}.ink' -> '${OUTPUT}'") + list(APPEND INK_OUT_FILES ${OUTPUT}) endforeach() target_sources(inkcpp_test PRIVATE ${INK_OUT_FILES}) if(TARGET inkcpp_c) file(GLOB TEST_FILES "${PROJECT_SOURCE_DIR}/inkcpp_c/tests/*.c") - foreach(TEST_FILE IN LISTS TEST_FILES) - get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE) - add_executable(${TEST_NAME} ${TEST_FILE}) + foreach(test_file IN LISTS TEST_FILES) + get_filename_component(TEST_NAME ${test_file} NAME_WE) + add_executable(${TEST_NAME} ${test_file}) target_link_libraries(${TEST_NAME} PRIVATE inkcpp_c) - target_compile_definitions(${TEST_NAME} PRIVATE - INK_TEST_RESOURCE_DIR="${INK_TEST_RESOURCE_DIR}/") - add_test( - NAME ${TEST_NAME} - COMMAND $ - ) + target_compile_definitions(${TEST_NAME} + PRIVATE INK_TEST_RESOURCE_DIR="${INK_TEST_RESOURCE_DIR}/") + add_test(NAME ${TEST_NAME} COMMAND $) endforeach() endif(TARGET inkcpp_c) diff --git a/inkcpp_test/Migration.cpp b/inkcpp_test/Migration.cpp new file mode 100644 index 00000000..748a256a --- /dev/null +++ b/inkcpp_test/Migration.cpp @@ -0,0 +1,187 @@ +#include "catch.hpp" +#include "snapshot.h" +#include "../snapshot_impl.h" + +#include +#include +#include +#include +#include +#include + +using namespace ink::runtime; + +SCENARIO("Simple isolated migration tests.") +{ + std::unique_ptr base_story{story::from_file(INK_TEST_RESOURCE_DIR "MigrationBase.bin")}; + globals base_globals = base_story->new_globals(); + runner base_thread = base_story->new_runner(base_globals); + WHEN("Just Run the base story") + { + std::string content = base_thread->getall(); + REQUIRE(base_thread->has_choices()); + REQUIRE(content == "A\n0 -1 0\nThis is a simple story.\n"); + THEN("All values as defined") + { + CHECK(base_globals->get("do_not_migrate").value_or(0) == 10); + CHECK(base_globals->get("do_migrate").value_or(0) == 15); + } + REQUIRE(base_thread->num_global_tags() == 2); + REQUIRE(base_thread->get_global_tag(0) == std::string("test:migration")); + REQUIRE(base_thread->get_global_tag(1) == std::string("flavor:base")); + REQUIRE(base_thread->num_knot_tags() == 1); + REQUIRE(base_thread->get_knot_tag(0) == std::string("knot:Main")); + } + GIVEN("Simple story with changes in globals.") + { + std::unique_ptr new_story{story::from_file(INK_TEST_RESOURCE_DIR + "MigrationChangeGlobals.bin")}; + WHEN("Just Run the new story") + { + globals new_globals = new_story->new_globals(); + runner new_thread = new_story->new_runner(new_globals); + std::string content = new_thread->getall(); + REQUIRE(new_thread->has_choices()); + REQUIRE(content == "A\n0 -1 0\nThis is a simple story.\n"); + THEN("All values as defined") + { + CHECK(new_globals->get("do_migrate").value_or(0) == 10); + CHECK(new_globals->get("new_var").value_or(0) == 20); + } + } + WHEN("Run base story and load in new_story") + { + std::string content = base_thread->getall(); + REQUIRE(base_thread->has_choices()); + REQUIRE(content == "A\n0 -1 0\nThis is a simple story.\n"); + base_thread->choose(0); + std::unique_ptr snap{base_thread->create_snapshot()}; + REQUIRE(snap->can_be_migrated()); + globals new_globals = new_story->new_globals_from_snapshot(*snap); + runner new_thread = new_story->new_runner_from_snapshot(*snap, new_globals); + THEN("expect merged globals") + { + CHECK_FALSE(new_globals->get("do_not_migrate").has_value()); + CHECK(new_globals->get("do_migrate").value_or(0) == 15); + CHECK(new_globals->get("new_var").value_or(0) == 20); + } + THEN("expect story to continue normally") + { + content = new_thread->getall(); + REQUIRE(content == "A\ncatch\n1 -1 0\nOh.\n"); + } + } + } + GIVEN("Simple story with changed knots.") + { + std::unique_ptr new_story{story::from_file(INK_TEST_RESOURCE_DIR + "MigrationChangeNodes.bin")}; + WHEN("Just Run the new story") + { + globals new_globals = new_story->new_globals(); + runner new_thread = new_story->new_runner(new_globals); + std::string content = new_thread->getall(); + REQUIRE(new_thread->has_choices()); + REQUIRE(content == "B\n-1 0 0\nThis is a simple story.\n"); + new_thread->choose(0); + content = new_thread->getall(); + REQUIRE(content == "A\ncatch\n-1 1 0\nOh.\n"); + } + WHEN("Run base story and load new story.") + { + std::string content = base_thread->getall(); + REQUIRE(base_thread->has_choices()); + REQUIRE(content == "A\n0 -1 0\nThis is a simple story.\n"); + base_thread->choose(0); + std::unique_ptr snap{base_thread->create_snapshot()}; + REQUIRE(snap->can_be_migrated()); + globals new_globals = new_story->new_globals_from_snapshot(*snap); + runner new_thread = new_story->new_runner_from_snapshot(*snap, new_globals); + THEN("Migrated visit counts. Unreachable node has visit, new node has no") + { + content = new_thread->getall(); + REQUIRE(content == "A\ncatch\n1 1 0\nOh.\n"); + } + } + } + GIVEN("Simple story with changed temporary variables.") + { + std::unique_ptr new_story{story::from_file(INK_TEST_RESOURCE_DIR "MigrationTemp.bin")}; + WHEN("Just Run the new story.") + { + globals new_globals = new_story->new_globals(); + runner new_thread = new_story->new_runner(new_globals); + std::string content = new_thread->getall(); + REQUIRE(new_thread->has_choices()); + REQUIRE(content == "A\n0 -1 0\nThis is a simple story.\n"); + REQUIRE(new_thread->get_current_knot() == 0x25e83b84); + new_thread->choose(0); + REQUIRE(new_thread->get_current_knot() == 0x25e83b84); + content = new_thread->getall(); + REQUIRE(new_thread->get_current_knot() == 0x25e83b84); + REQUIRE(content == "A\ncatch\n2 - 3\n1 -1 0\nOh.\n"); + } + WHEN("Run base story and load new story.") + { + std::string content = base_thread->getall(); + REQUIRE(base_thread->has_choices()); + REQUIRE(content == "A\n0 -1 0\nThis is a simple story.\n"); + REQUIRE(base_thread->get_current_knot() == 0x25e83b84); + base_thread->choose(0); + REQUIRE(base_thread->get_current_knot() == 0x25e83b84); + std::unique_ptr snap{base_thread->create_snapshot()}; + REQUIRE(snap->can_be_migrated()); + globals new_globals = new_story->new_globals_from_snapshot(*snap); + runner new_thread = new_story->new_runner_from_snapshot(*snap, new_globals); + THEN("Transfared old temporary variable, and kept default from new one.") + { + REQUIRE(new_thread->get_current_knot() == 0x25e83b84); + content = new_thread->getall(); + REQUIRE(content == "A\ncatch\n5 - 6\n1 -1 0\nOh.\n"); + } + } + } + GIVEN("Simple story with other knot/global tags.") + { + std::unique_ptr new_story{story::from_file(INK_TEST_RESOURCE_DIR "MigrationKnotTags.bin") + }; + WHEN("Just Run the new story.") + { + globals new_globals = new_story->new_globals(); + runner new_thread = new_story->new_runner(new_globals); + std::string content = new_thread->getall(); + REQUIRE(new_thread->has_choices()); + REQUIRE(content == "A\n0 -1 0\nThis is a simple story.\n"); + REQUIRE(new_thread->num_global_tags() == 2); + REQUIRE(new_thread->get_global_tag(0) == std::string("test:migration")); + REQUIRE(new_thread->get_global_tag(1) == std::string("flavor:changed")); + REQUIRE(new_thread->num_knot_tags() == 1); + REQUIRE(new_thread->get_knot_tag(0) == std::string("knot:different")); + } + WHEN("Run base story and load new story.") + { + std::string content = base_thread->getall(); + REQUIRE(base_thread->has_choices()); + REQUIRE(content == "A\n0 -1 0\nThis is a simple story.\n"); + REQUIRE(base_thread->num_global_tags() == 2); + REQUIRE(base_thread->get_global_tag(0) == std::string("test:migration")); + REQUIRE(base_thread->get_global_tag(1) == std::string("flavor:base")); + REQUIRE(base_thread->num_knot_tags() == 1); + REQUIRE(base_thread->get_knot_tag(0) == std::string("knot:Main")); + base_thread->choose(0); + std::unique_ptr snap{base_thread->create_snapshot()}; + REQUIRE(snap->can_be_migrated()); + globals new_globals = new_story->new_globals_from_snapshot(*snap); + runner new_thread = new_story->new_runner_from_snapshot(*snap, new_globals); + THEN("Got new global/knot tags") + { + REQUIRE(content == "A\n0 -1 0\nThis is a simple story.\n"); + REQUIRE(new_thread->num_global_tags() == 2); + REQUIRE(new_thread->get_global_tag(0) == std::string("test:migration")); + REQUIRE(new_thread->get_global_tag(1) == std::string("flavor:changed")); + REQUIRE(new_thread->num_knot_tags() == 1); + REQUIRE(new_thread->get_knot_tag(0) == std::string("knot:different")); + } + } + } +} diff --git a/inkcpp_test/ink/MigrationBase.ink b/inkcpp_test/ink/MigrationBase.ink new file mode 100644 index 00000000..21f6ab51 --- /dev/null +++ b/inkcpp_test/ink/MigrationBase.ink @@ -0,0 +1,26 @@ +# test:migration +# flavor:base + +VAR do_not_migrate = 10 +VAR do_migrate = 15 +->Node1 +=== OldNode +O +-> Node1 +=== Node1 +A +-> Main +=== Main +# knot:Main +~ temp tKeep = 2 +~ temp tOld = 3 +{TURNS_SINCE(-> Node1)} {TURNS_SINCE(-> OldNode)} {TURNS_SINCE(-> Main)} +This is a simple story. +~ tKeep = 5 +* A +* B +- catch +{tKeep} {tOld} +{TURNS_SINCE(-> Node1)} {TURNS_SINCE(-> OldNode)} {TURNS_SINCE(-> Main)} +Oh. +->DONE diff --git a/inkcpp_test/ink/MigrationChangeGlobals.ink b/inkcpp_test/ink/MigrationChangeGlobals.ink new file mode 100644 index 00000000..b0174ea7 --- /dev/null +++ b/inkcpp_test/ink/MigrationChangeGlobals.ink @@ -0,0 +1,18 @@ +VAR do_migrate = 10 +VAR new_var = 20 +->Node1 +=== OldNode +O +-> Node1 +=== Node1 +A +-> Main +=== Main +{TURNS_SINCE(-> Node1)} {TURNS_SINCE(-> OldNode)} {TURNS_SINCE(-> Main)} +This is a simple story. +* A +* B +- catch +{TURNS_SINCE(-> Node1)} {TURNS_SINCE(-> OldNode)} {TURNS_SINCE(-> Main)} +Oh. +->DONE diff --git a/inkcpp_test/ink/MigrationChangeNodes.ink b/inkcpp_test/ink/MigrationChangeNodes.ink new file mode 100644 index 00000000..62c7ca4e --- /dev/null +++ b/inkcpp_test/ink/MigrationChangeNodes.ink @@ -0,0 +1,18 @@ +VAR do_not_migrate = 10 +VAR do_migrate = 15 +->NewNode +=== Node1 +A +-> Main +=== NewNode +B +-> Main +=== Main +{TURNS_SINCE(-> Node1)} {TURNS_SINCE(-> NewNode)} {TURNS_SINCE(-> Main)} +This is a simple story. +* A +* B +- catch +{TURNS_SINCE(-> Node1)} {TURNS_SINCE(-> NewNode)} {TURNS_SINCE(-> Main)} +Oh. +->DONE diff --git a/shared/public/system.h b/shared/public/system.h index 7b996272..937008c3 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -52,15 +52,6 @@ typedef uint32_t hash_t; /** Invalid hash value */ const hash_t InvalidHash = 0; -#ifdef INK_ENABLE_UNREAL -/** Simple hash for serialization of strings */ -inline hash_t hash_string(const char* string) -{ - return CityHash32(string, FCStringAnsi::Strlen(string)); -} -#else -hash_t hash_string(const char* string); -#endif /** Byte type */ typedef unsigned char byte_t; @@ -95,6 +86,17 @@ constexpr list_flag null_flag{-1, -1}; /** value representing an empty list */ constexpr list_flag empty_flag{-1, 0}; +#ifdef INK_ENABLE_UNREAL +/** Simple hash for serialization of strings */ +inline hash_t hash_string(const char* string) +{ + return CityHash32(string, FCStringAnsi::Strlen(string)); +} +#else +hash_t hash_string(const char* string); +hash_t hash_data(const unsigned char* data, size_t len); +#endif + namespace internal { #ifdef __GNUC__ From bb99e12cacd9b833fcb88f31a0cf45126ceb0b28 Mon Sep 17 00:00:00 2001 From: Harry Rose Date: Sat, 13 Dec 2025 15:15:29 +0000 Subject: [PATCH 08/24] Add option for inkcpp to compile without exceptions --- CMakeLists.txt | 2 ++ shared/public/system.h | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5764136d..2953515f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,8 @@ set(INKCPP_INKLECATE set_property(CACHE INKCPP_INKLECATE PROPERTY STRINGS "NONE" "OS" "ALL") option(INKCPP_NO_RTTI "Disable real time type information depended code. Used to build without RTTI." OFF) +option(INKCPP_NO_EXCEPTIONS + "Used to build without support for exceptions." OFF) option(INKCPP_NO_EXCEPTIONS "Used to build without support for exceptions, disables try/catch blocks and throws" OFF) option(INKCPP_NO_STD "Disables the use of C(++) std libs." OFF) diff --git a/shared/public/system.h b/shared/public/system.h index e9522079..a04fbbe3 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -231,14 +231,18 @@ void ink_assert(bool condition, const char* msg = nullptr, Args... args) size_t size = snprintf(nullptr, 0, msg, args...) + 1; char* message = static_cast(malloc(size)); snprintf(message, size, msg, args...); +#ifdef INK_ENABLE_EXCEPTIONS msg = message; +#else + fprintf(stderr, "Ink Assert: %s\n", message); + abort(); +#endif } -# ifdef INK_ENABLE_EXCEPTIONS throw ink_exception(msg); -# elif defined(INK_ENABLE_CSTD) fprintf(stderr, "Ink Assert: %s\n", msg); abort(); +#endif # else # error "This path needs a way to warn and then terminate, otherwise it'll silently fail" # endif From 2add7d304250f88d0333329e0d655db2f6762a39 Mon Sep 17 00:00:00 2001 From: Harry Rose Date: Sat, 13 Dec 2025 15:23:05 +0000 Subject: [PATCH 09/24] Run clang-format --- shared/public/system.h | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/shared/public/system.h b/shared/public/system.h index a04fbbe3..60f0fb2c 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -231,18 +231,14 @@ void ink_assert(bool condition, const char* msg = nullptr, Args... args) size_t size = snprintf(nullptr, 0, msg, args...) + 1; char* message = static_cast(malloc(size)); snprintf(message, size, msg, args...); -#ifdef INK_ENABLE_EXCEPTIONS msg = message; -#else - fprintf(stderr, "Ink Assert: %s\n", message); - abort(); -#endif } - +#ifdef INK_ENABLE_EXCEPTIONS throw ink_exception(msg); +#else fprintf(stderr, "Ink Assert: %s\n", msg); abort(); -#endif +# endif # else # error "This path needs a way to warn and then terminate, otherwise it'll silently fail" # endif From c7ed6a3bcd0e608ae9a9106901685e90bdb25a21 Mon Sep 17 00:00:00 2001 From: Harry Rose Date: Sun, 14 Dec 2025 13:16:32 +0000 Subject: [PATCH 10/24] Combine INK_ENABLE_EH and INK_ENABLE_EXCEPTIONS defines/options --- CMakeLists.txt | 6 ++---- shared/public/config.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2953515f..1c1448ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,10 +67,8 @@ set(INKCPP_INKLECATE set_property(CACHE INKCPP_INKLECATE PROPERTY STRINGS "NONE" "OS" "ALL") option(INKCPP_NO_RTTI "Disable real time type information depended code. Used to build without RTTI." OFF) -option(INKCPP_NO_EXCEPTIONS - "Used to build without support for exceptions." OFF) -option(INKCPP_NO_EXCEPTIONS - "Used to build without support for exceptions, disables try/catch blocks and throws" OFF) +option(INKCPP_NO_EXCEPTIONS "Used to build without support for exceptions, disables try/catch blocks and throws" OFF) + option(INKCPP_NO_STD "Disables the use of C(++) std libs." OFF) if(INKCPP_NO_RTTI) diff --git a/shared/public/config.h b/shared/public/config.h index 76d58f42..b67f43f7 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -19,7 +19,7 @@ # ifndef INKCPP_NO_STD # define INK_ENABLE_STL # define INK_ENABLE_CSTD -# endif +#endif #endif #ifndef INKCPP_NO_RTTI From d804139ca3ec94857a3ae7783570faa1decc0a1f Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 10 Dec 2025 11:14:21 +0100 Subject: [PATCH 11/24] fix(STL): guard references to STL and CSTD behind compile options + disable C++STL, EH and RTTI for CLIB + add c bindnings for story::from_binary and snapshot::from_binary + add compile option INKCPP_NO_STL to generate more standalone lib files --- CMakeLists.txt | 2 - inkcpp/CMakeLists.txt | 114 ++++++++++-------- inkcpp/include/list.h | 4 +- inkcpp/include/runner.h | 2 - inkcpp/include/story.h | 132 ++++++++++---------- inkcpp/runner_impl.cpp | 12 +- inkcpp/runner_impl.h | 2 - inkcpp/story_impl.cpp | 10 +- inkcpp/story_impl.h | 6 +- inkcpp/string_utils.h | 17 +-- inkcpp/value.h | 6 +- inkcpp_c/CMakeLists.txt | 108 +++++++++-------- inkcpp_c/include/inkcpp.h | 29 ++++- inkcpp_c/inkcpp.cpp | 100 +++++++++++----- inkcpp_cl/CMakeLists.txt | 87 ++++++++------ inkcpp_compiler/json_compiler.cpp | 4 +- inkcpp_test/CMakeLists.txt | 193 ++++++++++++++++-------------- shared/public/system.h | 36 +++--- 18 files changed, 487 insertions(+), 377 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c1448ac..d4e6a359 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,8 +67,6 @@ set(INKCPP_INKLECATE set_property(CACHE INKCPP_INKLECATE PROPERTY STRINGS "NONE" "OS" "ALL") option(INKCPP_NO_RTTI "Disable real time type information depended code. Used to build without RTTI." OFF) -option(INKCPP_NO_EXCEPTIONS "Used to build without support for exceptions, disables try/catch blocks and throws" OFF) - option(INKCPP_NO_STD "Disables the use of C(++) std libs." OFF) if(INKCPP_NO_RTTI) diff --git a/inkcpp/CMakeLists.txt b/inkcpp/CMakeLists.txt index 7c9ce0da..1785fdc2 100644 --- a/inkcpp/CMakeLists.txt +++ b/inkcpp/CMakeLists.txt @@ -1,73 +1,89 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) -list(APPEND SOURCES - array.h - choice.cpp - functional.cpp - functions.h functions.cpp - globals_impl.h globals_impl.cpp - output.h output.cpp - platform.h - runner_impl.h runner_impl.cpp - simple_restorable_stack.h stack.h stack.cpp - story_impl.h story_impl.cpp - snapshot_impl.h snapshot_impl.cpp snapshot_interface.h - story_ptr.cpp - system.cpp - value.h value.cpp +list( + APPEND + SOURCES + array.h + choice.cpp + functional.cpp + functions.h + functions.cpp + globals_impl.h + globals_impl.cpp + output.h + output.cpp + platform.h + runner_impl.h + runner_impl.cpp + simple_restorable_stack.h + stack.h + stack.cpp + story_impl.h + story_impl.cpp + snapshot_impl.h + snapshot_impl.cpp + snapshot_interface.h + story_ptr.cpp + system.cpp + value.h + value.cpp tuple.hpp - string_table.h string_table.cpp - list_table.h list_table.cpp - list_impl.h list_impl.cpp - operations.h operation_bases.h - list_operations.h list_operations.cpp - container_operations.h container_operations.cpp - numeric_operations.h numeric_operations.cpp - string_operations.h string_operations.cpp + string_table.h + string_table.cpp + list_table.h + list_table.cpp + list_impl.h + list_impl.cpp + operations.h + operation_bases.h + list_operations.h + list_operations.cpp + container_operations.h + container_operations.cpp + numeric_operations.h + numeric_operations.cpp + string_operations.h + string_operations.cpp string_operations.cpp numeric_operations.cpp casting.h executioner.h string_utils.h header.cpp - random.h -) -list(APPEND COLLECTION_SOURCES - collections/restorable.h - collections/restorable.cpp -) -FILE(GLOB PUBLIC_HEADERS "include/*") + random.h) +list(APPEND COLLECTION_SOURCES collections/restorable.h collections/restorable.cpp) +file(GLOB PUBLIC_HEADERS "include/*") source_group(Collections REGULAR_EXPRESSION collections/.*) add_library(inkcpp_o OBJECT ${SOURCES} ${COLLECTION_SOURCES}) -target_include_directories(inkcpp_o PUBLIC - $ - $ -) -add_library(inkcpp $) -target_include_directories(inkcpp PUBLIC - $ - $ -) +target_include_directories(inkcpp_o PUBLIC $ + $) +target_compile_definitions(inkcpp_o PRIVATE INKCPP_BUILD_CLIB INKCPP_NO_EXCEPTIONS INKCPP_NO_RTTI) +add_library(inkcpp ${SOURCES} ${COLLECTION_SOURCES}) +target_include_directories(inkcpp PUBLIC $ + $) set_target_properties(inkcpp PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") -# Make sure the include directory is included +# Make sure the include directory is included target_link_libraries(inkcpp_o PUBLIC inkcpp_shared) target_link_libraries(inkcpp PUBLIC inkcpp_shared) # Make sure this project and all dependencies use the C++17 standard target_compile_features(inkcpp PUBLIC cxx_std_17) - # Unreal installation list(REMOVE_ITEM SOURCES "avl_array.h") -configure_file("avl_array.h" "${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/ThirdParty/Private/avl_array.h" COPYONLY) -foreach(FILE IN LISTS SOURCES) - configure_file("${FILE}" "${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/inkcpp/Private/ink/${FILE}" COPYONLY) +configure_file("avl_array.h" + "${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/ThirdParty/Private/avl_array.h" COPYONLY) +foreach(file IN LISTS SOURCES) + configure_file("${file}" "${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/inkcpp/Private/ink/${file}" + COPYONLY) endforeach() -foreach(FILE IN LISTS PUBLIC_HEADERS) - get_filename_component(FILE "${FILE}" NAME) - configure_file("include/${FILE}" "${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/inkcpp/Public/ink/${FILE}" COPYONLY) +foreach(file IN LISTS PUBLIC_HEADERS) + get_filename_component(file "${file}" NAME) + configure_file("include/${file}" + "${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/inkcpp/Public/ink/${FILE}" COPYONLY) endforeach() -foreach(FILE IN LISTS COLLECTION_SOURCES) - configure_file("${FILE}" "${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/inkcpp/Private/ink/${FILE}" COPYONLY) +foreach(file IN LISTS COLLECTION_SOURCES) + configure_file("${file}" "${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/inkcpp/Private/ink/${file}" + COPYONLY) endforeach() diff --git a/inkcpp/include/list.h b/inkcpp/include/list.h index 910a94f2..7a3ee7ee 100644 --- a/inkcpp/include/list.h +++ b/inkcpp/include/list.h @@ -12,7 +12,7 @@ # include #endif -#ifdef INK_BUILD_CLIB +#ifdef INKCPP_BUILD_CLIB struct InkListIter; struct HInkList; int ink_list_flags(const HInkList*, InkListIter*); @@ -61,7 +61,7 @@ class list_interface int _i; bool _one_list_iterator; ///< iterates only though values of one list friend list_interface; -#ifdef INK_BUILD_CLIB +#ifdef INKCPP_BUILD_CLIB friend int ::ink_list_flags(const HInkList*, InkListIter*); friend int ::ink_list_flags_from(const HInkList*, const char*, InkListIter*); friend int ::ink_list_iter_next(InkListIter* self); diff --git a/inkcpp/include/runner.h b/inkcpp/include/runner.h index f9a647b4..c48a8797 100644 --- a/inkcpp/include/runner.h +++ b/inkcpp/include/runner.h @@ -83,7 +83,6 @@ class runner_interface */ virtual snapshot* create_snapshot() const = 0; -#ifdef INK_ENABLE_CSTD /** * Continue execution until the next newline, then allocate a c-style * string with the output. This allocated string is managed by the runtime @@ -93,7 +92,6 @@ class runner_interface * @return allocated c-style string with the output of a single line of execution */ virtual const char* getline_alloc() = 0; -#endif #if defined(INK_ENABLE_STL) || defined(INK_ENABLE_UNREAL) /** diff --git a/inkcpp/include/story.h b/inkcpp/include/story.h index cc696fb7..06591779 100644 --- a/inkcpp/include/story.h +++ b/inkcpp/include/story.h @@ -27,79 +27,79 @@ class story public: virtual ~story(){}; #pragma region Interface Methods - /** - * Creates a new global store - * - * Creates a new global store that can be passed in when - * creating new runners for this story. Note: Can not be - * used for other stories. It is tied to this story. - * - * @return managed pointer to a new global store - */ - virtual globals new_globals() = 0; - /** Reconstructs globals from snapshot - * @param obj snapshot to load - */ - virtual globals new_globals_from_snapshot(const snapshot& obj) = 0; + /** + * Creates a new global store + * + * Creates a new global store that can be passed in when + * creating new runners for this story. Note: Can not be + * used for other stories. It is tied to this story. + * + * @return managed pointer to a new global store + */ + virtual globals new_globals() = 0; + /** Reconstructs globals from snapshot + * @param obj snapshot to load + */ + virtual globals new_globals_from_snapshot(const snapshot& obj) = 0; - /** - * Creates a new runner - * - * Creates a new runner whose initial instruction pointer - * is the first instruction in this story. If no global - * store is passed, a new one will be created for the runner. - * - * @param store globals to use for the runner - * @return managed pointer to a new runner - */ - virtual runner new_runner(globals store = nullptr) = 0; - /** - * @brief reconstruct runner from a snapshot - * @attention runner must be snap_shotted from the same story - * @attention if globals is explicit set, - * make sure the globals are from the same snapshot as - * @attention if you snap_shotted a multiple runner with shared global - * please reconstruct it in the same fashion - * @param obj - * @param store can be set if explicit access to globals is required or multiple runner with a - * shared global are used - * @param runner_id if the snapshot was of a multiple runner one global situation load first the - * global, and then each runner with global set and increasing idx - */ - virtual runner new_runner_from_snapshot( - const snapshot& obj, globals store = nullptr, unsigned runner_id = 0 - ) = 0; + /** + * Creates a new runner + * + * Creates a new runner whose initial instruction pointer + * is the first instruction in this story. If no global + * store is passed, a new one will be created for the runner. + * + * @param store globals to use for the runner + * @return managed pointer to a new runner + */ + virtual runner new_runner(globals store = nullptr) = 0; + /** + * @brief reconstruct runner from a snapshot + * @attention runner must be snap_shotted from the same story + * @attention if globals is explicit set, + * make sure the globals are from the same snapshot as + * @attention if you snap_shotted a multiple runner with shared global + * please reconstruct it in the same fashion + * @param obj + * @param store can be set if explicit access to globals is required or multiple runner with a + * shared global are used + * @param runner_id if the snapshot was of a multiple runner one global situation load first the + * global, and then each runner with global set and increasing idx + */ + virtual runner + new_runner_from_snapshot(const snapshot& obj, globals store = nullptr, unsigned runner_id = 0) + = 0; #pragma endregion #pragma region Factory Methods - /** - * Creates a new story object from a file. - * - * Requires STL or other extension which allows files - * to be loaded and read. Will allocate all the data - * necessary to load the file and close it. - * - * @param filename filename of the binary ink data - * @return new story object - */ - static story* from_file(const char* filename); + /** + * Creates a new story object from a file. + * + * Requires STL or other extension which allows files + * to be loaded and read. Will allocate all the data + * necessary to load the file and close it. + * + * @param filename filename of the binary ink data + * @return new story object + */ + static story* from_file(const char* filename); - /** - * Create a new story object from binary buffer - * - * No extensions required. Creates the story from binary - * data already loaded into memory. By default, the story - * will free this buffer when it is destroyed. - * - * @param data binary data - * @param length of the binary data in bytes - * @param freeOnDestroy if true, free this buffer once the story is destroyed - * @return new story object - */ - static story* from_binary(unsigned char* data, size_t length, bool freeOnDestroy = true); + /** + * Create a new story object from binary buffer + * + * No extensions required. Creates the story from binary + * data already loaded into memory. By default, the story + * will free this buffer when it is destroyed. + * + * @param data binary data + * @param length of the binary data in bytes + * @param freeOnDestroy if true, free this buffer once the story is destroyed + * @return new story object + */ + static story* from_binary(const unsigned char* data, size_t length, bool freeOnDestroy = true); #pragma endregion }; -} +} // namespace ink::runtime /** @namespace ink * Namespace contaning all modules and classes from InkCPP diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 8c459096..68d389e4 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -15,11 +15,12 @@ #include "system.h" #include "value.h" -#include -#include +#ifdef INK_ENABLE_STL +# include namespace ink::runtime { +#ifdef INK_ENABLE_STL static void write_hash(std::ostream& out, ink::hash_t value) { using namespace std; @@ -28,6 +29,7 @@ static void write_hash(std::ostream& out, ink::hash_t value) out << "0x" << hex << setfill('0') << setw(8) << static_cast(value); out.flags(state); } +#endif const choice* runner_interface::get_choice(size_t index) const { @@ -472,7 +474,11 @@ runner_impl::runner_impl(const story_impl* data, globals global) , _choices() , _tags_begin(0, ~0) , _container(ContainerData{}) +#ifdef INK_ENABLE_CSTD , _rng(static_cast(time(NULL))) +#else + , _rng() +#endif { @@ -711,7 +717,6 @@ const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& l return ptr; } -#ifdef INK_ENABLE_CSTD const char* runner_impl::getline_alloc() { advance_line(); @@ -722,7 +727,6 @@ const char* runner_impl::getline_alloc() inkAssert(_output.is_empty(), "Output should be empty after getline!"); return res; } -#endif bool runner_impl::move_to(hash_t path) { diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 0d5359a3..aed0c91e 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -124,10 +124,8 @@ class runner_impl size_t snap(unsigned char* data, snapper&) const; const unsigned char* snap_load(const unsigned char* data, loader&); -#ifdef INK_ENABLE_CSTD // c-style getline virtual const char* getline_alloc() override; -#endif // move to path virtual bool move_to(hash_t path) override; diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index 3f58d42f..becc06f5 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -19,7 +19,7 @@ namespace ink::runtime story* story::from_file(const char* filename) { return new internal::story_impl(filename); } #endif -story* story::from_binary(unsigned char* data, size_t length, bool freeOnDestroy) +story* story::from_binary(const unsigned char* data, size_t length, bool freeOnDestroy) { return new internal::story_impl(data, length, freeOnDestroy); } @@ -35,9 +35,7 @@ unsigned char* read_file_into_memory(const char* filename, size_t* read) ifstream ifs(filename, ios::binary | ios::ate); - if (! ifs.is_open()) { - ink_assert(false, "Failed to open file: %s", filename); - } + inkAssert(ifs.is_open(), "Failed to open file: " FORMAT_STRING_STR, filename); ifstream::pos_type pos = ifs.tellg(); size_t length = ( size_t ) pos; @@ -69,7 +67,7 @@ story_impl::story_impl(const char* filename) } #endif -story_impl::story_impl(unsigned char* binary, size_t len, bool manage /*= true*/) +story_impl::story_impl(const unsigned char* binary, size_t len, bool manage /*= true*/) : _file(binary) , _length(len) , _managed(manage) @@ -269,7 +267,7 @@ runner story_impl::new_runner_from_snapshot(const snapshot& data, globals store, void story_impl::setup_pointers() { using header = ink::internal::header; - _header = header::parse_header(reinterpret_cast(_file)); + _header = header::parse_header(reinterpret_cast(_file)); // String table is after the header _string_table = ( char* ) _file + header::Size; diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index e8cc1217..83dd37cb 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -25,7 +25,7 @@ class story_impl : public story #endif // Create story from allocated binary data in memory. If manage is true, this class will delete // the pointers on destruction - story_impl(unsigned char* binary, size_t len, bool manage = true); + story_impl(const unsigned char* binary, size_t len, bool manage = true); virtual ~story_impl(); const char* string(uint32_t index) const; @@ -65,8 +65,8 @@ class story_impl : public story private: // file information - unsigned char* _file; - size_t _length; + const unsigned char* _file; + size_t _length; ink::internal::header _header; diff --git a/inkcpp/string_utils.h b/inkcpp/string_utils.h index 295389a0..7f056fe1 100644 --- a/inkcpp/string_utils.h +++ b/inkcpp/string_utils.h @@ -10,8 +10,6 @@ #include "traits.h" #include "value.h" -#include - #ifndef EINVAL # define EINVAL -1 #endif @@ -58,13 +56,6 @@ inline int toStr(char* buffer, size_t size, int32_t value) // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/gcvt-s?view=msvc-160 inline int toStr(char* buffer, size_t size, float value) { -#ifdef WIN32 - int digits = 7; - for (float f = value; f > 1.f; f /= 10.f) { - ++digits; - } - int ec = _gcvt_s(buffer, size, value, digits); // number of significant digits -#else if (buffer == nullptr || size < 1) { return EINVAL; } @@ -73,8 +64,7 @@ inline int toStr(char* buffer, size_t size, float value) return EINVAL; } // trunc cat zeros B007 - int ec = 0; -#endif + int ec = 0; char* itr = buffer + strlen(buffer) - 1; while (*itr == '0') { *itr-- = 0; @@ -185,6 +175,11 @@ inline constexpr const char* str_find(const char* str, char c) return nullptr; } +inline constexpr bool isspace(int c) +{ + return c == ' ' || c == '\t' || c == '\v' || c == '\n' || c == '\f' || c == '\r'; +} + /** removes leading & tailing spaces as wide spaces * @param begin iterator of string * @param end iterator of string diff --git a/inkcpp/value.h b/inkcpp/value.h index aef705da..38995dc2 100644 --- a/inkcpp/value.h +++ b/inkcpp/value.h @@ -14,7 +14,6 @@ #include "system.h" #include "command.h" #include "list_table.h" -#include "snapshot_impl.h" #include "tuple.hpp" #include "types.h" @@ -185,8 +184,9 @@ class value : public snapshot_interface } else if (ty != type()) { return redefine(oth, env); } else { - return internal::redefine::type, tuple>(env - )(get(), oth.get()); + return internal::redefine::type, tuple>(env)( + get(), oth.get() + ); } } diff --git a/inkcpp_c/CMakeLists.txt b/inkcpp_c/CMakeLists.txt index 49f5bbbc..0d3c0704 100644 --- a/inkcpp_c/CMakeLists.txt +++ b/inkcpp_c/CMakeLists.txt @@ -1,62 +1,66 @@ -add_library(inkcpp_c inkcpp.cpp - $ - $ ) -target_include_directories(inkcpp_c PUBLIC - $ - $ -) -target_include_directories(inkcpp_c PUBLIC - $ - $ - $ - $ -) +add_library(inkcpp_c inkcpp.cpp $) +target_include_directories(inkcpp_c PUBLIC $ + $) +target_include_directories( + inkcpp_c + PUBLIC $ + $ $) set_target_properties(inkcpp_c PROPERTIES PUBLIC_HEADER "include/inkcpp.h") target_link_libraries(inkcpp_c PRIVATE inkcpp_shared) -target_compile_definitions(inkcpp_c PRIVATE INK_BUILD_CLIB) +target_compile_definitions(inkcpp_c PRIVATE INKCPP_BUILD_CLIB INKCPP_NO_EXCEPTIONS INKCPP_NO_RTTI) -install(TARGETS inkcpp_c - EXPORT inkcppTarget - ARCHIVE DESTINATION "lib/ink" - COMPONENT lib EXCLUDE_FROM_ALL - PUBLIC_HEADER DESTINATION "include/ink/c" - COMPONENT lib EXCLUDE_FROM_ALL -) +install( + TARGETS inkcpp_c + EXPORT inkcppTarget + ARCHIVE DESTINATION "lib/ink" + COMPONENT lib + EXCLUDE_FROM_ALL + PUBLIC_HEADER + DESTINATION "include/ink/c" + COMPONENT lib + EXCLUDE_FROM_ALL) -install(TARGETS inkcpp_c inkcpp_shared - EXPORT inkcpp_cTarget - ARCHIVE DESTINATION "lib/ink" - COMPONENT clib EXCLUDE_FROM_ALL - PUBLIC_HEADER DESTINATION "include/ink/" - COMPONENT clib EXCLUDE_FROM_ALL) +install( + TARGETS inkcpp_c inkcpp_shared + EXPORT inkcpp_cTarget + ARCHIVE DESTINATION "lib/ink" + COMPONENT clib + EXCLUDE_FROM_ALL + PUBLIC_HEADER + DESTINATION "include/ink/" + COMPONENT clib + EXCLUDE_FROM_ALL) include(CMakePackageConfigHelpers) -configure_package_config_file(${PROJECT_SOURCE_DIR}/Config.cmake.in - "${CMAKE_CURRENT_BINARY_DIR}/inkcppConfig.cmake" - INSTALL_DESTINATION "lib/cmake/inkcpp" - NO_SET_AND_CHECK_MACRO - NO_CHECK_REQUIRED_COMPONENTS_MACRO) +configure_package_config_file( + ${PROJECT_SOURCE_DIR}/Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/inkcppConfig.cmake" + INSTALL_DESTINATION "lib/cmake/inkcpp" + NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO) write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/inkcppConfigVersion.cmake" - VERSION "${inkcpp_VERSION_MAJOR}.${inkcpp_VERSION_MINOR}" - COMPATIBILITY AnyNewerVersion) -install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/inkcppConfig.cmake - ${CMAKE_CURRENT_BINARY_DIR}/inkcppConfigVersion.cmake - DESTINATION lib/cmake/inkcpp COMPONENT clib EXCLUDE_FROM_ALL) -export(EXPORT inkcpp_cTarget - FILE "${CMAKE_CURRENT_BINARY_DIR}/inkcppTargets.cmake") -install(EXPORT inkcpp_cTarget - FILE inkcppTargets.cmake DESTINATION "lib/cmake/inkcpp" - COMPONENT clib EXCLUDE_FROM_ALL) + "${CMAKE_CURRENT_BINARY_DIR}/inkcppConfigVersion.cmake" + VERSION "${inkcpp_VERSION_MAJOR}.${inkcpp_VERSION_MINOR}" + COMPATIBILITY AnyNewerVersion) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/inkcppConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/inkcppConfigVersion.cmake + DESTINATION lib/cmake/inkcpp + COMPONENT clib + EXCLUDE_FROM_ALL) +export(EXPORT inkcpp_cTarget FILE "${CMAKE_CURRENT_BINARY_DIR}/inkcppTargets.cmake") +install( + EXPORT inkcpp_cTarget + FILE inkcppTargets.cmake + DESTINATION "lib/cmake/inkcpp" + COMPONENT clib + EXCLUDE_FROM_ALL) # configure in two steps to get the current installation prefix set(PREFIX "@PREFIX@") -configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/inkcpp_c.pc.in - ${CMAKE_BINARY_DIR}/inkcpp_c.pc.in - @ONLY) -install(CODE [[ - get_filename_component(PREFIX ${CMAKE_INSTALL_PREFIX} ABSOLUTE) - configure_file(inkcpp_c.pc.in ${PREFIX}/lib/pkgconfig/inkcpp.pc @ONLY) - ]] COMPONENT clib EXCLUDE_FROM_ALL) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/inkcpp_c.pc.in ${CMAKE_BINARY_DIR}/inkcpp_c.pc.in @ONLY) +install( + CODE [[ + get_filename_component(PREFIX ${CMAKE_INSTALL_PREFIX} ABSOLUTE) + configure_file(inkcpp_c.pc.in ${PREFIX}/lib/pkgconfig/inkcpp.pc @ONLY) + ]] + COMPONENT clib + EXCLUDE_FROM_ALL) diff --git a/inkcpp_c/include/inkcpp.h b/inkcpp_c/include/inkcpp.h index eb16edd4..7725de87 100644 --- a/inkcpp_c/include/inkcpp.h +++ b/inkcpp_c/include/inkcpp.h @@ -67,18 +67,35 @@ typedef struct HInkSTory HInkStory; struct HInkSnapshot; /** @memberof HInkSnapshot * @copydoc ink::runtime::snapshot::from_file() + * @attention only supported if build with STL libraries. + * @sa ink_snapshot_from_binary() */ HInkSnapshot* ink_snapshot_from_file(const char* filename); + /** @memberof HInkSnapshot + * @copydoc ink::runtime::snapshot::from_binary() + */ + HInkSnapshot* + ink_snapshot_from_binary(const unsigned char* data, size_t length, bool freeOnDestroy); /** @memberof HInkSnapshot * @copydoc ink::runtime::snapshot::num_runners() * @param self */ - int ink_snapshot_num_runners(const HInkSnapshot* self); + int ink_snapshot_num_runners(const HInkSnapshot* self); /** @memberof HInkSnapshot * @copydoc ink::runtime::snapshot::write_to_file() * @param self + * @attention only supported if build with STL libraries. + * @sa ink_snapshot_get_binary() + */ + void ink_snapshot_write_to_file(const HInkSnapshot* self, const char* filename); + /** @memberof HInkSnapshot + * @param self + * @param[out] data pointer to snapshot data + * @param[out] data_length length of snapshot data */ - void ink_snapshot_write_to_file(const HInkSnapshot* self, const char* filename); + void ink_snapshot_get_binary( + const HInkSnapshot* self, const unsigned char** data, size_t* data_length + ); /** @class HInkChoice * @ingroup clib @@ -381,9 +398,15 @@ typedef struct HInkSTory HInkStory; */ struct HInkStory; /** @memberof HInkStory - * @copydoc ink::runtime::story::from_file + * @copydoc ink::runtime::story::from_file() + * @attention only supported if build with STL libraries. + * @sa ink_story_from_binary() */ HInkStory* ink_story_from_file(const char* filename); + /** @memberof HInkStory + * @copydoc ink::runtime::story::from_binary() + */ + HInkStory* ink_story_from_binary(const unsigned char* data, size_t length, bool freeOnDestroy); /** @memberof HInkStory * deletes a story and all assoziated resources * @param self diff --git a/inkcpp_c/inkcpp.cpp b/inkcpp_c/inkcpp.cpp index c12ea018..d0073e02 100644 --- a/inkcpp_c/inkcpp.cpp +++ b/inkcpp_c/inkcpp.cpp @@ -1,17 +1,21 @@ #include "inkcpp.h" +#include "include/inkcpp.h" #include "list.h" #include "system.h" #include "types.h" -#include +#ifdef INK_ENABLE_CSTD +# include +# include +# include +#endif + -#include #include #include #include #include #include -#include using namespace ink::runtime; @@ -64,19 +68,78 @@ value inkvar_from_c(InkValue& val) } extern "C" { - HInkSnapshot* ink_snapshot_from_file(const char* filename) +#ifdef INK_ENABLE_CSTD + HInkStory* ink_story_from_file(const char* filename) { - return reinterpret_cast(snapshot::from_file(filename)); + FILE* file = fopen(filename, "rb"); + fseek(file, 0, SEEK_END); + long file_length = ftell(file); + fseek(file, 0, SEEK_SET); + unsigned char* data = static_cast(malloc(file_length)); + inkAssert(data, "Malloc of size %u failed", file_length); + unsigned length = fread_s(data, file_length, 1, file_length, file); + inkAssert( + file_length == length, "Expected to read file of size %u, but only read %u", file_length, + length + ); + fclose(file); + return reinterpret_cast(story::from_binary(data, file_length)); } - int ink_snapshot_num_runners(const HInkSnapshot* self) + HInkSnapshot* ink_snapshot_from_file(const char* filename) { - return reinterpret_cast(self)->num_runners(); + FILE* file = fopen(filename, "rb"); + fseek(file, 0, SEEK_END); + long file_length = ftell(file); + fseek(file, 0, SEEK_SET); + unsigned char* data = static_cast(malloc(file_length)); + inkAssert(data, "Malloc of size %u failed", file_length); + unsigned length = fread_s(data, file_length, sizeof(unsigned char), file_length, file); + inkAssert( + file_length == length, "Expected to read file of size %u, but only read %u", file_length, + length + ); + fclose(file); + return reinterpret_cast(snapshot::from_binary(data, file_length)); } void ink_snapshot_write_to_file(const HInkSnapshot* self, const char* filename) { - reinterpret_cast(self)->write_to_file(filename); + FILE* file = fopen(filename, "wb"); + const snapshot& snap = *reinterpret_cast(self); + unsigned length = fwrite(snap.get_data(), sizeof(unsigned char), snap.get_data_len(), file); + inkAssert( + length == snap.get_data_len(), + "Snapshot write failed, snapshot of size %u, but only %u bytes where written.", + snap.get_data_len(), length + ); + fclose(file); + } +#endif + + HInkStory* ink_story_from_binary(const unsigned char* data, size_t length, bool freeOnDestroy) + { + return reinterpret_cast(story::from_binary(data, length, freeOnDestroy)); + } + + HInkSnapshot* + ink_snapshot_from_binary(const unsigned char* data, size_t length, bool freeOnDestroy) + { + return reinterpret_cast(snapshot::from_binary(data, length, freeOnDestroy)); + } + + void ink_snapshot_get_binary( + const HInkSnapshot* self, const unsigned char** data, size_t* data_length + ) + { + const snapshot& snap = *reinterpret_cast(self); + *data = snap.get_data(); + *data_length = snap.get_data_len(); + } + + int ink_snapshot_num_runners(const HInkSnapshot* self) + { + return reinterpret_cast(self)->num_runners(); } const char* ink_choice_text(const HInkChoice* self) @@ -303,11 +366,6 @@ extern "C" { return reinterpret_cast(self)->get()->set(variable_name, inkvar_from_c(val)); } - HInkStory* ink_story_from_file(const char* filename) - { - return reinterpret_cast(story::from_file(filename)); - } - void ink_story_delete(HInkStory* self) { delete reinterpret_cast(self); } HInkRunner* ink_story_new_runner(HInkStory* self, HInkGlobals* global_store) @@ -349,20 +407,4 @@ extern "C" { )) ); } - - void ink_compile_json(const char* input_filename, const char* output_filename, const char** error) - { - ink::compiler::compilation_results result; - ink::compiler::run(input_filename, output_filename, &result); - if (error != nullptr && ! result.errors.empty() || ! result.warnings.empty()) { - std::string str{}; - for (auto& warn : result.warnings) { - str += "WARNING: " + warn + '\n'; - } - for (auto& err : result.errors) { - str += "ERROR: " + err + '\n'; - } - *error = strdup(str.c_str()); - } - } } diff --git a/inkcpp_cl/CMakeLists.txt b/inkcpp_cl/CMakeLists.txt index eea6a759..f10455ad 100644 --- a/inkcpp_cl/CMakeLists.txt +++ b/inkcpp_cl/CMakeLists.txt @@ -1,4 +1,8 @@ # Create executable +if(INKCPP_NO_STL) + message(WARNING "Will not build CL because") + return() +endif() add_executable(inkcpp_cl inkcpp_cl.cpp test.h test.cpp) # Include compiler and runtime libraries @@ -6,44 +10,59 @@ target_link_libraries(inkcpp_cl PUBLIC inkcpp inkcpp_compiler inkcpp_shared) # For https://en.cppreference.com/w/cpp/filesystem#Notes if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.1") - target_link_libraries(inkcpp_cl PRIVATE stdc++fs) - endif() + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.1") + target_link_libraries(inkcpp_cl PRIVATE stdc++fs) + endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0") - target_link_libraries(inkcpp_cl PRIVATE stdc++fs) - endif() + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0") + target_link_libraries(inkcpp_cl PRIVATE stdc++fs) + endif() endif() # Install -install(TARGETS inkcpp_cl DESTINATION . COMPONENT cl EXCLUDE_FROM_ALL) +install( + TARGETS inkcpp_cl + DESTINATION . + COMPONENT cl + EXCLUDE_FROM_ALL) string(TOUPPER "${INKCPP_INKLECATE}" inkcpp_inklecate_upper) -unset(inklecate_dir) +unset(INKLECATE_DIR) if((inkcpp_inklecate_upper STREQUAL "ALL") OR (inkcpp_inklecate_upper STREQUAL "OS")) - if(UNIX AND NOT APPLE) - FetchContent_GetProperties(inklecate_linux) - if(inklecate_linux_POPULATED) - set(inklecate_dir "${inklecate_linux_SOURCE_DIR}") - else() - message(WARNING "Inklecate for this OS was not downloaded successfully, and will therfore not be packat with cl") - endif(inklecate_linux_POPULATED) - elseif(APPLE) - if(inklecate_mac_POPULATED) - set(inklecate_dir "${inklecate_mac_SOURCE_DIR}") - else() - message(WARNING "Inklecate for this OS was not downloaded successfully, and will therfore not be packat with cl") - endif(inklecate_mac_POPULATED) - elseif(MSYS OR MINGW OR WIN32 OR CYGWIN) - if(inklecate_windows_POPULATED) - set(inklecate_dir "${inklecate_windows_SOURCE_DIR}") - else() - message(WARNING "Inklecate for this OS was not downloaded successfully, and will therfore not be packat with cl") - endif(inklecate_windows_POPULATED) - else() - message(WARNING "Failed to determine OS for bundling inklecate with command line application!") - endif() - if(inklecate_dir) - file(GLOB inklecate_files "${inklecate_dir}/*") - install(FILES ${inklecate_files} DESTINATION . COMPONENT cl EXCLUDE_FROM_ALL) - endif(inklecate_dir) + if(UNIX AND NOT APPLE) + FetchContent_GetProperties(inklecate_linux) + if(inklecate_linux_POPULATED) + set(INKLECATE_DIR "${inklecate_linux_SOURCE_DIR}") + else() + message(WARNING "Inklecate for this OS was not downloaded successfully, \ + and will therfore not be packat with cl") + endif(inklecate_linux_POPULATED) + elseif(APPLE) + if(inklecate_mac_POPULATED) + set(INKLECATE_DIR "${inklecate_mac_SOURCE_DIR}") + else() + message(WARNING "Inklecate for this OS was not downloaded successfully, \ + and will therfore not be packat with cl") + endif(inklecate_mac_POPULATED) + elseif( + MSYS + OR MINGW + OR WIN32 + OR CYGWIN) + if(inklecate_windows_POPULATED) + set(INKLECATE_DIR "${inklecate_windows_SOURCE_DIR}") + else() + message(WARNING "Inklecate for this OS was not downloaded successfully, \ + and will therfore not be packat with cl") + endif(inklecate_windows_POPULATED) + else() + message(WARNING "Failed to determine OS for bundling inklecate with command line application!") + endif() + if(INKLECATE_DIR) + file(GLOB inklecate_files "${INKLECATE_DIR}/*") + install( + FILES ${inklecate_files} + DESTINATION . + COMPONENT cl + EXCLUDE_FROM_ALL) + endif(INKLECATE_DIR) endif((inkcpp_inklecate_upper STREQUAL "ALL") OR (inkcpp_inklecate_upper STREQUAL "OS")) diff --git a/inkcpp_compiler/json_compiler.cpp b/inkcpp_compiler/json_compiler.cpp index 51de770c..e6550f0e 100644 --- a/inkcpp_compiler/json_compiler.cpp +++ b/inkcpp_compiler/json_compiler.cpp @@ -411,9 +411,7 @@ void json_compiler::compile_complex_command(const nlohmann::json& command) else if (get(command, "#", val)) { if (_ink_version > 20) { - throw ink_exception( - "with inkVerison 21 the tag system chages, and the '#: ' is deprecated now" - ); + inkFail("with inkVerison 21 the tag system chages, and the '#: ' is deprecated now"); } _emitter->write_string(Command::TAG, CommandFlag::NO_FLAGS, val); } diff --git a/inkcpp_test/CMakeLists.txt b/inkcpp_test/CMakeLists.txt index 1a628535..172cb84a 100644 --- a/inkcpp_test/CMakeLists.txt +++ b/inkcpp_test/CMakeLists.txt @@ -1,119 +1,136 @@ -add_executable(inkcpp_test catch.hpp Main.cpp - Array.cpp - Pointer.cpp - Stack.cpp - Callstack.cpp - Restorable.cpp - Value.cpp - Globals.cpp - Lists.cpp - Tags.cpp - NewLines.cpp - FallbackFunction.cpp - LabelCondition.cpp - Observer.cpp - InkyJson.cpp - SpaceAfterBracketChoice.cpp - ThirdTierChoiceAfterBrackets.cpp - NoEarlyTags.cpp - ExternalFunctionsExecuteProperly.cpp +if(INKCPP_NO_STL) + message(FATAL_ERROR "Can not build tests without STL support, please disable INKCPP_TEST") +endif() +add_executable( + inkcpp_test + catch.hpp + Main.cpp + Array.cpp + Pointer.cpp + Stack.cpp + Callstack.cpp + Restorable.cpp + Value.cpp + Globals.cpp + Lists.cpp + Tags.cpp + NewLines.cpp + FallbackFunction.cpp + LabelCondition.cpp + Observer.cpp + InkyJson.cpp + SpaceAfterBracketChoice.cpp + ThirdTierChoiceAfterBrackets.cpp + NoEarlyTags.cpp + ExternalFunctionsExecuteProperly.cpp ExternalFunctionTypes.cpp - LookaheadSafe.cpp - EmptyStringForDivert.cpp - MoveTo.cpp - Fixes.cpp -) + LookaheadSafe.cpp + EmptyStringForDivert.cpp + MoveTo.cpp + Fixes.cpp) target_link_libraries(inkcpp_test PUBLIC inkcpp inkcpp_compiler inkcpp_shared) target_include_directories(inkcpp_test PRIVATE ../shared/private/) # For https://en.cppreference.com/w/cpp/filesystem#Notes if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.1") - target_link_libraries(inkcpp_test PRIVATE stdc++fs) - endif() + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.1") + target_link_libraries(inkcpp_test PRIVATE stdc++fs) + endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0") - target_link_libraries(inkcpp_test PRIVATE stdc++fs) - endif() + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0") + target_link_libraries(inkcpp_test PRIVATE stdc++fs) + endif() endif() add_test(NAME UnitTests COMMAND $) -if ($ENV{INKLECATE}) - set(INKLECATE_CMD $ENV{INKLECATE}) +if($ENV{INKLECATE}) + set(INKLECATE_CMD $ENV{INKLECATE}) else() - set(INKLECATE_CMD "inklecate") + set(INKLECATE_CMD "inklecate") endif() string(TOUPPER "${INKCPP_INKLECATE}" inkcpp_inklecate_upper) if((inkcpp_inklecate_upper STREQUAL "ALL") OR (inkcpp_inklecate_upper STREQUAL "OS")) - if(UNIX AND NOT APPLE) - FetchContent_GetProperties(inklecate_linux) - if(inklecate_linux_POPULATED) - set(INKLECATE_CMD "${inklecate_linux_SOURCE_DIR}/inklecate") - else() - message(FATAL_ERROR "inklecate download is not provided, please check if the download failed. - You may set INKCPP_INKLECATE=NONE and provide inkcleate via your PATH or the INKLECATE enviroment variable. - You can also disable tests altogether by setting INKCPP_TEST=OFF") - endif(inklecate_linux_POPULATED) - elseif(APPLE) - if(inklecate_mac_POPULATED) - set(INKLECATE_CMD "${inklecate_mac_SOURCE_DIR}/inklecate") - else() - message(FATAL_ERROR "inklecate download is not provided, please check if the download failed. - You may set INKCPP_INKLECATE=NONE and provide inkcleate via your PATH or the INKLECATE enviroment variable. - You can also disable tests altogether by setting INKCPP_TEST=OFF") - endif(inklecate_mac_POPULATED) - elseif(MSYS OR MINGW OR WIN32 OR CYGWIN) - if(inklecate_windows_POPULATED) - set(INKLECATE_CMD "${inklecate_windows_SOURCE_DIR}/inklecate") - else() - message(FATAL_ERROR "inklecate download is not provided, please check if the download failed. - You may set INKCPP_INKLECATE=NONE and provide inkcleate via your PATH or the INKLECATE enviroment variable. - You can also disable tests altogether by setting INKCPP_TEST=OFF") - endif(inklecate_windows_POPULATED) - else() - message(FATAL_ERROR "Current os could not be identified, therfore inklecate must be provided explicit for the tests to work - please set INKCPP_INKLECATE=NONE and provide inklecate via your PATH or the INKLECATE enviroment variables. - Alternatily disable tests by setting INKCPP_TEST=OFF.") - endif() + if(UNIX AND NOT APPLE) + FetchContent_GetProperties(inklecate_linux) + if(inklecate_linux_POPULATED) + set(INKLECATE_CMD "${inklecate_linux_SOURCE_DIR}/inklecate") + else() + message( + FATAL_ERROR + "inklecate download is not provided, please check if the download failed. + You may set INKCPP_INKLECATE=NONE and provide inkcleate via your PATH or \ + the INKLECATE enviroment variable. + You can also disable tests altogether by setting INKCPP_TEST=OFF") + endif(inklecate_linux_POPULATED) + elseif(APPLE) + if(inklecate_mac_POPULATED) + set(INKLECATE_CMD "${inklecate_mac_SOURCE_DIR}/inklecate") + else() + message( + FATAL_ERROR + "inklecate download is not provided, please check if the download failed. + You may set INKCPP_INKLECATE=NONE and provide inkcleate via your PATH or \ + the INKLECATE enviroment variable. + You can also disable tests altogether by setting INKCPP_TEST=OFF") + endif(inklecate_mac_POPULATED) + elseif( + MSYS + OR MINGW + OR WIN32 + OR CYGWIN) + if(inklecate_windows_POPULATED) + set(INKLECATE_CMD "${inklecate_windows_SOURCE_DIR}/inklecate") + else() + message( + FATAL_ERROR + "inklecate download is not provided, please check if the download failed. + You may set INKCPP_INKLECATE=NONE and provide inkcleate via your PATH or \ + the INKLECATE enviroment variable. + You can also disable tests altogether by setting INKCPP_TEST=OFF") + endif(inklecate_windows_POPULATED) + else() + message( + FATAL_ERROR + "Current os could not be identified, \ + therfore inklecate must be provided explicit for the tests to work + please set INKCPP_INKLECATE=NONE and provide inklecate via your PATH or \ + the INKLECATE enviroment variables. + Alternatily disable tests by setting INKCPP_TEST=OFF.") + endif() endif((inkcpp_inklecate_upper STREQUAL "ALL") OR (inkcpp_inklecate_upper STREQUAL "OS")) set(INK_TEST_RESOURCE_DIR "${PROJECT_BINARY_DIR}/ink") file(MAKE_DIRECTORY "${INK_TEST_RESOURCE_DIR}") -target_compile_definitions(inkcpp_test PRIVATE - INK_TEST_RESOURCE_DIR="${INK_TEST_RESOURCE_DIR}/") +target_compile_definitions(inkcpp_test PRIVATE INK_TEST_RESOURCE_DIR="${INK_TEST_RESOURCE_DIR}/") file(GLOB JSON_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ink/*.json") -file(COPY ${JSON_FILES} DESTINATION ${INK_TEST_RESOURCE_DIR}) +file(COPY ${JSON_FILES} DESTINATION ${INK_TEST_RESOURCE_DIR}) file(GLOB INK_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ink/*.ink") -foreach(INK_FILE IN LISTS INK_FILES) - get_filename_component(INK_FILENAME ${INK_FILE} NAME_WE) - set(output "${INK_TEST_RESOURCE_DIR}/${INK_FILENAME}.bin") - add_custom_command( - OUTPUT ${output} - COMMAND $ -o "${output}" --inklecate "${INKLECATE_CMD}" "${INK_FILE}" - DEPENDS ${INK_FILE} inkcpp_compiler - COMMENT "Compile test ink file '${INK_FILENAME}.ink' -> '${output}'" - ) - list(APPEND INK_OUT_FILES ${output}) +foreach(ink_file IN LISTS INK_FILES) + get_filename_component(INK_FILENAME ${ink_file} NAME_WE) + set(OUTPUT "${INK_TEST_RESOURCE_DIR}/${INK_FILENAME}.bin") + add_custom_command( + OUTPUT ${OUTPUT} + COMMAND $ -o "${OUTPUT}" --inklecate "${INKLECATE_CMD}" "${ink_file}" + DEPENDS ${ink_file} inkcpp_compiler + COMMENT "Compile test ink file '${INK_FILENAME}.ink' -> '${OUTPUT}'") + list(APPEND INK_OUT_FILES ${OUTPUT}) endforeach() target_sources(inkcpp_test PRIVATE ${INK_OUT_FILES}) if(TARGET inkcpp_c) - file(GLOB TEST_FILES "${PROJECT_SOURCE_DIR}/inkcpp_c/tests/*.c") - foreach(TEST_FILE IN LISTS TEST_FILES) - get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE) - add_executable(${TEST_NAME} ${TEST_FILE}) - target_link_libraries(${TEST_NAME} PRIVATE inkcpp_c) - target_compile_definitions(${TEST_NAME} PRIVATE - INK_TEST_RESOURCE_DIR="${INK_TEST_RESOURCE_DIR}/") - add_test( - NAME ${TEST_NAME} - COMMAND $ - ) - endforeach() + file(GLOB TEST_FILES "${PROJECT_SOURCE_DIR}/inkcpp_c/tests/*.c") + foreach(test_file IN LISTS TEST_FILES) + get_filename_component(TEST_NAME ${test_file} NAME_WE) + add_executable(${TEST_NAME} ${test_file}) + target_link_libraries(${TEST_NAME} PRIVATE inkcpp_c) + target_compile_definitions(${TEST_NAME} + PRIVATE INK_TEST_RESOURCE_DIR="${INK_TEST_RESOURCE_DIR}/") + add_test(NAME ${TEST_NAME} COMMAND $) + endforeach() endif(TARGET inkcpp_c) diff --git a/shared/public/system.h b/shared/public/system.h index 60f0fb2c..a3f5ca9f 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -22,11 +22,11 @@ # include # include # include -# include -# include #endif #ifdef INK_ENABLE_CSTD +# include # include +# include #endif // Platform specific defines // @@ -45,8 +45,10 @@ #else # define inkAssert ink::ink_assert # define inkFail(...) ink::ink_assert(false, __VA_ARGS__) + #endif + namespace ink { /** define basic numeric type @@ -156,21 +158,20 @@ namespace internal while (true) { switch (*(string++)) { case 0: return true; + case '\f': [[fallthrough]]; + case '\r': [[fallthrough]]; case '\n': if (! includeNewline) return false; [[fallthrough]]; case '\t': [[fallthrough]]; + case '\v': [[fallthrough]]; case ' ': continue; default: return false; } } } - - /** check if character can be only part of a word, when two part of word characters put together - * the will be a space inserted I049 - */ - static inline bool is_part_of_word(char character) + inline bool is_part_of_word(char character) { return isalpha(character) || isdigit(character); } { return isalpha(character) || isdigit(character); } @@ -197,7 +198,7 @@ namespace internal #endif } // namespace internal -#ifdef INK_ENABLE_EXCEPTIONS +#ifdef INK_ENABLE_STL /** exception type thrown if something goes wrong */ using ink_exception = std::runtime_error; #else @@ -218,7 +219,6 @@ class ink_exception #endif // assert -#ifndef INK_ENABLE_UNREAL template void ink_assert(bool condition, const char* msg = nullptr, Args... args) { @@ -227,21 +227,22 @@ void ink_assert(bool condition, const char* msg = nullptr, Args... args) msg = EMPTY; } if (! condition) { +#if defined(INKCPP_ENABLE_STL) || defined(INKCPP_ENABLE_CSTD) if constexpr (sizeof...(args) > 0) { size_t size = snprintf(nullptr, 0, msg, args...) + 1; char* message = static_cast(malloc(size)); snprintf(message, size, msg, args...); msg = message; - } -#ifdef INK_ENABLE_EXCEPTIONS - throw ink_exception(msg); -#else - fprintf(stderr, "Ink Assert: %s\n", msg); - abort(); -# endif + } else +#endif + { +# ifdef INK_ENABLE_EXCEPTIONS + throw ink_exception(msg); # else -# error "This path needs a way to warn and then terminate, otherwise it'll silently fail" + fprintf(stderr, "Ink Assert: %s\n", msg); + abort(); # endif + } } } @@ -251,7 +252,6 @@ template ink_assert(false, msg, args...); exit(EXIT_FAILURE); } -#endif namespace runtime::internal { From 97186083afd162bd5f0fb6ae4660589e849f7e87 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Tue, 6 Jan 2026 14:14:31 +0100 Subject: [PATCH 12/24] fix(NO_STD): touchups needed for rebase of current master --- inkcpp/avl_array.h | 22 +++++++++++----------- inkcpp/functional.cpp | 6 +++--- inkcpp/include/types.h | 14 ++++++++++++++ inkcpp/runner_impl.cpp | 6 ++---- inkcpp/snapshot_interface.h | 4 ++-- inkcpp_c/include/inkcpp.h | 1 + inkcpp_c/inkcpp.cpp | 4 ++-- shared/public/system.h | 32 +++++++++++++++++++++++++------- 8 files changed, 60 insertions(+), 29 deletions(-) diff --git a/inkcpp/avl_array.h b/inkcpp/avl_array.h index d3847a51..b015e517 100644 --- a/inkcpp/avl_array.h +++ b/inkcpp/avl_array.h @@ -65,13 +65,13 @@ class avl_array // node storage, due to possible structure packing effects, single arrays are used instead of a // 'node' structure - ink::runtime::internal::if_t key_; - ink::runtime::internal::if_t val_; - ink::runtime::internal::if_t balance_; - ink::runtime::internal::if_t child_; - size_type size_; // actual size - size_type _capacity; - size_type root_; // root node + ink::runtime::internal::if_t key_; + ink::runtime::internal::if_t val_; + ink::runtime::internal::if_t balance_; + ink::runtime::internal::if_t child_; + size_type size_; // actual size + size_type _capacity; + size_type root_; // root node ink::runtime::internal::if_t parent_; // invalid index (like 'nullptr' in a pointer implementation) @@ -197,7 +197,7 @@ class avl_array if constexpr (dynamic) { key_ = new Key[Size]; val_ = new T[Size]; - balance_ = new std::int8_t[Size]; + balance_ = new char[Size]; child_ = new child_type[Size]; if constexpr (Fast) { parent_ = new size_type[Size]; @@ -285,7 +285,7 @@ class avl_array val_ = new_data; } { - std::int8_t* new_data = new std::int8_t[new_size]; + char* new_data = new char[new_size]; for (size_type i = 0; i < _capacity; ++i) { new_data[i] = balance_[i]; } @@ -660,7 +660,7 @@ class avl_array set_parent(target, get_parent(source)); } - void insert_balance(size_type node, std::int8_t balance) + void insert_balance(size_type node, char balance) { while (node != INVALID_IDX) { balance = (balance_[node] += balance); @@ -691,7 +691,7 @@ class avl_array } } - void delete_balance(size_type node, std::int8_t balance) + void delete_balance(size_type node, char balance) { while (node != INVALID_IDX) { balance = (balance_[node] += balance); diff --git a/inkcpp/functional.cpp b/inkcpp/functional.cpp index 8ed82ea6..735e7fe7 100644 --- a/inkcpp/functional.cpp +++ b/inkcpp/functional.cpp @@ -33,21 +33,21 @@ int32_t function_base::pop(basic_eval_stack* stack, list_table&) } template<> -uint32_t function_base::pop(basic_eval_stack* stack, list_table& lists) +uint32_t function_base::pop(basic_eval_stack* stack, list_table&) { value val = stack->pop(); return casting::numeric_cast(val); } template<> -bool function_base::pop(basic_eval_stack* stack, list_table& lists) +bool function_base::pop(basic_eval_stack* stack, list_table&) { value val = stack->pop(); return casting::numeric_cast(val) != 0; } template<> -float function_base::pop(basic_eval_stack* stack, list_table& lists) +float function_base::pop(basic_eval_stack* stack, list_table&) { value val = stack->pop(); return casting::numeric_cast(val); diff --git a/inkcpp/include/types.h b/inkcpp/include/types.h index 8183e6c2..c771cb1a 100644 --- a/inkcpp/include/types.h +++ b/inkcpp/include/types.h @@ -113,6 +113,20 @@ struct value { } } + constexpr value& operator=(const value& v) + { + type = v.type; + switch (type) { + case Type::Bool: v_bool = v.v_bool; break; + case Type::Uint32: v_uint32 = v.v_uint32; break; + case Type::Int32: v_int32 = v.v_int32; break; + case Type::String: v_string = v.v_string; break; + case Type::Float: v_float = v.v_float; break; + case Type::List: v_list = v.v_list; break; + } + return *this; + } + /// @} /** Get value to corresponding type diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 68d389e4..996eec31 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -17,6 +17,7 @@ #ifdef INK_ENABLE_STL # include +#endif namespace ink::runtime { @@ -312,11 +313,8 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) container_t id; ip_t offset = nullptr; bool reversed = _ptr > dest; - - // move to destination and update container stack on the go - const ContainerData* c_iter = nullptr; // number of container which were already on the stack at current position - size_t comm_end = _container.size(); + size_t comm_end = _container.size(); iter = nullptr; while (_story->iterate_containers(iter, id, offset)) { diff --git a/inkcpp/snapshot_interface.h b/inkcpp/snapshot_interface.h index 10a1e35d..fa7f3b0f 100644 --- a/inkcpp/snapshot_interface.h +++ b/inkcpp/snapshot_interface.h @@ -22,7 +22,7 @@ class value; class snapshot_interface { public: - constexpr snapshot_interface(){}; + constexpr snapshot_interface() {}; static unsigned char* snap_write(unsigned char* ptr, const void* data, size_t length, bool write) { @@ -70,7 +70,7 @@ class snapshot_interface const char* story_string_table; const snap_tag* runner_tags = nullptr; - loader(managed_array& string_table, const char* story_sting_table) + loader(managed_array& string_table, const char* story_string_table) : string_table{string_table} , story_string_table{story_string_table} { diff --git a/inkcpp_c/include/inkcpp.h b/inkcpp_c/include/inkcpp.h index 7725de87..44ad21e1 100644 --- a/inkcpp_c/include/inkcpp.h +++ b/inkcpp_c/include/inkcpp.h @@ -3,6 +3,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { diff --git a/inkcpp_c/inkcpp.cpp b/inkcpp_c/inkcpp.cpp index d0073e02..3b5d85c9 100644 --- a/inkcpp_c/inkcpp.cpp +++ b/inkcpp_c/inkcpp.cpp @@ -77,7 +77,7 @@ extern "C" { fseek(file, 0, SEEK_SET); unsigned char* data = static_cast(malloc(file_length)); inkAssert(data, "Malloc of size %u failed", file_length); - unsigned length = fread_s(data, file_length, 1, file_length, file); + unsigned length = fread(data, sizeof(unsigned char), file_length, file); inkAssert( file_length == length, "Expected to read file of size %u, but only read %u", file_length, length @@ -94,7 +94,7 @@ extern "C" { fseek(file, 0, SEEK_SET); unsigned char* data = static_cast(malloc(file_length)); inkAssert(data, "Malloc of size %u failed", file_length); - unsigned length = fread_s(data, file_length, sizeof(unsigned char), file_length, file); + unsigned length = fread(data, sizeof(unsigned char), file_length, file); inkAssert( file_length == length, "Expected to read file of size %u, but only read %u", file_length, length diff --git a/shared/public/system.h b/shared/public/system.h index a3f5ca9f..f37dfce2 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -25,6 +25,7 @@ #endif #ifdef INK_ENABLE_CSTD # include +# include # include # include #endif @@ -171,10 +172,8 @@ namespace internal } } } + inline bool is_part_of_word(char character) { return isalpha(character) || isdigit(character); } - { - return isalpha(character) || isdigit(character); - } static inline constexpr bool is_whitespace(char character, bool includeNewline = true) { @@ -218,6 +217,16 @@ class ink_exception }; #endif +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-parameter" +#else +# pragma warning(push) +# pragma warning( \ + disable : 4100, \ + justification : "dependend on rtti, exception and stl support not all arguments are needed" \ + ) +#endif // assert template void ink_assert(bool condition, const char* msg = nullptr, Args... args) @@ -233,24 +242,33 @@ void ink_assert(bool condition, const char* msg = nullptr, Args... args) char* message = static_cast(malloc(size)); snprintf(message, size, msg, args...); msg = message; - } else + } else #endif { -# ifdef INK_ENABLE_EXCEPTIONS +#ifdef INK_ENABLE_EXCEPTIONS throw ink_exception(msg); -# else +#elif defined(INK_ENABLE_CSTD) fprintf(stderr, "Ink Assert: %s\n", msg); abort(); -# endif +#else +# warning no assertion handling this could lead to invalid code paths +#endif } } } +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#else +# pragma warning(pop) +#endif template [[noreturn]] inline void ink_assert(const char* msg = nullptr, Args... args) { ink_assert(false, msg, args...); +#ifdef INK_ENABLE_CSTD exit(EXIT_FAILURE); +#endif } namespace runtime::internal From c68c033221de5d2d79bbe8ddbc98c5d536c203c5 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Mon, 23 Mar 2026 17:59:20 +0100 Subject: [PATCH 13/24] feat(Migration): add hungarian_solver to match list elements --- inkcpp/CMakeLists.txt | 2 + inkcpp/globals_impl.cpp | 5 +- inkcpp/globals_impl.h | 3 +- inkcpp/hungarian_solver.cpp | 121 ++++++++++++++++++++++ inkcpp/hungarian_solver.h | 9 ++ inkcpp/list_table.cpp | 140 +++++++++++++++++++++++++- inkcpp/list_table.h | 34 +++++-- inkcpp/snapshot_impl.cpp | 16 ++- inkcpp/snapshot_impl.h | 4 +- inkcpp/story_impl.cpp | 11 +- inkcpp/story_impl.h | 3 + inkcpp_test/CMakeLists.txt | 1 + inkcpp_test/ListMatching.cpp | 39 +++++++ inkcpp_test/ink/MigrationKnotTags.ink | 26 +++++ inkcpp_test/ink/MigrationTemp.ink | 26 +++++ 15 files changed, 419 insertions(+), 21 deletions(-) create mode 100644 inkcpp/hungarian_solver.cpp create mode 100644 inkcpp/hungarian_solver.h create mode 100644 inkcpp_test/ListMatching.cpp create mode 100644 inkcpp_test/ink/MigrationKnotTags.ink create mode 100644 inkcpp_test/ink/MigrationTemp.ink diff --git a/inkcpp/CMakeLists.txt b/inkcpp/CMakeLists.txt index 1785fdc2..942a517e 100644 --- a/inkcpp/CMakeLists.txt +++ b/inkcpp/CMakeLists.txt @@ -46,6 +46,8 @@ list( string_operations.cpp string_operations.cpp numeric_operations.cpp + hungarian_solver.h + hungarian_solver.cpp casting.h executioner.h string_utils.h diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 9d29f11a..fb884a41 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -318,9 +318,10 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa return ptr; } -bool globals_impl::migrate_new_globals(globals_impl& new_globals) +bool globals_impl::migrate_new_globals(globals_impl& new_globals, const char* list_metadata) { - return _variables.migrate(new_globals._variables); + return _variables.migrate(new_globals._variables) + && _lists.migrate(list_metadata, _owner->get_header()); } config::statistics::global globals_impl::statistics() const diff --git a/inkcpp/globals_impl.h b/inkcpp/globals_impl.h index b5d3bfae..4f13fa40 100644 --- a/inkcpp/globals_impl.h +++ b/inkcpp/globals_impl.h @@ -40,9 +40,10 @@ class globals_impl final * no longer existing ones are deleted. * @retval true on success * @param[in] new_globals to read current relevant variables from. It is modified to be equal to + * @param[in] list_metadata old list metadata to migrate list * the globals stored inside. */ - bool migrate_new_globals(globals_impl& new_globals); + bool migrate_new_globals(globals_impl& new_globals, const char* list_metadata); // Initializes a new global store from the given story globals_impl(const story_impl*); diff --git a/inkcpp/hungarian_solver.cpp b/inkcpp/hungarian_solver.cpp new file mode 100644 index 00000000..2dd5c433 --- /dev/null +++ b/inkcpp/hungarian_solver.cpp @@ -0,0 +1,121 @@ +#include "hungarian_solver.h" +#include + +class HungarienCtx +{ + const size_t n; + const float* cost; + + struct { + float* row; + float* col; + } pot; + + float* slack; + int* col_2_row; + int* path; + bool* visit_col; + +public: + HungarienCtx(const float* cost, size_t n) + : n{n} + , cost{cost} + { + pot.row = new float[n + 1]; + memset(pot.row, 0, sizeof(float) * (n + 1)); + pot.col = new float[n + 1]; + memset(pot.col, 0, sizeof(float) * (n + 1)); + slack = new float[n + 1]; + col_2_row = new int[n + 1]; + memset(col_2_row, 0, sizeof(int) * (n + 1)); + path = new int[n + 1]; + visit_col = new bool[n + 1]; + } + + ~HungarienCtx() + { + delete[] pot.row; + delete[] pot.col; + delete[] slack; + delete[] col_2_row; + delete[] path; + delete[] visit_col; + } + + int operator[](int col) { return col_2_row[col]; } + + void init_search(size_t row) + { + col_2_row[0] = row; + for (size_t i = 0; i <= n; ++i) { + visit_col[i] = false; + slack[i] = std::numeric_limits::max(); + path[i] = 0; + } + } + + int find_augmenting_path() + { + int current_col = 0; + do { + visit_col[current_col] = true; + int current_row = col_2_row[current_col]; + float delta = std::numeric_limits::max(); + int next_col = 0; + for (size_t col = 1; col <= n; ++col) { + if (! visit_col[col]) { + float reduced + = cost[(current_row - 1) * n + (col - 1)] - pot.row[current_row] - pot.col[col]; + if (reduced < slack[col]) { + slack[col] = reduced; + path[col] = current_col; + } + if (slack[col] < delta) { + delta = slack[col]; + next_col = col; + } + } + } + + for (size_t col = 0; col <= n; ++col) { + if (visit_col[col]) { + pot.row[col_2_row[col]] += delta; + pot.col[col] -= delta; + } else { + slack[col] -= delta; + } + } + current_col = next_col; + } while (col_2_row[current_col]); + return current_col; + } + + // end_col = 0 -> no augmenting + void augment_matching(int end_col) + { + int col = end_col; + do { + int prev = path[col]; + col_2_row[col] = col_2_row[prev]; + col = prev; + } while (col != 0); + } +}; + +float hungarian_solver(const float* cost, int* matches, size_t n) +{ + HungarienCtx ctx(cost, n); + for (size_t row = 1; row <= n; ++row) { + ctx.init_search(row); + int end_col = ctx.find_augmenting_path(); + ctx.augment_matching(end_col); + } + + float total_cost = 0; + for (size_t col = 1; col <= n; ++col) { + int row = ctx[col] - 1; + matches[row] = col - 1; + total_cost += cost[row * n + matches[row]]; + } + return total_cost; +} diff --git a/inkcpp/hungarian_solver.h b/inkcpp/hungarian_solver.h new file mode 100644 index 00000000..6b181ee7 --- /dev/null +++ b/inkcpp/hungarian_solver.h @@ -0,0 +1,9 @@ +#pragma once + +/** Hungarian Algorithm to solve an assignment problem in O(N3). + * https://en.wikipedia.org/wiki/Hungarian_algorithm + * @param[in] cost matrix m x n + * @param[out] matches optimal mapping m -> n + * @param n number of jobs/assignments + */ +float hungarian_solver(const float* cost, int* matches, size_t n); diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index 703de027..dcc43d21 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -12,6 +12,7 @@ #include "random.h" #include "string_utils.h" #include "list_impl.h" +#include #ifdef INK_ENABLE_STL # include @@ -74,7 +75,7 @@ list_table::list list_table::create() } list new_entry(_entry_state.size()); - // TODO: initelized unused? + // TODO: initialized unused? _entry_state.push() = state::used; for (int i = 0; i < _entrySize; ++i) { _data.push() = 0; @@ -858,4 +859,141 @@ config::statistics::list_table list_table::statistics() const }; } +/** Distance function for string labels. + * @param lh,rh null terminated ASCII strings to compare + * @return 0 if identical + */ +float d_label(const char* lh, const char* rh) { return 0; } + +/** Distance function for two values. + * @param lh,rh numeric values to compare + * @param lh_range,rh_range min/max value of the number + * @returns 0 if identical + */ +float d_value(int lh, int rh, int lh_range[2], int rh_range[2]) +{ + if (lh == rh) { + return 0; + } + float res = (static_cast(lh) - lh_range[0]) / (lh_range[1] - lh_range[0]); + res -= (static_cast(rh) - rh_range[0]) / (rh_range[1] - rh_range[0]); + if (res < 0) { + res = -res; + } + return res; +} + +struct MatchListValues { + const char* const* names; + const int* values; + size_t length; +}; + +void get_range(const MatchListValues& values, int range[2]) +{ + range[0] = std::numeric_limits::max(); + range[1] = std::numeric_limits::min(); + for (size_t i = 0; i < values.length; ++i) { + if (values.values[i] < range[0]) { + range[0] = values.values[i]; + } + if (values.values[i] > range[1]) { + range[1] = values.values[i]; + } + } +} + +float** cost_matrix(const MatchListValues& lh, const MatchListValues& rh, float drop_penalty) +{ + size_t n_flags = lh.length > rh.length ? lh.length : rh.length; + float** matrix = reinterpret_cast( + malloc(sizeof(float*) * n_flags + sizeof(float) * n_flags * n_flags) + ); + for (size_t i = 0; i < n_flags; ++i) { + matrix[i] = reinterpret_cast(matrix + n_flags) + i * n_flags; + } + int lh_range[2], rh_range[2]; + get_range(lh, lh_range); + get_range(rh, rh_range); + + for (size_t i = 0; i < lh.length; ++i) { + for (size_t j = 0; j < rh.length; ++j) { + float dl = d_label(lh.names[i], rh.names[j]); + float dv = d_value(lh.values[i], rh.values[j], lh_range, rh_range); + matrix[i][j] = dl * 0.8 + dv * 0.2; + } + } + return matrix; +} + +list_table::list_table( + const char* data, const ink::internal::header&, const decltype(_data)& values, + const decltype(_entry_state)& state +) +{ +} + +bool list_table::migrate(const char* old_list_metadata, const ink::internal::header& header) +{ + return true; + // FIXME: old data are decoded with old header + list_table old_ref_table(old_list_metadata, header, _data, _entry_state); + _data.clear(); + _entry_state.clear(); + + for (size_t idx = 0; idx < old_ref_table._entry_state.size(); ++idx) { + // migrate + list new_list{-1}; + switch (old_ref_table._entry_state[idx]) { + case state::permanent: new_list = create_permament(); break; + case state::used: new_list = create(); break; + default: continue; + } + inkAssert(new_list.lid == idx, "Did not sequencially restore/migrate list"); + if (new_list.lid != idx) { + return false; + } + // find best mapping between old and new list elements + // + c_ij(value) = min(|v_i - v_j|/Rv,1) + // + c_ij(name) = levenshtein, cosine n-grams, jaro-winkler + // + c_ij(position_in_list) = min(|p_i - p_j|/Rp, 1) + // find best mapping between lists + // + c_ij(name) = levenshtein, cosine n-grams, jaro-winkler + // + c_ij(entries) = entries existing in both + // 1. h_entry_map = high confidents mapping of list elements (value, name, position_in_list) + // 2. h_list_map = high confident mapping of lists (name, h_entry_map) + // 3. entry_map = mapping of list elements (value, name, position_in_list, + // h_list_map[list_name]) + // 4. list_map = mapping of lists (name, entry_map) + + // generate cost matrix + constexpr float DROP_PANELTY = 0.3; + float** matrix = cost_matrix( + MatchListValues{ + _flag_names.data(), + _flag_values.data(), + _flag_names.size(), + }, + MatchListValues{ + old_ref_table._flag_names.data(), + old_ref_table._flag_values.data(), + old_ref_table._flag_names.size(), + }, + DROP_PANELTY + ); + + + free(matrix); + + for (const named_flag_itr::position& flag : old_ref_table.named_flags(new_list)) { + int value = old_ref_table.get_flag_value(flag.flag); + const char* list_name = old_ref_table._list_names[flag.flag.list_id]; + const char* flag_name = flag.name; + } + } + + return true; +} + + } // namespace ink::runtime::internal diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index f0a255d6..b2434cd6 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -113,8 +113,9 @@ class list_table : public snapshot_interface list create_permament(); list& add_inplace(list& lh, list_flag rh); - // parse binary list meta data list_table(const char* data, const ink::internal::header&); + // binary list metadata of currently loaded list + bool migrate(const char* old_list_metadata, const ink::internal::header& header); explicit list_table() : _entrySize{0} @@ -147,11 +148,11 @@ class list_table : public snapshot_interface size_t snap(unsigned char* data, const snapper&) const; const unsigned char* snap_load(const unsigned char* data, const loader&); - bool can_be_migrated() const { return true; } + bool can_be_migrated() const { return _list_handouts.size() == 0; } - /** special traitment when a list get assignet again - * when a list get assigned and would have no origin, it gets the origin of the base with origin - * eg. I072 + /** special treatment when a list gets assigned again + * when a list gets assigned and would have no origin, it gets the origin of the base with origin + * e.g. I072 */ list redefine(list lh, list rh); @@ -363,6 +364,11 @@ class list_table : public snapshot_interface // entries (created lists) managed_array _data; managed_array _entry_state; + // parse binary list metadata + list_table( + const char* data, const ink::internal::header&, const decltype(_data)& values, + const decltype(_entry_state)& state + ); // defined list (meta data) managed_array _list_end; @@ -380,13 +386,17 @@ class list_table : public snapshot_interface class named_flag_itr { + public: + struct position { + list_flag flag; + const char* name; + }; + + private: const list_table& _list; const data_t* _data; - struct { - list_flag flag; - const char* name; - } _pos; + position _pos; /** carry list change. * if the iterator incremented to the next flag, also increment the list if necessary @@ -451,7 +461,11 @@ class list_table : public snapshot_interface , _pos{null_flag, nullptr} {}; named_flag_itr(const list_table& list, const data_t* filter, int) - : _list{list}, _data{filter}, _pos{{0,0},list._flag_names[0]} + : _list{ + list + } + , _data{filter} + , _pos{{0, 0}, list._flag_names[0]} { goToValid(); } diff --git a/inkcpp/snapshot_impl.cpp b/inkcpp/snapshot_impl.cpp index 1760a20f..cf083b1a 100644 --- a/inkcpp/snapshot_impl.cpp +++ b/inkcpp/snapshot_impl.cpp @@ -52,9 +52,11 @@ void snapshot::write_to_file(const char* filename) const namespace ink::runtime::internal { -size_t snapshot_impl::file_size(size_t serialization_length, size_t runner_cnt) +size_t + snapshot_impl::file_size(size_t serialization_length, size_t runner_cnt, bool list_definition) { - return serialization_length + sizeof(header) + (runner_cnt + 1) * sizeof(size_t); + return serialization_length + sizeof(header) + + (runner_cnt + 1 + (list_definition ? 1 : 0)) * sizeof(size_t); } bool snapshot_impl::can_be_migrated(const story& story) const @@ -79,8 +81,11 @@ snapshot_impl::snapshot_impl(const globals_impl& globals) migratable = migratable && node->object->can_be_migrated(); ++runner_cnt; } + if (migratable) { + _length += globals._owner->list_meta_size(); + } - _length = file_size(_length, runner_cnt); + _length = file_size(_length, runner_cnt, migratable); _header.length = _length; _header.num_runners = runner_cnt; _header.hash = globals._owner->hash(); @@ -102,12 +107,17 @@ snapshot_impl::snapshot_impl(const globals_impl& globals) ptr += sizeof(offset); offset += node->object->snap(nullptr, snapper); } + memcpy(ptr, &offset, sizeof(offset)); + ptr += sizeof(offset); + offset += globals._owner->list_meta_size(); } ptr += globals.snap(ptr, snapper); for (auto node = globals._runners_start; node; node = node->next) { ptr += node->object->snap(ptr, snapper); } + memcpy(ptr, globals._owner->list_meta(), globals._owner->list_meta_size()); + ptr += globals._owner->list_meta_size(); } snapshot_impl::snapshot_impl(const unsigned char* data, size_t length, bool managed) diff --git a/inkcpp/snapshot_impl.h b/inkcpp/snapshot_impl.h index 9078c1a1..af3c03ba 100644 --- a/inkcpp/snapshot_impl.h +++ b/inkcpp/snapshot_impl.h @@ -98,6 +98,8 @@ class snapshot_impl final : public snapshot const unsigned char* get_runner_snap(size_t idx) const { return _file + get_offset(idx + 1); } + const unsigned char* get_list_metadata() const { return _file + get_offset(num_runners() + 1); } + size_t num_runners() const override { return _header.num_runners; } bool can_be_migrated() const override { return _header.migratable; } @@ -114,7 +116,7 @@ class snapshot_impl final : public snapshot const unsigned char* _file; size_t _length; bool _managed; - static size_t file_size(size_t, size_t); + static size_t file_size(size_t, size_t, bool); struct header { size_t num_runners; diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index f8d44d70..6094c3ca 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -241,7 +241,10 @@ globals story_impl::new_globals_from_snapshot(const snapshot& data) if (hash() != snapshot.hash()) { globals new_globs = new_globals(); runner thread = new_runner(new_globs); - if (! globs->migrate_new_globals(*new_globs.cast().get())) { + if (! globs->migrate_new_globals( + *new_globs.cast().get(), + reinterpret_cast(snapshot.get_list_metadata()) + )) { delete globs; return globals(); } @@ -342,9 +345,11 @@ void story_impl::setup_pointers() while (_header.read_list_flag(ptr) != null_flag) ; } + _list_meta_size = ptr - _list_meta; } else { - _list_meta = nullptr; - _lists = nullptr; + _list_meta = nullptr; + _list_meta_size = 0; + _lists = nullptr; } inkAssert( _header.ink_bin_version_number == ink::InkBinVersion, diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index 6c650027..ae2a07e5 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -40,6 +40,8 @@ class story_impl : public story const char* list_meta() const { return _list_meta; } + size_t list_meta_size() const { return _list_meta_size; } + bool iterate_containers( const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse = false ) const; @@ -77,6 +79,7 @@ class story_impl : public story const char* _string_table; const char* _list_meta; + size_t _list_meta_size; const list_flag* _lists; // container info diff --git a/inkcpp_test/CMakeLists.txt b/inkcpp_test/CMakeLists.txt index 8f657637..a12a6da3 100644 --- a/inkcpp_test/CMakeLists.txt +++ b/inkcpp_test/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable( LookaheadSafe.cpp EmptyStringForDivert.cpp MoveTo.cpp + ListMatching.cpp Fixes.cpp) target_link_libraries(inkcpp_test PUBLIC inkcpp inkcpp_compiler inkcpp_shared) diff --git a/inkcpp_test/ListMatching.cpp b/inkcpp_test/ListMatching.cpp new file mode 100644 index 00000000..24724636 --- /dev/null +++ b/inkcpp_test/ListMatching.cpp @@ -0,0 +1,39 @@ +#include "catch.hpp" + +#include "../inkcpp/hungarian_solver.h" + +SCENARIO("find best assigments", "[hungarian]") +{ + GIVEN("Example 1") + { + // clang-format off + float cost[] = { + 8/**/, 5 , 9 , + 4 , 2 , 4/**/, + 7 , 3/**/, 8 , + }; + // clang-format on + int matches[3]; + float total_cost = hungarian_solver(cost, matches, 3); + CHECK(total_cost == 15.f); + CHECK(matches[0] == 0); + CHECK(matches[1] == 2); + CHECK(matches[2] == 1); + } + GIVEN("Example 2") + { + // clang-format off + float cost[] = { + 108 , 150 , 122/**/, + 125 , 135/**/, 148 , + 150/**/, 175 , 250 , + }; + // clang-format off + int matches[3]; + float total_cost = hungarian_solver(cost, matches, 3); + CHECK(total_cost == 407); + CHECK(matches[0] == 2); + CHECK(matches[1] == 1); + CHECK(matches[2] == 0); + } +} diff --git a/inkcpp_test/ink/MigrationKnotTags.ink b/inkcpp_test/ink/MigrationKnotTags.ink new file mode 100644 index 00000000..9c4037b0 --- /dev/null +++ b/inkcpp_test/ink/MigrationKnotTags.ink @@ -0,0 +1,26 @@ +# test:migration +# flavor:changed + +VAR do_not_migrate = 10 +VAR do_migrate = 15 +->Node1 +=== OldNode +O +-> Node1 +=== Node1 +A +-> Main +=== Main +# knot:different +~ temp tKeep = 2 +~ temp tOld = 3 +{TURNS_SINCE(-> Node1)} {TURNS_SINCE(-> OldNode)} {TURNS_SINCE(-> Main)} +This is a simple story. +~ tKeep = 5 +* A +* B +- catch +{tKeep} {tOld} +{TURNS_SINCE(-> Node1)} {TURNS_SINCE(-> OldNode)} {TURNS_SINCE(-> Main)} +Oh. +->DONE diff --git a/inkcpp_test/ink/MigrationTemp.ink b/inkcpp_test/ink/MigrationTemp.ink new file mode 100644 index 00000000..422829f9 --- /dev/null +++ b/inkcpp_test/ink/MigrationTemp.ink @@ -0,0 +1,26 @@ +# test:migration +# flavor:base + +VAR do_not_migrate = 10 +VAR do_migrate = 15 +->Node1 +=== OldNode +O +-> Node1 +=== Node1 +A +-> Main +=== Main +# knot:Main +~ temp tKeep = 2 +~ temp tNew = 6 +{TURNS_SINCE(-> Node1)} {TURNS_SINCE(-> OldNode)} {TURNS_SINCE(-> Main)} +This is a simple story. +~ tNew = 3 +* A +* B +- catch +{tKeep} - {tNew} +{TURNS_SINCE(-> Node1)} {TURNS_SINCE(-> OldNode)} {TURNS_SINCE(-> Main)} +Oh. +->DONE From 73b02dffbbe5aa8f3034e0c47fc13958e8ac074d Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 25 Mar 2026 13:26:29 +0100 Subject: [PATCH 14/24] WIP --- inkcpp/hungarian_solver.h | 10 ++++++++++ inkcpp_test/ListMatching.cpp | 23 ++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/inkcpp/hungarian_solver.h b/inkcpp/hungarian_solver.h index 6b181ee7..5a4ed4f3 100644 --- a/inkcpp/hungarian_solver.h +++ b/inkcpp/hungarian_solver.h @@ -1,9 +1,19 @@ #pragma once +/** Jaro Similarity of two null terminated byte strings. + * supports ASCII encoding, UTF-8 might be broken. + * https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance#Jaro_similarity + * @param lh,rh null terminated byte strings to compare + * @return similarity between lh and rh + * @retval 1 if equal + */ +float jaro_simularity(const char* lh, const char* rh); + /** Hungarian Algorithm to solve an assignment problem in O(N3). * https://en.wikipedia.org/wiki/Hungarian_algorithm * @param[in] cost matrix m x n * @param[out] matches optimal mapping m -> n * @param n number of jobs/assignments + * @return total cost of assigment */ float hungarian_solver(const float* cost, int* matches, size_t n); diff --git a/inkcpp_test/ListMatching.cpp b/inkcpp_test/ListMatching.cpp index 24724636..68090294 100644 --- a/inkcpp_test/ListMatching.cpp +++ b/inkcpp_test/ListMatching.cpp @@ -2,7 +2,27 @@ #include "../inkcpp/hungarian_solver.h" -SCENARIO("find best assigments", "[hungarian]") +namespace ink::runtime::internal +{ +struct MatchListValues { + const char* const* names; + const int* values; + size_t length; +}; + +float** cost_matrix(const MatchListValues& rh, const MatchListValues& lh, float drop_panelty); +float d_value(int lh, int rh, int lh_range[2], int rh_range[2]); +float d_label(const char* lh, const char* rh); +} // namespace ink::runtime::internal + +SCENARIO("santy check distance functions", "[list_match]") { + SECTION("Labels") { + + } + SECTION("Values") {} +} + +SCENARIO("find best assigments", "[list_match,hungarian]") { GIVEN("Example 1") { @@ -37,3 +57,4 @@ SCENARIO("find best assigments", "[hungarian]") CHECK(matches[2] == 0); } } + From 3812d284e619cc7023717e633a10df029988977a Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 25 Mar 2026 16:31:53 +0100 Subject: [PATCH 15/24] feat(Migration): add distance functions for labels and values --- inkcpp/hungarian_solver.cpp | 77 ++++++++++++++++++++++++++++++++++++ inkcpp/hungarian_solver.h | 12 ++++++ inkcpp_test/ListMatching.cpp | 66 ++++++++++++++++++++++++++++--- 3 files changed, 149 insertions(+), 6 deletions(-) diff --git a/inkcpp/hungarian_solver.cpp b/inkcpp/hungarian_solver.cpp index 2dd5c433..5f0016d6 100644 --- a/inkcpp/hungarian_solver.cpp +++ b/inkcpp/hungarian_solver.cpp @@ -1,4 +1,8 @@ #include "hungarian_solver.h" +#include "system.h" +#include +#include +#include #include class HungarienCtx @@ -119,3 +123,76 @@ float hungarian_solver(const float* cost, int* matches, size_t n) } return total_cost; } + +float jaro_simularity(const char* lh, const char* rh) +{ + const size_t lh_len = strlen(lh); + uint8_t lh_matched[256] = {}; + if (lh_len > sizeof(lh_matched) * 8) { + return 0; + } + const size_t rh_len = strlen(rh); + uint8_t rh_matched[256] = {}; + if (rh_len > sizeof(rh_matched) * 8) { + return 0; + } + + if ((lh_len == 1 && rh_len == 1) || lh_len == 0 || rh_len == 0) { + return 0; + } + size_t max_offset = (std::max(lh_len, rh_len) / 2) - 1; + float m = 0; + + for (int lh_idx = 0; static_cast(lh_idx) < lh_len; ++lh_idx) { + for (int rh_idx = std::max(lh_idx - static_cast(max_offset), 0); + static_cast(rh_idx) <= std::min(rh_len, lh_idx + max_offset); ++rh_idx) { + if (! (rh_matched[rh_idx / 8] & (1 << (rh_idx & 7))) + && tolower(rh[rh_idx]) == tolower(lh[lh_idx])) { + lh_matched[lh_idx / 8] |= 1 << (lh_idx & 7); + rh_matched[rh_idx / 8] |= 1 << (rh_idx & 7); + m += 1.; + break; + } + } + } + + if (m == 0) { + return 0; + } + int rh_idx = 0; + float t = 0; + for (int lh_idx = 0; static_cast(lh_idx) < lh_len; ++lh_idx) { + if (lh_matched[lh_idx / 8] & (1 << (lh_idx & 7))) { + int next_idx = rh_idx; + while (static_cast(next_idx) < rh_len) { + if (rh_matched[next_idx / 8] & 1 << (next_idx & 7)) { + rh_idx = next_idx + 1; + break; + } + next_idx += 1; + } + if (tolower(lh[lh_idx]) != tolower(rh[next_idx])) { + t += 1.; + } + } + } + t /= 2.; + return ((m / lh_len) + (m / rh_len) + ((m - t) / m)) / 3.f; +} + +static constexpr float P = 0.1; + +float jaro_winkler_simularity(const char* lh, const char* rh) +{ + float j = jaro_simularity(lh, rh); + int l = 0; + const char *l_iter, *r_iter; + // calculate length of common prefix + for (l_iter = lh, r_iter = rh; *l_iter && *r_iter && *l_iter == *r_iter; ++lh, ++rh) { + l += 1; + if (l == 4) { + break; + } + } + return j + l * P * (1 - j); +} diff --git a/inkcpp/hungarian_solver.h b/inkcpp/hungarian_solver.h index 5a4ed4f3..c455d0f8 100644 --- a/inkcpp/hungarian_solver.h +++ b/inkcpp/hungarian_solver.h @@ -2,6 +2,7 @@ /** Jaro Similarity of two null terminated byte strings. * supports ASCII encoding, UTF-8 might be broken. + * ignores case. * https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance#Jaro_similarity * @param lh,rh null terminated byte strings to compare * @return similarity between lh and rh @@ -9,6 +10,17 @@ */ float jaro_simularity(const char* lh, const char* rh); +/** Jaro Winkler Similarity of two null terminated byte strings. + * supports ASCII encoding, UTF-8 might be broken. + * ignores case. + * https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance#Jaro%E2%80%93Winkler_similarity + * @param lh,rh null terminated byte strings to compare + * @sa jaro_simularity + * @return similarity between lh and rh + * @retval 1 if equal + */ +float jaro_winkler_simularity(const char* lh, const char* rh); + /** Hungarian Algorithm to solve an assignment problem in O(N3). * https://en.wikipedia.org/wiki/Hungarian_algorithm * @param[in] cost matrix m x n diff --git a/inkcpp_test/ListMatching.cpp b/inkcpp_test/ListMatching.cpp index 68090294..2f018c66 100644 --- a/inkcpp_test/ListMatching.cpp +++ b/inkcpp_test/ListMatching.cpp @@ -2,6 +2,8 @@ #include "../inkcpp/hungarian_solver.h" +#include + namespace ink::runtime::internal { struct MatchListValues { @@ -15,14 +17,67 @@ float d_value(int lh, int rh, int lh_range[2], int rh_range[2]); float d_label(const char* lh, const char* rh); } // namespace ink::runtime::internal -SCENARIO("santy check distance functions", "[list_match]") { - SECTION("Labels") { - +SCENARIO("santy check distance functions", "[list_match]") +{ + SECTION("Labels") + { + SECTION("jaro_simularity") + { + GIVEN("Two Stings") + { + float j = jaro_simularity("FAREMVIEL", "FARMVILLE"); + CHECK_THAT(j, Catch::Matchers::WithinAbs(0.88, 0.01)); + } + GIVEN("Two Strings in different Casing, no impact ignore casing") + { + float j = jaro_simularity("FAREMVIEL", "farmville"); + CHECK_THAT(j, Catch::Matchers::WithinAbs(0.88, 0.01)); + } + GIVEN("Two strings with fill characters, small impact") + { + float j = jaro_simularity("FAREMVIEL", "FARMV_IL-LE"); + CHECK_THAT(j, Catch::Matchers::WithinAbs(0.83, 0.01)); + } + } + SECTION("jaro_winkler_simularity") + { + GIVEN("Two Strings wih without prefix") + { + float j = jaro_simularity("ZFAREMVIEL", "YFARMVILLE"); + float jw = jaro_winkler_simularity("ZFAREMVIEL", "YFARMVILLE"); + CHECK_THAT(jw, Catch::Matchers::WithinAbs(j, 0.01)); + } + GIVEN("Two Strings with prefix") + { + float j = jaro_simularity("FAREMVIEL", "FARMVILLE"); + float jw = jaro_winkler_simularity("FAREMVIEL", "FARMVILLE"); + CHECK(j < jw); + } + } + } + SECTION("Values") + { + int range1[] = {0, 20}; + int range2[] = {5, 35}; + GIVEN("Same Value") + { + float d = ink::runtime::internal::d_value(5, 5, range1, range1); + CHECK(d == 0); + d = ink::runtime::internal::d_value(5, 5, range1, range2); + CHECK(d == 0); + } + GIVEN("Different Value") + { + float d1 = ink::runtime::internal::d_value(10, 20, range1, range1); + float d2 = ink::runtime::internal::d_value(10, 20, range2, range2); + float d3 = ink::runtime::internal::d_value(10, 20, range1, range2); + CHECK(d3 == 0); // there are both in the center of their respected range + CHECK(d1 > d2); // same absolute distance in bigger range is a smaller distance + } } - SECTION("Values") {} } -SCENARIO("find best assigments", "[list_match,hungarian]") +SCENARIO("find best assigments", "[list_match][hungarian]") { GIVEN("Example 1") { @@ -57,4 +112,3 @@ SCENARIO("find best assigments", "[list_match,hungarian]") CHECK(matches[2] == 0); } } - From 629a288637822faf0fc5c5a2fa43ec2433f1e30d Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 27 Mar 2026 15:36:00 +0100 Subject: [PATCH 16/24] fix(assert): fixes defines (INKCPP_ENABLE... -> INK_ENABLE_...) to have formatted asserts again also added a format message for the endian assert --- inkcpp/header.cpp | 53 +++++++++++++++++++---------------------- inkcpp_cl/inkcpp_cl.cpp | 50 +++++++++++++++++++------------------- shared/public/system.h | 12 ++++------ 3 files changed, 56 insertions(+), 59 deletions(-) diff --git a/inkcpp/header.cpp b/inkcpp/header.cpp index e99d08cc..96d1e836 100644 --- a/inkcpp/header.cpp +++ b/inkcpp/header.cpp @@ -7,38 +7,35 @@ #include "header.h" #include "version.h" -namespace ink::internal { +namespace ink::internal +{ - header header::parse_header(const char *data) - { - header res; - const char* ptr = data; - res.endien = *reinterpret_cast(ptr); - ptr += sizeof(header::endian_types); +header header::parse_header(const char* data) +{ + header res; + const char* ptr = data; + res.endien = *reinterpret_cast(ptr); + ptr += sizeof(header::endian_types); - using v_t = decltype(header::ink_version_number); - using vcpp_t = decltype(header::ink_bin_version_number); + using v_t = decltype(header::ink_version_number); + using vcpp_t = decltype(header::ink_bin_version_number); - if (res.endien == header::endian_types::same) { - res.ink_version_number = - *reinterpret_cast(ptr); - ptr += sizeof(v_t); - res.ink_bin_version_number = - *reinterpret_cast(ptr); + if (res.endien == header::endian_types::same) { + res.ink_version_number = *reinterpret_cast(ptr); + ptr += sizeof(v_t); + res.ink_bin_version_number = *reinterpret_cast(ptr); - } else if (res.endien == header::endian_types::differ) { - res.ink_version_number = - swap_bytes(*reinterpret_cast(ptr)); - ptr += sizeof(v_t); - res.ink_bin_version_number = - swap_bytes(*reinterpret_cast(ptr)); - } else { - inkFail("Failed to parse endian encoding!"); - } + } else if (res.endien == header::endian_types::differ) { + res.ink_version_number = swap_bytes(*reinterpret_cast(ptr)); + ptr += sizeof(v_t); + res.ink_bin_version_number = swap_bytes(*reinterpret_cast(ptr)); + } else { + inkFail("Failed to parse endian encoding! %#04x", res.endien); + } - if (res.ink_bin_version_number != InkBinVersion) { - inkFail("InkCpp-version mismatch: file was compiled with different InkCpp-version!"); - } - return res; + if (res.ink_bin_version_number != InkBinVersion) { + inkFail("InkCpp-version mismatch: file was compiled with different InkCpp-version!"); } + return res; } +} // namespace ink::internal diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index 1d9bbdde..889c7d3a 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -155,33 +155,35 @@ int main(int argc, const char** argv) } // Open file and compile - try { - ink::compiler::compilation_results results; - std::ofstream fout(outputFilename, std::ios::binary | std::ios::out); - ink::compiler::run(inputFilename.c_str(), fout, &results); - fout.close(); - if (json_file_is_tmp_file) { - remove(inputFilename.c_str()); - } + if (inputFilename != outputFilename) { + try { + ink::compiler::compilation_results results; + std::ofstream fout(outputFilename, std::ios::binary | std::ios::out); + ink::compiler::run(inputFilename.c_str(), fout, &results); + fout.close(); + if (json_file_is_tmp_file) { + remove(inputFilename.c_str()); + } - // Report errors - for (auto& warn : results.warnings) { - std::cerr << "WARNING: " << warn << '\n'; - } - for (auto& err : results.errors) { - std::cerr << "ERROR: " << err << '\n'; - } + // Report errors + for (auto& warn : results.warnings) { + std::cerr << "WARNING: " << warn << '\n'; + } + for (auto& err : results.errors) { + std::cerr << "ERROR: " << err << '\n'; + } - if (results.errors.size() > 0 && playMode) { - std::cerr << "Cancelling play mode. Errors detected in compilation" << std::endl; - return -1; - } - } catch (std::exception& e) { - if (json_file_is_tmp_file) { - remove(inputFilename.c_str()); + if (results.errors.size() > 0 && playMode) { + std::cerr << "Cancelling play mode. Errors detected in compilation" << std::endl; + return -1; + } + } catch (std::exception& e) { + if (json_file_is_tmp_file) { + remove(inputFilename.c_str()); + } + std::cerr << "Unhandled InkBin compiler exception: " << e.what() << std::endl; + return 1; } - std::cerr << "Unhandled InkBin compiler exception: " << e.what() << std::endl; - return 1; } if (! playMode) { diff --git a/shared/public/system.h b/shared/public/system.h index f37dfce2..801f1ef5 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -236,24 +236,22 @@ void ink_assert(bool condition, const char* msg = nullptr, Args... args) msg = EMPTY; } if (! condition) { -#if defined(INKCPP_ENABLE_STL) || defined(INKCPP_ENABLE_CSTD) +#if defined(INK_ENABLE_STL) || defined(INK_ENABLE_CSTD) if constexpr (sizeof...(args) > 0) { size_t size = snprintf(nullptr, 0, msg, args...) + 1; char* message = static_cast(malloc(size)); snprintf(message, size, msg, args...); msg = message; - } else + } #endif - { #ifdef INK_ENABLE_EXCEPTIONS - throw ink_exception(msg); + throw ink_exception(msg); #elif defined(INK_ENABLE_CSTD) - fprintf(stderr, "Ink Assert: %s\n", msg); - abort(); + fprintf(stderr, "Ink Assert: %s\n", msg); + abort(); #else # warning no assertion handling this could lead to invalid code paths #endif - } } } #ifdef __GNUC__ From c89dce104154841688aecae52f9fe6c620b14664 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Mon, 30 Mar 2026 14:40:02 +0200 Subject: [PATCH 17/24] fix(assert): fixes defines (INKCPP_ENABLE... -> INK_ENABLE_...) to have formatted asserts again also added a format message for the endian assert --- inkcpp/hungarian_solver.cpp | 5 +- inkcpp/hungarian_solver.h | 3 +- inkcpp/list_table.cpp | 229 ++++++++++++++++++++++++++--------- inkcpp/list_table.h | 10 ++ inkcpp_test/ListMatching.cpp | 59 +++++++++ 5 files changed, 249 insertions(+), 57 deletions(-) diff --git a/inkcpp/hungarian_solver.cpp b/inkcpp/hungarian_solver.cpp index 5f0016d6..19152a4e 100644 --- a/inkcpp/hungarian_solver.cpp +++ b/inkcpp/hungarian_solver.cpp @@ -106,7 +106,7 @@ class HungarienCtx } }; -float hungarian_solver(const float* cost, int* matches, size_t n) +float hungarian_solver(const float* cost, int* matches, size_t n, float threshold) { HungarienCtx ctx(cost, n); for (size_t row = 1; row <= n; ++row) { @@ -120,6 +120,9 @@ float hungarian_solver(const float* cost, int* matches, size_t n) int row = ctx[col] - 1; matches[row] = col - 1; total_cost += cost[row * n + matches[row]]; + if (threshold != 0 && cost[row * n + matches[row]] >= threshold) { + matches[row] = -1; + } } return total_cost; } diff --git a/inkcpp/hungarian_solver.h b/inkcpp/hungarian_solver.h index c455d0f8..ac16c129 100644 --- a/inkcpp/hungarian_solver.h +++ b/inkcpp/hungarian_solver.h @@ -25,7 +25,8 @@ float jaro_winkler_simularity(const char* lh, const char* rh); * https://en.wikipedia.org/wiki/Hungarian_algorithm * @param[in] cost matrix m x n * @param[out] matches optimal mapping m -> n + * @param threshold matches with a value higher than threshold will be set to `-1`, use 0 to ignore. * @param n number of jobs/assignments * @return total cost of assigment */ -float hungarian_solver(const float* cost, int* matches, size_t n); +float hungarian_solver(const float* cost, int* matches, size_t n, float threshold = 0); diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index dcc43d21..8951b707 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -6,6 +6,7 @@ */ #include "list_table.h" #include "config.h" +#include "hungarian_solver.h" #include "system.h" #include "traits.h" #include "header.h" @@ -83,6 +84,24 @@ list_table::list list_table::create() return new_entry; } +list_table::list list_table::create_at(size_t idx) +{ + if (idx < _entry_state.size()) { + if (_entry_state[idx] == state::empty) { + _entry_state[idx] = state::used; + return list(idx); + } + return list(-1); + } + while (_entry_state.size() <= idx) { + _entry_state.push() = state::empty; + for (int i = 0; i < _entrySize; ++i) { + _data.push() = 0; + } + } + return list(idx); +} + void list_table::clear_usage() { for (state& s : _entry_state) { @@ -270,6 +289,15 @@ list_table::list list_table::create_permament() return res; } +list_table::list list_table::create_permament_at(size_t idx) +{ + list res = create_at(idx); + if (res.lid >= 0) { + _entry_state[res.lid] = state::permanent; + } + return res; +} + list_table::list& list_table::add_inplace(list& lh, list_flag rh) { if (rh.list_id < 0) @@ -859,11 +887,29 @@ config::statistics::list_table list_table::statistics() const }; } +/** Distance of two lists based on their contained values. + * https://en.wikipedia.org/wiki/Jaccard_index + * @param lh,rh flag indexes contained in the lists + * @param matches mapping from lh -> rh, -1 for dropped + */ +float d_contains(const int lh[2], const int rh[2], const int* matches) +{ + int n_union = (lh[1] - lh[0]) + (rh[1] - rh[0]); + int n_intersection = 0; + for (int i = lh[0]; i < lh[1]; ++i) { + if (matches[i] >= rh[0] && matches[i] < rh[1]) { + n_intersection += 1; + } + } + n_union -= n_intersection; + return static_cast(n_intersection) / n_union; +} + /** Distance function for string labels. * @param lh,rh null terminated ASCII strings to compare * @return 0 if identical */ -float d_label(const char* lh, const char* rh) { return 0; } +float d_label(const char* lh, const char* rh) { return 1.f - jaro_winkler_simularity(lh, rh); } /** Distance function for two values. * @param lh,rh numeric values to compare @@ -883,6 +929,12 @@ float d_value(int lh, int rh, int lh_range[2], int rh_range[2]) return res; } +struct MatchList { + const size_t* list_ends; + const char* const* names; + size_t length; +}; + struct MatchListValues { const char* const* names; const int* values; @@ -903,24 +955,44 @@ void get_range(const MatchListValues& values, int range[2]) } } -float** cost_matrix(const MatchListValues& lh, const MatchListValues& rh, float drop_penalty) +float* cost_matrix( + const MatchList& lh, const MatchList& rh, const int* value_matches, float drop_penalty +) { - size_t n_flags = lh.length > rh.length ? lh.length : rh.length; - float** matrix = reinterpret_cast( - malloc(sizeof(float*) * n_flags + sizeof(float) * n_flags * n_flags) - ); - for (size_t i = 0; i < n_flags; ++i) { - matrix[i] = reinterpret_cast(matrix + n_flags) + i * n_flags; + size_t n_lists = lh.length > rh.length ? lh.length : rh.length; + float* matrix = new float[n_lists * n_lists]; + for (size_t i = 0; i < lh.length; ++i) { + for (size_t j = 0; j < rh.length; ++j) { + float dl = d_label(lh.names[i], rh.names[j]); + int lh_range[] = {lh.list_ends[i], lh.list_ends[i + 1]}; + int rh_range[] = {rh.list_ends[j], rh.list_ends[j + 1]}; + float dv = d_contains(lh_range, rh_range, value_matches); + } } - int lh_range[2], rh_range[2]; + return matrix; +} + +float* cost_matrix(const MatchListValues& lh, const MatchListValues& rh, float drop_penalty) +{ + size_t n_flags = lh.length > rh.length ? lh.length : rh.length; + float* matrix = new float[n_flags * n_flags]; + int lh_range[2], rh_range[2]; get_range(lh, lh_range); get_range(rh, rh_range); for (size_t i = 0; i < lh.length; ++i) { for (size_t j = 0; j < rh.length; ++j) { - float dl = d_label(lh.names[i], rh.names[j]); - float dv = d_value(lh.values[i], rh.values[j], lh_range, rh_range); - matrix[i][j] = dl * 0.8 + dv * 0.2; + float dl = d_label(lh.names[i], rh.names[j]); + float dv = d_value(lh.values[i], rh.values[j], lh_range, rh_range); + matrix[i * n_flags + j] = dl * 0.8 + dv * 0.2; + } + for (size_t j = rh.length; j < n_flags; ++j) { + matrix[i * n_flags + j] = drop_penalty; + } + } + for (size_t i = lh.length; i < n_flags; ++i) { + for (size_t j = 0; j < rh.length; ++j) { + matrix[i * n_flags + j] = drop_penalty; } } return matrix; @@ -935,63 +1007,110 @@ list_table::list_table( bool list_table::migrate(const char* old_list_metadata, const ink::internal::header& header) { - return true; - // FIXME: old data are decoded with old header list_table old_ref_table(old_list_metadata, header, _data, _entry_state); _data.clear(); _entry_state.clear(); + // find best mapping between old and new list elements + // + c_ij(value) = min(|v_i - v_j|/Rv,1) + // + c_ij(name) = levenshtein, cosine n-grams, jaro-winkler + // + c_ij(position_in_list) = min(|p_i - p_j|/Rp, 1) + // find best mapping between lists + // + c_ij(name) = levenshtein, cosine n-grams, jaro-winkler + // + c_ij(entries) = entries existing in both + // 1. h_entry_map = high confidents mapping of list elements (value, name, position_in_list) + // 2. h_list_map = high confident mapping of lists (name, h_entry_map) + // 3. entry_map = mapping of list elements (value, name, position_in_list, + // h_list_map[list_name]) + // 4. list_map = mapping of lists (name, entry_map) + + + // high confidance list value matches + constexpr float HIGH_CONFIDANCE_DROP_PANELTY = 0.3; + constexpr float LOW_CONFIDANCE_DROP_PANELTY = 0.6; + float* value_matrix = cost_matrix( + MatchListValues{_flag_names.data(), _flag_values.data(), numFlags()}, + MatchListValues{ + old_ref_table._flag_names.data(), old_ref_table._flag_values.data(), + old_ref_table.numFlags() + }, + LOW_CONFIDANCE_DROP_PANELTY + ); + const int n_flags = std::max(numFlags(), old_ref_table.numFlags()); + int* value_matches = new int[n_flags]; + const float value_cost_high + = hungarian_solver(value_matrix, value_matches, n_flags, HIGH_CONFIDANCE_DROP_PANELTY); + + // list matches + float* list_matrix = cost_matrix( + MatchList{_list_end.data(), _list_names.data(), numLists()}, + MatchList{ + old_ref_table._list_end.data(), old_ref_table._list_names.data(), old_ref_table.numLists() + }, + value_matches, LOW_CONFIDANCE_DROP_PANELTY + ); + const int n_lists = std::max(numLists(), old_ref_table.numLists()); + int* list_matches = new int[n_lists]; + const float total_list_cost + = hungarian_solver(list_matrix, list_matches, n_lists, LOW_CONFIDANCE_DROP_PANELTY); + + // low confidence list_value matches + const float value_cost_low + = hungarian_solver(value_matrix, value_matches, n_flags, LOW_CONFIDANCE_DROP_PANELTY); + for (size_t idx = 0; idx < old_ref_table._entry_state.size(); ++idx) { // migrate list new_list{-1}; switch (old_ref_table._entry_state[idx]) { - case state::permanent: new_list = create_permament(); break; - case state::used: new_list = create(); break; + case state::permanent: new_list = create_permament_at(idx); break; + case state::used: new_list = create_at(idx); break; default: continue; } - inkAssert(new_list.lid == idx, "Did not sequencially restore/migrate list"); - if (new_list.lid != idx) { - return false; + inkAssert(new_list.lid >= 0, "Failed to create new list entry for migration."); + inkAssert( + static_cast(new_list.lid) != idx, + "At position list creation failed with different valid idx." + ); + const data_t* entry = old_ref_table.getPtr(idx); + data_t* new_entry = getPtr(idx); + bool migrated = false; + for (size_t i = 0; i < old_ref_table.numLists(); ++i) { + if (old_ref_table.hasList(entry, i)) { + bool hit = false; + for (size_t j = old_ref_table.listBegin(i); j < old_ref_table._list_end[j]; ++j) { + if (old_ref_table.hasFlag(entry, j) && old_ref_table._flag_names[j]) { + if (value_matches[j] != -1) { + hit = true; + migrated = true; + for (size_t k = 0; _list_end[k] < static_cast(value_matches[j]); ++k) { + setList(new_entry, k); + } + setFlag(new_entry, value_matches[j]); + } + } + } + // keep list if list has match but all values where dropped + if (! hit && list_matches[i] != -1) { + setList(new_entry, list_matches[i]); + migrated = true; + } + } } - // find best mapping between old and new list elements - // + c_ij(value) = min(|v_i - v_j|/Rv,1) - // + c_ij(name) = levenshtein, cosine n-grams, jaro-winkler - // + c_ij(position_in_list) = min(|p_i - p_j|/Rp, 1) - // find best mapping between lists - // + c_ij(name) = levenshtein, cosine n-grams, jaro-winkler - // + c_ij(entries) = entries existing in both - // 1. h_entry_map = high confidents mapping of list elements (value, name, position_in_list) - // 2. h_list_map = high confident mapping of lists (name, h_entry_map) - // 3. entry_map = mapping of list elements (value, name, position_in_list, - // h_list_map[list_name]) - // 4. list_map = mapping of lists (name, entry_map) - - // generate cost matrix - constexpr float DROP_PANELTY = 0.3; - float** matrix = cost_matrix( - MatchListValues{ - _flag_names.data(), - _flag_values.data(), - _flag_names.size(), - }, - MatchListValues{ - old_ref_table._flag_names.data(), - old_ref_table._flag_values.data(), - old_ref_table._flag_names.size(), - }, - DROP_PANELTY - ); - - - free(matrix); - - for (const named_flag_itr::position& flag : old_ref_table.named_flags(new_list)) { - int value = old_ref_table.get_flag_value(flag.flag); - const char* list_name = old_ref_table._list_names[flag.flag.list_id]; - const char* flag_name = flag.name; + // drop list + if (! migrated) { + // FIXME: remove list ? + // _entry_state [idx] = state::empty; + return false; } + // FIXME: use Assert instead? + // inkAssert(migrated, "Migrating list @%d would lead to an empty list", idx); } + + delete[] list_matches; + delete[] value_matches; + delete[] value_matrix; + delete[] list_matrix; return true; } diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index b2434cd6..7edbbb38 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -266,6 +266,16 @@ class list_table : public snapshot_interface list_interface* handout_list(list); private: + /** create a list with id == idx. + * @attention used for migration only + * @sa create() + */ + list create_at(size_t idx); + /** create permanent list list id == idx. + * @attention used for migration only + * @se create_permenant_at() + */ + list create_permament_at(size_t idx); void copy_lists(const data_t* src, data_t* dst); static constexpr size_t bits_per_data = sizeof(data_t) * 8U; diff --git a/inkcpp_test/ListMatching.cpp b/inkcpp_test/ListMatching.cpp index 2f018c66..c776b14c 100644 --- a/inkcpp_test/ListMatching.cpp +++ b/inkcpp_test/ListMatching.cpp @@ -13,6 +13,7 @@ struct MatchListValues { }; float** cost_matrix(const MatchListValues& rh, const MatchListValues& lh, float drop_panelty); +float d_contains(const int lh[2], const int rh[2], const int* matches); float d_value(int lh, int rh, int lh_range[2], int rh_range[2]); float d_label(const char* lh, const char* rh); } // namespace ink::runtime::internal @@ -75,6 +76,49 @@ SCENARIO("santy check distance functions", "[list_match]") CHECK(d1 > d2); // same absolute distance in bigger range is a smaller distance } } + SECTION("Sets") + { + GIVEN("Equal Sets") + { + int lh[] = {5, 10}; + int rh[] = {5, 10}; + int matches[] = {0, 0, 0, 0, 0, 5, 6, 7, 8, 9}; + float d = ink::runtime::internal::d_contains(lh, rh, matches); + CHECK_THAT(d, Catch::Matchers::WithinAbs(1, 0.001)); + } + GIVEN("Dropped Values") + { + int lh[] = {5, 10}; + int rh[] = {5, 8}; + int matches[] = {0, 0, 0, 0, 0, 5, -1, -1, 6, 7}; + float d = ink::runtime::internal::d_contains(lh, rh, matches); + CHECK_THAT(d, Catch::Matchers::WithinAbs(0.6, 0.001)); + } + GIVEN("New Values") + { + int lh[] = {5, 8}; + int rh[] = {5, 10}; + int matches[] = {0, 0, 0, 0, 0, 5, 6, 7}; + float d = ink::runtime::internal::d_contains(lh, rh, matches); + CHECK_THAT(d, Catch::Matchers::WithinAbs(0.6, 0.001)); + } + GIVEN("Swapped Values") + { + int lh[] = {5, 10}; + int rh[] = {5, 10}; + int matches[] = {0, 0, 0, 0, 0, 5, 9, 6, 8, 7}; + float d = ink::runtime::internal::d_contains(lh, rh, matches); + CHECK_THAT(d, Catch::Matchers::WithinAbs(1, 0.001)); + } + GIVEN("Changed Values") + { + int lh[] = {5, 10}; + int rh[] = {5, 10}; + int matches[] = {0, 0, 0, 0, 0, 5, 9, -1, -1, -1}; + float d = ink::runtime::internal::d_contains(lh, rh, matches); + CHECK_THAT(d, Catch::Matchers::WithinAbs(0.25, 0.001)); + } + } } SCENARIO("find best assigments", "[list_match][hungarian]") @@ -111,4 +155,19 @@ SCENARIO("find best assigments", "[list_match][hungarian]") CHECK(matches[1] == 1); CHECK(matches[2] == 0); } + GIVEN("With Example 1 Threshold") { + // clang-format off + float cost[] = { + 8/**/, 5 , 9 , + 4 , 2 , 4/**/, + 7 , 3/**/, 8 , + }; + // clang-format on + int matches[3]; + float total_cost = hungarian_solver(cost, matches, 3, 5); + CHECK(total_cost == 15.f); + CHECK(matches[0] == -1); + CHECK(matches[1] == 2); + CHECK(matches[2] == 1); + } } From 0038b3014209509737d4157b95af4265c38f62ba Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Mon, 30 Mar 2026 17:41:53 +0200 Subject: [PATCH 18/24] fix(Snapshot): only access optional data if accasible --- inkcpp/snapshot_impl.cpp | 18 ++++++++++++------ inkcpp/story_impl.cpp | 3 ++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/inkcpp/snapshot_impl.cpp b/inkcpp/snapshot_impl.cpp index cf083b1a..7c6f2a3a 100644 --- a/inkcpp/snapshot_impl.cpp +++ b/inkcpp/snapshot_impl.cpp @@ -98,7 +98,9 @@ snapshot_impl::snapshot_impl(const globals_impl& globals) // write lookup table ptr += sizeof(header); { - size_t offset = static_cast((ptr - data) + (_header.num_runners + 1) * sizeof(size_t)); + size_t offset = static_cast( + (ptr - data) + (_header.num_runners + 1 + migratable) * sizeof(size_t) + ); memcpy(ptr, &offset, sizeof(offset)); ptr += sizeof(offset); offset += globals.snap(nullptr, snapper); @@ -107,17 +109,21 @@ snapshot_impl::snapshot_impl(const globals_impl& globals) ptr += sizeof(offset); offset += node->object->snap(nullptr, snapper); } - memcpy(ptr, &offset, sizeof(offset)); - ptr += sizeof(offset); - offset += globals._owner->list_meta_size(); + if (migratable) { + memcpy(ptr, &offset, sizeof(offset)); + ptr += sizeof(offset); + offset += globals._owner->list_meta_size(); + } } ptr += globals.snap(ptr, snapper); for (auto node = globals._runners_start; node; node = node->next) { ptr += node->object->snap(ptr, snapper); } - memcpy(ptr, globals._owner->list_meta(), globals._owner->list_meta_size()); - ptr += globals._owner->list_meta_size(); + if (migratable) { + memcpy(ptr, globals._owner->list_meta(), globals._owner->list_meta_size()); + ptr += globals._owner->list_meta_size(); + } } snapshot_impl::snapshot_impl(const unsigned char* data, size_t length, bool managed) diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index 6094c3ca..cc8868f5 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -276,7 +276,8 @@ runner story_impl::new_runner_from_snapshot(const snapshot& data, globals store, auto end = run->snap_load(snapshot.get_runner_snap(idx), loader); inkAssert( (idx + 1 < snapshot.num_runners() && end == snapshot.get_runner_snap(idx + 1)) - || end == snapshot.get_data() + snapshot.get_data_len(), + || end == snapshot.get_data() + snapshot.get_data_len() + || end == snapshot.get_list_metadata(), "not all data were used for runner reconstruction" ); if (hash() != snapshot.hash()) { From f7c94ec8726aaaf0f8b9863bd38415f572ca60ce Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 1 Apr 2026 12:11:43 +0200 Subject: [PATCH 19/24] fix(Snapshot): visit count migration now can handle different capacity growing rates --- inkcpp/array.h | 19 +++++++++---- inkcpp/globals_impl.cpp | 55 ++++++++++++++++++++++++------------ inkcpp/globals_impl.h | 2 ++ inkcpp/list_table.cpp | 2 +- inkcpp/runner_impl.cpp | 5 ++++ inkcpp/snapshot_impl.h | 5 +++- inkcpp_test/CMakeLists.txt | 3 +- inkcpp_test/ListMatching.cpp | 35 ++++++++++++++++++++++- inkcpp_test/Migration.cpp | 40 +++++++++++++++++++++++++- 9 files changed, 138 insertions(+), 28 deletions(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index 9a7537be..ff15dccc 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -326,6 +326,12 @@ class basic_restorable_array : public snapshot_interface // size of the array inline size_t capacity() const { return _capacity; } + inline size_t loaded_capacity() const + { + inkAssert(_loaded_capacity != ~0, "This object was not loaded from a snapshot."); + return _loaded_capacity; + } + // only const indexing is supported due to save/restore system inline const T& operator[](size_t index) const { return get(index); } @@ -372,6 +378,9 @@ class basic_restorable_array : public snapshot_interface // size of both _array and _temp size_t _capacity; + // if loaded with snap_load, this value was the original size, the current capacaty might be + // higher + size_t _loaded_capacity = ~0; // null const T _null; @@ -565,18 +574,18 @@ inline const unsigned char* { auto ptr = data; ptr = snap_read(ptr, _saved); - decltype(_capacity) capacity; - ptr = snap_read(ptr, capacity); + ptr = snap_read(ptr, _loaded_capacity); if (buffer() == nullptr) { - static_cast&>(*this).resize(capacity); + static_cast&>(*this).resize(_loaded_capacity); } inkAssert( - _capacity >= capacity, "New config does not allow for necessary size used by this snapshot!" + _capacity >= _loaded_capacity, + "New config does not allow for necessary size used by this snapshot!" ); T null; ptr = snap_read(ptr, null); inkAssert(null == _null, "null value is different to snapshot!"); - for (size_t i = 0; i < _capacity; ++i) { + for (size_t i = 0; i < _loaded_capacity; ++i) { ptr = snap_read(ptr, _array[i]); ptr = snap_read(ptr, _temp[i]); } diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index fb884a41..57cadc5a 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -26,22 +26,27 @@ globals_impl::globals_impl(const story_impl* story) _visit_counts.resize(_num_containers); if (_lists) { // initialize static lists - const list_flag* flags = story->lists(); + init_static_list_flags(); + } +} + +void globals_impl::init_static_list_flags() +{ + const list_flag* flags = _owner->lists(); + while (*flags != null_flag) { + list_table::list l = _lists.create_permament(); while (*flags != null_flag) { - list_table::list l = _lists.create_permament(); - while (*flags != null_flag) { - list_flag flag = _lists.external_fvalue_to_internal(*flags); - _lists.add_inplace(l, flag); - ++flags; - } + list_flag flag = _lists.external_fvalue_to_internal(*flags); + _lists.add_inplace(l, flag); ++flags; } - for (const auto& flag : _lists.named_flags()) { - set_variable( - hash_string(flag.name), - value{}.set(list_flag{flag.flag.list_id, flag.flag.flag}) - ); - } + ++flags; + } + for (const auto& flag : _lists.named_flags()) { + set_variable( + hash_string(flag.name), + value{}.set(list_flag{flag.flag.list_id, flag.flag.flag}) + ); } } @@ -284,13 +289,20 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa _globals_initialized = true; ptr = snap_read(ptr, _turn_cnt); ptr = _visit_counts.snap_load(ptr, loader); - size_t old_capacity = _visit_counts.capacity(); + size_t old_capacity = _visit_counts.loaded_capacity(); // shuffle values if needed if (loader.migratable) { - _visit_counts.resize(_owner->num_containers()); + // extend array if needed + if (_visit_counts.capacity() < _owner->num_containers()) { + _visit_counts.resize(_owner->num_containers()); + } _visit_counts.save(); } - inkAssert(old_capacity == _visit_counts.capacity(), "Missmatching number of tracked containers."); + + inkAssert( + _visit_counts.capacity() >= _owner->num_containers(), + "Missmatching number of tracked containers." + ); for (size_t i = 0; i < old_capacity; ++i) { hash_t path; ptr = snap_read(ptr, path); @@ -320,8 +332,15 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa bool globals_impl::migrate_new_globals(globals_impl& new_globals, const char* list_metadata) { - return _variables.migrate(new_globals._variables) - && _lists.migrate(list_metadata, _owner->get_header()); + bool success = _variables.migrate(new_globals._variables) + && ((! _lists) || _lists.migrate(list_metadata, _owner->get_header())); + if (! success) { + return false; + } + if (_lists) { + init_static_list_flags(); + } + return true; } config::statistics::global globals_impl::statistics() const diff --git a/inkcpp/globals_impl.h b/inkcpp/globals_impl.h index 4f13fa40..6b87f76d 100644 --- a/inkcpp/globals_impl.h +++ b/inkcpp/globals_impl.h @@ -30,6 +30,8 @@ class globals_impl final { friend snapshot_impl; + void init_static_list_flags(); + public: size_t snap(unsigned char* data, const snapper&) const; const unsigned char* snap_load(const unsigned char* data, const loader&); diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index 8951b707..f1544b9f 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -1062,7 +1062,7 @@ bool list_table::migrate(const char* old_list_metadata, const ink::internal::hea // migrate list new_list{-1}; switch (old_ref_table._entry_state[idx]) { - case state::permanent: new_list = create_permament_at(idx); break; + // permanent list are a result of the list definition and do not need to be migrated case state::used: new_list = create_at(idx); break; default: continue; } diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 149d23ef..a94daba3 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -683,6 +683,7 @@ size_t runner_impl::snap(unsigned char* data, snapper& snapper) const unsigned char* ptr = data; bool should_write = data != nullptr; std::uintptr_t offset = _ptr != nullptr ? _ptr - _story->instructions() : 0; + // TODO: remove ptr = snap_write(ptr, _story->container_hash(_ptr - 6), should_write); ptr = snap_write(ptr, offset, should_write); offset = _backup - _story->instructions(); @@ -726,6 +727,7 @@ const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& l auto ptr = data; std::uintptr_t offset; hash_t current_knot_name; + // TODO: remove ptr = snap_read(ptr, current_knot_name); ptr = snap_read(ptr, offset); _ptr = offset == 0 ? nullptr : _story->instructions() + offset; @@ -841,6 +843,9 @@ bool runner_impl::migrate_to(hash_t path) } } } + // rebuild container stack to display new offsets + _container.clear(); + _ptr = nullptr; jump(destination, false, true); return true; } diff --git a/inkcpp/snapshot_impl.h b/inkcpp/snapshot_impl.h index af3c03ba..2c684df2 100644 --- a/inkcpp/snapshot_impl.h +++ b/inkcpp/snapshot_impl.h @@ -128,7 +128,10 @@ class snapshot_impl final : public snapshot size_t get_offset(size_t idx) const { - inkAssert(idx <= _header.num_runners, "Out of Bound access for runner in snapshot."); + inkAssert( + idx <= _header.num_runners + (can_be_migrated() ? 1 : 0), + "Out of Bound access for runner in snapshot." + ); return reinterpret_cast(_file + sizeof(header))[idx]; } }; diff --git a/inkcpp_test/CMakeLists.txt b/inkcpp_test/CMakeLists.txt index 8518cad4..d8c01499 100644 --- a/inkcpp_test/CMakeLists.txt +++ b/inkcpp_test/CMakeLists.txt @@ -29,7 +29,8 @@ add_executable( EmptyStringForDivert.cpp MoveTo.cpp ListMatching.cpp - Fixes.cpp) + Fixes.cpp + Migration.cpp) target_link_libraries(inkcpp_test PUBLIC inkcpp inkcpp_compiler inkcpp_shared) target_include_directories(inkcpp_test PRIVATE ../shared/private/) diff --git a/inkcpp_test/ListMatching.cpp b/inkcpp_test/ListMatching.cpp index c776b14c..003e10f8 100644 --- a/inkcpp_test/ListMatching.cpp +++ b/inkcpp_test/ListMatching.cpp @@ -1,8 +1,12 @@ #include "catch.hpp" #include "../inkcpp/hungarian_solver.h" +#include +#include +#include +#include -#include +using namespace ink::runtime; namespace ink::runtime::internal { @@ -171,3 +175,32 @@ SCENARIO("find best assigments", "[list_match][hungarian]") CHECK(matches[2] == 1); } } + +SCENARIO("Simple List Migration stories", "[list_match]") +{ + GIVEN("Splitted List") + { + std::unique_ptr ink_a{story::from_file(INK_TEST_RESOURCE_DIR "ListMatchStoryA.bin")}; + std::unique_ptr ink_b{story::from_file(INK_TEST_RESOURCE_DIR "ListMatchStoryB.bin")}; + globals globals_a = ink_a->new_globals(); + runner thread_a = ink_a->new_runner(globals_a); + WHEN("Load new list extensions, split and typo fix") + { + CHECK(thread_a->getline() == "You are currently at Flor\n"); + REQUIRE(thread_a->has_choices()); + thread_a->choose(0); + std::unique_ptr snap{thread_a->create_snapshot()}; + REQUIRE(snap->can_be_migrated()); + CHECK( + thread_a->getall() + == "More\nYou are still at Flor, all posibilities are Flor, Balcony, Kitchen, Garden\n" + ); + auto globals_b = ink_b->new_globals_from_snapshot(*snap); + auto thread_b = ink_b->new_runner_from_snapshot(*snap); + CHECK( + thread_b->getall() + == "More\nYou are still at Floor, all posibilities are Floor, Kitchen, Livingroom\n" + ); + } + } +} diff --git a/inkcpp_test/Migration.cpp b/inkcpp_test/Migration.cpp index 748a256a..e3e17764 100644 --- a/inkcpp_test/Migration.cpp +++ b/inkcpp_test/Migration.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "snapshot.h" #include "../snapshot_impl.h" #include @@ -8,6 +7,7 @@ #include #include #include +#include using namespace ink::runtime; @@ -185,3 +185,41 @@ SCENARIO("Simple isolated migration tests.") } } } + +SCENARIO("Migration Test for small story") +{ + std::unique_ptr before{story::from_file(INK_TEST_RESOURCE_DIR "MigrationBefore.bin")}; + std::unique_ptr after{story::from_file(INK_TEST_RESOURCE_DIR "MigrationAfter.bin")}; + GIVEN("Test sequcen with multiple loads") + { + runner thread_before = before->new_runner(); + REQUIRE(thread_before->getall() == "We're going to the seaside!\n"); + CHECK(thread_before->num_choices() == 3); + CHECK(thread_before->get_choice(0)->text() == std::string("Make a sand castle")); + CHECK(thread_before->get_choice(1)->text() == std::string("Go swimming")); + CHECK(thread_before->get_choice(2)->text() == std::string("Time to go home")); + thread_before->choose(0); + REQUIRE(thread_before->getall() == "We made a great sand castle, it even has a moat!\nWe're going to the seaside!\nSo far we've done the following: SandCastle\n"); + CHECK(thread_before->num_choices() == 3); + CHECK(thread_before->get_choice(0)->text() == std::string("Make a sand castle")); + CHECK(thread_before->get_choice(1)->text() == std::string("Go swimming")); + CHECK(thread_before->get_choice(2)->text() == std::string("Time to go home")); + + thread_before->choose(1); + std::unique_ptr snap1{thread_before->create_snapshot()}; + REQUIRE(thread_before->getall() == "We swim and swam, it was delightful!\nWe're going to the seaside!\nSo far we've done the following: Swimming, SandCastle\n"); + + CHECK(thread_before->num_choices() == 2); + CHECK(thread_before->get_choice(0)->text() == std::string("Make a sand castle")); + CHECK(thread_before->get_choice(1)->text() == std::string("Time to go home")); + + runner thread_after = after->new_runner_from_snapshot(*snap1); + REQUIRE(thread_after->getall() == "We swim and swam, it was delightful!\nWe're going to the seaside!\nSo far we've done the following: Swimming, SandCastle\n"); + CHECK(thread_after->num_choices() == 3); + CHECK(thread_after->get_choice(0)->text() == std::string("Make a sand castle")); + CHECK(thread_after->get_choice(1)->text() == std::string("Get Ice Cream")); + CHECK(thread_after->get_choice(2)->text() == std::string("Time to go home")); + thread_after->choose(1); + REQUIRE(thread_after->getall() == "We got ice cream, mine was raspberry!\nWe're going to the seaside!\nSo far we've done the following: Swimming, SandCastle, IceCream\n"); + } +} From e573b85d56628c0fab6f5b7864217c02a58368a3 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Tue, 7 Apr 2026 11:34:21 +0200 Subject: [PATCH 20/24] fix(Migration): also migrate permanent lists Permanent lists are used also as base for variable --- README.md | 2 +- inkcpp/array.h | 14 ++--- inkcpp/include/snapshot.h | 29 +++++++++ inkcpp/list_table.cpp | 94 ++++++++++++++++------------- inkcpp/runner_impl.cpp | 1 - inkcpp/story_impl.cpp | 2 +- inkcpp_c/inkcpp.cpp | 5 ++ inkcpp_python/src/module.cpp | 10 ++- inkcpp_test/Fixes.cpp | 4 +- inkcpp_test/ListMatching.cpp | 50 +++++++-------- inkcpp_test/ink/ListMatchStoryA.ink | 7 +++ inkcpp_test/ink/ListMatchStoryB.ink | 9 +++ inkcpp_test/ink/MigrationAfter.ink | 31 ++++++++++ inkcpp_test/ink/MigrationBefore.ink | 25 ++++++++ 14 files changed, 201 insertions(+), 82 deletions(-) create mode 100644 inkcpp_test/ink/ListMatchStoryA.ink create mode 100644 inkcpp_test/ink/ListMatchStoryB.ink create mode 100644 inkcpp_test/ink/MigrationAfter.ink create mode 100644 inkcpp_test/ink/MigrationBefore.ink diff --git a/README.md b/README.md index 64478826..5ab46ef8 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Adapt `TargetPlatforms` as nessesarry. You might also want to install the Plugin Nice features for testing: + predefined choice selection `echo 1 2 1 | inkpp-cl -p story.(ink|json|bin)` + create snapshots to shorten testing: - + create snapshot by entering `-1` as choice `echo 1 2 -1 | inkcpp-cl -p story.ink` + + create snapshot by entering `-1` as choice `echo 1 2 -1 1 | inkcpp-cl -p story.ink` + load snapshot as an additional argument `echo 1 | inkcpp-cl -p story.snap story.ink` ## Including in C++ Code diff --git a/inkcpp/array.h b/inkcpp/array.h index ff15dccc..d727380d 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -20,7 +20,7 @@ namespace ink::runtime::internal * @tparam simple if the object has a trivial destructor, so delete[](char*) can be used instead of * calling the constructor. * @tparam dynamic if the memory should be allocated on the heap and grow if needed - * @tparam initialCapacitiy number of elements to allocate at construction, if !dynamic, this is + * @tparam initial capacity number of elements to allocate at construction, if !dynamic, this is * allocated in place and can not be changed. */ template @@ -312,7 +312,7 @@ class basic_restorable_array : public snapshot_interface clear_temp(); } - // == Non-Copyable == + // not copyable basic_restorable_array(const basic_restorable_array&) = delete; basic_restorable_array& operator=(const basic_restorable_array&) = delete; @@ -372,15 +372,15 @@ class basic_restorable_array : public snapshot_interface // real values live here T* _array; - // we store values here when we're in save mode + // we store values here when we're in safe mode // they're copied on a call to forget() T* _temp; // size of both _array and _temp size_t _capacity; - // if loaded with snap_load, this value was the original size, the current capacaty might be + // if loaded with snap_load, this value was the original size, the current capacity might be // higher - size_t _loaded_capacity = ~0; + size_t _loaded_capacity = static_cast(~0); // null const T _null; @@ -406,7 +406,7 @@ inline const T& basic_restorable_array::get(size_t index) const { check_index(index); - // If we're in save mode and we have a value at that index, return that instead + // If we're in safe mode, and we have a value at that index, return that instead if (_saved && _temp[index] != _null) { return _temp[index]; } @@ -445,7 +445,7 @@ inline void basic_restorable_array::forget() { // Run through the _temp array for (size_t i = 0; i < _capacity; i++) { - // Copy if there's values + // Copy if there are values if (_temp[i] != _null) { _array[i] = _temp[i]; } diff --git a/inkcpp/include/snapshot.h b/inkcpp/include/snapshot.h index 16a1c1e4..ba715f36 100644 --- a/inkcpp/include/snapshot.h +++ b/inkcpp/include/snapshot.h @@ -19,6 +19,35 @@ namespace ink::runtime * will be identical. If multiple runners are associated to the same globals all will be contained, * and cann be reconsrtucted with the id parameter of @ref * ink::runtime::story::new_runner_from_snapshot() + * A snapshot can be applied to an identical story file or an simulare if the snapshot is @ref + * ink::runtime::snapshot::can_be_migrated() "@c can_be_migrated()". + * A not migrated snapshot contiouse at exactly the place you are currently at. + * + * **A migrated one will "snap bag" to the last knot.** + * + * + Global variables which (name) still exist will be transfared. + * + New ones will be initelized with its default value + * + Old ones will be droped + * + Temp variables which (name) still exist will be tranfared + * + new ones will be initelized with its default vaule (possible missing transformations) + * + old ones will be kept + * + **attention** declarations in Tunnels will be missed + * + Stack/Threads/Tunnels must not be used in the moment of the snapshot for it to be migratable. + * + best practice is to create hub knots which names do not change in the progress of the update + * and which do not have local variables. Then only store after you stepped inside this knot. + * + Lists definitions are matched after best knowladge + * + for each pair of old list value and new list value the best good matching is used + * + the similarty is calculated based on the jaro-winkler similiarty of the value names, and + * the normalized difference of the values + * + for each pair of old and new list (definition) the best good match is taken + * + the similiraty is calculated based on the jaccard similiraty of the contained flags, and + * the jaro-winkler similarty of the lists names + * + visit counts (e.g. used for once only choices) + * + existing ones (exact name match) are kept + * + !! a choice with no explicit label is tracked via its position in the list, reordering + * choices can therfore break your visit counts + * + new ones are zero + * + old ones are discarded * * @todo Currently the id is equal to the creation order, a way to name the single runner/threads is * WIP diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index f1544b9f..b7201ab1 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -892,12 +892,15 @@ config::statistics::list_table list_table::statistics() const * @param lh,rh flag indexes contained in the lists * @param matches mapping from lh -> rh, -1 for dropped */ -float d_contains(const int lh[2], const int rh[2], const int* matches) +float d_contains(const size_t lh[2], const size_t rh[2], const int* matches) { int n_union = (lh[1] - lh[0]) + (rh[1] - rh[0]); int n_intersection = 0; - for (int i = lh[0]; i < lh[1]; ++i) { - if (matches[i] >= rh[0] && matches[i] < rh[1]) { + for (size_t i = lh[0]; i < lh[1]; ++i) { + if (matches[i] == -1) { + continue; + } + if (static_cast(matches[i]) >= rh[0] && static_cast(matches[i]) < rh[1]) { n_intersection += 1; } } @@ -963,10 +966,19 @@ float* cost_matrix( float* matrix = new float[n_lists * n_lists]; for (size_t i = 0; i < lh.length; ++i) { for (size_t j = 0; j < rh.length; ++j) { - float dl = d_label(lh.names[i], rh.names[j]); - int lh_range[] = {lh.list_ends[i], lh.list_ends[i + 1]}; - int rh_range[] = {rh.list_ends[j], rh.list_ends[j + 1]}; - float dv = d_contains(lh_range, rh_range, value_matches); + float dl = d_label(lh.names[i], rh.names[j]); + size_t lh_range[] = {i == 0 ? 0 : lh.list_ends[i - 1], lh.list_ends[i]}; + size_t rh_range[] = {j == 0 ? 0 : rh.list_ends[j - 1], rh.list_ends[j]}; + float dv = d_contains(lh_range, rh_range, value_matches); + matrix[i * n_lists + j] = dv * 0.8f + dl * 0.2f; + } + for (size_t j = rh.length; j < n_lists; ++j) { + matrix[i * n_lists + j] = drop_penalty; + } + } + for (size_t i = lh.length; i < n_lists; ++i) { + for (size_t j = 0; j < n_lists; ++j) { + matrix[i * n_lists + j] = drop_penalty; } } return matrix; @@ -984,7 +996,7 @@ float* cost_matrix(const MatchListValues& lh, const MatchListValues& rh, float d for (size_t j = 0; j < rh.length; ++j) { float dl = d_label(lh.names[i], rh.names[j]); float dv = d_value(lh.values[i], rh.values[j], lh_range, rh_range); - matrix[i * n_flags + j] = dl * 0.8 + dv * 0.2; + matrix[i * n_flags + j] = dl * 0.8f + dv * 0.2f; } for (size_t j = rh.length; j < n_flags; ++j) { matrix[i * n_flags + j] = drop_penalty; @@ -998,16 +1010,15 @@ float* cost_matrix(const MatchListValues& lh, const MatchListValues& rh, float d return matrix; } -list_table::list_table( - const char* data, const ink::internal::header&, const decltype(_data)& values, - const decltype(_entry_state)& state -) -{ -} - bool list_table::migrate(const char* old_list_metadata, const ink::internal::header& header) { - list_table old_ref_table(old_list_metadata, header, _data, _entry_state); + list_table old_ref_table(old_list_metadata, header); + for (const auto& x : _data) { + old_ref_table._data.push() = x; + } + for (const auto& x : _entry_state) { + old_ref_table._entry_state.push() = x; + } _data.clear(); _entry_state.clear(); @@ -1026,65 +1037,64 @@ bool list_table::migrate(const char* old_list_metadata, const ink::internal::hea // high confidance list value matches - constexpr float HIGH_CONFIDANCE_DROP_PANELTY = 0.3; - constexpr float LOW_CONFIDANCE_DROP_PANELTY = 0.6; + constexpr float HIGH_CONFIDANCE_DROP_PANELTY = 0.3f; + constexpr float LOW_CONFIDANCE_DROP_PANELTY = 0.6f; float* value_matrix = cost_matrix( - MatchListValues{_flag_names.data(), _flag_values.data(), numFlags()}, MatchListValues{ old_ref_table._flag_names.data(), old_ref_table._flag_values.data(), old_ref_table.numFlags() }, + MatchListValues{_flag_names.data(), _flag_values.data(), numFlags()}, LOW_CONFIDANCE_DROP_PANELTY ); - const int n_flags = std::max(numFlags(), old_ref_table.numFlags()); - int* value_matches = new int[n_flags]; - const float value_cost_high - = hungarian_solver(value_matrix, value_matches, n_flags, HIGH_CONFIDANCE_DROP_PANELTY); + const int n_flags = std::max(numFlags(), old_ref_table.numFlags()); + int* value_matches = new int[n_flags]; + hungarian_solver(value_matrix, value_matches, n_flags, HIGH_CONFIDANCE_DROP_PANELTY); // list matches float* list_matrix = cost_matrix( - MatchList{_list_end.data(), _list_names.data(), numLists()}, MatchList{ old_ref_table._list_end.data(), old_ref_table._list_names.data(), old_ref_table.numLists() }, - value_matches, LOW_CONFIDANCE_DROP_PANELTY + MatchList{_list_end.data(), _list_names.data(), numLists()}, value_matches, + LOW_CONFIDANCE_DROP_PANELTY ); - const int n_lists = std::max(numLists(), old_ref_table.numLists()); - int* list_matches = new int[n_lists]; - const float total_list_cost - = hungarian_solver(list_matrix, list_matches, n_lists, LOW_CONFIDANCE_DROP_PANELTY); + const int n_lists = std::max(numLists(), old_ref_table.numLists()); + int* list_matches = new int[n_lists]; + hungarian_solver(list_matrix, list_matches, n_lists, LOW_CONFIDANCE_DROP_PANELTY); // low confidence list_value matches - const float value_cost_low - = hungarian_solver(value_matrix, value_matches, n_flags, LOW_CONFIDANCE_DROP_PANELTY); + hungarian_solver(value_matrix, value_matches, n_flags, LOW_CONFIDANCE_DROP_PANELTY); for (size_t idx = 0; idx < old_ref_table._entry_state.size(); ++idx) { // migrate list new_list{-1}; switch (old_ref_table._entry_state[idx]) { - // permanent list are a result of the list definition and do not need to be migrated + case state::permanent: new_list = create_permament_at(idx); break; case state::used: new_list = create_at(idx); break; default: continue; } inkAssert(new_list.lid >= 0, "Failed to create new list entry for migration."); inkAssert( - static_cast(new_list.lid) != idx, + static_cast(new_list.lid) == idx, "At position list creation failed with different valid idx." ); - const data_t* entry = old_ref_table.getPtr(idx); - data_t* new_entry = getPtr(idx); - bool migrated = false; + const data_t* entry = old_ref_table.getPtr(idx); + data_t* new_entry = getPtr(idx); + bool migrated = false; + bool is_empty_list = true; for (size_t i = 0; i < old_ref_table.numLists(); ++i) { if (old_ref_table.hasList(entry, i)) { - bool hit = false; - for (size_t j = old_ref_table.listBegin(i); j < old_ref_table._list_end[j]; ++j) { + bool hit = false; + bool is_empty_list = false; + for (size_t j = old_ref_table.listBegin(i); j < old_ref_table._list_end[i]; ++j) { if (old_ref_table.hasFlag(entry, j) && old_ref_table._flag_names[j]) { if (value_matches[j] != -1) { hit = true; migrated = true; - for (size_t k = 0; _list_end[k] < static_cast(value_matches[j]); ++k) { - setList(new_entry, k); - } + size_t k; + for (k = 0; _list_end[k] < static_cast(value_matches[j]); ++k) {} + setList(new_entry, k); setFlag(new_entry, value_matches[j]); } } @@ -1097,7 +1107,7 @@ bool list_table::migrate(const char* old_list_metadata, const ink::internal::hea } } // drop list - if (! migrated) { + if (! is_empty_list && ! migrated) { // FIXME: remove list ? // _entry_state [idx] = state::empty; return false; diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index a94daba3..b39f91f5 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -668,7 +668,6 @@ bool runner_impl::can_be_migrated() const if (_entered_knot) { return false; } - // TODO(JBe): next store _ptr as path hash_t c_hash = _story->container_hash(_ptr - 6); if (c_hash == 0) { return false; diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index cc8868f5..8d284deb 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -346,7 +346,7 @@ void story_impl::setup_pointers() while (_header.read_list_flag(ptr) != null_flag) ; } - _list_meta_size = ptr - _list_meta; + _list_meta_size = static_cast(ptr - _list_meta); } else { _list_meta = nullptr; _list_meta_size = 0; diff --git a/inkcpp_c/inkcpp.cpp b/inkcpp_c/inkcpp.cpp index 3eff0c66..7131944f 100644 --- a/inkcpp_c/inkcpp.cpp +++ b/inkcpp_c/inkcpp.cpp @@ -152,6 +152,11 @@ extern "C" { return reinterpret_cast(self)->num_runners(); } + bool ink_snapshot_can_be_migrated(const HInkSnapshot* self) + { + return reinterpret_cast(self)->can_be_migrated(); + } + const char* ink_choice_text(const HInkChoice* self) { return reinterpret_cast(self)->text(); diff --git a/inkcpp_python/src/module.cpp b/inkcpp_python/src/module.cpp index 8800f7e8..cd8710fb 100644 --- a/inkcpp_python/src/module.cpp +++ b/inkcpp_python/src/module.cpp @@ -100,7 +100,7 @@ PYBIND11_MODULE(inkcpp_py, m) return py::make_iterator(self.begin(list_name), self.end()); }, R"(Rerutrns all flags contained in this list from a list of name list_name. - + Use iter(List) to iterate over all flags.)", py::keep_alive<0, 1>(), py::arg("list_name").none(false) ) @@ -183,7 +183,7 @@ Use iter(List) to iterate over all flags.)", return self.get(); }, R"(If value contains a inkcpp_py.Value.Type.String, return it. Else throws an AttributeError. - + If you want convert it to a string use: `str(value)`.)" ); py_value.def( @@ -219,6 +219,10 @@ If you want convert it to a string use: `str(value)`.)" "write_to_file", &snapshot::write_to_file, "Store snapshot in file.", py::arg("filename").none(false) ) + .def( + "can_be_migrated", &snapshot::can_be_migrated, + "If the snapshot can be migrated to a changed story file." + ) .def_static( "from_file", &snapshot::from_file, "Load snapshot from file", py::arg("filename").none(false) @@ -506,7 +510,7 @@ Reconstructs a runner from a snapshot. Args: snapshot: snapshot to load runner from. globals: store used by this runner, else load the store from the file and use it. - (created with inkcpp_py.Story.new_runner_from_snapshot) + (created with inkcpp_py.Story.new_runner_from_snapshot) runner_id: if multiple runners are stored in the snapshot, id of runner to reconstruct. (ids start at 0 and are dense) Returns: diff --git a/inkcpp_test/Fixes.cpp b/inkcpp_test/Fixes.cpp index 3c9cfd57..9790a1d3 100644 --- a/inkcpp_test/Fixes.cpp +++ b/inkcpp_test/Fixes.cpp @@ -267,7 +267,7 @@ SCENARIO("Provoke thread array expension _ #142", "[fixes]") REQUIRE(thread->num_choices() == 15); const char options[] = "abcdefghijklmno"; for (const char* c = options; *c; ++c) { - CHECK(thread->get_choice(static_cast(c - options))->text()[0] == *c); + CHECK(thread->get_choice(static_cast(c - options))->text()[0] == *c); } } } @@ -289,7 +289,7 @@ SCENARIO("Provoke thread array expension _ #142", "[fixes]") REQUIRE(thread->num_choices() == 10); const char* options = "bdfhjklmno"; for (const char* c = options; *c; ++c) { - CHECK(thread->get_choice(static_cast(c - options))->text()[0] == *c); + CHECK(thread->get_choice(static_cast(c - options))->text()[0] == *c); } } } diff --git a/inkcpp_test/ListMatching.cpp b/inkcpp_test/ListMatching.cpp index 003e10f8..ba2b0318 100644 --- a/inkcpp_test/ListMatching.cpp +++ b/inkcpp_test/ListMatching.cpp @@ -17,7 +17,7 @@ struct MatchListValues { }; float** cost_matrix(const MatchListValues& rh, const MatchListValues& lh, float drop_panelty); -float d_contains(const int lh[2], const int rh[2], const int* matches); +float d_contains(const size_t lh[2], const size_t rh[2], const int* matches); float d_value(int lh, int rh, int lh_range[2], int rh_range[2]); float d_label(const char* lh, const char* rh); } // namespace ink::runtime::internal @@ -84,42 +84,42 @@ SCENARIO("santy check distance functions", "[list_match]") { GIVEN("Equal Sets") { - int lh[] = {5, 10}; - int rh[] = {5, 10}; - int matches[] = {0, 0, 0, 0, 0, 5, 6, 7, 8, 9}; - float d = ink::runtime::internal::d_contains(lh, rh, matches); + ink::size_t lh[] = {5, 10}; + ink::size_t rh[] = {5, 10}; + int matches[] = {0, 0, 0, 0, 0, 5, 6, 7, 8, 9}; + float d = ink::runtime::internal::d_contains(lh, rh, matches); CHECK_THAT(d, Catch::Matchers::WithinAbs(1, 0.001)); } GIVEN("Dropped Values") { - int lh[] = {5, 10}; - int rh[] = {5, 8}; - int matches[] = {0, 0, 0, 0, 0, 5, -1, -1, 6, 7}; - float d = ink::runtime::internal::d_contains(lh, rh, matches); + ink::size_t lh[] = {5, 10}; + ink::size_t rh[] = {5, 8}; + int matches[] = {0, 0, 0, 0, 0, 5, -1, -1, 6, 7}; + float d = ink::runtime::internal::d_contains(lh, rh, matches); CHECK_THAT(d, Catch::Matchers::WithinAbs(0.6, 0.001)); } GIVEN("New Values") { - int lh[] = {5, 8}; - int rh[] = {5, 10}; - int matches[] = {0, 0, 0, 0, 0, 5, 6, 7}; - float d = ink::runtime::internal::d_contains(lh, rh, matches); + ink::size_t lh[] = {5, 8}; + ink::size_t rh[] = {5, 10}; + int matches[] = {0, 0, 0, 0, 0, 5, 6, 7}; + float d = ink::runtime::internal::d_contains(lh, rh, matches); CHECK_THAT(d, Catch::Matchers::WithinAbs(0.6, 0.001)); } GIVEN("Swapped Values") { - int lh[] = {5, 10}; - int rh[] = {5, 10}; - int matches[] = {0, 0, 0, 0, 0, 5, 9, 6, 8, 7}; - float d = ink::runtime::internal::d_contains(lh, rh, matches); + ink::size_t lh[] = {5, 10}; + ink::size_t rh[] = {5, 10}; + int matches[] = {0, 0, 0, 0, 0, 5, 9, 6, 8, 7}; + float d = ink::runtime::internal::d_contains(lh, rh, matches); CHECK_THAT(d, Catch::Matchers::WithinAbs(1, 0.001)); } GIVEN("Changed Values") { - int lh[] = {5, 10}; - int rh[] = {5, 10}; - int matches[] = {0, 0, 0, 0, 0, 5, 9, -1, -1, -1}; - float d = ink::runtime::internal::d_contains(lh, rh, matches); + ink::size_t lh[] = {5, 10}; + ink::size_t rh[] = {5, 10}; + int matches[] = {0, 0, 0, 0, 0, 5, 9, -1, -1, -1}; + float d = ink::runtime::internal::d_contains(lh, rh, matches); CHECK_THAT(d, Catch::Matchers::WithinAbs(0.25, 0.001)); } } @@ -186,20 +186,20 @@ SCENARIO("Simple List Migration stories", "[list_match]") runner thread_a = ink_a->new_runner(globals_a); WHEN("Load new list extensions, split and typo fix") { - CHECK(thread_a->getline() == "You are currently at Flor\n"); + CHECK(thread_a->getline() == "You are currently at Flor, Balcony\n"); REQUIRE(thread_a->has_choices()); thread_a->choose(0); std::unique_ptr snap{thread_a->create_snapshot()}; REQUIRE(snap->can_be_migrated()); CHECK( thread_a->getall() - == "More\nYou are still at Flor, all posibilities are Flor, Balcony, Kitchen, Garden\n" + == "More\nYou are still at Flor, Balcony - all posibilities are Flor, Balcony, Kitchen, Garden\n" ); auto globals_b = ink_b->new_globals_from_snapshot(*snap); - auto thread_b = ink_b->new_runner_from_snapshot(*snap); + auto thread_b = ink_b->new_runner_from_snapshot(*snap, globals_b); CHECK( thread_b->getall() - == "More\nYou are still at Floor, all posibilities are Floor, Kitchen, Livingroom\n" + == "More\nYou are still at Floor, Balcony - all posibilities are Kitchen, Street, Floor, Balcony, Livingroom, Garden\n" ); } } diff --git a/inkcpp_test/ink/ListMatchStoryA.ink b/inkcpp_test/ink/ListMatchStoryA.ink new file mode 100644 index 00000000..77439af4 --- /dev/null +++ b/inkcpp_test/ink/ListMatchStoryA.ink @@ -0,0 +1,7 @@ +LIST Stages = Flor, Balcony, Kitchen, Garden +VAR current_stage = () +~ current_stage = (Flor, Balcony) + +You are currently at {current_stage} +* More +- You are still at {current_stage} - all posibilities are {LIST_ALL(current_stage)} diff --git a/inkcpp_test/ink/ListMatchStoryB.ink b/inkcpp_test/ink/ListMatchStoryB.ink new file mode 100644 index 00000000..f76ac70b --- /dev/null +++ b/inkcpp_test/ink/ListMatchStoryB.ink @@ -0,0 +1,9 @@ +LIST Indoor = Kitchen, Floor, Livingroom +LIST Outdoor = Street, Balcony, Garden +VAR current_stage = () +~ current_stage = Floor + +You are currently at {current_stage} +* More +- You are still at {current_stage} - all posibilities are {LIST_ALL(current_stage)} +-> END diff --git a/inkcpp_test/ink/MigrationAfter.ink b/inkcpp_test/ink/MigrationAfter.ink new file mode 100644 index 00000000..6f690f90 --- /dev/null +++ b/inkcpp_test/ink/MigrationAfter.ink @@ -0,0 +1,31 @@ +# source https://github.com/harryr0se from issue: #112 +LIST activities = Swimming, SandCastle, IceCream +VAR completed = () + +-> holiday +=== holiday +We're going to the seaside! +{completed: So far we've done the following: {completed}} ++ [Make a sand castle] -> sand_castle -> holiday +* [Go swimming] -> swimming -> holiday ++ [Get Ice Cream] -> ice_cream -> holiday +* Time to go home -> home + += sand_castle +We made a great sand castle, it even has a moat! +~ completed += SandCastle +->-> + += swimming +We swim and swam, it was delightful! +~ completed += Swimming +->-> + += ice_cream +We got ice cream, mine was raspberry! +~ completed += IceCream +->-> + += home +What a nice holiday that was +-> END diff --git a/inkcpp_test/ink/MigrationBefore.ink b/inkcpp_test/ink/MigrationBefore.ink new file mode 100644 index 00000000..1b72658c --- /dev/null +++ b/inkcpp_test/ink/MigrationBefore.ink @@ -0,0 +1,25 @@ +# source https://github.com/harryr0se from issue: #112 +LIST activities = Swimming, SandCastle +VAR completed = () + +-> holiday +=== holiday +We're going to the seaside! +{completed: So far we've done the following: {completed}} ++ [Make a sand castle] -> sand_castle -> holiday +* [Go swimming] -> swimming -> holiday +* Time to go home -> home + += sand_castle +We made a great sand castle, it even has a moat! +~ completed += SandCastle +->-> + += swimming +We swim and swam, it was delightful! +~ completed += Swimming +->-> + += home +What a nice holiday that was +-> END From 254395b5c72034e889d7c3fbbe63221013539eea Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Tue, 7 Apr 2026 13:42:36 +0200 Subject: [PATCH 21/24] fix(Migration): also migrate permanent lists Permanent lists are used also as base for variable --- inkcpp/hungarian_solver.cpp | 14 +++++++------- inkcpp/list_table.cpp | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/inkcpp/hungarian_solver.cpp b/inkcpp/hungarian_solver.cpp index 19152a4e..8e0faf83 100644 --- a/inkcpp/hungarian_solver.cpp +++ b/inkcpp/hungarian_solver.cpp @@ -7,7 +7,7 @@ class HungarienCtx { - const size_t n; + const int n; const float* cost; struct { @@ -21,7 +21,7 @@ class HungarienCtx bool* visit_col; public: - HungarienCtx(const float* cost, size_t n) + HungarienCtx(const float* cost, int n) : n{n} , cost{cost} { @@ -48,10 +48,10 @@ class HungarienCtx int operator[](int col) { return col_2_row[col]; } - void init_search(size_t row) + void init_search(int row) { col_2_row[0] = row; - for (size_t i = 0; i <= n; ++i) { + for (size_t i = 0; i <= static_cast(n); ++i) { visit_col[i] = false; slack[i] = std::numeric_limits::max(); path[i] = 0; @@ -66,7 +66,7 @@ class HungarienCtx int current_row = col_2_row[current_col]; float delta = std::numeric_limits::max(); int next_col = 0; - for (size_t col = 1; col <= n; ++col) { + for (int col = 1; col <= n; ++col) { if (! visit_col[col]) { float reduced = cost[(current_row - 1) * n + (col - 1)] - pot.row[current_row] - pot.col[col]; @@ -81,7 +81,7 @@ class HungarienCtx } } - for (size_t col = 0; col <= n; ++col) { + for (size_t col = 0; col <= static_cast(n); ++col) { if (visit_col[col]) { pot.row[col_2_row[col]] += delta; pot.col[col] -= delta; @@ -110,7 +110,7 @@ float hungarian_solver(const float* cost, int* matches, size_t n, float threshol { HungarienCtx ctx(cost, n); for (size_t row = 1; row <= n; ++row) { - ctx.init_search(row); + ctx.init_search(static_cast(row)); int end_col = ctx.find_augmenting_path(); ctx.augment_matching(end_col); } diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index b7201ab1..6c6dba6d 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -1085,8 +1085,8 @@ bool list_table::migrate(const char* old_list_metadata, const ink::internal::hea bool is_empty_list = true; for (size_t i = 0; i < old_ref_table.numLists(); ++i) { if (old_ref_table.hasList(entry, i)) { - bool hit = false; - bool is_empty_list = false; + bool hit = false; + is_empty_list = false; for (size_t j = old_ref_table.listBegin(i); j < old_ref_table._list_end[i]; ++j) { if (old_ref_table.hasFlag(entry, j) && old_ref_table._flag_names[j]) { if (value_matches[j] != -1) { From c7a3203cf296a968d7e1a452062daaaa0716af2f Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 15 Apr 2026 12:49:22 +0200 Subject: [PATCH 22/24] fix(Migration): also migrate permanent lists Permanent lists are used also as base for variable --- inkcpp/array.h | 4 +- inkcpp/hungarian_solver.cpp | 11 ++- inkcpp/hungarian_solver.h | 6 ++ inkcpp/list_table.cpp | 11 ++- inkcpp_c/include/inkcpp.h | 6 ++ inkcpp_c/inkcpp.cpp | 2 +- inkcpp_c/tests/Snapshot.c | 110 ++++++++++++++++++++------- inkcpp_python/src/module.cpp | 14 ++-- inkcpp_python/tests/conftest.py | 10 +-- inkcpp_python/tests/test_Snapshot.py | 48 +++++++++++- inkcpp_test/ListMatching.cpp | 20 ++--- inkcpp_test/Migration.cpp | 1 + 12 files changed, 183 insertions(+), 60 deletions(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index d727380d..5eba1c88 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -328,7 +328,9 @@ class basic_restorable_array : public snapshot_interface inline size_t loaded_capacity() const { - inkAssert(_loaded_capacity != ~0, "This object was not loaded from a snapshot."); + inkAssert( + _loaded_capacity != static_cast(~0), "This object was not loaded from a snapshot." + ); return _loaded_capacity; } diff --git a/inkcpp/hungarian_solver.cpp b/inkcpp/hungarian_solver.cpp index 8e0faf83..edda38da 100644 --- a/inkcpp/hungarian_solver.cpp +++ b/inkcpp/hungarian_solver.cpp @@ -1,9 +1,11 @@ #include "hungarian_solver.h" + #include "system.h" #include #include #include #include +#include class HungarienCtx { @@ -106,6 +108,8 @@ class HungarienCtx } }; +namespace ink::algorithms +{ float hungarian_solver(const float* cost, int* matches, size_t n, float threshold) { HungarienCtx ctx(cost, n); @@ -129,12 +133,12 @@ float hungarian_solver(const float* cost, int* matches, size_t n, float threshol float jaro_simularity(const char* lh, const char* rh) { - const size_t lh_len = strlen(lh); + const size_t lh_len = static_cast(strlen(lh)); uint8_t lh_matched[256] = {}; if (lh_len > sizeof(lh_matched) * 8) { return 0; } - const size_t rh_len = strlen(rh); + const size_t rh_len = static_cast(strlen(rh)); uint8_t rh_matched[256] = {}; if (rh_len > sizeof(rh_matched) * 8) { return 0; @@ -183,7 +187,7 @@ float jaro_simularity(const char* lh, const char* rh) return ((m / lh_len) + (m / rh_len) + ((m - t) / m)) / 3.f; } -static constexpr float P = 0.1; +static constexpr float P = 0.1f; float jaro_winkler_simularity(const char* lh, const char* rh) { @@ -199,3 +203,4 @@ float jaro_winkler_simularity(const char* lh, const char* rh) } return j + l * P * (1 - j); } +} // namespace ink::algorithms diff --git a/inkcpp/hungarian_solver.h b/inkcpp/hungarian_solver.h index ac16c129..e4a53467 100644 --- a/inkcpp/hungarian_solver.h +++ b/inkcpp/hungarian_solver.h @@ -1,5 +1,10 @@ #pragma once +#include "system.h" + +namespace ink::algorithms +{ + /** Jaro Similarity of two null terminated byte strings. * supports ASCII encoding, UTF-8 might be broken. * ignores case. @@ -30,3 +35,4 @@ float jaro_winkler_simularity(const char* lh, const char* rh); * @return total cost of assigment */ float hungarian_solver(const float* cost, int* matches, size_t n, float threshold = 0); +} // namespace ink::algorithms diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index 6c6dba6d..b329c1f5 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -912,7 +912,10 @@ float d_contains(const size_t lh[2], const size_t rh[2], const int* matches) * @param lh,rh null terminated ASCII strings to compare * @return 0 if identical */ -float d_label(const char* lh, const char* rh) { return 1.f - jaro_winkler_simularity(lh, rh); } +float d_label(const char* lh, const char* rh) +{ + return 1.f - algorithms::jaro_winkler_simularity(lh, rh); +} /** Distance function for two values. * @param lh,rh numeric values to compare @@ -1049,7 +1052,7 @@ bool list_table::migrate(const char* old_list_metadata, const ink::internal::hea ); const int n_flags = std::max(numFlags(), old_ref_table.numFlags()); int* value_matches = new int[n_flags]; - hungarian_solver(value_matrix, value_matches, n_flags, HIGH_CONFIDANCE_DROP_PANELTY); + algorithms::hungarian_solver(value_matrix, value_matches, n_flags, HIGH_CONFIDANCE_DROP_PANELTY); // list matches float* list_matrix = cost_matrix( @@ -1061,10 +1064,10 @@ bool list_table::migrate(const char* old_list_metadata, const ink::internal::hea ); const int n_lists = std::max(numLists(), old_ref_table.numLists()); int* list_matches = new int[n_lists]; - hungarian_solver(list_matrix, list_matches, n_lists, LOW_CONFIDANCE_DROP_PANELTY); + algorithms::hungarian_solver(list_matrix, list_matches, n_lists, LOW_CONFIDANCE_DROP_PANELTY); // low confidence list_value matches - hungarian_solver(value_matrix, value_matches, n_flags, LOW_CONFIDANCE_DROP_PANELTY); + algorithms::hungarian_solver(value_matrix, value_matches, n_flags, LOW_CONFIDANCE_DROP_PANELTY); for (size_t idx = 0; idx < old_ref_table._entry_state.size(); ++idx) { // migrate diff --git a/inkcpp_c/include/inkcpp.h b/inkcpp_c/include/inkcpp.h index 0a905851..39513821 100644 --- a/inkcpp_c/include/inkcpp.h +++ b/inkcpp_c/include/inkcpp.h @@ -98,6 +98,12 @@ typedef struct HInkSTory HInkStory; void ink_snapshot_get_binary( const HInkSnapshot* self, const unsigned char** data, size_t* data_length ); + /** @memberof HInkSnapshot + * @copydoc ink::runtime::snapshot::can_be_migrated() + * @param self + * @retval True if the snapshot was taken at a simple state which can be migrated. + */ + bool ink_snapshot_can_be_migrated(const HInkSnapshot* self); /** @class HInkChoice * @ingroup clib diff --git a/inkcpp_c/inkcpp.cpp b/inkcpp_c/inkcpp.cpp index 7131944f..8c425301 100644 --- a/inkcpp_c/inkcpp.cpp +++ b/inkcpp_c/inkcpp.cpp @@ -80,7 +80,7 @@ extern "C" { fseek(file, 0, SEEK_SET); unsigned char* data = static_cast(malloc(file_length)); inkAssert(data, "Malloc of size %u failed", file_length); - unsigned length = fread(data, static_cast(sizeof(unsigned char)), file_length, file); + unsigned length = fread(data, sizeof(unsigned char), static_cast(file_length), file); inkAssert( file_length == static_cast(length), "Expected to read file of size %u, but only read %u", file_length, length diff --git a/inkcpp_c/tests/Snapshot.c b/inkcpp_c/tests/Snapshot.c index cbf10de5..fcfaa59a 100644 --- a/inkcpp_c/tests/Snapshot.c +++ b/inkcpp_c/tests/Snapshot.c @@ -18,48 +18,102 @@ void check_end(HInkRunner* runner) assert(ink_runner_num_choices(runner) == 2); } +#define CHECK_CHOICE(RUNNER, IDX, STR) \ + assert(strcmp(ink_choice_text(ink_runner_get_choice(RUNNER, IDX)), STR) == 0) +#define CHECK_NEXT_LINE(RUNNER, STR) assert(strcmp(ink_runner_get_line(RUNNER), STR) == 0) + int main() { - HInkStory* story = ink_story_from_file(INK_TEST_RESOURCE_DIR "SimpleStoryFlow.bin"); - HInkRunner* runner = ink_story_new_runner(story, NULL); + { + HInkStory* story = ink_story_from_file(INK_TEST_RESOURCE_DIR "SimpleStoryFlow.bin"); + HInkRunner* runner = ink_story_new_runner(story, NULL); - ink_runner_get_line(runner); - assert(ink_runner_num_choices(runner) == 3); - ink_runner_choose(runner, 2); + ink_runner_get_line(runner); + assert(ink_runner_num_choices(runner) == 3); + ink_runner_choose(runner, 2); - // snapshot after choose -> snapshot will print text after loading - HInkSnapshot* snap1 = ink_runner_create_snapshot(runner); + // snapshot after choose -> snapshot will print text after loading + HInkSnapshot* snap1 = ink_runner_create_snapshot(runner); - int cnt = 0; - while (ink_runner_can_continue(runner)) { - ink_runner_get_line(runner); - ++cnt; - } + int cnt = 0; + while (ink_runner_can_continue(runner)) { + ink_runner_get_line(runner); + ++cnt; + } - // snapshot befroe choose, context (last output lines) can not bet optained at loading - HInkSnapshot* snap2 = ink_runner_create_snapshot(runner); + // snapshot befroe choose, context (last output lines) can not bet optained at loading + HInkSnapshot* snap2 = ink_runner_create_snapshot(runner); - check_end(runner); + check_end(runner); - ink_runner_delete(runner); - runner = ink_story_new_runner_from_snapshot(story, snap1, NULL, 0); + ink_runner_delete(runner); + runner = ink_story_new_runner_from_snapshot(story, snap1, NULL, 0); - // same amount at output then before - while (ink_runner_can_continue(runner)) { - ink_runner_get_line(runner); - --cnt; - } - assert(cnt == 0); + // same amount at output then before + while (ink_runner_can_continue(runner)) { + ink_runner_get_line(runner); + --cnt; + } + assert(cnt == 0); - check_end(runner); + check_end(runner); - ink_runner_delete(runner); - runner = ink_story_new_runner_from_snapshot(story, snap2, NULL, 0); + ink_runner_delete(runner); + runner = ink_story_new_runner_from_snapshot(story, snap2, NULL, 0); + + assert(ink_runner_can_continue(runner) == 0); + check_end(runner); + } + { + HInkStory* before_story = ink_story_from_file(INK_TEST_RESOURCE_DIR "MigrationBefore.bin"); + HInkStory* after_story = ink_story_from_file(INK_TEST_RESOURCE_DIR "MigrationAfter.bin"); + HInkRunner* before_runner = ink_story_new_runner(before_story, NULL); + CHECK_NEXT_LINE(before_runner, "We're going to the seaside!\n"); + assert(ink_runner_num_choices(before_runner) == 3); + CHECK_CHOICE(before_runner, 0, "Make a sand castle"); + CHECK_CHOICE(before_runner, 1, "Go swimming"); + CHECK_CHOICE(before_runner, 2, "Time to go home"); + ink_runner_choose(before_runner, 0); + + CHECK_NEXT_LINE(before_runner, "We made a great sand castle, it even has a moat!\n"); + CHECK_NEXT_LINE(before_runner, "We're going to the seaside!\n"); + CHECK_NEXT_LINE(before_runner, "So far we've done the following: SandCastle\n"); + assert(ink_runner_num_choices(before_runner) == 3); + CHECK_CHOICE(before_runner, 0, "Make a sand castle"); + CHECK_CHOICE(before_runner, 1, "Go swimming"); + CHECK_CHOICE(before_runner, 2, "Time to go home"); + ink_runner_choose(before_runner, 1); + + HInkSnapshot* snap = ink_runner_create_snapshot(before_runner); + assert(ink_snapshot_can_be_migrated(snap)); + + CHECK_NEXT_LINE(before_runner, "We swim and swam, it was delightful!\n"); + CHECK_NEXT_LINE(before_runner, "We're going to the seaside!\n"); + CHECK_NEXT_LINE(before_runner, "So far we've done the following: Swimming, SandCastle\n"); + assert(ink_runner_num_choices(before_runner) == 2); + CHECK_CHOICE(before_runner, 0, "Make a sand castle"); + CHECK_CHOICE(before_runner, 1, "Time to go home"); + + HInkRunner* after_runner = ink_story_new_runner_from_snapshot(after_story, snap, NULL, 0); + + CHECK_NEXT_LINE(after_runner, "We swim and swam, it was delightful!\n"); + CHECK_NEXT_LINE(after_runner, "We're going to the seaside!\n"); + CHECK_NEXT_LINE(after_runner, "So far we've done the following: Swimming, SandCastle\n"); + assert(ink_runner_num_choices(after_runner) == 3); + CHECK_CHOICE(after_runner, 0, "Make a sand castle"); + CHECK_CHOICE(after_runner, 1, "Get Ice Cream"); + CHECK_CHOICE(after_runner, 2, "Time to go home"); + ink_runner_choose(after_runner, 1); + + CHECK_NEXT_LINE(after_runner, "We got ice cream, mine was raspberry!\n"); + CHECK_NEXT_LINE(after_runner, "We're going to the seaside!\n"); + CHECK_NEXT_LINE( + after_runner, "So far we've done the following: Swimming, SandCastle, IceCream\n" + ); + } - assert(ink_runner_can_continue(runner) == 0); - check_end(runner); return 0; } diff --git a/inkcpp_python/src/module.cpp b/inkcpp_python/src/module.cpp index cd8710fb..5706d00b 100644 --- a/inkcpp_python/src/module.cpp +++ b/inkcpp_python/src/module.cpp @@ -256,7 +256,7 @@ If you want convert it to a string use: `str(value)`.)" "tags", [](const choice& self) { std::vector tags(self.num_tags()); - for (size_t i = 0; i < self.num_tags(); ++i) { + for (ink::size_t i = 0; i < self.num_tags(); ++i) { tags[i] = self.get_tag(i); } return tags; @@ -338,7 +338,7 @@ To reload: "tags", [](const runner& self) { std::vector tags(self.num_tags()); - for (size_t i = 0; i < self.num_tags(); ++i) { + for (ink::size_t i = 0; i < self.num_tags(); ++i) { tags[i] = self.get_tag(i); } return tags; @@ -361,7 +361,7 @@ To reload: "knot_tags", [](const runner& self) { std::vector tags(self.num_knot_tags()); - for (size_t i = 0; i < self.num_knot_tags(); ++i) { + for (ink::size_t i = 0; i < self.num_knot_tags(); ++i) { tags[i] = self.get_knot_tag(i); } return tags; @@ -385,7 +385,7 @@ To reload: "global_tags", [](const runner& self) { std::vector tags(self.num_global_tags()); - for (size_t i = 0; i < self.num_global_tags(); ++i) { + for (ink::size_t i = 0; i < self.num_global_tags(); ++i) { tags[i] = self.get_global_tag(i); } return tags; @@ -396,15 +396,15 @@ To reload: "all_tags", [](const runner& self) { std::vector line_tags(self.num_tags()); - for (size_t i = 0; i < self.num_tags(); ++i) { + for (ink::size_t i = 0; i < self.num_tags(); ++i) { line_tags[i] = self.get_tag(i); } std::vector knot_tags(self.num_knot_tags()); - for (size_t i = 0; i < self.num_knot_tags(); ++i) { + for (ink::size_t i = 0; i < self.num_knot_tags(); ++i) { knot_tags[i] = self.get_knot_tag(i); } std::vector global_tags(self.num_global_tags()); - for (size_t i = 0; i < self.num_global_tags(); ++i) { + for (ink::size_t i = 0; i < self.num_global_tags(); ++i) { global_tags[i] = self.get_global_tag(i); } return py::dict("line"_a = line_tags, "knot"_a = knot_tags, "global"_a = global_tags); diff --git a/inkcpp_python/tests/conftest.py b/inkcpp_python/tests/conftest.py index a9c5fa82..0d089546 100644 --- a/inkcpp_python/tests/conftest.py +++ b/inkcpp_python/tests/conftest.py @@ -37,12 +37,12 @@ def res(ink_source): def story_path(tmpdir_factory): tmpdir = tmpdir_factory.getbasetemp() # tmpdir = os.fsencode('/tmp/pytest') - return {name: files - for (name, files) in map(extract_paths(tmpdir), + return {name: files + for (name, files) in map(extract_paths(tmpdir), filter( - lambda file: os.path.splitext(file)[1] == ".ink", + lambda file: os.path.splitext(file)[1] == ".ink", os.listdir("./inkcpp_test/ink/")))} - + @pytest.fixture(scope='session', autouse=True) def assets(story_path, inklecate_cmd): res = {} @@ -62,7 +62,7 @@ def assets(story_path, inklecate_cmd): @pytest.fixture(scope='session', autouse=True) def generate(): - def g(asset): + def g(asset: dict[str, ink.Story]): store = asset.new_globals() return [ asset, diff --git a/inkcpp_python/tests/test_Snapshot.py b/inkcpp_python/tests/test_Snapshot.py index 4ad0c6cd..704f1f92 100644 --- a/inkcpp_python/tests/test_Snapshot.py +++ b/inkcpp_python/tests/test_Snapshot.py @@ -1,6 +1,7 @@ import inkcpp_py as ink import pytest + def check_end(runner): assert runner.num_choices() == 3 runner.choose(2) @@ -8,9 +9,10 @@ def check_end(runner): runner.getline() assert runner.num_choices() == 2 + class TestSnapshot: def test_snapshot(self, assets, generate): - [story, glob, runner] = generate(assets['SimpleStoryFlow']) + [story, glob, runner] = generate(assets["SimpleStoryFlow"]) runner2 = story.new_runner(glob) runner.getline() assert runner.num_choices() == 3 @@ -55,3 +57,47 @@ def test_snapshot(self, assets, generate): runner = story.new_runner_from_snapshot(snap2, glob, 0) assert not runner.can_continue() check_end(runner) + + def test_migration(self, assets, generate): + [before_story, befero_glob, before_runner] = generate(assets["MigrationBefore"]) + after_story = assets["MigrationAfter"] + assert before_runner.getall() == "We're going to the seaside!\n" + assert before_runner.num_choices() == 3 + assert before_runner.get_choice(0).text() == "Make a sand castle" + assert before_runner.get_choice(1).text() == "Go swimming" + assert before_runner.get_choice(2).text() == "Time to go home" + before_runner.choose(0) + assert ( + before_runner.getall() + == "We made a great sand castle, it even has a moat!\n" + "We're going to the seaside!\nSo far we've done the following: SandCastle\n" + ) + assert before_runner.num_choices() == 3 + assert before_runner.get_choice(0).text() == "Make a sand castle" + assert before_runner.get_choice(1).text() == "Go swimming" + assert before_runner.get_choice(2).text() == "Time to go home" + before_runner.choose(1) + snap = before_runner.create_snapshot() + assert snap.can_be_migrated() + assert ( + before_runner.getall() == "We swim and swam, it was delightful!\n" + "We're going to the seaside!\nSo far we've done the following: Swimming, SandCastle\n" + ) + assert before_runner.num_choices() == 2 + assert before_runner.get_choice(0).text() == "Make a sand castle" + assert before_runner.get_choice(1).text() == "Time to go home" + + after_runner = after_story.new_runner_from_snapshot(snap) + assert ( + after_runner.getall() == "We swim and swam, it was delightful!\n" + "We're going to the seaside!\nSo far we've done the following: Swimming, SandCastle\n" + ) + assert after_runner.num_choices() == 3 + assert after_runner.get_choice(0).text() == "Make a sand castle" + assert after_runner.get_choice(1).text() == "Get Ice Cream" + assert after_runner.get_choice(2).text() == "Time to go home" + after_runner.choose(1) + assert ( + after_runner.getall() == "We got ice cream, mine was raspberry!\n" + "We're going to the seaside!\nSo far we've done the following: Swimming, SandCastle, IceCream\n" + ) diff --git a/inkcpp_test/ListMatching.cpp b/inkcpp_test/ListMatching.cpp index ba2b0318..830b43de 100644 --- a/inkcpp_test/ListMatching.cpp +++ b/inkcpp_test/ListMatching.cpp @@ -30,17 +30,17 @@ SCENARIO("santy check distance functions", "[list_match]") { GIVEN("Two Stings") { - float j = jaro_simularity("FAREMVIEL", "FARMVILLE"); + float j = ink::algorithms::jaro_simularity("FAREMVIEL", "FARMVILLE"); CHECK_THAT(j, Catch::Matchers::WithinAbs(0.88, 0.01)); } GIVEN("Two Strings in different Casing, no impact ignore casing") { - float j = jaro_simularity("FAREMVIEL", "farmville"); + float j = ink::algorithms::jaro_simularity("FAREMVIEL", "farmville"); CHECK_THAT(j, Catch::Matchers::WithinAbs(0.88, 0.01)); } GIVEN("Two strings with fill characters, small impact") { - float j = jaro_simularity("FAREMVIEL", "FARMV_IL-LE"); + float j = ink::algorithms::jaro_simularity("FAREMVIEL", "FARMV_IL-LE"); CHECK_THAT(j, Catch::Matchers::WithinAbs(0.83, 0.01)); } } @@ -48,14 +48,14 @@ SCENARIO("santy check distance functions", "[list_match]") { GIVEN("Two Strings wih without prefix") { - float j = jaro_simularity("ZFAREMVIEL", "YFARMVILLE"); - float jw = jaro_winkler_simularity("ZFAREMVIEL", "YFARMVILLE"); + float j = ink::algorithms::jaro_simularity("ZFAREMVIEL", "YFARMVILLE"); + float jw = ink::algorithms::jaro_winkler_simularity("ZFAREMVIEL", "YFARMVILLE"); CHECK_THAT(jw, Catch::Matchers::WithinAbs(j, 0.01)); } GIVEN("Two Strings with prefix") { - float j = jaro_simularity("FAREMVIEL", "FARMVILLE"); - float jw = jaro_winkler_simularity("FAREMVIEL", "FARMVILLE"); + float j = ink::algorithms::jaro_simularity("FAREMVIEL", "FARMVILLE"); + float jw = ink::algorithms::jaro_winkler_simularity("FAREMVIEL", "FARMVILLE"); CHECK(j < jw); } } @@ -137,7 +137,7 @@ SCENARIO("find best assigments", "[list_match][hungarian]") }; // clang-format on int matches[3]; - float total_cost = hungarian_solver(cost, matches, 3); + float total_cost = ink::algorithms::hungarian_solver(cost, matches, 3); CHECK(total_cost == 15.f); CHECK(matches[0] == 0); CHECK(matches[1] == 2); @@ -153,7 +153,7 @@ SCENARIO("find best assigments", "[list_match][hungarian]") }; // clang-format off int matches[3]; - float total_cost = hungarian_solver(cost, matches, 3); + float total_cost = ink::algorithms::hungarian_solver(cost, matches, 3); CHECK(total_cost == 407); CHECK(matches[0] == 2); CHECK(matches[1] == 1); @@ -168,7 +168,7 @@ SCENARIO("find best assigments", "[list_match][hungarian]") }; // clang-format on int matches[3]; - float total_cost = hungarian_solver(cost, matches, 3, 5); + float total_cost = ink::algorithms::hungarian_solver(cost, matches, 3, 5); CHECK(total_cost == 15.f); CHECK(matches[0] == -1); CHECK(matches[1] == 2); diff --git a/inkcpp_test/Migration.cpp b/inkcpp_test/Migration.cpp index e3e17764..ab0f42ac 100644 --- a/inkcpp_test/Migration.cpp +++ b/inkcpp_test/Migration.cpp @@ -207,6 +207,7 @@ SCENARIO("Migration Test for small story") thread_before->choose(1); std::unique_ptr snap1{thread_before->create_snapshot()}; + REQUIRE(snap1->can_be_migrated()); REQUIRE(thread_before->getall() == "We swim and swam, it was delightful!\nWe're going to the seaside!\nSo far we've done the following: Swimming, SandCastle\n"); CHECK(thread_before->num_choices() == 2); From 8d6d2f97e0c75b6103dabc0754f11dcacd6295d0 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 15 Apr 2026 13:08:57 +0200 Subject: [PATCH 23/24] build(Py): bump pybind11 to version v3.0.3 --- inkcpp_python/pybind11 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inkcpp_python/pybind11 b/inkcpp_python/pybind11 index 0c69e1eb..1b499083 160000 --- a/inkcpp_python/pybind11 +++ b/inkcpp_python/pybind11 @@ -1 +1 @@ -Subproject commit 0c69e1eb2177fa8f8580632c7b1f97fdb606ce8f +Subproject commit 1b4990838904501de7110d27e96c0a4152029156 From a92265101ba89c0030dbfefb8450b30c466926fb Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 15 Apr 2026 13:11:27 +0200 Subject: [PATCH 24/24] style: adapt to clang-format version used by github runner --- .github/workflows/build.yml | 1 + inkcpp/include/runner.h | 2 +- inkcpp/include/snapshot.h | 2 +- inkcpp/include/story.h | 2 +- inkcpp/snapshot_interface.h | 2 +- inkcpp_compiler/reporter.h | 87 +++++++++++++++++++------------------ 6 files changed, 50 insertions(+), 46 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index de6e7de2..59974295 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -304,6 +304,7 @@ jobs: git fetch origin master - name: Check clang-format run: | + clang-format-18 --version git clang-format-18 --extensions c,cpp,h,hpp --style file -q --diff origin/master diff=$(git clang-format-18 --extensions c,cpp,h,hpp --style file -q --diff origin/master) echo "::error Format diff >$diff<" diff --git a/inkcpp/include/runner.h b/inkcpp/include/runner.h index 4b40ac45..c48a8797 100644 --- a/inkcpp/include/runner.h +++ b/inkcpp/include/runner.h @@ -36,7 +36,7 @@ class choice; class runner_interface { public: - virtual ~runner_interface() {}; + virtual ~runner_interface(){}; // String type to simplify interfaces working with strings #ifdef INK_ENABLE_STL diff --git a/inkcpp/include/snapshot.h b/inkcpp/include/snapshot.h index ba715f36..5b8879f9 100644 --- a/inkcpp/include/snapshot.h +++ b/inkcpp/include/snapshot.h @@ -55,7 +55,7 @@ namespace ink::runtime class snapshot { public: - virtual ~snapshot() {}; + virtual ~snapshot(){}; /** Construct snapshot from blob. * Memory must be kept valid until the snapshot is deconstructed. diff --git a/inkcpp/include/story.h b/inkcpp/include/story.h index 2b3f4cb5..bc3fba71 100644 --- a/inkcpp/include/story.h +++ b/inkcpp/include/story.h @@ -25,7 +25,7 @@ namespace ink::runtime class story { public: - virtual ~story() {}; + virtual ~story(){}; #pragma region Interface Methods /** * Creates a new global store diff --git a/inkcpp/snapshot_interface.h b/inkcpp/snapshot_interface.h index 67473d8b..ced1f112 100644 --- a/inkcpp/snapshot_interface.h +++ b/inkcpp/snapshot_interface.h @@ -22,7 +22,7 @@ class value; class snapshot_interface { public: - constexpr snapshot_interface() {}; + constexpr snapshot_interface(){}; static unsigned char* snap_write(unsigned char* ptr, const void* data, size_t length, bool write) { diff --git a/inkcpp_compiler/reporter.h b/inkcpp_compiler/reporter.h index 359be80d..47c2c2fa 100644 --- a/inkcpp_compiler/reporter.h +++ b/inkcpp_compiler/reporter.h @@ -11,45 +11,48 @@ namespace ink::compiler::internal { - class error_strbuf : public std::stringbuf - { - public: - // start a new error message to be outputted to a given list - void start(error_list* list); - - // If set, the next sync will throw an exception - void throw_on_sync(bool); - protected: - virtual int sync() override; - - private: - error_list* _list = nullptr; - bool _throw = false; - }; - - class reporter - { - protected: - reporter(); - virtual ~reporter() { } - - // sets the results pointer for this reporter - void set_results(compilation_results*); - - // clears the results pointer - void clear_results(); - - // report warning - std::ostream& warn(); - - // report error - std::ostream& err(); - - // report critical error - std::ostream& crit(); - private: - compilation_results* _results; - error_strbuf _buffer; - std::ostream _stream; - }; - } // namespace ink::compiler::internal +class error_strbuf : public std::stringbuf +{ +public: + // start a new error message to be outputted to a given list + void start(error_list* list); + + // If set, the next sync will throw an exception + void throw_on_sync(bool); + +protected: + virtual int sync() override; + +private: + error_list* _list = nullptr; + bool _throw = false; +}; + +class reporter +{ +protected: + reporter(); + + virtual ~reporter() {} + + // sets the results pointer for this reporter + void set_results(compilation_results*); + + // clears the results pointer + void clear_results(); + + // report warning + std::ostream& warn(); + + // report error + std::ostream& err(); + + // report critical error + std::ostream& crit(); + +private: + compilation_results* _results; + error_strbuf _buffer; + std::ostream _stream; +}; +} // namespace ink::compiler::internal