From f245cf665a44769760aec4f1cb06267e45dcd0ea Mon Sep 17 00:00:00 2001 From: JBenda Date: Thu, 11 Jan 2024 20:21:41 +0100 Subject: [PATCH] Introducing C Bindings (#75) Adds a new component (clib) for a library linkable to C + adds testing for C and python bindings + enhanced Doxygen documentation + fixes in the snapshot mechanism --- .github/workflows/build.yml | 35 +- .github/workflows/release.yml | 8 +- CMakeLists.txt | 15 +- Documentation/cmake_example.zip | Bin 1961 -> 2609 bytes Documentation/cmake_example/CMakeLists.txt | 8 +- Documentation/cmake_example/main.c | 38 ++ Documentation/cmake_example/test.ink.json | 1 + Doxyfile | 5 +- inkcpp/CMakeLists.txt | 11 +- inkcpp/array.h | 72 +-- inkcpp/collections/restorable.cpp | 184 ++++--- inkcpp/globals_impl.cpp | 5 + inkcpp/include/list.h | 18 +- inkcpp/include/runner.h | 6 +- inkcpp/include/snapshot.h | 35 +- inkcpp/include/story.h | 13 +- inkcpp/output.cpp | 19 + inkcpp/runner_impl.cpp | 73 +-- inkcpp/runner_impl.h | 2 +- inkcpp/simple_restorable_stack.h | 512 +++++++++--------- inkcpp/string_table.cpp | 223 ++++---- inkcpp/string_table.h | 3 +- inkcpp_c/CMakeLists.txt | 62 +++ inkcpp_c/include/inkcpp.h | 365 +++++++++++++ inkcpp_c/inkcpp.cpp | 344 ++++++++++++ inkcpp_c/inkcpp_c.pc.in | 10 + inkcpp_c/tests/ExternalFunction.c | 56 ++ inkcpp_c/tests/Globals.c | 98 ++++ inkcpp_c/tests/Lists.c | 73 +++ inkcpp_c/tests/Observer.c | 36 ++ inkcpp_c/tests/Snapshot.c | 65 +++ inkcpp_compiler/CMakeLists.txt | 9 +- inkcpp_compiler/compiler.cpp | 2 +- inkcpp_py/example.py | 8 +- inkcpp_py/src/module.cpp | 77 +-- inkcpp_py/tests/conftest.py | 53 ++ inkcpp_py/tests/test_ExternalFunctions.py | 31 +- inkcpp_py/tests/test_Globals.py | 40 ++ inkcpp_py/tests/test_Lists.py | 43 ++ inkcpp_py/tests/test_Observer.py | 27 + inkcpp_py/tests/test_Snapshot.py | 40 ++ inkcpp_test/CMakeLists.txt | 52 +- inkcpp_test/EmptyStringForDivert.cpp | 5 +- .../ExternalFunctionsExecuteProperly.cpp | 7 +- inkcpp_test/FallbackFunction.cpp | 5 +- inkcpp_test/Globals.cpp | 5 +- inkcpp_test/InkyJson.cpp | 35 +- inkcpp_test/LabelCondition.cpp | 5 +- inkcpp_test/Lists.cpp | 5 +- inkcpp_test/LookaheadSafe.cpp | 5 +- inkcpp_test/NewLines.cpp | 129 +++-- inkcpp_test/NoEarlyTags.cpp | 5 +- inkcpp_test/Observer.cpp | 5 +- inkcpp_test/SpaceAfterBracketChoice.cpp | 5 +- inkcpp_test/Tags.cpp | 9 +- inkcpp_test/ThirdTierChoiceAfterBrackets.cpp | 7 +- inkcpp_test/UTF8.cpp | 4 +- inkcpp_test/Value.cpp | 6 +- inkcpp_test/ink/SimpleStoryFlow.ink | 65 +++ setup.py | 2 +- shared/public/config.h | 2 + unreal/CMakeLists.txt | 46 +- .../inkcpp_editor/Private/InkAssetFactory.cpp | 2 +- .../Private/inklecate_cmd.cpp.in | 20 +- 64 files changed, 2331 insertions(+), 825 deletions(-) create mode 100644 Documentation/cmake_example/main.c create mode 100644 Documentation/cmake_example/test.ink.json create mode 100644 inkcpp_c/CMakeLists.txt create mode 100644 inkcpp_c/include/inkcpp.h create mode 100644 inkcpp_c/inkcpp.cpp create mode 100644 inkcpp_c/inkcpp_c.pc.in create mode 100644 inkcpp_c/tests/ExternalFunction.c create mode 100644 inkcpp_c/tests/Globals.c create mode 100644 inkcpp_c/tests/Lists.c create mode 100644 inkcpp_c/tests/Observer.c create mode 100644 inkcpp_c/tests/Snapshot.c create mode 100644 inkcpp_py/tests/conftest.py create mode 100644 inkcpp_py/tests/test_Globals.py create mode 100644 inkcpp_py/tests/test_Lists.py create mode 100644 inkcpp_py/tests/test_Observer.py create mode 100644 inkcpp_py/tests/test_Snapshot.py create mode 100644 inkcpp_test/ink/SimpleStoryFlow.ink diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4eaaa7c8..1ec01419 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,16 +20,19 @@ jobs: name: MacOSX inklecate_url: https://github.com/inkle/ink/releases/download/v1.1.1/inklecate_mac.zip proof: false + unreal: false - os: windows-latest artifact: win64 name: Windows x64 inklecate_url: https://github.com/inkle/ink/releases/download/v1.1.1/inklecate_windows.zip proof: false + unreal: false - os: "ubuntu-20.04" artifact: linux name: Linux x64 inklecate_url: https://github.com/inkle/ink/releases/download/v1.1.1/inklecate_linux.zip proof: true + unreal: true steps: @@ -74,7 +77,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{github.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DINKCPP_PY=OFF + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DINKCPP_PY=OFF -DINKCPP_C=ON # Build using CMake and OS toolkit - name: Build @@ -89,40 +92,46 @@ jobs: run: ctest . -C $BUILD_TYPE -V # Copy all build artifacts to the bin directory - - name: Install CL + - name: Install Cl working-directory: ${{github.workspace}}/build shell: bash run: cmake --install . --config $BUILD_TYPE --prefix comp_cl --component cl - - # Upload bin directory as artifact - - name: Upload Binary Artifact + - name: Upload Cl uses: actions/upload-artifact@v3 with: name: ${{ matrix.artifact }}-cl path: build/comp_cl/ - - name: Install LIB + - name: Install Lib working-directory: ${{github.workspace}}/build shell: bash run: cmake --install . --config $BUILD_TYPE --prefix comp_lib --component lib - - # Upload bin directory as artifact - - name: Upload Binary Artifact + - name: Upload Lib uses: actions/upload-artifact@v3 with: name: ${{ matrix.artifact }}-lib path: build/comp_lib/ + - name: Install CLib + working-directory: ${{github.workspace}}/build + shell: bash + run: cmake --install . --config $BUILD_TYPE --prefix comp_clib --component clib + - name: Upload Clib + uses: actions/upload-artifact@v3 + with: + name: ${{matrix.artifact}}-clib + path: build/comp_clib + - name: Install UE + if: ${{ matrix.unreal }} working-directory: ${{github.workspace}}/build shell: bash run: cmake --install . --config $BUILD_TYPE --prefix comp_unreal --component unreal - - # Upload bin directory as artifact - - name: Upload Binary Artifact + - name: Upload UE + if: ${{ matrix.unreal }} uses: actions/upload-artifact@v3 with: - name: ${{ matrix.artifact }}-unreal + name: unreal path: build/comp_unreal/ # Make sure Inkproof has everything it needs to run our executable diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 449ff557..943eb2a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,13 +23,13 @@ jobs: - name: Download artifacts uses: marcofaggian/action-download-multiple-artifacts@v3.0.8 with: - names: linux-cl linux-lib linux-unreal macos-cl macos-lib macos-unreal win64-cl win64-lib win64-unreal python-package-distribution - paths: linux-cl linux-lib linux-unreal macos-cl macos-lib macos-unreal win64-cl win64-lib win64-unreal dist + 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-cliswin64-cl win64-lib win64-clib dist workflow: build.yml branch: master - name: Zip run: | - for f in linux-cl linux-lib linux-unreal macos-cl macos-lib macos-unreal win64-cl win64-lib win64-unreal; do zip -r $f $f; done + 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 - name: List run: tree - name: Publish to PyPI @@ -43,5 +43,5 @@ jobs: --repo="$GITHUB_REPOSITORY" \ --title="${GITHUB_REPOSITORY#*/} ${tag#v}" \ --generate-notes \ - "$tag" "linux-cl.zip" "linux-lib.zip" "linux-unreal.zip" "macos-cl.zip" "macos-lib.zip" "macos-unreal.zip" "win64-cl.zip" "win64-lib.zip" "win64-unreal.zip" + "$tag" "linux-cl.zip" "linux-lib.zip" "linux-clib.zip" "unreal.zip" "macos-cl.zip" "macos-lib.zip" "macos-clib.zip" "win64-cl.zip" "win64-lib.zip" "win64-clib.zip" diff --git a/CMakeLists.txt b/CMakeLists.txt index 15dcc442..e61f5498 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.1) +project(inkcpp VERSION 0.1.2) SET(CMAKE_CXX_STANDARD 20) SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(CMAKE_INSTALL_LIBRARY_DIR lib) @@ -13,6 +13,7 @@ SET(CMAKE_INSTALL_INCLUDE_DIR include) # Add subdirectories set(INKCPP_PY OFF CACHE BOOL "Build python bindings") set(WHEEL_BUILD OFF CACHE BOOL "Set for build wheel python lib (do not forgett INKCPP_PY") +set(INKCPP_C OFF CACHE BOOL "Build c library") if (INKCPP_PY) add_compile_options(-fPIC) @@ -21,15 +22,17 @@ endif(INKCPP_PY) add_subdirectory(shared) add_subdirectory(inkcpp) add_subdirectory(inkcpp_compiler) +if (INKCPP_C) + add_subdirectory(inkcpp_c) +endif(INKCPP_C) if (NOT WHEEL_BUILD) -add_subdirectory(inkcpp_cl) -add_subdirectory(inkcpp_test) -add_subdirectory(unreal) + add_subdirectory(inkcpp_cl) + add_subdirectory(inkcpp_test) + add_subdirectory(unreal) endif(NOT WHEEL_BUILD) - -install(TARGETS inkcpp inkcpp_compiler inkcpp_shared +install(TARGETS inkcpp inkcpp_shared inkcpp_compiler EXPORT inkcppTarget ARCHIVE DESTINATION "lib/ink" COMPONENT lib EXCLUDE_FROM_ALL diff --git a/Documentation/cmake_example.zip b/Documentation/cmake_example.zip index 492d8d14128f000313c9fb2942a77106615a2561..262bcee6d919c170d570c7b2f313d81b2273fa48 100644 GIT binary patch delta 1566 zcmZ{kc{tR07{GsH4i$sk$27^Hh788Fj@c;6wPA9F#n6acqjew6j<4QS( z9Hs1-2w@WIN+OkO980tzJ9eMl=h@o*zR&mjJn!?q??2z?^M1+&YebRO7EsteVE3Rh zlrxd?svYP;^6m`@=K~H09%yt$Mnp&B*bSW>GmPGA zM3g2tDS#HyvtH4<(;IP??O!{h?lDf`o%O_2pO3E_Rt`v5U?Dvg^nFym>z>Lu=#!m? zi!jaRK*gSiQZ(=Sj#`!tn#L1R`Lc&qrI8j=1#a&%;#)6EEF$vf*-|Ip#GjCq$hdcN zW9p)sxK?A;JIqfrRy|6j$Tn^Ta?-u_iZep*dmI=#7xIbY1sdejjke|!fmxUjV>C_Pl* zr=#<*cJq=SOjiVYS^D68fK-N{l&RBgi$sL8gBklUwNm858nMmRhj zMWE5d78)q|&XJyG=jZGcL?-anxU^0c$ zRc_OnyZZKsfyhJ*R$(xjer>&K*lFYz?-teye*lHEfwTQ35_D_lkz=F5GM*$FTv7l?HQ5CNU_LKa_te&TrmjcrVj-q)BjGF|%7 z`Q(^aeOG&xW>RqUkgkPmB(sEgyF_G;(H!g)TPxbhDR43FhKt_YA#Yb+f`AY>0cxjP z2pYi?sDHygp0Df(P8|q+%Z1=D3;;v{6yWJk@+G^G!%6-WKeDPn$tM8kX=l3^fX4p! z9;6bx1IE;Z44mC+C_-_`-yiEQ&dZgNow-$IAuWVAF{vaIWw8$eUQ?HFSl!y^;)LM= zYH#neZ=WjDO$Mih?R(#6J6UyaPFJL3e-I$|n$7Q>r-BX7%q{?{g+s7Yc z`rIlKW8M`8;ggt-%XAn$udhOBwQEM+=t#htdv`)pssbQn|VLtF zd$O35Fu7{`we6MpH<%9_5Sh(|RKiQ(8$HN8XtyVv)N@tByYm$*Z{bXFeVq=~y)8oV z#l=3%GagEX#Ml*VnwM_M#PG}YR;+`j6FbBsouhTyeSg-b@FsBI6jI75%FlbMi8G9% z%Z!YcWa^ixELOO;`3{ZV|5;)-g1#nm;FJt%CCREP@NSNZ1l{I%bDH@O!KyRbpM3w& zp_Z=YccM7YM=VeHj`mq*cqPs_%JxiS=tLWj2g8QdvNbzmvZ@~9RAl~Ozc*q|zLY(A zAyy+Y%@%!R`$=|tW4@UzY9(PCVQm57K|}wsDfj}8hjLX4b|X*%|F$NGM`%I0+T1OH zi3rjcvROl@LAkQ|vwDIT4c&qPAQ~s_0*SKRxvt5SLbPGcdBeU}j)oxd|jd>O_F5I2csZ zRU_KxHu7%(^6oKAOi-_%_HKIW86ZZ}qU*PpugO5-&TkRfmrhAOC29ej+!>CGgM)AR z2Q7^7_Wbg8?SmroJL^prvFCkRaW-d1^Yt~dD_%H7#)hcgJ2qR^@tUo?LTyTm9H&#c ztyW&^8&~#;a@Co-eycwPJehIv^3DVkZoP#f?H=YQZg!V7sCL&2uv-YL3RlWJgUNOoz z#!vQRbYcd2Wb}Y%ObFrtzu%{f36eCVRgf+VuJ3y*=jD$t$vcOC_ei zsC>+R{zmF?r@MQ)!`-vB{z#PD3hwGJJb#V%f5XPRC$|J2Ev%$IdmI z(o)h8qsIBb^UMp&$u8e6M|WK}VExwisATn`vuyjKV*3v_9N8kRz!BrztQ2p2t)O5V zvt3)K{JePyiUENddzqnHo6f${W8Ddd8%%ebZyeMR;)rJn&D;8dEA4!^ zBUi`w)+tkhwIXlrPfkvm$oFA;jpz1)8Gk=bFv-(!I6R~jXt=J5cC=r;^Rj?Q>xR7hZv@R=2X6Q`|KH|c z%#;7H*fJk`KYg+dYb!4!lN>WVZ%k(7Fk-wsc|E5bA8HPmoWNnqH0|Bw4Vz>% diff --git a/Documentation/cmake_example/CMakeLists.txt b/Documentation/cmake_example/CMakeLists.txt index af11af8f..e80f325d 100644 --- a/Documentation/cmake_example/CMakeLists.txt +++ b/Documentation/cmake_example/CMakeLists.txt @@ -3,6 +3,10 @@ project(main) find_package(inkcpp CONFIG REQUIRED) -add_executable(main main.cpp) -set_property(TARGET main PROPERTY CXX_STANDARD 17) +# for CXX builds +add_executable(main_cpp main.cpp) target_link_libraries(main inkcpp inkcpp_compiler) + +# for C builds +# add_executable(main_c main.c) +# target_link_libraries(main inkcpp_c) diff --git a/Documentation/cmake_example/main.c b/Documentation/cmake_example/main.c new file mode 100644 index 00000000..d2a76bd9 --- /dev/null +++ b/Documentation/cmake_example/main.c @@ -0,0 +1,38 @@ +#include +#include +#include + +#include // if -lib.zip was used for the installation + +// #include // if -clib.zip was used for the installation + +InkValue ink_add(int argc, const InkValue argv[]) +{ + assert(argc == 2); + assert(argv[0].type == ValueTypeInt32 && argv[1].type == ValueTypeInt32); + return (InkValue){.type = ValueTypeInt32, .int32_v = argv[0].int32_v + argv[1].int32_v}; +} + +int main() +{ + ink_compile_json("test.ink.json", "test.bin", NULL); + HInkStory* story = ink_story_from_file("test.bin"); + HInkRunner* runner = ink_story_new_runner(story, NULL); + + ink_runner_bind(runner, "my_ink_function", ink_add, 1); + + while (1) { + while (ink_runner_can_continue(runner)) { + printf("%s", ink_runner_get_line(runner)); + } + if (ink_runner_num_choices(runner) == 0) + break; + for (int i = 0; i < ink_runner_num_choices(runner); ++i) { + printf("%i. %s\n", i, ink_choice_text(ink_runner_get_choice(runner, i))); + } + + int id; + scanf("%i", &id); + ink_runner_choose(runner, id); + } +} diff --git a/Documentation/cmake_example/test.ink.json b/Documentation/cmake_example/test.ink.json new file mode 100644 index 00000000..527456c0 --- /dev/null +++ b/Documentation/cmake_example/test.ink.json @@ -0,0 +1 @@ +{"inkVersion":21,"root":[["^Hello world!","\n",["ev",{"^->":"0.2.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":"0.c-0","flg":18},{"s":["^Hello back!",{"->":"$r","var":true},null]}],["ev",{"^->":"0.3.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":"0.c-1","flg":18},{"s":["^Bye",{"->":"$r","var":true},null]}],{"c-0":["ev",{"^->":"0.c-0.$r2"},"/ev",{"temp=":"$r"},{"->":"0.2.s"},[{"#n":"$r2"}],"\n","^Nice to hear from you!","\n",{"->":"0.g-0"},{"#f":5}],"c-1":["ev",{"^->":"0.c-1.$r2"},"/ev",{"temp=":"$r"},{"->":"0.3.s"},[{"#n":"$r2"}],"\n","^BTW 3 + 5 = ","ev",3,5,{"x()":"my_ink_function","exArgs":2},"out","/ev","\n","end",{"->":"0.g-0"},{"#f":5}],"g-0":["done",null]}],"done",null],"listDefs":{}} \ No newline at end of file diff --git a/Doxyfile b/Doxyfile index 615e3060..a291627c 100644 --- a/Doxyfile +++ b/Doxyfile @@ -946,7 +946,8 @@ WARN_LOGFILE = INPUT = inkcpp/include \ shared/public \ inkcpp_compiler/include \ - unreal/inkcpp/Source/inkcpp/Public/ + unreal/inkcpp/Source/inkcpp/Public \ + inkcpp_c/include # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -1686,7 +1687,7 @@ DISABLE_INDEX = NO # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -GENERATE_TREEVIEW = NO +GENERATE_TREEVIEW = YES # When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the # FULL_SIDEBAR option determines if the side bar is limited to only the treeview diff --git a/inkcpp/CMakeLists.txt b/inkcpp/CMakeLists.txt index d8d0b883..455ce745 100644 --- a/inkcpp/CMakeLists.txt +++ b/inkcpp/CMakeLists.txt @@ -36,16 +36,23 @@ list(APPEND COLLECTION_SOURCES collections/restorable.h collections/restorable.cpp ) +FILE(GLOB PUBLIC_HEADERS "include/*") + source_group(Collections REGULAR_EXPRESSION collections/.*) -add_library(inkcpp ${SOURCES} ${COLLECTION_SOURCES}) +add_library(inkcpp_o OBJECT ${SOURCES} ${COLLECTION_SOURCES}) +target_include_directories(inkcpp_o PUBLIC + $ + $ +) +add_library(inkcpp $) target_include_directories(inkcpp PUBLIC $ $ ) -FILE(GLOB PUBLIC_HEADERS "include/*") set_target_properties(inkcpp PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") # Make sure the include directory is included +target_link_libraries(inkcpp_o PRIVATE inkcpp_shared) target_link_libraries(inkcpp PRIVATE inkcpp_shared) # Make sure this project and all dependencies use the C++17 standard target_compile_features(inkcpp PUBLIC cxx_std_17) diff --git a/inkcpp/array.h b/inkcpp/array.h index 0be3484b..8a6dbf2e 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -279,37 +279,6 @@ class basic_restorable_array : public snapshot_interface const T _null; }; -template -inline size_t basic_restorable_array::snap(unsigned char* data, const snapper& snapper) const -{ - unsigned char* ptr = data; - bool should_write = data != nullptr; - ptr = snap_write(ptr, _saved, should_write); - ptr = snap_write(ptr, _capacity, should_write); - ptr = snap_write(ptr, _null, should_write); - for (size_t i = 0; i < _capacity; ++i) { - ptr = snap_write(ptr, _array[i], should_write); - ptr = snap_write(ptr, _temp[i], should_write); - } - return ptr - data; -} - -template -inline const unsigned char* - basic_restorable_array::snap_load(const unsigned char* data, const loader& loader) -{ - auto ptr = data; - ptr = snap_read(ptr, _saved); - ptr = snap_read(ptr, _capacity); - T null; - ptr = snap_read(ptr, null); - inkAssert(null == _null, "null value is different to snapshot!"); - for (size_t i = 0; i < _capacity; ++i) { - ptr = snap_read(ptr, _array[i]); - ptr = snap_read(ptr, _temp[i]); - } - return ptr; -} template inline void basic_restorable_array::set(size_t index, const T& value) @@ -440,7 +409,7 @@ class allocated_restorable_array final : public basic_restorable_array } delete[] _buffer; } - for (size_t i = base::capacity(); i < new_capacity; ++i) { + for (size_t i = base::capacity(); i < new_capacity / 2; ++i) { new_buffer[i] = _initialValue; new_buffer[i + base::capacity()] = _nullValue; } @@ -462,4 +431,43 @@ class allocated_restorable_array final : public basic_restorable_array T _nullValue; T* _buffer; }; + +template +inline size_t basic_restorable_array::snap(unsigned char* data, const snapper& snapper) const +{ + unsigned char* ptr = data; + bool should_write = data != nullptr; + ptr = snap_write(ptr, _saved, should_write); + ptr = snap_write(ptr, _capacity, should_write); + ptr = snap_write(ptr, _null, should_write); + for (size_t i = 0; i < _capacity; ++i) { + ptr = snap_write(ptr, _array[i], should_write); + ptr = snap_write(ptr, _temp[i], should_write); + } + return ptr - data; +} + +template +inline const unsigned char* + basic_restorable_array::snap_load(const unsigned char* data, const loader& loader) +{ + auto ptr = data; + ptr = snap_read(ptr, _saved); + decltype(_capacity) capacity; + ptr = snap_read(ptr, capacity); + if (buffer() == nullptr) { + static_cast&>(*this).resize(capacity); + } + inkAssert( + _capacity >= capacity, "New config does not allow for necessary size used by this snapshot!" + ); + T null; + ptr = snap_read(ptr, null); + inkAssert(null == _null, "null value is different to snapshot!"); + for (size_t i = 0; i < _capacity; ++i) { + ptr = snap_read(ptr, _array[i]); + ptr = snap_read(ptr, _temp[i]); + } + return ptr; +} } // namespace ink::runtime::internal diff --git a/inkcpp/collections/restorable.cpp b/inkcpp/collections/restorable.cpp index b7bf8230..6ce8e495 100644 --- a/inkcpp/collections/restorable.cpp +++ b/inkcpp/collections/restorable.cpp @@ -1,96 +1,118 @@ #include "restorable.h" #include "../stack.h" -namespace ink::runtime::internal { - unsigned char* snap_base(unsigned char* ptr, bool write, size_t pos, size_t jump, size_t save, size_t& max) - { - ptr = snapshot_interface::snap_write(ptr, pos, write); - ptr = snapshot_interface::snap_write(ptr, jump, write); - ptr = snapshot_interface::snap_write(ptr, save, write); - max = pos; - if (jump > max) { max = jump; } - if (save > max) { max = save; } - return ptr; +namespace ink::runtime::internal +{ +unsigned char* + snap_base(unsigned char* ptr, bool write, size_t pos, size_t jump, size_t save, size_t& max) +{ + ptr = snapshot_interface::snap_write(ptr, pos, write); + ptr = snapshot_interface::snap_write(ptr, jump, write); + ptr = snapshot_interface::snap_write(ptr, save, write); + max = pos; + if (jump != ~0 && jump > max) { + max = jump; } - const unsigned char* snap_load_base(const unsigned char* ptr, size_t& pos, size_t& jump, size_t& save, size_t& max) - { - ptr = snapshot_interface::snap_read(ptr, pos); - ptr = snapshot_interface::snap_read(ptr, jump); - ptr = snapshot_interface::snap_read(ptr, save); - max = pos; - if (jump > max) { max = jump; } - if (save > max) { max = save; } - return ptr; + if (save != ~0 && save > max) { + max = save; } + return ptr; +} - template<> - size_t restorable::snap(unsigned char* data, const snapper& snapper) const - { - unsigned char* ptr = data; - size_t max; - ptr = snap_base(ptr, data != nullptr, _pos, _jump, _save, max); - for(size_t i = 0; i < max; ++i) { - ptr = snap_write(ptr, _buffer[i].name, data != nullptr); - ptr += _buffer[i].data.snap(data ? ptr : nullptr, snapper); - } - return ptr - data; +const unsigned char* + snap_load_base(const unsigned char* ptr, size_t& pos, size_t& jump, size_t& save, size_t& max) +{ + ptr = snapshot_interface::snap_read(ptr, pos); + ptr = snapshot_interface::snap_read(ptr, jump); + ptr = snapshot_interface::snap_read(ptr, save); + max = pos; + if (jump != ~0 && jump > max) { + max = jump; } - template<> - size_t restorable::snap(unsigned char* data, const snapper& snapper) const - { - unsigned char* ptr = data; - size_t max; - ptr = snap_base(ptr, data != nullptr, _pos, _jump, _save, max); - for(size_t i = 0; i < max; ++i) { - ptr += _buffer[i].snap(data ? ptr : nullptr, snapper); - } - return ptr - data; + if (save != ~0 && save > max) { + max = save; } - template<> - size_t restorable::snap(unsigned char* data, const snapper&) const - { - unsigned char* ptr = data; - size_t max; - ptr = snap_base(ptr, data != nullptr, _pos, _jump, _save, max); - for(size_t i = 0; i < max; ++i) { - ptr = snap_write(ptr, _buffer[i], data != nullptr); - } - return ptr - data; + return ptr; +} + +template<> +size_t restorable::snap(unsigned char* data, const snapper& snapper) const +{ + unsigned char* ptr = data; + size_t max; + ptr = snap_base(ptr, data != nullptr, _pos, _jump, _save, max); + for (size_t i = 0; i < max; ++i) { + ptr = snap_write(ptr, _buffer[i].name, data != nullptr); + ptr += _buffer[i].data.snap(data ? ptr : nullptr, snapper); } + return ptr - data; +} - template<> - const unsigned char* restorable::snap_load(const unsigned char* ptr, const loader& loader) - { - size_t max; - ptr = snap_load_base(ptr, _pos, _jump, _save, max); - while(_size < max) { overflow(_buffer, _size); } - for(size_t i = 0; i < max; ++i) { - ptr = snap_read(ptr, _buffer[i].name); - ptr = _buffer[i].data.snap_load(ptr, loader); - } - return ptr; +template<> +size_t restorable::snap(unsigned char* data, const snapper& snapper) const +{ + unsigned char* ptr = data; + size_t max; + ptr = snap_base(ptr, data != nullptr, _pos, _jump, _save, max); + for (size_t i = 0; i < max; ++i) { + ptr += _buffer[i].snap(data ? ptr : nullptr, snapper); } - template<> - const unsigned char* restorable::snap_load(const unsigned char* ptr, const loader& loader) - { - size_t max; - ptr = snap_load_base(ptr, _pos, _jump, _save, max); - while(_size < max) { overflow(_buffer, _size); } - for(size_t i = 0; i < max; ++i) { - ptr = _buffer[i].snap_load(ptr, loader); - } - return ptr; + return ptr - data; +} + +template<> +size_t restorable::snap(unsigned char* data, const snapper&) const +{ + unsigned char* ptr = data; + size_t max; + ptr = snap_base(ptr, data != nullptr, _pos, _jump, _save, max); + for (size_t i = 0; i < max; ++i) { + ptr = snap_write(ptr, _buffer[i], data != nullptr); + } + return ptr - data; +} + +template<> +const unsigned char* restorable::snap_load(const unsigned char* ptr, const loader& loader) +{ + size_t max; + ptr = snap_load_base(ptr, _pos, _jump, _save, max); + while (_size < max) { + overflow(_buffer, _size); } - template<> - const unsigned char* restorable::snap_load(const unsigned char* ptr, const loader& loader) - { - size_t max; - ptr = snap_load_base(ptr, _pos, _jump, _save, max); - while(_size < max) { overflow(_buffer, _size); } - for(size_t i = 0; i < max; ++i) { - ptr = snap_read(ptr, _buffer[i]); - } - return ptr; + for (size_t i = 0; i < max; ++i) { + ptr = snap_read(ptr, _buffer[i].name); + ptr = _buffer[i].data.snap_load(ptr, loader); } + return ptr; +} + +template<> +const unsigned char* restorable::snap_load(const unsigned char* ptr, const loader& loader) +{ + size_t max; + ptr = snap_load_base(ptr, _pos, _jump, _save, max); + while (_size < max) { + overflow(_buffer, _size); + } + for (size_t i = 0; i < max; ++i) { + ptr = _buffer[i].snap_load(ptr, loader); + } + return ptr; +} +template<> +const unsigned char* restorable::snap_load(const unsigned char* ptr, const loader& loader) +{ + size_t max; + ptr = snap_load_base(ptr, _pos, _jump, _save, max); + while (_size < max) { + overflow(_buffer, _size); + } + for (size_t i = 0; i < max; ++i) { + ptr = snap_read(ptr, _buffer[i]); + } + return ptr; } + +} // namespace ink::runtime::internal diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index fc3e23be..e7ec18c0 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -254,7 +254,9 @@ size_t globals_impl::snap(unsigned char* data, const snapper& snapper) const _globals_initialized, "Only support snapshot of globals with runner! or you don't need a snapshot for this state" ); + ptr = snap_write(ptr, _turn_cnt, data != nullptr); ptr += _visit_counts.snap(data ? ptr : nullptr, snapper); + ptr += _visit_counts_backup.snap(data ? ptr : nullptr, snapper); ptr += _strings.snap(data ? ptr : nullptr, snapper); ptr += _lists.snap(data ? ptr : nullptr, snapper); ptr += _variables.snap(data ? ptr : nullptr, snapper); @@ -264,7 +266,10 @@ size_t globals_impl::snap(unsigned char* data, const snapper& snapper) const const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loader& loader) { _globals_initialized = true; + ptr = snap_read(ptr, _turn_cnt); ptr = _visit_counts.snap_load(ptr, loader); + ptr = _visit_counts_backup.snap_load(ptr, loader); + inkAssert(_visit_counts.size() == _visit_counts_backup.size(), "Data inconsitency"); inkAssert( _num_containers == _visit_counts.size(), "errer when loading visit counts, story file dont match snapshot!" diff --git a/inkcpp/include/list.h b/inkcpp/include/list.h index 06c062d1..ccd89426 100644 --- a/inkcpp/include/list.h +++ b/inkcpp/include/list.h @@ -6,6 +6,14 @@ #include #endif +#ifdef INK_BUILD_CLIB +struct InkListIter; +struct HInkList; +int ink_list_flags(const HInkList*, InkListIter*); +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; @@ -19,8 +27,14 @@ namespace ink::runtime { const char* _list_name; const list_interface& _list; int _i; - bool _one_list_iterator; //< iterates only though values of one list - friend list_interface; + 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); +#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) {} diff --git a/inkcpp/include/runner.h b/inkcpp/include/runner.h index 06265c1a..3c53cdc4 100644 --- a/inkcpp/include/runner.h +++ b/inkcpp/include/runner.h @@ -66,12 +66,12 @@ class runner_interface #ifdef INK_ENABLE_CSTD /** * Continue execution until the next newline, then allocate a c-style - * string with the output. This allocated string is now the callers - * responsibility and it should be deleted. + * string with the output. This allocated string is managed by the runtime + * and will be deleted at the next @ref choose() or @ref getline * * @return allocated c-style string with the output of a single line of execution */ - virtual char* getline_alloc() = 0; + virtual const char* getline_alloc() = 0; #endif /** diff --git a/inkcpp/include/snapshot.h b/inkcpp/include/snapshot.h index 987a7564..157295f0 100644 --- a/inkcpp/include/snapshot.h +++ b/inkcpp/include/snapshot.h @@ -4,19 +4,32 @@ namespace ink::runtime { - class snapshot { - public: - virtual ~snapshot() {}; +/** + * 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 + * 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() + * + * @todo Currently the id is equal to the creation order, a way to name the single runner/threads is + * WIP + */ +class snapshot +{ +public: + virtual ~snapshot(){}; - static snapshot* from_binary(const unsigned char* data, size_t length, bool freeOnDestroy = true); + static snapshot* from_binary(const unsigned char* data, size_t length, bool freeOnDestroy = true); - virtual const unsigned char* get_data() const = 0; - virtual size_t get_data_len() const = 0; - virtual size_t num_runners() const = 0; + virtual const unsigned char* get_data() const = 0; + virtual size_t get_data_len() const = 0; + virtual size_t num_runners() const = 0; #ifdef INK_ENABLE_STL - static snapshot* from_file(const char* filename); - void write_to_file(const char* filename) const; + static snapshot* from_file(const char* filename); + void write_to_file(const char* filename) const; #endif - }; -} +}; +} // namespace ink::runtime diff --git a/inkcpp/include/story.h b/inkcpp/include/story.h index 16850680..233109f2 100644 --- a/inkcpp/include/story.h +++ b/inkcpp/include/story.h @@ -13,8 +13,8 @@ namespace ink::runtime * share globals (variables, visit counts, etc). through the * globals object. By default, each runner gets its own newly * created globals store. - * @see runner_interface - * @see globals_interface + * @see ink::runtime::runner_interface + * @see ink::runtime::globals_interface */ class story { @@ -118,6 +118,9 @@ class story * way is to use `inklecate .ink`.
Which is available at the [official release * page](https://github.com/inkle/ink/releases/latest).
* + * If you want to use the inkcpp with C link against the target inkcpp_c and `#include + * ` The C-API documentation and example can be found @ref clib "here". + * * Exampl with library extracted at /YOUR/PROJECT/linux-lib * And the [Example project](../cmake_example.zip) is extracted to /YOUR/PROJECT * @code {sh} @@ -128,7 +131,7 @@ class story * inkcpp_DIR=../linux-lib cmake .. * cmake --build . * cp ../test.ink.json . - * ./main + * ./main_cpp * @endcode * * @subsection src_main main.cpp @@ -144,8 +147,8 @@ class story * @section ue Unreal Installation * * The current release is available at the [release - * page](https://github.com/JBenda/inkcpp/releases/latest), as `-unreal.zip` (e.g. - * `win64-unreal.zip`).
Unpack this folder in `/PATH/TO/UNREAL_PROJECT/Plugins/` and it will be + * page](https://github.com/JBenda/inkcpp/releases/latest), as `unreal.zip`.
+ * 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". diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 66f5ce2e..35fdee30 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -20,12 +20,29 @@ basic_stream::basic_stream(value* buffer, size_t len) void basic_stream::append(const value& in) { + // newline after glue -> no newline + // newline after glue // SPECIAL: Incoming newline if (in.type() == value_type::newline && _size > 1) { // If the end of the stream is a function start marker, we actually // want to ignore this. Function start trimming. if (_data[_size - 1].type() == value_type::func_start) return; + size_t i = _size - 1; + while (true) { + value& d = _data[i]; + // 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 { + break; + } + if (i == 0) { + break; + } + --i; + } } // Ignore leading newlines @@ -421,6 +438,8 @@ bool basic_stream::text_past_save() const return true; } else if (d.printable()) { return true; + } else if (d.type() == value_type::null) { + return true; } } diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 725ee16f..b80f9ef3 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -397,24 +397,11 @@ runner_impl::~runner_impl() #ifdef INK_ENABLE_STL std::string runner_impl::getline() { - std::string result{""}; - bool fill = false; - do { - if (fill) { - result += " "; - } - // Advance interpreter one line - advance_line(); - // Read line into std::string - result += _output.get(); - fill = _output.last_char() == ' '; - } while (_ptr != nullptr && _output.last_char() != '\n'); - - // TODO: fallback choice = no choice + advance_line(); + std::string result = _output.get(); if (! has_choices() && _fallback_choice) { choose(~0); } - // Return result inkAssert(_output.is_empty(), "Output should be empty after getline!"); return result; @@ -422,19 +409,10 @@ std::string runner_impl::getline() void runner_impl::getline(std::ostream& out) { - bool fill = false; - do { - if (fill) { - out << " "; - } - // Advance interpreter one line - advance_line(); - // Write into out - out << _output; - fill = _output.last_char() == ' '; - } while (_ptr != nullptr && _output.last_char() != '\n'); - - // TODO: fallback choice = no choice + // Advance interpreter one line + advance_line(); + // Write into out + out << _output; if (! has_choices() && _fallback_choice) { choose(~0); } @@ -463,11 +441,9 @@ void runner_impl::getall(std::ostream& out) // Advance interpreter until we're stopped while (can_continue()) { advance_line(); + // Send output into stream + out << _output; } - - // Send output into stream - out << _output; - // Return result inkAssert(_output.is_empty(), "Output should be empty after getall!"); } @@ -477,25 +453,12 @@ void runner_impl::getall(std::ostream& out) FString runner_impl::getline() { clear_tags(); - FString result{}; - bool fill = false; - do { - if (fill) { - result += " "; - } - // Advance interpreter one line - advance_line(); - // Read lin ve into std::string - const char* str = _output.get_alloc(_globals->strings(), _globals->lists()); - result.Append(str, c_str_len(str)); - fill = _output.last_char() == ' '; - } while (_ptr != nullptr && _output.last_char() != '\n'); + advance_line() FString result{}; + result.Append(_output.get_alloc(_globals->strings(), globals->lists())); - // TODO: fallback choice = no choice if (! has_choices() && _fallback_choice) { choose(~0); } - // Return result inkAssert(_output.is_empty(), "Output should be empty after getline!"); return result; @@ -657,11 +620,15 @@ const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& l } #ifdef INK_ENABLE_CSTD -char* runner_impl::getline_alloc() +const char* runner_impl::getline_alloc() { - /// TODO - inkFail("Not implemented yet!"); - return nullptr; + advance_line(); + const char* res = _output.get_alloc(_globals->strings(), _globals->lists()); + if (! has_choices() && _fallback_choice) { + choose(~0); + } + inkAssert(_output.is_empty(), "Output should be empty after getline!"); + return res; } #endif @@ -704,8 +671,6 @@ runner_impl::change_type runner_impl::detect_change() const return change_type::newline_removed; } - // TODO New Tags -> extended - // If there's new text content, we went too far if (hasAddedNewText) { return change_type::extended_past_newline; @@ -1042,7 +1007,7 @@ void runner_impl::step() _eval.push(values::ex_fn_not_found); } else if (_output.saved() && _output.saved_ends_with(value_type::newline) && ! fn->lookaheadSafe()) { // TODO: seperate token? - _output.append(values::newline); + _output.append(values::null); } else { fn->call(&_eval, numArguments, _globals->strings(), _globals->lists()); } diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index eccc6a5f..c3e27d65 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -68,7 +68,7 @@ namespace ink::runtime::internal #ifdef INK_ENABLE_CSTD // c-style getline - virtual char* getline_alloc() override; + virtual const char* getline_alloc() override; #endif // move to path diff --git a/inkcpp/simple_restorable_stack.h b/inkcpp/simple_restorable_stack.h index 6d8a23b3..3e359adb 100644 --- a/inkcpp/simple_restorable_stack.h +++ b/inkcpp/simple_restorable_stack.h @@ -6,300 +6,324 @@ namespace ink::runtime::internal { - /// only use this type for simple objects with simple copy operator and no heap references - /// because they will may be serialized, stored and loaded in a different instance - template - class simple_restorable_stack : public snapshot_interface +/// only use this type for simple objects with simple copy operator and no heap references +/// because they will may be serialized, stored and loaded in a different instance +template +class simple_restorable_stack : public snapshot_interface +{ +public: + simple_restorable_stack(T* buffer, size_t size, const T& null) + : _buffer(buffer) + , _size(size) + , _null(null) { - public: - simple_restorable_stack(T* buffer, size_t size, const T& null) - : _buffer(buffer), _size(size), _null(null) { } - virtual ~simple_restorable_stack() = default; - - - void push(const T& value); - T pop(); - const T& top() const; - - size_t size() const; - bool empty() const; - void clear(); - - bool iter(const T*& iterator) const; - bool rev_iter(const T*& iterator) const; - - // == Save/Restore == - void save(); - void restore(); - void forget(); - - virtual size_t snap(unsigned char* data, const snapper&) const; - virtual const unsigned char* snap_load(const unsigned char* data, const loader&); - - protected: - virtual void overflow(T*& buffer, size_t& size) { - inkFail("Stack overflow!"); - } - - void initialize_data(T* buffer, size_t size) { - inkAssert(_buffer == nullptr && _size == 0, "Try to double initialize a restorable stack." - "To extend the size use overflow()"); - _buffer = buffer; - _size = size; - } - private: - T* _buffer; - size_t _size; - const T _null; + } - const static size_t InvalidIndex = ~0; + virtual ~simple_restorable_stack() = default; - size_t _pos = 0; - size_t _save = InvalidIndex, _jump = InvalidIndex; - }; - template - class managed_restorable_stack : public simple_restorable_stack - { - using base = simple_restorable_stack; - public: - template = true> - managed_restorable_stack(const T& null) : simple_restorable_stack(nullptr, 0, null) { } - template = true> - managed_restorable_stack(const T& null) : - simple_restorable_stack(nullptr, 0, null), _stack{} - { base::initialize_data(_stack.data(), N); } - virtual void overflow(T*& buffer, size_t& size) override { - if constexpr (dynamic) { - if (buffer) { - _stack.extend(); - } - buffer = _stack.data(); - size = _stack.capacity(); - } else { - base::overflow(buffer, size); - } - } - private: - managed_array _stack; - }; + void push(const T& value); + T pop(); + const T& top() const; + size_t size() const; + bool empty() const; + void clear(); - template - inline void simple_restorable_stack::push(const T& value) - { - inkAssert(value != _null, "Can not push a 'null' value onto the stack."); + bool iter(const T*& iterator) const; + bool rev_iter(const T*& iterator) const; - // Don't overwrite saved data. Jump over it and record where we jumped from - if (_save != InvalidIndex && _pos < _save) - { - _jump = _pos; - _pos = _save; - } + // == Save/Restore == + void save(); + void restore(); + void forget(); - if (_pos >= _size) { - overflow(_buffer, _size); - } + virtual size_t snap(unsigned char* data, const snapper&) const; + virtual const unsigned char* snap_load(const unsigned char* data, const loader&); - // Push onto the top of the stack - _buffer[_pos++] = value; - } +protected: + virtual void overflow(T*& buffer, size_t& size) { inkFail("Stack overflow!"); } - template - inline T simple_restorable_stack::pop() + void initialize_data(T* buffer, size_t size) { - inkAssert(_pos > 0, "Nothing left to pop!"); - - // Move over jump area - if (_pos == _save) { - _pos = _jump; - } - - // Decrement and return - return _buffer[--_pos]; + inkAssert( + _buffer == nullptr && _size == 0, + "Try to double initialize a restorable stack." + "To extend the size use overflow()" + ); + _buffer = buffer; + _size = size; } - template - inline const T& simple_restorable_stack::top() const - { - if (_pos == _save) - { - inkAssert(_jump > 0, "Stack is empty! No top()"); - return _buffer[_jump - 1]; - } +private: + T* _buffer; + size_t _size; + const T _null; - inkAssert(_pos > 0, "Stack is empty! No top()"); - return _buffer[_pos - 1]; - } + const static size_t InvalidIndex = ~0; - template - inline size_t simple_restorable_stack::size() const - { - // If we're past the save point, ignore anything in the jump region - if (_pos >= _save) - return _pos - (_save - _jump); + size_t _pos = 0; + size_t _save = InvalidIndex, _jump = InvalidIndex; +}; - // Otherwise, pos == size - return _pos; - } +template +class managed_restorable_stack : public simple_restorable_stack +{ + using base = simple_restorable_stack; - template - inline bool simple_restorable_stack::empty() const +public: + template = true> + managed_restorable_stack(const T& null) + : simple_restorable_stack(nullptr, 0, null) { - return size() == 0; } - template - inline void simple_restorable_stack::clear() + template = true> + managed_restorable_stack(const T& null) + : simple_restorable_stack(nullptr, 0, null) + , _stack{} { - // Reset to start - _save = _jump = InvalidIndex; - _pos = 0; + base::initialize_data(_stack.data(), N); } - template - inline bool simple_restorable_stack::iter(const T*& iterator) const + virtual void overflow(T*& buffer, size_t& size) override { - // If empty, nothing to iterate - if (_pos == 0) - return false; - - // Begin at the top of the stack - if (iterator == nullptr || iterator < _buffer || iterator > _buffer + _pos) - { - if (_pos == _save) { - if(_jump == 0) { - iterator = nullptr; - return false; - } - iterator = _buffer + _jump -1; - } else { - iterator = _buffer + _pos - 1; + if constexpr (dynamic) { + if (buffer) { + _stack.extend(); } - return true; + buffer = _stack.data(); + size = _stack.capacity(); + } else { + base::overflow(buffer, size); } + } - // Move over stored data - if (iterator == _buffer + _save) - iterator = _buffer + _jump; +private: + managed_array _stack; +}; - // Run backwards - iterator--; +template +inline void simple_restorable_stack::push(const T& value) +{ + inkAssert(value != _null, "Can not push a 'null' value onto the stack."); + // Don't overwrite saved data. Jump over it and record where we jumped from + if (_save != InvalidIndex && _pos < _save) { + _jump = _pos; + _pos = _save; + } - // End - if (iterator < _buffer) - { - iterator = nullptr; - return false; - } + if (_pos >= _size) { + overflow(_buffer, _size); + } - return true; + // Push onto the top of the stack + _buffer[_pos++] = value; +} + +template +inline T simple_restorable_stack::pop() +{ + inkAssert(_pos > 0, "Nothing left to pop!"); + + // Move over jump area + if (_pos == _save) { + _pos = _jump; } - template - inline bool simple_restorable_stack::rev_iter(const T*& iterator) const - { - if (_pos == 0) - return false; - if (iterator == nullptr || iterator < _buffer || iterator > _buffer + _pos) { + // Decrement and return + return _buffer[--_pos]; +} + +template +inline const T& simple_restorable_stack::top() const +{ + if (_pos == _save) { + inkAssert(_jump > 0, "Stack is empty! No top()"); + return _buffer[_jump - 1]; + } + + inkAssert(_pos > 0, "Stack is empty! No top()"); + return _buffer[_pos - 1]; +} + +template +inline size_t simple_restorable_stack::size() const +{ + // If we're past the save point, ignore anything in the jump region + if (_pos >= _save) + return _pos - (_save - _jump); + + // Otherwise, pos == size + return _pos; +} + +template +inline bool simple_restorable_stack::empty() const +{ + return size() == 0; +} + +template +inline void simple_restorable_stack::clear() +{ + // Reset to start + _save = _jump = InvalidIndex; + _pos = 0; +} + +template +inline bool simple_restorable_stack::iter(const T*& iterator) const +{ + // If empty, nothing to iterate + if (_pos == 0) + return false; + + // Begin at the top of the stack + if (iterator == nullptr || iterator < _buffer || iterator > _buffer + _pos) { + if (_pos == _save) { if (_jump == 0) { - if (_save == _pos) { - iterator = nullptr; - return false; - } - iterator = _buffer + _save; - } else { - iterator = _buffer; + iterator = nullptr; + return false; } - return true; - } - ++iterator; - if (iterator == _buffer + _jump) { - iterator = _buffer + _save; - } - - if(iterator == _buffer + _pos) { - iterator = nullptr; - return false; + iterator = _buffer + _jump - 1; + } else { + iterator = _buffer + _pos - 1; } return true; } - template - inline void simple_restorable_stack::save() - { - inkAssert(_save == InvalidIndex, "Can not save stack twice! restore() or forget() first"); + // Move over stored data + if (iterator == _buffer + _save) + iterator = _buffer + _jump; - // Save current stack position - _save = _jump = _pos; - } + // Run backwards + iterator--; - template - inline void simple_restorable_stack::restore() - { - inkAssert(_save != InvalidIndex, "Can not restore() when there is no save!"); - // Move position back to saved position - _pos = _save; - _save = _jump = InvalidIndex; + // End + if (iterator < _buffer) { + iterator = nullptr; + return false; } - template - inline void simple_restorable_stack::forget() - { - inkAssert(_save != InvalidIndex, "Can not forget when the stack has never been saved!"); - - inkAssert(_pos >= _save || _pos < _jump, "Pos is in backup areal! (should be impossible)"); - // if we are below the backup areal, no changes are needed - // if we above the backup areal, we need to collpse it - if (_pos >= _save) { - size_t delta = _save - _jump; - for(size_t i = _save; i < _pos; ++i) { - _buffer[i - delta] = _buffer[i]; + return true; +} + +template +inline bool simple_restorable_stack::rev_iter(const T*& iterator) const +{ + if (_pos == 0) + return false; + if (iterator == nullptr || iterator < _buffer || iterator > _buffer + _pos) { + if (_jump == 0) { + if (_save == _pos) { + iterator = nullptr; + return false; } - _pos -= delta; + iterator = _buffer + _save; + } else { + iterator = _buffer; } + return true; + } + ++iterator; + if (iterator == _buffer + _jump) { + iterator = _buffer + _save; + } - // Just reset save position - _save = _jump = InvalidIndex; + if (iterator == _buffer + _pos) { + iterator = nullptr; + return false; } - template - size_t simple_restorable_stack::snap(unsigned char* data, const snapper&) const - { - unsigned char* ptr = data; - bool should_write = data != nullptr; - ptr = snap_write(ptr, _null, should_write); - ptr = snap_write(ptr, _pos, should_write ); - ptr = snap_write(ptr, _save, should_write ); - ptr = snap_write(ptr, _jump, should_write ); - size_t max = _pos; - if (_save > max) { max = _save; } - if (_jump > max) { max = _jump; } - for(size_t i = 0; i < max; ++i) - { - ptr = snap_write(ptr, _buffer[i], should_write ); + return true; +} + +template +inline void simple_restorable_stack::save() +{ + inkAssert(_save == InvalidIndex, "Can not save stack twice! restore() or forget() first"); + + // Save current stack position + _save = _jump = _pos; +} + +template +inline void simple_restorable_stack::restore() +{ + inkAssert(_save != InvalidIndex, "Can not restore() when there is no save!"); + + // Move position back to saved position + _pos = _save; + _save = _jump = InvalidIndex; +} + +template +inline void simple_restorable_stack::forget() +{ + inkAssert(_save != InvalidIndex, "Can not forget when the stack has never been saved!"); + + inkAssert(_pos >= _save || _pos < _jump, "Pos is in backup areal! (should be impossible)"); + // if we are below the backup areal, no changes are needed + // if we above the backup areal, we need to collpse it + if (_pos >= _save) { + size_t delta = _save - _jump; + for (size_t i = _save; i < _pos; ++i) { + _buffer[i - delta] = _buffer[i]; } - return ptr - data; + _pos -= delta; } - template - const unsigned char* simple_restorable_stack::snap_load(const unsigned char* ptr, const loader& loader) - { - T null; - ptr = snap_read(ptr, null); - inkAssert(null == _null, "different null value compared to snapshot!"); - ptr = snap_read(ptr, _pos); - ptr = snap_read(ptr, _save); - ptr = snap_read(ptr, _jump); - size_t max = _pos; - if(_save > max) { max = _save; } - if(_jump > max) { max = _jump; } - while(_size < max) { overflow(_buffer, _size); } - for(size_t i = 0; i < max; ++i) - { - ptr = snap_read(ptr, _buffer[i]); - } - return ptr; + // Just reset save position + _save = _jump = InvalidIndex; +} + +template +size_t simple_restorable_stack::snap(unsigned char* data, const snapper&) const +{ + unsigned char* ptr = data; + bool should_write = data != nullptr; + ptr = snap_write(ptr, _null, should_write); + ptr = snap_write(ptr, _pos, should_write); + ptr = snap_write(ptr, _save, should_write); + ptr = snap_write(ptr, _jump, should_write); + size_t max = _pos; + if (_save != InvalidIndex && _save > max) { + max = _save; + } + if (_jump != InvalidIndex && _jump > max) { + max = _jump; + } + for (size_t i = 0; i < max; ++i) { + ptr = snap_write(ptr, _buffer[i], should_write); + } + return ptr - data; +} + +template +const unsigned char* + simple_restorable_stack::snap_load(const unsigned char* ptr, const loader& loader) +{ + T null; + ptr = snap_read(ptr, null); + inkAssert(null == _null, "different null value compared to snapshot!"); + ptr = snap_read(ptr, _pos); + ptr = snap_read(ptr, _save); + ptr = snap_read(ptr, _jump); + size_t max = _pos; + if (_save != InvalidIndex && _save > max) { + max = _save; + } + if (_jump != InvalidIndex && _jump > max) { + max = _jump; + } + while (_size < max) { + overflow(_buffer, _size); + } + for (size_t i = 0; i < max; ++i) { + ptr = snap_read(ptr, _buffer[i]); } + return ptr; } +} // namespace ink::runtime::internal diff --git a/inkcpp/string_table.cpp b/inkcpp/string_table.cpp index 467a6e1a..05f70378 100644 --- a/inkcpp/string_table.cpp +++ b/inkcpp/string_table.cpp @@ -2,135 +2,142 @@ namespace ink::runtime::internal { - string_table::~string_table() - { - // Delete all allocated strings - for (auto iter = _table.begin(); iter != _table.end(); ++iter) - delete[] iter.key(); - _table.clear(); +string_table::~string_table() +{ + // Delete all allocated strings + for (auto iter = _table.begin(); iter != _table.end(); ++iter) + delete[] iter.key(); + _table.clear(); +} + +char* string_table::duplicate(const char* str) +{ + int len = 0; + for (const char* i = str; *i != 0; ++i) { + ++len; } - char* string_table::duplicate(const char* str) - { - int len = 0; - for(const char* i = str; *i != 0; ++i) { - ++len; - } - char* res = create(len + 1); - char* out = res; - for(const char* i = str; *i != 0; ++i, ++out) { - *out = *i; - } - *out = 0; - return res; + char* res = create(len + 1); + char* out = res; + for (const char* i = str; *i != 0; ++i, ++out) { + *out = *i; } + *out = 0; + return res; +} - char* string_table::create(size_t length) - { - // allocate the string - char* data = new char[length]; - if (data == nullptr) - return nullptr; - - // Add to the tree - bool success = _table.insert(data, true); // TODO: Should it start as used? - inkAssert(success, "Duplicate string pointer in the string_table. How is that possible?"); - if (!success) - { - delete[] data; - return nullptr; - } +char* string_table::create(size_t length) +{ + // allocate the string + /// @todo use continuous memory + char* data = new char[length]; + if (data == nullptr) + return nullptr; - // Return allocated string - return data; + // Add to the tree + bool success = _table.insert(data, true); // TODO: Should it start as used? + inkAssert(success, "Duplicate string pointer in the string_table. How is that possible?"); + if (! success) { + delete[] data; + return nullptr; } - void string_table::clear_usage() - { - // Clear usages - for (auto iter = _table.begin(); iter != _table.end(); ++iter) - iter.val() = false; - } + // Return allocated string + return data; +} - void string_table::mark_used(const char* string) - { - auto iter = _table.find(string); - if (iter == _table.end()) - return; // assert?? +void string_table::clear_usage() +{ + // Clear usages + for (auto iter = _table.begin(); iter != _table.end(); ++iter) + iter.val() = false; +} - // set used flag - *iter = true; - } +void string_table::mark_used(const char* string) +{ + auto iter = _table.find(string); + if (iter == _table.end()) + return; // assert?? - void string_table::gc() - { - // begin at the start - auto iter = _table.begin(); - - const char* last = nullptr; - while (iter != _table.end()) - { - // If the string is not used - if (!*iter) - { - // Delete it - delete[] iter.key(); - _table.erase(iter); - - // Re-establish iterator at last position - // TODO: BAD. We need inline delete that doesn't invalidate pointers - if (last == nullptr) - iter = _table.begin(); - else - { - iter = _table.find(last); - iter++; - } + // set used flag + *iter = true; +} + +void string_table::gc() +{ + // begin at the start + auto iter = _table.begin(); + + const char* last = nullptr; + while (iter != _table.end()) { + // If the string is not used + if (! *iter) { + // Delete it + delete[] iter.key(); + _table.erase(iter); - continue; + // Re-establish iterator at last position + // TODO: BAD. We need inline delete that doesn't invalidate pointers + if (last == nullptr) + iter = _table.begin(); + else { + iter = _table.find(last); + iter++; } - // Next - last = iter.key(); - iter++; + continue; } + + // Next + last = iter.key(); + iter++; } +} - size_t string_table::snap(unsigned char* data, const snapper&) const - { - unsigned char* ptr = data; - bool should_write = data != nullptr; - for (size_t i = 0; i < _table.size(); ++i) { - for(auto itr = _table.begin(); itr != _table.end(); ++itr) { - if (itr.temp_identifier() == i) { - ptr = snap_write(ptr, itr.key(), strlen(itr.key()) + 1, should_write ); - break; +size_t string_table::snap(unsigned char* data, const snapper&) const +{ + unsigned char* ptr = data; + bool should_write = data != nullptr; + for (size_t i = 0; i < _table.size(); ++i) { + for (auto itr = _table.begin(); itr != _table.end(); ++itr) { + if (itr.temp_identifier() == i) { + size_t length = strlen(itr.key()) + 1; + if (length == 1) { + ptr = snap_write(ptr, EMPTY_STRING, 2, should_write); + } else { + ptr = snap_write(ptr, itr.key(), length, should_write); } + break; } } - ptr = snap_write(ptr, "\0", 1, should_write ); - return ptr - data; } + ptr = snap_write(ptr, "\0", 1, should_write); + return ptr - data; +} - const unsigned char* string_table::snap_load(const unsigned char* data, const loader& loader) - { - auto* ptr = data; - int i = 0; - while(*ptr) { - size_t len = 0; - for(;ptr[len];++len); - ++len; - auto str = create(len); - loader.string_table.push() = str; - ptr = snap_read(ptr, str, len); - mark_used(str); +const unsigned char* string_table::snap_load(const unsigned char* data, const loader& loader) +{ + auto* ptr = data; + int i = 0; + while (*ptr) { + size_t len = 0; + for (; ptr[len]; ++len) + ; + ++len; + auto str = create(len); + loader.string_table.push() = str; + ptr = snap_read(ptr, str, len); + if (len == 2 && str[0] == EMPTY_STRING[0]) { + str[0] = 0; } - return ptr + 1; + mark_used(str); } + return ptr + 1; +} - size_t string_table::get_id(const char* string) const - { - auto iter = _table.find(string); - inkAssert(iter != _table.end(), "Try to fetch not contained string!"); - return iter.temp_identifier(); - } +size_t string_table::get_id(const char* string) const +{ + auto iter = _table.find(string); + inkAssert(iter != _table.end(), "Try to fetch not contained string!"); + return iter.temp_identifier(); } +} // namespace ink::runtime::internal diff --git a/inkcpp/string_table.h b/inkcpp/string_table.h index 0d66489d..09b05513 100644 --- a/inkcpp/string_table.h +++ b/inkcpp/string_table.h @@ -36,5 +36,6 @@ namespace ink::runtime::internal private: avl_array _table; - }; + static constexpr const char* EMPTY_STRING = "\x03"; + }; } diff --git a/inkcpp_c/CMakeLists.txt b/inkcpp_c/CMakeLists.txt new file mode 100644 index 00000000..49f5bbbc --- /dev/null +++ b/inkcpp_c/CMakeLists.txt @@ -0,0 +1,62 @@ +add_library(inkcpp_c inkcpp.cpp + $ + $ ) +target_include_directories(inkcpp_c PUBLIC + $ + $ +) +target_include_directories(inkcpp_c PUBLIC + $ + $ + $ + $ +) +set_target_properties(inkcpp_c PROPERTIES PUBLIC_HEADER "include/inkcpp.h") +target_link_libraries(inkcpp_c PRIVATE inkcpp_shared) +target_compile_definitions(inkcpp_c PRIVATE INK_BUILD_CLIB) + +install(TARGETS inkcpp_c + EXPORT inkcppTarget + ARCHIVE DESTINATION "lib/ink" + COMPONENT lib EXCLUDE_FROM_ALL + PUBLIC_HEADER DESTINATION "include/ink/c" + COMPONENT lib EXCLUDE_FROM_ALL +) + +install(TARGETS inkcpp_c inkcpp_shared + EXPORT inkcpp_cTarget + ARCHIVE DESTINATION "lib/ink" + COMPONENT clib EXCLUDE_FROM_ALL + PUBLIC_HEADER DESTINATION "include/ink/" + COMPONENT clib EXCLUDE_FROM_ALL) + +include(CMakePackageConfigHelpers) +configure_package_config_file(${PROJECT_SOURCE_DIR}/Config.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/inkcppConfig.cmake" + INSTALL_DESTINATION "lib/cmake/inkcpp" + NO_SET_AND_CHECK_MACRO + NO_CHECK_REQUIRED_COMPONENTS_MACRO) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/inkcppConfigVersion.cmake" + VERSION "${inkcpp_VERSION_MAJOR}.${inkcpp_VERSION_MINOR}" + COMPATIBILITY AnyNewerVersion) +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/inkcppConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/inkcppConfigVersion.cmake + DESTINATION lib/cmake/inkcpp COMPONENT clib EXCLUDE_FROM_ALL) +export(EXPORT inkcpp_cTarget + FILE "${CMAKE_CURRENT_BINARY_DIR}/inkcppTargets.cmake") +install(EXPORT inkcpp_cTarget + FILE inkcppTargets.cmake DESTINATION "lib/cmake/inkcpp" + COMPONENT clib EXCLUDE_FROM_ALL) + +# configure in two steps to get the current installation prefix +set(PREFIX "@PREFIX@") +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/inkcpp_c.pc.in + ${CMAKE_BINARY_DIR}/inkcpp_c.pc.in + @ONLY) +install(CODE [[ + get_filename_component(PREFIX ${CMAKE_INSTALL_PREFIX} ABSOLUTE) + configure_file(inkcpp_c.pc.in ${PREFIX}/lib/pkgconfig/inkcpp.pc @ONLY) + ]] COMPONENT clib EXCLUDE_FROM_ALL) diff --git a/inkcpp_c/include/inkcpp.h b/inkcpp_c/include/inkcpp.h new file mode 100644 index 00000000..de279754 --- /dev/null +++ b/inkcpp_c/include/inkcpp.h @@ -0,0 +1,365 @@ +#ifndef _INKCPP_H +#define _INKCPP_H + +#include + +#ifdef __cplusplus +extern "C" { +#else +typedef struct HInkSnapshot HInkSnapshot; +typedef struct HInkChoice HInkChoice; +typedef struct HInkList HInkList; +typedef struct InkListIter InkListIter; +typedef struct InkFlag InkFlag; +typedef struct InkValue InkValue; +typedef struct HInkRunner HInkRunner; +typedef struct HInkGlobals HInkGlobals; +typedef struct HInkSTory HInkStory; +#endif + + /** @defgroup clib Clib Interface + * C bindings for inkcpp + * + * There are two different ways to get the C bindings. + * 1. Use the distributed `-lib.zip` for a C++ and C lib combined, then use CMake for the + * linkadge (or do it manually) + * 2. Use the distrubuted `-clib.zip` for a C only lib, then use CMake, or pkg-config. + * + * Please note that the included header is different between this two installation methods. + * 1. `#include ` + * 2. `#include ` + * + * 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" + * + * For setup an example for option `2.` without cmake create a directory with the files below: + * + `main.c`: found below + * + `test.ink.json`: found at @ref src_story_json + * + * And extract `-clib.zip` from the [release + * page](https://github.com/JBenda/inkcpp/releases/latest) to `/MY/INKCPP/EXAMPLE_INSTALL/PATH`. + *
To run the example do the following: + * + * + change the `prefix=...` in `/MY/INKCPP/EXAMPLE_INSTALL/PATH/lib/pkgconfig/inkcpp.pc` + * to `prefix=/MY/INKCPP_EXAMPLE_INSTALL_PATH/` + * + `export PKG_CONFIG_PATH=/MY/INKCPP/EXAMPLE_INSTALL/PATH/lib/pkgconfig` + * + `gcc -c main.c $(pkg-config --cflags inkcpp)` + * + `g++ -o main main.o $(pkg-config --libs inkcpp)` + * + `./main` + * + * As a sideproduct a file named `test.bin` should be created coaining the binary format used by + * inkCPP. + * + * @subsection src_main_c main.c + * @include cmake_example/main.c + */ + + /** @class HInkSnapshot + * @ingroup clib + * @brief Handler for a @ref ink::runtime::snapshot "ink snapshot" + * @copydetails ink::runtime::snapshot + */ + struct HInkSnapshot; + /** @memberof HInkSnapshot + * @copydoc ink::runtime::snapshot::from_file() + */ + HInkSnapshot* ink_snapshot_from_file(const char* filename); + /** @memberof HInkSnapshot + * @copydoc ink::runtime::snapshot::num_runner() + */ + int ink_snapshot_num_runners(const HInkSnapshot* self); + /** @memberof HInkSnapshot + * @copydoc ink::runtime::snapshot::write_to_file() + */ + void ink_snapshot_write_to_file(const HInkSnapshot* self, const char* filename); + + /** @class HInkChoice + * @ingroup clib + * @brief Handler for a @ref ink::runtime::choice "ink choice" + * @copydetails ink::runtime::choice + */ + struct HInkChoice; + /** @memberof HInkChoice + * @copydoc ink::runtime::choice::text + */ + const char* ink_choice_text(const HInkChoice* self); + /** @memberof HInkChoice + * @copydoc ink::runtime::choice::num_tags + */ + int ink_choice_num_tags(const HInkChoice* self); + /** @memberof HInkChoice + * @copydoc ink::runtime::choice::get_tag + */ + const char* ink_choice_get_tag(const HInkChoice* self, int index); + + /** @class HInkList + * @ingroup clib + * @brief Handler for a @ref ink::runtime::list_interface "ink list" + */ + struct HInkList; + + /** + * Iterater used to iterate flags of a ::HInkList + * @see ink_list_flags() ink_list_flags_from() + * @ingroup clib + * @code + * const HInkList* list = ...; + * InkListIter iter; + * if (ink_list_flags(list, &iter)) { + * do { + * iter->flag_name; + * iter->list_name; + * // ... + * } while(ink_list_iter_next(&iter)); + * } + * @endcode + */ + struct InkListIter { + const void* _data; ///< @private + int _i; ///< @private + int _single_list; ///< @private + const char* flag_name; ///< Name of the current flag + const char* list_name; ///< name of the list the flag corresponds to + }; + + void ink_list_add(HInkList* self, const char* flag_name); + void ink_list_remove(HInkList* self, const char* flag_name); + int ink_list_contains(const HInkList* self, const char* flag_name); + /** + * @memberof HInkList + * Creates an Iterator over all flags contained in a list. + * @see @ref InkListIter for a usage example + * @retval 0 if the list contains no flags and the iterator would be invalid + */ + int ink_list_flags(const HInkList* self, InkListIter* iter); + /** + * @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 list_name name of defined list which elements should be filterd + * @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); + /** + * @memberof InkListIter + * @retval 0 if the there is no next element + * @retval 1 if a new flag can be found in iter.flag_name + */ + int ink_list_iter_next(InkListIter* self); + + /** Repserentation of a ink variable. + * @ingroup clib + * 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() + */ + struct InkValue { + union { + int bool_v; ///< contains value if #type == @ref InkValue::Type::ValueTypeBool "ValueTypeBool" + uint32_t uint32_v; ///< contains value if #type == @ref InkValue::Type::ValueTypeUint32 + ///< "ValueTypeUint32" + int32_t int32_v; ///< contains value if #type == @ref InkValue::Type::ValueTypeInt32 + ///< "ValueTypeInt32" + const char* string_v; ///< contains value if #type == @ref InkValue::Type::ValueTypeString + ///< "ValueTypeString" + float float_v; ///< contains value if #type == @ref InkValue::Type::ValueTypeFloat + ///< "ValueTypeFloat" + HInkList* + list_v; ///< contains value if #type == @ref InkValue::Type::ValueTypeList "ValueTypeList" + }; + + /// indicates which type is contained in the value + enum Type { + ValueTypeNone, ///< the Value does not contain any value + ValueTypeBool, ///< a boolean + ValueTypeUint32, ///< a unsigned integer + ValueTypeInt32, ///< a signed integer + ValueTypeString, ///< a string + ValueTypeFloat, ///< a floating point number + ValueTypeList ///< a ink list + } type; ///< indicates type contained in value + }; + + // const char* ink_value_to_string(const InkValue* self); + + /** @memberof HInkRunner + * Callback for a Ink external function which returns void + * @param argc number of arguments + * @param argv array containing the arguments + */ + typedef InkValue (*InkExternalFunction)(int argc, const InkValue argv[]); + /** @memberof HInkRunner + * Callback for a Ink external function wihich returns a value + * @param argc number of arguments + * @param argv array contaning the arguments + * @return value to be furthe process by the ink runtime + */ + typedef void (*InkExternalFunctionVoid)(int argc, const InkValue argv[]); + + /** @class HInkRunner + * @ingroup clib + * A handle for an @ref ink::runtime::runner_interface "ink runner" + * @copydetails ink::runtime::runner_interface + */ + struct HInkRunner; + /** @memberof HInkRunner + * Deconstructs the Runner and all frees assoziated resources + */ + void ink_runner_delete(HInkRunner* self); + /** @memberof HInkRunner + * Creates a snapshot, for later reloading. + * @attention All runners assoziated with the same globals will create the same snapshot + * @ref ::HInkSnapshot + */ + HInkSnapshot* ink_runner_create_snapshot(const HInkRunner* self); + /** @memberof HInkRunner + * @copydoc ink::runtime::runner_interface::can_continue() + */ + int ink_runner_can_continue(const HInkRunner* self); + /** @memberof HInkRunner + * @copydoc ink::runtime::runner_interface::getline_alloc() + */ + const char* ink_runner_get_line(HInkRunner* self); + /** @memberof HInkRunner + * @copydoc ink::runtime::runner_interface::num_tags() + */ + int ink_runner_num_tags(const HInkRunner* self); + /** @memberof HInkRunner + * @copydoc ink::runtime::runner_interface::get_tag() + */ + const char* ink_runner_tag(const HInkRunner* self, int tag_id); + /** @memberof HInkRunner + * @copydoc ink::runtiem::runner_interface::num_choices() + */ + int ink_runner_num_choices(const HInkRunner* self); + /** @memberof HInkRunner + * @copydoc ink::runtime::runner_interface::get_choice() + */ + const HInkChoice* ink_runner_get_choice(const HInkRunner* self, int choice_id); + /** @memberof HInkRunner + * @copydoc ink::runtime::runner_interface::choose() + */ + void ink_runner_choose(HInkRunner* self, int choice_id); + /** @memberof HInkRunner + * Binds a external function which is called form the runtime, with no return value. + * @see ink_runner_bind() + * @param lookaheadSafe if false stop glue lookahead if encounter this function + * this prevents double execution of external functions but can lead to + * missing glues + */ + void ink_runner_bind_void( + HInkRunner* self, const char* function_name, InkExternalFunctionVoid callback, + int lookaheadSafe + ); + /** @memberof HInkRunner + * Binds a external function which is called from the runtime, with a return vallue. + * @see ink_runner_bind_void() + * @param lookaheadSafe if false stop glue lookahead if encounter this function + * this prevents double execution of external functions but can lead to + * missing glues + */ + void ink_runner_bind( + HInkRunner* self, const char* function_name, InkExternalFunction callback, int lookaheadSafe + ); + + + /** @class HInkGlobals + * @ingroup clib + * Handle for the @ref ink::runtime::globals_interface "global variable store" shared among ink + * runners. + * @copydetails ink::runtime::globals_interface + */ + struct HInkGlobals; + /** @memberof HInkGlobals + * @param new_value contains the value newly assigned + * @param old_value contains the previous value or a @ref InkValue::Type::ValueTypeNone + * "ValueTypeNone" if the variable was previously unset. + */ + typedef void (*InkObserver)(InkValue new_value, InkValue old_value); + /** @memberof HInkGlobals + * Deconstructs the globals store and frees all assoziated memories. + * @attention invalidates all assoziated @ref HInkRunner + */ + void ink_globals_delete(HInkGlobals* self); + /** @memberof HInkGlobals + * Creates a snapshot for later reloading. + * @attention All runners assoziated with the same globals will create the same snapshot. + * @ref ::HInkSnapshot + */ + HInkSnapshot* ink_globals_create_snapshot(const HInkGlobals* self); + /** @memberof HInkGlobals + * assignes a observer to the variable with the corresponding name. + * The observer is called each time the value of the variable gets assigned. + * To monitor value changes compare the old with new value (see @ref InkObserver) + */ + void ink_globals_observe(HInkGlobals* self, const char* variable_name, InkObserver observer); + /** @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 + */ + 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) + * @return false if the variable was not set, because the variable with this name does no exists + * or the value did not match. + */ + int ink_globals_set(HInkGlobals* self, const char* variable_name, InkValue value); + + /** @class HInkStory + * @ingroup clib + * Handle for a loaded @ref ink::runtime::story "ink story" + * @copydetails ink::runtime::story + * @see HInkGlobals + * @see HInkRunner + */ + struct HInkStory; + /** @memberof HInkStory + * @copydoc ink::runtime::story::from_file + */ + HInkStory* ink_story_from_file(const char* filename); + /** @memberof HInkStory + * deletes a story and all assoziated resources + * @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 + */ + HInkGlobals* ink_story_new_globals(HInkStory* self); + /** @memberof HInkStory + * @copydoc ink::runtime::story::new_runner + */ + HInkRunner* ink_story_new_runner(HInkStory* self, HInkGlobals* globals); + /** @memberof HInkStory + * @copydoc ink::runtime::story::new_globals_from_snapshot + */ + HInkGlobals* ink_story_new_globals_from_snapshot(HInkStory* self, const HInkSnapshot* snapshot); + /** @memberof HInkStory + * @copydoc ink::runtime::story::new_runner_from_snapshot + */ + HInkRunner* ink_story_new_runner_from_snapshot( + HInkStory* self, const HInkSnapshot* snapshot, HInkGlobals* globals, int runner_id + ); + + /** + * @ingroup clib + * Compiles a .ink.json file to an inkCPP .bin file. + * @param input_filename path to file contaning input data (.ink.json) + * @param output_filename path to file output data will be written (.bin) + * @param error if not NULL will contain a error message if an error occures (else will be set to + * NULL) + */ + void + ink_compile_json(const char* input_filename, const char* output_filename, const char** error); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/inkcpp_c/inkcpp.cpp b/inkcpp_c/inkcpp.cpp new file mode 100644 index 00000000..09b28e70 --- /dev/null +++ b/inkcpp_c/inkcpp.cpp @@ -0,0 +1,344 @@ +#include "inkcpp.h" +#include "list.h" +#include "system.h" +#include "types.h" + +#include + +#include +#include +#include +#include +#include +#include + +using namespace ink::runtime; + +InkValue inkvar_to_c(value& val) +{ + switch (val.type) { + case value::Type::Bool: + return InkValue{ + .bool_v = val.get(), + .type = InkValue::ValueTypeBool, + }; + case value::Type::Uint32: + return InkValue{ + .uint32_v = val.get(), + .type = InkValue::ValueTypeUint32, + }; + case value::Type::Int32: + return InkValue{ + .int32_v = val.get(), + .type = InkValue::ValueTypeInt32, + }; + case value::Type::String: + return InkValue{ + .string_v = val.get(), + .type = InkValue::ValueTypeString, + }; + case value::Type::Float: + return InkValue{ + .float_v = val.get(), + .type = InkValue::ValueTypeFloat, + }; + case value::Type::List: + return InkValue{ + .list_v = reinterpret_cast(val.get()), + .type = InkValue::ValueTypeList, + }; + } + inkFail("Undefined value type can not be translated"); + return InkValue{}; +} + +value inkvar_from_c(InkValue& val) +{ + switch (val.type) { + case InkValue::ValueTypeBool: return value(val.bool_v); + case InkValue::ValueTypeUint32: return value(val.uint32_v); + case InkValue::ValueTypeInt32: return value(val.int32_v); + case InkValue::ValueTypeString: return value(val.string_v); + case InkValue::ValueTypeFloat: return value(val.float_v); + case InkValue::ValueTypeList: return value(reinterpret_cast(val.list_v)); + case InkValue::ValueTypeNone: break; + } + inkFail("Undefined value type can not be translated"); + return value{}; +} + +extern "C" { + HInkSnapshot* ink_snapshot_from_file(const char* filename) + { + return reinterpret_cast(snapshot::from_file(filename)); + } + + int ink_snapshot_num_runners(const HInkSnapshot* self) + { + return reinterpret_cast(self)->num_runners(); + } + + void ink_snapshot_write_to_file(const HInkSnapshot* self, const char* filename) + { + reinterpret_cast(self)->write_to_file(filename); + } + + const char* ink_choice_text(const HInkChoice* self) + { + return reinterpret_cast(self)->text(); + } + + int ink_choice_num_tags(const HInkChoice* self) + { + return reinterpret_cast(self)->num_tags(); + } + + const char* ink_choice_get_tag(const HInkChoice* self, int tag_id) + { + return reinterpret_cast(self)->get_tag(tag_id); + } + + void ink_list_add(HInkList* self, const char* flag_name) + { + reinterpret_cast(self)->add(flag_name); + } + + void ink_list_remove(HInkList* self, const char* flag_name) + { + reinterpret_cast(self)->remove(flag_name); + } + + int ink_list_contains(const HInkList* self, const char* flag_name) + { + return reinterpret_cast(self)->contains(flag_name); + } + + int ink_list_flags(const HInkList* self, InkListIter* iter) + { + list_interface::iterator itr = reinterpret_cast(self)->begin(); + *iter = InkListIter{ + ._data = &itr._list, + ._i = itr._i, + ._single_list = itr._one_list_iterator, + .flag_name = itr._flag_name, + .list_name = itr._list_name, + }; + return itr != reinterpret_cast(self)->end(); + } + + int ink_list_flags_from(const HInkList* self, const char* list_name, InkListIter* iter) + { + list_interface::iterator itr = reinterpret_cast(self)->begin(list_name); + *iter = InkListIter{ + ._data = &itr._list, + ._i = itr._i, + ._single_list = itr._one_list_iterator, + .flag_name = itr._flag_name, + .list_name = itr._list_name, + }; + return itr != reinterpret_cast(self)->end(); + } + + int ink_list_iter_next(InkListIter* self) + { + list_interface::iterator itr( + self->flag_name, *reinterpret_cast(self->_data), self->_i, + self->_single_list + ); + ++itr; + self->flag_name = itr._flag_name; + self->list_name = itr._list_name; + self->_i = itr._i; + return itr != itr._list.end(); + } + + void ink_runner_delete(HInkRunner* self) { delete reinterpret_cast(self); } + + HInkSnapshot* ink_runner_create_snapshot(const HInkRunner* self) + { + return reinterpret_cast( + reinterpret_cast(self)->get()->create_snapshot() + ); + } + + int ink_runner_can_continue(const HInkRunner* self) + { + return reinterpret_cast(self)->get()->can_continue(); + } + + const char* ink_runner_get_line(HInkRunner* self) + { + return reinterpret_cast(self)->get()->getline_alloc(); + } + + int ink_runner_get_num_tags(const HInkRunner* self) + { + return reinterpret_cast(self)->get()->num_tags(); + } + + const char* ink_runner_tag(const HInkRunner* self, int tag_id) + { + return reinterpret_cast(self)->get()->get_tag(tag_id); + } + + int ink_runner_num_choices(const HInkRunner* self) + { + return reinterpret_cast(self)->get()->num_choices(); + } + + const HInkChoice* ink_runner_get_choice(const HInkRunner* self, int choice_id) + { + return reinterpret_cast( + reinterpret_cast(self)->get()->get_choice(choice_id) + ); + } + + void ink_runner_choose(HInkRunner* self, int choice_id) + { + return reinterpret_cast(self)->get()->choose(choice_id); + } + + void ink_runner_bind_void( + HInkRunner* self, const char* function_name, InkExternalFunctionVoid callback, + int lookaheadSafe + ) + { + static_assert(sizeof(ink::runtime::value) >= sizeof(InkValue)); + return reinterpret_cast(self)->get()->bind( + function_name, + [callback](size_t len, const value* vals) { + InkValue* c_vals = reinterpret_cast(const_cast(vals)); + int c_len = len; + for (int i = 0; i < c_len; ++i) { + c_vals[i] = inkvar_to_c(const_cast(vals[i])); + } + callback(c_len, c_vals); + }, + lookaheadSafe + ); + } + + void ink_runner_bind( + HInkRunner* self, const char* function_name, InkExternalFunction callback, int lookaheadSafe + ) + { + static_assert(sizeof(ink::runtime::value) >= sizeof(InkValue)); + return reinterpret_cast(self)->get()->bind( + function_name, + [callback](size_t len, const value* vals) -> value { + InkValue* c_vals = reinterpret_cast(const_cast(vals)); + int c_len = len; + for (int i = 0; i < c_len; ++i) { + c_vals[i] = inkvar_to_c(const_cast(vals[i])); + } + InkValue res = callback(c_len, c_vals); + return inkvar_from_c(res); + }, + lookaheadSafe + ); + } + + void ink_globals_delete(HInkGlobals* self) { delete reinterpret_cast(self); } + + HInkSnapshot* ink_globals_create_snapshot(const HInkGlobals* self) + { + return reinterpret_cast( + reinterpret_cast(self)->get()->create_snapshot() + ); + } + + void ink_globals_observe(HInkGlobals* self, const char* variable_name, InkObserver observer) + { + reinterpret_cast(self)->get()->observe( + variable_name, + [observer](value new_value, ink::optional old_value) { + observer( + inkvar_to_c(new_value), old_value.has_value() + ? inkvar_to_c(old_value.value()) + : InkValue{.type = InkValue::Type::ValueTypeNone} + ); + } + ); + } + + InkValue ink_globals_get(const HInkGlobals* self, const char* variable_name) + { + ink::optional o_val + = reinterpret_cast(self)->get()->get(variable_name); + if (! o_val.has_value()) { + return InkValue{ + .type = InkValue::ValueTypeNone, + }; + } else { + return inkvar_to_c(o_val.value()); + } + } + + int ink_globals_set(HInkGlobals* self, const char* variable_name, InkValue val) + { + return reinterpret_cast(self)->get()->set(variable_name, inkvar_from_c(val)); + } + + HInkStory* ink_story_from_file(const char* filename) + { + return reinterpret_cast(story::from_file(filename)); + } + + void ink_story_delete(HInkStory* self) { delete reinterpret_cast(self); } + + HInkRunner* ink_story_new_runner(HInkStory* self, HInkGlobals* global_store) + { + runner* res = new runner( + global_store + ? reinterpret_cast(self)->new_runner(*reinterpret_cast(global_store)) + : reinterpret_cast(self)->new_runner() + ); + return reinterpret_cast(res); + } + + HInkRunner* ink_story_new_runner_from_snapshot( + HInkStory* self, const HInkSnapshot* snapshot, HInkGlobals* global_store, int runner_id + ) + { + const ink::runtime::snapshot& snap = *reinterpret_cast(snapshot); + runner* res = new runner( + global_store + ? reinterpret_cast(self)->new_runner_from_snapshot( + snap, *reinterpret_cast(global_store), runner_id + ) + : reinterpret_cast(self)->new_runner_from_snapshot(snap, nullptr, runner_id) + ); + return reinterpret_cast(res); + } + + HInkGlobals* ink_story_new_globals(HInkStory* self) + { + return reinterpret_cast(new globals(reinterpret_cast(self)->new_globals()) + ); + } + + HInkGlobals* ink_story_new_globals_from_snapshot(HInkStory* self, const HInkSnapshot* snap) + { + return reinterpret_cast( + new globals(reinterpret_cast(self)->new_globals_from_snapshot( + *reinterpret_cast(snap) + )) + ); + } + + void ink_compile_json(const char* input_filename, const char* output_filename, const char** error) + { + ink::compiler::compilation_results result; + ink::compiler::run(input_filename, output_filename, &result); + if (error != nullptr && ! result.errors.empty() || ! result.warnings.empty()) { + std::string str{}; + for (auto& warn : result.warnings) { + str += "WARNING: " + warn + '\n'; + } + for (auto& err : result.errors) { + str += "ERROR: " + err + '\n'; + } + *error = strdup(str.c_str()); + } + } +} diff --git a/inkcpp_c/inkcpp_c.pc.in b/inkcpp_c/inkcpp_c.pc.in new file mode 100644 index 00000000..b13beb58 --- /dev/null +++ b/inkcpp_c/inkcpp_c.pc.in @@ -0,0 +1,10 @@ +prefix=@PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/lib/ink +includedir=${prefix}/include + +Name: inkcpp +Description: C Bindnigs for inkpp. @CMAKE_PROJECT_DESCRIPTION@ +Version: @PROJECT_VERSION@ +CFlags: -I${includedir} +Libs: -L${libdir} -linkcpp_c diff --git a/inkcpp_c/tests/ExternalFunction.c b/inkcpp_c/tests/ExternalFunction.c new file mode 100644 index 00000000..b50a3f48 --- /dev/null +++ b/inkcpp_c/tests/ExternalFunction.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include + +#undef NDEBUG +#include + +int cnt_my_sqrt = 0; + +InkValue my_sqrt(int argc, const InkValue argv[]) +{ + cnt_my_sqrt += 1; + assert(argc == 1); + InkValue v = argv[0]; + switch (v.type) { + case ValueTypeFloat: v.float_v = sqrtf(v.float_v); break; + case ValueTypeInt32: v.int32_v = sqrt(v.int32_v); break; + case ValueTypeUint32: v.uint32_v = sqrtf(v.uint32_v); break; + default: assert(0); + } + return v; +} + +int cnt_greeting = 0; + +InkValue greeting(int argc, const InkValue argv[]) +{ + cnt_greeting += 1; + assert(argc == 0); + InkValue v; + v.type = ValueTypeString; + v.string_v = "Hohooh"; + return v; +} + +int main(int argc, const char* argv[]) +{ + HInkStory* story = ink_story_from_file(INK_TEST_RESOURCE_DIR "FallBack.bin"); + HInkRunner* runner = ink_story_new_runner(story, NULL); + ink_runner_bind(runner, "greeting", greeting, 0); + ink_runner_bind(runner, "sqrt", my_sqrt, 0); + + const char* res = ink_runner_get_line(runner); + assert(strcmp(res, "Hohooh ! A small demonstration of my power:\n") == 0); + assert(ink_runner_can_continue(runner)); + + assert(strcmp(ink_runner_get_line(runner), "Math 4 * 4 = 16, stunning i would say\n") == 0); + assert(ink_runner_can_continue(runner) == 0); + + assert(cnt_my_sqrt == 2); + assert(cnt_greeting == 1); + return 0; +} diff --git a/inkcpp_c/tests/Globals.c b/inkcpp_c/tests/Globals.c new file mode 100644 index 00000000..d6f02f8f --- /dev/null +++ b/inkcpp_c/tests/Globals.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include + +#include + +#undef NDEBUG +#include + +HInkStory* story = NULL; +HInkGlobals* store = NULL; +HInkRunner* thread = NULL; + +void setup() +{ + if (! story) { + story = ink_story_from_file(INK_TEST_RESOURCE_DIR "GlobalStory.bin"); + } + if (store) { + ink_globals_delete(store); + } + store = ink_story_new_globals(story); + if (thread) { + ink_runner_delete(thread); + } + thread = ink_story_new_runner(story, store); +} + +int main() +{ + //====== Just reading Globals ===== + setup(); + + // check story + assert( + strcmp( + ink_runner_get_line(thread), + "My name is Jean Passepartout, but my friend's call me Jackie. I'm 23 years old.\n" + ) + == 0 + ); + assert(strcmp(ink_runner_get_line(thread), "Foo:23\n") == 0); + + // check values in store + InkValue val = ink_globals_get(store, "age"); + assert(val.type == ValueTypeInt32 && val.int32_v == 23); + val = ink_globals_get(store, "friendly_name_of_player"); + assert(val.type == ValueTypeString && strcmp(val.string_v, "Jackie") == 0); + + + //===== Modifing Globals ===== + setup(); + + // set value of 'age' + val.type = ValueTypeInt32; + val.int32_v = 30; + assert(ink_globals_set(store, "age", val)); + + // set value of 'friendl_name_of_player' + val.type = ValueTypeString; + val.string_v = "Freddy"; + assert(ink_globals_set(store, "friendly_name_of_player", val)); + + + // check story output + assert( + strcmp( + ink_runner_get_line(thread), + "My name is Jean Passepartout, but my friend's call me Freddy. I'm 30 years old.\n" + ) + == 0 + ); + assert(strcmp(ink_runner_get_line(thread), "Foo:30\n") == 0); + + // check variable content + val = ink_globals_get(store, "age"); + assert(val.type == ValueTypeInt32 && val.int32_v == 30); + val = ink_globals_get(store, "friendly_name_of_player"); + assert(val.type == ValueTypeString && strcmp(val.string_v, "Freddy") == 0); + val = ink_globals_get(store, "concat"); + assert(val.type == ValueTypeString && strcmp(val.string_v, "Foo:30") == 0); + + //===== Fail to set variables with invalid types or non existing variables ===== + setup(); + val = ink_globals_get(store, "foo"); + assert(val.type == ValueTypeNone); + // not existing variable + val.type = ValueTypeString; + val.string_v = "o"; + assert(! ink_globals_set(store, "foo", val)); + val.type = ValueTypeString; + val.string_v = "o"; + // wrong type + assert(! ink_globals_set(store, "age", val)); + + return 0; +} diff --git a/inkcpp_c/tests/Lists.c b/inkcpp_c/tests/Lists.c new file mode 100644 index 00000000..f48eec1b --- /dev/null +++ b/inkcpp_c/tests/Lists.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +#include + +#undef NDEBUG +#include + +int main() +{ + HInkStory* story = ink_story_from_file(INK_TEST_RESOURCE_DIR "ListStory.bin"); + HInkGlobals* store = ink_story_new_globals(story); + HInkRunner* runner = ink_story_new_runner(story, store); + + InkValue val = ink_globals_get(store, "list"); + assert(val.type == ValueTypeList); + HInkList* list = val.list_v; + + InkListIter iter; + + // iterate through all flags + { + int hits[3] = {0}; + if (ink_list_flags(list, &iter)) { + do { + if (strcmp(iter.flag_name, "bird") == 0 && strcmp(iter.list_name, "animals") == 0) { + hits[0] = 1; + } else if (strcmp(iter.flag_name, "red") == 0 && strcmp(iter.list_name, "colors") == 0) { + hits[1] = 1; + } else if (strcmp(iter.flag_name, "yellow") == 0 && strcmp(iter.list_name, "colors") == 0) { + hits[2] = 1; + } else { + assert(0); + } + } while (ink_list_iter_next(&iter)); + assert(hits[0] && hits[1] && hits[2]); + } + } + // through all animals in list + { + int hits[2] = {0}; + if (ink_list_flags_from(list, "colors", &iter)) { + do { + if (strcmp(iter.flag_name, "red") == 0 && strcmp(iter.list_name, "colors") == 0) { + hits[0] = 1; + } else if (strcmp(iter.flag_name, "yellow") == 0 && strcmp(iter.list_name, "colors") == 0) { + hits[1] = 1; + } else { + assert(0); + } + } while (ink_list_iter_next(&iter)); + } + assert(hits[0] && hits[1]); + } + + assert(ink_list_contains(list, "yellow")); + assert(ink_list_contains(list, "white") == 0); + + ink_list_add(list, "white"); + ink_list_remove(list, "yellow"); + assert(ink_list_contains(list, "yellow") == 0); + assert(ink_list_contains(list, "white")); + + assert(ink_globals_set(store, "list", val)); + + assert(strcmp(ink_runner_get_line(runner), "cat, snake\n") == 0); + assert(ink_runner_num_choices(runner) == 2); + const HInkChoice* choice = ink_runner_get_choice(runner, 0); + assert(strcmp(ink_choice_text(choice), "list: bird, white, red") == 0); + return 0; +} diff --git a/inkcpp_c/tests/Observer.c b/inkcpp_c/tests/Observer.c new file mode 100644 index 00000000..b809eaf1 --- /dev/null +++ b/inkcpp_c/tests/Observer.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +#include + +#undef NDEBUG +#include + +int cnt = 0; + +void observer(InkValue new_value, InkValue old_value) +{ + if (cnt++ == 0) { + assert(new_value.type == ValueTypeInt32 && new_value.int32_v == 1); + assert(old_value.type == ValueTypeNone); + } else { + assert(new_value.type == ValueTypeInt32 && new_value.int32_v == 5); + assert(old_value.type == ValueTypeInt32 && old_value.int32_v == 1); + } +} + +int main() +{ + HInkStory* story = ink_story_from_file(INK_TEST_RESOURCE_DIR "ObserverStory.bin"); + HInkGlobals* store = ink_story_new_globals(story); + HInkRunner* thread = ink_story_new_runner(story, store); + + ink_globals_observe(store, "var1", observer); + + assert(strcmp(ink_runner_get_line(thread), "hello line 1 1 hello line 2 5 test line 3 5\n") == 0); + assert(cnt == 2); + + return 0; +} diff --git a/inkcpp_c/tests/Snapshot.c b/inkcpp_c/tests/Snapshot.c new file mode 100644 index 00000000..cbf10de5 --- /dev/null +++ b/inkcpp_c/tests/Snapshot.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +#include + +#undef NDEBUG +#include + +void check_end(HInkRunner* runner) +{ + assert(ink_runner_num_choices(runner) == 3); + ink_runner_choose(runner, 2); + while (ink_runner_can_continue(runner)) { + ink_runner_get_line(runner); + } + assert(ink_runner_num_choices(runner) == 2); +} + +int main() +{ + HInkStory* story = ink_story_from_file(INK_TEST_RESOURCE_DIR "SimpleStoryFlow.bin"); + HInkRunner* runner = ink_story_new_runner(story, NULL); + + ink_runner_get_line(runner); + assert(ink_runner_num_choices(runner) == 3); + ink_runner_choose(runner, 2); + + // snapshot after choose -> snapshot will print text after loading + HInkSnapshot* snap1 = ink_runner_create_snapshot(runner); + + int cnt = 0; + while (ink_runner_can_continue(runner)) { + ink_runner_get_line(runner); + ++cnt; + } + + // snapshot befroe choose, context (last output lines) can not bet optained at loading + HInkSnapshot* snap2 = ink_runner_create_snapshot(runner); + + check_end(runner); + + + ink_runner_delete(runner); + runner = ink_story_new_runner_from_snapshot(story, snap1, NULL, 0); + + // same amount at output then before + while (ink_runner_can_continue(runner)) { + ink_runner_get_line(runner); + --cnt; + } + assert(cnt == 0); + + check_end(runner); + + + ink_runner_delete(runner); + runner = ink_story_new_runner_from_snapshot(story, snap2, NULL, 0); + + assert(ink_runner_can_continue(runner) == 0); + check_end(runner); + + return 0; +} diff --git a/inkcpp_compiler/CMakeLists.txt b/inkcpp_compiler/CMakeLists.txt index b3da94dc..ea90cf0c 100644 --- a/inkcpp_compiler/CMakeLists.txt +++ b/inkcpp_compiler/CMakeLists.txt @@ -9,8 +9,13 @@ list(APPEND SOURCES command.cpp ) add_definitions(-DINK_COMPILER -DINK_EXPOSE_JSON) -add_library(inkcpp_compiler ${SOURCES}) +add_library(inkcpp_compiler_o OBJECT ${SOURCES}) +add_library(inkcpp_compiler $) +target_include_directories(inkcpp_compiler_o PUBLIC + $ + $ +) target_include_directories(inkcpp_compiler PUBLIC $ $ @@ -19,9 +24,11 @@ FILE(GLOB PUBLIC_HEADERS "include/*") set_target_properties(inkcpp_compiler PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") target_link_libraries(inkcpp_compiler PRIVATE inkcpp_shared) +target_link_libraries(inkcpp_compiler_o PRIVATE inkcpp_shared) # Make sure this project and all dependencies use the C++17 standard target_compile_features(inkcpp_compiler PUBLIC cxx_std_17) +target_compile_features(inkcpp_compiler PUBLIC cxx_std_17) # Unreal installation install(DIRECTORY "include/" DESTINATION "inkcpp/Source/inkcpp_editor/Private/ink/" COMPONENT unreal EXCLUDE_FROM_ALL) diff --git a/inkcpp_compiler/compiler.cpp b/inkcpp_compiler/compiler.cpp index 5a3be487..5084a9af 100644 --- a/inkcpp_compiler/compiler.cpp +++ b/inkcpp_compiler/compiler.cpp @@ -87,4 +87,4 @@ namespace ink::compiler // Close file fout.close(); } -} \ No newline at end of file + } // namespace ink::compiler diff --git a/inkcpp_py/example.py b/inkcpp_py/example.py index c07de706..43ad3acf 100755 --- a/inkcpp_py/example.py +++ b/inkcpp_py/example.py @@ -37,16 +37,10 @@ globals.background = inkcpp_py.Value(bg) # observer examples -def ob_ping(): - print("chang^-^") -def ob_value(x): - print("now: ", x) def ob_delta(x, y): print("from:",y,"to:",x) -globals.observe_ping("brightness", ob_ping) -globals.observe_value("brightness", ob_value) -globals.observe_delta("brightness", ob_delta) +globals.observe("brightness", ob_delta) # external function with no input, but return value diff --git a/inkcpp_py/src/module.cpp b/inkcpp_py/src/module.cpp index 83a3046d..fc5a0d94 100644 --- a/inkcpp_py/src/module.cpp +++ b/inkcpp_py/src/module.cpp @@ -35,12 +35,13 @@ PYBIND11_DECLARE_HOLDER_TYPE(T, ink::runtime::story_ptr); struct StringValueWrap : public value { StringValueWrap(const std::string& s) - : value(s.c_str()) + : value() , str{s} { + static_cast(*this) = value(str.c_str()); } - ~StringValueWrap() { std::cout << "death" << std::endl; } + ~StringValueWrap() {} std::string str; }; @@ -78,12 +79,9 @@ PYBIND11_MODULE(inkcpp_py, m) "new_runner_from_snapshot", [](story& self, const snapshot& snap, globals_ptr store ) { return self.new_runner_from_snapshot(snap, store); } ) - .def( - "new_runner_from_snapshot", - [](story& self, const snapshot& snap, globals_ptr store, unsigned idx) { - return self.new_runner_from_snapshot(snap, store, idx); - } - ); + .def("new_runner_from_snapshot", [](story& self, const snapshot& snap) { + return self.new_runner_from_snapshot(snap); + }); py::class_(m, "Globals") .def( @@ -91,19 +89,7 @@ PYBIND11_MODULE(inkcpp_py, m) "Creates a snapshot from the current state for later usage" ) .def( - "observe_ping", - [](globals& self, const char* name, std::function f) { self.observe(name, f); }, - "Get a ping each time the observed variable changes" - ) - .def( - "observe_value", - [](globals& self, const char* name, std::function f) { - self.observe(name, f); - }, - "Get a call with new value each time the variable changes" - ) - .def( - "observe_delta", + "observe", [](globals& self, const char* name, std::function)> f) { self.observe(name, f); @@ -126,27 +112,49 @@ PYBIND11_MODULE(inkcpp_py, m) if (! self.set(key.c_str(), val)) { throw py::key_error( std::string("No global variable with name '") + key - + "' found you are trying to override a non string variable with a string" + + "' found or you are trying to override a non string variable with a string" ); } }); - py::class_>( + py::class_> py_list( m, "List", "Allows reading and editing inkcpp lists. !Only valid until next choose ore getline a runner " "referncing the corresponding global" - ) - .def("add", &list::add, "Add flag to list") + ); + py_list.def("add", &list::add, "Add flag to list") .def("remove", &list::remove, "Remove flag from list") .def("contains", &list::contains, "Check if list contains the given flag") + .def( + "flags_from", + [](const list& self, const char* list_name) { + return py::make_iterator(self.begin(list_name), self.end()); + }, + "Rerutrns all flags contained in list from a list", py::keep_alive<0, 1>() + ) + .def( + "__iter__", [](const list& self) { return py::make_iterator(self.begin(), self.end()); }, + py::keep_alive<0, 1>() + ) .def("__str__", &list_to_str); - + py::class_( + py_list, "Flag", "A list flag containing the name of the flag and the corresponding list" + ) + .def_readonly("name", &list::iterator::Flag::flag_name, "The flag") + .def_readonly( + "list_name", &list::iterator::Flag::list_name, "Name of the corresponding list" + ); py::class_ py_value(m, "Value", "A Value of a Ink Variable"); py_value.def_readonly("type", &value::type, "Type contained in value"); py_value.def(py::init<>()); py_value.def(py::init()); - py_value.def(py::init()); + py_value.def("__init__", [](value& self, uint32_t v, value::Type type) { + if (type != value::Type::Uint32) { + throw py::key_error("only use this signture if you want to explicit pass a uint"); + } + self = value(v); + }); py_value.def(py::init()); py_value.def(py::init()); py_value.def(py::init()); @@ -175,7 +183,7 @@ PYBIND11_MODULE(inkcpp_py, m) throw py::attribute_error("value is in an invalid state"); }); - py::enum_(m, "Type") + py::enum_(py_value, "Type") .value("Bool", value::Type::Bool) .value("Uint32", value::Type::Uint32) .value("Int32", value::Type::Int32) @@ -190,8 +198,15 @@ PYBIND11_MODULE(inkcpp_py, m) "Creates a snapshot from the current state for later usage" ) .def("can_continue", &runner::can_continue, "check if there is content left in story") - .def("getline", static_cast(&runner::getline)) - .def("has_tags", &runner::has_tags, "Where there tags since last getline") + .def( + "getline", static_cast(&runner::getline), + "Get content of one output line" + ) + .def( + "getall", static_cast(&runner::getall), + "execute getline and append until can_continue is false" + ) + .def("has_tags", &runner::has_tags, "Where there tags since last getline?") .def("num_tags", &runner::num_tags, "Number of tags currently stored") .def( "get_tag", &runner::get_tag, "Get Tag currently stored at position i", @@ -237,7 +252,7 @@ PYBIND11_MODULE(inkcpp_py, m) ); }, py::arg("function_name"), py::arg("function"), py::arg_v("lookaheadSafe", false), - "Bind function which void result" + "Bind function with void result" ) .def( "bind", diff --git a/inkcpp_py/tests/conftest.py b/inkcpp_py/tests/conftest.py new file mode 100644 index 00000000..14fce796 --- /dev/null +++ b/inkcpp_py/tests/conftest.py @@ -0,0 +1,53 @@ +import pytest +import os +import sys +import inkcpp_py as ink + +@pytest.fixture(scope='session', autouse=True) +def inklecate_cmd(): + res = os.getenv("INKLECATE") + if res is None or res == "": + return "inklecate" + return res + + +def extract_paths(tmpdir): + def res(ink_source): + name = os.path.splitext(os.path.basename(ink_source))[0] + return ( + name, + list(map(lambda x: tmpdir + ("/" + name + x), [".bin", ".tmp"])) + ["./inkcpp_test/ink/" + ink_source] + ) + return res + +@pytest.fixture(scope='session', autouse=True) +def story_path(tmpdir_factory): + tmpdir = tmpdir_factory.getbasetemp() + # tmpdir = os.fsencode('/tmp/pytest') + return {name: files + for (name, files) in map(extract_paths(tmpdir), + filter( + lambda file: os.path.splitext(file)[1] == ".ink", + os.listdir("./inkcpp_test/ink/")))} + +@pytest.fixture(scope='session', autouse=True) +def assets(story_path, inklecate_cmd): + res = {} + for (name, files) in story_path.items(): + if not os.path.exists(files[0]): + if not os.path.exists(files[1]): + os.system('{} -o {} {}'.format(inklecate_cmd, files[1], files[2])) + ink.compile_json(str(files[1]), str(files[0])) + res[name] = ink.Story.from_file(str(files[0])) + return res + +@pytest.fixture(scope='session', autouse=True) +def generate(): + def g(asset): + store = asset.new_globals() + return [ + asset, + store, + asset.new_runner(store), + ] + return g diff --git a/inkcpp_py/tests/test_ExternalFunctions.py b/inkcpp_py/tests/test_ExternalFunctions.py index c56518e6..fc2ce97b 100644 --- a/inkcpp_py/tests/test_ExternalFunctions.py +++ b/inkcpp_py/tests/test_ExternalFunctions.py @@ -2,38 +2,15 @@ import pytest import os -@pytest.fixture -def inklecate_cmd(): - res = os.getenv("INKLECATE") - if res is None or res == "": - return "inklecate" - return res - -@pytest.fixture -def story_path(tmpdir): - return list(map(lambda x: tmpdir / ("LookaheadSafe" + x), [".bin", ".tmp"])) + ["inkcpp_test/ink/LookaheadSafe.ink"] - -@pytest.fixture -def assets(story_path, inklecate_cmd): - if not os.path.exists(story_path[0]): - if not os.path.exists(story_path[1]): - os.system('{} -o {} {}'.format(inklecate_cmd, story_path[1], story_path[2])) - ink.compile_json(str(story_path[1]), str(story_path[0])) - ink_story = ink.Story.from_file(str(story_path[0])) - ink_globals = ink_story.new_globals(); - ink_runner = ink_story.new_runner(ink_globals) - return [ink_story, ink_globals, ink_runner] - class Cnt: def __init__(self): self.cnt = 0 def __call__(self, _): self.cnt += 1 class TestExternalFunctions: - def test_lookaheadSafe(self, assets): + def test_lookaheadSafe(self, assets, generate): cnt = Cnt() - assert len(assets) == 3 - [story, globals, runner] = assets + [story, globals, runner] = generate(assets['LookaheadSafe']) runner.bind_void("foo", cnt, True) out = runner.getline() assert out == "Call1 glued to Call 2\n" @@ -42,9 +19,9 @@ def test_lookaheadSafe(self, assets): assert out == "Call 3 is seperated\n" assert cnt.cnt == 4 - def test_lookahadeUnsafe(self, assets): + def test_lookahadeUnsafe(self, assets, generate): cnt = Cnt() - [story, globals, runner] = assets + [story, globals, runner] = generate(assets['LookaheadSafe']) runner.bind_void("foo", cnt) out = runner.getline() assert out == "Call1\n" diff --git a/inkcpp_py/tests/test_Globals.py b/inkcpp_py/tests/test_Globals.py new file mode 100644 index 00000000..bb6a3674 --- /dev/null +++ b/inkcpp_py/tests/test_Globals.py @@ -0,0 +1,40 @@ +import inkcpp_py as ink +import pytest + + +class TestGlobals: + def test_reading_globals(self, assets, generate): + [story, globals, runner] = generate(assets['GlobalStory']) + + assert runner.getline() == "My name is Jean Passepartout, but my friend's call me Jackie. I'm 23 years old.\n" + assert runner.getline() == "Foo:23\n" + + val = globals.age + assert val.type == ink.Value.Int32 and str(val) == '23' + val = globals.friendly_name_of_player + assert val.type == ink.Value.String and str(val) == 'Jackie' + + def test_writing_globals(self, assets, generate): + [story, globals, runner] = generate(assets['GlobalStory']) + globals.age = ink.Value(30) + globals.friendly_name_of_player = ink.Value("Freddy") + + + assert runner.getline() == "My name is Jean Passepartout, but my friend's call me Freddy. I'm 30 years old.\n" + assert runner.getline() == "Foo:30\n" + + val = globals.age + assert val.type == ink.Value.Int32 and str(val) == '30' + val = globals.friendly_name_of_player + assert val.type == ink.Value.String and str(val) == 'Freddy' + + + def test_invalid_operations(self, assets, generate): + [story, globals, runner] = generate(assets['GlobalStory']) + + with pytest.raises(KeyError): + val = globals.foo + with pytest.raises(KeyError): + globals.foo = ink.Value(0) + with pytest.raises(KeyError): + globals.age = ink.Value('foo') diff --git a/inkcpp_py/tests/test_Lists.py b/inkcpp_py/tests/test_Lists.py new file mode 100644 index 00000000..fe423802 --- /dev/null +++ b/inkcpp_py/tests/test_Lists.py @@ -0,0 +1,43 @@ +import inkcpp_py as ink + +class TestLists: + def test_lists(self, assets, generate): + [story, globals, runner] = generate(assets['ListStory']) + + val = globals.list + l = val.as_list() + hits = [False, False, False] + for flag in l: + if flag.name == 'bird' and flag.list_name == 'animals': + hits[0] = True + elif flag.name == 'red' and flag.list_name == 'colors': + hits[1] = True + elif flag.name == 'yellow' and flag.list_name == 'colors': + hits[2] = True + else: + assert False + assert hits[0] and hits[1] and hits[2] + + hits = [False, False] + for flag in l.flags_from('colors'): + if flag.name == 'red' and flag.list_name == 'colors': + hits[0] = True + elif flag.name == 'yellow' and flag.list_name == 'colors': + hits[1] = True + else: + assert False + assert hits[0] and hits[1] + + assert l.contains('yellow') + assert not l.contains('white') + + l.add('white') + l.remove('yellow') + + assert not l.contains('yellow') + assert l.contains('white') + + globals.list = ink.Value(l) + + assert runner.getline() == 'cat, snake\n' + assert runner.get_choice(0).text() == 'list: bird, white, red' diff --git a/inkcpp_py/tests/test_Observer.py b/inkcpp_py/tests/test_Observer.py new file mode 100644 index 00000000..848adf19 --- /dev/null +++ b/inkcpp_py/tests/test_Observer.py @@ -0,0 +1,27 @@ +import inkcpp_py as ink +import pytest + +class Observer: + def __init__(self): + self.cnt = 0 + def __call__(self, new, old): + self.cnt += 1 + if self.cnt == 1: + assert new.type == ink.Value.Int32 and str(new) == '1' + assert old is None + else: + assert new.type == ink.Value.Int32 and str(new) == '5' + assert old.type == ink.Value.Int32 and str(old) == '1' + + +class TestObserver: + def test_observer(self, assets, generate): + [story, store, runner] = generate(assets['ObserverStory']) + + obs = Observer() + store.observe('var1', obs) + + assert runner.getline() == "hello line 1 1 hello line 2 5 test line 3 5\n" + assert obs.cnt == 2 + + diff --git a/inkcpp_py/tests/test_Snapshot.py b/inkcpp_py/tests/test_Snapshot.py new file mode 100644 index 00000000..b1a5b048 --- /dev/null +++ b/inkcpp_py/tests/test_Snapshot.py @@ -0,0 +1,40 @@ +import inkcpp_py as ink +import pytest + +def check_end(runner): + assert runner.num_choices() == 3 + runner.choose(2) + while runner.can_continue(): + runner.getline() + assert runner.num_choices() == 2 + +class TestSnapshot: + def test_snapshot(self, assets, generate): + [story, _, runner] = generate(assets['SimpleStoryFlow']) + + runner.getline() + assert runner.num_choices() == 3 + runner.choose(2) + + snap1 = runner.create_snapshot() + + cnt = 0 + while runner.can_continue(): + runner.getline() + cnt += 1 + + snap2 = runner.create_snapshot() + + check_end(runner) + + runner = story.new_runner_from_snapshot(snap1) + while runner.can_continue(): + runner.getline() + cnt -= 1 + assert cnt == 0 + + check_end(runner) + + runner = story.new_runner_from_snapshot(snap2) + assert not runner.can_continue() + check_end(runner) diff --git a/inkcpp_test/CMakeLists.txt b/inkcpp_test/CMakeLists.txt index 85f14219..922269be 100644 --- a/inkcpp_test/CMakeLists.txt +++ b/inkcpp_test/CMakeLists.txt @@ -37,11 +37,47 @@ endif() add_test(NAME UnitTests COMMAND $) -set (source "${CMAKE_CURRENT_SOURCE_DIR}/ink") -set (destination "${CMAKE_CURRENT_BINARY_DIR}/ink") -add_custom_command( - TARGET inkcpp_test POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${source} ${destination} - DEPENDS ${source} - COMMENT "symbolic link resources folder from ${source} => ${destination}" -) +if ($ENV{INKLECATE}) + set(INKLECATE_CMD $ENV{INKLECATE}) +else() + set(INKLECATE_CMD "inklecate") +endif() + + +set(INK_TEST_RESOURCE_DIR "${PROJECT_BINARY_DIR}/ink") +file(MAKE_DIRECTORY "${INK_TEST_RESOURCE_DIR}") + +target_compile_definitions(inkcpp_test PRIVATE + INK_TEST_RESOURCE_DIR="${INK_TEST_RESOURCE_DIR}/") + +file(GLOB JSON_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ink/*.json") +file(COPY ${JSON_FILES} DESTINATION ${INK_TEST_RESOURCE_DIR}) + +file(GLOB INK_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ink/*.ink") +foreach(INK_FILE IN LISTS INK_FILES) + get_filename_component(INK_FILENAME ${INK_FILE} NAME_WE) + set(output "${INK_TEST_RESOURCE_DIR}/${INK_FILENAME}.bin") + add_custom_command( + OUTPUT ${output} + COMMAND $ -o "${output}" "${INK_FILE}" + DEPENDS ${INK_FILE} + COMMENT "Compile test ink file '${INK_FILENAME}.ink' -> '${output}'" + ) + list(APPEND INK_OUT_FILES ${output}) +endforeach() +target_sources(inkcpp_test PRIVATE ${INK_OUT_FILES}) + +if(TARGET inkcpp_c) + file(GLOB TEST_FILES "${PROJECT_SOURCE_DIR}/inkcpp_c/tests/*.c") + foreach(TEST_FILE IN LISTS TEST_FILES) + get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE) + add_executable(${TEST_NAME} ${TEST_FILE}) + target_link_libraries(${TEST_NAME} PRIVATE inkcpp_c) + target_compile_definitions(${TEST_NAME} PRIVATE + INK_TEST_RESOURCE_DIR="${INK_TEST_RESOURCE_DIR}/") + add_test( + NAME ${TEST_NAME} + COMMAND $ + ) + endforeach() +endif(TARGET inkcpp_c) diff --git a/inkcpp_test/EmptyStringForDivert.cpp b/inkcpp_test/EmptyStringForDivert.cpp index 762e3cd9..341f0754 100644 --- a/inkcpp_test/EmptyStringForDivert.cpp +++ b/inkcpp_test/EmptyStringForDivert.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "../inkcpp_cl/test.h" #include #include @@ -13,9 +12,7 @@ SCENARIO("a story with a white space infront of an conditional Divert", "[Output // based on https://github.com/JBenda/inkcpp/issues/71 GIVEN("A story") { - inklecate("ink/EmptyStringForDivert.ink", "EmptyStringForDivert.tmp"); - ink::compiler::run("EmptyStringForDivert.tmp", "EmptyStringForDivert.bin"); - auto ink = story::from_file("EmptyStringForDivert.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "EmptyStringForDivert.bin"); runner thread = ink->new_runner(); WHEN("run") diff --git a/inkcpp_test/ExternalFunctionsExecuteProperly.cpp b/inkcpp_test/ExternalFunctionsExecuteProperly.cpp index d2b1187d..1eefb2c0 100644 --- a/inkcpp_test/ExternalFunctionsExecuteProperly.cpp +++ b/inkcpp_test/ExternalFunctionsExecuteProperly.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "../inkcpp_cl/test.h" #include #include @@ -12,11 +11,7 @@ SCENARIO("a story with an external function evaluates the function at the right { GIVEN("a story with an external function") { - inklecate("ink/ExternalFunctionsExecuteProperly.ink", "ExternalFunctionsExecuteProperly.tmp"); - ink::compiler::run( - "ExternalFunctionsExecuteProperly.tmp", "ExternalFunctionsExecuteProperly.bin" - ); - auto ink = story::from_file("ExternalFunctionsExecuteProperly.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ExternalFunctionsExecuteProperly.bin"); runner thread = ink->new_runner(); int line_count = 0; diff --git a/inkcpp_test/FallbackFunction.cpp b/inkcpp_test/FallbackFunction.cpp index d1a5acca..35990ac1 100644 --- a/inkcpp_test/FallbackFunction.cpp +++ b/inkcpp_test/FallbackFunction.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "../inkcpp_cl/test.h" #include #include @@ -15,9 +14,7 @@ SCENARIO("run a story with external function and fallback function", "[external { GIVEN("story with two external functions, one with fallback") { - inklecate("ink/FallBack.ink", "FallBack.tmp"); - ink::compiler::run("FallBack.tmp", "FallBack.bin"); - auto ink = story::from_file("FallBack.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "FallBack.bin"); runner thread = ink->new_runner(); WHEN("bind both external functions") diff --git a/inkcpp_test/Globals.cpp b/inkcpp_test/Globals.cpp index 15eb86d7..c2a6e8a1 100644 --- a/inkcpp_test/Globals.cpp +++ b/inkcpp_test/Globals.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "../inkcpp_cl/test.cpp" #include #include @@ -12,9 +11,7 @@ SCENARIO("run story with global variable", "[global variables]") { GIVEN ("a story with global variables") { - inklecate("ink/GlobalStory.ink", "GlobalsStory.tmp"); - ink::compiler::run("GlobalsStory.tmp", "GlobalsStory.bin"); - auto ink = story::from_file("GlobalsStory.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "GlobalStory.bin"); globals globStore = ink->new_globals(); runner thread = ink->new_runner(globStore); diff --git a/inkcpp_test/InkyJson.cpp b/inkcpp_test/InkyJson.cpp index 2a39af88..1de37df4 100644 --- a/inkcpp_test/InkyJson.cpp +++ b/inkcpp_test/InkyJson.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -// #include "../inkcpp_cl/test.cpp" #include #include @@ -9,25 +8,27 @@ using namespace ink::runtime; static constexpr const char* OUTPUT_PART_1 = "Once upon a time...\n"; -static constexpr const char* OUTPUT_PART_2 = "There were two choices.\nThey lived happily ever after.\n"; +static constexpr const char* OUTPUT_PART_2 + = "There were two choices.\nThey lived happily ever after.\n"; static constexpr size_t CHOICE = 0; SCENARIO("run inklecate 1.1.1 story") { - auto compiler = GENERATE("inklecate", "inky"); - GIVEN(compiler) - { - auto input_file = std::string("ink/simple-1.1.1-") + compiler + ".json"; - ink::compiler::run(input_file.c_str(), "simple.bin"); - auto ink = story::from_file("simple.bin"); - runner thread = ink->new_runner(); + auto compiler = GENERATE("inklecate", "inky"); + GIVEN(compiler) + { + auto input_file = std::string(INK_TEST_RESOURCE_DIR "simple-1.1.1-") + compiler + ".json"; + ink::compiler::run(input_file.c_str(), "simple.bin"); + auto ink = story::from_file("simple.bin"); + runner thread = ink->new_runner(); - THEN("Expect normal output") { - REQUIRE(thread->getall() == OUTPUT_PART_1); - REQUIRE(thread->has_choices()); - REQUIRE(thread->num_choices() == 2); - thread->choose(CHOICE); - REQUIRE(thread->getall() == OUTPUT_PART_2); - } - } + THEN("Expect normal output") + { + REQUIRE(thread->getall() == OUTPUT_PART_1); + REQUIRE(thread->has_choices()); + REQUIRE(thread->num_choices() == 2); + thread->choose(CHOICE); + REQUIRE(thread->getall() == OUTPUT_PART_2); + } + } } diff --git a/inkcpp_test/LabelCondition.cpp b/inkcpp_test/LabelCondition.cpp index 52dbf5d7..347e385b 100644 --- a/inkcpp_test/LabelCondition.cpp +++ b/inkcpp_test/LabelCondition.cpp @@ -1,4 +1,3 @@ -#include "../inkcpp_cl/test.h" #include "catch.hpp" #include @@ -13,9 +12,7 @@ SCENARIO( "run story with hidden choice" ) { GIVEN( "a story with choice visibale by second visit" ) { - inklecate( "ink/LabelConditionStory.ink", "LabelConditionStory.tmp" ); - ink::compiler::run( "LabelConditionStory.tmp", "LabelCondition.bin" ); - auto ink = story::from_file( "LabelCondition.bin" ); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "LabelConditionStory.bin"); globals globals = ink->new_globals(); runner thread = ink->new_runner( globals ); diff --git a/inkcpp_test/Lists.cpp b/inkcpp_test/Lists.cpp index 0d12ce87..1f9d5a06 100644 --- a/inkcpp_test/Lists.cpp +++ b/inkcpp_test/Lists.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "../inkcpp_cl/test.h" #include #include @@ -15,9 +14,7 @@ SCENARIO("run a story with lists", "[lists]") { GIVEN("a story with multi lists") { - inklecate("ink/ListStory.ink", "ListStory.tmp"); - ink::compiler::run("ListStory.tmp", "ListStory.bin"); - auto ink = story::from_file("ListStory.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ListStory.bin"); globals globals = ink->new_globals(); runner thread = ink->new_runner(globals); diff --git a/inkcpp_test/LookaheadSafe.cpp b/inkcpp_test/LookaheadSafe.cpp index 74dbac60..501e1714 100644 --- a/inkcpp_test/LookaheadSafe.cpp +++ b/inkcpp_test/LookaheadSafe.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "../inkcpp_cl/test.h" #include #include @@ -12,9 +11,7 @@ SCENARIO("A story with external functions and glue", "[external]") { GIVEN("The story") { - inklecate("ink/LookaheadSafe.ink", "LookaheadSafe.tmp"); - ink::compiler::run("LookaheadSafe.tmp", "LookaheadSafe.bin"); - auto ink = story::from_file("LookaheadSafe.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "LookaheadSafe.bin"); int cnt = 0; auto foo = [&cnt]() { diff --git a/inkcpp_test/NewLines.cpp b/inkcpp_test/NewLines.cpp index 910dfa88..e187e97a 100644 --- a/inkcpp_test/NewLines.cpp +++ b/inkcpp_test/NewLines.cpp @@ -1,68 +1,61 @@ -#include "catch.hpp" -#include "../inkcpp_cl/test.h" - -#include -#include -#include -#include - -using namespace ink::runtime; - -SCENARIO("a story has the proper line breaks", "[lines]") -{ - GIVEN("a story with line breaks") - { - inklecate("ink/LinesStory.ink", "LinesStory.tmp"); - ink::compiler::run("LinesStory.tmp", "LinesStory.bin"); - auto ink = story::from_file("LinesStory.bin"); - runner thread = ink->new_runner(); - WHEN("start thread") - { - THEN("thread can continue") - { - REQUIRE(thread->can_continue()); - } - WHEN("consume lines") - { - std::string line1 = thread->getline(); - std::string line2 = thread->getline(); - std::string line3 = thread->getline(); - std::string line4 = thread->getline(); - THEN("lines are correct") - { - REQUIRE(line1 == "Line 1\n"); - REQUIRE(line2 == "Line 2\n"); - REQUIRE(line3 == "Line 3\n"); - REQUIRE(line4 == "Line 4\n"); - } - } - WHEN("consume lines with functions") - { - thread->move_to(ink::hash_string("Functions")); - std::string line1 = thread->getline(); - std::string line2 = thread->getline(); - - THEN("function lines are correct") { - REQUIRE(line1 == "Function Line\n"); - REQUIRE(line2 == "Function Result\n"); - } - } - WHEN("consume lines with tunnels") - { - thread->move_to(ink::hash_string("Tunnels")); - std::string line1 = thread->getline(); - std::string line2 = thread->getline(); - - THEN("tunnel lines are correct") { - REQUIRE(line1 == "Tunnel Line\n"); - REQUIRE(line2 == "Tunnel Result\n"); - } - - THEN("thread cannot continue") - { - REQUIRE(!thread->can_continue()); - } - } - } - } -} +#include "catch.hpp" + +#include +#include +#include +#include + +using namespace ink::runtime; + +SCENARIO("a story has the proper line breaks", "[lines]") +{ + GIVEN("a story with line breaks") + { + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "LinesStory.bin"); + runner thread = ink->new_runner(); + WHEN("start thread") + { + THEN("thread can continue") { REQUIRE(thread->can_continue()); } + WHEN("consume lines") + { + std::string line1 = thread->getline(); + std::string line2 = thread->getline(); + std::string line3 = thread->getline(); + std::string line4 = thread->getline(); + THEN("lines are correct") + { + REQUIRE(line1 == "Line 1\n"); + REQUIRE(line2 == "Line 2\n"); + REQUIRE(line3 == "Line 3\n"); + REQUIRE(line4 == "Line 4\n"); + } + } + WHEN("consume lines with functions") + { + thread->move_to(ink::hash_string("Functions")); + std::string line1 = thread->getline(); + std::string line2 = thread->getline(); + + THEN("function lines are correct") + { + REQUIRE(line1 == "Function Line\n"); + REQUIRE(line2 == "Function Result\n"); + } + } + WHEN("consume lines with tunnels") + { + thread->move_to(ink::hash_string("Tunnels")); + std::string line1 = thread->getline(); + std::string line2 = thread->getline(); + + THEN("tunnel lines are correct") + { + REQUIRE(line1 == "Tunnel Line\n"); + REQUIRE(line2 == "Tunnel Result\n"); + } + + THEN("thread cannot continue") { REQUIRE(! thread->can_continue()); } + } + } + } +} diff --git a/inkcpp_test/NoEarlyTags.cpp b/inkcpp_test/NoEarlyTags.cpp index 6351cbc9..7073a5fc 100644 --- a/inkcpp_test/NoEarlyTags.cpp +++ b/inkcpp_test/NoEarlyTags.cpp @@ -1,4 +1,3 @@ -#include "../inkcpp_cl/test.h" #include "catch.hpp" #include @@ -12,9 +11,7 @@ SCENARIO("Story with tags and glues", "[glue, tags]") { GIVEN("lines intersected with tags and glue") { - inklecate("ink/NoEarlyTags.ink", "NoEarlyTags.tmp"); - ink::compiler::run("NoEarlyTags.tmp", "NoEarlyTags.bin"); - auto ink = story::from_file("NoEarlyTags.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "NoEarlyTags.bin"); auto thread = ink->new_runner(); WHEN("no glue") { diff --git a/inkcpp_test/Observer.cpp b/inkcpp_test/Observer.cpp index caae3b6f..43c33431 100644 --- a/inkcpp_test/Observer.cpp +++ b/inkcpp_test/Observer.cpp @@ -1,4 +1,3 @@ -#include "../inkcpp_cl/test.h" #include "catch.hpp" #include @@ -13,9 +12,7 @@ SCENARIO("Observer", "[variables]") { GIVEN("a story which changes variables") { - inklecate("ink/ObserverStory.ink", "ObserverStory.tmp"); - ink::compiler::run("ObserverStory.tmp", "ObserverStory.bin"); - auto ink = story::from_file("ObserverStory.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ObserverStory.bin"); auto globals = ink->new_globals(); runner thread = ink->new_runner(globals); WHEN("Run without observers") diff --git a/inkcpp_test/SpaceAfterBracketChoice.cpp b/inkcpp_test/SpaceAfterBracketChoice.cpp index 923a81a0..b11cb2e6 100644 --- a/inkcpp_test/SpaceAfterBracketChoice.cpp +++ b/inkcpp_test/SpaceAfterBracketChoice.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "../inkcpp_cl/test.h" #include #include @@ -12,9 +11,7 @@ SCENARIO("a story with bracketed choices and spaces can choose correctly", "[cho { GIVEN("a story with line breaks") { - inklecate("ink/ChoiceBracketStory.ink", "ChoiceBracketStory.tmp"); - ink::compiler::run("ChoiceBracketStory.tmp", "ChoiceBracketStory.bin"); - auto ink = story::from_file("ChoiceBracketStory.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ChoiceBracketStory.bin"); runner thread = ink->new_runner(); thread->getall(); WHEN("start thread") diff --git a/inkcpp_test/Tags.cpp b/inkcpp_test/Tags.cpp index ffbf222c..2eef1b7f 100644 --- a/inkcpp_test/Tags.cpp +++ b/inkcpp_test/Tags.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "../inkcpp_cl/test.h" #include #include @@ -11,9 +10,7 @@ using namespace ink::runtime; SCENARIO("tags", "[tags]") { - inklecate("ink/AHF.ink", "AHF.tmp"); - ink::compiler::run("AHF.tmp", "AHF.bin"); - auto ink = story::from_file("AHF.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "AHF.bin"); runner thread = ink->new_runner(); thread->move_to(ink::hash_string("test_knot")); while(thread->can_continue()) { @@ -26,9 +23,7 @@ SCENARIO("run story with tags", "[tags]") { GIVEN("a story with tags") { - inklecate("ink/TagsStory.ink", "TagsStory.tmp"); - ink::compiler::run("TagsStory.tmp", "TagsStory.bin"); - auto ink = story::from_file("TagsStory.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "TagsStory.bin"); runner thread = ink->new_runner(); WHEN("start thread") { diff --git a/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp b/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp index 3997aead..5aeef107 100644 --- a/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp +++ b/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "../inkcpp_cl/test.h" #include #include @@ -15,11 +14,7 @@ SCENARIO( { GIVEN("a story with brackets and nested choices") { - inklecate("ink/ThirdTierChoiceAfterBracketsStory.ink", "ThirdTierChoiceAfterBracketsStory.tmp"); - ink::compiler::run( - "ThirdTierChoiceAfterBracketsStory.tmp", "ThirdTierChoiceAfterBracketsStory.bin" - ); - auto ink = story::from_file("ThirdTierChoiceAfterBracketsStory.bin"); + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ThirdTierChoiceAfterBracketsStory.bin"); runner thread = ink->new_runner(); WHEN("start thread") diff --git a/inkcpp_test/UTF8.cpp b/inkcpp_test/UTF8.cpp index be95ecc8..1a03c55d 100644 --- a/inkcpp_test/UTF8.cpp +++ b/inkcpp_test/UTF8.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "../inkcpp_cl/test.h" #include #include @@ -14,8 +13,7 @@ SCENARIO("a story supports UTF-8", "[utf-8]") { GIVEN("a story with UTF8 characters") { - inklecate("ink/UTF8Story.ink", "UTF8Story.tmp"); - ink::compiler::run("UTF8Story.tmp", "UTF8Story.bin"); + ink::compiler::run(INK_TEST_RESOURCE_DIR "UTF8Story.ink.json", "UTF8Story.bin"); auto ink = story::from_file("UTF8Story.bin"); runner thread = ink->new_runner(); diff --git a/inkcpp_test/Value.cpp b/inkcpp_test/Value.cpp index 47fb2cdd..d3aea713 100644 --- a/inkcpp_test/Value.cpp +++ b/inkcpp_test/Value.cpp @@ -1,5 +1,4 @@ #include "catch.hpp" -#include "../inkcpp_cl/test.h" #include #include @@ -26,7 +25,6 @@ using eval_stack = ink::runtime::internal::eval_stack<28, false>; using ink::Command; using ink::runtime::internal::story_impl; -using ink::runtime::internal::runner_impl; using ink::runtime::internal::globals_impl; using ink::runtime::globals; using ink::runtime::runner; @@ -42,9 +40,7 @@ SCENARIO("compare concatenated values") list_table lst_table{}; prng rng; eval_stack stack; - inklecate("ink/ListStory.ink", "ListStory_value.tmp"); - ink::compiler::run("ListStory_value.tmp", "ListStory_value.bin"); - story_impl story("ListStory_value.bin"); + story_impl story(INK_TEST_RESOURCE_DIR "ListStory.bin"); globals globs_ptr = story.new_globals(); runner run = story.new_runner(globs_ptr); globals_impl& globs = *globs_ptr.cast(); diff --git a/inkcpp_test/ink/SimpleStoryFlow.ink b/inkcpp_test/ink/SimpleStoryFlow.ink new file mode 100644 index 00000000..1e17f646 --- /dev/null +++ b/inkcpp_test/ink/SimpleStoryFlow.ink @@ -0,0 +1,65 @@ +// Using ink for rooms and objects +// --------------- +// https://heavens-vault-game.tumblr.com/post/162943569425/using-ink-for-rooms-and-objects + +// Heaven’s Vault is a bit different from our previous game, in that it uses ink - our narrative scripting language - to simulate what a “normal” adventure game would do, with rooms full of interactive props. +// For any ink devs out there, here’s roughly how we’re doing it. +// Each “room” is a knot. Inside the knot, we use threads for each of the props the player can interact with, so we can divide up the action into chunks of interactivity. In each thread, there’s a link back to the top of the location. +// The great thing about this structure it’s very portable - if you have “background” objects that appear in multiple rooms, you simply thread them in and pass a parameter for where to return to. +// And if you have things that can happen in the room - say, a bit of dialogue for when you stand in a certain place - you can put that in the main “hub” of the location and it’ll always be tested for. +// Heaven’s Vault gets a bit more complicated than this - because we also handle conversation, which can happen anywhere, at any time, from a separate contextual system. But that’s for another post maybe. + + +LIST waypoints = PLATEAU_BELOW_TOWER, NEAR_TOWER_BASE //, etc + +-> plateau_below_tower + +=== plateau_below_tower === +- (top) + <- distant_tower + <- half_buried_skull +- -> DONE + += distant_tower + // This choice syntax is Heaven's Vault specific, and is how we specify choices where you interact with props in the world. + * (carved) [Tower -- "Is it really a tower?"] + El: It Can't have been built. + Six: I believe you can call it what you wish, Mistress. + Six: It has, however, been carved. + + * (carvedcrater) {carved} [CraterWalls -- "They carved out this whole crater?"] + Six: It is impossible to say, Mistress. + + * [TowerBase >> Approach] + -> start_walk_to(NEAR_TOWER_BASE) -> + + // The '>>>' syntax is Heaven's Vault specific, and is how we write narrated elements. + >>> We made our way across the desolate plain. + >>> Perhaps this was once a pilgrim's path; a way of worship. + + * * El: Do you think it's going to fall on top of us? + Six: It has stood for thousands of years. + Six: But I will charge the hopper, all the same. + - - -> end_walk -> near_tower_base + + - -> top + += half_buried_skull + * (see_skull) [Skull - "Is that a skull?"] + Six: I beleive so, Mistress. + Six: Thank you for pointing it out, or I would have rolled over it. + * {see_skull} [Skull - Work it free] + El: Let's see if I can get this free without breaking it... + - -> top + += near_tower_base +-> END + + +== start_walk_to(destination) == +// [Special ink that triggers walk in Heaven's Vault] +->-> + +== end_walk +// [Special ink that waits for a walk to complete in Heaven's Vault] +->-> diff --git a/setup.py b/setup.py index 22230858..1f2bfcb1 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.1", + version="0.1.2", 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/config.h b/shared/public/config.h index 23efc89b..eab3ba0e 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -2,6 +2,8 @@ #ifdef INKCPP_API # define INK_ENABLE_UNREAL +#elif INKCPP_BUILD_CLIB +# define INK_ENABLE_CSTD #else # define INK_ENABLE_STL # define INK_ENABLE_CSTD diff --git a/unreal/CMakeLists.txt b/unreal/CMakeLists.txt index 6ebd1aa6..8e413f95 100644 --- a/unreal/CMakeLists.txt +++ b/unreal/CMakeLists.txt @@ -18,34 +18,28 @@ 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(INKLECATE_CMD "") -if(WIN32) - FetchContent_MakeAvailable(inklecate_windows) - if(NOT inklecate_windows_SOURCE_DIR) - message(WARNING "failed to download inklecate for windows, " +FetchContent_MakeAvailable(inklecate_windows) +if(NOT inklecate_windows_SOURCE_DIR) +message(WARNING "failed to download inklecate for windows, " + "the unreal plugin will be unable use a .ink file as asset directly") +else() +set(INKLECATE_CMD_WIN "Resources/inklecate/windows/inklecate.exe") +endif() + +FetchContent_MakeAvailable(inklecate_mac) +if(NOT inklecate_mac_SOURCE_DIR) + message(WARNING "failed to download inklecate for MacOS, " + "the unreal plugin will be unable use a .ink file as asset directly") +else() +set(INKLECATE_CMD_MAC "Resources/inklecate/mac/inklecate") +endif() + +FetchContent_MakeAvailable(inklecate_linux) +if(NOT inklecate_linux_SOURCE_DIR) + message(WARNING "failed to download inklecate for linux, " "the unreal plugin will be unable use a .ink file as asset directly") - else() - set(INKLECATE_CMD "Resources/inklecate/windows/inklecate.exe") - endif() -elseif(APPLE) - FetchContent_MakeAvailable(inklecate_mac) - if(NOT inklecate_mac_SOURCE_DIRE) - message(WARNING "failed to download inklecate for MacOS, " - "the unreal plugin will be unable use a .ink file as asset directly") - else() - set(INKLECATE_CMD "Resources/inklecate/mac/inklecate") - endif() -elseif(UNIX) - FetchContent_MakeAvailable(inklecate_linux) - if(NOT inklecate_linux_SOURCE_DIR) - message(WARNING "failed to download inklecate for linux, " - "the unreal plugin will be unable use a .ink file as asset directly") - else() - set(INKLECATE_CMD "Resources/inklecate/linux/inklecate") - endif() else() -message(WARNING "unable to determine OS -> unreal component cant compile ink files directly" - " to fix this may define WIN32/APPLE or UNIX manually and run again") +set(INKLECATE_CMD_LINUX "Resources/inklecate/linux/inklecate") endif() configure_file( diff --git a/unreal/inkcpp/Source/inkcpp_editor/Private/InkAssetFactory.cpp b/unreal/inkcpp/Source/inkcpp_editor/Private/InkAssetFactory.cpp index 73b39e02..c5c01212 100644 --- a/unreal/inkcpp/Source/inkcpp_editor/Private/InkAssetFactory.cpp +++ b/unreal/inkcpp/Source/inkcpp_editor/Private/InkAssetFactory.cpp @@ -38,7 +38,7 @@ UObject* UInkAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, { std::stringstream output; std::stringstream cmd{}; - static const std::string inklecate_cmd{ INKLECATE_CMD }; + static const std::string inklecate_cmd{inklecate_cmd()}; static const std::string ink_suffix{".ink"}; try { diff --git a/unreal/inkcpp/Source/inkcpp_editor/Private/inklecate_cmd.cpp.in b/unreal/inkcpp/Source/inkcpp_editor/Private/inklecate_cmd.cpp.in index f250c6d2..17c1e433 100644 --- a/unreal/inkcpp/Source/inkcpp_editor/Private/inklecate_cmd.cpp.in +++ b/unreal/inkcpp/Source/inkcpp_editor/Private/inklecate_cmd.cpp.in @@ -1,3 +1,17 @@ -#ifndef INKLECATE_CMD -#define INKLECATE_CMD "@INKLECATE_CMD@" -#endif \ No newline at end of file +#pragma once + +#include "Kismet/GameplayStatics.h" + +inline FString inklecate_cmd() { + FString platform = UGameplayStatics::GetPlatformName(); + if (platform == TEST("Windows")) { + return FString(TEXT("@INKLECATE_CMD_WIN@")); + } else if (platform == TEST("Mac")) { + return FString(TEXT("@INKLECATE_CMD_MAC@")); + } else if (platform == TEST("Linux")) { + retrun FString(TEXT("@INKLECATE_CMD_LINUX@")); + } else { + UE_LOG(InkCpp, Warning, TEXT("Platform: '") + platform + TEXT("' is not know. For compiling a .ink file a system wide 'inklecate' executable wil be tried to use.")); + return FString(TEXT("inklecate")); + } +}