From f791b3602321513ecbe827d68b74a49286419f7e Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Sat, 8 Nov 2025 00:27:07 +1300 Subject: [PATCH 01/15] Small config changes to build without STL and Unreal --- inkcpp/include/runner.h | 2 ++ inkcpp/include/story_ptr.h | 7 +++---- inkcpp/include/traits.h | 21 +++++++++++++++++++-- inkcpp/list_table.h | 4 ++-- inkcpp/runner_impl.cpp | 8 ++++++-- inkcpp/runner_impl.h | 2 ++ shared/public/config.h | 14 ++++++++++++++ shared/public/system.h | 18 +++++++++++++++++- 8 files changed, 65 insertions(+), 11 deletions(-) diff --git a/inkcpp/include/runner.h b/inkcpp/include/runner.h index 68400d49..64bbb780 100644 --- a/inkcpp/include/runner.h +++ b/inkcpp/include/runner.h @@ -95,6 +95,7 @@ class runner_interface */ virtual snapshot* create_snapshot() const = 0; +#ifndef INK_ENABLE_CSTD /** * Execute the next line of the script. * @@ -114,6 +115,7 @@ class runner_interface * @return string with the next line of output */ virtual line_type getall() = 0; +#endif #ifdef INK_ENABLE_STL /** diff --git a/inkcpp/include/story_ptr.h b/inkcpp/include/story_ptr.h index ad4b4290..44c12c98 100644 --- a/inkcpp/include/story_ptr.h +++ b/inkcpp/include/story_ptr.h @@ -143,11 +143,10 @@ class story_ptr : public internal::story_ptr_base story_ptr cast() { // if cast fails, return null -#ifdef INK_ENABLE_UNREAL - // Unreal disables RTTI - U* casted = reinterpret_cast(_ptr); -#else +#ifdef INK_ENABLE_RTTI U* casted = dynamic_cast(_ptr); +#else + U* casted = reinterpret_cast(_ptr); #endif if (casted == nullptr) return nullptr; diff --git a/inkcpp/include/traits.h b/inkcpp/include/traits.h index e119a640..bb0de3b2 100644 --- a/inkcpp/include/traits.h +++ b/inkcpp/include/traits.h @@ -101,9 +101,26 @@ struct remove_cv { typedef T type; }; +template +struct remove_reference { + typedef T type; +}; + +template +struct remove_reference { + typedef T type; +}; + +template +struct remove_reference +{ + typedef T type; +}; + template -struct remove_cvref { - typedef std::remove_cv_t> type; +struct remove_cvref +{ + typedef remove_cv>::type; }; // == string testing (from me) == diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index 7abcca39..ded1993d 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -264,13 +264,13 @@ class list_table : public snapshot_interface const data_t* getPtr(int eid) const { return _data.begin() - + static_cast(_entrySize) * static_cast(eid); + + static_cast(_entrySize) * static_cast(eid); } data_t* getPtr(int eid) { return _data.begin() - + static_cast(_entrySize) * static_cast(eid); + + static_cast(_entrySize) * static_cast(eid); } int numFlags() const diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 05ab406b..1ab2c914 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -505,6 +505,8 @@ runner_impl::~runner_impl() } } +#ifndef INK_ENABLE_CSTD + runner_impl::line_type runner_impl::getline() { // Advance interpreter one line and write to output @@ -545,6 +547,8 @@ runner_impl::line_type runner_impl::getall() return result; } +#endif + #ifdef INK_ENABLE_STL void runner_impl::getline(std::ostream& out) { out << getline(); } @@ -864,7 +868,7 @@ bool runner_impl::line_step() void runner_impl::step() { -#ifndef INK_ENABLE_UNREAL +#ifdef INK_ENABLE_EH try #endif { @@ -1496,7 +1500,7 @@ void runner_impl::step() } #endif } -#ifndef INK_ENABLE_UNREAL +#ifdef INK_ENABLE_EH catch (...) { // Reset our whole state as it's probably corrupt reset(); diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 26f4e44a..8e119929 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -132,11 +132,13 @@ class runner_impl // move to path virtual bool move_to(hash_t path) override; +#ifndef INK_ENABLE_CSTD // Gets a single line of output virtual line_type getline() override; // get all into string virtual line_type getall() override; +#endif #ifdef INK_ENABLE_STL // Reads a line into a std::ostream diff --git a/shared/public/config.h b/shared/public/config.h index 4bb0877f..d0c164e1 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -7,7 +7,13 @@ #pragma once #ifdef INKCPP_API +#ifndef INKCPP_NO_UNREAL # define INK_ENABLE_UNREAL +# define INKCPP_NO_EH +# define INKCPP_NO_RTTI +#else +# define INK_ENABLE_CSTD +#endif #elif INKCPP_BUILD_CLIB # define INK_ENABLE_CSTD #else @@ -15,6 +21,14 @@ # define INK_ENABLE_CSTD #endif +#ifndef INKCPP_NO_EH +#define INK_ENABLE_EH +#endif + +#ifndef INKCPP_NO_RTTI +#define INK_ENABLE_RTTI +#endif + // Only turn on if you have json.hpp and you want to use it with the compiler // #define INK_EXPOSE_JSON diff --git a/shared/public/system.h b/shared/public/system.h index 5047a971..23f5fffd 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -13,7 +13,6 @@ # include "Misc/CString.h" # include "HAL/UnrealMemory.h" # include "Hash/CityHash.h" - #endif #ifdef INK_ENABLE_STL # include @@ -24,6 +23,9 @@ # include # include #endif +#ifdef INK_ENABLE_CSTD +# include +#endif // Platform specific defines // @@ -46,6 +48,20 @@ namespace ink */ typedef unsigned int uint32_t; +#ifndef INK_ENABLE_STL + +/** Additional signed integer types */ +typedef int int32_t; +typedef short int16_t; + +/** Additional unsigned integer types */ +typedef unsigned long long uint64_t; +typedef unsigned short uint16_t; +typedef long long ptrdiff_t; +#else +typedef std::ptrdiff_t ptrdiff_t; +#endif // ndef INK_ENABLE_STL + /** Name hash (used for temporary variables) */ typedef uint32_t hash_t; From 8645d012c07f6739e069f87482d8d295e01c6835 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Sat, 8 Nov 2025 20:09:50 +1300 Subject: [PATCH 02/15] Fix optional::emplace(), which wasn't marking the newly-constructed value as present. Replace unchecked access to _value with checked access for the various operators. --- shared/public/system.h | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/shared/public/system.h b/shared/public/system.h index 23f5fffd..89e8e318 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -285,13 +285,13 @@ class optional { } - const T& operator*() const { return _value; } + const T& operator*() const { return value(); } - T& operator*() { return _value; } + T& operator*() { return value(); } - const T* operator->() const { return &_value; } + const T* operator->() const { return &value(); } - T* operator->() { return &_value; } + T* operator->() { return &value(); } constexpr bool has_value() const { return _has_value; } @@ -318,8 +318,12 @@ class optional template T& emplace(Args... args) { - _value.~T(); - return *(new (&_value) T(args...)); + if (_has_value) + _value.~T(); + + new (&_value) T(args...); + _has_value = true; + return _value; } private: From 00509f57c408d2bbb686e1abaec19fe4f01d3c3a Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:43:16 +1300 Subject: [PATCH 03/15] Verify sized types And define ptrdiff_t using decltype so it works for 32b and 64b arch. --- inkcpp/include/runner.h | 18 ++++++++---------- shared/public/system.h | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/inkcpp/include/runner.h b/inkcpp/include/runner.h index 64bbb780..d765ac24 100644 --- a/inkcpp/include/runner.h +++ b/inkcpp/include/runner.h @@ -76,6 +76,13 @@ class runner_interface */ virtual bool can_continue() const = 0; + /** + * @brief creates a snapshot containing the runner, globals and all other runners connected to the + * globals. + * @sa story::new_runner_from_snapshot, story::new_globals_from_snapshot + */ + virtual snapshot* create_snapshot() const = 0; + #ifdef INK_ENABLE_CSTD /** * Continue execution until the next newline, then allocate a c-style @@ -86,16 +93,7 @@ class runner_interface * @return allocated c-style string with the output of a single line of execution */ virtual const char* getline_alloc() = 0; -#endif - - /** - * @brief creates a snapshot containing the runner, globals and all other runners connected to the - * globals. - * @sa story::new_runner_from_snapshot, story::new_globals_from_snapshot - */ - virtual snapshot* create_snapshot() const = 0; - -#ifndef INK_ENABLE_CSTD +#else /** * Execute the next line of the script. * diff --git a/shared/public/system.h b/shared/public/system.h index 89e8e318..7a01a8c7 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -57,9 +57,6 @@ typedef short int16_t; /** Additional unsigned integer types */ typedef unsigned long long uint64_t; typedef unsigned short uint16_t; -typedef long long ptrdiff_t; -#else -typedef std::ptrdiff_t ptrdiff_t; #endif // ndef INK_ENABLE_STL /** Name hash (used for temporary variables) */ @@ -81,6 +78,18 @@ hash_t hash_string(const char* string); /** Byte type */ typedef unsigned char byte_t; +/** Ptr difference type */ +typedef decltype(static_cast(nullptr) - static_cast(nullptr)) ptrdiff_t; + +/** Verify sizes */ +static_assert(sizeof(byte_t) == 1); +static_assert(sizeof(uint16_t) == 2); +static_assert(sizeof(int16_t) == 2); +static_assert(sizeof(uint32_t) == 4); +static_assert(sizeof(int32_t) == 4); +static_assert(sizeof(uint64_t) == 8); +static_assert(sizeof(ptrdiff_t) == sizeof(void*)); + /** Used to identify an offset in a data table (like a string in the string table) */ typedef uint32_t offset_t; From 6a44a0a36ec8a859b52862043594609da8c7b5a7 Mon Sep 17 00:00:00 2001 From: Will Vale <66674079+willvale@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:02:59 +1300 Subject: [PATCH 04/15] Allow manual workflow dispatch. --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c7b7cae..a6b6b6f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: branches: - master pull_request: + workflow_dispatch: env: BUILD_TYPE: release From b4f47e08b9300f0e607dcb4b70e7b473aaf7bc14 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:28:17 +1300 Subject: [PATCH 05/15] Fix traits for ubuntu? --- inkcpp/include/traits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inkcpp/include/traits.h b/inkcpp/include/traits.h index bb0de3b2..38f2afee 100644 --- a/inkcpp/include/traits.h +++ b/inkcpp/include/traits.h @@ -120,7 +120,7 @@ struct remove_reference template struct remove_cvref { - typedef remove_cv>::type; + using typename remove_cv>::type; }; // == string testing (from me) == From 4c56a70baa53d226b16e1e8f100563e8042416a2 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:42:05 +1300 Subject: [PATCH 06/15] Fix definitions for line_type-based APIs. This might be better with a high-level flag as well? --- inkcpp/include/runner.h | 4 +++- inkcpp/runner_impl.cpp | 2 +- inkcpp/runner_impl.h | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/inkcpp/include/runner.h b/inkcpp/include/runner.h index d765ac24..f9a647b4 100644 --- a/inkcpp/include/runner.h +++ b/inkcpp/include/runner.h @@ -93,7 +93,9 @@ class runner_interface * @return allocated c-style string with the output of a single line of execution */ virtual const char* getline_alloc() = 0; -#else +#endif + +#if defined(INK_ENABLE_STL) || defined(INK_ENABLE_UNREAL) /** * Execute the next line of the script. * diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 1ab2c914..05be3fd7 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -505,7 +505,7 @@ runner_impl::~runner_impl() } } -#ifndef INK_ENABLE_CSTD +#if defined(INK_ENABLE_STL) || defined(INK_ENABLE_UNREAL) runner_impl::line_type runner_impl::getline() { diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 8e119929..7cf61d32 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -132,7 +132,7 @@ class runner_impl // move to path virtual bool move_to(hash_t path) override; -#ifndef INK_ENABLE_CSTD +#if defined(INK_ENABLE_STL) || defined(INK_ENABLE_UNREAL) // Gets a single line of output virtual line_type getline() override; From 23ffa041ec758bb9234961b81ad86dec337e4184 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:05:04 +1300 Subject: [PATCH 07/15] For real this time (Template wasn't being instantiated in my build environment, d'oh) --- inkcpp/include/traits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inkcpp/include/traits.h b/inkcpp/include/traits.h index 38f2afee..8f6af95a 100644 --- a/inkcpp/include/traits.h +++ b/inkcpp/include/traits.h @@ -120,7 +120,7 @@ struct remove_reference template struct remove_cvref { - using typename remove_cv>::type; + typedef typename remove_cv::type>::type type; }; // == string testing (from me) == From 3f7f6f2a825220ad88bdcb8da60ccce4b3f0443d Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 28 Nov 2025 11:54:47 +0100 Subject: [PATCH 08/15] build(Options) Add NO_EH and NO_RTTI as cmake options. --- .cmake-format.json | 6 + .editorconfig | 5 +- .pre-commit-config.yaml | 2 +- CMakeLists.txt | 289 +++++++++++++++++++++++----------------- inkcpp/include/traits.h | 46 +++---- inkcpp/list_table.h | 6 +- shared/public/config.h | 9 +- shared/public/system.h | 4 +- 8 files changed, 210 insertions(+), 157 deletions(-) create mode 100644 .cmake-format.json diff --git a/.cmake-format.json b/.cmake-format.json new file mode 100644 index 00000000..f0c505c3 --- /dev/null +++ b/.cmake-format.json @@ -0,0 +1,6 @@ +{ + "format": { + "line_width": 100, + "use_tabchars": true + } +} diff --git a/.editorconfig b/.editorconfig index bc97b69a..218304f5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,10 @@ root = true [*] guidelines = 100 indent_style = tab -indent_size = 4 +indent_size = 2 # Unix-style newlines end_of_line = lf insert_final_newline = true + +[CMakeLists.txt] +max_line_length = 100 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 38ab6a8d..55f7478b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: text-unicode-replacement-char - repo: https://github.com/cheshirekow/cmake-format-precommit - rev: v0.6.10 + rev: v0.6.13 hooks: - id: cmake-format - id: cmake-lint diff --git a/CMakeLists.txt b/CMakeLists.txt index 490af3e7..cebfd9f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,21 +4,21 @@ if(${CMAKE_VERSION} VERSION_GREATER "3.24.0") cmake_policy(SET CMP0135 NEW) endif() include(FetchContent) -FetchContent_Declare(inklecate_mac -URL https://github.com/inkle/ink/releases/download/v1.1.1/inklecate_mac.zip -URL_HASH SHA256=c516402bca5fa249a7712e62591b048b137eba3098c53f9fb85a4253f9b9e2c0 -SOURCE_DIR "${CMAKE_BINARY_DIR}/inklecate/mac" -) -FetchContent_Declare(inklecate_windows -URL https://github.com/inkle/ink/releases/download/v1.1.1/inklecate_windows.zip -URL_HASH SHA256=6f317cb4c59bf1b31c6dd61e80c6a2287a1d8c241a703f0586f736ae00871aab -SOURCE_DIR "${CMAKE_BINARY_DIR}/inklecate/windows" -) -FetchContent_Declare(inklecate_linux -URL https://github.com/inkle/ink/releases/download/v1.1.1/inklecate_linux.zip -URL_HASH SHA256=26f4e188e02536d6e99e73e71d9b13e2c2144187f1368a87e82fd5066176cff8 -SOURCE_DIR "${CMAKE_BINARY_DIR}/inklecate/linux" -) +FetchContent_Declare( + inklecate_mac + URL https://github.com/inkle/ink/releases/download/v1.1.1/inklecate_mac.zip + URL_HASH SHA256=c516402bca5fa249a7712e62591b048b137eba3098c53f9fb85a4253f9b9e2c0 + SOURCE_DIR "${CMAKE_BINARY_DIR}/inklecate/mac") +FetchContent_Declare( + inklecate_windows + URL https://github.com/inkle/ink/releases/download/v1.1.1/inklecate_windows.zip + URL_HASH SHA256=6f317cb4c59bf1b31c6dd61e80c6a2287a1d8c241a703f0586f736ae00871aab + SOURCE_DIR "${CMAKE_BINARY_DIR}/inklecate/windows") +FetchContent_Declare( + inklecate_linux + URL https://github.com/inkle/ink/releases/download/v1.1.1/inklecate_linux.zip + URL_HASH SHA256=26f4e188e02536d6e99e73e71d9b13e2c2144187f1368a87e82fd5066176cff8 + SOURCE_DIR "${CMAKE_BINARY_DIR}/inklecate/linux") set(FETCHCONTENT_QUIET OFF) mark_as_advanced(FETCHCONTENT_QUIET) set(CMAKE_TLS_VERIFY true) @@ -42,46 +42,67 @@ enable_testing() # Project setup project(inkcpp VERSION 0.1.9) -SET(CMAKE_CXX_STANDARD 17) -SET(CMAKE_CXX_STANDARD_REQUIRED ON) -SET(CMAKE_INSTALL_LIBRARY_DIR lib) -SET(CMAKE_INSTALL_INCLUDE_DIR include) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_INSTALL_LIBRARY_DIR lib) +set(CMAKE_INSTALL_INCLUDE_DIR include) # Add subdirectories include(CMakeDependentOption) option(INKCPP_PY "Build python bindings" OFF) -cmake_dependent_option(WHEEL_BUILD "Set for build wheel python lib. (see setup.py for ussage)" OFF "INKCPP_PY" OFF) +cmake_dependent_option(WHEEL_BUILD "Set for build wheel python lib. (see setup.py for ussage)" OFF + "INKCPP_PY" OFF) option(INKCPP_C "Build c library" OFF) -option(INKCPP_TEST "Build inkcpp tests (requires: inklecate in path / env: INKLECATE set / INKCPP_INKLECATE=OS or ALL)" OFF) -set(INKCPP_INKLECATE "NONE" CACHE STRING "If inklecate should be downloaded automatically from the official release page. NONE -> No, OS -> Yes, but only for the current OS, ALL -> Yes, for all availible OSs") +option(INKCPP_TEST "Build inkcpp tests (requires: inklecate in path / env: INKLECATE set \ + / INKCPP_INKLECATE=OS or ALL)" OFF) +set(INKCPP_INKLECATE + "NONE" + CACHE STRING "If inklecate should be downloaded automatically from the official release page. \ + NONE -> No, OS -> Yes, but only for the current OS, ALL -> Yes, for all availible OSs") set_property(CACHE INKCPP_INKLECATE PROPERTY STRINGS "NONE" "OS" "ALL") +option(INKCPP_NO_EH "Disable try/catch in runtime. Used to build without error handling." OFF) +option(INKCPP_NO_RTTI + "Disable real time type information depended code. Used to build without RTTI." OFF) + +if(INKCPP_NO_EH) + add_definitions(-DINKCPP_NO_EH) +endif() +if(INKCPP_NO_RTTI) + add_definitions(-DINKCPP_NO_RTTI) +endif() string(TOUPPER "${INKCPP_INKLECATE}" inkcpp_inklecate_upper) -if (inkcpp_inklecate_upper STREQUAL "ALL") +if(inkcpp_inklecate_upper STREQUAL "ALL") FetchContent_MakeAvailable(inklecate_windows inklecate_mac inklecate_linux) -elseif(inkcpp_inklecate_upper STREQUAL "OS") +elseif(inkcpp_inklecate_upper STREQUAL "OS") if(UNIX AND NOT APPLE) FetchContent_MakeAvailable(inklecate_linux) elseif(APPLE) FetchContent_MakeAvailable(inklecate_mac) - elseif(MSYS OR MINGW OR WIN32 OR CYGWIN) + elseif( + MSYS + OR MINGW + OR WIN32 + OR CYGWIN) FetchContent_MakeAvailable(inklecate_windows) else() - message(FATAL_ERROR "Unable to identify OS for option INKCPP_INKLECATE=OS, please consider using NONE or ALL.") + message( + FATAL_ERROR + "Unable to identify OS for option INKCPP_INKLECATE=OS, please consider using NONE or ALL.") endif() endif() -if (INKCPP_PY) +if(INKCPP_PY) add_compile_options(-fPIC) add_subdirectory(inkcpp_python) endif(INKCPP_PY) add_subdirectory(shared) add_subdirectory(inkcpp) add_subdirectory(inkcpp_compiler) -if (INKCPP_C) +if(INKCPP_C) add_subdirectory(inkcpp_c) endif(INKCPP_C) -if (NOT WHEEL_BUILD) +if(NOT WHEEL_BUILD) add_subdirectory(inkcpp_cl) if(INKCPP_TEST) add_subdirectory(inkcpp_test) @@ -89,35 +110,40 @@ if (NOT WHEEL_BUILD) add_subdirectory(unreal) endif(NOT WHEEL_BUILD) - -install(TARGETS inkcpp inkcpp_shared inkcpp_compiler +install( + TARGETS inkcpp inkcpp_shared inkcpp_compiler EXPORT inkcppTarget ARCHIVE DESTINATION "lib/ink" - COMPONENT lib EXCLUDE_FROM_ALL - PUBLIC_HEADER DESTINATION "include/ink" - COMPONENT lib EXCLUDE_FROM_ALL -) + COMPONENT lib + EXCLUDE_FROM_ALL + PUBLIC_HEADER + DESTINATION "include/ink" + COMPONENT lib + EXCLUDE_FROM_ALL) -install(EXPORT inkcppTarget - FILE inkcppTargets.cmake DESTINATION "lib/cmake/inkcpp" - COMPONENT lib EXCLUDE_FROM_ALL) +install( + EXPORT inkcppTarget + FILE inkcppTargets.cmake + DESTINATION "lib/cmake/inkcpp" + COMPONENT lib + EXCLUDE_FROM_ALL) include(CMakePackageConfigHelpers) -configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in - "${CMAKE_CURRENT_BINARY_DIR}/inkcppConfig.cmake" +configure_package_config_file( + ${CMAKE_CURRENT_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) + 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 lib EXCLUDE_FROM_ALL) -export(EXPORT inkcppTarget - FILE "${CMAKE_CURRENT_BINARY_DIR}/inkcppTargets.cmake") +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/inkcppConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/inkcppConfigVersion.cmake + DESTINATION lib/cmake/inkcpp + COMPONENT lib + EXCLUDE_FROM_ALL) +export(EXPORT inkcppTarget FILE "${CMAKE_CURRENT_BINARY_DIR}/inkcppTargets.cmake") # include(InstallRequiredSystemLibraries) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") @@ -130,78 +156,103 @@ set(CPACK_COMPONENTS_GROUPING IGNORE) include(CPack) if(NOT WHEEL_BUILD) -find_package(Doxygen) -if (DOXYGEN_FOUND) - option(INKCPP_DOC_BlueprintUE "Building doxygen documentation with BlueprintUE visualisation for unreal blueprints. (Requires node js)" ON) - if (INKCPP_DOC_BlueprintUE) - set(NODEJS_HINT "None" CACHE PATH "Directory of NodeJS executable to use for generating BlueprintUE visualisation.") - if (IS_DIRECTORY "${NODEJS_HINT}") - find_program(NODEJS_PATH node HINTS ${NODEJS_HINT} DOC "NodeJS executable to build BlueprintUE visualisation in documentation.") - else() - find_program(NODEJS_PATH node DOC "NodeJS executable to build BlueprintUE visualisation in documentation.") - endif (IS_DIRECTORY "${NODEJS_HINT}") - if ("${NODEJS_PATH}" STREQUAL "NODEJS-NOTFOUND") - message(SEND_ERROR "NodeJS is required to build BlueprintUE visualisation. But it was not found. Install NodeJS set NODEJS_HINT to a directory containing the executable. Or disable building the visualisation with setting INKCPP_DOC_BlueprintUE=OFF") - endif("${NODEJS_PATH}" STREQUAL "NODEJS-NOTFOUND") - endif(INKCPP_DOC_BlueprintUE) + find_package(Doxygen) + if(DOXYGEN_FOUND) + option(INKCPP_DOC_BlueprintUE + "Building doxygen documentation with BlueprintUE visualisation for unreal blueprints. \ + (Requires node js)" ON) + if(INKCPP_DOC_BlueprintUE) + set(NODEJS_HINT + "None" + CACHE PATH + "Directory of NodeJS executable to use for generating BlueprintUE visualisation.") + if(IS_DIRECTORY "${NODEJS_HINT}") + find_program( + NODEJS_PATH node + HINTS ${NODEJS_HINT} + DOC "NodeJS executable to build BlueprintUE visualisation in documentation.") + else() + find_program(NODEJS_PATH node + DOC "NodeJS executable to build BlueprintUE visualisation in documentation.") + endif(IS_DIRECTORY "${NODEJS_HINT}") + if("${NODEJS_PATH}" STREQUAL "NODEJS-NOTFOUND") + message( + SEND_ERROR + "NodeJS is required to build BlueprintUE visualisation. \ + But it was not found. Install NodeJS set NODEJS_HINT \ + to a directory containing the executable. \ + Or disable building the visualisation with setting INKCPP_DOC_BlueprintUE=OFF") + endif("${NODEJS_PATH}" STREQUAL "NODEJS-NOTFOUND") + endif(INKCPP_DOC_BlueprintUE) - set(DOXYGEN_PROJECT_NAME ${PROJECT_NAME}) - # enable if update to cmake version 3.28 - # doxygen_add_docs(doc WORKING_DIR ${PROJECT_SOURCE_DIR} CONFIG_FILE "${PROJECT_SOURCE_DIR}/Doxyfile" COMMENT "Generate docs") - set(INPUT_FILTER "") + set(DOXYGEN_PROJECT_NAME ${PROJECT_NAME}) + # enable if update to cmake version 3.28 doxygen_add_docs(doc WORKING_DIR ${PROJECT_SOURCE_DIR} + # CONFIG_FILE "${PROJECT_SOURCE_DIR}/Doxyfile" COMMENT "Generate docs") + set(INPUT_FILTER "") - if (INKCPP_DOC_BlueprintUE AND NOT "${NODEJS_PATH}" STREQUAL "NODEJS-NOTFOUND") - # TODO: make as dependecy - file(COPY "${PROJECT_SOURCE_DIR}/unreal/blueprint_filter.js" DESTINATION ${PROJECT_BINARY_DIR}) - # file(DOWNLOAD - # "https://raw.githubusercontent.com/blueprintue/blueprintue-self-hosted-edition/main/www/bue-render/render.css" - # "${PROJECT_BINARY_DIR}/render.css" - # EXPECTED_HASH SHA256=875364e36f8aa5d6c1d41d58043f13b48a499b5c969e8daef35bd29bbf7c6e8d) - file(COPY "${PROJECT_SOURCE_DIR}/unreal/render.css" DESTINATION ${PROJECT_BINARY_DIR}) - file(APPEND "${PROJECT_BINARY_DIR}/render.css" ".bue-render .icon { background-color: unset; }") - file(READ "${PROJECT_SOURCE_DIR}/Doxyfile" DOXYFILE) - string(REPLACE "FILTER_PATTERNS =" "FILTER_PATTERNS = \"*/unreal/*=node ${PROJECT_BINARY_DIR}/blueprint_filter.js\"" DOXYFILE2 ${DOXYFILE}) - string(REPLACE "HTML_EXTRA_STYLESHEET =" "HTML_EXTRA_STYLESHEET = ${PROJECT_BINARY_DIR}/render.css " DOXYFILE ${DOXYFILE2}) - file(WRITE "${PROJECT_BINARY_DIR}/Doxyfile" ${DOXYFILE}) - else () - configure_file( - "${PROJECT_SOURCE_DIR}/Doxyfile" - "${PROJECT_BINARY_DIR}/Doxyfile" - COPYONLY) - endif() -# "Build Doxygen documentation in ${PROJECT_SOURCE_DIR}/Documentation." "To view them you can for example use" "'python -m http.server -d \"${PROJECT_SOURCE_DIR}/Documentation\" 8080'" "'explorer http://localhost:8080/html'" - add_custom_target(doc - ${DOXYGEN_EXECUTABLE} "${PROJECT_BINARY_DIR}/Doxyfile" - WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" - COMMAND ${CMAKE_COMMAND} -E echo - COMMENT "Generate DoxygenDocumentation") - add_custom_command(TARGET doc POST_BUILD - COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --cyan - "Build Doxygen documentation in ${PROJECT_SOURCE_DIR}/Documentation." - "To view them you can for example use" - "\" python -m http.server -d \\\"${PROJECT_SOURCE_DIR}/Documentation\\\" 8080\"" - " explorer http://localhost:8080/html") + if(INKCPP_DOC_BlueprintUE AND NOT "${NODEJS_PATH}" STREQUAL "NODEJS-NOTFOUND") + # TODO: make as dependecy + file(COPY "${PROJECT_SOURCE_DIR}/unreal/blueprint_filter.js" + DESTINATION ${PROJECT_BINARY_DIR}) + # file(DOWNLOAD "https://raw.githubusercontent.com/blueprintue/ \ + # blueprintue-self-hosted-edition/main/www/bue-render/render.css" + # "${PROJECT_BINARY_DIR}/render.css" EXPECTED_HASH + # SHA256=875364e36f8aa5d6c1d41d58043f13b48a499b5c969e8daef35bd29bbf7c6e8d) + file(COPY "${PROJECT_SOURCE_DIR}/unreal/render.css" DESTINATION ${PROJECT_BINARY_DIR}) + file(APPEND "${PROJECT_BINARY_DIR}/render.css" + ".bue-render .icon { background-color: unset; }") + file(READ "${PROJECT_SOURCE_DIR}/Doxyfile" DOXYFILE) + string( + REPLACE + "FILTER_PATTERNS =" + "FILTER_PATTERNS = \"*/unreal/*=node ${PROJECT_BINARY_DIR}/blueprint_filter.js\"" + DOXYFILE2 ${DOXYFILE}) + string(REPLACE "HTML_EXTRA_STYLESHEET =" + "HTML_EXTRA_STYLESHEET = ${PROJECT_BINARY_DIR}/render.css " DOXYFILE + ${DOXYFILE2}) + file(WRITE "${PROJECT_BINARY_DIR}/Doxyfile" ${DOXYFILE}) + else() + configure_file("${PROJECT_SOURCE_DIR}/Doxyfile" "${PROJECT_BINARY_DIR}/Doxyfile" COPYONLY) + endif() + # "Build Doxygen documentation in ${PROJECT_SOURCE_DIR}/Documentation." "To view them you can + # for example use" "'python -m http.server -d \"${PROJECT_SOURCE_DIR}/Documentation\" 8080'" + # "'explorer http://localhost:8080/html'" + add_custom_target( + doc + ${DOXYGEN_EXECUTABLE} "${PROJECT_BINARY_DIR}/Doxyfile" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + COMMAND ${CMAKE_COMMAND} -E echo + COMMENT "Generate DoxygenDocumentation") + add_custom_command( + TARGET doc + POST_BUILD + COMMENT "" + COMMAND + ${CMAKE_COMMAND} -E cmake_echo_color --cyan + "Build Doxygen documentation in ${PROJECT_SOURCE_DIR}/Documentation." + "To view them you can for example use" "\" python -m http.server -d \\\"\ + ${PROJECT_SOURCE_DIR}/Documentation\\\" 8080\"" " explorer http://localhost:8080/html") - set(PY_HTML "${PROJECT_SOURCE_DIR}/Documentation/inkcpp_py.html") - if (INKCPP_PY) - find_package( - Python3 - REQUIRED - COMPONENTS Interpreter - ) - add_custom_target(inkcpp_py_doc - python -m pybind11_stubgen -o . inkcpp_py - COMMAND python -m pdoc -d google -o . inkcpp_py.pyi - COMMAND ${CMAKE_COMMAND} -E copy "./inkcpp_py.html" ${PY_HTML} - DEPENDS inkcpp_py - WORKING_DIRECTORY $ - COMMENT "Generates simple python documentation") - add_dependencies(doc inkcpp_py_doc) - else() - message(WARNING "The python target is disabled, therfore no python documentation will be build. Set INKCPP_PY to change this") - file(WRITE ${PY_HTML} "

