diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1ec01419..5dd2f19f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,7 +58,7 @@ jobs: echo "INKLECATE=${{ matrix.inklecate_pre }}$GITHUB_WORKSPACE/inklecate/inklecate${{ matrix.inklecate_post }}" >> $GITHUB_ENV # Setup python - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 if: ${{ matrix.proof }} with: python-version: '3.7' @@ -192,7 +192,13 @@ jobs: with: python-version: "3.x" - name: Install Doxygen - run: sudo apt-get install doxygen graphviz -y + run: | + sudo apt-get install graphviz -y + wget https://www.doxygen.nl/files/doxygen-1.10.0.linux.bin.tar.gz + gunzip doxygen-*.tar.gz + tar xf doxygen-*.tar + cd doxygen-1.10.0/ + sudo make install - name: Setup cmake uses: jwlawson/actions-setup-cmake@v1.14.2 with: @@ -202,7 +208,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{github.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DINKCPP_PY=ON + run: cmake $GITHUB_WORKSPACE -DINKCPP_PY=ON -DINKCPP_DOC_BlueprintUE=ON - name: Build working-directory: ${{github.workspace}}/build shell: bash @@ -290,7 +296,7 @@ jobs: pull-requests: write steps: # Download Ink Proof Results - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: results path: "results" @@ -327,19 +333,19 @@ jobs: - uses: actions/checkout@v4 # Download Ink Proof page for Linux - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: linux-www path: www/proof - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: doxygen path: www # Deploy to Github Pages - name: Deploy Page - uses: JamesIves/github-pages-deploy-action@4.1.4 + uses: JamesIves/github-pages-deploy-action@4 with: branch: inkproof-page folder: www diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 56a80508..57ea0382 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,12 +21,12 @@ jobs: steps: - uses: actions/checkout@v3 - name: Download artifacts - uses: marcofaggian/action-download-multiple-artifacts@v3.0.8 - with: - names: linux-cl linux-lib linux-clib unreal macos-cl macos-lib macos-clib win64-cl win64-lib win64-clib python-package-distribution - paths: linux-cl linux-lib linux-clib unreal macos-cl macos-lib macos-clib win64-cl win64-lib win64-clib dist - workflow: build.yml - branch: master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ID=$(gh run list -b master --limit 1 --json databaseId | jq '.[0].databaseId') + gh run download $ID -n linux-cl -n linux-lib -n linux-clib -n unreal -n macos-cl -n macos-lib -n macos-clib -n win64-cl -n win64-lib -n win64-clib -n python-package-distribution + mv python-package-distribution dist - name: Zip run: | for f in linux-cl linux-lib linux-clib unreal macos-cl macos-lib macos-clib win64-cl win64-lib win64-clib; do zip -r $f $f; done diff --git a/CMakeLists.txt b/CMakeLists.txt index e61f5498..22ed0cf0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.16) enable_testing() # Project setup -project(inkcpp VERSION 0.1.2) +project(inkcpp VERSION 0.1.3) SET(CMAKE_CXX_STANDARD 20) SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(CMAKE_INSTALL_LIBRARY_DIR lib) @@ -71,13 +71,36 @@ set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) set(CPACK_COMPONENTS_GROUPING IGNORE) include(CPack) +if(NOT WHEEL_BUILD) find_package(Doxygen) if (DOXYGEN_FOUND) + set(DOXYGEN_PROJECT_NAME ${PROJECT_NAME}) # enable if update to cmake version 3.28 # doxygen_add_docs(doc WORKING_DIR ${PROJECT_SOURCE_DIR} CONFIG_FILE "${PROJECT_SOURCE_DIR}/Doxyfile" COMMENT "Generate docs") + set(INPUT_FILTER "") + + if (INKCPP_DOC_BlueprintUE) + # TODO: make as dependecy + file(COPY "${PROJECT_SOURCE_DIR}/unreal/blueprint_filter.js" DESTINATION ${PROJECT_BINARY_DIR}) + file(DOWNLOAD + "https://blueprintue.com/bue-render/render.css" + "${PROJECT_BINARY_DIR}/render.css" + EXPECTED_HASH SHA256=875364e36f8aa5d6c1d41d58043f13b48a499b5c969e8daef35bd29bbf7c6e8d) + file(APPEND "${PROJECT_BINARY_DIR}/render.css" ".bue-render .icon { background-color: unset; }") + file(READ "${PROJECT_SOURCE_DIR}/Doxyfile" DOXYFILE) + string(REPLACE "FILTER_PATTERNS =" "FILTER_PATTERNS = \"*/unreal/*=node ${PROJECT_BINARY_DIR}/blueprint_filter.js\"" DOXYFILE2 ${DOXYFILE}) + string(REPLACE "HTML_EXTRA_STYLESHEET =" "HTML_EXTRA_STYLESHEET = ${PROJECT_BINARY_DIR}/render.css " DOXYFILE ${DOXYFILE2}) + file(WRITE "${PROJECT_BINARY_DIR}/Doxyfile" ${DOXYFILE}) + else () + configure_file( + "${PROJECT_SOURCE_DIR}/Doxyfile" + "${PROJECT_BINARY_DIR}/Doxyfile" + COPYONLY) + endif() + add_custom_target(doc - ${DOXYGEN_EXECUTABLE} "${PROJECT_SOURCE_DIR}/Doxyfile" + ${DOXYGEN_EXECUTABLE} "${PROJECT_BINARY_DIR}/Doxyfile" WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" COMMENT "Generate doxygen documentation") set(PY_HTML "${PROJECT_SOURCE_DIR}/Documentation/inkcpp_py.html") @@ -101,3 +124,4 @@ if (DOXYGEN_FOUND) else(DOXYGEN_FOUND) message("Doxygen needed to generate documntation!") endif(DOXYGEN_FOUND) +endif(NOT WHEEL_BUILD) diff --git a/Documentation/cmake_example/CMakeLists.txt b/Documentation/cmake_example/CMakeLists.txt index e80f325d..f8baba56 100644 --- a/Documentation/cmake_example/CMakeLists.txt +++ b/Documentation/cmake_example/CMakeLists.txt @@ -5,8 +5,8 @@ find_package(inkcpp CONFIG REQUIRED) # for CXX builds add_executable(main_cpp main.cpp) -target_link_libraries(main inkcpp inkcpp_compiler) +target_link_libraries(main_cpp inkcpp inkcpp_compiler) # for C builds # add_executable(main_c main.c) -# target_link_libraries(main inkcpp_c) +# target_link_libraries(main_c inkcpp_c) diff --git a/Documentation/unreal/InkCPP_DEMO.zip b/Documentation/unreal/InkCPP_DEMO.zip new file mode 100644 index 00000000..fa1c0c86 Binary files /dev/null and b/Documentation/unreal/InkCPP_DEMO.zip differ diff --git a/Documentation/unreal/imgs/CreateThread.png b/Documentation/unreal/imgs/CreateThread.png new file mode 100644 index 00000000..d96176e2 Binary files /dev/null and b/Documentation/unreal/imgs/CreateThread.png differ diff --git a/Documentation/unreal/imgs/HandleChoice.png b/Documentation/unreal/imgs/HandleChoice.png new file mode 100644 index 00000000..cbe08e46 Binary files /dev/null and b/Documentation/unreal/imgs/HandleChoice.png differ diff --git a/Documentation/unreal/imgs/ListElementOf.png b/Documentation/unreal/imgs/ListElementOf.png new file mode 100644 index 00000000..6b8e0022 Binary files /dev/null and b/Documentation/unreal/imgs/ListElementOf.png differ diff --git a/Documentation/unreal/imgs/MinimalRuntime.png b/Documentation/unreal/imgs/MinimalRuntime.png new file mode 100644 index 00000000..7ec2eaca Binary files /dev/null and b/Documentation/unreal/imgs/MinimalRuntime.png differ diff --git a/Documentation/unreal/imgs/MinimalThread.png b/Documentation/unreal/imgs/MinimalThread.png new file mode 100644 index 00000000..a2f70948 Binary files /dev/null and b/Documentation/unreal/imgs/MinimalThread.png differ diff --git a/Documentation/unreal/imgs/ObseverChange.png b/Documentation/unreal/imgs/ObseverChange.png new file mode 100644 index 00000000..1752e02c Binary files /dev/null and b/Documentation/unreal/imgs/ObseverChange.png differ diff --git a/Documentation/unreal/imgs/TagListGetValue.png b/Documentation/unreal/imgs/TagListGetValue.png new file mode 100644 index 00000000..22c4be93 Binary files /dev/null and b/Documentation/unreal/imgs/TagListGetValue.png differ diff --git a/Documentation/unreal/imgs/YieldResume.png b/Documentation/unreal/imgs/YieldResume.png new file mode 100644 index 00000000..f05a1c1a Binary files /dev/null and b/Documentation/unreal/imgs/YieldResume.png differ diff --git a/Doxyfile b/Doxyfile index a291627c..ded1987f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1070,7 +1070,7 @@ EXCLUDE_PATTERNS = # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test -EXCLUDE_SYMBOLS = ink::list_flag +EXCLUDE_SYMBOLS = ink::list_flag ink::runtime::internal* ink::internal* # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include @@ -2348,7 +2348,7 @@ ENABLE_PREPROCESSING = YES # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = NO +MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and @@ -2356,7 +2356,7 @@ MACRO_EXPANSION = NO # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_ONLY_PREDEF = NO +EXPAND_ONLY_PREDEF = YES # If the SEARCH_INCLUDES tag is set to YES, the include files in the # INCLUDE_PATH will be searched if a #include is found. @@ -2389,7 +2389,11 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = DOXYGEN \ + INK_ENABLE_STL \ + INK_ENABLE_CSTD \ + INK_ENABLE_UNREAL \ + UFUNCTION(...):= # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2398,7 +2402,7 @@ PREDEFINED = # definition found in the source code. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = UFUNCTION # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have diff --git a/inkcpp/array.h b/inkcpp/array.h index 8a6dbf2e..d3b0567f 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -156,6 +156,8 @@ class managed_restorable_array : public managed_array(); size_t len = string_handler::length(src); char* buffer = allocate(strings, len + 1); char* ptr = buffer; while (*src != '\0') *(ptr++) = *(src++); *ptr = 0; - result.v_string = buffer; + result = ink::runtime::value(buffer); } push(stack, result); } diff --git a/inkcpp/include/globals.h b/inkcpp/include/globals.h index c9ca1ab8..97513516 100644 --- a/inkcpp/include/globals.h +++ b/inkcpp/include/globals.h @@ -29,8 +29,9 @@ class globals_interface /** * @brief Write new value in global store. * @param name name of variable, as defined in InkScript + * @param val * @tparam T c++ type of variable - * @return true on success + * @retval true on success */ template bool set(const char* name, const T& val) @@ -45,6 +46,7 @@ class globals_interface * Calls callback with `value` or with casted value if it is one of * values variants. The callback will also be called with the current value * when the observe is bind. + * @param name of variable to observe * @param callback functor with: * * 0 arguments * * 1 argument: `new_value` @@ -57,13 +59,19 @@ class globals_interface internal_observe(hash_string(name), new internal::callback(callback)); } + /** create a snapshot of the current runtime state. + * (inclusive all runners assoziated with this globals) + */ virtual snapshot* create_snapshot() const = 0; virtual ~globals_interface() = default; protected: + /** @private */ virtual optional get_var(hash_t name) const = 0; + /** @private */ virtual bool set_var(hash_t name, const value& val) = 0; + /** @private */ virtual void internal_observe(hash_t name, internal::callback_base* callback) = 0; }; diff --git a/inkcpp/include/list.h b/inkcpp/include/list.h index ccd89426..aca8e8b3 100644 --- a/inkcpp/include/list.h +++ b/inkcpp/include/list.h @@ -3,7 +3,7 @@ #include "system.h" #ifdef INK_ENABLE_STL -#include +# include #endif #ifdef INK_BUILD_CLIB @@ -14,95 +14,168 @@ int ink_list_flags_from(const HInkList*, const char*, InkListIter*); int ink_list_iter_next(InkListIter*); #endif -namespace ink::runtime { - namespace internal { - class list_table; +namespace ink::runtime +{ +namespace internal +{ + class list_table; +} // namespace internal + +/** Interface for accessing a Ink list. + * + * Every function which takes a flag name can also be feed with + * a full flag name in the format `listName.flagName` + * @code + * using namespace ink::runtime; + * list l = globals.get("list_var"); + * l.add("flagName"); + * l.add("listName.FlagName") + * globals.set("list_var", l); + * @endcode + */ +class list_interface +{ +public: + list_interface() + : _list_table{nullptr} + , _list{-1} + { } - class list_interface { - public: - list_interface() : _list_table{nullptr}, _list{-1} {} - - class iterator { - const char* _flag_name; - const char* _list_name; - const list_interface& _list; - int _i; - bool _one_list_iterator; ///< iterates only though values of one list - friend list_interface; + + /** iterater for flags in a list + * @todo implement `operator->` + */ + class iterator + { + const char* _flag_name; + const char* _list_name; + const list_interface& _list; + int _i; + bool _one_list_iterator; ///< iterates only though values of one list + friend list_interface; #ifdef INK_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); + 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); #endif - protected: - iterator(const char* flag_name, const list_interface& list, size_t i, bool one_list_only = false) - : _flag_name(flag_name), _list(list), _i(i), _one_list_iterator(one_list_only) {} - public: - struct Flag { - const char* flag_name; - const char* list_name; + protected: + /** @private */ + iterator( + const char* flag_name, const list_interface& list, size_t i, bool one_list_only = false + ) + : _flag_name(flag_name) + , _list(list) + , _i(i) + , _one_list_iterator(one_list_only) + { + } + + public: + /** contains flag data */ + struct Flag { + const char* flag_name; ///< name of the flag + const char* list_name; ///< name of the list #ifdef INK_ENABLE_STL - friend std::ostream& operator<<(std::ostream& os, const Flag& flag) { - os << flag.list_name << "(" << flag.flag_name << ")"; - return os; - } -#endif - }; - Flag operator*() const { return Flag{ .flag_name = _flag_name, .list_name = _list_name }; }; - iterator& operator++() + /** serelization operator + * @param os + * @param flag + */ + friend std::ostream& operator<<(std::ostream& os, const Flag& flag) { - _list.next( _flag_name, _list_name, _i ); - return *this; - } - bool operator!=(const iterator& itr) const { return itr._i != _i; } - bool operator==(const iterator& itr) const { - return itr._i == _i; + os << flag.list_name << "(" << flag.flag_name << ")"; + return os; } +#endif }; - /** checks if a flag is contained in the list */ - virtual bool contains(const char* flag) const { - inkAssert(false, "Not implemented function from interfaces is called!"); return false; - }; + /** access value the iterator is pointing to */ + Flag operator*() const { return Flag{.flag_name = _flag_name, .list_name = _list_name}; }; - /** add a flag to list */ - virtual void add(const char* flag) { - inkAssert(false, "Not implemented function from interface is called!"); - }; + /** continue iterator to next value */ + iterator& operator++() + { + _list.next(_flag_name, _list_name, _i, _one_list_iterator); + return *this; + } - /** removes a flag from list */ - virtual void remove(const char* flag) { - inkAssert(false, "Not implemented function from interface is called!"); - }; + /** checks if iterator points not to the same element + * @param itr other iterator + */ + bool operator!=(const iterator& itr) const { return itr._i != _i; } - /** begin iterator for contained flags in list */ - virtual iterator begin() const { - inkAssert(false, "Not implemented function from interface is called!"); - return new_iterator(nullptr, -1); - }; + /** checks if iterator points to the same element + * @param itr other iterator + */ + bool operator==(const iterator& itr) const { return itr._i == _i; } + }; - /** returns a iterator over elements of the given list */ - virtual iterator begin(const char* list_name) const { - inkAssert(false, "Not implemented function from interface is called!"); - return new_iterator(nullptr, -1); - } - /** end iterator for contained flags in list */ - virtual iterator end() const { - inkAssert(false, "Not implemented function from interface is called!"); - return new_iterator(nullptr, -1); }; - - private: - friend iterator; - virtual void next(const char*& flag_name, const char*& list_name, int& i) const { - inkAssert(false, "Not implemented funciton from interface is called!"); - }; - protected: - iterator new_iterator(const char* flag_name, int i, bool one_list_only = false) const { - return iterator(flag_name, *this, i, one_list_only); - } - list_interface(internal::list_table& table, int list) : _list_table {&table}, _list{list} {} - internal::list_table* _list_table; - int _list; + /** checks if a flag is contained in the list */ + virtual bool contains(const char* flag) const + { + inkAssert(false, "Not implemented function from interfaces is called!"); + return false; + }; + + /** add a flag to list */ + virtual void add(const char* flag) + { + inkAssert(false, "Not implemented function from interface is called!"); + }; + + /** removes a flag from list */ + virtual void remove(const char* flag) + { + inkAssert(false, "Not implemented function from interface is called!"); }; -} + + /** begin iterator for contained flags in list */ + virtual iterator begin() const + { + inkAssert(false, "Not implemented function from interface is called!"); + return new_iterator(nullptr, -1); + }; + + /** returns a iterator over elements of the given list */ + virtual iterator begin(const char* list_name) const + { + inkAssert(false, "Not implemented function from interface is called!"); + return new_iterator(nullptr, -1); + } + + /** end iterator for contained flags in list */ + virtual iterator end() const + { + inkAssert(false, "Not implemented function from interface is called!"); + return new_iterator(nullptr, -1); + }; + +private: + friend iterator; + + virtual void + next(const char*& flag_name, const char*& list_name, int& i, bool _one_list_iterator) const + { + inkAssert(false, "Not implemented funciton from interface is called!"); + }; + +protected: + /** @private */ + iterator new_iterator(const char* flag_name, int i, bool one_list_only = false) const + { + return iterator(flag_name, *this, i, one_list_only); + } + + /** @private */ + list_interface(internal::list_table& table, int list) + : _list_table{&table} + , _list{list} + { + } + + /** @private */ + internal::list_table* _list_table; + /** @private */ + int _list; +}; +} // namespace ink::runtime diff --git a/inkcpp/include/runner.h b/inkcpp/include/runner.h index 3c53cdc4..e0158701 100644 --- a/inkcpp/include/runner.h +++ b/inkcpp/include/runner.h @@ -67,7 +67,8 @@ class runner_interface /** * Continue execution until the next newline, then allocate a c-style * string with the output. This allocated string is managed by the runtime - * and will be deleted at the next @ref choose() or @ref getline + * and will be deleted at the next @ref ink::runtime::runner_interface::choose() "choose()" or + * @ref ink::runtime::runner_interface::getline() "getline()" * * @return allocated c-style string with the output of a single line of execution */ @@ -165,13 +166,19 @@ class runner_interface /** check if since last choice selection tags have been added */ virtual bool has_tags() const = 0; - /** return the number of tags accumulated since last choice + /** return the number of current. + * + * The tags will be accumulated since last choice * order of tags wont change, and new are added at the end */ virtual size_t num_tags() const = 0; + /** access tag. + * @param index tag id to fetch [0;@ref ink::runtime::runner_interface::num_tags() "num_tags()") + */ virtual const char* get_tag(size_t index) const = 0; protected: - // internal bind implementation. not for calling. + /** internal bind implementation. not for calling. + * @private */ virtual void internal_bind(hash_t name, internal::function_base* function) = 0; public: @@ -216,6 +223,11 @@ class runner_interface } #ifdef INK_ENABLE_UNREAL + /** bind and unreal delegate + * @param name hash of external function name in ink script + * @param functionDelegate + * @param lookaheadSafe @ref #bind() + */ template void bind_delegate(hash_t name, D functionDelegate, bool lookaheadSafe) { diff --git a/inkcpp/include/snapshot.h b/inkcpp/include/snapshot.h index 157295f0..9bbc12e8 100644 --- a/inkcpp/include/snapshot.h +++ b/inkcpp/include/snapshot.h @@ -6,12 +6,13 @@ namespace ink::runtime { /** * Container for an InkCPP runtime snapshot. - * Each snapshot contains a @ref globals_interface "globals store" - * and all assoziated @ref runner_interface "runners/threads" - * For convinience there exist @ref globals_interface::create_snapshot() and + * Each snapshot contains a @ref ink::runtime::globals_interface "globals store" + * and all assoziated @ref ink::runtime::runner_interface "runners/threads" + * For convinience there exist @ref ink::runtime::globals_interface::create_snapshot() and * runner_interface::create_snapshot() . If the runner is assoziated to the globals the snapshot * will be identical. If multiple runners are assoziated to the same globals all will be contained, - * and cann be reconsrtucted with the id parameter of @ref story::new_runner_from_snapshot() + * and cann be reconsrtucted with the id parameter of @ref + * ink::runtime::story::new_runner_from_snapshot() * * @todo Currently the id is equal to the creation order, a way to name the single runner/threads is * WIP @@ -21,14 +22,33 @@ class snapshot public: virtual ~snapshot(){}; + /** Construct snapshot from blob. + * Memory must be kept valid until the snapshot is deconstructed. + * @param data pointer to blob + * @param length number of bytes in blob + * @param freeOnDestroy if the memory should be freed (delete[]) when the snapshot is + * deconstructed + * @return newly created snapshot + */ static snapshot* from_binary(const unsigned char* data, size_t length, bool freeOnDestroy = true); + /** acces blob inside snapshot */ virtual const unsigned char* get_data() const = 0; + /** size of blob inside snapshot */ virtual size_t get_data_len() const = 0; + /** number of runners which are stored inside this snapshot */ virtual size_t num_runners() const = 0; #ifdef INK_ENABLE_STL + /** deserialize snapshot from file. + * @param filename of input file + * @throws ink_exception if it fails to open the file + */ static snapshot* from_file(const char* filename); + /** serialize snapshot to file + * @param filename output file filename, if already exist it will be overwritten + * @throws ink_exception if it failt to open the file + */ void write_to_file(const char* filename) const; #endif }; diff --git a/inkcpp/include/story.h b/inkcpp/include/story.h index 233109f2..55249eda 100644 --- a/inkcpp/include/story.h +++ b/inkcpp/include/story.h @@ -31,29 +31,38 @@ class story * @return managed pointer to a new global store */ virtual globals new_globals() = 0; - virtual globals new_globals_from_snapshot(const snapshot&) = 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. - * - * @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 store can be set if explicit access to globals is required or multiple runner with a shared global are used - * @param idx 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&, globals store = nullptr, unsigned idx = 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 @@ -148,11 +157,12 @@ class story * * The current release is available at the [release * page](https://github.com/JBenda/inkcpp/releases/latest), as `unreal.zip`.
- * Unpack this folder in `/PATH/TO/UNREAL_PROJECT/Plugins/` and it will be + * Unpack this foldor in `/PATH/TO/UNREAL_ENGINE/Engine/Plugins/` and it will be available + * as plugin in the plugin list.
+ * Or unpack this folder in `/PATH/TO/UNREAL_PROJECT/Plugins/` and it will be * intigrated at the next startup.
A MarketPlace appearance is work in progress :) * - * The overview to the UE Blueprint class can be found at @ref unreal "here". - * A more detailed explination with images in WIP. + * The overview to the UE Blueprint class and examples can be found at @ref unreal "here". * * If you want to use the newest version clone the project and install the unreal component. * @code {sh} diff --git a/inkcpp/include/story_ptr.h b/inkcpp/include/story_ptr.h index caba3eac..b75e9425 100644 --- a/inkcpp/include/story_ptr.h +++ b/inkcpp/include/story_ptr.h @@ -4,181 +4,224 @@ namespace ink::runtime { - namespace internal - { - struct ref_block +namespace internal +{ + /** @private */ + struct ref_block { + ref_block() + : references(0) + , valid(true) { - ref_block() - : references(0) - , valid(true) - { } - - size_t references; - bool valid; + } - static void remove_reference(ref_block*&); - }; + size_t references; + bool valid; - class story_ptr_base - { - protected: - story_ptr_base(internal::ref_block* story); - story_ptr_base(internal::ref_block* story, internal::ref_block* instance); - story_ptr_base(const story_ptr_base&); - - story_ptr_base& operator=(const story_ptr_base&) = delete; - - void add_reference(); - bool remove_reference(); - - void set(const story_ptr_base& other); - - inline bool is_valid() const { - return - _story_block != nullptr && _instance_block != nullptr - &&_story_block->valid && _instance_block->valid; - } - - inline bool is_story_valid() const { - return _story_block != nullptr && _story_block->valid; - } - - private: - // reference block for the parent story - ref_block* _story_block; - - // reference block for this pointer - ref_block* _instance_block; - }; - } + static void remove_reference(ref_block*&); + }; - /** - * Pointer wrapper to an object whose lifetime is tied to a story object. - * - * A shared pointer whose lifetime is also tied to the lifetime of the parent - * story object. The referenced object will live until either - * 1) There are no more story_ptr's pointing to this object - * 2) The story object which owns this object dies - * - * @see story_interface - * @see runner_interface - * @see globals_interface - */ - template - class story_ptr : public internal::story_ptr_base + /** @private */ + class story_ptr_base { - public: - // constructor. internal use only. - story_ptr(T* ptr, internal::ref_block* story) - : story_ptr_base(story) - , _ptr(ptr) - { - add_reference(); - } - - // casting constructor. internal use only - template - story_ptr(T* ptr, const story_ptr& other) - : story_ptr_base(other) - , _ptr(ptr) + protected: + /** construct a new pointer with new instance block */ + story_ptr_base(internal::ref_block* story); + /** construct a new pointer with existing instance block */ + story_ptr_base(internal::ref_block* story, internal::ref_block* instance); + /** construct a new pointer basedd on existing story pointer */ + story_ptr_base(const story_ptr_base&); + + story_ptr_base& operator=(const story_ptr_base&) = delete; + + /** increases reference count */ + void add_reference(); + /** decreses reference count + * @retval true if count reaches zero and object was removed + */ + bool remove_reference(); + + /** switch lifetime block */ + void set(const story_ptr_base& other); + + /** checks if pointer is still alive */ + inline bool is_valid() const { - add_reference(); + return _story_block != nullptr && _instance_block != nullptr && _story_block->valid + && _instance_block->valid; } - // pointer constructor. for nullptr only. - story_ptr(T* ptr) - : story_ptr_base(nullptr, nullptr) - , _ptr(nullptr) - { - inkAssert(ptr == nullptr, "can not create story_ptr from existing pointer!"); - } + /** checks if story still exists */ + inline bool is_story_valid() const { return _story_block != nullptr && _story_block->valid; } - // null constructor - story_ptr() - : story_ptr_base(nullptr, nullptr) - , _ptr(nullptr) - { - } + private: + // reference block for the parent story + ref_block* _story_block; - // destructor - ~story_ptr(); + // reference block for this pointer + ref_block* _instance_block; + }; +} // namespace internal + +/** + * Pointer wrapper to an object whose lifetime is tied to a story object. + * + * A shared pointer whose lifetime is also tied to the lifetime of the parent + * story object. The referenced object will live until either + * 1) There are no more story_ptr's pointing to this object + * 2) The story object which owns this object dies + * + * @see story_interface + * @see runner_interface + * @see globals_interface + */ +template +class story_ptr : public internal::story_ptr_base +{ +public: + /** constructor. internal use only. + * @private + */ + story_ptr(T* ptr, internal::ref_block* story) + : story_ptr_base(story) + , _ptr(ptr) + { + add_reference(); + } + + /** casting constructor. internal use only + * @private + */ + template + story_ptr(T* ptr, const story_ptr& other) + : story_ptr_base(other) + , _ptr(ptr) + { + add_reference(); + } - // == copy methods == - story_ptr(const story_ptr&); - story_ptr& operator=(const story_ptr&); + /** pointer constructor. for nullptr only. + * @param ptr will be ignored, should be nullptr + */ + story_ptr(T* ptr) + : story_ptr_base(nullptr, nullptr) + , _ptr(nullptr) + { + inkAssert(ptr == nullptr, "can not create story_ptr from existing pointer!"); + } - // == casting == - template - story_ptr cast() - { - // if cast fails, return null + // null constructor + story_ptr() + : story_ptr_base(nullptr, nullptr) + , _ptr(nullptr) + { + } + + // destructor + ~story_ptr(); + + // == copy methods == + /** copy constructor + * @param oth + */ + story_ptr(const story_ptr& oth); + /** copy assigment operator + * @param oth + */ + story_ptr& operator=(const story_ptr& oth); + + // == casting == + /** pointer cast while keeping ref count + * @tparam U new pointer type, valid cast form T to U must be available + */ + template + story_ptr cast() + { + // if cast fails, return null #ifdef INK_ENABLE_UNREAL - // Unreal disables RTTI - U* casted = reinterpret_cast(_ptr); + // Unreal disables RTTI + U* casted = reinterpret_cast(_ptr); #else - U* casted = dynamic_cast(_ptr); + U* casted = dynamic_cast(_ptr); #endif - if (casted == nullptr) - return nullptr; + if (casted == nullptr) + return nullptr; - // create new pointer with casted value but same instance blocks - return story_ptr(casted, *this); - } + // create new pointer with casted value but same instance blocks + return story_ptr(casted, *this); + } - // == equality == - inline bool operator==(const story_ptr& other) { return _ptr == other._ptr; } + // == equality == + /** implement operator== */ + inline bool operator==(const story_ptr& other) { return _ptr == other._ptr; } - // == validity == - bool is_valid() const { return story_ptr_base::is_valid() && _ptr; } - inline operator bool() const { return is_valid(); } + // == validity == + /** checks if optional contains a value */ + bool is_valid() const { return story_ptr_base::is_valid() && _ptr; } - // === dereference operators == - inline T* get() { return is_valid() ? _ptr : nullptr; } - inline const T* get() const { return is_valid() ? _ptr : nullptr; } - inline T* operator->() { return get(); } - inline const T* operator->() const { return get(); } - inline T& operator*() { return *get(); } - inline const T& operator*() const { return *get(); } - private: - T* _ptr; - }; + /** auto cast to bool with value from @ref #is_valid() */ + inline operator bool() const { return is_valid(); } - template - story_ptr::~story_ptr() - { - if (remove_reference()) - { - delete _ptr; - _ptr = nullptr; - } - } + // === dereference operators == + /** access value as ptr + * @retval nullptr if value is not @ref #is_valid() "valid" + */ + inline T* get() { return is_valid() ? _ptr : nullptr; } - template - story_ptr::story_ptr(const story_ptr& other) - : story_ptr_base(other) - , _ptr(other._ptr) - { - add_reference(); - } + /** access value as ptr + * @retval nullptr if value is not @ref #is_valid() "valid" + */ + inline const T* get() const { return is_valid() ? _ptr : nullptr; } - template - story_ptr& story_ptr::operator=(const story_ptr& other) - { - // Clear out any old data - if (remove_reference()) - { - delete _ptr; - _ptr = nullptr; - } + /** implement operator-> */ + inline T* operator->() { return get(); } - // Set pointers - set(other); - _ptr = other._ptr; + /** implement operator-> */ + inline const T* operator->() const { return get(); } - // initialize - add_reference(); + /** implement operator* */ + inline T& operator*() { return *get(); } + + /** implement operator* */ + inline const T& operator*() const { return *get(); } + +private: + T* _ptr; +}; + +template +story_ptr::~story_ptr() +{ + if (remove_reference()) { + delete _ptr; + _ptr = nullptr; + } +} + +template +story_ptr::story_ptr(const story_ptr& other) + : story_ptr_base(other) + , _ptr(other._ptr) +{ + add_reference(); +} - // return reference to self - return *this; +template +story_ptr& story_ptr::operator=(const story_ptr& other) +{ + // Clear out any old data + if (remove_reference()) { + delete _ptr; + _ptr = nullptr; } -} \ No newline at end of file + + // Set pointers + set(other); + _ptr = other._ptr; + + // initialize + add_reference(); + + // return reference to self + return *this; +} +} // namespace ink::runtime diff --git a/inkcpp/include/traits.h b/inkcpp/include/traits.h index 809d134f..85411704 100644 --- a/inkcpp/include/traits.h +++ b/inkcpp/include/traits.h @@ -4,208 +4,261 @@ #include "system.h" #ifdef INK_ENABLE_STL -#include +# include #endif /** Util templates and implimentation of STL if STL is not available */ namespace ink::runtime::internal { - template - constexpr size_t sizeof_largest_type() - { - size_t ret = 0; - return ( (ret = sizeof(Ts) > ret ? sizeof(Ts) : ret), ... ); - } - - template - struct get_ith_type : get_ith_type {}; - - template - struct get_ith_type<0, Arg, Args...> - { - using type = Arg; - }; - - // constant and is_same from http://www.cppreference.com - template - struct constant { - static constexpr T value = v; - typedef T value_type; - typedef constant type; // using injected-class-name - constexpr operator value_type() const noexcept { return value; } - constexpr value_type operator()() const noexcept { return value; } //since c++14 - }; - - struct false_type : constant {}; - struct true_type : constant{}; - - template - true_type test_ptr_conv(const volatile B*); - template - false_type test_ptr_conv(const volatile void*); - template - auto test_is_base_of(int) -> decltype(test_ptr_conv(static_cast(nullptr))); - // template /// FIXME: needed? - // auto test_is_base_of(...) -> true_type; - - template - struct is_base_of : constant(0))::value> {}; - - template - struct is_same : false_type {}; - - template - struct is_same : true_type {}; - - template - struct is_pointer : false_type {}; - - template - struct is_pointer : true_type {}; - - template struct remove_cv { typedef T type; }; - template struct remove_cv { typedef T type; }; - template struct remove_cv { typedef T type; }; - template struct remove_cv { typedef T type; }; - template - struct remove_cvref - { typedef std::remove_cv_t> type; }; - - - // == string testing (from me) == - - template - struct is_string : false_type { }; - - template - struct is_string : is_string { }; - - template - struct is_string : is_string { }; - - template - struct is_string : is_string { }; - - template - struct string_handler { }; +template +constexpr size_t sizeof_largest_type() +{ + size_t ret = 0; + return ((ret = sizeof(Ts) > ret ? sizeof(Ts) : ret), ...); +} - template - struct string_handler : string_handler { }; +template +struct get_ith_type : /** @cond */ get_ith_type /** @endcond */ { /** @cond */ +}; + +template +struct get_ith_type<0, Arg, Args...> { + /** @endcond */ + using type = Arg; +}; - template - struct string_handler : string_handler { }; +// constant and is_same from http://www.cppreference.com +template +struct constant { + static constexpr T value = v; + typedef T value_type; + typedef constant type; // using injected-class-name - template - struct string_handler : string_handler { }; + constexpr operator value_type() const noexcept { return value; } + + constexpr value_type operator()() const noexcept { return value; } // since c++14 +}; + +struct false_type : constant { +}; + +struct true_type : constant { +}; + +template +true_type test_ptr_conv(const volatile B*); +template +false_type test_ptr_conv(const volatile void*); +template +auto test_is_base_of(int) -> decltype(test_ptr_conv(static_cast(nullptr))); + +// template /// FIXME: needed? +// auto test_is_base_of(...) -> true_type; + +template +struct is_base_of : constant(0))::value> { +}; + +template +struct is_same : false_type { +}; + +template +struct is_same : true_type { +}; + +template +struct is_pointer : false_type { +}; + +template +struct is_pointer : true_type { +}; + +template +struct remove_cv { + typedef T type; +}; + +template +struct remove_cv { + typedef T type; +}; + +template +struct remove_cv { + typedef T type; +}; + +template +struct remove_cv { + typedef T type; +}; + +template +struct remove_cvref { + typedef std::remove_cv_t> type; +}; + +// == string testing (from me) == + +template +struct is_string : false_type { +}; + +template +struct is_string : is_string { +}; + +template +struct is_string : is_string { +}; + +template +struct is_string : is_string { +}; + +template +struct string_handler { +}; + +template +struct string_handler : string_handler { +}; + +template +struct string_handler : string_handler { +}; + +template +struct string_handler : string_handler { +}; -#define MARK_AS_STRING(TYPE, LEN, SRC) template<> struct is_string : constant { }; \ - template<> struct string_handler { \ +#define MARK_AS_STRING(TYPE, LEN, SRC) \ + template<> \ + struct is_string : constant { \ + }; \ + template<> \ + struct string_handler { \ static size_t length(const TYPE& x) { return LEN; } \ - static void src_copy(const TYPE& x, char* output) { \ - [&output](const char* src){\ - while(*src != '\0') *(output++) = *(src++); \ - *output = 0; \ - }(SRC);\ - } \ + static void src_copy(const TYPE& x, char* output) \ + { \ + [&output](const char* src) { \ + while (*src != '\0') \ + *(output++) = *(src++); \ + *output = 0; \ + }(SRC); \ + } \ } - inline size_t c_str_len(const char* c) { - const char* i = c; - while (*i != 0) - i++; - return i - c; - } +inline size_t c_str_len(const char* c) +{ + const char* i = c; + while (*i != 0) + i++; + return i - c; +} - MARK_AS_STRING(char*, c_str_len(x), x); +MARK_AS_STRING(char*, c_str_len(x), x); #ifdef INK_ENABLE_STL - MARK_AS_STRING(std::string, x.size(), x.c_str()); +MARK_AS_STRING(std::string, x.size(), x.c_str()); #endif #ifdef INK_ENABLE_UNREAL - MARK_AS_STRING(FString, x.Len(), TCHAR_TO_UTF8(*x)); +MARK_AS_STRING(FString, x.Len(), TCHAR_TO_UTF8(*x)); #endif #undef MARK_AS_STRING - // function_traits from https://functionalcpp.wordpress.com/2013/08/05/function-traits/ +// function_traits from https://functionalcpp.wordpress.com/2013/08/05/function-traits/ - template - struct function_traits; +template +struct function_traits; - // function pointer - template - struct function_traits : public function_traits - {}; +// function pointer +template +struct function_traits : public function_traits { +}; - template - struct function_traits - { - using return_type = R; +template +struct function_traits { + using return_type = R; - static constexpr unsigned int arity = sizeof...(Args); + static constexpr unsigned int arity = sizeof...(Args); - template - struct argument - { - static_assert(N < arity, "error: invalid parameter index."); - using type = typename get_ith_type::type; - }; + template + struct argument { + static_assert(N < arity, "error: invalid parameter index."); + using type = typename get_ith_type::type; }; - - // member function pointer - template - struct function_traits : public function_traits - {}; - - // const member function pointer - template - struct function_traits : public function_traits - {}; - - // member object pointer - template - struct function_traits : public function_traits - {}; - - // functor - template - struct function_traits - { - private: - using call_type = function_traits; - public: - using return_type = typename call_type::return_type; - - static constexpr unsigned int arity = call_type::arity - 1; - - template - struct argument - { - static_assert(N < arity, "error: invalid parameter index."); - using type = typename call_type::template argument::type; - }; +}; + +// member function pointer +template +struct function_traits : public function_traits { +}; + +// const member function pointer +template +struct function_traits : public function_traits { +}; + +// member object pointer +template +struct function_traits : public function_traits { +}; + +// functor +template +struct function_traits { +private: + using call_type = function_traits; + +public: + using return_type = typename call_type::return_type; + + static constexpr unsigned int arity = call_type::arity - 1; + + template + struct argument { + static_assert(N < arity, "error: invalid parameter index."); + using type = typename call_type::template argument::type; }; +}; - // from https://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence - // using aliases for cleaner syntax - template using Invoke = typename T::type; +// from https://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence +// using aliases for cleaner syntax +template +using Invoke = typename T::type; - template struct seq { using type = seq; }; +template +struct seq { + using type = seq; +}; - template struct concat; +template +struct concat; - template - struct concat, seq> - : seq {}; +template +struct concat, seq> : seq { +}; - template - using Concat = Invoke>; +template +using Concat = Invoke>; - template struct gen_seq; - template using GenSeq = Invoke>; +template +struct gen_seq; +template +using GenSeq = Invoke>; - template - struct gen_seq : Concat, GenSeq> {}; +template +struct gen_seq : Concat, GenSeq> { +}; - template<> struct gen_seq<0> : seq<> {}; - template<> struct gen_seq<1> : seq<0> {}; -} +template<> +struct gen_seq<0> : seq<> { +}; + +template<> +struct gen_seq<1> : seq<0> { +}; +} // namespace ink::runtime::internal diff --git a/inkcpp/include/types.h b/inkcpp/include/types.h index 623af031..f7276f20 100644 --- a/inkcpp/include/types.h +++ b/inkcpp/include/types.h @@ -10,16 +10,19 @@ class globals_interface; class runner_interface; class snapshot; +/** alias for an managed @ref ink::runtime::globals_interface pointer */ using globals = story_ptr; +/** alias for an managed @ref ink::runtime::runner_interface pointer */ using runner = story_ptr; +/** alias for @ref ink::runtime::list_interface pointer */ using list = list_interface*; /** A Ink variable * * Used for accassing, writing and observing global variables - * @ref globals_interface::get() const, - * @ref globals_interface::set() - * @ref globals_interface::observe() + * @ref ink::runtime::globals_interface::get() + * @ref ink::runtime::globals_interface::set() + * @ref ink::runtime::globals_interface::observe() * * and for the execution of extern functions * @ref ink::runtime::runner_interface::bind() @@ -116,36 +119,42 @@ struct value { } }; +/** access #value::Type::Bool value */ template<> inline const auto& value::get() const { return v_bool; } +/** access #value::Type::Uint32 value */ template<> inline const auto& value::get() const { return v_uint32; } +/** access #value::Type::Int32 value */ template<> inline const auto& value::get() const { return v_int32; } +/** access #value::Type::String value */ template<> inline const auto& value::get() const { return v_string; } +/** access #value::Type::Float value */ template<> inline const auto& value::get() const { return v_float; } +/** access #value::Type::List value */ template<> inline const auto& value::get() const { diff --git a/inkcpp/list_impl.cpp b/inkcpp/list_impl.cpp index f4551d5b..bb442e97 100644 --- a/inkcpp/list_impl.cpp +++ b/inkcpp/list_impl.cpp @@ -2,57 +2,70 @@ #include "list.h" #include "list_table.h" -namespace ink::runtime::internal { - bool list_impl::contains(const char* flag_name) const { - auto flag = _list_table->toFlag(flag_name); - inkAssert(flag.has_value(), "No flag with name found! '%s'", flag_name); - return _list_table->has(list_table::list{_list}, *flag); - } +namespace ink::runtime::internal +{ +bool list_impl::contains(const char* flag_name) const +{ + auto flag = _list_table->toFlag(flag_name); + inkAssert(flag.has_value(), "No flag with name found! '%s'", flag_name); + return _list_table->has(list_table::list{_list}, *flag); +} - void list_impl::add(const char* flag_name) { - auto flag = _list_table->toFlag(flag_name); - inkAssert(flag.has_value(), "No flag with name found to add! '%s'", flag_name); - _list = _list_table->add(list_table::list{_list}, *flag).lid; - } +void list_impl::add(const char* flag_name) +{ + auto flag = _list_table->toFlag(flag_name); + inkAssert(flag.has_value(), "No flag with name found to add! '%s'", flag_name); + _list = _list_table->add(list_table::list{_list}, *flag).lid; +} - void list_impl::remove(const char* flag_name) { - auto flag = _list_table->toFlag(flag_name); - inkAssert(flag.has_value(), "No flag with name found to remove! '%s'", flag_name); - _list = _list_table->sub(list_table::list{_list}, *flag).lid; - } +void list_impl::remove(const char* flag_name) +{ + auto flag = _list_table->toFlag(flag_name); + inkAssert(flag.has_value(), "No flag with name found to remove! '%s'", flag_name); + _list = _list_table->sub(list_table::list{_list}, *flag).lid; +} - void list_impl::next(const char*& flag_name, const char*& list_name, int& i) const { - if (i == -1) { return; } +void list_impl::next(const char*& flag_name, const char*& list_name, int& i, bool one_list_only) + const +{ + if (i == -1) { + return; + } - list_flag flag{.list_id = static_cast(i >> 16), .flag = static_cast(i & 0xFF)}; - if(flag_name != nullptr) { - ++flag.flag; - } - if (flag.flag >= _list_table->_list_end[flag.list_id]) { - next_list: - flag.flag = 0; - do { - ++flag.list_id; - if(static_cast(flag.list_id) >= _list_table->_list_end.size()) { - i = -1; - return; - } - } while(!_list_table->hasList(_list_table->getPtr(_list), flag.list_id)); - } - 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)) { - goto next_list; - } - } - flag_name = _list_table->_flag_names[_list_table->toFid(flag)]; - list_name = _list_table->_list_names[flag.list_id]; + list_flag flag{.list_id = static_cast(i >> 16), .flag = static_cast(i & 0xFF)}; + if (flag_name != nullptr) { + ++flag.flag; + } + if (flag.flag >= _list_table->_list_end[flag.list_id]) { +next_list: + if (one_list_only) { + i = -1; + return; + } + flag.flag = 0; + do { + ++flag.list_id; + if (static_cast(flag.list_id) >= _list_table->_list_end.size()) { + i = -1; + return; + } + } while (! _list_table->hasList(_list_table->getPtr(_list), flag.list_id)); + } + 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)) { + goto next_list; + } + } + flag_name = _list_table->_flag_names[_list_table->toFid(flag)]; + list_name = _list_table->_list_names[flag.list_id]; - i = (flag.list_id << 16) | flag.flag; - } + i = (flag.list_id << 16) | flag.flag; +} - list_interface::iterator list_impl::begin(const char* list_name) const { - size_t list_id = _list_table->get_list_id(list_name).list_id; - return ++new_iterator(nullptr, list_id<<16); - } +list_interface::iterator list_impl::begin(const char* list_name) const +{ + size_t list_id = _list_table->get_list_id(list_name).list_id; + return ++new_iterator(nullptr, list_id << 16, true); } +} // namespace ink::runtime::internal diff --git a/inkcpp/list_impl.h b/inkcpp/list_impl.h index 09b09694..73a7d9ee 100644 --- a/inkcpp/list_impl.h +++ b/inkcpp/list_impl.h @@ -27,7 +27,7 @@ namespace ink::runtime::internal { 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) const override; - + void next(const char*& flag_name, const char*& list_name, int& i, bool one_list_only) + const override; }; } diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index a988ab0a..b4ca3bbc 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -6,675 +6,770 @@ #include "list_impl.h" #ifdef INK_ENABLE_STL -#include +# include #endif namespace ink::runtime::internal { - void list_table::copy_lists(const data_t* src, data_t* dst) { - int len = numLists() / bits_per_data; - int rest = numLists() % bits_per_data; - for(int i = 0; i < len; ++i) { - dst[i] = src[i]; - } - if (rest) { - dst[len] |= src[len] & (~static_cast(0) << (bits_per_data - rest)); - } +void list_table::copy_lists(const data_t* src, data_t* dst) +{ + int len = numLists() / bits_per_data; + int rest = numLists() % bits_per_data; + for (int i = 0; i < len; ++i) { + dst[i] = src[i]; + } + if (rest) { + dst[len] |= src[len] & (~static_cast(0) << (bits_per_data - rest)); } +} - list_table::list_table(const char* data, const ink::internal::header& header) - : _valid{false} - { - if (data == nullptr) { return; } - list_flag flag; - const char* ptr = data; - int start = 0; - while((flag = header.read_list_flag(ptr)) != null_flag) { - if (_list_end.size() == flag.list_id) { - start = _list_end.size() == 0 ? 0 : _list_end.back(); - _list_end.push() = start; - _list_names.push() = ptr; - while(*ptr) { ++ptr; } ++ptr; // skip string - } - while(_list_end.back() - start < flag.flag) { - _flag_names.push() = nullptr; - ++_list_end.back(); +list_table::list_table(const char* data, const ink::internal::header& header) + : _valid{false} +{ + if (data == nullptr) { + return; + } + list_flag flag; + const char* ptr = data; + int start = 0; + while ((flag = header.read_list_flag(ptr)) != null_flag) { + if (_list_end.size() == flag.list_id) { + start = _list_end.size() == 0 ? 0 : _list_end.back(); + _list_end.push() = start; + _list_names.push() = ptr; + while (*ptr) { + ++ptr; } - _flag_names.push() = ptr; + ++ptr; // skip string + } + while (_list_end.back() - start < flag.flag) { + _flag_names.push() = nullptr; ++_list_end.back(); - while(*ptr) { ++ptr; } ++ptr; // skip string } - _entrySize = segmentsFromBits( - _list_end.size() + _flag_names.size(), - sizeof(data_t)); - _valid = true; - } - - list_table::list list_table::create() - { - for(size_t i = 0; i < _entry_state.size(); ++i) { - if (_entry_state[i] == state::empty) { - _entry_state[i] = state::used; - return list(i); - } + _flag_names.push() = ptr; + ++_list_end.back(); + while (*ptr) { + ++ptr; } + ++ptr; // skip string + } + _entrySize = segmentsFromBits(_list_end.size() + _flag_names.size(), sizeof(data_t)); + _valid = true; +} - list new_entry(_entry_state.size()); - // TODO: initelized unused? - _entry_state.push() = state::used; - for(int i = 0; i < _entrySize; ++i) { - _data.push() = 0; +list_table::list list_table::create() +{ + for (size_t i = 0; i < _entry_state.size(); ++i) { + if (_entry_state[i] == state::empty) { + _entry_state[i] = state::used; + return list(i); } - return new_entry; } + list new_entry(_entry_state.size()); + // TODO: initelized unused? + _entry_state.push() = state::used; + for (int i = 0; i < _entrySize; ++i) { + _data.push() = 0; + } + return new_entry; +} - void list_table::clear_usage() { - for(state& s : _entry_state) { - if(s == state::used) { - s = state::unused; - } +void list_table::clear_usage() +{ + for (state& s : _entry_state) { + if (s == state::used) { + s = state::unused; } } +} - void list_table::mark_used(list l) { - if (_entry_state[l.lid] == state::unused) { - _entry_state[l.lid] = state::used; - } +void list_table::mark_used(list l) +{ + if (_entry_state[l.lid] == state::unused) { + _entry_state[l.lid] = state::used; } +} - void list_table::gc() { - for(size_t i = 0; i < _entry_state.size(); ++i) { - if (_entry_state[i] == state::unused) { - _entry_state[i] = state::empty; - data_t* entry = getPtr(i); - for(int j = 0; j != _entrySize; ++j) { - entry[j] = 0; - } +void list_table::gc() +{ + for (size_t i = 0; i < _entry_state.size(); ++i) { + if (_entry_state[i] == state::unused) { + _entry_state[i] = state::empty; + data_t* entry = getPtr(i); + for (int j = 0; j != _entrySize; ++j) { + entry[j] = 0; } } - _list_handouts.clear(); } + _list_handouts.clear(); +} - int list_table::toFid(list_flag e) const { - return listBegin(e.list_id) + e.flag; - } +int 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)); } - size_t list_table::stringLen(const list_flag& e) const { - return c_str_len(toString(e)); - } - const char* list_table::toString(const list_flag &e) const { - if(e.list_id < 0 || e.flag < 0 ) { - return nullptr; - } - return _flag_names[toFid(e)]; +const char* list_table::toString(const list_flag& e) const +{ + if (e.list_id < 0 || e.flag < 0) { + return nullptr; } + return _flag_names[toFid(e)]; +} - 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) { - if(hasList(entry, i)) { - for(int j = listBegin(i); j < _list_end[i]; ++j) { - if (hasFlag(entry,j) && _flag_names[j]) { - if(!first) { - len += 2; // ', ' - } else {first = false;} - len += c_str_len(_flag_names[j]); +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) { + if (hasList(entry, i)) { + for (int j = listBegin(i); j < _list_end[i]; ++j) { + if (hasFlag(entry, j) && _flag_names[j]) { + if (! first) { + len += 2; // ', ' + } else { + first = false; } + len += c_str_len(_flag_names[j]); } } } - return len; } + return len; +} - char* list_table::toString(char* out, const list& l) const { - char* itr = out; - - const data_t* entry = getPtr(l.lid); - bool first = true; - int max_list_len = 0; - for(int i = 0; i < numLists(); ++i) { - if(hasList(entry,i)) { - int len = _list_end[i] - listBegin(i); - if (len > max_list_len) max_list_len = len; - } - } - for(int j = 0; j < max_list_len; ++j) { - for(int i = 0; i < numLists(); ++i) { - int len = _list_end[i] - listBegin(i); - if(j < len && hasList(entry, i)) { - int flag = j + listBegin(i); - if(hasFlag(entry,flag) && _flag_names[flag]) { - if(!first) { - *itr++ = ','; *itr++ = ' '; - } else { first = false; } - for(const char* c = _flag_names[flag]; *c; ++c) { - *itr++ = *c; - } +char* list_table::toString(char* out, const list& l) const +{ + char* itr = out; + + const data_t* entry = getPtr(l.lid); + bool first = true; + int max_list_len = 0; + for (int i = 0; i < numLists(); ++i) { + if (hasList(entry, i)) { + int len = _list_end[i] - listBegin(i); + if (len > max_list_len) + max_list_len = len; + } + } + for (int j = 0; j < max_list_len; ++j) { + for (int i = 0; i < numLists(); ++i) { + int len = _list_end[i] - listBegin(i); + if (j < len && hasList(entry, i)) { + int flag = j + listBegin(i); + if (hasFlag(entry, flag) && _flag_names[flag]) { + if (! first) { + *itr++ = ','; + *itr++ = ' '; + } else { + first = false; + } + for (const char* c = _flag_names[flag]; *c; ++c) { + *itr++ = *c; } } } } - return itr; - } - - list_table::list list_table::range(list_table::list l, int min, int max) { - list res = create(); - data_t* in = getPtr(l.lid); - data_t* out = getPtr(res.lid); - bool has_any_list = false; - for(int i = 0; i < numLists(); ++i) { - if(hasList(in, i)) { - bool has_flag = false; - for(int j = listBegin(i); j < _list_end[i]; ++j) { - if(j - listBegin(i) < min || j - listBegin(i) > max) { continue; } - if(hasFlag(in, j)) { - setFlag(out,j); - has_flag = true; - } + } + return itr; +} + +list_table::list list_table::range(list_table::list l, int min, int max) +{ + list res = create(); + data_t* in = getPtr(l.lid); + data_t* out = getPtr(res.lid); + bool has_any_list = false; + for (int i = 0; i < numLists(); ++i) { + if (hasList(in, i)) { + bool has_flag = false; + for (int j = listBegin(i); j < _list_end[i]; ++j) { + if (j - listBegin(i) < min || j - listBegin(i) > max) { + continue; } - if(has_flag) { - has_any_list = true; - setList(out, i); + if (hasFlag(in, j)) { + setFlag(out, j); + has_flag = true; } } + if (has_flag) { + has_any_list = true; + setList(out, i); + } } - if(has_any_list) { return res; } - copy_lists(in, out); - return res; } - - list_table::list list_table::add(list_flag lh, list_flag rh) { - list res = create(); - data_t* o = getPtr(res.lid); - setList(o, lh.list_id); - setFlag(o, toFid(lh)); - setList(o, rh.list_id); - setFlag(o, toFid(rh)); + if (has_any_list) { return res; } - list_table::list list_table::add(list lh, list rh) { - list res = create(); - data_t* l = getPtr(lh.lid); - data_t* r = getPtr(rh.lid); - data_t* o = getPtr(res.lid); - for(int i = 0; i < _entrySize; ++i) { - o[i] = l[i] | r[i]; + copy_lists(in, out); + return res; +} + +list_table::list list_table::add(list_flag lh, list_flag rh) +{ + list res = create(); + data_t* o = getPtr(res.lid); + setList(o, lh.list_id); + setFlag(o, toFid(lh)); + setList(o, rh.list_id); + setFlag(o, toFid(rh)); + return res; +} + +list_table::list list_table::add(list lh, list rh) +{ + list res = create(); + data_t* l = getPtr(lh.lid); + data_t* r = getPtr(rh.lid); + data_t* o = getPtr(res.lid); + for (int i = 0; i < _entrySize; ++i) { + o[i] = l[i] | r[i]; + } + return res; +} + +list_table::list list_table::create_permament() +{ + list res = create(); + _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) + return lh; // empty or null flag (skip) + data_t* l = getPtr(lh.lid); + setList(l, rh.list_id); + if (rh.flag >= 0) { // origin entry + setFlag(l, toFid(rh)); + } + return lh; +} + +list_table::list list_table::add(list lh, list_flag rh) +{ + list res = create(); + data_t* l = getPtr(lh.lid); + data_t* o = getPtr(res.lid); + for (int i = 0; i < _entrySize; ++i) { + o[i] = l[i]; + } + setList(o, rh.list_id); + setFlag(o, toFid(rh)); + return res; +} + +list_table::list list_table::sub(list lh, list rh) +{ + list res = create(); + data_t* l = getPtr(lh.lid); + data_t* r = getPtr(rh.lid); + data_t* o = getPtr(res.lid); + bool active_flag = false; + for (int i = 0; i < _entrySize; ++i) { + o[i] = (l[i] & r[i]) ^ l[i]; + } + + for (int i = 0; i < numLists(); ++i) { + if (hasList(r, i)) { + if (hasList(l, i)) { + for (int j = listBegin(i); j < _list_end[j]; ++j) { + if (hasFlag(o, j)) { + setList(o, i); + active_flag = true; + break; + } + } + } } - return res; } - - list_table::list list_table::create_permament() - { - list res = create(); - _entry_state[res.lid] = state::permanent; + if (active_flag) { return res; } - - list_table::list& list_table::add_inplace(list& lh, list_flag rh) { - if(rh.list_id < 0) return lh; // empty or null flag (skip) - data_t* l = getPtr(lh.lid); - setList(l, rh.list_id); - if(rh.flag >= 0) { // origin entry - setFlag(l, toFid(rh)); + for (int i = 0; i < numLists(); ++i) { + if (hasList(o, i)) { + return res; } - return lh; } + copy_lists(l, o); + return res; +} - list_table::list list_table::add(list lh, list_flag rh) { - list res = create(); - data_t* l = getPtr(lh.lid); - data_t* o = getPtr(res.lid); - for(int i = 0; i < _entrySize; ++i) { - o[i] = l[i]; - } - setList(o, rh.list_id); - setFlag(o, toFid(rh)); - return res; +list_table::list list_table::sub(list lh, list_flag rh) +{ + list res = create(); + data_t* l = getPtr(lh.lid); + data_t* o = getPtr(res.lid); + for (int i = 0; i < _entrySize; ++i) { + o[i] = l[i]; } - - list_table::list list_table::sub(list lh, list rh) { - list res = create(); - data_t* l = getPtr(lh.lid); - data_t* r = getPtr(rh.lid); - data_t* o = getPtr(res.lid); - bool active_flag = false; - for(int i = 0; i < _entrySize; ++i) { - o[i] = (l[i] & r[i]) ^ l[i]; - } - - for(int i = 0; i < numLists(); ++i) { - if (hasList(r,i)) { - if (hasList(l,i)) { - for(int j = listBegin(i); j < _list_end[j]; ++j) - { - if(hasFlag(o, j)) { - setList(o,i); - active_flag = true; - break; - } - } - } - } + setFlag(o, toFid(rh), false); + for (int i = listBegin(rh.list_id); i < _list_end[rh.list_id]; ++i) { + if (hasFlag(o, i)) { + return res; } - if(active_flag) { return res; } - for(int i = 0; i < numLists(); ++i) { - if(hasList(o,i)) { - return res; - } + } + setList(l, rh.list_id, false); + for (int i = 0; i < numLists(); ++i) { + if (hasList(o, i)) { + return res; } - copy_lists(l, o); - return res; } + copy_lists(l, o); + return res; +} - list_table::list list_table::sub(list lh, list_flag rh) { - list res = create(); - data_t* l = getPtr(lh.lid); - data_t* o = getPtr(res.lid); - for(int i = 0; i < _entrySize; ++i) { - o[i] = l[i]; - } - setFlag(o, toFid(rh), false); - for(int i = listBegin(rh.list_id); i < _list_end[rh.list_id]; ++i) { - if(hasFlag(o, i)) { - return res; +list_flag list_table::sub(list_flag lh, list rh) +{ + data_t* r = getPtr(rh.lid); + if (hasList(r, lh.list_id) && hasFlag(r, toFid(lh))) { + return list_flag{lh.list_id, -1}; + } + return lh; +} + +list_table::list list_table::add(list arg, int n) +{ + // TODO: handle i == 0 (for performance only) + if (n < 0) { + return sub(arg, -n); + } + list res = create(); + data_t* l = getPtr(arg.lid); + data_t* o = getPtr(res.lid); + bool active_flag = false; + ; + for (int i = 0; i < numLists(); ++i) { + if (hasList(l, i)) { + bool has_flag = false; + for (int j = listBegin(i); j < _list_end[i] - i; ++j) { + if (hasFlag(l, j)) { + setFlag(o, j + n); + has_flag = true; + } } - } - setList(l, rh.list_id, false); - for(int i = 0; i < numLists(); ++i) { - if(hasList(o,i)) { - return res; + if (has_flag) { + active_flag = true; + setList(o, i); } } + } + if (! active_flag) { copy_lists(l, o); - return res; } + return res; +} - list_flag list_table::sub(list_flag lh, list rh) { - data_t* r = getPtr(rh.lid); - if(hasList(r, lh.list_id) && hasFlag(r, toFid(lh))) { - return list_flag{lh.list_id, -1}; - } - return lh; +list_flag list_table::add(list_flag arg, int i) +{ + arg.flag += i; + if (arg.flag < 0 || arg.flag > _list_end[arg.list_id] - listBegin(arg.list_id)) { + arg.flag = -1; } + return arg; +} - - list_table::list list_table::add(list arg, int n) { - // TODO: handle i == 0 (for performance only) - if (n < 0) { - return sub(arg, -n); - } - list res = create(); - data_t* l = getPtr(arg.lid); - data_t* o = getPtr(res.lid); - bool active_flag = false;; - for(int i = 0; i < numLists(); ++i) { - if(hasList(l, i)) { - bool has_flag = false; - for(int j = listBegin(i); j < _list_end[i] - i;++j) - { - if(hasFlag(l, j)) { - setFlag(o,j+n); - has_flag = true; - } - } - if(has_flag) { - active_flag = true; - setList(o,i); +list_table::list list_table::sub(list arg, int n) +{ + // TODO: handle i == 0 (for perofrgmance only) + if (n < 0) { + return add(arg, -n); + } + list res = create(); + data_t* l = getPtr(arg.lid); + data_t* o = getPtr(res.lid); + bool active_flag = false; + for (int i = 0; i < numLists(); ++i) { + if (hasList(l, i)) { + bool has_flag = false; + for (int j = listBegin(i) + i; j < _list_end[i]; ++j) { + if (hasFlag(l, j)) { + setFlag(o, j - n); + has_flag = true; } } + if (has_flag) { + active_flag = true; + setList(o, i); + } } - if(!active_flag) { - copy_lists(l, o); - } - return res; } + if (! active_flag) { + copy_lists(l, o); + } + return res; +} - list_flag list_table::add(list_flag arg, int i) { - arg.flag += i; - if (arg.flag < 0 || arg.flag > _list_end[arg.list_id] - listBegin(arg.list_id)) - { - arg.flag = -1; - } - return arg; +list_flag list_table::sub(list_flag arg, int i) +{ + arg.flag -= i; + if (arg.flag < 0 || arg.flag > _list_end[arg.list_id] - listBegin(arg.list_id)) { + arg.flag = -1; } + return arg; +} - list_table::list list_table::sub(list arg, int n) { - // TODO: handle i == 0 (for perofrgmance only) - if(n < 0) { - return add(arg, -n); - } - list res = create(); - data_t* l = getPtr(arg.lid); - data_t* o = getPtr(res.lid); - bool active_flag = false; - for(int i = 0; i < numLists(); ++i) { - if(hasList(l, i)) { - bool has_flag = false; - for(int j = listBegin(i) + i; j < _list_end[i]; ++j) - { - if(hasFlag(l,j)) { - setFlag(o,j-n); - has_flag = true; - } - } - if(has_flag) { - active_flag = true; - setList(o, i); +int list_table::count(list l) const +{ + int count = 0; + const data_t* data = getPtr(l.lid); + for (int i = 0; i < numLists(); ++i) { + if (hasList(data, i)) { + for (int j = listBegin(i); j < _list_end[j]; ++j) { + if (hasFlag(data, j)) { + ++count; } } } - if (!active_flag) { - copy_lists(l, o); - } - return res; } - list_flag list_table::sub(list_flag arg, int i) { - arg.flag -= i; - if (arg.flag < 0 || arg.flag > _list_end[arg.list_id] - listBegin(arg.list_id)) - { - arg.flag = -1; - } - return arg; - } - - int list_table::count(list l) const { - int count = 0; - const data_t* data = getPtr(l.lid); - for(int i = 0; i < numLists(); ++i) { - if(hasList(data, i)) { - for(int j = listBegin(i); j < _list_end[j]; ++j) - { - if(hasFlag(data, j)) { - ++count; - } - } - } - } - return count; - } - - 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) { - if(hasList(data, i)) { - for(int j = listBegin(i); j < _list_end[i]; ++j) { - if(hasFlag(data, j)) { - int value = j - listBegin(i); - if(res.flag < 0 || value < res.flag) { - res.flag = value; - res.list_id = i; - } - break; + return count; +} + +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) { + if (hasList(data, i)) { + for (int j = listBegin(i); j < _list_end[i]; ++j) { + if (hasFlag(data, j)) { + int value = j - listBegin(i); + if (res.flag < 0 || value < res.flag) { + res.flag = value; + res.list_id = i; } + break; } } } - return res; } + return res; +} - 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) { - if(hasList(data, i)) { - for(int j = _list_end[i] - 1; j >= listBegin(i); --j) - { - if(hasFlag(data, j)) { - int value = j - listBegin(i); - if (value > res.flag) { - res.flag = value; - res.list_id = i; - } - break; +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) { + if (hasList(data, i)) { + for (int j = _list_end[i] - 1; j >= listBegin(i); --j) { + if (hasFlag(data, j)) { + int value = j - listBegin(i); + if (value > res.flag) { + res.flag = value; + res.list_id = i; } + break; } } } - return res; } + return res; +} - 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) { - if(hasList(l, i) != hasList(r,i)) { return false; } - if (hasList(l,i)) { - for(int j = listBegin(i); j < _list_end[i]; ++j) { - if(hasFlag(l,j) != hasFlag(r,j)) { - return false; - } +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) { + if (hasList(l, i) != hasList(r, i)) { + return false; + } + if (hasList(l, i)) { + for (int j = listBegin(i); j < _list_end[i]; ++j) { + if (hasFlag(l, j) != hasFlag(r, j)) { + return false; } } } - return true; } + return true; +} - 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)) { 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))) { - return false; - } +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)) { + return false; } - return true; } - - - 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) { - if(hasList(l, i)) { - setList(o,i); - for(int j = listBegin(i); j < _list_end[i]; ++j) - { - setFlag(o, j); - } - } + 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))) { + return false; } - return res; } + return true; +} - list_table::list list_table::all(list_flag arg) { - list res = create(); - 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) { - setFlag(o, i); +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) { + if (hasList(l, i)) { + setList(o, i); + for (int j = listBegin(i); j < _list_end[i]; ++j) { + setFlag(o, j); } } - return res; } + return res; +} - // ATTENTION: can produce an list without setted flag list (same behavior than inklecate) - list_table::list list_table::invert(list arg) { - list res = create(); - data_t* l = getPtr(arg.lid); +list_table::list list_table::all(list_flag arg) +{ + list res = create(); + if (arg != null_flag) { data_t* o = getPtr(res.lid); - for(int i = 0; i < numLists(); ++i) { - if(hasList(l, i)) { - bool hasList = false; - for(int j = listBegin(i); j < _list_end[i]; ++j) - { - bool have = hasFlag(l,j); - if(!have) { - hasList = true; - setFlag(o,j); - } - } - if(hasList) { - setList(o,i); - } - } + setList(o, arg.list_id); + for (int i = listBegin(arg.list_id); i < _list_end[arg.list_id]; ++i) { + setFlag(o, i); } - return res; } + return res; +} - 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); +// ATTENTION: can produce an list without setted flag list (same behavior than inklecate) +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) { + if (hasList(l, i)) { + bool hasList = false; + for (int j = listBegin(i); j < _list_end[i]; ++j) { + bool have = hasFlag(l, j); + if (! have) { + hasList = true; + setFlag(o, j); + } + } + if (hasList) { + setList(o, i); } } - return res; } + return res; +} - list_table::list list_table::intersect(list lh, list rh) { - list res = create(); - data_t* l = getPtr(lh.lid); - data_t* r = getPtr(rh.lid); +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 = 0; i < _entrySize; ++i) { - o[i] = l[i] & r[i]; + for (int i = listBegin(arg.list_id); i < _list_end[arg.list_id]; ++i) { + setFlag(o, i, i - listBegin(arg.list_id) != arg.flag); } - return res; } + return res; +} - list_flag list_table::intersect(list lh, list_flag rh) { - const data_t* l = getPtr(lh.lid); - if(hasList(l, rh.list_id) && hasFlag(l, toFid(rh))) { - return rh; - } - return null_flag; - } +list_table::list list_table::intersect(list lh, list rh) +{ + list res = create(); + data_t* l = getPtr(lh.lid); + data_t* r = getPtr(rh.lid); + data_t* o = getPtr(res.lid); + for (int i = 0; i < _entrySize; ++i) { + o[i] = l[i] & r[i]; + } + return res; +} - bool list_table::has(list lh, list_flag rh) const { - const data_t* l = getPtr(lh.lid); - return hasList(l, rh.list_id) && hasFlag(l, toFid(rh)); +list_flag list_table::intersect(list lh, list_flag rh) +{ + const data_t* l = getPtr(lh.lid); + if (hasList(l, rh.list_id) && hasFlag(l, toFid(rh))) { + return rh; } + return null_flag; +} - list_flag list_table::lrnd(list lh, prng& rng) const { - const data_t* l = getPtr(lh.lid); - int n = count(lh); - n = rng.rand(n); - int count = 0; - for(int i = 0; i < numLists(); ++i) { - if(hasList(l, i)) { - for(int j = listBegin(i); j < _list_end[i]; ++j) { - if(hasFlag(l,j)) { - if(count++ == n) { - return list_flag{ - static_cast(i), - static_cast( j - listBegin(i) ) - }; - } +bool list_table::has(list lh, list_flag rh) const +{ + const data_t* l = getPtr(lh.lid); + return hasList(l, rh.list_id) && hasFlag(l, toFid(rh)); +} + +list_flag list_table::lrnd(list lh, prng& rng) const +{ + const data_t* l = getPtr(lh.lid); + int n = count(lh); + n = rng.rand(n); + int count = 0; + for (int i = 0; i < numLists(); ++i) { + if (hasList(l, i)) { + for (int j = listBegin(i); j < _list_end[i]; ++j) { + if (hasFlag(l, j)) { + if (count++ == n) { + return list_flag{ + static_cast(i), + static_cast(j - listBegin(i))}; } } } } - return null_flag; } + return null_flag; +} - 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) { - if (hasList(r, i)) { - if(!hasList(l, i)) { return false; } - for(int j = listBegin(i); j < _list_end[i]; ++j) { - if(hasFlag(r, j) && !hasFlag(l,j)) { return false; } +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) { + if (hasList(r, i)) { + if (! hasList(l, i)) { + return false; + } + for (int j = listBegin(i); j < _list_end[i]; ++j) { + if (hasFlag(r, j) && ! hasFlag(l, j)) { + return false; } } } - return true; } + return true; +} + +optional list_table::toFlag(const char* flag_name) const +{ - list_flag list_table::get_list_id(const char *list_name) const { - using int_t = decltype(list_flag::list_id); - for(int_t i = 0; i < static_cast(_list_names.size()); ++i) { - if(str_equal(list_name, _list_names[i])) { - return list_flag{i, -1}; + const char* periode = str_find(flag_name, '.'); + if (periode) { + 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) { + if (str_equal(flag_name, _flag_names[i])) { + return { + list_flag{.list_id = list.list_id, .flag = static_cast(i - list_begin)} + }; + } + } + } 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; + int begin = 0; + for (auto list_itr = _list_end.begin(); list_itr != _list_end.end(); ++list_itr) { + if (*list_itr > fid) { + lid = list_itr - _list_end.begin(); + break; + } + begin = *list_itr; + } + return { + list_flag{ + .list_id = static_cast(lid), .flag = static_cast(fid - begin)} + }; } } - inkAssert(false, "No list with name found!"); - return null_flag; } + return nullopt; +} - list_table::list list_table::redefine(list lh, list rh) { - data_t* l = getPtr(lh.lid); - data_t* r = getPtr(rh.lid); - list res = create(); - data_t* o = getPtr(res.lid); - - // 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)) { has_origin = true; break; } - } - if(!has_origin) { - copy_lists(l, r); +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); + 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}; } + } + inkAssert(false, "No list with name found!"); + return null_flag; +} + +list_table::list list_table::redefine(list lh, list rh) +{ + data_t* l = getPtr(lh.lid); + data_t* r = getPtr(rh.lid); + list res = create(); + data_t* o = getPtr(res.lid); - for(int i = 0; i < _entrySize; ++i) { - o[i] = r[i]; + // 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)) { + has_origin = true; + break; } - return res; + } + if (! has_origin) { + copy_lists(l, r); } - list_interface* list_table::handout_list(list l) { - static_assert(sizeof(list_interface) == sizeof(list_impl)); - auto& res = _list_handouts.push(); - new(&res) list_impl(*this, l.lid); - return &res; + for (int i = 0; i < _entrySize; ++i) { + o[i] = r[i]; } + return res; +} + +list_interface* list_table::handout_list(list l) +{ + static_assert(sizeof(list_interface) == sizeof(list_impl)); + auto& res = _list_handouts.push(); + new (&res) list_impl(*this, l.lid); + return &res; +} #ifdef INK_ENABLE_STL - std::ostream& list_table::write(std::ostream& os, list l) const { - bool first = true; - - const data_t* entry = getPtr(l.lid); - int max_list_len = 0; - for(int i = 0; i < numLists(); ++i) { - if(hasList(entry,i)) { - int len = _list_end[i] - listBegin(i); - if (len > max_list_len) max_list_len = len; - } - } - for(int j = 0; j < max_list_len; ++j) { - for(int i = 0; i < numLists(); ++i) { - int len = _list_end[i] - listBegin(i); - if(j < len && hasList(entry, i)) { - int flag = listBegin(i) + j; - if(hasFlag(entry,flag) && _flag_names[flag]) { - if(!first) { - os << ", "; - } else { first = false; } - os << _flag_names[flag]; +std::ostream& list_table::write(std::ostream& os, list l) const +{ + bool first = true; + + const data_t* entry = getPtr(l.lid); + int max_list_len = 0; + for (int i = 0; i < numLists(); ++i) { + if (hasList(entry, i)) { + int len = _list_end[i] - listBegin(i); + if (len > max_list_len) + max_list_len = len; + } + } + for (int j = 0; j < max_list_len; ++j) { + for (int i = 0; i < numLists(); ++i) { + int len = _list_end[i] - listBegin(i); + if (j < len && hasList(entry, i)) { + int flag = listBegin(i) + j; + if (hasFlag(entry, flag) && _flag_names[flag]) { + if (! first) { + os << ", "; + } else { + first = false; } + os << _flag_names[flag]; } } } - return os; } + return os; +} #endif - 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; - } - - const unsigned char* list_table::snap_load(const unsigned char* ptr, const loader& loader) - { - ptr = _data.snap_load(ptr, loader); - ptr = _entry_state.snap_load(ptr, loader); - return ptr; - } +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; +} +const unsigned char* list_table::snap_load(const unsigned char* ptr, const loader& loader) +{ + ptr = _data.snap_load(ptr, loader); + ptr = _entry_state.snap_load(ptr, loader); + return ptr; } +} // namespace ink::runtime::internal diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index 05ebf2de..e34bd838 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -5,338 +5,405 @@ #include "snapshot_impl.h" #ifdef INK_ENABLE_STL -#include +# include #endif -namespace ink::internal { - struct header; -} +namespace ink::internal +{ +struct header; +} // namespace ink::internal + namespace ink::runtime::internal { - class prng; - - // TODO: move to utils - // memory segments - // @param bits size in bits - // @param size segment size in bytes - constexpr int segmentsFromBits(int bits, int size) { - size *= 8; - return bits / size + (bits % size ? 1 : 0); +class prng; + +// TODO: move to utils +// memory segments +// @param bits size in bits +// @param size segment size in bytes +constexpr int segmentsFromBits(int bits, int size) +{ + size *= 8; + return bits / size + (bits % size ? 1 : 0); +} + +/// managed all list entries and list metadata +class list_table : public snapshot_interface +{ + using data_t = int; + enum class state : char { + unused, + used, + permanent, + empty + }; + +public: + /// handle to acces a list + struct list { + constexpr explicit list(int id) + : lid{id} + { + } + + int lid; ///< id of list to handle + }; + + /// creates an empty list + list create(); + + /** @return list_flag with list_id set to list with name list_name */ + list_flag get_list_id(const char* list_name) const; + + /// zeros all usage values + void clear_usage(); + + /// mark list as used + void mark_used(list); + + /// delete unused lists + void gc(); + + + // function to setup list_table + 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&); + + explicit list_table() + : _entrySize{0} + , _valid{false} + { } - /// managed all list entries and list metadata - class list_table : public snapshot_interface + size_t stringLen(const list_flag& e) const; + const char* toString(const list_flag& e) const; + + /** returns len of string representation of list */ + size_t stringLen(const list& l) const; + + /** converts list to string representation + * @param out char array with minimu size of stringLen(l) + * @param l list to stringify + * @return pointer to end of inserted string + */ + char* toString(char* out, const list& l) const; + + /** Finds flag id to flag name + * currently used a simple O(n) serach, for the expected number of flags should this be no problem + * @param flag_name null terminated string contaning the flag name + * @return list_flag with corresponding name + * @retval nullopt if no flag was found + */ + optional toFlag(const char* flag_name) const; + + // snapshot interface implementation + size_t snap(unsigned char* data, const snapper&) const; + const unsigned char* snap_load(const unsigned char* data, const loader&); + + /** 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 + */ + list redefine(list lh, list rh); + + list add(list l, int i); + list_flag add(list_flag f, int i); + + list add(list lh, list rh); + list add(list lh, list_flag rh); + + list add(list_flag lh, list rh) { return add(rh, lh); } + + list add(list_flag lh, list_flag rh); + + list sub(list l, int i); + list_flag sub(list_flag l, int i); + list sub(list lh, list rh); + list sub(list lh, list_flag rh); + list_flag sub(list_flag lh, list rh); + + list_flag sub(list_flag lh, list_flag rh) { return lh == rh ? list_flag{lh.list_id, -1} : lh; } + + list intersect(list lh, list rh); + list_flag intersect(list lh, list_flag rh); + + list_flag intersect(list_flag lh, list rh) { return intersect(rh, lh); } + + list_flag intersect(list_flag lh, list_flag rh) { return lh == rh ? lh : null_flag; } + + int count(list l) const; + + int count(list_flag f) const { return f.flag < 0 ? 0 : 1; } + + list_flag min(list l) const; + + list_flag min(list_flag f) const { return f; } + + list_flag max(list l) const; + + list_flag max(list_flag f) const { return f; } + + list_flag lrnd(list l, prng&) const; + + list_flag lrnd(list_flag f) const { return f; } + + list all(list l); + list all(list_flag l); + list invert(list l); + list invert(list_flag f); + + template + bool less(L lh, R rh) const { - using data_t = int; - enum class state : char { - unused, - used, - permanent, - empty - }; + return max(lh).flag < min(rh).flag; + } - public: - /// handle to acces a list - struct list{ - constexpr explicit list(int id) : lid{id} {} - int lid; ///< id of list to handle - }; - - /// creates an empty list - list create(); - - /** @return list_flag with list_id set to list with name list_name */ - list_flag get_list_id(const char* list_name) const; - - /// zeros all usage values - void clear_usage(); - - /// mark list as used - void mark_used(list); - - /// delete unused lists - void gc(); - - - // function to setup list_table - 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&); - explicit list_table() : _entrySize{0}, _valid{ false } {} - size_t stringLen(const list_flag& e) const; - const char* toString(const list_flag& e) const; - - /** returns len of string representation of list */ - size_t stringLen(const list& l) const; - - /** converts list to string representation - * @param out char array with minimu size of stringLen(l) - * @param l list to stringify - * @return pointer to end of inserted string - */ - char* toString(char* out, const list& l) const; - - /** Finds flag id to flag name - * currently used a simple O(n) serach, for the expected number of flags should this be no problem - * @param flag_name null terminated string contaning the flag name - * @return list_flag with corresponding name - * @retval nullopt if no flag was found - */ - optional toFlag(const char* flag_name) const { - for(auto flag_itr = _flag_names.begin(); flag_itr != _flag_names.end(); ++flag_itr) { - if (strcmp(*flag_itr, flag_name) == 0) { - int fid = flag_itr - _flag_names.begin(); - int lid = 0; - int begin = 0; - for(auto list_itr = _list_end.begin(); list_itr != _list_end.end(); ++list_itr) { - if(*list_itr > fid) { - lid = list_itr - _list_end.begin(); - break; - } - begin = *list_itr; - } - return {list_flag{.list_id = static_cast(lid), .flag = static_cast(fid - begin)}}; - } - } - return nullopt; - } + template + bool greater(L lh, R rh) const + { + return min(lh).flag > max(rh).flag; + } - // snapshot interface implementation - size_t snap(unsigned char* data, const snapper&) const; - const unsigned char* snap_load(const unsigned char* data, const loader&); - - /** 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 - */ - list redefine(list lh, list rh); - - list add(list l, int i); - list_flag add(list_flag f, int i); - - list add(list lh, list rh); - list add(list lh ,list_flag rh); - list add(list_flag lh, list rh) { return add(rh, lh); } - list add(list_flag lh, list_flag rh); - - list sub(list l, int i); - list_flag sub(list_flag l, int i); - list sub(list lh, list rh); - list sub(list lh, list_flag rh); - list_flag sub(list_flag lh, list rh); - list_flag sub(list_flag lh, list_flag rh) { - return lh == rh ? list_flag{lh.list_id, -1} : lh; - } - list intersect(list lh, list rh); - list_flag intersect(list lh, list_flag rh); - list_flag intersect(list_flag lh, list rh) { return intersect(rh, lh); } - list_flag intersect(list_flag lh, list_flag rh) { - return lh == rh ? lh : null_flag; - } - int count(list l) const; - int count(list_flag f) const { return f.flag < 0 ? 0 : 1; } - list_flag min(list l) const; - list_flag min(list_flag f) const { return f; } - list_flag max(list l) const; - list_flag max(list_flag f) const { return f; } - list_flag lrnd(list l, prng&) const; - list_flag lrnd(list_flag f) const { return f; } - list all(list l); - list all(list_flag l); - list invert(list l); - list invert(list_flag f); - template - bool less(L lh, R rh) const { return max(lh).flag < min(rh).flag; } - template - bool greater(L lh, R rh) const { return min(lh).flag > max(rh).flag; } - bool equal(list lh, list rh) const; - bool equal(list lh, list_flag rh) const; - bool equal(list_flag lh, list rh) const { return equal(rh, lh); } - bool equal(list_flag lh, list_flag rh) const { return lh == rh; } - template - bool not_equal(L lh, R rh) const { return equal(lh, rh); } - template - bool greater_equal(L lh, R rh) const { - return max(lh).flag >= max(rh).flag && min(lh).flag >= min(rh).flag; - } - template - bool less_equal(L lh, R rh) const { - return max(lh).flag <= max(rh).flag && min(lh).flag <= min(rh).flag; - } - bool has(list lh, list rh) const; - bool has(list lh, list_flag rh) const; - bool has(list_flag lh, list rh) const { return has(rh, lh); } - bool has(list_flag lh, list_flag rh) const { return lh == rh; } - template - bool hasnt(L lh, R rh) const { return !has(lh,rh); } - operator bool () const{ - return _valid; - } - list range(list l, int min, int max); - - list_interface* handout_list(list); - private: - void copy_lists(const data_t* src, data_t* dst); - static constexpr int bits_per_data = sizeof(data_t) * 8; - int listBegin(int lid) const { - return lid == 0 ? 0 : _list_end[lid-1]; - } - const data_t* getPtr(int eid) const { - return _data.begin() + static_cast(_entrySize) * static_cast(eid); - } - data_t* getPtr(int eid) { - return _data.begin() + static_cast(_entrySize) * static_cast(eid); - } - int numFlags() const { - return _flag_names.size(); - // return _list_end.end()[-1]; TODO: - } - int numLists() const { - return _list_end.size(); - } - bool getBit(const data_t* data, int id) const { - return data[id / bits_per_data] & - (0x01 << (bits_per_data - 1 - (id % bits_per_data))); + bool equal(list lh, list rh) const; + bool equal(list lh, list_flag rh) const; + + bool equal(list_flag lh, list rh) const { return equal(rh, lh); } + + bool equal(list_flag lh, list_flag rh) const { return lh == rh; } + + template + bool not_equal(L lh, R rh) const + { + return equal(lh, rh); + } + + template + bool greater_equal(L lh, R rh) const + { + return max(lh).flag >= max(rh).flag && min(lh).flag >= min(rh).flag; + } + + template + bool less_equal(L lh, R rh) const + { + return max(lh).flag <= max(rh).flag && min(lh).flag <= min(rh).flag; + } + + bool has(list lh, list rh) const; + bool has(list lh, list_flag rh) const; + + bool has(list_flag lh, list rh) const { return has(rh, lh); } + + bool has(list_flag lh, list_flag rh) const { return lh == rh; } + + template + bool hasnt(L lh, R rh) const + { + return ! has(lh, rh); + } + + operator bool() const { return _valid; } + + list range(list l, int min, int max); + + list_interface* handout_list(list); + +private: + void copy_lists(const data_t* src, data_t* dst); + static constexpr int bits_per_data = sizeof(data_t) * 8; + + int listBegin(int lid) const { return lid == 0 ? 0 : _list_end[lid - 1]; } + + const data_t* getPtr(int eid) const + { + return _data.begin() + + static_cast(_entrySize) * static_cast(eid); + } + + data_t* getPtr(int eid) + { + return _data.begin() + + static_cast(_entrySize) * static_cast(eid); + } + + int numFlags() const + { + return _flag_names.size(); + // return _list_end.end()[-1]; TODO: + } + + int numLists() const { return _list_end.size(); } + + bool getBit(const data_t* data, int 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) + { + data_t mask = 0x01 << (bits_per_data - 1 - (id % bits_per_data)); + if (value) { + data[id / bits_per_data] |= mask; + } else { + data[id / bits_per_data] &= ~mask; } - void setBit(data_t* data, int id, bool value = true) { - data_t mask = 0x01 << (bits_per_data-1 - (id % bits_per_data)); - if (value) { - data[id/bits_per_data] |= mask; - } else { - data[id/bits_per_data] &= ~mask; - } + } + + bool hasList(const data_t* data, int lid) const { return getBit(data, lid); } + + void setList(data_t* data, int lid, bool value = true) + { + if (lid >= 0) { + setBit(data, lid, value); } - bool hasList(const data_t* data, int lid) const { - return getBit(data, lid); + } + + bool hasFlag(const data_t* data, int fid) const { return getBit(data, fid + numLists()); } + + void setFlag(data_t* data, int fid, bool value = true) + { + if (fid >= 0) { + setBit(data, fid + numLists(), value); } - void setList(data_t* data, int lid, bool value = true) { - if(lid >= 0) { - setBit(data, lid, value); + } + + int toFid(list_flag e) const; + + auto flagStartMask() const + { + struct { + int segment; + data_t mask; + } res{numLists() / bits_per_data, ~static_cast(0) >> (numLists() % bits_per_data)}; + + return res; + } + + template + using managed_array = managed_array < T, + config<0, abs(config)>; + + static constexpr int 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)); + + int _entrySize; ///< entry size in data_t + // entries (created lists) + managed_array _data; + managed_array _entry_state; + + // defined list (meta data) + managed_array _list_end; + managed_array _flag_names; + managed_array _list_names; + /// keep track over lists accessed with get_var, and clear then at gc time + managed_array _list_handouts; + + bool _valid; + +public: + friend class name_flag_itr; + friend class list_impl; + + class named_flag_itr + { + const list_table& _list; + const data_t* _data; + + struct { + list_flag flag; + const char* name; + } _pos; + + void carry() + { + if (_pos.flag.flag + == _list._list_end[_pos.flag.list_id] - _list.listBegin(_pos.flag.list_id)) { + _pos.flag.flag = 0; + ++_pos.flag.list_id; } - } - bool hasFlag(const data_t* data, int fid) const { - return getBit(data, fid + numLists()); - } - void setFlag(data_t* data, int fid, bool value = true) { - if (fid >= 0) { - setBit(data, fid + numLists(), value); + if (_pos.flag.list_id == _list.numLists()) { + _pos.flag = null_flag; + } else { + _pos.name = _list._flag_names[_list.toFid(_pos.flag)]; } } - int toFid(list_flag e) const; - auto flagStartMask() const { - struct { int segment; data_t mask; } - res { - numLists() / bits_per_data, - ~static_cast(0) >> (numLists() % bits_per_data)}; - return res; - } - template - using managed_array = managed_array; - - static constexpr int 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)); - - int _entrySize; ///< entry size in data_t - // entries (created lists) - managed_array _data; - managed_array _entry_state; - - // defined list (meta data) - managed_array _list_end; - managed_array _flag_names; - managed_array _list_names; - /// keep track over lists accessed with get_var, and clear then at gc time - managed_array _list_handouts; - - bool _valid; - public: - friend class name_flag_itr; - friend class list_impl; - class named_flag_itr { - const list_table& _list; - const data_t* _data; - struct { - list_flag flag; - const char* name; - } _pos; - void carry() { - if (_pos.flag.flag == - _list._list_end[_pos.flag.list_id] - _list.listBegin(_pos.flag.list_id)) { - _pos.flag.flag = 0; - ++_pos.flag.list_id; - } - if(_pos.flag.list_id == _list.numLists()) { - _pos.flag = null_flag; - } else { - _pos.name = _list._flag_names[_list.toFid(_pos.flag)]; - } - } - void goToValid() { - bool valid; - do { - valid = true; - int fid = _list.toFid(_pos.flag); - if (_data == nullptr) { - if(_list._flag_names[fid] == nullptr) { - valid = false; - ++_pos.flag.flag; - } - } 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) { + void goToValid() + { + bool valid; + do { + valid = true; + int fid = _list.toFid(_pos.flag); + if (_data == nullptr) { + if (_list._flag_names[fid] == nullptr) { valid = false; ++_pos.flag.flag; - } - carry(); - } while(_pos.flag != null_flag && !valid); - } - public: - 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) - : _list{list}, _data{filter}, _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]}{ - goToValid(); - } - const auto* operator->() const { return &_pos; } - const auto& operator*() const { return _pos; } - const named_flag_itr& operator++() { - if (_pos.flag == null_flag) return *this; - ++_pos.flag.flag; + } + } 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) { + valid = false; + ++_pos.flag.flag; + } carry(); - if (_pos.flag == null_flag) return *this; - goToValid(); + } while (_pos.flag != null_flag && ! valid); + } + + public: + 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) + : _list{list} + , _data{filter} + , _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]} + { + goToValid(); + } + + const auto* operator->() const { return &_pos; } + + const auto& operator*() const { return _pos; } + + const named_flag_itr& operator++() + { + if (_pos.flag == null_flag) return *this; - } - }; - auto named_flags(list filter = list(-1)) const { - const data_t* f = filter.lid < 0 ? nullptr : getPtr(filter.lid); - struct { named_flag_itr _begin; named_flag_itr _end; - named_flag_itr begin() const { return _begin; } - named_flag_itr end() const { return _end; } - } res { - named_flag_itr(*this, f, 0), - named_flag_itr(*this, f)}; - return res; + ++_pos.flag.flag; + carry(); + if (_pos.flag == null_flag) + return *this; + goToValid(); + return *this; } + }; + + auto named_flags(list filter = list(-1)) const + { + const data_t* f = filter.lid < 0 ? nullptr : getPtr(filter.lid); + + struct { + named_flag_itr _begin; + named_flag_itr _end; + + named_flag_itr begin() const { return _begin; } + + named_flag_itr end() const { return _end; } + } res{named_flag_itr(*this, f, 0), named_flag_itr(*this, f)}; + + return res; + } #ifdef INK_ENABLE_STL - std::ostream& write(std::ostream&,list) const; + std::ostream& write(std::ostream&, list) const; #endif - }; -} +}; +} // namespace ink::runtime::internal diff --git a/inkcpp/operations.h b/inkcpp/operations.h index ac24ca2d..17019daa 100644 --- a/inkcpp/operations.h +++ b/inkcpp/operations.h @@ -6,60 +6,66 @@ #include "stack.h" #include "value.h" -namespace ink::runtime::internal { +namespace ink::runtime::internal +{ - namespace casting { - // default cast to none (invalid cast) - template - struct cast { - static constexpr value_type value = value_type::none; - }; +namespace casting +{ + // default cast to none (invalid cast) + template + struct cast { + static constexpr value_type value = value_type::none; + }; + + // no cast for same type + template + struct cast { + static constexpr value_type value = t; + }; +} // namespace casting - // no cast for same type - template - struct cast { - static constexpr value_type value = t; - }; +/** + * @brief Determines the number of arguments needed for an command. + */ +constexpr size_t command_num_args(Command cmd) +{ + if (cmd >= Command::TERNARY_OPERATORS_START && cmd <= Command::TERNARY_OPERATORS_END) { + return 3; + } else if (cmd >= Command::BINARY_OPERATORS_START && cmd <= Command::BINARY_OPERATORS_END) { + return 2; + } else if (cmd >= Command::UNARY_OPERATORS_START && cmd <= Command::UNARY_OPERATORS_END) { + return 1; + } else { + return 0; } +} - /** - * @brief Determines the number of arguments needed for an command. - */ - constexpr size_t command_num_args(Command cmd) { - if (cmd >= Command::TERNARY_OPERATORS_START && cmd <= Command::TERNARY_OPERATORS_END) { - return 3; - } else if (cmd >= Command::BINARY_OPERATORS_START && cmd <= Command::BINARY_OPERATORS_END) { - return 2; - } else if (cmd >= Command::UNARY_OPERATORS_START && cmd <= Command::UNARY_OPERATORS_END) { - return 1; - } else { - return 0; - } +/** + * @brief Operation definition. + * A class which contains a call operator to execute the operation needed + * for the command type combination. + * @tparam cmd Command which should be executed + * @tparam ty type on which the command should be executed + */ +template +class operation +{ +public: + static constexpr bool enabled = false; + + template + operation(const T& t) + { } /** - * @brief Operation definition. - * A class which contains a call operator to execute the operation needed - * for the command type combination. - * @tparam cmd Command which should be executed - * @tparam ty type on which the command should be executed + * @brief execute operation. + * @param stack were the result(s) get pushed + * @param vs array of values, first one = first argument etc */ - template - class operation { - public: - static constexpr bool enabled = false; - template - operation(const T& t) {} - /** - * @brief execute operation. - * @param stack were the result(s) get pushed - * @param vs array of values, first one = first argument etc - */ - void operator()(basic_eval_stack& stack, value* vs) { - inkFail("operation not implemented!"); - } - }; -} + void operator()(basic_eval_stack& stack, value* vs) { inkFail("operation not implemented!"); } +}; +} // namespace ink::runtime::internal // include header here to ensure correct order @@ -69,3 +75,20 @@ namespace ink::runtime::internal { #include "list_operations.h" #include "container_operations.h" #include "casting.h" + +template +ink::runtime::internal::value + ink::runtime::internal::value::redefine(const value& oth, T&... env) const +{ + if (type() != oth.type() && (type() == value_type::list_flag || type() == value_type::list) + && (oth.type() == value_type::list_flag || oth.type() == value_type::list)) { + /// @todo could break origin + if (oth.type() == value_type::list) { + return value{}.set(oth.get()); + } else { + return value{}.set(oth.get()); + } + } + inkAssert(type() == oth.type(), "try to redefine value of other type"); + return redefine(oth, {&env...}); +} diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 35fdee30..ef1ce58b 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -34,12 +34,13 @@ void basic_stream::append(const value& in) // ignore additional newlines after newline or glue if (d.type() == value_type::newline || d.type() == value_type::glue) { return; - } else if (d.type() == value_type::string && is_whitespace(d.get())) { + } else if (d.type() == value_type::string && ink::internal::is_whitespace(d.get())) { + } else if (d.type() == value_type::func_start || d.type() == value_type::func_end) { } else { break; } if (i == 0) { - break; + return; } --i; } @@ -67,9 +68,10 @@ void basic_stream::append(const value& in) } // Nullify whitespace - else if (d.type() == value_type::string && is_whitespace(d.get())) + else if (d.type() == value_type::string && ::ink::internal::is_whitespace(d.get())) d = value{}; + // If it's not a newline or whitespace, stop else break; @@ -434,7 +436,7 @@ bool basic_stream::text_past_save() const const value& d = _data[i]; if (d.type() == value_type::string) { // TODO: Cache what counts as whitespace? - if (! is_whitespace(d.get(), false)) + if (! ink::internal::is_whitespace(d.get(), false)) return true; } else if (d.printable()) { return true; diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index b80f9ef3..5d600c5e 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -169,7 +169,8 @@ inline const char* runner_impl::read() choice& runner_impl::add_choice() { inkAssert( - config::maxChoices < 0 || _choices.size() < config::maxChoices, "Ran out of choice storage!" + config::maxChoices < 0 || _choices.size() < static_cast(config::maxChoices), + "Ran out of choice storage!" ); return _choices.push(); } @@ -453,8 +454,8 @@ void runner_impl::getall(std::ostream& out) FString runner_impl::getline() { clear_tags(); - advance_line() FString result{}; - result.Append(_output.get_alloc(_globals->strings(), globals->lists())); + advance_line(); + FString result(ANSI_TO_TCHAR(_output.get_alloc(_globals->strings(), _globals->lists()))); if (! has_choices() && _fallback_choice) { choose(~0); @@ -477,20 +478,22 @@ void runner_impl::advance_line() // can be in save state becaues of choice // Garbage collection TODO: How often do we want to do this? + if (_saved) { + restore(); + } _globals->gc(); if (_output.saved()) { _output.restore(); } } -bool runner_impl::can_continue() const { return _ptr != nullptr; } +bool runner_impl::can_continue() const { return _ptr != nullptr && ! has_choices(); } void runner_impl::choose(size_t index) { if (has_choices()) { inkAssert(index < _choices.size(), "Choice index out of range"); } - restore(); // restore to stack state when choice was maked _globals->turn(); // Get the choice const auto& c = has_choices() ? _choices[index] : _fallback_choice.value(); @@ -659,7 +662,7 @@ runner_impl::change_type runner_impl::detect_change() const // Check if the old newline is still present (hasn't been glu'd) and // if there is new text (non-whitespace) in the stream since saving bool stillHasNewline = _output.saved_ends_with(value_type::newline); - bool hasAddedNewText = _output.text_past_save() || _tags.has_changed(); + bool hasAddedNewText = _output.text_past_save() || _tags.last_size() < num_tags(); // Newline is still there and there's no new text if (stillHasNewline && ! hasAddedNewText) { @@ -1120,7 +1123,8 @@ void runner_impl::step() // Create choice and record it if (flag & CommandFlag::CHOICE_IS_INVISIBLE_DEFAULT) { - _fallback_choice = choice{}.setup( + _fallback_choice.emplace(); + _fallback_choice.value().setup( _output, _globals->strings(), _globals->lists(), _choices.size(), path, current_thread(), tags->ptr() ); diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index c3e27d65..97c7c564 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -22,283 +22,309 @@ namespace ink::runtime::internal { - class story_impl; - class globals_impl; - class snapshot_impl; +class story_impl; +class globals_impl; +class snapshot_impl; +class runner_impl + : public runner_interface + , public snapshot_interface +{ +public: + // Creates a new runner at the start of a loaded ink story + runner_impl(const story_impl*, globals); + virtual ~runner_impl(); - class runner_impl : public runner_interface, public snapshot_interface - { - public: - // Creates a new runner at the start of a loaded ink story - runner_impl(const story_impl*, globals); - virtual ~runner_impl(); - - // used by the globals object to do garbage collection - void mark_used(string_table&, list_table&) const; + // used by the globals object to do garbage collection + void mark_used(string_table&, list_table&) const; #pragma region runner Implementation - // sets seed for prng in runner - virtual void set_rng_seed(uint32_t seed) override { _rng.srand(seed); } - // Checks that the runner can continue - virtual bool can_continue() const override; + // sets seed for prng in runner + virtual void set_rng_seed(uint32_t seed) override { _rng.srand(seed); } + + // Checks that the runner can continue + virtual bool can_continue() const override; - // Begin iterating choices - virtual const choice* begin() const override { return _choices.begin(); } + // Begin iterating choices + virtual const choice* begin() const override { return _choices.begin(); } - // End iterating choices - virtual const choice* end() const override { return _choices.end(); } + // End iterating choices + virtual const choice* end() const override { return _choices.end(); } - // Chooses a choice by index - virtual void choose(size_t index) override; + // Chooses a choice by index + virtual void choose(size_t index) override; - // runs silently - void getline_silent(); + /** Continue one line silently. + * executes story until end of next line and discards the result. */ + void getline_silent(); - virtual bool has_tags() const override; - virtual size_t num_tags() const override; - virtual const char* get_tag(size_t index) const override; + virtual bool has_tags() const override; + virtual size_t num_tags() const override; + virtual const char* get_tag(size_t index) const override; - snapshot* create_snapshot() const override; + snapshot* create_snapshot() const override; - size_t snap(unsigned char* data, snapper&) const; - const unsigned char* snap_load(const unsigned char* data, loader&); + 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; + // c-style getline + virtual const char* getline_alloc() override; #endif - // move to path - virtual bool move_to(hash_t path) override; + // move to path + virtual bool move_to(hash_t path) override; #ifdef INK_ENABLE_STL - // Gets a single line of output and stores it in a C++ std::string - virtual std::string getline() override; + // Gets a single line of output and stores it in a C++ std::string + virtual std::string getline() override; - // Reads a line into a std::ostream - virtual void getline(std::ostream&) override; + // Reads a line into a std::ostream + virtual void getline(std::ostream&) override; - // get all into string - virtual std::string getall() override; + // get all into string + virtual std::string getall() override; - // get all into stream - virtual void getall(std::ostream&) override; + // get all into stream + virtual void getall(std::ostream&) override; #endif #ifdef INK_ENABLE_UNREAL - // Reads a line into an Unreal FString - virtual FString getline() override; + // Reads a line into an Unreal FString + virtual FString getline() override; #endif #pragma endregion - protected: - // bind external - virtual void internal_bind(hash_t name, internal::function_base* function) override; - private: - // Advances the interpreter by a line. This fills the output buffer - void advance_line(); - - // Steps the interpreter a single instruction and returns - // when it has hit a new line - bool line_step(); - - // Steps the interpreter a single instruction - void step(); - - // Resets the runtime - void reset(); - - // == Save/Restore - void save(); - void restore(); - void forget(); - - enum class Scope { NONE, GLOBAL, LOCAL}; - template - value* get_var(hash_t variableName); - template - const value* get_var(hash_t variableName) const; - template - void set_var(hash_t variableName, const value& val, bool is_redef); - const value* dereference(const value& val); - - enum class change_type - { - no_change, - extended_past_newline, - newline_removed - }; - change_type detect_change() const; +protected: + // bind external + virtual void internal_bind(hash_t name, internal::function_base* function) override; - private: - template - inline T read(); +private: + // Advances the interpreter by a line. This fills the output buffer + void advance_line(); + + // Steps the interpreter a single instruction and returns + // when it has hit a new line + bool line_step(); + + // Steps the interpreter a single instruction + void step(); + + // Resets the runtime + void reset(); + + // == Save/Restore + void save(); + void restore(); + void forget(); + + enum class Scope { + NONE, + GLOBAL, + LOCAL + }; + template + value* get_var(hash_t variableName); + template + const value* get_var(hash_t variableName) const; + template + void set_var(hash_t variableName, const value& val, bool is_redef); + const value* dereference(const value& val); + + enum class change_type { + no_change, + extended_past_newline, + newline_removed + }; + + change_type detect_change() const; + +private: + template + inline T read(); + + choice& add_choice(); + void clear_choices(); - choice& add_choice(); - void clear_choices(); + void clear_tags(); - void clear_tags(); + // Special code for jumping from the current IP to another + void jump(ip_t, bool record_visits); - // Special code for jumping from the current IP to another - void jump(ip_t, bool record_visits); + void run_binary_operator(unsigned char cmd); + void run_unary_operator(unsigned char cmd); - void run_binary_operator(unsigned char cmd); - void run_unary_operator(unsigned char cmd); + frame_type execute_return(); + template + void start_frame(uint32_t target); - frame_type execute_return(); - template - void start_frame(uint32_t target); + void on_done(bool setDone); + void set_done_ptr(ip_t ptr); - void on_done(bool setDone); - void set_done_ptr(ip_t ptr); + inline thread_t current_thread() const { return _threads.empty() ? ~0 : _threads.top(); } - inline thread_t current_thread() const { return _threads.empty() ? ~0 : _threads.top(); } +public: + template + class threads : public internal::managed_restorable_stack + { + using base = internal::managed_restorable_stack; public: - template - class threads : public internal::managed_restorable_stack { - using base = internal::managed_restorable_stack; - public: - template = true > - threads() - : base(~0), - _threadDone(nullptr, reinterpret_cast(~0)) { - static_assert(sizeof...(D) == 0, "Don't use explicit template arguments!"); - } - template = true > - threads() - : base(~0), - _threadDone(nullptr, reinterpret_cast(~0)) { - static_assert(sizeof...(D) == 0, "Don't use explicit template arguments"); - _threadDone.clear(nullptr); - } - void clear() { - base::clear(); - _threadDone.clear(nullptr); - } - void save() { - base::save(); - _threadDone.save(); - } - void restore() { - base::restore(); - _threadDone.restore(); - } - void forget() { - base::forget(); - _threadDone.forget(); - } - - void set(size_t index, const ip_t& value) { - _threadDone.set(index, value); - } - const ip_t& get(size_t index) const { - return _threadDone.get(index); - } - const ip_t& operator[](size_t index) const { return get(index); } - - // snapshot interface - size_t snap(unsigned char* data, const snapper&) const override; - const unsigned char* snap_load(const unsigned char* data, const loader&) override; - - protected: - virtual void overflow(thread_t*& buffer, size_t& size) override final; - private: - using array_type = if_t, - internal::fixed_restorable_array>; - - void resize(size_t size, int) { _threadDone.resize(size); } - - - array_type _threadDone; - }; + template = true> + threads() + : base(~0) + , _threadDone(nullptr, reinterpret_cast(~0)) + { + static_assert(sizeof...(D) == 0, "Don't use explicit template arguments!"); + } + + template = true> + threads() + : base(~0) + , _threadDone(nullptr, reinterpret_cast(~0)) + { + static_assert(sizeof...(D) == 0, "Don't use explicit template arguments"); + _threadDone.clear(nullptr); + } + + void clear() + { + base::clear(); + _threadDone.clear(nullptr); + } + + void save() + { + base::save(); + _threadDone.save(); + } + + void restore() + { + base::restore(); + _threadDone.restore(); + } + + void forget() + { + base::forget(); + _threadDone.forget(); + } + + void set(size_t index, const ip_t& value) { _threadDone.set(index, value); } + + const ip_t& get(size_t index) const { return _threadDone.get(index); } + + const ip_t& operator[](size_t index) const { return get(index); } + + // snapshot interface + size_t snap(unsigned char* data, const snapper&) const override; + const unsigned char* snap_load(const unsigned char* data, const loader&) override; + + protected: + virtual void overflow(thread_t*& buffer, size_t& size) override final; private: - const story_impl* const _story; - story_ptr _globals; - executer _operations; - - // == State == - - // Instruction pointer - ip_t _ptr; - ip_t _backup; // backup pointer - ip_t _done; // when we last hit a done - - // Output stream - internal::stream _output; - - // Runtime stack. Used to store temporary variables and callstack - internal::stack _stack; - internal::stack _ref_stack; - - // Evaluation stack - bool _evaluation_mode = false; - bool _string_mode = false; - internal::eval_stack _eval; - bool _saved_evaluation_mode = false; - - // Keeps track of what threads we're inside - threads _threads; - - // Choice list - managed_restorable_array _choices; - optional _fallback_choice; - - // Tag list - managed_restorable_array _tags; - int _choice_tags_begin; - - // TODO: Move to story? Both? - functions _functions; - - // Container set - struct ContainerData { - container_t id = ~0u; - ip_t offset = 0; - bool operator==(const ContainerData& oth) const { - return oth.id == id && oth.offset == offset; - } - bool operator!=(const ContainerData& oth) const { return !(*this == oth); } - }; - internal::managed_restorable_stack _container; - bool _is_falling = false; - - bool _saved = false; - - prng _rng; + using array_type = if_t< + dynamic, internal::allocated_restorable_array, + internal::fixed_restorable_array>; + + void resize(size_t size, int) { _threadDone.resize(size); } + + array_type _threadDone; }; - template - void runner_impl::threads::overflow(thread_t*& buffer, size_t& size) { - base::overflow(buffer, size); - if constexpr (dynamic) { - resize(size, 0); - } - } +private: + const story_impl* const _story; + story_ptr _globals; + executer _operations; - template - size_t runner_impl::threads::snap( unsigned char* data, const snapper& snapper ) const { - unsigned char* ptr = data; - ptr += base::snap( data ? ptr : nullptr, snapper ); - ptr += _threadDone.snap( data ? ptr : nullptr, snapper ); - return ptr - data; - } + // == State == - template - const unsigned char* runner_impl::threads::snap_load( const unsigned char* ptr, const loader& loader ) { - ptr = base::snap_load( ptr, loader ); - ptr = _threadDone.snap_load( ptr, loader ); - return ptr; + // Instruction pointer + ip_t _ptr; + ip_t _backup; // backup pointer + ip_t _done; // when we last hit a done + + // Output stream + internal::stream _output; + + // Runtime stack. Used to store temporary variables and callstack + internal::stack < abs(config::limitRuntimeStack), config::limitRuntimeStack<0> _stack; + internal::stack < abs(config::limitReferenceStack), config::limitReferenceStack<0> _ref_stack; + + // Evaluation stack + bool _evaluation_mode = false; + bool _string_mode = false; + internal::eval_stack < abs(config::limitEvalStackDepth), config::limitEvalStackDepth<0> _eval; + bool _saved_evaluation_mode = false; + + // Keeps track of what threads we're inside + threads < config::limitContainerDepth<0, abs(config::limitThreadDepth)> _threads; + + // Choice list + managed_restorable_array < snap_choice, config::maxChoices<0, abs(config::maxChoices)> _choices; + optional _fallback_choice; + + // Tag list + managed_restorable_array < snap_tag, + config::limitActiveTags<0, abs(config::limitActiveTags)> _tags; + int _choice_tags_begin; + + // TODO: Move to story? Both? + functions _functions; + + // Container set + struct ContainerData { + container_t id = ~0u; + ip_t offset = 0; + + bool operator==(const ContainerData& oth) const { return oth.id == id && oth.offset == offset; } + + bool operator!=(const ContainerData& oth) const { return ! (*this == oth); } + }; + + internal::managed_restorable_stack < ContainerData, + config::limitContainerDepth<0, abs(config::limitContainerDepth)> _container; + bool _is_falling = false; + + bool _saved = false; + + prng _rng; +}; + +template +void runner_impl::threads::overflow(thread_t*& buffer, size_t& size) +{ + base::overflow(buffer, size); + if constexpr (dynamic) { + resize(size, 0); } +} - template<> - inline const char* runner_impl::read(); +template +size_t runner_impl::threads::snap(unsigned char* data, const snapper& snapper) const +{ + unsigned char* ptr = data; + ptr += base::snap(data ? ptr : nullptr, snapper); + ptr += _threadDone.snap(data ? ptr : nullptr, snapper); + return ptr - data; +} + +template +const unsigned char* + runner_impl::threads::snap_load(const unsigned char* ptr, const loader& loader) +{ + ptr = base::snap_load(ptr, loader); + ptr = _threadDone.snap_load(ptr, loader); + return ptr; +} + +template<> +inline const char* runner_impl::read(); #ifdef INK_ENABLE_STL - std::ostream& operator<<(std::ostream&, runner_impl&); +std::ostream& operator<<(std::ostream&, runner_impl&); #endif -} +} // namespace ink::runtime::internal diff --git a/inkcpp/stack.h b/inkcpp/stack.h index 5bed766a..7faca24f 100644 --- a/inkcpp/stack.h +++ b/inkcpp/stack.h @@ -7,199 +7,231 @@ namespace ink { - namespace runtime +namespace runtime +{ + namespace internal { - namespace internal + class string_table; + + struct entry { + hash_t name = 0; + value data; + }; + + enum class frame_type : uint32_t { + function, + tunnel, + thread + }; + + class basic_stack : protected restorable { - class string_table; - struct entry - { - hash_t name = 0; - value data; - }; + protected: + basic_stack(entry* data, size_t size); - enum class frame_type : uint32_t - { - function, - tunnel, - thread - }; + // base class + using base = restorable; - class basic_stack : protected restorable - { - protected: - basic_stack(entry* data, size_t size); + public: + virtual ~basic_stack() = default; - // base class - using base = restorable; + // Sets existing value, or creates a new one at this callstack entry + void set(hash_t name, const value& val); - public: - // Sets existing value, or creates a new one at this callstack entry - void set(hash_t name, const value& val); + // Gets an existing value, or nullptr + const value* get(hash_t name) const; + value* get(hash_t name); + value* get_from_frame(int ci, hash_t name); - // Gets an existing value, or nullptr - const value* get(hash_t name) const; - value* get(hash_t name); - value* get_from_frame(int ci, hash_t name); + // pushes a new frame onto the stack + // @param eval if evaluation mode was active + template + void push_frame(offset_t return_to, bool eval); - // pushes a new frame onto the stack - // @param eval if evaluation mode was active - template - void push_frame(offset_t return_to, bool eval); + // Pops a frame (and all temporary variables) from the callstack. + offset_t pop_frame(frame_type* type, bool& eval); - // Pops a frame (and all temporary variables) from the callstack. - offset_t pop_frame(frame_type* type, bool& eval); + // Returns true if there are any frames on the stack + bool has_frame(frame_type* type = nullptr) const; - // Returns true if there are any frames on the stack - bool has_frame(frame_type* type = nullptr) const; + // Clears the entire stack + void clear(); - // Clears the entire stack - void clear(); + // Garbage collection + void mark_used(string_table&, list_table&) const; - // Garbage collection - void mark_used(string_table&, list_table&) const; + // == Threading == - // == Threading == + // Forks a new thread from the current callstack and returns that thread's unique id + thread_t fork_thread(); - // Forks a new thread from the current callstack and returns that thread's unique id - thread_t fork_thread(); + // Mark a thread as "done". It's callstack is still preserved until collapse_to_thread is + // called. + void complete_thread(thread_t thread); - // Mark a thread as "done". It's callstack is still preserved until collapse_to_thread is called. - void complete_thread(thread_t thread); + // Collapses the callstack to the state of a single thread + void collapse_to_thread(thread_t thread); - // Collapses the callstack to the state of a single thread - void collapse_to_thread(thread_t thread); + // == Save/Restore == + void save(); + void restore(); + void forget(); - // == Save/Restore == - void save(); - void restore(); - void forget(); + // replace all pointer in current frame with values from _stack + void fetch_values(basic_stack& _stack); + // push all values to other _stack + void push_values(basic_stack& _stack); - // replace all pointer in current frame with values from _stack - void fetch_values(basic_stack& _stack); - // push all values to other _stack - void push_values(basic_stack& _stack); + // snapshot interface + size_t snap(unsigned char* data, const snapper&) const; + const unsigned char* snap_load(const unsigned char* data, const loader&); - // snapshot interface - size_t snap(unsigned char* data, const snapper&) const; - const unsigned char* snap_load(const unsigned char* data, const loader&); + private: + entry& add(hash_t name, const value& val); + const entry* pop(); - private: - entry& add(hash_t name, const value& val); - const entry* pop(); + entry* do_thread_jump_pop(const iterator& jump); - entry* do_thread_jump_pop(const iterator& jump); + // thread ids + thread_t _next_thread = 0; + thread_t _backup_next_thread = 0; - // thread ids - thread_t _next_thread = 0; - thread_t _backup_next_thread = 0; + static const hash_t NulledHashId = ~0; + }; - static const hash_t NulledHashId = ~0; - }; - - template<> void basic_stack::push_frame(offset_t return_to, bool eval); - template<> void basic_stack::push_frame(offset_t return_to, bool eval); - template<> void basic_stack::push_frame(offset_t return_to, bool eval); + template<> + void basic_stack::push_frame(offset_t return_to, bool eval); + template<> + void basic_stack::push_frame(offset_t return_to, bool eval); + template<> + void basic_stack::push_frame(offset_t return_to, bool eval); - /** - * @brief stack for call history and temporary variables - * @tparam N initial capacity of stack - * @tparam Dynamic weather or not expand if stack is full - */ - template - class stack : public basic_stack + /** + * @brief stack for call history and temporary variables + * @tparam N initial capacity of stack + * @tparam Dynamic weather or not expand if stack is full + */ + template + class stack : public basic_stack + { + public: + stack() + : basic_stack(&_stack[0], N) { - public: - stack() : basic_stack(&_stack[0], N) { } - private: - // stack - entry _stack[N]; - }; - - template - class stack : public basic_stack + } + + private: + // stack + entry _stack[N]; + }; + + template + class stack : public basic_stack + { + public: + stack() + : basic_stack(nullptr, 0) { - public: - stack() : basic_stack(nullptr, 0) {} - protected: - virtual void overflow(entry*& buffer, size_t& size) override { - if (!buffer) { - buffer = _stack.data(); - size = _stack.capacity(); - } else { - _stack.extend(); - buffer = _stack.data(); - size = _stack.capacity(); - } - } - private: - managed_array _stack; - }; + } - class basic_eval_stack : protected restorable + protected: + virtual void overflow(entry*& buffer, size_t& size) override { - protected: - basic_eval_stack(value* data, size_t size); + if (! buffer) { + buffer = _stack.data(); + size = _stack.capacity(); + } else { + _stack.extend(); + buffer = _stack.data(); + size = _stack.capacity(); + } + } - using base = restorable; + private: + managed_array _stack; + }; - public: + class basic_eval_stack : protected restorable + { + protected: + basic_eval_stack(value* data, size_t size); + + using base = restorable; - // Push value onto the stack - void push(const value&); + public: + virtual ~basic_eval_stack() = default; - // Pop a value off the stack - value pop(); + // Push value onto the stack + void push(const value&); - // Gets the top value without popping - const value& top() const; + // Pop a value off the stack + value pop(); - // Gets the top non null value without popping - const value& top_value() const; + // Gets the top value without popping + const value& top() const; - // Check if the stack is empty - bool is_empty() const; + // Gets the top non null value without popping + const value& top_value() const; - // Clear stack - void clear(); + // Check if the stack is empty + bool is_empty() const; - /** Mark used strings and lists for garbage collection */ - void mark_used(string_table&, list_table&) const; + // Clear stack + void clear(); - // == Save/Restore == - void save(); - void restore(); - void forget(); + /** Mark used strings and lists for garbage collection */ + void mark_used(string_table&, list_table&) const; - // snapshot interface - size_t snap(unsigned char* data, const snapper& snapper) const - { return base::snap(data, snapper); } - const unsigned char* snap_load(const unsigned char* data, const loader& loader) - { return base::snap_load(data, loader); } - }; + // == Save/Restore == + void save(); + void restore(); + void forget(); - template - class eval_stack : public basic_eval_stack + // snapshot interface + size_t snap(unsigned char* data, const snapper& snapper) const { - public: - eval_stack() : basic_eval_stack(_stack, N) { } - private: - value _stack[N]; - }; - template - class eval_stack : public basic_eval_stack + return base::snap(data, snapper); + } + + const unsigned char* snap_load(const unsigned char* data, const loader& loader) { - public: - eval_stack() : basic_eval_stack(nullptr, 0) {} - protected: - virtual void overflow(value*& buffer, size_t& size) override { - _stack.extend(); - buffer = _stack.data(); - size = _stack.capacity(); - } - private: - managed_array _stack; - }; - } - } -} + return base::snap_load(data, loader); + } + }; + + template + class eval_stack : public basic_eval_stack + { + public: + eval_stack() + : basic_eval_stack(_stack, N) + { + } + + private: + value _stack[N]; + }; + + template + class eval_stack : public basic_eval_stack + { + public: + eval_stack() + : basic_eval_stack(nullptr, 0) + { + } + + protected: + virtual void overflow(value*& buffer, size_t& size) override + { + _stack.extend(); + buffer = _stack.data(); + size = _stack.capacity(); + } + + private: + managed_array _stack; + }; + } // namespace internal +} // namespace runtime +} // namespace ink diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index 64ee33b6..a3eacd79 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -10,243 +10,248 @@ namespace ink::runtime { #ifdef INK_ENABLE_STL - story* story::from_file(const char* filename) - { - return new internal::story_impl(filename); - } +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) - { - return new internal::story_impl(data, length, freeOnDestroy); - } +story* story::from_binary(unsigned char* data, size_t length, bool freeOnDestroy) +{ + return new internal::story_impl(data, length, freeOnDestroy); } +} // namespace ink::runtime namespace ink::runtime::internal { #ifdef INK_ENABLE_STL - unsigned char* read_file_into_memory(const char* filename, size_t* read) - { - using namespace std; +unsigned char* read_file_into_memory(const char* filename, size_t* read) +{ + using namespace std; - ifstream ifs(filename, ios::binary | ios::ate); + ifstream ifs(filename, ios::binary | ios::ate); - if (!ifs.is_open()) { - throw ink_exception("Failed to open file: " + std::string(filename)); - } + if (! ifs.is_open()) { + throw ink_exception("Failed to open file: " + std::string(filename)); + } - ifstream::pos_type pos = ifs.tellg(); - size_t length = (size_t)pos; - unsigned char* data = new unsigned char[length]; - ifs.seekg(0, ios::beg); - ifs.read((char*)data, length); - ifs.close(); + ifstream::pos_type pos = ifs.tellg(); + size_t length = ( size_t ) pos; + unsigned char* data = new unsigned char[length]; + ifs.seekg(0, ios::beg); + ifs.read(( char* ) data, length); + ifs.close(); - *read = (size_t)length; - return data; - } + *read = ( size_t ) length; + return data; +} - story_impl::story_impl(const char* filename) - : _file(nullptr) - , _length(0) - , _string_table(nullptr) - , _instruction_data(nullptr) - , _managed(true) - { - // Load file into memory - _file = read_file_into_memory(filename, &_length); +story_impl::story_impl(const char* filename) + : _file(nullptr) + , _length(0) + , _string_table(nullptr) + , _instruction_data(nullptr) + , _managed(true) +{ + // Load file into memory + _file = read_file_into_memory(filename, &_length); - // Find all the right data sections - setup_pointers(); + // Find all the right data sections + setup_pointers(); - // create story block - _block = new internal::ref_block(); - _block->references = 1; - } + // create story block + _block = new internal::ref_block(); + _block->references = 1; +} #endif - story_impl::story_impl(unsigned char* binary, size_t len, bool manage /*= true*/) - : _file(binary), _length(len), _managed(manage) - { - // Setup data section pointers - setup_pointers(); - - // create story block - _block = new internal::ref_block(); - _block->references = 1; - } +story_impl::story_impl(unsigned char* binary, size_t len, bool manage /*= true*/) + : _file(binary) + , _length(len) + , _managed(manage) +{ + // Setup data section pointers + setup_pointers(); - story_impl::~story_impl() - { - // delete file memory if we're responsible for it - if (_file != nullptr && _managed) - delete[] _file; - - // clear pointers - _file = nullptr; - _instruction_data = nullptr; - _string_table = nullptr; - - // clear out our reference block - _block->valid = false; - internal::ref_block::remove_reference(_block); - _block = nullptr; - } + // create story block + _block = new internal::ref_block(); + _block->references = 1; +} - const char* story_impl::string(uint32_t index) const - { - return _string_table + index; - } +story_impl::~story_impl() +{ + // delete file memory if we're responsible for it + if (_file != nullptr && _managed) + delete[] _file; + + // clear pointers + _file = nullptr; + _instruction_data = nullptr; + _string_table = nullptr; + + // clear out our reference block + _block->valid = false; + internal::ref_block::remove_reference(_block); + _block = nullptr; +} - bool story_impl::iterate_containers(const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse) const - { - if (iterator == nullptr) - { - // Empty check - if (_container_list_size == 0) - { - return false; - } +const char* story_impl::string(uint32_t index) const { return _string_table + index; } - // Start - iterator = reverse - ? _container_list + (_container_list_size - 1) * 2 - : _container_list; - } - else - { - // Range check - inkAssert(iterator >= _container_list && iterator <= _container_list + _container_list_size * 2, "Container fail"); - - // Advance - iterator += reverse ? -2 : 2; - - // End? - if (iterator >= _container_list + _container_list_size * 2 || iterator < _container_list) - { - iterator = nullptr; - index = 0; - offset = nullptr; - return false; - } +bool story_impl::iterate_containers( + const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse +) const +{ + if (iterator == nullptr) { + // Empty check + if (_container_list_size == 0) { + return false; } - // Get metadata - index = *(iterator + 1); - offset = *iterator + instructions(); - return true; + // Start + iterator = reverse ? _container_list + (_container_list_size - 1) * 2 : _container_list; + } else { + // Range check + inkAssert( + iterator >= _container_list && iterator <= _container_list + _container_list_size * 2, + "Container fail" + ); + + // Advance + iterator += reverse ? -2 : 2; + + // End? + if (iterator >= _container_list + _container_list_size * 2 || iterator < _container_list) { + iterator = nullptr; + index = 0; + offset = nullptr; + return false; + } } - bool story_impl::get_container_id(ip_t offset, container_t& container_id) const - { - const uint32_t* iter = nullptr; - ip_t iter_offset = nullptr; - while (iterate_containers(iter, container_id, iter_offset)) - { - if (iter_offset == offset) - return true; - } + // Get metadata + index = *(iterator + 1); + offset = *iterator + instructions(); + return true; +} - return false; +bool story_impl::get_container_id(ip_t offset, container_t& container_id) const +{ + const uint32_t* iter = nullptr; + ip_t iter_offset = nullptr; + while (iterate_containers(iter, container_id, iter_offset)) { + if (iter_offset == offset) + return true; } - - CommandFlag story_impl::container_flag(ip_t offset) const { - inkAssert((static_cast(offset[0]) == Command::START_CONTAINER_MARKER || - static_cast(offset[0]) == Command::END_CONTAINER_MARKER), "Tried to fetch container flag from non container command!"); - return static_cast(offset[1]); - } - CommandFlag story_impl::container_flag(container_t id) const { - const uint32_t* iter = nullptr; - ip_t offset; - container_t c_id; - while(iterate_containers(iter, c_id, offset)) { - if (c_id == id) { - inkAssert(static_cast(offset[0]) == Command::START_CONTAINER_MARKER, "Container list pointer is invalid!"); - return static_cast(offset[1]); - } + return false; +} + +CommandFlag story_impl::container_flag(ip_t offset) const +{ + inkAssert( + (static_cast(offset[0]) == Command::START_CONTAINER_MARKER + || static_cast(offset[0]) == Command::END_CONTAINER_MARKER), + "Tried to fetch container flag from non container command!" + ); + return static_cast(offset[1]); +} + +CommandFlag story_impl::container_flag(container_t id) const +{ + const uint32_t* iter = nullptr; + ip_t offset; + container_t c_id; + while (iterate_containers(iter, c_id, offset)) { + if (c_id == id) { + inkAssert( + static_cast(offset[0]) == Command::START_CONTAINER_MARKER, + "Container list pointer is invalid!" + ); + return static_cast(offset[1]); } - inkFail("Container not found -> can't fetch flag"); - return CommandFlag::NO_FLAGS; } + inkFail("Container not found -> can't fetch flag"); + return CommandFlag::NO_FLAGS; +} - ip_t story_impl::find_offset_for(hash_t path) const - { - hash_t* iter = _container_hash_start; - - while (iter != _container_hash_end) - { - if (*iter == path) - { - return instructions() + *(offset_t*)(iter + 1); - } +ip_t story_impl::find_offset_for(hash_t path) const +{ + hash_t* iter = _container_hash_start; - iter += 2; + while (iter != _container_hash_end) { + if (*iter == path) { + return instructions() + *( offset_t* ) (iter + 1); } - return nullptr; + iter += 2; } - globals story_impl::new_globals() - { - // create the new globals store - return globals(new globals_impl(this), _block); - } + return nullptr; +} - 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, - }); - inkAssert(end == snapshot.get_runner_snap(0), "not all data were used for global reconstruction"); - return globals(globs, _block); - } +globals story_impl::new_globals() +{ + // create the new globals store + return globals(new globals_impl(this), _block); +} - runner story_impl::new_runner(globals store) - { - if (store == nullptr) - store = new_globals(); - return runner(new runner_impl(this, store), _block); - } +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, + } + ); + inkAssert(end == snapshot.get_runner_snap(0), "not all data were used for global reconstruction"); + return globals(globs, _block); +} - runner story_impl::new_runner_from_snapshot(const snapshot& data, globals store, unsigned idx) - { - 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 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" - ); - return runner(run, _block); - } +runner story_impl::new_runner(globals store) +{ + if (store == nullptr) + store = new_globals(); + return runner(new runner_impl(this, store), _block); +} - void story_impl::setup_pointers() - { - using header = ink::internal::header; - _header = header::parse_header(reinterpret_cast(_file)); +runner story_impl::new_runner_from_snapshot(const snapshot& data, globals store, unsigned idx) +{ + 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, + }; + // snapshot id is inverso of creation time, but creation time is the more intouitve numbering to + // use + idx = (data.num_runners() - idx - 1); + 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" + ); + return runner(run, _block); +} + +void story_impl::setup_pointers() +{ + using header = ink::internal::header; + _header = header::parse_header(reinterpret_cast(_file)); - // String table is after the header - _string_table = (char*)_file + header::Size; + // String table is after the header + _string_table = ( char* ) _file + header::Size; - // Pass over strings - const char* ptr = _string_table; - if (*ptr == 0) // SPECIAL: No strings - { - ptr++; - } - else while (true) - { + // Pass over strings + const char* ptr = _string_table; + if (*ptr == 0) // SPECIAL: No strings + { + ptr++; + } else + while (true) { // Read until null terminator while (*ptr != 0) ptr++; @@ -255,89 +260,96 @@ namespace ink::runtime::internal ptr++; // Second null. Strings are done. - if (*ptr == 0) - { + if (*ptr == 0) { ptr++; break; } } - // check if lists are defined - _list_meta = ptr; - if(list_flag flag = _header.read_list_flag(ptr); flag != null_flag) { - // skip list definitions - auto list_id = flag.list_id; - while(*ptr != 0) {++ptr;} ++ptr; // skip list name - do{ - if(flag.list_id != list_id) { - list_id = flag.list_id; - while(*ptr != 0) {++ptr;} ++ptr; // skip list name - } - while(*ptr != 0) { ++ptr; } ++ptr; // skip flag name - } while ((flag = _header.read_list_flag(ptr)) != null_flag); - - _lists = reinterpret_cast(ptr); - // skip predefined lists - while(_header.read_list_flag(ptr) != null_flag) { - while(_header.read_list_flag(ptr) != null_flag); - } - } else { - _list_meta = nullptr; - _lists = nullptr; + // check if lists are defined + _list_meta = ptr; + if (list_flag flag = _header.read_list_flag(ptr); flag != null_flag) { + // skip list definitions + auto list_id = flag.list_id; + while (*ptr != 0) { + ++ptr; } - inkAssert(_header.ink_bin_version_number == ink::InkBinVersion, "invalid InkBinVerison! currently: %i you used %i", ink::InkBinVersion, _header.ink_bin_version_number); - inkAssert(_header.endien == header::endian_types::same, - "different endien support not yet implemented"); - - - - _num_containers = *(uint32_t*)(ptr); - ptr += sizeof(uint32_t); - - // Pass over the container data - _container_list_size = 0; - _container_list = (uint32_t*)(ptr); - while (true) - { - uint32_t val = *(uint32_t*)ptr; - if (val == ~0) - { - ptr += sizeof(uint32_t); - break; + ++ptr; // skip list name + do { + if (flag.list_id != list_id) { + list_id = flag.list_id; + while (*ptr != 0) { + ++ptr; + } + ++ptr; // skip list name } - else - { - ptr += sizeof(uint32_t) * 2; - _container_list_size++; + while (*ptr != 0) { + ++ptr; } + ++ptr; // skip flag name + } while ((flag = _header.read_list_flag(ptr)) != null_flag); + + _lists = reinterpret_cast(ptr); + // skip predefined lists + while (_header.read_list_flag(ptr) != null_flag) { + while (_header.read_list_flag(ptr) != null_flag) + ; } - - // Next is the container hash map - _container_hash_start = (hash_t*)(ptr); - while (true) - { - uint32_t val = *(uint32_t*)ptr; - if (val == ~0) - { - _container_hash_end = (hash_t*)(ptr); - ptr += sizeof(uint32_t); - break; - } - + } else { + _list_meta = nullptr; + _lists = nullptr; + } + inkAssert( + _header.ink_bin_version_number == ink::InkBinVersion, + "invalid InkBinVerison! currently: %i you used %i", ink::InkBinVersion, + _header.ink_bin_version_number + ); + inkAssert( + _header.endien == header::endian_types::same, "different endien support not yet implemented" + ); + + + _num_containers = *( uint32_t* ) (ptr); + ptr += sizeof(uint32_t); + + // Pass over the container data + _container_list_size = 0; + _container_list = ( uint32_t* ) (ptr); + while (true) { + uint32_t val = *( uint32_t* ) ptr; + if (val == ~0) { + ptr += sizeof(uint32_t); + break; + } else { ptr += sizeof(uint32_t) * 2; + _container_list_size++; } + } - // After strings comes instruction data - _instruction_data = (ip_t)ptr; + // Next is the container hash map + _container_hash_start = ( hash_t* ) (ptr); + while (true) { + uint32_t val = *( uint32_t* ) ptr; + if (val == ~0) { + _container_hash_end = ( hash_t* ) (ptr); + ptr += sizeof(uint32_t); + break; + } - // Debugging info - /*{ - const uint32_t* iter = nullptr; - container_t index; ip_t offset; - while (this->iterate_containers(iter, index, offset)) - { - std::clog << "Container #" << index << ": " << (int)offset << std::endl; - } - }*/ + ptr += sizeof(uint32_t) * 2; } + + // After strings comes instruction data + _instruction_data = ( ip_t ) ptr; + + // Debugging info + /*{ + const uint32_t* iter = nullptr; + container_t index; ip_t offset; + while (this->iterate_containers(iter, index, offset)) + { + std::clog << "Container #" << index << ": " << (int)offset << std::endl; + } + }*/ } +} // namespace ink::runtime::internal diff --git a/inkcpp/string_utils.h b/inkcpp/string_utils.h index 3f8b97b5..33dc26e2 100644 --- a/inkcpp/string_utils.h +++ b/inkcpp/string_utils.h @@ -7,138 +7,195 @@ #include #ifndef EINVAL -#define EINVAL -1 +# define EINVAL -1 #endif -namespace ink::runtime::internal { - // error behavior from: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/itoa-s-itow-s?view=msvc-160 - inline int toStr(char * buffer, size_t size, uint32_t value) { +namespace ink::runtime::internal +{ +// error behavior from: +// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/itoa-s-itow-s?view=msvc-160 +inline int toStr(char* buffer, size_t size, uint32_t value) +{ #ifdef WIN32 - return _itoa_s(value, buffer, size, 10); + return _itoa_s(value, buffer, size, 10); #else - if ( buffer == nullptr || size < 1 ){ return EINVAL; } - int res = snprintf(buffer, size, "%d", value); - if (res > 0 && res < size) { return 0; } + if (buffer == nullptr || size < 1) { return EINVAL; -#endif } + int res = snprintf(buffer, size, "%d", value); + if (res > 0 && res < size) { + return 0; + } + return EINVAL; +#endif +} - // error behavior from: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/itoa-s-itow-s?view=msvc-160 - inline int toStr(char * buffer, size_t size, int32_t value) { +// error behavior from: +// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/itoa-s-itow-s?view=msvc-160 +inline int toStr(char* buffer, size_t size, int32_t value) +{ #ifdef WIN32 - return _itoa_s(value, buffer, size, 10); + return _itoa_s(value, buffer, size, 10); #else - if ( buffer == nullptr || size < 1 ){ return EINVAL; } - int res = snprintf(buffer, size, "%d", value); - if (res > 0 && res < size) { return 0; } + if (buffer == nullptr || size < 1) { return EINVAL; -#endif } + int res = snprintf(buffer, size, "%d", value); + if (res > 0 && res < size) { + return 0; + } + return EINVAL; +#endif +} - // error behavior from: 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) { +// error behavior from: +// 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 - return _gcvt_s(buffer, size, value, 7); // number of significant digits + int digits = 7; + for (float f = value; f > 1.f; f /= 10.f) { + ++digits; + } + int res = _gcvt_s(buffer, size, value, digits); // number of significant digits + return res; #else - if ( buffer == nullptr || size < 1 ) { return EINVAL; } - int res = snprintf(buffer, size, "%.7f", value); - if (res < 0 || res >= size) { return EINVAL; } - // trunc cat zeros B007 - char* itr = buffer + res - 1; - while(*itr == '0') { *itr--=0; --res; } - if (*itr == '.') { *itr-- = 0; --res;} - return 0; + if (buffer == nullptr || size < 1) { + return EINVAL; + } + int res = snprintf(buffer, size, "%.7f", value); + if (res < 0 || res >= size) { + return EINVAL; + } + // trunc cat zeros B007 + char* itr = buffer + res - 1; + while (*itr == '0') { + *itr-- = 0; + --res; + } + if (*itr == '.') { + *itr-- = 0; + --res; + } + return 0; #endif +} + +inline int toStr(char* buffer, size_t size, const char* c) +{ + char* ptr = buffer; + size_t i = 0; + while (*c && i < size) { + *ptr++ = *c; + ++i; + } + if (i >= size) { + return EINVAL; } + *ptr = 0; + return 0; +} - inline int toStr(char* buffer, size_t size, const char* c) { - char* ptr = buffer; - size_t i = 0; - while(*c && i < size) { - *ptr++ = *c; - ++i; - } - if (i >= size) { return EINVAL; } - *ptr = 0; - return 0; +inline int toStr(char* buffer, size_t size, const value& v) +{ + switch (v.type()) { + case value_type::int32: return toStr(buffer, size, v.get()); + case value_type::uint32: return toStr(buffer, size, v.get()); + case value_type::float32: return toStr(buffer, size, v.get()); + case value_type::newline: return toStr(buffer, size, "\n"); + default: inkFail("only support toStr for numeric types"); return -1; } +} - inline int toStr(char * buffer, size_t size, const value& v) { - switch(v.type()) { - case value_type::int32: - return toStr(buffer, size, v.get()); - case value_type::uint32: - return toStr(buffer, size, v.get()); - case value_type::float32: - return toStr(buffer, size, v.get()); - case value_type::newline: - return toStr(buffer, size, "\n"); - default: - inkFail("only support toStr for numeric types"); - return -1; - } +// return a upper bound for the string representation of the number +inline constexpr size_t decimal_digits(uint32_t number) +{ + size_t length = 1; + while (number /= 10) { + ++length; } + return length; +} - // return a upper bound for the string representation of the number - inline constexpr size_t decimal_digits(uint32_t number) { - size_t length = 1; - while(number /= 10) { ++length; } - return length; +inline constexpr size_t decimal_digits(int32_t number) +{ + size_t length = number < 0 ? 2 : 1; + while (number /= 10) { + ++length; } + return length; +} + +inline constexpr size_t decimal_digits(float number) +{ + return decimal_digits(static_cast(number)) + 8; +} - inline constexpr size_t decimal_digits(int32_t number) { - size_t length = number < 0 ? 2 : 1; - while(number /= 10) { ++length; } - return length; +inline constexpr size_t value_length(const value& v) +{ + switch (v.type()) { + case value_type::int32: return decimal_digits(v.get()); + case value_type::uint32: return decimal_digits(v.get()); + 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; } +} - inline constexpr size_t decimal_digits(float number) { - return decimal_digits(static_cast(number)) + 8; +inline constexpr bool str_equal(const char* lh, const char* rh) +{ + while (*lh && *rh && *lh == *rh) { + ++lh; + ++rh; } + return *lh == *rh; +} - inline constexpr size_t value_length(const value& v) { - switch(v.type()) { - case value_type::int32: - return decimal_digits(v.get()); - case value_type::uint32: - return decimal_digits(v.get()); - 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; +inline constexpr bool str_equal_len(const char* lh, const char* rh, size_t len) +{ + for (size_t i = 0; i < len; ++i) { + if (! (*rh && *lh && *lh == *rh)) { + return false; } } + return true; +} - inline constexpr bool str_equal(const char* lh, const char* rh) { - while(*lh && *rh && *lh == *rh) { ++lh; ++rh; } - return *lh == *rh; +inline constexpr const char* str_find(const char* str, char c) +{ + while (*str && *str != c) { + ++str; + } + if (*str == c) { + return str; } + return nullptr; +} - /** removes leading & tailing spaces as wide spaces - * @param begin iterator of string - * @param end iterator of string - * @return new end iterator - */ - template - 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 && (src[0] == ' ' || src[0] == '\n')) { continue; } - } - else if(src[-1] == '\n' && (src[0] == ' ' || src[0] == '\n')) { continue;} - else if ( src[0] == ' ' && ( ( src + 1 == end && TAILING_SPACES ) || (( src + 1 != end ) && ( src[1] == ' ' || src[1] == '\n' ) ) ) ) - { +/** removes leading & tailing spaces as wide spaces + * @param begin iterator of string + * @param end iterator of string + * @return new end iterator + */ +template +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 && (src[0] == ' ' || src[0] == '\n')) { continue; } - else if(src[0] == '\n' && dst != begin && dst[-1] == '\n') { continue; } - *dst++ = *src; + } else if (src[-1] == '\n' && (src[0] == ' ' || src[0] == '\n')) { + continue; + } else if (src[0] == ' ' && ((src + 1 == end && TAILING_SPACES) || ((src + 1 != end) && (src[1] == ' ' || src[1] == '\n')))) { + continue; + } else if (src[0] == '\n' && dst != begin && dst[-1] == '\n') { + continue; } - return dst; + *dst++ = *src; } + return dst; } +} // namespace ink::runtime::internal diff --git a/inkcpp/system.cpp b/inkcpp/system.cpp index b3580a09..f303cb02 100644 --- a/inkcpp/system.cpp +++ b/inkcpp/system.cpp @@ -19,12 +19,15 @@ namespace ink return h; // or return h % C; } - 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 + { + 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 \ No newline at end of file +#endif diff --git a/inkcpp/value.h b/inkcpp/value.h index 36207989..7d02e216 100644 --- a/inkcpp/value.h +++ b/inkcpp/value.h @@ -167,11 +167,7 @@ class value : public snapshot_interface /// execute the type exclusive overwrite function and return a new value with /// this new type template - value redefine(const value& oth, T&... env) const - { - inkAssert(type() == oth.type(), "try to redefine value of other type"); - return redefine(oth, {&env...}); - } + value redefine(const value& oth, T&... env) const; private: template diff --git a/inkcpp_c/include/inkcpp.h b/inkcpp_c/include/inkcpp.h index de279754..93917c73 100644 --- a/inkcpp_c/include/inkcpp.h +++ b/inkcpp_c/include/inkcpp.h @@ -29,6 +29,8 @@ typedef struct HInkSTory HInkStory; * 1. `#include ` * 2. `#include ` * + * @section example_c Example + * * To setup an example for option `1.` and `2.` if you use cmake checkout @ref cmake and replace * `target_link_libraries` with `target_link_libraries(main inkcpp_c)` The story and source file * can be used as @ref src_main_c "noted down" @@ -66,11 +68,13 @@ typedef struct HInkSTory HInkStory; */ HInkSnapshot* ink_snapshot_from_file(const char* filename); /** @memberof HInkSnapshot - * @copydoc ink::runtime::snapshot::num_runner() + * @copydoc ink::runtime::snapshot::num_runners() + * @param self */ int ink_snapshot_num_runners(const HInkSnapshot* self); /** @memberof HInkSnapshot * @copydoc ink::runtime::snapshot::write_to_file() + * @param self */ void ink_snapshot_write_to_file(const HInkSnapshot* self, const char* filename); @@ -82,14 +86,17 @@ typedef struct HInkSTory HInkStory; struct HInkChoice; /** @memberof HInkChoice * @copydoc ink::runtime::choice::text + * @param self */ const char* ink_choice_text(const HInkChoice* self); /** @memberof HInkChoice * @copydoc ink::runtime::choice::num_tags + * @param self */ int ink_choice_num_tags(const HInkChoice* self); /** @memberof HInkChoice * @copydoc ink::runtime::choice::get_tag + * @param self */ const char* ink_choice_get_tag(const HInkChoice* self, int index); @@ -137,7 +144,9 @@ typedef struct HInkSTory HInkStory; * @memberof HInkList * Creates an Iterator over all flags contained in a list assziated with a defined list. * @see @ref InkListIter for a usage example + * @param self * @param list_name name of defined list which elements should be filterd + * @param[out] iter constructed iterator * @retval 0 if the list contains no flags and the iterator would be invalid */ int ink_list_flags_from(const HInkList* self, const char* list_name, InkListIter* iter); @@ -153,7 +162,7 @@ typedef struct HInkSTory HInkStory; * The concret type contained is noted in @ref InkValue::type "type", please use this information * to access the corresponding field of the union * @attention a InkValue of type @ref InkValue::Type::ValueTypeNone "ValueTypeNone" dose not - * contain any value! It is use e.g. at @ref ink_globals_get() + * contain any value! It is use e.g. at @ref HInkGlobals::ink_globals_get() "ink_globals_get()" */ struct InkValue { union { @@ -228,23 +237,31 @@ typedef struct HInkSTory HInkStory; int ink_runner_num_tags(const HInkRunner* self); /** @memberof HInkRunner * @copydoc ink::runtime::runner_interface::get_tag() + * @param self */ - const char* ink_runner_tag(const HInkRunner* self, int tag_id); + const char* ink_runner_tag(const HInkRunner* self, int index); /** @memberof HInkRunner - * @copydoc ink::runtiem::runner_interface::num_choices() + * @copydoc ink::runtime::runner_interface::num_choices() + * @param self */ int ink_runner_num_choices(const HInkRunner* self); /** @memberof HInkRunner * @copydoc ink::runtime::runner_interface::get_choice() + * @param self */ - const HInkChoice* ink_runner_get_choice(const HInkRunner* self, int choice_id); + const HInkChoice* ink_runner_get_choice(const HInkRunner* self, int index); /** @memberof HInkRunner * @copydoc ink::runtime::runner_interface::choose() + * @param self */ - void ink_runner_choose(HInkRunner* self, int choice_id); + void ink_runner_choose(HInkRunner* self, int index); + /** @memberof HInkRunner * Binds a external function which is called form the runtime, with no return value. * @see ink_runner_bind() + * @param self + * @param function_name declared in ink script + * @param callback * @param lookaheadSafe if false stop glue lookahead if encounter this function * this prevents double execution of external functions but can lead to * missing glues @@ -256,6 +273,9 @@ typedef struct HInkSTory HInkStory; /** @memberof HInkRunner * Binds a external function which is called from the runtime, with a return vallue. * @see ink_runner_bind_void() + * @param self + * @param function_name name of external function declared inside ink script + * @param callback * @param lookaheadSafe if false stop glue lookahead if encounter this function * this prevents double execution of external functions but can lead to * missing glues @@ -298,12 +318,15 @@ typedef struct HInkSTory HInkStory; /** @memberof HInkGlobals * Gets the value of a global variable * @param variable_name name of variable (same as in ink script) - * @retval @ref InkValue::Type::ValueTypeNone "ValueTypeNone" iff the variable does not exist + * @param self + * @retval InkValue::Type::ValueTypeNone iff the variable does not exist */ InkValue ink_globals_get(const HInkGlobals* self, const char* variable_name); /** @memberof HInkGlobals * Sets the value of a globals variable. * @param variable_name name of variable (same as in ink script) + * @param self + * @param value * @return false if the variable was not set, because the variable with this name does no exists * or the value did not match. */ @@ -323,27 +346,32 @@ typedef struct HInkSTory HInkStory; HInkStory* ink_story_from_file(const char* filename); /** @memberof HInkStory * deletes a story and all assoziated resources + * @param self * @attention this will invalidate all ::HInkRunner and ::HInkGlobals handles assoziated with this * story */ void ink_story_delete(HInkStory* self); /** @memberof HInkStory * @copydoc ink::runtime::story::new_globals + * @param self */ HInkGlobals* ink_story_new_globals(HInkStory* self); /** @memberof HInkStory * @copydoc ink::runtime::story::new_runner + * @param self */ - HInkRunner* ink_story_new_runner(HInkStory* self, HInkGlobals* globals); + HInkRunner* ink_story_new_runner(HInkStory* self, HInkGlobals* store); /** @memberof HInkStory * @copydoc ink::runtime::story::new_globals_from_snapshot + * @param self */ - HInkGlobals* ink_story_new_globals_from_snapshot(HInkStory* self, const HInkSnapshot* snapshot); + HInkGlobals* ink_story_new_globals_from_snapshot(HInkStory* self, const HInkSnapshot* obj); /** @memberof HInkStory * @copydoc ink::runtime::story::new_runner_from_snapshot + * @param self */ HInkRunner* ink_story_new_runner_from_snapshot( - HInkStory* self, const HInkSnapshot* snapshot, HInkGlobals* globals, int runner_id + HInkStory* self, const HInkSnapshot* obj, HInkGlobals* store, int runner_id ); /** diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index 1e7c3a5d..4b66023c 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -9,440 +9,432 @@ #include #ifndef WIN32 -#include +# include #endif namespace ink::compiler::internal { - using std::vector; - using std::map; - using std::string; +using std::vector; +using std::map; +using std::string; - char* strtok_s(char * s, const char * sep, char** context) { +char* strtok_s(char* s, const char* sep, char** context) +{ #if defined(_WIN32) || defined(_WIN64) - return ::strtok_s(s, sep, context); + return ::strtok_s(s, sep, context); #else - if ( - context == nullptr || - sep == nullptr || - (s == nullptr && *context == nullptr) ) - { - errno = EINVAL; - return nullptr; - } - return ::strtok_r(s, sep, context); -#endif + if (context == nullptr || sep == nullptr || (s == nullptr && *context == nullptr)) { + errno = EINVAL; + return nullptr; } + return ::strtok_r(s, sep, context); +#endif +} - // holds information about a container - struct container_data - { - // child containers - vector children; - - // children names (not all containers have names) - map named_children; - - // Indexes of child containers (index is child index of parent) - map indexed_children; - - // Offsets of noop operations - map noop_offsets; - - // parent pointer - container_data* parent = nullptr; - - // Offset in the binary stream - uint32_t offset = 0; - uint32_t end_offset = 0; - - // Index used in CNT? operations - container_t counter_index = ~0; - - ~container_data() - { - // Destroy children - for (auto child : children) - delete child; - - // Clear lists - children.clear(); - //named_children.clear(); - //indexed_children.clear(); - //noop_offsets.clear(); - parent = nullptr; - } - }; +// holds information about a container +struct container_data { + // child containers + vector children; - binary_emitter::binary_emitter() - : _root(nullptr) - { - } + // children names (not all containers have names) + map named_children; + + // Indexes of child containers (index is child index of parent) + map indexed_children; + + // Offsets of noop operations + map noop_offsets; + + // parent pointer + container_data* parent = nullptr; - binary_emitter::~binary_emitter() + // Offset in the binary stream + uint32_t offset = 0; + uint32_t end_offset = 0; + + // Index used in CNT? operations + container_t counter_index = ~0; + + ~container_data() { - if (_root != nullptr) - delete _root; - _root = nullptr; + // Destroy children + for (auto child : children) + delete child; + + // Clear lists + children.clear(); + // named_children.clear(); + // indexed_children.clear(); + // noop_offsets.clear(); + parent = nullptr; } +}; - uint32_t binary_emitter::start_container(int index_in_parent, const std::string& name) - { - // Create new container metadata - auto container = new container_data(); - - // Store root - if (_root == nullptr) - _root = container; - - // Parent it to the current container - container->parent = _current; - - // Set offset to the current position - container->offset = _containers.pos(); - - // Add to parents lists - if (_current != nullptr) - { - _current->children.push_back(container); - _current->indexed_children.insert({ index_in_parent, container }); - - if (!name.empty()) { - _current->named_children.insert({ name, container }); - } - } +binary_emitter::binary_emitter() + : _root(nullptr) +{ +} - // Set this as the current pointer - _current = container; +binary_emitter::~binary_emitter() +{ + if (_root != nullptr) + delete _root; + _root = nullptr; +} - // Return current position - return _containers.pos(); - } +uint32_t binary_emitter::start_container(int index_in_parent, const std::string& name) +{ + // Create new container metadata + auto container = new container_data(); - uint32_t binary_emitter::end_container() - { - // Move up the chain - _current->end_offset = _containers.pos(); - _current = _current->parent; + // Store root + if (_root == nullptr) + _root = container; - // Return offset - return _containers.pos(); - } + // Parent it to the current container + container->parent = _current; - int binary_emitter::function_container_arguments(const std::string& name) - { - if(_root == nullptr) { return -1; } - auto fn = _root->named_children.find(name); - if (fn == _root->named_children.end()) { return -1; } - - size_t offset = fn->second->offset; - byte_t cmd = _containers.get(offset); - int arity = 0; - while(static_cast(cmd) == Command::DEFINE_TEMP) { - offset += 6; // command(1) + flag(1) + variable_name_hash(4) - cmd = _containers.get(offset); - ++arity; + // Set offset to the current position + container->offset = _containers.pos(); + + // Add to parents lists + if (_current != nullptr) { + _current->children.push_back(container); + _current->indexed_children.insert({index_in_parent, container}); + + if (! name.empty()) { + _current->named_children.insert({name, container}); } - return arity; } - void binary_emitter::write_raw(Command command, CommandFlag flag, const char* payload, ink::size_t payload_size) - { - _containers.write(command); - _containers.write(flag); - if(payload_size > 0) - _containers.write((const byte_t*)payload, payload_size); - } + // Set this as the current pointer + _current = container; - void binary_emitter::write_path(Command command, CommandFlag flag, const std::string& path, bool useCountIndex) - { - // Write blank command with 0 payload - write(command, (uint32_t)0, flag); + // Return current position + return _containers.pos(); +} - // Note the position of this later so we can resolve the paths at the end - size_t param_position = _containers.pos() - sizeof(uint32_t); - bool op = flag & CommandFlag::FALLBACK_FUNCTION; - _paths.push_back(std::make_tuple(param_position, path, op, _current, useCountIndex)); - } +uint32_t binary_emitter::end_container() +{ + // Move up the chain + _current->end_offset = _containers.pos(); + _current = _current->parent; - void binary_emitter::write_variable(Command command, CommandFlag flag, const std::string& name) - { - // Use hash as identifier - uint32_t hash = hash_string(name.c_str()); + // Return offset + return _containers.pos(); +} - // Write it out - write(command, hash, flag); +int binary_emitter::function_container_arguments(const std::string& name) +{ + if (_root == nullptr) { + return -1; + } + auto fn = _root->named_children.find(name); + if (fn == _root->named_children.end()) { + return -1; } - void binary_emitter::write_string(Command command, CommandFlag flag, const std::string& string) - { - // Save current position in table - uint32_t pos = _strings.pos(); + size_t offset = fn->second->offset; + byte_t cmd = _containers.get(offset); + int arity = 0; + while (static_cast(cmd) == Command::DEFINE_TEMP) { + offset += 6; // command(1) + flag(1) + variable_name_hash(4) + cmd = _containers.get(offset); + ++arity; + } + return arity; +} - // Write string to table (omit ^ if it begins with one) - if (string.length() > 0 && string[0] == '^') - _strings.write(string.substr(1)); - else - _strings.write(string); +void binary_emitter::write_raw( + Command command, CommandFlag flag, const char* payload, ink::size_t payload_size +) +{ + _containers.write(command); + _containers.write(flag); + if (payload_size > 0) + _containers.write(( const byte_t* ) payload, payload_size); +} - // Written position is what we write out in our command - write(command, pos, flag); - } +void binary_emitter::write_path( + Command command, CommandFlag flag, const std::string& path, bool useCountIndex +) +{ + // Write blank command with 0 payload + write(command, ( uint32_t ) 0, flag); - void binary_emitter::write_list(Command command, CommandFlag flag, const std::vector& entries) { - uint32_t id = _list_count++; - for(const list_flag& entry : entries) { - _lists.write(entry); - } - _lists.write(null_flag); - write(command, id, flag); - } + // Note the position of this later so we can resolve the paths at the end + size_t param_position = _containers.pos() - sizeof(uint32_t); + bool op = flag & CommandFlag::FALLBACK_FUNCTION; + _paths.push_back(std::make_tuple(param_position, path, op, _current, useCountIndex)); +} - void binary_emitter::handle_nop(int index_in_parent) - { - _current->noop_offsets.insert({ index_in_parent, _containers.pos() }); +void binary_emitter::write_variable(Command command, CommandFlag flag, const std::string& name) +{ + // Use hash as identifier + uint32_t hash = hash_string(name.c_str()); + + // Write it out + write(command, hash, flag); +} + +void binary_emitter::write_string(Command command, CommandFlag flag, const std::string& string) +{ + // Save current position in table + uint32_t pos = _strings.pos(); + + // Write string to table (omit ^ if it begins with one) + if (string.length() > 0 && string[0] == '^') + _strings.write(string.substr(1)); + else + _strings.write(string); + + // Written position is what we write out in our command + write(command, pos, flag); +} + +void binary_emitter::write_list( + Command command, CommandFlag flag, const std::vector& entries +) +{ + uint32_t id = _list_count++; + for (const list_flag& entry : entries) { + _lists.write(entry); } + _lists.write(null_flag); + write(command, id, flag); +} - void binary_emitter::output(std::ostream& out) - { - // Write the ink version - // TODO: define this order in header? - using header = ink::internal::header; - header::endian_types same = header::endian_types::same; - out.write((const char*)&same, sizeof(decltype(same))); - out.write((const char*)&_ink_version, sizeof(decltype(_ink_version))); - out.write((const char*)&ink::InkBinVersion, sizeof(decltype(ink::InkBinVersion))); +void binary_emitter::handle_nop(int index_in_parent) +{ + _current->noop_offsets.insert({index_in_parent, _containers.pos()}); +} - // Write the string table - _strings.write_to(out); +void binary_emitter::output(std::ostream& out) +{ + // Write the ink version + // TODO: define this order in header? + using header = ink::internal::header; + header::endian_types same = header::endian_types::same; + out.write(( const char* ) &same, sizeof(decltype(same))); + out.write(( const char* ) &_ink_version, sizeof(decltype(_ink_version))); + out.write(( const char* ) &ink::InkBinVersion, sizeof(decltype(ink::InkBinVersion))); - // Write a separator - out << (char)0; + // Write the string table + _strings.write_to(out); - // Write lists meta data and defined lists - _lists.write_to(out); - // Write a seperator - out.write(reinterpret_cast(&null_flag), sizeof(null_flag)); + // Write a separator + out << ( char ) 0; - // Write out container map - write_container_map(out, _container_map, _max_container_index); + // Write lists meta data and defined lists + _lists.write_to(out); + // Write a seperator + out.write(reinterpret_cast(&null_flag), sizeof(null_flag)); - // Write a separator - uint32_t END_MARKER = ~0; - out.write((const char*)&END_MARKER, sizeof(uint32_t)); + // Write out container map + write_container_map(out, _container_map, _max_container_index); - // Write container hash list - write_container_hash_map(out); - out.write((const char*)&END_MARKER, sizeof(uint32_t)); + // Write a separator + uint32_t END_MARKER = ~0; + out.write(( const char* ) &END_MARKER, sizeof(uint32_t)); - // Write the container data - _containers.write_to(out); + // Write container hash list + write_container_hash_map(out); + out.write(( const char* ) &END_MARKER, sizeof(uint32_t)); - // Flush the file - out.flush(); - } + // Write the container data + _containers.write_to(out); - void binary_emitter::initialize() - { - // Reset binary data stores - _strings.reset(); - _list_count = 0; - _lists.reset(); - _containers.reset(); + // Flush the file + out.flush(); +} - // clear other data - _paths.clear(); +void binary_emitter::initialize() +{ + // Reset binary data stores + _strings.reset(); + _list_count = 0; + _lists.reset(); + _containers.reset(); - if (_root != nullptr) - delete _root; + // clear other data + _paths.clear(); - _current = nullptr; - _root = nullptr; - } + if (_root != nullptr) + delete _root; - void binary_emitter::finalize() - { - // post process path commands - process_paths(); - } + _current = nullptr; + _root = nullptr; +} - void binary_emitter::setContainerIndex(container_t index) - { - _current->counter_index = index; - } +void binary_emitter::finalize() +{ + // post process path commands + process_paths(); +} - uint32_t binary_emitter::fallthrough_divert() - { - // write a fallthrough divert ??? - write(Command::DIVERT, (uint32_t)0, CommandFlag::DIVERT_IS_FALLTHROUGH); +void binary_emitter::setContainerIndex(container_t index) { _current->counter_index = index; } - // Return the location of the divert offset - return _containers.pos() - sizeof(uint32_t); - } +uint32_t binary_emitter::fallthrough_divert() +{ + // write a fallthrough divert ??? + write(Command::DIVERT, ( uint32_t ) 0, CommandFlag::DIVERT_IS_FALLTHROUGH); - void binary_emitter::patch_fallthroughs(uint32_t position) - { - // Patch - _containers.set(position, _containers.pos()); - } + // Return the location of the divert offset + return _containers.pos() - sizeof(uint32_t); +} - void binary_emitter::process_paths() - { - for (auto pair : _paths) - { - // We need to replace the uint32_t at this location with the byte position of the requested container - using std::get; - size_t position = get<0>(pair); - const std::string& path = get<1>(pair); - bool optional = get<2>(pair); - container_data* context = get<3>(pair); - bool useCountIndex = get<4>(pair); - - // Start at the root - container_data* container = _root; - - // Unless it's a relative path - const char* path_cstr = path.c_str(); - if (path_cstr[0] == '.') - { - container = context; - path_cstr += 1; - } +void binary_emitter::patch_fallthroughs(uint32_t position) +{ + // Patch + _containers.set(position, _containers.pos()); +} - bool firstParent = true; - - // We need to parse the path - offset_t noop_offset = ~0; - char* _context = nullptr; - const char* token = ink::compiler::internal::strtok_s( - const_cast(path_cstr), ".", &_context); - while (token != nullptr) - { - // Number - // variable names can start with a number - bool isNumber = true; - for(const char* i = token; *i; ++i) { - if(!isdigit(*i)) { isNumber = false; break; } - } - if(isNumber) - { - // Check if we have a nop registered at that index - int index = atoi(token); - auto nop_iter = container->noop_offsets.find(index); - if (nop_iter != container->noop_offsets.end()) - { - noop_offset = nop_iter->second; - break; - } - else - container = container->indexed_children[index]; - } - // Parent - else if (token[0] == '^') - { - if (!firstParent) - container = container->parent; - } - // Named child - else - { - auto itr = container->named_children.find(token); - container = itr == container->named_children.end() - ? nullptr - : itr->second; +void binary_emitter::process_paths() +{ + for (auto pair : _paths) { + // We need to replace the uint32_t at this location with the byte position of the requested + // container + using std::get; + size_t position = get<0>(pair); + const std::string& path = get<1>(pair); + bool optional = get<2>(pair); + container_data* context = get<3>(pair); + bool useCountIndex = get<4>(pair); + + // Start at the root + container_data* container = _root; + + // Unless it's a relative path + const char* path_cstr = path.c_str(); + if (path_cstr[0] == '.') { + container = context; + path_cstr += 1; + } + + bool firstParent = true; + + // We need to parse the path + offset_t noop_offset = ~0; + char* _context = nullptr; + const char* token + = ink::compiler::internal::strtok_s(const_cast(path_cstr), ".", &_context); + while (token != nullptr) { + // Number + // variable names can start with a number + bool isNumber = true; + for (const char* i = token; *i; ++i) { + if (! isdigit(*i)) { + isNumber = false; + break; } + } + if (isNumber) { + // Check if we have a nop registered at that index + int index = atoi(token); + auto nop_iter = container->noop_offsets.find(index); + if (nop_iter != container->noop_offsets.end()) { + noop_offset = nop_iter->second; + break; + } else + container = container->indexed_children[index]; + } + // Parent + else if (token[0] == '^') { + if (! firstParent) + container = container->parent; + } + // Named child + else { + auto itr = container->named_children.find(token); + container = itr == container->named_children.end() ? nullptr : itr->second; + } - firstParent = false; + firstParent = false; - // Get the next token - token = ink::compiler::internal::strtok_s(nullptr, ".", &_context); - } + // Get the next token + token = ink::compiler::internal::strtok_s(nullptr, ".", &_context); + } - if (noop_offset != ~0) - { - 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!"); - _containers.set(position, container->counter_index); - } - else - { - // Otherwise, write container address - if (container == nullptr) { - _containers.set(position, 0); - inkAssert(optional, "Was not able to resolve a not optional path! '%s'", path.c_str()); - } else { - _containers.set(position, container->offset); - } + if (noop_offset != ~0) { + 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!"); + _containers.set(position, container->counter_index); + } else { + // Otherwise, write container address + if (container == nullptr) { + _containers.set(position, 0); + inkAssert(optional, "Was not able to resolve a not optional path! '%s'", path.c_str()); + } else { + _containers.set(position, container->offset); } } } } +} - void binary_emitter::write_container_map(std::ostream& out, const container_map& map, container_t num) - { - // Write out container count - out.write(reinterpret_cast(&num), sizeof(container_t)); - - // Write out entries - for (const auto& pair : map) - { - out.write((const char*)&pair.first, sizeof(uint32_t)); - out.write((const char*)&pair.second, sizeof(uint32_t)); - } - } +void binary_emitter::write_container_map( + std::ostream& out, const container_map& map, container_t num +) +{ + // Write out container count + out.write(reinterpret_cast(&num), sizeof(container_t)); - void binary_emitter::write_container_hash_map(std::ostream& out) - { - write_container_hash_map(out, "", _root); + // Write out entries + for (const auto& pair : map) { + out.write(( const char* ) &pair.first, sizeof(uint32_t)); + out.write(( const char* ) &pair.second, sizeof(uint32_t)); } +} - void binary_emitter::write_container_hash_map(std::ostream& out, const std::string& name, const container_data* context) - { - for (auto child : context->named_children) - { - // Get the child's name in the hierarchy - std::string child_name = name.empty() ? child.first : (name + "." + child.first); - hash_t name_hash = hash_string(child_name.c_str()); - - // Write out name hash and offset - out.write((const char*)&name_hash, sizeof(hash_t)); - out.write((const char*)&child.second->offset, sizeof(uint32_t)); - - // Recurse - write_container_hash_map(out, child_name, child.second); - } +void binary_emitter::write_container_hash_map(std::ostream& out) +{ + write_container_hash_map(out, "", _root); +} - for (auto child : context->indexed_children) - { - write_container_hash_map(out, name, child.second); - } +void binary_emitter::write_container_hash_map( + std::ostream& out, const std::string& name, const container_data* context +) +{ + for (auto child : context->named_children) { + // Get the child's name in the hierarchy + std::string child_name = name.empty() ? child.first : (name + "." + child.first); + hash_t name_hash = hash_string(child_name.c_str()); + // Write out name hash and offset + out.write(( const char* ) &name_hash, sizeof(hash_t)); + out.write(( const char* ) &child.second->offset, sizeof(uint32_t)); + + // Recurse + write_container_hash_map(out, child_name, child.second); } - void binary_emitter::set_list_meta(const list_data &list_defs) { - if (list_defs.empty()) { - return; - } + for (auto child : context->indexed_children) { + write_container_hash_map(out, name, child.second); + } +} - auto flags = list_defs.get_flags(); - auto list_names = list_defs.get_list_names().begin(); - int list_id = -1; - for(const auto& flag : flags) { - _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()); - ++list_names; - _lists.write('\0'); - } - _lists.write(reinterpret_cast(flag.name.c_str()), flag.name.size() + 1); +void binary_emitter::set_list_meta(const list_data& list_defs) +{ + if (list_defs.empty()) { + return; + } + + auto flags = list_defs.get_flags(); + auto list_names = list_defs.get_list_names().begin(); + int list_id = -1; + for (const auto& flag : flags) { + _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()); + ++list_names; + _lists.write('\0'); } - _lists.write(null_flag); + _lists.write(reinterpret_cast(flag.name.c_str()), flag.name.size() + 1); } + _lists.write(null_flag); } +} // namespace ink::compiler::internal diff --git a/inkcpp_compiler/include/compilation_results.h b/inkcpp_compiler/include/compilation_results.h index a1a4a33e..f4a91652 100644 --- a/inkcpp_compiler/include/compilation_results.h +++ b/inkcpp_compiler/include/compilation_results.h @@ -5,12 +5,12 @@ namespace ink::compiler { - typedef std::vector error_list; +/** list of errors/warnings */ +typedef std::vector error_list; - // stores results from the compilation process - struct compilation_results - { - error_list warnings; - error_list errors; - }; -} \ No newline at end of file +/** stores results from the compilation process */ +struct compilation_results { + error_list warnings; ///< list of all warnings generated + error_list errors; ///< list of all errors generated +}; +} // namespace ink::compiler diff --git a/inkcpp_compiler/include/compiler.h b/inkcpp_compiler/include/compiler.h index e88f4f44..7a1f7d37 100644 --- a/inkcpp_compiler/include/compiler.h +++ b/inkcpp_compiler/include/compiler.h @@ -29,7 +29,7 @@ namespace compiler /** stream -> stream */ void run(std::istream& in, std::ostream& out, compilation_results* results = nullptr); - /* stream -> file **/ + /** stream -> file */ void run(std::istream& in, const char* filenameOut, compilation_results* results = nullptr); } // namespace compiler } diff --git a/inkcpp_py/src/module.cpp b/inkcpp_py/src/module.cpp index fc5a0d94..4d5fbaa3 100644 --- a/inkcpp_py/src/module.cpp +++ b/inkcpp_py/src/module.cpp @@ -265,6 +265,13 @@ PYBIND11_MODULE(inkcpp_py, m) }, py::arg("function_name"), py::arg("function"), py::arg_v("lookaheadSafe", false), "Bind a function with return value" + ) + .def( + "move_to", + [](runner& self, const char* path) -> bool { + return self.move_to(ink::hash_string(path)); + }, + "Moves execution pointer to start of container desrcipet by the path" ); py::class_(m, "Choice") .def("text", &choice::text, "Get choice printable content") diff --git a/inkcpp_py/tests/test_Snapshot.py b/inkcpp_py/tests/test_Snapshot.py index b1a5b048..4ad0c6cd 100644 --- a/inkcpp_py/tests/test_Snapshot.py +++ b/inkcpp_py/tests/test_Snapshot.py @@ -10,10 +10,12 @@ def check_end(runner): class TestSnapshot: def test_snapshot(self, assets, generate): - [story, _, runner] = generate(assets['SimpleStoryFlow']) - + [story, glob, runner] = generate(assets['SimpleStoryFlow']) + runner2 = story.new_runner(glob) runner.getline() assert runner.num_choices() == 3 + snap0 = runner.create_snapshot() + runner.choose(2) snap1 = runner.create_snapshot() @@ -27,14 +29,29 @@ def test_snapshot(self, assets, generate): check_end(runner) - runner = story.new_runner_from_snapshot(snap1) + glob = story.new_globals_from_snapshot(snap0) + runner = story.new_runner_from_snapshot(snap0, glob, 0) + assert runner.num_choices() == 3 + runner.choose(2) + cnt_x = 0 + while runner.can_continue(): + runner.getline() + cnt_x += 1 + assert cnt_x == cnt + + check_end(runner) + + glob = story.new_globals_from_snapshot(snap1) + runner = story.new_runner_from_snapshot(snap1, glob, 0) + cnt_x = 0 while runner.can_continue(): runner.getline() - cnt -= 1 - assert cnt == 0 + cnt_x += 1 + assert cnt_x == cnt check_end(runner) - runner = story.new_runner_from_snapshot(snap2) + glob = story.new_globals_from_snapshot(snap2) + runner = story.new_runner_from_snapshot(snap2, glob, 0) assert not runner.can_continue() check_end(runner) diff --git a/inkcpp_test/CMakeLists.txt b/inkcpp_test/CMakeLists.txt index 922269be..4a36268d 100644 --- a/inkcpp_test/CMakeLists.txt +++ b/inkcpp_test/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable(inkcpp_test catch.hpp Main.cpp ExternalFunctionsExecuteProperly.cpp LookaheadSafe.cpp EmptyStringForDivert.cpp + MoveTo.cpp ) target_link_libraries(inkcpp_test PUBLIC inkcpp inkcpp_compiler inkcpp_shared) diff --git a/inkcpp_test/Lists.cpp b/inkcpp_test/Lists.cpp index 1f9d5a06..0e24e316 100644 --- a/inkcpp_test/Lists.cpp +++ b/inkcpp_test/Lists.cpp @@ -32,6 +32,25 @@ SCENARIO("run a story with lists", "[lists]") } } + WHEN("modify with full flag name") + { + std::string out = thread->getall(); + std::string choice1 = thread->get_choice(0)->text(); + thread->choose(0); + + list l1 = *globals->get("list"); + l1->add("animals.dog"); + l1->remove("colors.red"); + REQUIRE(globals->set("list", l1)); + + std::string out2 = thread->getall(); + + THEN("Output should be changed") + { + REQUIRE(out2 == "list: bird, dog, yellow\ncat, snake\nbird, dog, yellow\n"); + } + } + WHEN("modify") { std::string out = thread->getall(); diff --git a/inkcpp_test/MoveTo.cpp b/inkcpp_test/MoveTo.cpp new file mode 100644 index 00000000..a515993a --- /dev/null +++ b/inkcpp_test/MoveTo.cpp @@ -0,0 +1,95 @@ +#include "catch.hpp" +#include "system.h" + +#include +#include +#include +#include + +using namespace ink::runtime; + +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); + + WHEN("just run main story") + { + THEN("expect normal output") + { + REQUIRE( + main->getall() + == "Lava kadaver a very boring introduction\nYou are head to head to the minister\n" + ); + REQUIRE(main->num_choices() == 1); + REQUIRE(main->get_choice(0)->text() == std::string("Hellow mister menistery")); + main->choose(0); + REQUIRE(main->getall() == "Hellow mister menistery\nYou are head to head to the minister\nIt seems you are out of options, you shold give up\n"); + } + } + + WHEN("skip intorduction") + { + main->move_to(ink::hash_string("Dialog.core")); + THEN("expect output minus introduction") + { + REQUIRE(main->getall() == "You are head to head to the minister\n"); + REQUIRE(main->num_choices() == 1); + REQUIRE(main->get_choice(0)->text() == std::string("Hellow mister menistery")); + main->choose(0); + REQUIRE(main->getall() == "Hellow mister menistery\nYou are head to head to the minister\nIt seems you are out of options, you shold give up\n"); + } + } + + WHEN("second runner is executed") + { + main->move_to(ink::hash_string("Dialog.core")); + side->move_to(ink::hash_string("Transformations.Nothing")); + THEN("normal main output, and side output") + { + REQUIRE(side->getall() == "Hard you could transform if you would like\n"); + + REQUIRE(main->getall() == "You are head to head to the minister\n"); + REQUIRE(main->num_choices() == 1); + REQUIRE(main->get_choice(0)->text() == std::string("Hellow mister menistery")); + main->choose(0); + REQUIRE(main->getall() == "Hellow mister menistery\nYou are head to head to the minister\nIt seems you are out of options, you shold give up\n"); + } + } + WHEN("second runner modifies value") + { + main->move_to(ink::hash_string("Dialog.core")); + side->move_to(ink::hash_string("Transformations.ToTiger")); + THEN("main thread output changes") + { + REQUIRE(side->getall() == "Your body growths, and growth, you feel the muscels building.\nAnd there you are, a tiger.\n"); + REQUIRE(main->getall() == "You are head to head to the minister\n"); + REQUIRE(main->num_choices() == 1); + REQUIRE(main->get_choice(0)->text() == std::string("Roar")); + main->choose(0); + REQUIRE(main->getall() == "Grrrrh, Roarrr\nThe people are quit confused\nYou are head to head to the minister\nIt seems you are out of options, you shold give up\n"); + } + } + WHEN("execute mutliple small runners with sideeffects") + { + main->move_to(ink::hash_string("Dialog.core")); + side->move_to(ink::hash_string("Transformations.ToTiger")); + runner side2 = ink->new_runner(globStore); + side2->move_to(ink::hash_string("Transformations.ToOwl")); + THEN("side threads should influence each other") + { + REQUIRE(side2->getall() == "You Shrink and you get feathers. And, now you are a Owl\n"); + REQUIRE(side->getall() == "Your body growths, and growth, you feel the muscels building.\nAnd there you are, a tiger.\nyou puke a few feathers, where are they coming from?\n"); + REQUIRE(main->getall() == "You are head to head to the minister\n"); + REQUIRE(main->num_choices() == 1); + REQUIRE(main->get_choice(0)->text() == std::string("Roar")); + main->choose(0); + REQUIRE(main->getall() == "Grrrrh, Roarrr\nThe people are quit confused\nYou are head to head to the minister\nIt seems you are out of options, you shold give up\n"); + } + } + } +} diff --git a/inkcpp_test/ink/MoveTo.ink b/inkcpp_test/ink/MoveTo.ink new file mode 100644 index 00000000..bbd1f5d6 --- /dev/null +++ b/inkcpp_test/ink/MoveTo.ink @@ -0,0 +1,36 @@ +-> Dialog + +LIST Form = (Humen), Owl, Tiger + +=== Transformations += Nothing + Hard you could transform if you would like + ->END += ToOwl + You Shrink and you get feathers. And, now you are a Owl + ~ Form = Owl + ->END += ToTiger + Your body growths, and growth, you feel the muscels building. + And there you are, a tiger. + { Form == Owl: + you puke a few feathers, where are they coming from? + } + ~ Form = Tiger + ->END + +=== Dialog += introduction + Lava kadaver a very boring introduction + -> core + += core +You are head to head to the minister +* {Form == Tiger} [Roar] Grrrrh, Roarrr + The people are quit confused +* {Form == Humen} Hellow mister menistery +* -> + It seems you are out of options, you shold give up + -> END +- -> core + diff --git a/setup.py b/setup.py index 1f2bfcb1..45738d85 100644 --- a/setup.py +++ b/setup.py @@ -153,7 +153,7 @@ def build_extension(self, ext: CMakeExtension) -> None: setup( name="inkcpp-py", - version="0.1.2", + version="0.1.3", author="Julian Benda", author_email="julian.benda@ovgu.de", description="Python bindings for InkCPP a Inkle runtime written in C++", diff --git a/shared/public/system.h b/shared/public/system.h index 1ff957b8..86556d8a 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -20,16 +20,19 @@ namespace ink { +/** define basic numeric type + * @todo use a less missleading name + */ typedef unsigned int uint32_t; -// Name hash (used for temporary variables) +/** Name hash (used for temporary variables) */ typedef uint32_t hash_t; -// Invalid hash +/** Invalid hash value */ const hash_t InvalidHash = 0; -// Simple hash for serialization of strings #ifdef INK_ENABLE_UNREAL +/** Simple hash for serialization of strings */ inline hash_t hash_string(const char* string) { return CityHash32(string, FCStringAnsi::Strlen(string)); @@ -38,22 +41,22 @@ inline hash_t hash_string(const char* string) hash_t hash_string(const char* string); #endif -// Byte type +/** Byte type */ typedef unsigned char byte_t; -// Used to identify an offset in a data table (like a string in the string table) +/** Used to identify an offset in a data table (like a string in the string table) */ typedef uint32_t offset_t; -// Instruction pointer used for addressing within the story instructions +/** Instruction pointer used for addressing within the story instructions */ typedef const unsigned char* ip_t; -// Used for the size of arrays +/** Used for the size of arrays */ typedef unsigned int size_t; -// Used as the unique identifier for an ink container +/** Used as the unique identifier for an ink container */ typedef uint32_t container_t; -// Used to uniquely identify threads +/** Used to uniquely identify threads */ typedef uint32_t thread_t; /** Used to unique identify a list flag */ @@ -66,49 +69,55 @@ struct list_flag { bool operator!=(const list_flag& o) const { return ! (*this == o); } }; +/** value of an unset list_flag */ constexpr list_flag null_flag{-1, -1}; +/** value representing an empty list */ constexpr list_flag empty_flag{-1, 0}; -// Checks if a string is only whitespace -static bool is_whitespace(const char* string, bool includeNewline = true) +namespace internal { - // Iterate string - while (true) { - switch (*(string++)) { - case 0: return true; + /** Checks if a string is only whitespace*/ + static bool is_whitespace(const char* string, bool includeNewline = true) + { + // Iterate string + while (true) { + switch (*(string++)) { + case 0: return true; + case '\n': + if (! includeNewline) + return false; + case '\t': + 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 + */ + inline bool is_part_of_word(char character) { return isalpha(character) || isdigit(character); } + + inline constexpr bool is_whitespace(char character, bool includeNewline = true) + { + switch (character) { case '\n': if (! includeNewline) return false; - case '\t': - case ' ': continue; + case '\t': [[fallthrough]]; + case ' ': return true; 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 -inline bool is_part_of_word(char character) { return isalpha(character) || isdigit(character); } - -inline constexpr bool is_whitespace(char character, bool includeNewline = true) -{ - switch (character) { - case '\n': - if (! includeNewline) - return false; - case '\t': [[fallthrough]]; - case ' ': return true; - default: return false; - } -} -// Zero memory #ifndef INK_ENABLE_UNREAL -void zero_memory(void* buffer, size_t length); + /** populate memory with Zero */ + void zero_memory(void* buffer, size_t length); #endif - +} // namespace internal #ifdef INK_ENABLE_STL +/** exception type thrown if something goes wrong */ using ink_exception = std::runtime_error; #else // Non-STL exception class @@ -154,6 +163,10 @@ template exit(EXIT_FAILURE); } #else +/** platform indipendent assert(false) with message. + * @param msg formatting string + * @param args arguments to format string + */ template inline void ink_fail(const char* msg, Args... args) { @@ -202,8 +215,12 @@ namespace runtime::internal #ifdef INK_ENABLE_STL +/** custom optional implementation for usage if STL is disabled + * @tparam T type contaied in optional + */ template using optional = std::optional; +/** an empty #optional */ constexpr std::nullopt_t nullopt = std::nullopt; #else struct nullopt_t { @@ -261,6 +278,13 @@ class optional return _has_value ? _value : static_cast(u); } + template + T& emplace(Args... args) + { + _value.~T(); + return *(new (&_value) T(args...)); + } + private: void test_value() const { @@ -282,7 +306,7 @@ class optional # define inkAssert(condition, text, ...) checkf(condition, TEXT(text), ##__VA_ARGS__) # define inkFail ink::ink_fail #else -# define inkZeroMemory ink::zero_memory +# define inkZeroMemory ink::internal::zero_memory # define inkAssert ink::ink_assert # define inkFail(...) ink::ink_assert(false, __VA_ARGS__) #endif diff --git a/shared/public/version.h b/shared/public/version.h index 9278640f..2298a7d0 100644 --- a/shared/public/version.h +++ b/shared/public/version.h @@ -3,6 +3,6 @@ #include "system.h" namespace ink { - constexpr uint32_t InkBinVersion = 1; - constexpr uint32_t InkVersion = 21; +constexpr uint32_t InkBinVersion = 1; ///< Supportet version of ink.bin files +constexpr uint32_t InkVersion = 21; ///< Supported version of ink.json files }; diff --git a/unreal/CMakeLists.txt b/unreal/CMakeLists.txt index 8e413f95..133f7d39 100644 --- a/unreal/CMakeLists.txt +++ b/unreal/CMakeLists.txt @@ -18,6 +18,9 @@ URL https://github.com/inkle/ink/releases/download/v1.1.1/inklecate_linux.zip URL_HASH SHA256=26f4e188e02536d6e99e73e71d9b13e2c2144187f1368a87e82fd5066176cff8 SOURCE_DIR "inkcpp/Resources/inklecate/linux" ) + +set(FETCHCONTENT_QUIET OFF) + FetchContent_MakeAvailable(inklecate_windows) if(NOT inklecate_windows_SOURCE_DIR) message(WARNING "failed to download inklecate for windows, " @@ -50,3 +53,4 @@ configure_file( # Copy files into destination directory install(DIRECTORY "inkcpp/" DESTINATION "inkcpp" COMPONENT unreal EXCLUDE_FROM_ALL PATTERN "*.in" EXCLUDE) +install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/inkcpp" DESTINATION "." COMPONENT unreal EXCLUDE_FROM_ALL) diff --git a/unreal/UE_example.ink b/unreal/UE_example.ink new file mode 100644 index 00000000..77cc9e9c --- /dev/null +++ b/unreal/UE_example.ink @@ -0,0 +1,92 @@ +LIST Potions = TalkWithAnimals, Invisibility +LIST Clues = Skull, Feather +VAR Inventory = (Skull, TalkWithAnimals) +VAR Health = 100 +VAR can_talk_with_animals = false +VAR is_insible = false + +-> Mansion.Car + +EXTERNAL transition(to) +=== function transition(to) === +~ return 0 + +=== function walking(to) === +You startk walking to {to}. +~ transition(to) +~ return + +=== Wait +-> DONE + +=== TClues += TSkull + A human skull, why do I have this again? + -> END += TFeather + A Large Feather found insede the dining room, I wounder from which bird it is. + -> END +=== TPotions += TTalkWithAnimals + A potion which allows the consumer to talk with a variaty of animals. Just make sure + your serroundings do not think you are crazy. + {can_talk_with_animals: Drinking more of it will not increase the effect.} + + [Put it Back] + You put the potion back into your pouch. + -> END + + {not can_talk_with_animals} [Drink] + A take a sip. The potion tastes like Hores, it is afull. + ~ can_talk_with_animals = true + -> END += TInvisibility + A potion which allows the consumer to stay unseen for the human eye. Not tested + wtih other speccies. + {is_insible: Drinking more of it will not increase the effect.} + + [Put it Back] + You put the potion back into your pouch. + -> END + + {not is_insible}[Drink] + You put a small drop on your thoungh. It feels like liking a battery, such a nice feeling + ~ is_insible = true + -> END + +=== Faint +You collapse, the next thing you can remember is how you are given to the ambulance. +No further action for today ... +-> DONE + +=== Mansion += 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. + -> Car_cycle ++ [go to the mension] + ~ walking("Mansion.Entrance") + -> Entrance + += Entrance +{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] + While watching around you, <> + {Inventory hasnt Invisibility: + see a small bottle in the Pot next to the door. + -else: + see nothing of intrest + } + -> 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. + -> Entrance_cycle ++ [Knock] + "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 diff --git a/unreal/blueprint_filter.js b/unreal/blueprint_filter.js new file mode 100644 index 00000000..39b17c1f --- /dev/null +++ b/unreal/blueprint_filter.js @@ -0,0 +1,377 @@ +let window = { + navigator: { + platform: "win", + userAgent: "", + }, + addEventListener: function(a,b){}, +} +setTimeout = function(fn,b) {fn()} + + +;(function() { +"use strict"; + +void 0===window.blueprintUE&&(window.blueprintUE={}),void 0===window.blueprintUE.render&&(window.blueprintUE.render={}); +function rgba(t,e,n,r){function sanitizeRGB(t){t=parseInt(t,10);return isNaN(t)?255:Math.min(Math.max(t,0),255)}return{red:sanitizeRGB(t),green:sanitizeRGB(e),blue:sanitizeRGB(n),alpha:(t=r,t=parseFloat(t),isNaN(t)?1:Math.min(Math.max(t,0),1)),log:function(){},generateBlueprintText:function(){return"(R="+this.decimalToFloat(this.red)+",G="+this.decimalToFloat(this.green)+",B="+this.decimalToFloat(this.blue)+",A="+this.fixAlphaForBlueprintText(this.alpha)+")"},decodeBlueprintText:function(t){t=String(t).split(",");return rgba(this.floatToDecimal(t[0].substr(3)),this.floatToDecimal(t[1].substr(2)),this.floatToDecimal(t[2].substr(2)),parseFloat(t[3].substr(2,t[3].length-3)))},setValuesFromBlueprintText:function(t){t=this.decodeBlueprintText(t);this.red=t.red,this.green=t.green,this.blue=t.blue,this.alpha=t.alpha},setValuesFromProps:function(t){for(var e,n=0,r=t.length;n"),n.replace(/\\r\\n/g,"
")+e)}function n2br(t){var e="",n="";return null==t?t:("\\n"===(n=String(t)).substring(n.length-2)&&(e="
"),n.replace(/\\n/g,"
")+e)}function transformInternalName(t){var e="";return null==t?t:(e=0===(e=(e=(e=String(t)).replace(/_/g," ")).replace(/([A-Z])([A-Z])([a-z])|([a-z])([A-Z])/g,"$1$4 $2$3$5")).indexOf("K2")?e.replace(/ /g,"").substring(2):e).trim()}function getSvgPath(t,e,n,r){return t.left<=e.left?t.top<=e.top?"M 0 0 C"+(n/2>>0)+",0 "+(n/2>>0)+","+r+" "+n+","+r:"M 0 "+r+" C"+(n/2>>0)+","+r+" "+(n/2>>0)+",0 "+n+",0":t.top<=e.top?"M "+n+" 0 C"+(n+n)+",0 "+-n+","+r+" 0,"+r:"M "+n+" "+r+" C"+(n+n)+","+r+" "+-n+",0 0,0"}function correctSvgPathForKnot(t,e,n,r,a,i,u){var o=e.parentElement.parentElement.parentElement.parentElement.parentElement.classList.contains("knot"),l=n.parentElement.parentElement.parentElement.parentElement.parentElement.classList.contains("knot");if(!o&&l){if(!e.parentElement.parentElement.parentElement.classList.contains("left-col")&&r.left>a.left)return r.top>a.top?"M "+i+" "+u+" C"+(i+150)+","+u+" "+i+",0 0,0":"M "+i+" 0 C"+(i+150)+",0 "+i+","+u+" 0,"+u}else if(o&&!l&&n.parentElement.parentElement.parentElement.classList.contains("left-col")&&r.left>a.left)return a.top>r.top?"M 0 "+u+" C-75,"+u+" -75,0 "+i+",0":"M 0 0 C-75,0 -75,"+u+" "+i+","+u;return t}function ucfirst(t){t=t.trim();return t.charAt(0).toUpperCase()+t.substring(1)}function floatFixed(t,e){t=t.toFixed(e);return parseFloat(t)}function getCoefficentOffsetForScale(t,e){return{1.56:{in:.6756661991584852,out:0},1.48:{in:.7142327650111193,out:.6410658307210031},1.4:{in:.7574685534591195,out:.6754098360655738},1.32:{in:.806276150627615,out:.7144970414201184},1.24:{in:.8622540250447227,out:.7571157495256167},1.16:{in:.9260326609029779,out:.8064024390243902},1.08:{in:1,out:.8620689655172413},1:{in:1.086873508353222,out:.92616226071103},.92:{in:1.1905717151454362,out:1},.84:{in:1.3155934007450771,out:1.0877659574468086},.76:{in:1.4703557312252964,out:1.191860465116279},.68:{in:1.666871921182266,out:1.3173076923076923},.6:{in:1.9227967953386744,out:1.4714285714285715},.52:{in:2.272340425531915,out:1.6653225806451613},.44:{in:2.7781954887218046,out:1.9209302325581394},.36:{in:3.5708661417322833,out:2.273224043715847},.28:{in:5,out:2.78},.2:{in:8.33266129032258,out:3.572649572649573},.12:{in:25,out:5},.04:{in:0,out:8.333333333333334}}[t][e]}function getCoefficentOffsetForScaleWhenZoomIn(t){return getCoefficentOffsetForScale(t,"in")}function getCoefficentOffsetForScaleWhenZoomOut(t){return getCoefficentOffsetForScale(t,"out")}function getKismetMathText(t){var e=t.indexOf("_");return-1!==e?"GreaterEqual"===(e=t.substring(0,e))?">=":"Greater"===e?">":"Subtract"===e?"-":"LessEqual"===e?"<=":"Less"===e?"<":"EqualEqual"===e?"==":"NotEqual"===e?"!=":"Not"===e?"NOT":"Percent"===e?"%":"Multiply"===e?"×":"Divide"===e?"÷":"Add"===e&&"+":-1!==t.indexOf("NAND")?"NAND":-1!==t.indexOf("AND")?"AND":-1!==t.indexOf("XOR")?"XOR":-1!==t.indexOf("NOR")?"NOR":-1!==t.indexOf("OR")?"OR":-1!==t.indexOf("Max")?"MAX":-1!==t.indexOf("Min")?"MIN":-1!==t.indexOf("Abs")?"ABS":-1!==t.indexOf("DegSin")?"SINd":-1!==t.indexOf("DegAcos")?"ACOSd":-1!==t.indexOf("Sqrt")&&"SQRT"}function getArrayText(t){return t.replace("Array_","")}function formatAxisValueForDisplay(t){var e="";return void 0===t||"0"===(e=t.trim())||"0.000000"===e?"0.0":("."===(e=e.replace(/0+$/,"")).charAt(e.length-1)&&(e+="0"),e)}function getLastPartAsset(t){var e="";return null==t?t:"'"===(e=(t=(t=t.split("/"))[t.length-1].split("."))[t.length-1]).charAt(e.length-1)?e.substring(0,e.length-1):e}function float2hex(t){return("0"+(255*clamp(t,0,1)>>0).toString(16)).slice(-2)}function float2dec(t){return 255*clamp(t,0,1)>>0}function createElem(t,e,n){for(var r=document.createElement(t),a=0,i=e.length;a=o[0][0]&&e[d][0]<=o[0][1]&&e[d][1]>=o[1][0]&&e[d][1]<=o[1][1]&&s.push(e[d]);if(0===s.length)return null;if(1===s.length)return s[0];for(a=s[0],l=1,t=s.length;ln||l[0]===n&&l[1]>i)&&(l=[n,i]),d=Math.min(n,d),r=Math.min(i,r);return d===l[0]&&r===l[1]?{x:-(d+s),y:-(r+o)}:(null===(h=fixPositionByCheckingNodesInViewport(a,[d,r],[(t=this.data.htmlElement.querySelector(".frame").getBoundingClientRect()).width,t.height]))?h=[-(l[0]+s),-(l[1]+o)]:(h[0]=-(h[0]+s),h[1]=-(h[1]+o)),{x:h[0],y:h[1]})}function extractBreadcrumb(e,n){for(var i=0,t=this.blueprint.nodesParsed.length;i>0)<=.5?t*STEP:(1+t)*STEP}function getPositions(t){var t=t.getBoundingClientRect(),e=t.top,s=t.left;return[[s,s+t.width],[e,e+t.height]]}function comparePositions(t,e){var s=0,n=0,s=t[0]n[0]||s[0]===n[0]}function isOverlapping(t,e){t=getPositions(t),e=getPositions(e);return comparePositions(t[0],e[0])&&comparePositions(t[1],e[1])}function parents(t,e,s){var n,i=t.parentNode,o=0,a=0;if(null===i||void 0===i.classList)return null;for(n=e.length;a>0,1>0)),-1!==o&&-1!==(s=t.indexOf(")",o+6))&&(e.scale=parseFloat(t.substring(o+6,s)),isNaN(e.scale)&&(e.scale=1)),e}function Interactor(t,e,s){this.dom={breadcrumb:null,btns:null,canvas:null,debugInfos:null,frame:null,layer:null,multiSelect:null,nodeMoving:null,nodesMoving:[],overlay:null,panel:null,panelBtns:[],reference:null,root:t,zoom:null},this.bus=s,this.events={pointerDown:null,pointerMove:null,pointerUp:null},this.states={current:null,previous:null,mode:MODE_READ,platform:getPlatform(),isTouch:!1,isLongpress:!1,isLeftClick:!1,isRightClick:!1,isPanelOpen:!1,inPlayground:!1,isMovingNode:!1,inFullscreen:!1,isTreatingPaste:!1,enableDebug:!1,enableMultiUsers:!1,enableMaterialViz:!1,enableAnnotations:!1,timeoutLongpress:null,timeoutOverlay:null,nodeIDsAlreadySelected:[],multiSelectHasCtrlKey:!1,multiSelectHasShiftKey:!1,wheelAccumulator:0},this.eventsBinding={breadcrumb:this.eventBreadcrumb.bind(this),contextMenu:this.eventContextMenu.bind(this),copyNodes:this.eventCopyNodes.bind(this),exitFullscreenHandlerForResizing:this.eventExitFullscreenHandlerForResizing.bind(this),focusPlayground:this.eventFocusPlayground.bind(this),headerButtons:this.eventHeaderButtons.bind(this),hideOverlayForZoom:this.eventHideOverlayForZoom.bind(this),panelButtons:this.eventPanelButtons.bind(this),pasteNodes:this.eventPasteNodes.bind(this),pointerDoubleClick:this.eventPointerDoubleClick.bind(this),pointerDown:this.eventPointerDown.bind(this),pointerLongpress:this.eventPointerLongpress.bind(this),pointerMove:this.eventPointerMove.bind(this),pointerPinch:this.eventPointerPinch.bind(this),pointerUp:this.eventPointerUp.bind(this),pointerWheel:this.eventPointerWheel.bind(this),doMoveCanvas:this.eventDoMoveCanvas.bind(this)},this.pinchDistance=null,this.canvas={x:0,y:0},this.resetPosition={x:0,y:0},this.scale=1,this.startPinLink=null,this.currentZoom=0,this.lastUpdateCalled=null,this.frameSize=e.height||INIT_FRAME_SIZE}Interactor.prototype.startAllBinding=function(){var t,e=0;for(this.dom.frame=this.dom.root.querySelector(".frame"),this.dom.layer=this.dom.root.querySelector(".layer"),this.dom.reference=this.dom.root.querySelector(".reference"),this.dom.canvas=this.dom.root.querySelector(".canvas"),this.dom.btns=this.dom.root.querySelector(".frame-header__buttons"),this.dom.panel=this.dom.root.querySelector(".panel"),this.dom.panelBtns=this.dom.root.querySelectorAll(".panel input[data-feature-panel-checkbox]"),this.dom.zoom=this.dom.root.querySelector(".frame-header__current-zoom"),this.dom.overlay=this.dom.root.querySelector(".overlay"),this.dom.breadcrumb=this.dom.root.querySelector(".frame-header__breadcrumb"),window.addEventListener("mousedown",this.eventsBinding.focusPlayground),window.addEventListener("touchstart",this.eventsBinding.focusPlayground),this.dom.layer.addEventListener("mousedown",this.eventsBinding.pointerDown),this.dom.layer.addEventListener("touchstart",this.eventsBinding.pointerDown,{passive:!1}),window.addEventListener("mousemove",this.eventsBinding.pointerMove),window.addEventListener("touchmove",this.eventsBinding.pointerMove,{passive:!1}),window.addEventListener("mouseup",this.eventsBinding.pointerUp),window.addEventListener("touchend",this.eventsBinding.pointerUp,{passive:!1}),this.dom.layer.addEventListener("wheel",this.eventsBinding.pointerWheel),this.dom.layer.addEventListener("dblclick",this.eventsBinding.pointerDoubleClick),document.addEventListener("copy",this.eventsBinding.copyNodes),document.addEventListener("paste",this.eventsBinding.pasteNodes),this.dom.btns.addEventListener("click",this.eventsBinding.headerButtons),document.addEventListener("webkitfullscreenchange",this.eventsBinding.exitFullscreenHandlerForResizing),document.addEventListener("fullscreenchange",this.eventsBinding.exitFullscreenHandlerForResizing),this.dom.breadcrumb.addEventListener("click",this.eventsBinding.breadcrumb),t=this.dom.panelBtns.length;e>0,t=t.clientY-this.diffY>>0;this.canvas.x===e&&this.canvas.y===t||(this.dom.canvas.style.transform=getStyleTransformCSS(e,t,this.scale),this.dom.reference.style.transform=getStyleTransformCSS(e,t,this.scale),this.canvas.x=e,this.canvas.y=t)},Interactor.prototype.startMovingNode=function(t,e){var s=t.clientX/this.scale,t=t.clientY/this.scale,n=e.getBoundingClientRect().width/this.scale>>0,i=e.getBoundingClientRect().height/this.scale>>0,o=extractTranslateScale(e.style.transform);this.eWi=n,this.eHe=i,this.diffX=o.x+n-s,this.diffY=o.y+i-t,this.dom.nodeMoving=e,window.getSelection().removeAllRanges()},Interactor.prototype.moveNode=function(t){var e,s,n,i,o=0,a=0,r=t.clientX/this.scale,t=t.clientY/this.scale,r=r+this.diffX-this.eWi+2*this.scale,t=t+this.diffY-this.eHe+2*this.scale,l=[],h=null,r=bestMultiple(r),t=bestMultiple(t);if(this.dom.nodeMoving.classList.contains("selected"))0===this.dom.nodesMoving.length&&(this.dom.nodesMoving=this.dom.canvas.querySelectorAll(".node.selected"));else{for(o=0,a=(l=this.dom.canvas.querySelectorAll(".node.selected")).length;o=LIMIT_ZOOM_IN||0===this.currentZoom&&!1===t.ctrlKey&&!1===e||(this.currentZoom+=1,this.scale=floatFixed(this.scale+ZOOM_STEP,2),e=getCoefficentOffsetForScaleWhenZoomIn(this.scale),n=this.dom.frame.getBoundingClientRect(),s=(t.pageX-(n.left+window.scrollX)-this.canvas.x)*e>>0,t=(t.pageY-(n.top+window.scrollY)-this.canvas.y)*e>>0,n=this.scale-i,this.canvas.x=this.canvas.x-s*n>>0,this.canvas.y=this.canvas.y-t*n>>0,this.dom.canvas.style.transform=getStyleTransformCSS(this.canvas.x,this.canvas.y,this.scale),this.dom.reference.style.transform=getStyleTransformCSS(this.canvas.x,this.canvas.y,this.scale),this.updateZoomText())},Interactor.prototype.zoomOut=function(t){var e,s,n,i=this.scale;this.currentZoom<=LIMIT_ZOOM_OUT||(0>0,t=(t.pageY-(n.top+window.scrollY)-this.canvas.y)*e>>0,n=this.scale-i,this.canvas.x=this.canvas.x-s*n>>0,this.canvas.y=this.canvas.y-t*n>>0,this.dom.canvas.style.transform=getStyleTransformCSS(this.canvas.x,this.canvas.y,this.scale),this.dom.reference.style.transform=getStyleTransformCSS(this.canvas.x,this.canvas.y,this.scale),this.updateZoomText())},Interactor.prototype.updateZoomText=function(){var t=this.currentZoom;0===this.currentZoom?t="1:1":0>0)+"px",s=(r=this.dom.canvas.querySelectorAll(".node")).length;l/g)||[]).length<1)s.text=n,s.attrs=[{name:"style",value:"top:-33px"}];else{for(t=(e=n.split(/
/g)).length;r";if(-1!==(t=this.pins[m].getPropFromPinType("PinSubCategoryObject").value.lastIndexOf(".")))return"Break "+(n=this.pins[m].getPropFromPinType("PinSubCategoryObject").value.substring(t+1).replace("'",""))}return"Create Widget"},UNode.prototype.findHeaderSubname=function(){return""},UNode.prototype.generateHTMLBody=function(){var e=[{tag:"div",classes:["body"],childs:[{tag:"div",classes:["left-col"],childs:this.generateHTMLPinsInput()},{tag:"div",classes:["right-col"],childs:this.generateHTMLPinsOutput()}]}];return this.hasAdvancedPinDisplay()&&(this.isAdvancedPinDisplayExpanded()?e.push({tag:"div",classes:["less"],childs:[{tag:"span"}]}):e.push({tag:"div",classes:["more"],childs:[{tag:"span"}]})),e},UNode.prototype.generateHTMLPinDelegate=function(){for(var e=[],t=0,n=this.pins.length;t"))e=[{tag:"span",classes:["fake-input",i],attrs:[{name:"contenteditable",value:"true"}],text:u}];else{for(o=[],h=(c=u.split("
")).length;p"))e=[{tag:"span",classes:["fake-input",i],attrs:[{name:"contenteditable",value:"true"}],text:u}];else{for(o=[],h=(c=u.split("
")).length;p>0,t.height/2>>0]},Environment.prototype.computeSVGLink=function(t,e,o){var a=null,n=null,s=0,i=0,r=t.getBoundingClientRect(),l=e.getBoundingClientRect(),c=this.dom.canvas.getBoundingClientRect(),r={top:r.top+window.scrollY,left:r.left+window.scrollX},l={top:l.top+window.scrollY,left:l.left+window.scrollX},d=this.dom.canvas.offsetTop,u=this.dom.canvas.offsetLeft,p=c.top+window.scrollY,c=c.left+window.scrollX;return r.top-=d+p,r.left-=u+c,l.top-=d+p,l.left-=u+c,d=correctionWidth(t),p=correctionHeight(t),u=Math.min(r.left,l.left)+d,c=Math.min(r.top,l.top)+p,0===(s=Math.max(r.left,l.left)-Math.min(r.left,l.left)>>0)&&(s=2),0===(i=Math.max(r.top,l.top)-Math.min(r.top,l.top)>>0)&&(i=2),d=getSvgPath(r,l,s,i),d=correctSvgPathForKnot(d,t,e,r,l,s,i),(a=document.createElementNS("http://www.w3.org/2000/svg","svg")).classList.add("link"),a.setAttribute("data-id",o),a.setAttribute("style","transform: translate("+u+"px, "+c+"px)"),a.setAttribute("width",s),a.setAttribute("height",i),a.setAttribute("pointer-events","none"),a.setAttribute("position","absolute"),(n=document.createElementNS("http://www.w3.org/2000/svg","path")).setAttribute("d",d),n.setAttribute("transform",""),n.setAttribute("pointer-events","visibleStroke"),n.setAttribute("fill","none"),n.setAttribute("stroke","#"+getLinkColor(t)),n.setAttribute("style",""),n.setAttribute("stroke-width","2"),a.appendChild(n),a},Environment.prototype.drawLinks=function(t){for(var e,o,a=document.createDocumentFragment(),n=this.dom.canvas.querySelectorAll(".pin .clink.filled"),s={},i=0,r=n.length,l=0,c=t.length;i>0)&&(i=2),0===(r=Math.max(l.top,e.top)-Math.min(l.top,e.top)>>0)&&(r=2),(n=document.createElementNS("http://www.w3.org/2000/svg","svg")).classList.add("link","moving"),n.setAttribute("data-id",o),n.setAttribute("style","transform: translate("+a+"px, "+d+"px)"),n.setAttribute("width",i),n.setAttribute("height",r),n.setAttribute("pointer-events","none"),n.setAttribute("position","absolute"),(s=document.createElementNS("http://www.w3.org/2000/svg","path")).setAttribute("d",getSvgPath(l,e,i,r)),s.setAttribute("transform",""),s.setAttribute("pointer-events","visibleStroke"),s.setAttribute("fill","none"),s.setAttribute("stroke","#"+getLinkColor(t)),s.setAttribute("style",""),s.setAttribute("stroke-width","2"),n.appendChild(s),n},Environment.prototype.drawNewLink=function(t,e,o){t=t.querySelector(".clink"),t=this.computeSVGLinkWithMouse(t,e,"moving",o);this.dom.canvas.appendChild(t)},Environment.prototype.moveLink=function(t,e,o){this.dom.canvas.querySelector("svg.moving").remove(),t=t.querySelector(".clink"),t=this.computeSVGLinkWithMouse(t,e,"moving",o),this.dom.canvas.appendChild(t)},Environment.prototype.newLink=function(t,e){this.dom.canvas.appendChild(this.computeSVGLink(t,e,""))},Environment.prototype.updateLoading=function(t){this.dom.overlay.textContent=t},Environment.prototype.removeLoading=function(){this.dom.overlay.style.display="none"},Environment.prototype.showLoading=function(){this.dom.overlay.style.display=""},Environment.prototype.addDataTimerInPlayground=function(t){this.dom.frame.setAttribute("data-parse_blueprint",(t.parseBlueprint>>0).toString()),this.dom.frame.setAttribute("data-create_playground",(t.createPlayground>>0).toString()),this.dom.frame.setAttribute("data-display_nodes",(t.displayNodesInViewport>>0).toString()),this.dom.frame.setAttribute("data-draw_links",(t.drawLinks>>0).toString()),this.dom.frame.setAttribute("data-interactions",(t.startAllBinding>>0).toString()),this.dom.frame.setAttribute("data-sum",(t.parseBlueprint+t.createPlayground+t.displayNodesInViewport+t.drawLinks+t.startAllBinding>>0).toString())},Environment.prototype.stop=function(){var t=null;for(this.dom.canvas=null,this.dom.frame=null,this.dom.overlay=null,t=this.dom.root.lastElementChild;t;)this.dom.root.removeChild(t),t=this.dom.root.lastElementChild;this.dom.root=null}; +function Main(t,e,n){var r={},i=null;return"string"!=typeof t?new TypeError("Argument 'text', expect string, get "+typeof t):e instanceof HTMLElement?(r={htmlElement:e,options:n||{},text:t},this.start=function(t){i&&(i.stop(),i=null),(i=new Editor(r.text,r.htmlElement,r.options,new Bus)).start(t)},this.updateBlueprintText=function(t,e){if("string"!=typeof t)return new TypeError("Argument 'newBlueprintText', expect string, get "+typeof t);r.text=t,i&&i.updateBlueprintText(t,e)},this.stop=function(){i&&(i.stop(),i=null)},this.getBlueprintData=function(){return i.getBlueprintData()},void(this.moveTo=function(t,e,n){i.moveTo(t,e,n)})):new TypeError("Argument 'htmlElement', expect HTMLElement, get "+typeof e)}Object.freeze(Main.prototype),Object.freeze(Main),window.blueprintUE.render.Main=Main; +}()); + + +function Element(tag) { + this.tag = tag; + this.classList = new Set(); + this.attributes = {}; + this.childs = []; + this.style = {}; +} +Element.prototype.toString = function(indent) { + if (!indent) { indent = 0; } + let indent_str = indent === -1 ? "" : Array(indent).fill("").join(" ") + if (this.tag === "br") { + return `${indent_str}
`; + } + let class_str = ""; + for (const elem of this.classList) { + class_str += elem + " "; + } + if (this.attributes.style) { + let style = JSON.parse(`{"${this.attributes.style.replaceAll(';','","').replaceAll(':','":"')}"}`); + delete this.attributes.style; + this.style = {...this.style, ...style}; + } + let attribute_str = ""; + for (const key of Object.keys(this.attributes)) { + attribute_str += `${key}="${this.attributes[key]}" ` + } + if (this.style.position === "absolute") { + this.style.position = "relative"; + } + let style_str = `"${JSON.stringify(this.style).replaceAll(",", ";").replaceAll('"','').slice(1,-1)}"`; + + let res = `${indent_str}<${this.tag} class="${class_str}" ${attribute_str} style=${style_str}>${indent >= 0 ? '\n' : ''}`; + if (this.textContent) { + res += this.textContent; + } else { + for (const child of this.childs) { + res += child.toString(indent >= 0 ? indent + 1 : indent); + } + } + return res + `${indent_str}${indent >= 0 ? '\n' : ''}`; +} +Element.prototype.setAttribute = function(key, value) { + this.attributes[key] = value; +} +Element.prototype.getAttribute = function(key) { + return this.attribute[key]; +} +Element.prototype.appendChild = function(obj) { + this.childs.push(obj); +} +Element.prototype.addEventListener = function(a,b){} +function match(obj, part) { + let res = 1; + if (part[0] == '.') { + for (const subs of part.split('.').slice(1)) { + if (!obj.classList.has(subs)) { + res = 0; + break; + } + } + } else if (part.startsWith('input[')) { + let sub = part.slice(6,-1); + if (sub.includes('=') || sub.includes(',') || sub.includes(' ') || sub.includes('.')) { + throw new Error("Doof"); + } + if (obj.tag != "input" || obj.attributes[sub]) { + res = 0; + } + } else { + throw new Error("Doof2"); + } + return res; +} +Element.prototype.querySelectorAll = function(query) { + let res = []; + let parts = query.split(' '); + if (match(this, parts[0]) === 1) { + parts = parts.slice(1); + } + if (parts.length === 0) { + res.push(this); + } + for (const child of this.childs) { + if (child.querySelector) { + res = res.concat(child.querySelectorAll(parts.join(' '))); + } + } + return res; +} +Element.prototype.querySelector = function(query) { + let parts = query.split(' '); + if (match(this, parts[0]) === 1) { + parts = parts.slice(1); + } + if (parts.length === 0) { + return this; + } else { + for (const child of this.childs) { + if (child.querySelector) { + let res = child.querySelector(parts.join(' ')); + if (res) { + return res; + } + } + } + } + return undefined; +} + +class HTMLElement extends Element {} + + +class Document extends Element {} +Document.prototype.createElement = function(tag) { + return new Element(tag); +} +Document.prototype.createTextNode = function(text) { + return text; +}, +Document.prototype.createDocumentFragment = function() { + return new Document(); +} +Document.prototype.addEventListener = function(a,b) {} + + +let output = new HTMLElement("body"); +let document = new Document(); + +let ue_object = +`Begin Object Class="" Name="" + FunctionReference=(MemberName="Crouch") + NodePosX=0 + NodePosY=0 + CustomProperties Pin (PinType.PinCategory="exec",) + CustomProperties Pin (Direction="EGPD_Output",PinType.PinCategory="exec",) + CustomProperties Pin (PinName="Target",PinType.PinCategory="object") +End Object` + +function is_out(arg) { + return arg.out || (!arg.const && arg.ref); +} +function get_ue_type(type) { + if (type.includes("Delegate")) { + return 'PinType.PinCategory="delegate",' + } + switch (type) { + case "void": return undefined + case "float": return 'PinType.PinCategory="real",PinType.PinSubCategory="float",' + case "bool": return 'PinType.PinCategory="bool",' + case "uint8": return 'PinType.PinCategory="byte",' + case "FString": return 'PinType.PinCategory="String",' + case "int": return 'PinType.PinCategory="int",' + case "FName": return 'PinType.PinCategory="Name",' + case "FText": return 'PinType.PinCategory="Text",' + default: return 'PinType.PinCategory="object",' + } +} +function build_pin(arg) { + let type = get_ue_type(arg.type); + if (type === undefined) { return ""; } + let out = is_out(arg) ? 'Direction="EGPD_Output",' : ''; + let det_val = arg.default ? `DefaultValue=${arg.default.trim()},` : ''; + if (det_val === '' && arg.type === "UEnum") { + det_val = 'DefaultValue="",'; + } + let array = arg.array ? `PinType.ContainerType="Array",`: ''; + let res = `CustomProperties Pin (PinName="${arg.name}",${out}${type}${det_val}${array})\n`; + return res; +} +function build_call(type, name, args) { + for (const key in args) { + args[key] = build_pin(args[key]) + } + return ` +Begin Object Class="" Name="" + FunctionReference=(MemberName="${name}") + NodePosX=0 + NodePosY=0 + CustomProperties Pin (PinType.PinCategory="exec",) + CustomProperties Pin (Direction="EGPD_Output", PinType.PinCategory="exec",) + CustomProperties Pin (PinName="Target",PinType.PinCategory="object",) + ${build_pin(type)} + ${args.join("")} +End Object +` +} +function build_event(type, name, args) { + for (const key in args) { + args[key].out = true; + args[key] = build_pin(args[key]) + } + return ` +Begin Object Class=K2Node_Event Name="" ExportPath="" + EventReference=(MemberName="${name}") + NodePosX=0 + NodePosY=0 + CustomProperties Pin (Direction="EGPD_Output",PinType.PinCategory="delegate",) + CustomProperties Pin (Direction="EGPD_Output", PinType.PinCategory="exec",) + ${args.join("")} +End Object +` +} +function build_pure(type, name, args, static) { + for (const key in args) { + args[key] = build_pin(args[key]) + } + return ` +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="" + FunctionReference=(MemberName="${name}") + bIsPureFunc=True + bIsConstFunc=True + NodePosX=0 + NodePosY=0 + ${static ? '' : 'CustomProperties Pin (PinName="Target",PinType.PinCategory="object",)'} + ${build_pin(type)} + ${args.join("")} +End Object +` +} +function construct_ueasset(type, signature) { + var builder = undefined; + switch (type) { + case "BlueprintCallable": builder = build_call; break; + case "BlueprintImplementableEvent": builder = build_event; break; + case "BlueprintPure": builder = build_pure; break; + default: throw new Exception(`unknown type: '${type}'`); + } + var re = /\s*(?static)?\s*(?.*?) (?\S*)\((?[^)]*)\)/; + // var re2 = /((?const)?(?[^,]*?)(?\*|&)? (?\S*))(,|$)/g + var re_return = /\s*(?const)?\s*(?