Python Documenattion was not build!

") - endif(INKCPP_PY) -else(DOXYGEN_FOUND) - message("Doxygen needed to generate documntation!") -endif(DOXYGEN_FOUND) + set(PY_HTML "${PROJECT_SOURCE_DIR}/Documentation/inkcpp_py.html") + if(INKCPP_PY) + find_package(Python3 REQUIRED COMPONENTS Interpreter) + add_custom_target( + inkcpp_py_doc + python -m pybind11_stubgen -o . inkcpp_py + COMMAND python -m pdoc -d google -o . inkcpp_py.pyi + COMMAND ${CMAKE_COMMAND} -E copy "./inkcpp_py.html" ${PY_HTML} + DEPENDS inkcpp_py + WORKING_DIRECTORY $ + COMMENT "Generates simple python documentation") + add_dependencies(doc inkcpp_py_doc) + else() + message( + WARNING "The python target is disabled, therfore no python documentation will be build. \ + Set INKCPP_PY to change this") + file(WRITE ${PY_HTML} + "

Python Documenattion was not build!

") + endif(INKCPP_PY) + else(DOXYGEN_FOUND) + message("Doxygen needed to generate documntation!") + endif(DOXYGEN_FOUND) endif(NOT WHEEL_BUILD) diff --git a/inkcpp/include/traits.h b/inkcpp/include/traits.h index 8f6af95a..43f95bb9 100644 --- a/inkcpp/include/traits.h +++ b/inkcpp/include/traits.h @@ -101,25 +101,23 @@ struct remove_cv { typedef T type; }; -template +template struct remove_reference { typedef T type; }; -template +template struct remove_reference { - typedef T type; + typedef T type; }; -template -struct remove_reference -{ - typedef T type; +template +struct remove_reference { + typedef T type; }; template -struct remove_cvref -{ +struct remove_cvref { typedef typename remove_cv::type>::type type; }; @@ -157,21 +155,21 @@ template struct string_handler : string_handler { }; -#define MARK_AS_STRING(TYPE, LEN, SRC) \ - template<> \ - struct is_string : constant { \ - }; \ - template<> \ - struct string_handler { \ - static size_t length(const TYPE& x) { return static_cast(LEN); } \ - static void src_copy(const TYPE& x, char* output) \ - { \ - [&output](const char* src) { \ - while (*src != '\0') \ - *(output++) = *(src++); \ - *output = 0; \ - }(SRC); \ - } \ +#define MARK_AS_STRING(TYPE, LEN, SRC) \ + template<> \ + struct is_string : constant { \ + }; \ + template<> \ + struct string_handler { \ + static size_t length(const TYPE& x) { return static_cast(LEN); } \ + static void src_copy(const TYPE& x, char* output) \ + { \ + [&output](const char* src) { \ + while (*src != '\0') \ + *(output++) = *(src++); \ + *output = 0; \ + }(SRC); \ + } \ } inline size_t c_str_len(const char* c) diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index 16b12ee9..1740d9b8 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -263,14 +263,12 @@ class list_table : public snapshot_interface const data_t* getPtr(int eid) const { - return _data.begin() - + static_cast(_entrySize) * static_cast(eid); + return _data.begin() + static_cast(_entrySize) * static_cast(eid); } data_t* getPtr(int eid) { - return _data.begin() - + static_cast(_entrySize) * static_cast(eid); + return _data.begin() + static_cast(_entrySize) * static_cast(eid); } int numFlags() const diff --git a/shared/public/config.h b/shared/public/config.h index d0c164e1..e2d14ea4 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -6,14 +6,11 @@ */ #pragma once +// The UE build process will define INKCPP_API #ifdef INKCPP_API -#ifndef INKCPP_NO_UNREAL # define INK_ENABLE_UNREAL # define INKCPP_NO_EH # define INKCPP_NO_RTTI -#else -# define INK_ENABLE_CSTD -#endif #elif INKCPP_BUILD_CLIB # define INK_ENABLE_CSTD #else @@ -22,11 +19,11 @@ #endif #ifndef INKCPP_NO_EH -#define INK_ENABLE_EH +# define INK_ENABLE_EH #endif #ifndef INKCPP_NO_RTTI -#define INK_ENABLE_RTTI +# define INK_ENABLE_RTTI #endif // Only turn on if you have json.hpp and you want to use it with the compiler diff --git a/shared/public/system.h b/shared/public/system.h index 7a01a8c7..25040751 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -51,12 +51,12 @@ typedef unsigned int uint32_t; #ifndef INK_ENABLE_STL /** Additional signed integer types */ -typedef int int32_t; +typedef int int32_t; typedef short int16_t; /** Additional unsigned integer types */ typedef unsigned long long uint64_t; -typedef unsigned short uint16_t; +typedef unsigned short uint16_t; #endif // ndef INK_ENABLE_STL /** Name hash (used for temporary variables) */ From 4cd08e552d53bb1b4c8aeb91f8d3061977a83fe3 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Wed, 17 Dec 2025 23:54:03 +1300 Subject: [PATCH 09/15] Support more parameter types for external functions Support uint32, bool and float in function_base::push/pop Support int32 -> uint32 cast Allow bool output. Fix toStr(char*) for strings longer than 1. Add toStr(bool) --- inkcpp/functional.cpp | 43 +++++++++++++++++++++++++++++++++++-- inkcpp/numeric_operations.h | 1 + inkcpp/output.cpp | 1 + inkcpp/string_utils.h | 11 +++++----- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/inkcpp/functional.cpp b/inkcpp/functional.cpp index 81289436..5a20067a 100644 --- a/inkcpp/functional.cpp +++ b/inkcpp/functional.cpp @@ -9,6 +9,7 @@ #include "value.h" #include "stack.h" #include "string_table.h" +#include "operations.h" #ifdef INK_ENABLE_UNREAL # include "InkVar.h" @@ -28,8 +29,28 @@ template<> int32_t function_base::pop(basic_eval_stack* stack, list_table& lists) { value val = stack->pop(); - inkAssert(val.type() == value_type::int32, "Type mismatch!"); - return val.get(); + return casting::numeric_cast(val); +} + +template<> +uint32_t function_base::pop(basic_eval_stack* stack, list_table& lists) +{ + value val = stack->pop(); + return casting::numeric_cast(val); +} + +template<> +bool function_base::pop(basic_eval_stack* stack, list_table& lists) +{ + value val = stack->pop(); + return casting::numeric_cast(val) != 0; +} + +template<> +float function_base::pop(basic_eval_stack* stack, list_table& lists) +{ + value val = stack->pop(); + return casting::numeric_cast(val); } template<> @@ -46,6 +67,24 @@ void function_base::push(basic_eval_stack* stack, const int32_t& v) stack->push(value{}.set(v)); } +template<> +void function_base::push(basic_eval_stack* stack, const uint32_t& v) +{ + stack->push(value{}.set(v)); +} + +template<> +void function_base::push(basic_eval_stack* stack, const float& v) +{ + stack->push(value{}.set(v)); +} + +template<> +void function_base::push(basic_eval_stack* stack, const bool& v) +{ + stack->push(value{}.set(v)); +} + void function_base::push_void(basic_eval_stack* stack) { stack->push(values::null); } void function_base::push_string(basic_eval_stack* stack, const char* dynamic_string) diff --git a/inkcpp/numeric_operations.h b/inkcpp/numeric_operations.h index d2593139..0914f401 100644 --- a/inkcpp/numeric_operations.h +++ b/inkcpp/numeric_operations.h @@ -88,6 +88,7 @@ namespace casting numeric_cast(const value& v) { switch (v.type()) { + case value_type::int32: return v.get(); case value_type::uint32: return v.get(); /// bool value can cast to uint32 case value_type::boolean: return static_cast(v.get()); diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 6162db2c..06614125 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -362,6 +362,7 @@ char* basic_stream::get_alloc(string_table& strings, list_table& lists) case value_type::int32: case value_type::float32: case value_type::uint32: + case value_type::boolean: // Convert to string and advance toStr(ptr, end - ptr, _data[i]); while (*ptr != 0) diff --git a/inkcpp/string_utils.h b/inkcpp/string_utils.h index 5924af95..ad87ad82 100644 --- a/inkcpp/string_utils.h +++ b/inkcpp/string_utils.h @@ -85,18 +85,18 @@ inline int toStr(char* buffer, size_t size, float value) return ec; } -inline int toStr(char* buffer, size_t size, const char* c) +inline int toStr(char* buffer, size_t size, const char* str) { char* ptr = buffer; size_t i = 0; - while (*c && i < size) { - *ptr++ = *c; + while (i < size && str[i]) { + ptr[i] = str[i]; ++i; } if (i >= size) { return EINVAL; } - *ptr = 0; + ptr[i] = 0; return 0; } @@ -113,7 +113,7 @@ inline int toStr(char* buffer, size_t size, const value& v) case value_type::float32: return toStr(buffer, size, v.get()); case value_type::boolean: return toStr(buffer, size, v.get()); case value_type::newline: return toStr(buffer, size, "\n"); - default: inkFail("only support toStr for numeric types"); return -1; + default: inkFail("No toStr implementation for this type"); return -1; } } @@ -148,6 +148,7 @@ inline constexpr size_t value_length(const value& v) case value_type::uint32: return decimal_digits(v.get()); case value_type::float32: return decimal_digits(v.get()); case value_type::string: return c_str_len(v.get()); + case value_type::boolean: return v.get() ? c_str_len("true") : c_str_len("false"); case value_type::newline: return 1; default: inkFail("Can't determine length of this value type"); return -1; } From a6a04349d63063b82ec949053edb64ba7812575c Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:06:43 +1300 Subject: [PATCH 10/15] Added test case for external function parameter types. Added ExternalFunctionTypes.ink/cpp, which test a roundtrip of different types to the host. Updated CMakeLists.txt. --- inkcpp_test/CMakeLists.txt | 1 + inkcpp_test/ExternalFunctionTypes.cpp | 48 +++++++++++++++++++++++ inkcpp_test/ink/ExternalFunctionTypes.ink | 20 ++++++++++ 3 files changed, 69 insertions(+) create mode 100644 inkcpp_test/ExternalFunctionTypes.cpp create mode 100644 inkcpp_test/ink/ExternalFunctionTypes.ink diff --git a/inkcpp_test/CMakeLists.txt b/inkcpp_test/CMakeLists.txt index 2f292f46..1a628535 100644 --- a/inkcpp_test/CMakeLists.txt +++ b/inkcpp_test/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable(inkcpp_test catch.hpp Main.cpp ThirdTierChoiceAfterBrackets.cpp NoEarlyTags.cpp ExternalFunctionsExecuteProperly.cpp + ExternalFunctionTypes.cpp LookaheadSafe.cpp EmptyStringForDivert.cpp MoveTo.cpp diff --git a/inkcpp_test/ExternalFunctionTypes.cpp b/inkcpp_test/ExternalFunctionTypes.cpp new file mode 100644 index 00000000..fae1b726 --- /dev/null +++ b/inkcpp_test/ExternalFunctionTypes.cpp @@ -0,0 +1,48 @@ +#include "catch.hpp" + +#include <../runner_impl.h> +#include +#include +#include +#include + +using namespace ink::runtime; + +SCENARIO("a story with external functions support types", "[story]") +{ + GIVEN("a story with external functions") + { + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ExternalFunctionTypes.bin"); + auto thread = ink->new_runner().cast(); + + std::stringstream debug; + thread->set_debug_enabled(&debug); + + bool b = false; + int i = 0; + unsigned int u = 0; + float f = 0; + std::string s; + + thread->bind("SET_BOOL", [&b](bool o) { b = o; }); + thread->bind("SET_INT", [&i](int o) { i = o; }); + thread->bind("SET_UINT", [&u](unsigned int o) { u = o; }); + thread->bind("SET_FLOAT", [&f](float o) { f = o; }); + thread->bind("SET_STRING",[&s](std::string o) { s = o; }); + + thread->bind("GET_BOOL", [&b]() { return b; }); + thread->bind("GET_INT", [&i]() { return i; }); + thread->bind("GET_UINT", [&u]() { return u; }); + thread->bind("GET_FLOAT", [&f]() { return f; }); + thread->bind("GET_STRING",[&s]() { return s; }); + + WHEN("run thread") + { + THEN("thread has correct line counts") + { + auto line = thread->getline(); + REQUIRE(line == "true 1.5 -5 17 foo\n"); + } + } + } +} diff --git a/inkcpp_test/ink/ExternalFunctionTypes.ink b/inkcpp_test/ink/ExternalFunctionTypes.ink new file mode 100644 index 00000000..004a13df --- /dev/null +++ b/inkcpp_test/ink/ExternalFunctionTypes.ink @@ -0,0 +1,20 @@ +EXTERNAL GET_BOOL() +EXTERNAL GET_FLOAT() +EXTERNAL GET_INT() +EXTERNAL GET_UINT() +EXTERNAL GET_STRING() +EXTERNAL SET_BOOL(b) +EXTERNAL SET_FLOAT(f) +EXTERNAL SET_INT(i) +EXTERNAL SET_UINT(i) +EXTERNAL SET_STRING(s) + +~ SET_BOOL(true) +~ SET_FLOAT(1.5) +~ SET_INT(-5) +~ SET_UINT(17) +~ SET_STRING("foo") + +{GET_BOOL()} {GET_FLOAT()} {GET_INT()} {GET_UINT()} {GET_STRING()} + +-> DONE From 7511e5f56ced7d0806ebcf9f4559f8c0a7b9dffb Mon Sep 17 00:00:00 2001 From: Will Vale <66674079+willvale@users.noreply.github.com> Date: Tue, 16 Dec 2025 00:57:18 +1300 Subject: [PATCH 11/15] Fix restrictive numeric casts/assignments. (#134) Allow type conversion on redefine if the casting matrix says the types have a common base. Allow bool->float conversion. --- inkcpp/numeric_operations.h | 1 + inkcpp/operations.h | 19 ++-- inkcpp/string_operations.h | 101 +++++++++++++--------- inkcpp/string_utils.h | 6 ++ inkcpp_test/Fixes.cpp | 32 +++++++ inkcpp_test/UTF8.cpp | 1 + inkcpp_test/ink/134_restrictive_casts.ink | 51 +++++++++++ 7 files changed, 161 insertions(+), 50 deletions(-) create mode 100644 inkcpp_test/ink/134_restrictive_casts.ink diff --git a/inkcpp/numeric_operations.h b/inkcpp/numeric_operations.h index 489919b4..d2593139 100644 --- a/inkcpp/numeric_operations.h +++ b/inkcpp/numeric_operations.h @@ -116,6 +116,7 @@ namespace casting // int value can cast to float case value_type::int32: return static_cast(v.get()); case value_type::uint32: return static_cast(v.get()); + case value_type::boolean: return v.get() ? 1.0f : 0.0f; default: inkFail("invalid numeric_cast!"); return 0; } } diff --git a/inkcpp/operations.h b/inkcpp/operations.h index 48080beb..5bab700c 100644 --- a/inkcpp/operations.h +++ b/inkcpp/operations.h @@ -86,15 +86,16 @@ template ink::runtime::internal::value ink::runtime::internal::value::redefine(const value& oth, T&... env) const { - if (type() != oth.type() && (type() == value_type::list_flag || type() == value_type::list) - && (oth.type() == value_type::list_flag || oth.type() == value_type::list)) { - /// @todo could break origin - if (oth.type() == value_type::list) { - return value{}.set(oth.get()); - } else { - return value{}.set(oth.get()); - } + if (type() != oth.type()) { + + const value vs[] = {*this, oth}; + inkAssert( + casting::common_base<2>(vs) != value_type::none, + "try to redefine value of other type with no cast available" + ); + + // There's a valid conversion, so redefine as input value. + return oth; } - inkAssert(type() == oth.type(), "try to redefine value of other type"); return redefine(oth, {&env...}); } diff --git a/inkcpp/string_operations.h b/inkcpp/string_operations.h index a0f8ff49..2617871a 100644 --- a/inkcpp/string_operations.h +++ b/inkcpp/string_operations.h @@ -8,60 +8,79 @@ /// defines operations allowed on strings. -namespace ink::runtime::internal { +namespace ink::runtime::internal +{ - namespace casting { - // define valid castings - // when operate on float and string, the result is a string - template<> - struct cast - { static constexpr value_type value = value_type::string; }; - template<> - struct cast - { static constexpr value_type value = value_type::string; }; - template<> - struct cast - { static constexpr value_type value = value_type::string; }; - template<> - struct cast - { static constexpr value_type value = value_type::string; }; - } - - // operation declaration add +namespace casting +{ + // define valid castings + // when operate on float and string, the result is a string template<> - class operation : public operation_base { - public: - using operation_base::operation_base; - void operator()(basic_eval_stack& stack, value* vals); + struct cast { + static constexpr value_type value = value_type::string; }; - // operation declaration equality template<> - class operation : public operation_base { - public: - using operation_base::operation_base; - void operator()(basic_eval_stack& stack, value* vals); + struct cast { + static constexpr value_type value = value_type::string; }; template<> - class operation : public operation_base { - public: - using operation_base::operation_base; - void operator()(basic_eval_stack& stack, value* vals); + struct cast { + static constexpr value_type value = value_type::string; }; template<> - class operation : public operation_base { - public: - using operation_base::operation_base; - void operator()(basic_eval_stack& stack, value* vals); + struct cast { + static constexpr value_type value = value_type::string; }; template<> - class operation : public operation_base { - public: - using operation_base::operation_base; - void operator()(basic_eval_stack& stack, value* vals); + struct cast { + static constexpr value_type value = value_type::string; }; +} // namespace casting + +// operation declaration add +template<> +class operation : public operation_base +{ +public: + using operation_base::operation_base; + void operator()(basic_eval_stack& stack, value* vals); +}; + +// operation declaration equality +template<> +class operation : public operation_base +{ +public: + using operation_base::operation_base; + void operator()(basic_eval_stack& stack, value* vals); +}; + +template<> +class operation : public operation_base +{ +public: + using operation_base::operation_base; + void operator()(basic_eval_stack& stack, value* vals); +}; + +template<> +class operation : public operation_base +{ +public: + using operation_base::operation_base; + void operator()(basic_eval_stack& stack, value* vals); +}; + +template<> +class operation : public operation_base +{ +public: + using operation_base::operation_base; + void operator()(basic_eval_stack& stack, value* vals); +}; -} +} // namespace ink::runtime::internal diff --git a/inkcpp/string_utils.h b/inkcpp/string_utils.h index 00df3111..5924af95 100644 --- a/inkcpp/string_utils.h +++ b/inkcpp/string_utils.h @@ -100,12 +100,18 @@ inline int toStr(char* buffer, size_t size, const char* c) return 0; } +inline int toStr(char* buffer, size_t size, bool b) +{ + return toStr(buffer, size, b ? "true" : "false"); +} + inline int toStr(char* buffer, size_t size, const value& v) { switch (v.type()) { case value_type::int32: return toStr(buffer, size, v.get()); case value_type::uint32: return toStr(buffer, size, v.get()); case value_type::float32: return toStr(buffer, size, v.get()); + case value_type::boolean: return toStr(buffer, size, v.get()); case value_type::newline: return toStr(buffer, size, "\n"); default: inkFail("only support toStr for numeric types"); return -1; } diff --git a/inkcpp_test/Fixes.cpp b/inkcpp_test/Fixes.cpp index 33134408..94d5170a 100644 --- a/inkcpp_test/Fixes.cpp +++ b/inkcpp_test/Fixes.cpp @@ -126,3 +126,35 @@ SCENARIO("missing leading whitespace inside choice-only text and glued text _ #1 } } } + +SCENARIO("Casting during redefinition is too strict _ #134", "[fixes]") +{ + GIVEN("story with problematic text") + { + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "134_restrictive_casts.bin"); + runner thread = ink->new_runner(); + + WHEN("run story") + { + // Initial casts/assignments are allowed. + auto line = thread->getline(); + THEN("expect initial values") { REQUIRE(line == "true 1 1 text A\n"); } + line = thread->getline(); + THEN("expect evaluated") { REQUIRE(line == "1.5 1.5 1.5 text0.5 B\n"); } + line = thread->getline(); + THEN("expect assigned") { REQUIRE(line == "1.5 1.5 1.5 text0.5 B\n"); } + } + + // Six cases that should fail. We can't pollute lookahead with these so they need to be + // separated out. + for (int i = 0; i < 6; ++i) { + WHEN("Jump to failing case") + { + const std::string name = "Fail" + std::to_string(i); + REQUIRE_NOTHROW(thread->move_to(ink::hash_string(name.c_str()))); + std::string line; + REQUIRE_THROWS_AS(line = thread->getline(), ink::ink_exception); + } + } + } +} diff --git a/inkcpp_test/UTF8.cpp b/inkcpp_test/UTF8.cpp index 1a03c55d..81b03b3e 100644 --- a/inkcpp_test/UTF8.cpp +++ b/inkcpp_test/UTF8.cpp @@ -1,6 +1,7 @@ #include "catch.hpp" #include +#include #include #include diff --git a/inkcpp_test/ink/134_restrictive_casts.ink b/inkcpp_test/ink/134_restrictive_casts.ink new file mode 100644 index 00000000..563b5986 --- /dev/null +++ b/inkcpp_test/ink/134_restrictive_casts.ink @@ -0,0 +1,51 @@ +VAR b = true +VAR i = 1 +VAR f = 1.0 +VAR t = "text" +LIST l = (A), B +VAR d = ->Knot + +// Input with initial values +{b} {i} {f} {t} {l} {d} + +// Cast during evaluation +{b+0.5} {i+0.5} {f+0.5} {t+0.5} {l+1} + +// Cast by variable redefinition +~b = b + 0.5 +~i = i + 0.5 +~f = f + 0.5 +~t = t + 0.5 +~l = l + 1 +{b} {i} {f} {t} {l} +->DONE + +===Knot +->DONE + +===Fail0 +{l + true} +->DONE + +===Fail1 +{l + 0.5} +->DONE + +===Fail2 +{d + 0.5} +->DONE + +===Fail3 +~l = l + true +{l} +->DONE + +===Fail4 +~l = l + 0.5 +{l} +->DONE + +===Fail5 +~d = d + 0.5 +{d} +->DONE From 776102996834d68b8bb75c71a060ea06a0709a81 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Wed, 17 Dec 2025 23:54:03 +1300 Subject: [PATCH 12/15] Support more parameter types for external functions Support uint32, bool and float in function_base::push/pop Support int32 -> uint32 cast Allow bool output. Fix toStr(char*) for strings longer than 1. Add toStr(bool) --- inkcpp/functional.cpp | 43 +++++++++++++++++++++++++++++++++++-- inkcpp/numeric_operations.h | 1 + inkcpp/output.cpp | 1 + inkcpp/string_utils.h | 11 +++++----- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/inkcpp/functional.cpp b/inkcpp/functional.cpp index 81289436..5a20067a 100644 --- a/inkcpp/functional.cpp +++ b/inkcpp/functional.cpp @@ -9,6 +9,7 @@ #include "value.h" #include "stack.h" #include "string_table.h" +#include "operations.h" #ifdef INK_ENABLE_UNREAL # include "InkVar.h" @@ -28,8 +29,28 @@ template<> int32_t function_base::pop(basic_eval_stack* stack, list_table& lists) { value val = stack->pop(); - inkAssert(val.type() == value_type::int32, "Type mismatch!"); - return val.get(); + return casting::numeric_cast(val); +} + +template<> +uint32_t function_base::pop(basic_eval_stack* stack, list_table& lists) +{ + value val = stack->pop(); + return casting::numeric_cast(val); +} + +template<> +bool function_base::pop(basic_eval_stack* stack, list_table& lists) +{ + value val = stack->pop(); + return casting::numeric_cast(val) != 0; +} + +template<> +float function_base::pop(basic_eval_stack* stack, list_table& lists) +{ + value val = stack->pop(); + return casting::numeric_cast(val); } template<> @@ -46,6 +67,24 @@ void function_base::push(basic_eval_stack* stack, const int32_t& v) stack->push(value{}.set(v)); } +template<> +void function_base::push(basic_eval_stack* stack, const uint32_t& v) +{ + stack->push(value{}.set(v)); +} + +template<> +void function_base::push(basic_eval_stack* stack, const float& v) +{ + stack->push(value{}.set(v)); +} + +template<> +void function_base::push(basic_eval_stack* stack, const bool& v) +{ + stack->push(value{}.set(v)); +} + void function_base::push_void(basic_eval_stack* stack) { stack->push(values::null); } void function_base::push_string(basic_eval_stack* stack, const char* dynamic_string) diff --git a/inkcpp/numeric_operations.h b/inkcpp/numeric_operations.h index d2593139..0914f401 100644 --- a/inkcpp/numeric_operations.h +++ b/inkcpp/numeric_operations.h @@ -88,6 +88,7 @@ namespace casting numeric_cast(const value& v) { switch (v.type()) { + case value_type::int32: return v.get(); case value_type::uint32: return v.get(); /// bool value can cast to uint32 case value_type::boolean: return static_cast(v.get()); diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 6162db2c..06614125 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -362,6 +362,7 @@ char* basic_stream::get_alloc(string_table& strings, list_table& lists) case value_type::int32: case value_type::float32: case value_type::uint32: + case value_type::boolean: // Convert to string and advance toStr(ptr, end - ptr, _data[i]); while (*ptr != 0) diff --git a/inkcpp/string_utils.h b/inkcpp/string_utils.h index 5924af95..ad87ad82 100644 --- a/inkcpp/string_utils.h +++ b/inkcpp/string_utils.h @@ -85,18 +85,18 @@ inline int toStr(char* buffer, size_t size, float value) return ec; } -inline int toStr(char* buffer, size_t size, const char* c) +inline int toStr(char* buffer, size_t size, const char* str) { char* ptr = buffer; size_t i = 0; - while (*c && i < size) { - *ptr++ = *c; + while (i < size && str[i]) { + ptr[i] = str[i]; ++i; } if (i >= size) { return EINVAL; } - *ptr = 0; + ptr[i] = 0; return 0; } @@ -113,7 +113,7 @@ inline int toStr(char* buffer, size_t size, const value& v) case value_type::float32: return toStr(buffer, size, v.get()); case value_type::boolean: return toStr(buffer, size, v.get()); case value_type::newline: return toStr(buffer, size, "\n"); - default: inkFail("only support toStr for numeric types"); return -1; + default: inkFail("No toStr implementation for this type"); return -1; } } @@ -148,6 +148,7 @@ inline constexpr size_t value_length(const value& v) case value_type::uint32: return decimal_digits(v.get()); case value_type::float32: return decimal_digits(v.get()); case value_type::string: return c_str_len(v.get()); + case value_type::boolean: return v.get() ? c_str_len("true") : c_str_len("false"); case value_type::newline: return 1; default: inkFail("Can't determine length of this value type"); return -1; } From e4a745c013eac7c443ade405c0115e5611b8e2d4 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:06:43 +1300 Subject: [PATCH 13/15] Added test case for external function parameter types. Added ExternalFunctionTypes.ink/cpp, which test a roundtrip of different types to the host. Updated CMakeLists.txt. --- inkcpp_test/CMakeLists.txt | 1 + inkcpp_test/ExternalFunctionTypes.cpp | 48 +++++++++++++++++++++++ inkcpp_test/ink/ExternalFunctionTypes.ink | 20 ++++++++++ 3 files changed, 69 insertions(+) create mode 100644 inkcpp_test/ExternalFunctionTypes.cpp create mode 100644 inkcpp_test/ink/ExternalFunctionTypes.ink diff --git a/inkcpp_test/CMakeLists.txt b/inkcpp_test/CMakeLists.txt index 2f292f46..1a628535 100644 --- a/inkcpp_test/CMakeLists.txt +++ b/inkcpp_test/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable(inkcpp_test catch.hpp Main.cpp ThirdTierChoiceAfterBrackets.cpp NoEarlyTags.cpp ExternalFunctionsExecuteProperly.cpp + ExternalFunctionTypes.cpp LookaheadSafe.cpp EmptyStringForDivert.cpp MoveTo.cpp diff --git a/inkcpp_test/ExternalFunctionTypes.cpp b/inkcpp_test/ExternalFunctionTypes.cpp new file mode 100644 index 00000000..fae1b726 --- /dev/null +++ b/inkcpp_test/ExternalFunctionTypes.cpp @@ -0,0 +1,48 @@ +#include "catch.hpp" + +#include <../runner_impl.h> +#include +#include +#include +#include + +using namespace ink::runtime; + +SCENARIO("a story with external functions support types", "[story]") +{ + GIVEN("a story with external functions") + { + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "ExternalFunctionTypes.bin"); + auto thread = ink->new_runner().cast(); + + std::stringstream debug; + thread->set_debug_enabled(&debug); + + bool b = false; + int i = 0; + unsigned int u = 0; + float f = 0; + std::string s; + + thread->bind("SET_BOOL", [&b](bool o) { b = o; }); + thread->bind("SET_INT", [&i](int o) { i = o; }); + thread->bind("SET_UINT", [&u](unsigned int o) { u = o; }); + thread->bind("SET_FLOAT", [&f](float o) { f = o; }); + thread->bind("SET_STRING",[&s](std::string o) { s = o; }); + + thread->bind("GET_BOOL", [&b]() { return b; }); + thread->bind("GET_INT", [&i]() { return i; }); + thread->bind("GET_UINT", [&u]() { return u; }); + thread->bind("GET_FLOAT", [&f]() { return f; }); + thread->bind("GET_STRING",[&s]() { return s; }); + + WHEN("run thread") + { + THEN("thread has correct line counts") + { + auto line = thread->getline(); + REQUIRE(line == "true 1.5 -5 17 foo\n"); + } + } + } +} diff --git a/inkcpp_test/ink/ExternalFunctionTypes.ink b/inkcpp_test/ink/ExternalFunctionTypes.ink new file mode 100644 index 00000000..004a13df --- /dev/null +++ b/inkcpp_test/ink/ExternalFunctionTypes.ink @@ -0,0 +1,20 @@ +EXTERNAL GET_BOOL() +EXTERNAL GET_FLOAT() +EXTERNAL GET_INT() +EXTERNAL GET_UINT() +EXTERNAL GET_STRING() +EXTERNAL SET_BOOL(b) +EXTERNAL SET_FLOAT(f) +EXTERNAL SET_INT(i) +EXTERNAL SET_UINT(i) +EXTERNAL SET_STRING(s) + +~ SET_BOOL(true) +~ SET_FLOAT(1.5) +~ SET_INT(-5) +~ SET_UINT(17) +~ SET_STRING("foo") + +{GET_BOOL()} {GET_FLOAT()} {GET_INT()} {GET_UINT()} {GET_STRING()} + +-> DONE From f1a8c7842d9bcb2423f4ad1a5de544cb080b7269 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:57:14 +1300 Subject: [PATCH 14/15] Run Clangformat manually --- inkcpp/string_utils.h | 3 ++- inkcpp_test/ExternalFunctionTypes.cpp | 32 +++++++++++++-------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/inkcpp/string_utils.h b/inkcpp/string_utils.h index ad87ad82..c4eeba21 100644 --- a/inkcpp/string_utils.h +++ b/inkcpp/string_utils.h @@ -148,7 +148,8 @@ inline constexpr size_t value_length(const value& v) case value_type::uint32: return decimal_digits(v.get()); case value_type::float32: return decimal_digits(v.get()); case value_type::string: return c_str_len(v.get()); - case value_type::boolean: return v.get() ? c_str_len("true") : c_str_len("false"); + case value_type::boolean: + return v.get() ? c_str_len("true") : c_str_len("false"); case value_type::newline: return 1; default: inkFail("Can't determine length of this value type"); return -1; } diff --git a/inkcpp_test/ExternalFunctionTypes.cpp b/inkcpp_test/ExternalFunctionTypes.cpp index fae1b726..980dab3f 100644 --- a/inkcpp_test/ExternalFunctionTypes.cpp +++ b/inkcpp_test/ExternalFunctionTypes.cpp @@ -18,30 +18,30 @@ SCENARIO("a story with external functions support types", "[story]") std::stringstream debug; thread->set_debug_enabled(&debug); - bool b = false; - int i = 0; + bool b = false; + int i = 0; unsigned int u = 0; - float f = 0; - std::string s; + float f = 0; + std::string s; - thread->bind("SET_BOOL", [&b](bool o) { b = o; }); - thread->bind("SET_INT", [&i](int o) { i = o; }); - thread->bind("SET_UINT", [&u](unsigned int o) { u = o; }); - thread->bind("SET_FLOAT", [&f](float o) { f = o; }); - thread->bind("SET_STRING",[&s](std::string o) { s = o; }); + thread->bind("SET_BOOL", [&b](bool o) { b = o; }); + thread->bind("SET_INT", [&i](int o) { i = o; }); + thread->bind("SET_UINT", [&u](unsigned int o) { u = o; }); + thread->bind("SET_FLOAT", [&f](float o) { f = o; }); + thread->bind("SET_STRING", [&s](std::string o) { s = o; }); - thread->bind("GET_BOOL", [&b]() { return b; }); - thread->bind("GET_INT", [&i]() { return i; }); - thread->bind("GET_UINT", [&u]() { return u; }); - thread->bind("GET_FLOAT", [&f]() { return f; }); - thread->bind("GET_STRING",[&s]() { return s; }); + thread->bind("GET_BOOL", [&b]() { return b; }); + thread->bind("GET_INT", [&i]() { return i; }); + thread->bind("GET_UINT", [&u]() { return u; }); + thread->bind("GET_FLOAT", [&f]() { return f; }); + thread->bind("GET_STRING", [&s]() { return s; }); WHEN("run thread") { THEN("thread has correct line counts") { - auto line = thread->getline(); - REQUIRE(line == "true 1.5 -5 17 foo\n"); + auto line = thread->getline(); + REQUIRE(line == "true 1.5 -5 17 foo\n"); } } } From 619efef2aa37bcdb2e95b518ae0c8cbd422b1a88 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:08:01 +1300 Subject: [PATCH 15/15] Fix copypasta comment --- inkcpp_test/ExternalFunctionTypes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inkcpp_test/ExternalFunctionTypes.cpp b/inkcpp_test/ExternalFunctionTypes.cpp index 980dab3f..4c693f5f 100644 --- a/inkcpp_test/ExternalFunctionTypes.cpp +++ b/inkcpp_test/ExternalFunctionTypes.cpp @@ -38,7 +38,7 @@ SCENARIO("a story with external functions support types", "[story]") WHEN("run thread") { - THEN("thread has correct line counts") + THEN("output shows values from ink") { auto line = thread->getline(); REQUIRE(line == "true 1.5 -5 17 foo\n");