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/32] 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/32] 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/32] 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 fce4b1e35aa0889ce9c16850006537b9423685c0 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Sat, 15 Nov 2025 21:08:21 +1300 Subject: [PATCH 04/32] Structural optimisations --- inkcpp/container_operations.cpp | 8 +- inkcpp/runner_impl.cpp | 160 ++++++++++++-------------- inkcpp/runner_impl.h | 13 +-- inkcpp/story_impl.cpp | 196 +++++++++++++++++--------------- inkcpp/story_impl.h | 39 +++++-- 5 files changed, 218 insertions(+), 198 deletions(-) diff --git a/inkcpp/container_operations.cpp b/inkcpp/container_operations.cpp index 64575b1d..9d3c7306 100644 --- a/inkcpp/container_operations.cpp +++ b/inkcpp/container_operations.cpp @@ -19,9 +19,7 @@ namespace ink::runtime::internal { basic_eval_stack& stack, value* vals) { container_t id; - bool success = _story.get_container_id( - _story.instructions() + vals[0].get(), - id); + bool success = _story.find_container_id(vals[0].get(), id); inkAssert(success, "failed to find container to read visit count!"); stack.push(value{}.set( static_cast(_visit_counts.visits( id ) @@ -32,9 +30,7 @@ namespace ink::runtime::internal { basic_eval_stack& stack, value* vals) { container_t id; - bool success = _story.get_container_id( - _story.instructions() + vals[0].get(), - id); + bool success = _story.find_container_id(vals[0].get(), id); inkAssert(success, "failed to find container to read turn count!"); stack.push(value{}.set( static_cast(_visit_counts.turns(id) diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 1ab2c914..fe63da71 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -15,6 +15,9 @@ #include "system.h" #include "value.h" +#include "..\..\..\sil\sil\main.h" +#include "..\..\..\sil\sil\profile.h" + #include namespace ink::runtime @@ -42,7 +45,7 @@ namespace ink::runtime::internal hash_t runner_impl::get_current_knot() const { - return _current_knot_id == ~0 ? 0 : _story->container_hash(_current_knot_id); + return _current_knot_id == ~0 ? 0 : _story->container(_current_knot_id)._hash; } template<> @@ -294,108 +297,76 @@ void runner_impl::clear_tags(tags_clear_level which) void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) { + SIL_PROFILE("ink_jump"); + // Optimization: if we are _is_falling, then we can // _should be_ able to safely assume that there is nothing to do here. A falling // divert should only be taking us from a container to that same container's end point // without entering any other containers // OR IF if target is same position do nothing // could happend if jumping to and of an unnamed container - if (dest == _ptr) { - _ptr = dest; + if (dest == _ptr) return; - } - const uint32_t* iter = nullptr; - container_t id; - ip_t offset = nullptr; - size_t comm_end; - bool reversed = _ptr > dest; - - if (reversed) { - comm_end = 0; - iter = nullptr; - const ContainerData* old_iter = nullptr; - const uint32_t* last_comm_iter = nullptr; - _container.rev_iter(old_iter); - - // find commen part of old and new stack - while (_story->iterate_containers(iter, id, offset)) { - if (old_iter == nullptr || offset >= dest) { - break; - } - if (old_iter != nullptr && id == old_iter->id) { - last_comm_iter = iter; - _container.rev_iter(old_iter); - ++comm_end; - } - } + // Assume we are going forwards, in which case we'll only add to the stack. + const size_t old_stack_depth = _container.size(); - // clear old part from stack - while (_container.size() > comm_end) { - _container.pop(); - } - iter = last_comm_iter; + // Empty stack, preserving saved data if any (i.e. don't use clear) + while (!_container.empty()) + _container.pop(); - } else { - iter = nullptr; - comm_end = _container.size(); - // go to current possition in container list - while (_story->iterate_containers(iter, id, offset)) { - if (offset >= _ptr) { - break; - } - } - _story->iterate_containers(iter, id, offset, true); - } + // Find the container at or before dest, which will become the top of the stack. + const uint32_t dest_offset = dest - _story->instructions(); + const container_t id = _story->find_container_for(dest_offset); + const story_impl::Container& c = _story->container(id); - // move to destination and update container stack on the go - while (_story->iterate_containers(iter, id, offset)) { - if (offset >= dest) { - break; - } - if (_container.empty() || _container.top().id != id) { - _container.push({id, offset - _story->instructions()}); - } else { - _container.pop(); - if (_container.size() < comm_end) { - comm_end = _container.size(); - } - } + // Walk back from current parent container, generating the new stack in the wrong order. + for (container_t p = id; p != 0; p = _story->container(p)._parent) + _container.push(p); + + // Then reverse that order to reassemble the stack. + const container_t* fwd = nullptr; + const container_t* rev = nullptr; + _container.iter(fwd); + _container.rev_iter(rev); + + // This would be nicer as a method on simple_restorable_stack. Doing it like this to avoid allocating a second stack. + while (fwd && rev && fwd > rev) + { + container_t temp = *fwd; + *const_cast(fwd) = *rev; + *const_cast(rev) = temp; + _container.iter(fwd); + _container.iter(rev); } + + // Do jump _ptr = dest; + // Find position on top of stack +// SIL_ASSERT(&c == &_story->container(id)); +// uint32_t p_offset = _ptr - _story->instructions(); +// SIL_ASSERT(c._start_offset <= p_offset); +// SIL_ASSERT(c._end_offset >= p_offset); + // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container - // it will get visited in the next step - if (offset == dest && static_cast(offset[0]) == Command::START_CONTAINER_MARKER) { - if (track_knot_visit - && static_cast(offset[1]) & CommandFlag::CONTAINER_MARKER_IS_KNOT) { + if (dest_offset == c._start_offset) { + if (track_knot_visit && (c._flags & CommandFlag::CONTAINER_MARKER_IS_KNOT)) { _current_knot_id = id; _entered_knot = true; } _ptr += 6; - _container.push({id, offset - _story->instructions()}); - if (reversed && comm_end == _container.size() - 1) { - ++comm_end; - } } // iff all container (until now) are entered at first position - bool allEnteredAtStart = true; - ip_t child_position = dest; - if (record_visits) { - const ContainerData* iData = nullptr; - size_t level = _container.size(); - if (_container.iter(iData) - && (level > comm_end - || _story->container_flag(iData->offset + _story->instructions()) - & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) { - auto parrent_offset = _story->instructions() + iData->offset; - inkAssert(child_position >= parrent_offset, "Container stack order is broken"); + if (record_visits && !_container.empty()) { + inkAssert(id == _container.top()); + if (_container.size() > old_stack_depth || (c._flags & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) { + inkAssert(dest_offset >= c._start_offset, "Container stack order is broken"); // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed // subcontainers first child check if child_positino is the first child of current container - allEnteredAtStart = allEnteredAtStart && ((child_position - parrent_offset) <= 6); - child_position = parrent_offset; - _globals->visit(iData->id, allEnteredAtStart); + bool all_entered_at_start = dest_offset - c._start_offset <= 6; + _globals->visit(id, all_entered_at_start); } } } @@ -403,6 +374,8 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) template void runner_impl::start_frame(uint32_t target) { + SIL_PROFILE("ink_start_frame"); + if constexpr (type == frame_type::function) { // add a function start marker _output << values::func_start; @@ -422,6 +395,8 @@ void runner_impl::start_frame(uint32_t target) frame_type runner_impl::execute_return() { + SIL_PROFILE("ink_return"); + // Pop the callstack _ref_stack.fetch_values(_stack); frame_type type; @@ -476,7 +451,7 @@ runner_impl::runner_impl(const story_impl* data, globals global) , _evaluation_mode{false} , _choices() , _tags_begin(0, ~0) - , _container(ContainerData{}) + , _container(~0) , _rng(time(NULL)) { @@ -589,6 +564,8 @@ bool runner_impl::can_continue() const { return _ptr != nullptr && ! has_choices void runner_impl::choose(size_t index) { + SIL_PROFILE("ink_choose"); + if (has_choices()) { inkAssert(index < _choices.size(), "Choice index out of range"); } else if (! _fallback_choice) { @@ -639,6 +616,8 @@ snapshot* runner_impl::create_snapshot() const { return _globals->create_snapsho size_t runner_impl::snap(unsigned char* data, snapper& snapper) const { + SIL_PROFILE("ink_snap"); + unsigned char* ptr = data; bool should_write = data != nullptr; std::uintptr_t offset = _ptr != nullptr ? _ptr - _story->instructions() : 0; @@ -675,6 +654,8 @@ size_t runner_impl::snap(unsigned char* data, snapper& snapper) const const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& loader) { + SIL_PROFILE("ink_snap_load"); + auto ptr = data; std::uintptr_t offset; ptr = snap_read(ptr, offset); @@ -751,6 +732,8 @@ void runner_impl::internal_bind(hash_t name, internal::function_base* function) runner_impl::change_type runner_impl::detect_change() const { + SIL_PROFILE("ink_detect_change"); + inkAssert(_output.saved(), "Cannot detect changes in non-saved stream."); // Check if the old newline is still present (hasn't been glu'd) and @@ -1335,7 +1318,7 @@ void runner_impl::step() if (flag & CommandFlag::CHOICE_IS_ONCE_ONLY) { // Need to convert offset to container index container_t destination = -1; - if (_story->get_container_id(_story->instructions() + path, destination)) { + if (_story->find_container_id(path, destination)) { // Ignore the choice if we've visited the destination before if (_globals->visits(destination) > 0) { break; @@ -1398,12 +1381,12 @@ void runner_impl::step() // Keep track of current container auto index = read(); // offset points to command, command has size 6 - _container.push({index, _ptr - _story->instructions() - 6}); + _container.push(index); // Increment visit count if (flag & CommandFlag::CONTAINER_MARKER_TRACK_VISITS || flag & CommandFlag::CONTAINER_MARKER_TRACK_TURNS) { - _globals->visit(_container.top().id, true); + _globals->visit(index, true); } if (flag & CommandFlag::CONTAINER_MARKER_IS_KNOT) { _current_knot_id = index; @@ -1414,7 +1397,10 @@ void runner_impl::step() case Command::END_CONTAINER_MARKER: { container_t index = read(); - inkAssert(_container.top().id == index, "Leaving container we are not in!"); + if (index != _container.top()) + _container.push(index); + + inkAssert(_container.top() == index, "Leaving container we are not in!"); // Move up out of the current container _container.pop(); @@ -1449,7 +1435,7 @@ void runner_impl::step() // Push the visit count for the current container to the top // is 0-indexed for some reason. idk why but this is what ink expects _eval.push(value{}.set( - static_cast(_globals->visits(_container.top().id) - 1) + static_cast(_globals->visits(_container.top()) - 1) )); } break; case Command::TURN: { @@ -1585,6 +1571,8 @@ void runner_impl::mark_used(string_table& strings, list_table& lists) const void runner_impl::save() { + SIL_PROFILE("ink_save"); + inkAssert(! _saved, "Runner state already saved"); _saved = true; @@ -1608,6 +1596,8 @@ void runner_impl::save() void runner_impl::restore() { + SIL_PROFILE("ink_restore"); + inkAssert(_saved, "Can't restore. No runner state saved."); // the output can be restored without the rest if (_output.saved()) { diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 8e119929..1e795a1a 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -339,17 +339,8 @@ class runner_impl // TODO: Move to story? Both? functions _functions; - // Container set - struct ContainerData { - container_t id = ~0u; - ptrdiff_t offset = 0; - - bool operator==(const ContainerData& oth) const { return oth.id == id && oth.offset == offset; } - - bool operator!=(const ContainerData& oth) const { return ! (*this == oth); } - }; - - internal::managed_restorable_stack < ContainerData, + // Container stack + internal::managed_restorable_stack _container; bool _is_falling = false; diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index f072d95a..a9f5e566 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -13,6 +13,10 @@ #include "snapshot_interface.h" #include "version.h" +#include "..\..\..\sil\sil\main.h" +#include "..\..\..\sil\sil\profile.h" + + namespace ink::runtime { #ifdef INK_ENABLE_STL @@ -101,53 +105,56 @@ story_impl::~story_impl() const char* story_impl::string(uint32_t index) const { return _string_table + index; } -bool story_impl::iterate_containers( - const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse -) const +bool story_impl::find_container_id(uint32_t offset, container_t& container_id) const { - if (iterator == nullptr) { - // Empty check - if (_container_list_size == 0) { - return false; - } + // Find inmost container. + container_id = find_container_for(offset); - // Start - iterator = reverse ? _container_list + (_container_list_size - 1) * 2 : _container_list; - } else { - // Range check - inkAssert( - iterator >= _container_list && iterator <= _container_list + _container_list_size * 2, - "Container fail" - ); - - // Advance - iterator += reverse ? -2 : 2; - - // End? - if (iterator >= _container_list + _container_list_size * 2 || iterator < _container_list) { - iterator = nullptr; - index = 0; - offset = nullptr; - return false; - } + // Exact match? + return container(container_id)._start_offset == offset; +} + +// Search sorted looking for the target or the largest value smaller than target. +// Assumes 2*count u32s, with the sorted values in the even slots. The odd slots can have any payload. +static const uint32_t *upper_bound(const uint32_t *sorted, uint32_t count, uint32_t target) +{ + uint32_t begin = 0; + uint32_t end = count; + + while (begin < end) { + const uint32_t mid = begin + (end - begin + 1) / 2; + const uint32_t mid_offset = sorted[mid * 2 + 0]; + + if (mid_offset > target) + // Look below + end = mid - 1; + else + // Look above + begin = mid; } - // Get metadata - index = *(iterator + 1); - offset = *iterator + instructions(); - return true; + return sorted + begin * 2; } -bool story_impl::get_container_id(ip_t offset, container_t& container_id) const +container_t story_impl::find_container_for(uint32_t offset) const { - const uint32_t* iter = nullptr; - ip_t iter_offset = nullptr; - while (iterate_containers(iter, container_id, iter_offset)) { - if (iter_offset == offset) - return true; + SIL_PROFILE("ink_find_container_before"); + + // Container map contains offsets in even slots, container ids in odd. + const uint32_t *iter = upper_bound(_container_list, _container_list_size, offset); + + for (int c = _container_list_size-1; c >= 0; --c) + { + if (_container_list[c * 2 + 0] <= offset) + { + SIL_ASSERT(iter == _container_list + c * 2); + break; + } } - return false; + // SIL: Do we need a test? Is there always an outermost container? + inkAssert(iter[0] <= offset); + return iter[1]; } CommandFlag story_impl::container_flag(ip_t offset) const @@ -160,61 +167,15 @@ CommandFlag story_impl::container_flag(ip_t offset) const return static_cast(offset[1]); } -CommandFlag story_impl::container_flag(container_t id) const -{ - const uint32_t* iter = nullptr; - ip_t offset; - container_t c_id; - while (iterate_containers(iter, c_id, offset)) { - if (c_id == id) { - inkAssert( - static_cast(offset[0]) == Command::START_CONTAINER_MARKER, - "Container list pointer is invalid!" - ); - return static_cast(offset[1]); - } - } - inkFail("Container not found -> can't fetch flag"); - return CommandFlag::NO_FLAGS; -} - -hash_t story_impl::container_hash(container_t id) const -{ - const uint32_t* iter = nullptr; - ip_t offset; - container_t c_id; - bool hit = false; - while (iterate_containers(iter, c_id, offset)) { - if (c_id == id) { - hit = true; - break; - } - } - inkAssert(hit, "Unable to find container for id!"); - hash_t* h_iter = _container_hash_start; - while (iter != _container_hash_end) { - if (instructions() + *( offset_t* ) (h_iter + 1) == offset) { - return *h_iter; - } - h_iter += 2; - } - inkAssert(false, "Did not find hash entry for container!"); - return 0; -} - ip_t story_impl::find_offset_for(hash_t path) const { - hash_t* iter = _container_hash_start; - - while (iter != _container_hash_end) { - if (*iter == path) { - return instructions() + *( offset_t* ) (iter + 1); - } + SIL_PROFILE("ink_find_offset_for"); - iter += 2; - } + // Hash map contains hashes in even slots, offsets in odd. + const uint32_t count = (_container_hash_end - _container_hash_start) / 2; + const uint32_t *iter = upper_bound(_container_hash_start, count, path); - return nullptr; + return iter[0] == path ? _instruction_data + iter[1] : nullptr; } globals story_impl::new_globals() @@ -372,6 +333,63 @@ void story_impl::setup_pointers() // After strings comes instruction data _instruction_data = ( ip_t ) ptr; + + container_t *stack = new container_t[_num_containers]; + uint32_t depth = 0; + stack[depth] = 0; + + // Build acceleration structure for containers. + _containers = new Container[_num_containers]; + for (uint32_t c = 0; c < _container_list_size; ++c) + { + const uint32_t *iter = _container_list + 2 * c; + const container_t id = iter[1]; + const uint32_t offset = iter[0]; + + const Command command = Command(_instruction_data[offset]); + + inkAssert(command == Command::START_CONTAINER_MARKER || command == Command::END_CONTAINER_MARKER); + + if (command == Command::START_CONTAINER_MARKER) + { + _containers[id]._start_offset = offset; + _containers[id]._flags = CommandFlag(_instruction_data[offset+1]); + _containers[id]._parent = stack[depth]; + + stack[++depth] = id; + } + else + { + _containers[stack[depth]]._end_offset = offset; + --depth; + } + + for (uint32_t *h = _container_hash_start; h < _container_hash_end; h += 2) + { + if (h[1] == offset) + { + _containers[id]._hash = h[0]; + break; + } + } + } + + delete[] stack; + + // Sort container hash so we can use a binary search. + struct Hash + { + hash_t _hash; + hash_t _offset; + + static int __cdecl compare(const void* lhs, const void *rhs) + { + return static_cast(lhs)->_hash - static_cast(rhs)->_hash; + } + }; + + qsort(reinterpret_cast(_container_hash_start), (_container_hash_end-_container_hash_start)/2, sizeof(Hash), &Hash::compare); + // Debugging info /*{ const uint32_t* iter = nullptr; diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index 02d0fca3..2f3ad903 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -40,14 +40,37 @@ class story_impl : public story const char* list_meta() const { return _list_meta; } - bool iterate_containers( - const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse = false - ) const; - bool get_container_id(ip_t offset, container_t& container_id) const; - /// Get container flag from container offset (either start or end) + // Find the innermost container containing offset. If offset is the start of a container, return that container. + container_t find_container_for(uint32_t offset) const; + + // Find the container which starts exactly at offset. Return false if this isn't the start of a container. + bool find_container_id(uint32_t offset, container_t& container_id) const; + + // Container description. + struct Container + { + /// Container flags (saves looking up via instruction data) + CommandFlag _flags : 4; + + /// Instruction offset to the start of this container. + uint32_t _start_offset : 28; + uint32_t _end_offset; + + /// Parent container, or ~0 if this is the root. + container_t _parent; + + /// Container hash. + uint32_t _hash; + }; + + // Look up the details of the given container + const Container& container(container_t id) const { inkAssert(id < _num_containers); return _containers[id]; } + + // Look up the instruction pointer for the start of the given container + ip_t container_offset(container_t id) const { return _instruction_data + container(id)._start_offset; } + + // Get container flag from container offset (either start or end) CommandFlag container_flag(ip_t offset) const; - CommandFlag container_flag(container_t id) const; - hash_t container_hash(container_t id) const; ip_t find_offset_for(hash_t path) const; @@ -81,6 +104,8 @@ class story_impl : public story uint32_t _container_list_size; uint32_t _num_containers; + Container *_containers; + // container hashes hash_t* _container_hash_start; hash_t* _container_hash_end; From 9359507833d2d2d6f0c3021e8e6a9dab54ba7081 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Sat, 15 Nov 2025 23:18:00 +1300 Subject: [PATCH 05/32] Closer to working, added original jump back for comparisons. --- inkcpp/globals_impl.cpp | 2 +- inkcpp/runner_impl.cpp | 185 +++++++++++++++++++++++++++++++--------- inkcpp/story_impl.cpp | 58 ++++++++++--- inkcpp/story_impl.h | 4 + 4 files changed, 195 insertions(+), 54 deletions(-) diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 6daed149..186dcf07 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -48,7 +48,7 @@ globals_impl::globals_impl(const story_impl* story) void globals_impl::visit(uint32_t container_id, bool entering_at_start) { - if ((! (_owner->container_flag(container_id) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) + if ((! (_owner->container(container_id)._flags & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) || entering_at_start) { _visit_counts[container_id].visits += 1; _visit_counts[container_id].turns = 0; diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index fe63da71..c31ad2a6 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -297,8 +297,128 @@ void runner_impl::clear_tags(tags_clear_level which) void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) { - SIL_PROFILE("ink_jump"); +//#define OLD_VERSION +#ifdef OLD_VERSION + // Optimization: if we are _is_falling, then we can + // _should be_ able to safely assume that there is nothing to do here. A falling + // divert should only be taking us from a container to that same container's end point + // without entering any other containers + // OR IF if target is same position do nothing + // could happend if jumping to and of an unnamed container + if (dest == _ptr) { + _ptr = dest; + return; + } + + const uint32_t* iter = nullptr; + container_t id; + ip_t offset = nullptr; + size_t comm_end; + bool reversed = _ptr > dest; + + if (reversed) { + comm_end = 0; + iter = nullptr; + const container_t * old_iter = nullptr; + const uint32_t* last_comm_iter = nullptr; + _container.rev_iter(old_iter); + + // find commen part of old and new stack + while (_story->iterate_containers(iter, id, offset)) { + if (old_iter == nullptr || offset >= dest) { + break; + } + if (old_iter != nullptr && id == *old_iter) { + last_comm_iter = iter; + _container.rev_iter(old_iter); + ++comm_end; + } + } + + // clear old part from stack + while (_container.size() > comm_end) { + _container.pop(); + } + iter = last_comm_iter; + + } else { + iter = nullptr; + comm_end = _container.size(); + // go to current possition in container list + while (_story->iterate_containers(iter, id, offset)) { + if (offset >= _ptr) { + break; + } + } + _story->iterate_containers(iter, id, offset, true); + } + + // move to destination and update container stack on the go + while (_story->iterate_containers(iter, id, offset)) { + if (offset >= dest) { + break; + } + if (_container.empty() || _container.top() != id) { + _container.push(id); + } else { + _container.pop(); + if (_container.size() < comm_end) { + comm_end = _container.size(); + } + } + + + + + + } + + + _ptr = dest; + + + + + + + // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container + // it will get visited in the next step + if (offset == dest && static_cast(offset[0]) == Command::START_CONTAINER_MARKER) { + if (track_knot_visit + && static_cast(offset[1]) & CommandFlag::CONTAINER_MARKER_IS_KNOT) { + _current_knot_id = id; + _entered_knot = true; + } + _ptr += 6; + _container.push(id); + if (reversed && comm_end == _container.size() - 1) { + ++comm_end; + } + } + + // iff all container (until now) are entered at first position + bool allEnteredAtStart = true; + ip_t child_position = dest; + if (record_visits) { + const container_t* iData = nullptr; + size_t level = _container.size(); + if (_container.iter(iData) + && (level > comm_end + || _story->container_flag(_story->container(*iData)._start_offset + _story->instructions()) + & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) { + auto parrent_offset = _story->instructions() + _story->container(*iData)._start_offset; + inkAssert(child_position >= parrent_offset, "Container stack order is broken"); + // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed + // subcontainers first child check if child_positino is the first child of current container + allEnteredAtStart = allEnteredAtStart && ((child_position - parrent_offset) <= 6); + child_position = parrent_offset; + _globals->visit(*iData, allEnteredAtStart); + } + } + + return; +#else // Optimization: if we are _is_falling, then we can // _should be_ able to safely assume that there is nothing to do here. A falling // divert should only be taking us from a container to that same container's end point @@ -318,7 +438,6 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) // Find the container at or before dest, which will become the top of the stack. const uint32_t dest_offset = dest - _story->instructions(); const container_t id = _story->find_container_for(dest_offset); - const story_impl::Container& c = _story->container(id); // Walk back from current parent container, generating the new stack in the wrong order. for (container_t p = id; p != 0; p = _story->container(p)._parent) @@ -343,39 +462,39 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) // Do jump _ptr = dest; - // Find position on top of stack -// SIL_ASSERT(&c == &_story->container(id)); -// uint32_t p_offset = _ptr - _story->instructions(); -// SIL_ASSERT(c._start_offset <= p_offset); -// SIL_ASSERT(c._end_offset >= p_offset); - - // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container - if (dest_offset == c._start_offset) { - if (track_knot_visit && (c._flags & CommandFlag::CONTAINER_MARKER_IS_KNOT)) { - _current_knot_id = id; - _entered_knot = true; + if (!_container.empty()) + { + // Find position on top of stack + const story_impl::Container& c = _story->container(id); + SIL_ASSERT(c._start_offset <= dest_offset); + SIL_ASSERT(c._end_offset >= dest_offset); + + // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container + if (dest_offset == c._start_offset) { + if (track_knot_visit && (c._flags & CommandFlag::CONTAINER_MARKER_IS_KNOT)) { + _current_knot_id = id; + _entered_knot = true; + } + _ptr += 6; } - _ptr += 6; - } - // iff all container (until now) are entered at first position - if (record_visits && !_container.empty()) { - inkAssert(id == _container.top()); - if (_container.size() > old_stack_depth || (c._flags & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) { - inkAssert(dest_offset >= c._start_offset, "Container stack order is broken"); - // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed - // subcontainers first child check if child_positino is the first child of current container - bool all_entered_at_start = dest_offset - c._start_offset <= 6; - _globals->visit(id, all_entered_at_start); + // iff all container (until now) are entered at first position + if (record_visits ) { + if (_container.size() > old_stack_depth || (c._flags & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) { + inkAssert(dest_offset >= c._start_offset, "Container stack order is broken"); + // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed + // subcontainers first child check if child_positino is the first child of current container + bool all_entered_at_start = dest_offset - c._start_offset <= 6; + _globals->visit(id, all_entered_at_start); + } } } +#endif } template void runner_impl::start_frame(uint32_t target) { - SIL_PROFILE("ink_start_frame"); - if constexpr (type == frame_type::function) { // add a function start marker _output << values::func_start; @@ -395,8 +514,6 @@ void runner_impl::start_frame(uint32_t target) frame_type runner_impl::execute_return() { - SIL_PROFILE("ink_return"); - // Pop the callstack _ref_stack.fetch_values(_stack); frame_type type; @@ -564,8 +681,6 @@ bool runner_impl::can_continue() const { return _ptr != nullptr && ! has_choices void runner_impl::choose(size_t index) { - SIL_PROFILE("ink_choose"); - if (has_choices()) { inkAssert(index < _choices.size(), "Choice index out of range"); } else if (! _fallback_choice) { @@ -616,8 +731,6 @@ snapshot* runner_impl::create_snapshot() const { return _globals->create_snapsho size_t runner_impl::snap(unsigned char* data, snapper& snapper) const { - SIL_PROFILE("ink_snap"); - unsigned char* ptr = data; bool should_write = data != nullptr; std::uintptr_t offset = _ptr != nullptr ? _ptr - _story->instructions() : 0; @@ -654,8 +767,6 @@ size_t runner_impl::snap(unsigned char* data, snapper& snapper) const const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& loader) { - SIL_PROFILE("ink_snap_load"); - auto ptr = data; std::uintptr_t offset; ptr = snap_read(ptr, offset); @@ -732,8 +843,6 @@ void runner_impl::internal_bind(hash_t name, internal::function_base* function) runner_impl::change_type runner_impl::detect_change() const { - SIL_PROFILE("ink_detect_change"); - inkAssert(_output.saved(), "Cannot detect changes in non-saved stream."); // Check if the old newline is still present (hasn't been glu'd) and @@ -1571,8 +1680,6 @@ void runner_impl::mark_used(string_table& strings, list_table& lists) const void runner_impl::save() { - SIL_PROFILE("ink_save"); - inkAssert(! _saved, "Runner state already saved"); _saved = true; @@ -1596,8 +1703,6 @@ void runner_impl::save() void runner_impl::restore() { - SIL_PROFILE("ink_restore"); - inkAssert(_saved, "Can't restore. No runner state saved."); // the output can be restored without the rest if (_output.saved()) { diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index a9f5e566..822c7503 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -105,6 +105,42 @@ story_impl::~story_impl() const char* story_impl::string(uint32_t index) const { return _string_table + index; } +bool story_impl::iterate_containers( + const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse +) const +{ + if (iterator == nullptr) { + // Empty check + if (_container_list_size == 0) { + return false; + } + // Start + iterator = reverse ? _container_list + (_container_list_size - 1) * 2 : _container_list; + } else { + // Range check + inkAssert( + iterator >= _container_list && iterator <= _container_list + _container_list_size * 2, + "Container fail" + ); + + // Advance + iterator += reverse ? -2 : 2; + + // End? + if (iterator >= _container_list + _container_list_size * 2 || iterator < _container_list) { + iterator = nullptr; + index = 0; + offset = nullptr; + return false; + } + } + + // Get metadata + index = *(iterator + 1); + offset = *iterator + instructions(); + return true; +} + bool story_impl::find_container_id(uint32_t offset, container_t& container_id) const { // Find inmost container. @@ -138,23 +174,21 @@ static const uint32_t *upper_bound(const uint32_t *sorted, uint32_t count, uint3 container_t story_impl::find_container_for(uint32_t offset) const { - SIL_PROFILE("ink_find_container_before"); - // Container map contains offsets in even slots, container ids in odd. const uint32_t *iter = upper_bound(_container_list, _container_list_size, offset); - for (int c = _container_list_size-1; c >= 0; --c) + // If we're not inside the container, walk out to find the actual parent. + container_t id = iter[1]; + while (id) { - if (_container_list[c * 2 + 0] <= offset) - { - SIL_ASSERT(iter == _container_list + c * 2); - break; - } + const Container& c = container(id); + if (c._start_offset <= offset && c._end_offset >= offset) + return id; + + id = c._parent; } - // SIL: Do we need a test? Is there always an outermost container? - inkAssert(iter[0] <= offset); - return iter[1]; + return id; } CommandFlag story_impl::container_flag(ip_t offset) const @@ -169,8 +203,6 @@ CommandFlag story_impl::container_flag(ip_t offset) const ip_t story_impl::find_offset_for(hash_t path) const { - SIL_PROFILE("ink_find_offset_for"); - // Hash map contains hashes in even slots, offsets in odd. const uint32_t count = (_container_hash_end - _container_hash_start) / 2; const uint32_t *iter = upper_bound(_container_hash_start, count, path); diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index 2f3ad903..cddeb72a 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -40,6 +40,10 @@ class story_impl : public story const char* list_meta() const { return _list_meta; } + bool iterate_containers( + const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse = false + ) const; + // Find the innermost container containing offset. If offset is the start of a container, return that container. container_t find_container_for(uint32_t offset) const; From 45471cf676da0b67f0a3f15dd1128a63973e0140 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Sun, 16 Nov 2025 22:53:46 +1300 Subject: [PATCH 06/32] Optimise/fix jump to visit all nested knots when we change path. Keep selective visit logic on the runner side. --- inkcpp/globals_impl.cpp | 9 +- inkcpp/globals_impl.h | 3 +- inkcpp/runner_impl.cpp | 220 ++++++++-------------------------------- inkcpp/story_impl.cpp | 49 ++------- inkcpp/story_impl.h | 7 +- 5 files changed, 59 insertions(+), 229 deletions(-) diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 186dcf07..0bf6a04d 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -46,13 +46,10 @@ globals_impl::globals_impl(const story_impl* story) } } -void globals_impl::visit(uint32_t container_id, bool entering_at_start) +void globals_impl::visit(uint32_t container_id) { - if ((! (_owner->container(container_id)._flags & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) - || entering_at_start) { - _visit_counts[container_id].visits += 1; - _visit_counts[container_id].turns = 0; - } + _visit_counts[container_id].visits += 1; + _visit_counts[container_id].turns = 0; } uint32_t globals_impl::visits(uint32_t container_id) const diff --git a/inkcpp/globals_impl.h b/inkcpp/globals_impl.h index 06c787d6..8517ccd7 100644 --- a/inkcpp/globals_impl.h +++ b/inkcpp/globals_impl.h @@ -52,8 +52,7 @@ class globals_impl final public: // Records a visit to a container - /// @param start_cmd iff the visit was initiatet through a MARKER_START_CONTAINER - void visit(uint32_t container_id, bool entering_at_start); + void visit(uint32_t container_id); // Checks the number of visits to a container uint32_t visits(uint32_t container_id) const; diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index c31ad2a6..03308b13 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -297,128 +297,8 @@ void runner_impl::clear_tags(tags_clear_level which) void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) { -//#define OLD_VERSION -#ifdef OLD_VERSION - // Optimization: if we are _is_falling, then we can - // _should be_ able to safely assume that there is nothing to do here. A falling - // divert should only be taking us from a container to that same container's end point - // without entering any other containers - // OR IF if target is same position do nothing - // could happend if jumping to and of an unnamed container - if (dest == _ptr) { - _ptr = dest; - return; - } - - const uint32_t* iter = nullptr; - container_t id; - ip_t offset = nullptr; - size_t comm_end; - bool reversed = _ptr > dest; - - if (reversed) { - comm_end = 0; - iter = nullptr; - const container_t * old_iter = nullptr; - const uint32_t* last_comm_iter = nullptr; - _container.rev_iter(old_iter); - - // find commen part of old and new stack - while (_story->iterate_containers(iter, id, offset)) { - if (old_iter == nullptr || offset >= dest) { - break; - } - if (old_iter != nullptr && id == *old_iter) { - last_comm_iter = iter; - _container.rev_iter(old_iter); - ++comm_end; - } - } - - // clear old part from stack - while (_container.size() > comm_end) { - _container.pop(); - } - iter = last_comm_iter; - - } else { - iter = nullptr; - comm_end = _container.size(); - // go to current possition in container list - while (_story->iterate_containers(iter, id, offset)) { - if (offset >= _ptr) { - break; - } - } - _story->iterate_containers(iter, id, offset, true); - } - - // move to destination and update container stack on the go - while (_story->iterate_containers(iter, id, offset)) { - if (offset >= dest) { - break; - } - if (_container.empty() || _container.top() != id) { - _container.push(id); - } else { - _container.pop(); - if (_container.size() < comm_end) { - comm_end = _container.size(); - } - } - - - - - - } - - - _ptr = dest; - + SIL_PROFILE("ink::jump"); - - - - - - // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container - // it will get visited in the next step - if (offset == dest && static_cast(offset[0]) == Command::START_CONTAINER_MARKER) { - if (track_knot_visit - && static_cast(offset[1]) & CommandFlag::CONTAINER_MARKER_IS_KNOT) { - _current_knot_id = id; - _entered_knot = true; - } - _ptr += 6; - _container.push(id); - if (reversed && comm_end == _container.size() - 1) { - ++comm_end; - } - } - - // iff all container (until now) are entered at first position - bool allEnteredAtStart = true; - ip_t child_position = dest; - if (record_visits) { - const container_t* iData = nullptr; - size_t level = _container.size(); - if (_container.iter(iData) - && (level > comm_end - || _story->container_flag(_story->container(*iData)._start_offset + _story->instructions()) - & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) { - auto parrent_offset = _story->instructions() + _story->container(*iData)._start_offset; - inkAssert(child_position >= parrent_offset, "Container stack order is broken"); - // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed - // subcontainers first child check if child_positino is the first child of current container - allEnteredAtStart = allEnteredAtStart && ((child_position - parrent_offset) <= 6); - child_position = parrent_offset; - _globals->visit(*iData, allEnteredAtStart); - } - } - - return; -#else // Optimization: if we are _is_falling, then we can // _should be_ able to safely assume that there is nothing to do here. A falling // divert should only be taking us from a container to that same container's end point @@ -428,68 +308,55 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) if (dest == _ptr) return; - // Assume we are going forwards, in which case we'll only add to the stack. - const size_t old_stack_depth = _container.size(); - - // Empty stack, preserving saved data if any (i.e. don't use clear) - while (!_container.empty()) - _container.pop(); + // Where are we now? + const uint32_t current_offset = _ptr - _story->instructions(); - // Find the container at or before dest, which will become the top of the stack. + // Find the container at or before dest, which will become the top of the post-jump stack. const uint32_t dest_offset = dest - _story->instructions(); - const container_t id = _story->find_container_for(dest_offset); + const container_t dest_id = _story->find_container_for(dest_offset); + const story_impl::Container& dest_container = _story->container(dest_id); + + // Count stack depth and assemble stack in reverse order by traversing container tree. + container_t stack[64]; + uint32_t depth = 0; + for (container_t id = dest_id; id != 0; id = _story->container(id)._parent) { + inkAssert(depth < 64); + stack[depth++] = id; + } - // Walk back from current parent container, generating the new stack in the wrong order. - for (container_t p = id; p != 0; p = _story->container(p)._parent) - _container.push(p); + // Discard existing stack, preserving save region. + while (!_container.empty()) + _container.pop(); - // Then reverse that order to reassemble the stack. - const container_t* fwd = nullptr; - const container_t* rev = nullptr; - _container.iter(fwd); - _container.rev_iter(rev); + // Are we entering the new container at its start? + const bool jump_to_start = dest_offset == dest_container._start_offset; - // This would be nicer as a method on simple_restorable_stack. Doing it like this to avoid allocating a second stack. - while (fwd && rev && fwd > rev) + // Update visit counts for new containers on the stack. If we jump directly to the start of a container, + // don't update it (last on the stack) as the normal instruction flow will process the start marker next. + for (uint32_t d = 0; d < depth - jump_to_start; ++d) { - container_t temp = *fwd; - *const_cast(fwd) = *rev; - *const_cast(rev) = temp; - _container.iter(fwd); - _container.iter(rev); - } + // Read temporary stack in forward order. + const container_t id = stack[depth-1-d]; - // Do jump - _ptr = dest; + // Push container onto stack. + _container.push(id); - if (!_container.empty()) - { - // Find position on top of stack - const story_impl::Container& c = _story->container(id); - SIL_ASSERT(c._start_offset <= dest_offset); - SIL_ASSERT(c._end_offset >= dest_offset); - - // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container - if (dest_offset == c._start_offset) { - if (track_knot_visit && (c._flags & CommandFlag::CONTAINER_MARKER_IS_KNOT)) { - _current_knot_id = id; - _entered_knot = true; - } - _ptr += 6; - } + // Named knots/stitches need special handling - their visit counts are updated wherever the story enters them, + // and we always need to know which knot we're in for tagging. + const story_impl::Container& container = _story->container(id); + if (container._flags & CommandFlag::CONTAINER_MARKER_IS_KNOT) { + // If the previous IP wasn't in this container, record the new visit. + if (track_knot_visit && !container.contains(current_offset)) + _globals->visit(id); - // iff all container (until now) are entered at first position - if (record_visits ) { - if (_container.size() > old_stack_depth || (c._flags & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) { - inkAssert(dest_offset >= c._start_offset, "Container stack order is broken"); - // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed - // subcontainers first child check if child_positino is the first child of current container - bool all_entered_at_start = dest_offset - c._start_offset <= 6; - _globals->visit(id, all_entered_at_start); - } + // Update current knot, the knot closest to the top of the stack will end up current. + _current_knot_id = id; + _entered_knot = true; } } -#endif + + // Finally, do the jump + _ptr = dest; } template @@ -1493,9 +1360,8 @@ void runner_impl::step() _container.push(index); // Increment visit count - if (flag & CommandFlag::CONTAINER_MARKER_TRACK_VISITS - || flag & CommandFlag::CONTAINER_MARKER_TRACK_TURNS) { - _globals->visit(index, true); + if (uint8_t(flag) & (uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_VISITS)|uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_TURNS))) { + _globals->visit(index); } if (flag & CommandFlag::CONTAINER_MARKER_IS_KNOT) { _current_knot_id = index; @@ -1505,10 +1371,6 @@ void runner_impl::step() } break; case Command::END_CONTAINER_MARKER: { container_t index = read(); - - if (index != _container.top()) - _container.push(index); - inkAssert(_container.top() == index, "Leaving container we are not in!"); // Move up out of the current container diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index 822c7503..3d34fa17 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -105,41 +105,6 @@ story_impl::~story_impl() const char* story_impl::string(uint32_t index) const { return _string_table + index; } -bool story_impl::iterate_containers( - const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse -) const -{ - if (iterator == nullptr) { - // Empty check - if (_container_list_size == 0) { - return false; - } - // Start - iterator = reverse ? _container_list + (_container_list_size - 1) * 2 : _container_list; - } else { - // Range check - inkAssert( - iterator >= _container_list && iterator <= _container_list + _container_list_size * 2, - "Container fail" - ); - - // Advance - iterator += reverse ? -2 : 2; - - // End? - if (iterator >= _container_list + _container_list_size * 2 || iterator < _container_list) { - iterator = nullptr; - index = 0; - offset = nullptr; - return false; - } - } - - // Get metadata - index = *(iterator + 1); - offset = *iterator + instructions(); - return true; -} bool story_impl::find_container_id(uint32_t offset, container_t& container_id) const { @@ -177,7 +142,13 @@ container_t story_impl::find_container_for(uint32_t offset) const // Container map contains offsets in even slots, container ids in odd. const uint32_t *iter = upper_bound(_container_list, _container_list_size, offset); - // If we're not inside the container, walk out to find the actual parent. + // The last container command before the offset could be either the start of a container + // (in which case the offset is contained within) or the end of a container, in which case + // the offset is inside that container's parent. + + // If we're not inside the container, walk out to find the actual parent. Normally we'd + // know that the parent contained the child, but the containers are sparse so we might + // not have anything. container_t id = iter[1]; while (id) { @@ -202,10 +173,10 @@ CommandFlag story_impl::container_flag(ip_t offset) const } ip_t story_impl::find_offset_for(hash_t path) const -{ +{ // Hash map contains hashes in even slots, offsets in odd. const uint32_t count = (_container_hash_end - _container_hash_start) / 2; - const uint32_t *iter = upper_bound(_container_hash_start, count, path); + const hash_t *iter = upper_bound(_container_hash_start, count, path); return iter[0] == path ? _instruction_data + iter[1] : nullptr; } @@ -388,6 +359,8 @@ void story_impl::setup_pointers() _containers[id]._flags = CommandFlag(_instruction_data[offset+1]); _containers[id]._parent = stack[depth]; + inkAssert(_containers[id]._flags != CommandFlag(0)); + stack[++depth] = id; } else diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index cddeb72a..272398ff 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -40,10 +40,6 @@ class story_impl : public story const char* list_meta() const { return _list_meta; } - bool iterate_containers( - const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse = false - ) const; - // Find the innermost container containing offset. If offset is the start of a container, return that container. container_t find_container_for(uint32_t offset) const; @@ -65,6 +61,9 @@ class story_impl : public story /// Container hash. uint32_t _hash; + + /// Does this container contain a given instruction offset? + bool contains(uint32_t offset) const { return _start_offset <= offset && _end_offset >= offset; } }; // Look up the details of the given container From 7ce47291c43385c268e915fb7124a46c1e00ff21 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Tue, 18 Nov 2025 00:27:06 +1300 Subject: [PATCH 07/32] Use ~0 to delimit containers. More correctness about whether we have a root container. Generate sorted hash in compile step, remove from runtime. --- inkcpp/runner_impl.cpp | 14 +++-- inkcpp/story_impl.cpp | 88 +++++++++++++----------------- inkcpp_compiler/binary_emitter.cpp | 25 ++++++--- inkcpp_compiler/binary_emitter.h | 11 +++- 4 files changed, 74 insertions(+), 64 deletions(-) diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 03308b13..14113703 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -308,18 +308,22 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) if (dest == _ptr) return; - // Where are we now? + // Record location and jump. const uint32_t current_offset = _ptr - _story->instructions(); + _ptr = dest; // Find the container at or before dest, which will become the top of the post-jump stack. const uint32_t dest_offset = dest - _story->instructions(); const container_t dest_id = _story->find_container_for(dest_offset); - const story_impl::Container& dest_container = _story->container(dest_id); + + // If there's no destination container, stop. + if (dest_id == ~0) + return; // Count stack depth and assemble stack in reverse order by traversing container tree. container_t stack[64]; uint32_t depth = 0; - for (container_t id = dest_id; id != 0; id = _story->container(id)._parent) { + for (container_t id = dest_id; id != ~0; id = _story->container(id)._parent) { inkAssert(depth < 64); stack[depth++] = id; } @@ -329,6 +333,7 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) _container.pop(); // Are we entering the new container at its start? + const story_impl::Container& dest_container = _story->container(dest_id); const bool jump_to_start = dest_offset == dest_container._start_offset; // Update visit counts for new containers on the stack. If we jump directly to the start of a container, @@ -354,9 +359,6 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) _entered_knot = true; } } - - // Finally, do the jump - _ptr = dest; } template diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index 3d34fa17..a9c7b220 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -119,6 +119,9 @@ bool story_impl::find_container_id(uint32_t offset, container_t& container_id) c // Assumes 2*count u32s, with the sorted values in the even slots. The odd slots can have any payload. static const uint32_t *upper_bound(const uint32_t *sorted, uint32_t count, uint32_t target) { + if (count == 0) + return nullptr; + uint32_t begin = 0; uint32_t end = count; @@ -149,8 +152,8 @@ container_t story_impl::find_container_for(uint32_t offset) const // If we're not inside the container, walk out to find the actual parent. Normally we'd // know that the parent contained the child, but the containers are sparse so we might // not have anything. - container_t id = iter[1]; - while (id) + container_t id = iter ? iter[1] : ~0; + while (id != ~0) { const Container& c = container(id); if (c._start_offset <= offset && c._end_offset >= offset) @@ -336,64 +339,51 @@ void story_impl::setup_pointers() // After strings comes instruction data _instruction_data = ( ip_t ) ptr; + if (_num_containers) { + container_t *stack = new container_t[_num_containers]; + uint32_t depth = 0; + stack[depth] = ~0; - container_t *stack = new container_t[_num_containers]; - uint32_t depth = 0; - stack[depth] = 0; - - // Build acceleration structure for containers. - _containers = new Container[_num_containers]; - for (uint32_t c = 0; c < _container_list_size; ++c) - { - const uint32_t *iter = _container_list + 2 * c; - const container_t id = iter[1]; - const uint32_t offset = iter[0]; - - const Command command = Command(_instruction_data[offset]); - - inkAssert(command == Command::START_CONTAINER_MARKER || command == Command::END_CONTAINER_MARKER); - - if (command == Command::START_CONTAINER_MARKER) + // Build acceleration structure for containers. + _containers = new Container[_num_containers]; + for (uint32_t c = 0; c < _container_list_size; ++c) { - _containers[id]._start_offset = offset; - _containers[id]._flags = CommandFlag(_instruction_data[offset+1]); - _containers[id]._parent = stack[depth]; + const uint32_t *iter = _container_list + 2 * c; + const container_t id = iter[1]; + const uint32_t offset = iter[0]; - inkAssert(_containers[id]._flags != CommandFlag(0)); + const Command command = Command(_instruction_data[offset]); - stack[++depth] = id; - } - else - { - _containers[stack[depth]]._end_offset = offset; - --depth; - } + inkAssert(command == Command::START_CONTAINER_MARKER || command == Command::END_CONTAINER_MARKER); - for (uint32_t *h = _container_hash_start; h < _container_hash_end; h += 2) - { - if (h[1] == offset) + if (command == Command::START_CONTAINER_MARKER) { - _containers[id]._hash = h[0]; - break; - } - } - } + _containers[id]._start_offset = offset; + _containers[id]._flags = CommandFlag(_instruction_data[offset+1]); + _containers[id]._parent = stack[depth]; - delete[] stack; + inkAssert(_containers[id]._flags != CommandFlag(0)); - // Sort container hash so we can use a binary search. - struct Hash - { - hash_t _hash; - hash_t _offset; + stack[++depth] = id; + } + else + { + _containers[stack[depth]]._end_offset = offset; + --depth; + } - static int __cdecl compare(const void* lhs, const void *rhs) - { - return static_cast(lhs)->_hash - static_cast(rhs)->_hash; + for (uint32_t *h = _container_hash_start; h < _container_hash_end; h += 2) + { + if (h[1] == offset) + { + _containers[id]._hash = h[0]; + break; + } + } } - }; - qsort(reinterpret_cast(_container_hash_start), (_container_hash_end-_container_hash_start)/2, sizeof(Hash), &Hash::compare); + delete[] stack; + } // Debugging info /*{ diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index 4f281843..595ec888 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #ifndef _MSC_VER # include @@ -398,27 +399,35 @@ void binary_emitter::write_container_map( void binary_emitter::write_container_hash_map(std::ostream& out) { - write_container_hash_map(out, "", _root); + std::vector hash; + hash.reserve(256); + build_container_hash_map(hash, "", _root); + + // Sort map on ascending hash code. + std::sort(hash.begin(), hash.end()); + + // Write + out.write(reinterpret_cast(&*hash.begin()), hash.size() * sizeof(container_hash_t)); } -void binary_emitter::write_container_hash_map( - std::ostream& out, const std::string& name, const container_data* context +void binary_emitter::build_container_hash_map( + std::vector& hash, const std::string& name, const container_data* context ) { for (auto child : context->named_children) { // Get the child's name in the hierarchy std::string child_name = name.empty() ? child.first : (name + "." + child.first); hash_t name_hash = hash_string(child_name.c_str()); - // Write out name hash and offset - out.write(( const char* ) &name_hash, sizeof(hash_t)); - out.write(( const char* ) &child.second->offset, sizeof(uint32_t)); + + // Append the name hash and offset + hash.push_back( {name_hash, child.second->offset} ); // Recurse - write_container_hash_map(out, child_name, child.second); + build_container_hash_map(hash, child_name, child.second); } for (auto child : context->indexed_children) { - write_container_hash_map(out, name, child.second); + build_container_hash_map(hash, name, child.second); } } diff --git a/inkcpp_compiler/binary_emitter.h b/inkcpp_compiler/binary_emitter.h index a03137af..3edf6693 100644 --- a/inkcpp_compiler/binary_emitter.h +++ b/inkcpp_compiler/binary_emitter.h @@ -48,7 +48,16 @@ namespace ink::compiler::internal void process_paths(); void write_container_map(std::ostream&, const container_map&, container_t); void write_container_hash_map(std::ostream&); - void write_container_hash_map(std::ostream&, const std::string&, const container_data*); + + struct container_hash_t + { + uint32_t _hash; + uint32_t _offset; + + bool operator<(const container_hash_t& other) const { return _hash < other._hash; } + }; + + void build_container_hash_map(std::vector& hash, const std::string&, const container_data*); private: container_data* _root; From 461a652060d1273032a76070c1d3e4ae2ea961f5 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:30:46 +1300 Subject: [PATCH 08/32] Use explicit types in compiled data And share with the runtime. Introduced container_data_t, container_hash_t, container_map_t and used to emit data. Still need to generate the container data at compile time. --- inkcpp/runner_impl.cpp | 9 ++-- inkcpp/story_impl.cpp | 71 +++++++++++++++--------------- inkcpp/story_impl.h | 41 ++++++----------- inkcpp_compiler/binary_emitter.cpp | 23 +++++----- inkcpp_compiler/binary_emitter.h | 10 +---- inkcpp_compiler/emitter.cpp | 12 ++--- inkcpp_compiler/emitter.h | 4 +- shared/private/header.h | 50 +++++++++++++++++++++ 8 files changed, 126 insertions(+), 94 deletions(-) diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 14113703..f9d1d87a 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -45,7 +45,7 @@ namespace ink::runtime::internal hash_t runner_impl::get_current_knot() const { - return _current_knot_id == ~0 ? 0 : _story->container(_current_knot_id)._hash; + return _current_knot_id == ~0 ? 0 : _story->container_data(_current_knot_id)._hash; } template<> @@ -323,7 +323,7 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) // Count stack depth and assemble stack in reverse order by traversing container tree. container_t stack[64]; uint32_t depth = 0; - for (container_t id = dest_id; id != ~0; id = _story->container(id)._parent) { + for (container_t id = dest_id; id != ~0; id = _story->container_data(id)._parent) { inkAssert(depth < 64); stack[depth++] = id; } @@ -333,7 +333,8 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) _container.pop(); // Are we entering the new container at its start? - const story_impl::Container& dest_container = _story->container(dest_id); + using container_data_t = ink::internal::container_data_t; + const container_data_t& dest_container = _story->container_data(dest_id); const bool jump_to_start = dest_offset == dest_container._start_offset; // Update visit counts for new containers on the stack. If we jump directly to the start of a container, @@ -348,7 +349,7 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) // Named knots/stitches need special handling - their visit counts are updated wherever the story enters them, // and we always need to know which knot we're in for tagging. - const story_impl::Container& container = _story->container(id); + const container_data_t& container = _story->container_data(id); if (container._flags & CommandFlag::CONTAINER_MARKER_IS_KNOT) { // If the previous IP wasn't in this container, record the new visit. if (track_knot_visit && !container.contains(current_offset)) diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index a9c7b220..cd706694 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -112,12 +112,12 @@ bool story_impl::find_container_id(uint32_t offset, container_t& container_id) c container_id = find_container_for(offset); // Exact match? - return container(container_id)._start_offset == offset; + return container_data(container_id)._start_offset == offset; } // Search sorted looking for the target or the largest value smaller than target. -// Assumes 2*count u32s, with the sorted values in the even slots. The odd slots can have any payload. -static const uint32_t *upper_bound(const uint32_t *sorted, uint32_t count, uint32_t target) +template +static const entry* upper_bound(const entry *sorted, uint32_t count, uint32_t key) { if (count == 0) return nullptr; @@ -127,9 +127,9 @@ static const uint32_t *upper_bound(const uint32_t *sorted, uint32_t count, uint3 while (begin < end) { const uint32_t mid = begin + (end - begin + 1) / 2; - const uint32_t mid_offset = sorted[mid * 2 + 0]; + const uint32_t mid_key = sorted[mid].key(); - if (mid_offset > target) + if (mid_key > key) // Look below end = mid - 1; else @@ -137,13 +137,13 @@ static const uint32_t *upper_bound(const uint32_t *sorted, uint32_t count, uint3 begin = mid; } - return sorted + begin * 2; + return sorted + begin; } container_t story_impl::find_container_for(uint32_t offset) const { // Container map contains offsets in even slots, container ids in odd. - const uint32_t *iter = upper_bound(_container_list, _container_list_size, offset); + const container_map_t* entry = upper_bound(_container_map, _container_map_size, offset); // The last container command before the offset could be either the start of a container // (in which case the offset is contained within) or the end of a container, in which case @@ -152,14 +152,14 @@ container_t story_impl::find_container_for(uint32_t offset) const // If we're not inside the container, walk out to find the actual parent. Normally we'd // know that the parent contained the child, but the containers are sparse so we might // not have anything. - container_t id = iter ? iter[1] : ~0; + container_t id = entry ? entry->_id : ~0; while (id != ~0) { - const Container& c = container(id); - if (c._start_offset <= offset && c._end_offset >= offset) + const container_data_t& data = container_data(id); + if (data._start_offset <= offset && data._end_offset >= offset) return id; - id = c._parent; + id = data._parent; } return id; @@ -178,10 +178,9 @@ CommandFlag story_impl::container_flag(ip_t offset) const ip_t story_impl::find_offset_for(hash_t path) const { // Hash map contains hashes in even slots, offsets in odd. - const uint32_t count = (_container_hash_end - _container_hash_start) / 2; - const hash_t *iter = upper_bound(_container_hash_start, count, path); + const container_hash_t* entry = upper_bound(_container_hash, _container_hash_size, path); - return iter[0] == path ? _instruction_data + iter[1] : nullptr; + return entry && entry->_hash == path ? _instruction_data + entry->_offset : nullptr; } globals story_impl::new_globals() @@ -310,30 +309,30 @@ void story_impl::setup_pointers() ptr += sizeof(uint32_t); // Pass over the container data - _container_list_size = 0; - _container_list = ( uint32_t* ) (ptr); + _container_map_size = 0; + _container_map = reinterpret_cast(ptr); while (true) { uint32_t val = *( uint32_t* ) ptr; if (val == ~0) { + _container_map_size = reinterpret_cast(ptr) - _container_map; ptr += sizeof(uint32_t); break; - } else { - ptr += sizeof(uint32_t) * 2; - _container_list_size++; } + + ptr += sizeof(container_map_t); } // Next is the container hash map - _container_hash_start = ( hash_t* ) (ptr); + _container_hash = reinterpret_cast(ptr); while (true) { uint32_t val = *( uint32_t* ) ptr; if (val == ~0) { - _container_hash_end = ( hash_t* ) (ptr); + _container_hash_size = reinterpret_cast(ptr) - _container_hash; ptr += sizeof(uint32_t); break; } - ptr += sizeof(uint32_t) * 2; + ptr += sizeof(container_hash_t); } // After strings comes instruction data @@ -345,12 +344,12 @@ void story_impl::setup_pointers() stack[depth] = ~0; // Build acceleration structure for containers. - _containers = new Container[_num_containers]; - for (uint32_t c = 0; c < _container_list_size; ++c) + _container_data = new container_data_t[_num_containers]; + for (uint32_t c = 0; c < _container_map_size; ++c) { - const uint32_t *iter = _container_list + 2 * c; - const container_t id = iter[1]; - const uint32_t offset = iter[0]; + const container_map_t& entry = _container_map[c]; + const container_t id = entry._id; + const uint32_t offset = entry._offset; const Command command = Command(_instruction_data[offset]); @@ -358,25 +357,27 @@ void story_impl::setup_pointers() if (command == Command::START_CONTAINER_MARKER) { - _containers[id]._start_offset = offset; - _containers[id]._flags = CommandFlag(_instruction_data[offset+1]); - _containers[id]._parent = stack[depth]; + container_data_t& data = const_cast(_container_data[id]); + data._start_offset = offset; + data._flags = CommandFlag(_instruction_data[offset+1]); + data._parent = stack[depth]; - inkAssert(_containers[id]._flags != CommandFlag(0)); + inkAssert(_container_data[id]._flags != CommandFlag(0)); stack[++depth] = id; } else { - _containers[stack[depth]]._end_offset = offset; + const_cast(_container_data[stack[depth]])._end_offset = offset; --depth; } - for (uint32_t *h = _container_hash_start; h < _container_hash_end; h += 2) + for (uint32_t h = 0; h < _container_hash_size; ++h) { - if (h[1] == offset) + const container_hash_t& entry = _container_hash[h]; + if (entry._offset == offset) { - _containers[id]._hash = h[0]; + const_cast(_container_data[id])._hash = entry._hash; break; } } diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index 272398ff..bec21d1b 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -46,31 +46,15 @@ class story_impl : public story // Find the container which starts exactly at offset. Return false if this isn't the start of a container. bool find_container_id(uint32_t offset, container_t& container_id) const; - // Container description. - struct Container - { - /// Container flags (saves looking up via instruction data) - CommandFlag _flags : 4; - - /// Instruction offset to the start of this container. - uint32_t _start_offset : 28; - uint32_t _end_offset; - - /// Parent container, or ~0 if this is the root. - container_t _parent; - - /// Container hash. - uint32_t _hash; - - /// Does this container contain a given instruction offset? - bool contains(uint32_t offset) const { return _start_offset <= offset && _end_offset >= offset; } - }; + using container_data_t = ink::internal::container_data_t; + using container_hash_t = ink::internal::container_hash_t; + using container_map_t = ink::internal::container_map_t; // Look up the details of the given container - const Container& container(container_t id) const { inkAssert(id < _num_containers); return _containers[id]; } + const container_data_t& container_data(container_t id) const { inkAssert(id < _num_containers); return _container_data[id]; } // Look up the instruction pointer for the start of the given container - ip_t container_offset(container_t id) const { return _instruction_data + container(id)._start_offset; } + ip_t container_offset(container_t id) const { return _instruction_data + container_data(id)._start_offset; } // Get container flag from container offset (either start or end) CommandFlag container_flag(ip_t offset) const; @@ -102,16 +86,17 @@ class story_impl : public story const char* _list_meta; const list_flag* _lists; - // container info - uint32_t* _container_list; - uint32_t _container_list_size; + // Information about containers. + const container_data_t* _container_data; uint32_t _num_containers; - Container *_containers; + // How to find containers from instruction offsets. + const container_map_t* _container_map; + uint32_t _container_map_size; - // container hashes - hash_t* _container_hash_start; - hash_t* _container_hash_end; + // How to find containers from string hashes. + const container_hash_t *_container_hash; + uint32_t _container_hash_size; // instruction info ip_t _instruction_data; diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index 595ec888..71cda5e4 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -391,27 +391,26 @@ void binary_emitter::write_container_map( out.write(reinterpret_cast(&num), sizeof(container_t)); // Write out entries - for (const auto& pair : map) { - out.write(( const char* ) &pair.first, sizeof(uint32_t)); - out.write(( const char* ) &pair.second, sizeof(uint32_t)); + for (const auto& entry : map) { + out.write(reinterpret_cast(&entry), sizeof(entry)); } } void binary_emitter::write_container_hash_map(std::ostream& out) { - std::vector hash; - hash.reserve(256); - build_container_hash_map(hash, "", _root); + std::vector hash_map; + hash_map.reserve(256); + build_container_hash_map(hash_map, "", _root); // Sort map on ascending hash code. - std::sort(hash.begin(), hash.end()); + std::sort(hash_map.begin(), hash_map.end()); // Write - out.write(reinterpret_cast(&*hash.begin()), hash.size() * sizeof(container_hash_t)); + out.write(reinterpret_cast(&*hash_map.begin()), hash_map.size() * sizeof(container_hash_t)); } void binary_emitter::build_container_hash_map( - std::vector& hash, const std::string& name, const container_data* context + std::vector& hash_map, const std::string& name, const container_data* context ) { for (auto child : context->named_children) { @@ -420,14 +419,14 @@ void binary_emitter::build_container_hash_map( hash_t name_hash = hash_string(child_name.c_str()); // Append the name hash and offset - hash.push_back( {name_hash, child.second->offset} ); + hash_map.push_back( {name_hash, child.second->offset} ); // Recurse - build_container_hash_map(hash, child_name, child.second); + build_container_hash_map(hash_map, child_name, child.second); } for (auto child : context->indexed_children) { - build_container_hash_map(hash, name, child.second); + build_container_hash_map(hash_map, name, child.second); } } diff --git a/inkcpp_compiler/binary_emitter.h b/inkcpp_compiler/binary_emitter.h index 3edf6693..17681607 100644 --- a/inkcpp_compiler/binary_emitter.h +++ b/inkcpp_compiler/binary_emitter.h @@ -8,6 +8,7 @@ #include "emitter.h" #include "binary_stream.h" +#include "header.h" namespace ink::compiler::internal { @@ -49,14 +50,7 @@ namespace ink::compiler::internal void write_container_map(std::ostream&, const container_map&, container_t); void write_container_hash_map(std::ostream&); - struct container_hash_t - { - uint32_t _hash; - uint32_t _offset; - - bool operator<(const container_hash_t& other) const { return _hash < other._hash; } - }; - + using container_hash_t = ink::internal::container_hash_t; void build_container_hash_map(std::vector& hash, const std::string&, const container_data*); private: diff --git a/inkcpp_compiler/emitter.cpp b/inkcpp_compiler/emitter.cpp index 6c7d99a0..c116b322 100644 --- a/inkcpp_compiler/emitter.cpp +++ b/inkcpp_compiler/emitter.cpp @@ -35,14 +35,14 @@ namespace ink::compiler::internal { if (_container_map.rbegin() != _container_map.rend()) { - if (_container_map.rbegin()->first > offset) + if (_container_map.rbegin()->_offset > offset) { warn() << "Container map written out of order. Wrote container at offset " - << offset << " after container with offset " << _container_map.rbegin()->first << std::flush; + << offset << " after container with offset " << _container_map.rbegin()->_offset << std::flush; } } - _container_map.push_back(std::make_pair(offset, index)); + _container_map.push_back( {offset, index} ); setContainerIndex(index); } @@ -50,13 +50,13 @@ namespace ink::compiler::internal { if (_container_map.rbegin() != _container_map.rend()) { - if (_container_map.rbegin()->first > offset) + if (_container_map.rbegin()->_offset > offset) { warn() << "Container map written out of order. Wrote container at offset " - << offset << " after container with offset " << _container_map.rbegin()->first << std::flush; + << offset << " after container with offset " << _container_map.rbegin()->_offset << std::flush; } } - _container_map.push_back(std::make_pair(offset, index)); + _container_map.push_back( {offset, index} ); } } diff --git a/inkcpp_compiler/emitter.h b/inkcpp_compiler/emitter.h index ae1458d4..96d0f2a9 100644 --- a/inkcpp_compiler/emitter.h +++ b/inkcpp_compiler/emitter.h @@ -9,6 +9,7 @@ #include "command.h" #include "system.h" #include "reporter.h" +#include "header.h" #include #include @@ -92,7 +93,8 @@ namespace ink::compiler::internal virtual void setContainerIndex(container_t index) = 0; protected: - typedef std::vector> container_map; + using container_map_t = ink::internal::container_map_t; + typedef std::vector container_map; // container map container_map _container_map; diff --git a/shared/private/header.h b/shared/private/header.h index aac8a541..288185b7 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -7,6 +7,7 @@ #pragma once #include "system.h" +#include "command.h" namespace ink::internal { @@ -43,4 +44,53 @@ namespace ink::internal { /// differ between platforms sizeof(uint16_t) + 2 * sizeof(uint32_t); }; + + // One entry in the container hash. Used to translate paths into story locations. + struct container_hash_t + { + // Hash of the container's path string. + hash_t _hash; + + // Offset to the start of this container. + uint32_t _offset; + + uint32_t key() const { return _hash; } + bool operator<(const container_hash_t& other) const { return _hash < other._hash; } + }; + + // One entry in the container map. Used to work out which container a story location is in. + struct container_map_t + { + // Offset to the start of this container's instructions. + uint32_t _offset; + + // Container index. + container_t _id; + + uint32_t key() const { return _offset; } + bool operator<(const container_map_t& other) const { return _offset < other._offset; } + }; + + // One entry in the container data. Describes containers. + struct container_data_t + { + /// Parent container, or ~0 if this is the root. + // TODO: Pack into 28 with explicit invalid container_t, since we expect fewer containers than instructions. + container_t _parent; + + /// Container flags (saves looking up via instruction data) + CommandFlag _flags : 4; + + /// Instruction offset to the start instruction (enter marker) of this container. + uint32_t _start_offset : 28; + + /// Instruction offset to the end instruction (leave marker) of this container + uint32_t _end_offset; + + /// Container hash. + uint32_t _hash; + + /// Check to see if the instruction offset is part of the instructions for this container. Note that this is inclusive not exclusive. + bool contains(uint32_t offset) const { return offset >= _start_offset && offset <= _end_offset; } + }; } From 2f2c30899caa1b5c40b6e41b31a25cd7a8b37509 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Wed, 19 Nov 2025 00:04:59 +1300 Subject: [PATCH 09/32] Write container data during compilation. Define aligned sections in header so we don't need to parse the data on load. Use defined sized types for header and contents. Expand first field to u32 so we don't get any padding. Remove vestigial Endian-swapping. Add new container data section and remove runtime setup. --- inkcpp/header.cpp | 38 ++---- inkcpp/runner_impl.cpp | 3 - inkcpp/story_impl.cpp | 178 +++++++-------------------- inkcpp/story_impl.h | 22 ++-- inkcpp_compiler/binary_emitter.cpp | 189 ++++++++++++++++++----------- inkcpp_compiler/binary_emitter.h | 16 ++- shared/private/header.h | 69 +++++++---- shared/public/version.h | 2 +- 8 files changed, 243 insertions(+), 274 deletions(-) diff --git a/inkcpp/header.cpp b/inkcpp/header.cpp index e99d08cc..9c580b07 100644 --- a/inkcpp/header.cpp +++ b/inkcpp/header.cpp @@ -9,36 +9,24 @@ namespace ink::internal { - header header::parse_header(const char *data) + bool header::verify() { - header res; - const char* ptr = data; - res.endien = *reinterpret_cast(ptr); - ptr += sizeof(header::endian_types); - - using v_t = decltype(header::ink_version_number); - using vcpp_t = decltype(header::ink_bin_version_number); - - if (res.endien == header::endian_types::same) { - res.ink_version_number = - *reinterpret_cast(ptr); - ptr += sizeof(v_t); - res.ink_bin_version_number = - *reinterpret_cast(ptr); + if (endian() == endian_types::none) { + inkFail("Header magic number was wrong!"); + return false; + } - } else if (res.endien == header::endian_types::differ) { - res.ink_version_number = - swap_bytes(*reinterpret_cast(ptr)); - ptr += sizeof(v_t); - res.ink_bin_version_number = - swap_bytes(*reinterpret_cast(ptr)); - } else { - inkFail("Failed to parse endian encoding!"); + if (endian() == endian_types::differ) { + inkFail("Can't load content with different endian-ness!"); + return false; } - if (res.ink_bin_version_number != InkBinVersion) { + if (ink_bin_version_number != InkBinVersion) { inkFail("InkCpp-version mismatch: file was compiled with different InkCpp-version!"); + return false; } - return res; + + return true; } + } diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index f9d1d87a..0ab1782b 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -173,9 +173,6 @@ inline T runner_impl::read() // Read memory T val = *( const T* ) _ptr; - if (_story->get_header().endien == header::endian_types::differ) { - val = header::swap_bytes(val); - } // Advance ip _ptr += sizeof(T); diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index cd706694..a90f5ff3 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -236,156 +236,66 @@ runner story_impl::new_runner_from_snapshot(const snapshot& data, globals store, void story_impl::setup_pointers() { using header = ink::internal::header; - _header = header::parse_header(reinterpret_cast(_file)); + _header = *reinterpret_cast(_file); + if (!_header.verify()) + return; - // String table is after the header - _string_table = ( char* ) _file + header::Size; + // Locate sections + if (_header._strings._bytes) + _string_table = reinterpret_cast(_file + _header._strings._start); - // Pass over strings - const char* ptr = _string_table; - if (*ptr == 0) // SPECIAL: No strings + if (_header._lists._bytes) { - ptr++; - } else - while (true) { - // Read until null terminator - while (*ptr != 0) - ptr++; - - // Check next character - ptr++; - - // Second null. Strings are done. - if (*ptr == 0) { - ptr++; - break; - } - } + _list_meta = reinterpret_cast(_file + _header._lists._start); - // check if lists are defined - _list_meta = ptr; - if (list_flag flag = _header.read_list_flag(ptr); flag != null_flag) { - // skip list definitions - auto list_id = flag.list_id; - while (*ptr != 0) { - ++ptr; - } - ++ptr; // skip list name - do { - if (flag.list_id != list_id) { - list_id = flag.list_id; - while (*ptr != 0) { - ++ptr; - } - ++ptr; // skip list name - } + const char *ptr = _list_meta; + if (list_flag flag = _header.read_list_flag(ptr); flag != null_flag) { + // skip list definitions + auto list_id = flag.list_id; while (*ptr != 0) { ++ptr; } - ++ptr; // skip flag name - } while ((flag = _header.read_list_flag(ptr)) != null_flag); - - _lists = reinterpret_cast(ptr); - // skip predefined lists - while (_header.read_list_flag(ptr) != null_flag) { - while (_header.read_list_flag(ptr) != null_flag) - ; - } - } else { - _list_meta = nullptr; - _lists = nullptr; - } - inkAssert( - _header.ink_bin_version_number == ink::InkBinVersion, - "invalid InkBinVerison! currently: %i you used %i", ink::InkBinVersion, - _header.ink_bin_version_number - ); - inkAssert( - _header.endien == header::endian_types::same, "different endien support not yet implemented" - ); - - - _num_containers = *( uint32_t* ) (ptr); - ptr += sizeof(uint32_t); + ++ptr; // skip list name + do { + if (flag.list_id != list_id) { + list_id = flag.list_id; + while (*ptr != 0) { + ++ptr; + } + ++ptr; // skip list name + } + while (*ptr != 0) { + ++ptr; + } + ++ptr; // skip flag name + } while ((flag = _header.read_list_flag(ptr)) != null_flag); - // Pass over the container data - _container_map_size = 0; - _container_map = reinterpret_cast(ptr); - while (true) { - uint32_t val = *( uint32_t* ) ptr; - if (val == ~0) { - _container_map_size = reinterpret_cast(ptr) - _container_map; - ptr += sizeof(uint32_t); - break; + _lists = reinterpret_cast(ptr); + inkAssert(ptr - _list_meta <= _header._lists._bytes); } - - ptr += sizeof(container_map_t); } - // Next is the container hash map - _container_hash = reinterpret_cast(ptr); - while (true) { - uint32_t val = *( uint32_t* ) ptr; - if (val == ~0) { - _container_hash_size = reinterpret_cast(ptr) - _container_hash; - ptr += sizeof(uint32_t); - break; - } - - ptr += sizeof(container_hash_t); + if (_header._containers._bytes) + { + _num_containers = _header._containers._bytes / sizeof(container_data_t); + _container_data = reinterpret_cast(_file + _header._containers._start); } - // After strings comes instruction data - _instruction_data = ( ip_t ) ptr; - - if (_num_containers) { - container_t *stack = new container_t[_num_containers]; - uint32_t depth = 0; - stack[depth] = ~0; - - // Build acceleration structure for containers. - _container_data = new container_data_t[_num_containers]; - for (uint32_t c = 0; c < _container_map_size; ++c) - { - const container_map_t& entry = _container_map[c]; - const container_t id = entry._id; - const uint32_t offset = entry._offset; - - const Command command = Command(_instruction_data[offset]); - - inkAssert(command == Command::START_CONTAINER_MARKER || command == Command::END_CONTAINER_MARKER); - - if (command == Command::START_CONTAINER_MARKER) - { - container_data_t& data = const_cast(_container_data[id]); - data._start_offset = offset; - data._flags = CommandFlag(_instruction_data[offset+1]); - data._parent = stack[depth]; - - inkAssert(_container_data[id]._flags != CommandFlag(0)); - - stack[++depth] = id; - } - else - { - const_cast(_container_data[stack[depth]])._end_offset = offset; - --depth; - } - - for (uint32_t h = 0; h < _container_hash_size; ++h) - { - const container_hash_t& entry = _container_hash[h]; - if (entry._offset == offset) - { - const_cast(_container_data[id])._hash = entry._hash; - break; - } - } - } + if (_header._container_map._bytes) + { + _container_map_size = _header._container_map._bytes / sizeof(container_map_t); + _container_map = reinterpret_cast(_file + _header._container_map._start); + } - delete[] stack; + if (_header._container_hash._bytes) + { + _container_hash_size = _header._container_hash._bytes / sizeof(container_hash_t); + _container_hash = reinterpret_cast(_file + _header._container_hash._start); } + if (_header._instructions._bytes) + _instruction_data = _file + _header._instructions._start; + // Debugging info /*{ const uint32_t* iter = nullptr; diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index bec21d1b..a7328978 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -75,31 +75,31 @@ class story_impl : public story private: // file information - unsigned char* _file; + uint8_t* _file; size_t _length; ink::internal::header _header; // string table - const char* _string_table; + const char* _string_table = nullptr; - const char* _list_meta; - const list_flag* _lists; + const char* _list_meta = nullptr; + const list_flag* _lists = nullptr; // Information about containers. - const container_data_t* _container_data; - uint32_t _num_containers; + const container_data_t* _container_data = nullptr; + uint32_t _num_containers = 0; // How to find containers from instruction offsets. - const container_map_t* _container_map; - uint32_t _container_map_size; + const container_map_t* _container_map = nullptr; + uint32_t _container_map_size = 0; // How to find containers from string hashes. - const container_hash_t *_container_hash; - uint32_t _container_hash_size; + const container_hash_t *_container_hash = nullptr; + uint32_t _container_hash_size = 0; // instruction info - ip_t _instruction_data; + ip_t _instruction_data = nullptr; // story block used to creat various weak pointers ref_block* _block; diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index 71cda5e4..915adda2 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -102,7 +102,7 @@ uint32_t binary_emitter::start_container(int index_in_parent, const std::string& container->parent = _current; // Set offset to the current position - container->offset = _containers.pos(); + container->offset = _instructions.pos(); // Add to parents lists if (_current != nullptr) { @@ -118,17 +118,17 @@ uint32_t binary_emitter::start_container(int index_in_parent, const std::string& _current = container; // Return current position - return _containers.pos(); + return _instructions.pos(); } uint32_t binary_emitter::end_container() { // Move up the chain - _current->end_offset = _containers.pos(); + _current->end_offset = _instructions.pos(); _current = _current->parent; // Return offset - return _containers.pos(); + return _instructions.pos(); } int binary_emitter::function_container_arguments(const std::string& name) @@ -142,11 +142,11 @@ int binary_emitter::function_container_arguments(const std::string& name) } size_t offset = fn->second->offset; - byte_t cmd = _containers.get(offset); + byte_t cmd = _instructions.get(offset); int arity = 0; while (static_cast(cmd) == Command::DEFINE_TEMP) { offset += 6; // command(1) + flag(1) + variable_name_hash(4) - cmd = _containers.get(offset); + cmd = _instructions.get(offset); ++arity; } return arity; @@ -156,10 +156,10 @@ void binary_emitter::write_raw( Command command, CommandFlag flag, const char* payload, ink::size_t payload_size ) { - _containers.write(command); - _containers.write(flag); + _instructions.write(command); + _instructions.write(flag); if (payload_size > 0) - _containers.write(( const byte_t* ) payload, payload_size); + _instructions.write(( const byte_t* ) payload, payload_size); } void binary_emitter::write_path( @@ -170,7 +170,7 @@ void binary_emitter::write_path( write(command, ( uint32_t ) 0, flag); // Note the position of this later so we can resolve the paths at the end - size_t param_position = _containers.pos() - sizeof(uint32_t); + size_t param_position = _instructions.pos() - sizeof(uint32_t); bool op = flag & CommandFlag::FALLBACK_FUNCTION; _paths.push_back(std::make_tuple(param_position, path, op, _current, useCountIndex)); } @@ -213,43 +213,80 @@ void binary_emitter::write_list( void binary_emitter::handle_nop(int index_in_parent) { - _current->noop_offsets.insert({index_in_parent, _containers.pos()}); + _current->noop_offsets.insert({index_in_parent, _instructions.pos()}); +} + +template +void binary_emitter::emit_section(std::ostream& stream, const std::vector& data) const +{ + stream.write(reinterpret_cast(&*data.begin()), data.size() * sizeof(type)); + close_section(stream); +} + +void binary_emitter::emit_section(std::ostream& stream, const binary_stream& data) const +{ + inkAssert((stream.tellp() & (ink::internal::header::Alignment-1)) == 0); + data.write_to(stream); + close_section(stream); +} + +void binary_emitter::close_section(std::ostream& stream) const +{ + // Write zeroes until aligned. + while (stream.tellp() % ink::internal::header::Alignment) + stream.put('\0'); } void binary_emitter::output(std::ostream& out) { - // Write the ink version - // TODO: define this order in header? - using header = ink::internal::header; - header::endian_types same = header::endian_types::same; - out.write(( const char* ) &same, sizeof(decltype(same))); - out.write(( const char* ) &_ink_version, sizeof(decltype(_ink_version))); - out.write(( const char* ) &ink::InkBinVersion, sizeof(decltype(ink::InkBinVersion))); + // Create container data + std::vector container_data; + container_data.resize(_max_container_index); + build_container_data(container_data, ~0, _root); - // Write the string table - _strings.write_to(out); + // Create container hash (and write the hashes into the data as well) + std::vector container_hash; + container_hash.reserve(_max_container_index); + build_container_hash_map(container_hash, container_data, "", _root); - // Write a separator - out << ( char ) 0; + // Sort map on ascending hash code. + std::sort(container_hash.begin(), container_hash.end()); + + // Fill in header + ink::internal::header header; + header.ink_version_number = _ink_version; + header.ink_bin_version_number = ink::InkBinVersion; + + // Fill in sections + uint32_t offset = sizeof(header); + header._strings.setup(offset, _strings.pos()); + header._lists.setup(offset, _lists.pos()); + header._containers.setup(offset, container_data.size() * sizeof(container_data_t)); + header._container_map.setup(offset, _container_map.size() * sizeof(container_map_t)); + header._container_hash.setup(offset, container_hash.size() * sizeof(container_hash_t)); + header._instructions.setup(offset, _instructions.pos()); + + // Write the header + out.write(reinterpret_cast(&header), sizeof(header)); + close_section(out); - // Write lists meta data and defined lists - _lists.write_to(out); - // Write a seperator - out.write(reinterpret_cast(&null_flag), sizeof(null_flag)); + // Write the string table + emit_section(out, _strings); - // Write out container map - write_container_map(out, _container_map, _max_container_index); + // Write lists meta data and defined lists + emit_section(out, _lists); - // Write a separator - uint32_t END_MARKER = ~0; - out.write(( const char* ) &END_MARKER, sizeof(uint32_t)); + // Write out container information + emit_section(out, container_data); + // Write out container map + emit_section(out, _container_map); + // Write container hash list - write_container_hash_map(out); - out.write(( const char* ) &END_MARKER, sizeof(uint32_t)); + emit_section(out, container_hash); - // Write the container data - _containers.write_to(out); + // Write the container contents (instruction stream) + emit_section(out, _instructions); // Flush the file out.flush(); @@ -261,7 +298,7 @@ void binary_emitter::initialize() _strings.reset(); _list_count = 0; _lists.reset(); - _containers.reset(); + _instructions.reset(); // clear other data _paths.clear(); @@ -287,13 +324,13 @@ uint32_t binary_emitter::fallthrough_divert() write(Command::DIVERT, ( uint32_t ) 0, CommandFlag::DIVERT_IS_FALLTHROUGH); // Return the location of the divert offset - return _containers.pos() - sizeof(uint32_t); + return _instructions.pos() - sizeof(uint32_t); } void binary_emitter::patch_fallthroughs(uint32_t position) { // Patch - _containers.set(position, _containers.pos()); + _instructions.set(position, _instructions.pos()); } void binary_emitter::process_paths() @@ -364,69 +401,72 @@ void binary_emitter::process_paths() if (noop_offset != ~0) { inkAssert(! useCountIndex, "Can't count visits to a noop!"); - _containers.set(position, noop_offset); + _instructions.set(position, noop_offset); } else { // If we want the count index, write that out if (useCountIndex) { inkAssert(container->counter_index != ~0, "No count index available for this container!"); - _containers.set(position, container->counter_index); + _instructions.set(position, container->counter_index); } else { // Otherwise, write container address if (container == nullptr) { - _containers.set(position, 0); + _instructions.set(position, 0); inkAssert(optional, "Was not able to resolve a not optional path! '%hs'", path.c_str()); } else { - _containers.set(position, container->offset); + _instructions.set(position, container->offset); } } } } } -void binary_emitter::write_container_map( - std::ostream& out, const container_map& map, container_t num -) +void binary_emitter::build_container_data(std::vector& data, container_t parent, const container_data *context) const { - // Write out container count - out.write(reinterpret_cast(&num), sizeof(container_t)); - - // Write out entries - for (const auto& entry : map) { - out.write(reinterpret_cast(&entry), sizeof(entry)); + // Build data for this container + if (context->counter_index != ~0) + { + container_data_t& d = data[context->counter_index]; + d._parent = parent; + d._start_offset = context->offset; + d._end_offset = context->end_offset; + d._flags = CommandFlag(_instructions.get(context->offset + 1)); + + // Since we might be skipping tree levels, we need to be explicit about the parent. + parent = context->counter_index; } + + // Recurse + for (auto child : context->children) + build_container_data(data, parent, child); } -void binary_emitter::write_container_hash_map(std::ostream& out) +void binary_emitter::build_container_hash_map( + std::vector& hash_map, std::vector& data, const std::string& name, const container_data* context +) const { - std::vector hash_map; - hash_map.reserve(256); - build_container_hash_map(hash_map, "", _root); + // If there's a non-empty name, hash it. + if (!name.empty()) + { + // Hash name. We only do this at the named child level. In theory we could support indexed children as well. + // The root is anonymous so the fact that it's skipped is not an issue. + const hash_t name_hash = hash_string(name.c_str()); - // Sort map on ascending hash code. - std::sort(hash_map.begin(), hash_map.end()); + // Store hash in the data. + if (context->counter_index != ~0) { + data[context->counter_index]._hash = name_hash; + } - // Write - out.write(reinterpret_cast(&*hash_map.begin()), hash_map.size() * sizeof(container_hash_t)); -} + // Append the name hash and offset + hash_map.push_back( {name_hash, context->offset} ); + } -void binary_emitter::build_container_hash_map( - std::vector& hash_map, const std::string& name, const container_data* context -) -{ + // Search named children only. for (auto child : context->named_children) { // Get the child's name in the hierarchy std::string child_name = name.empty() ? child.first : (name + "." + child.first); - hash_t name_hash = hash_string(child_name.c_str()); - - // Append the name hash and offset - hash_map.push_back( {name_hash, child.second->offset} ); // Recurse - build_container_hash_map(hash_map, child_name, child.second); - } - - for (auto child : context->indexed_children) { - build_container_hash_map(hash_map, name, child.second); + build_container_hash_map(hash_map, data, child_name, child.second); } } @@ -450,5 +490,8 @@ void binary_emitter::set_list_meta(const list_data& list_defs) _lists.write(reinterpret_cast(flag.name->c_str()), flag.name->size() + 1); } _lists.write(null_flag); + + // Extra terminator since we don't write them between sections. + _lists.write(null_flag); } } // namespace ink::compiler::internal diff --git a/inkcpp_compiler/binary_emitter.h b/inkcpp_compiler/binary_emitter.h index 17681607..5765b4a2 100644 --- a/inkcpp_compiler/binary_emitter.h +++ b/inkcpp_compiler/binary_emitter.h @@ -47,11 +47,19 @@ namespace ink::compiler::internal private: void process_paths(); - void write_container_map(std::ostream&, const container_map&, container_t); - void write_container_hash_map(std::ostream&); + template + void emit_section(std::ostream& out, const std::vector& data) const; + void emit_section(std::ostream& out, const binary_stream& stream) const; + void close_section(std::ostream& out) const; + + using container_data_t = ink::internal::container_data_t; + using container_map_t = ink::internal::container_map_t; using container_hash_t = ink::internal::container_hash_t; - void build_container_hash_map(std::vector& hash, const std::string&, const container_data*); + + void build_container_data(std::vector& data, container_t parent, const container_data* context) const; + + void build_container_hash_map(std::vector& hash, std::vector& data, const std::string&, const container_data* context) const; private: container_data* _root; @@ -61,7 +69,7 @@ namespace ink::compiler::internal binary_stream _strings; uint32_t _list_count = 0; binary_stream _lists; - binary_stream _containers; + binary_stream _instructions; // positon to write address // path as string diff --git a/shared/private/header.h b/shared/private/header.h index 288185b7..81f63ed7 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -12,38 +12,61 @@ namespace ink::internal { struct header { - static header parse_header(const char* data); - template - static T swap_bytes(const T& value) { - char data[sizeof(T)]; - for (int i = 0; i < sizeof(T); ++i) { - data[i] = reinterpret_cast(&value)[sizeof(T)-1-i]; + static constexpr uint32_t InkBinMagic = ('I' << 24) | ('N' << 16) | ('K' << 8) | 'B'; + static constexpr uint32_t InkBinMagic_Differ = ('B' << 24) | ('K' << 16) | ('N' << 8) | 'I'; + static constexpr uint32_t Alignment = 16; + + uint32_t ink_bin_magic = InkBinMagic; + uint16_t ink_version_number = 0; + uint16_t ink_bin_version_number = 0; + + enum class endian_types : uint8_t { + none, + same, + differ + }; + + constexpr endian_types endian() const + { + switch (ink_bin_magic) + { + case InkBinMagic : return endian_types::same; + case InkBinMagic_Differ: return endian_types::differ; + default: return endian_types::none; } - return *reinterpret_cast(data); } + + bool verify(); + + list_flag read_list_flag(const char*& ptr) const { list_flag result = *reinterpret_cast(ptr); ptr += sizeof(list_flag); - if (endien == ink::internal::header::endian_types::differ) { - result.flag = swap_bytes(result.flag); - result.list_id = swap_bytes(result.list_id); - } return result; } - enum class endian_types: uint16_t { - none = 0, - same = 0x0001, - differ = 0x0100 - } endien = endian_types::none; - uint32_t ink_version_number = 0; - uint32_t ink_bin_version_number = 0; - static constexpr size_t Size = ///< actual data size of Header, - /// because padding of struct may - /// differ between platforms - sizeof(uint16_t) + 2 * sizeof(uint32_t); - }; + struct section_t + { + uint32_t _start = 0; + uint32_t _bytes = 0; + + void setup(uint32_t& offset, uint32_t bytes) + { + _start = (offset + Alignment - 1) & ~(Alignment-1); + _bytes = bytes; + offset = _start + _bytes; + } + }; + + // File section sizes. Each section is aligned to Alignment + section_t _strings; + section_t _lists; + section_t _containers; + section_t _container_map; + section_t _container_hash; + section_t _instructions; + }; // One entry in the container hash. Used to translate paths into story locations. struct container_hash_t diff --git a/shared/public/version.h b/shared/public/version.h index db81dc29..62ea2faa 100644 --- a/shared/public/version.h +++ b/shared/public/version.h @@ -9,6 +9,6 @@ #include "system.h" namespace ink { -constexpr uint32_t InkBinVersion = 1; ///< Supportet version of ink.bin files +constexpr uint32_t InkBinVersion = 2; ///< Supportet version of ink.bin files constexpr uint32_t InkVersion = 21; ///< Supported version of ink.json files }; From 7baa7481cb039f9de9a105044caa79e17c6fc07a Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:35:31 +1300 Subject: [PATCH 10/32] Move read_list_flag out of header and make header private to story. --- inkcpp/globals_impl.cpp | 2 +- inkcpp/header.cpp | 2 +- inkcpp/list_table.cpp | 4 ++-- inkcpp/list_table.h | 2 +- inkcpp/story_impl.cpp | 42 ++++++++++++++++++++--------------------- inkcpp/story_impl.h | 4 ---- shared/private/header.h | 8 +------- shared/public/system.h | 6 ++++++ 8 files changed, 33 insertions(+), 37 deletions(-) diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 0bf6a04d..52a65787 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -20,7 +20,7 @@ globals_impl::globals_impl(const story_impl* story) , _visit_counts_backup() , _owner(story) , _runners_start(nullptr) - , _lists(story->list_meta(), story->get_header()) + , _lists(story->list_meta()) , _globals_initialized(false) { _visit_counts.resize(_num_containers); diff --git a/inkcpp/header.cpp b/inkcpp/header.cpp index 9c580b07..88a517ab 100644 --- a/inkcpp/header.cpp +++ b/inkcpp/header.cpp @@ -9,7 +9,7 @@ namespace ink::internal { - bool header::verify() + bool header::verify() const { if (endian() == endian_types::none) { inkFail("Header magic number was wrong!"); diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index 85a756ea..ebaaa7dc 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -32,7 +32,7 @@ void list_table::copy_lists(const data_t* src, data_t* dst) } } -list_table::list_table(const char* data, const ink::internal::header& header) +list_table::list_table(const char* data) : _valid{false} { if (data == nullptr) { @@ -41,7 +41,7 @@ list_table::list_table(const char* data, const ink::internal::header& header) list_flag flag; const char* ptr = data; int start = 0; - while ((flag = header.read_list_flag(ptr)) != null_flag) { + while ((flag = read_list_flag(ptr)) != null_flag) { // start of new list if (_list_end.size() == flag.list_id) { start = _list_end.size() == 0 ? 0 : _list_end.back(); diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index 16b12ee9..d5e40f7a 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -107,7 +107,7 @@ class list_table : public snapshot_interface list& add_inplace(list& lh, list_flag rh); // parse binary list meta data - list_table(const char* data, const ink::internal::header&); + list_table(const char* data); explicit list_table() : _entrySize{0} diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index a90f5ff3..b96bf024 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -235,21 +235,21 @@ runner story_impl::new_runner_from_snapshot(const snapshot& data, globals store, void story_impl::setup_pointers() { - using header = ink::internal::header; - _header = *reinterpret_cast(_file); - if (!_header.verify()) + const ink::internal::header& header = *reinterpret_cast(_file); + if (!header.verify()) return; // Locate sections - if (_header._strings._bytes) - _string_table = reinterpret_cast(_file + _header._strings._start); + if (header._strings._bytes) + _string_table = reinterpret_cast(_file + header._strings._start); - if (_header._lists._bytes) + if (header._lists._bytes) { - _list_meta = reinterpret_cast(_file + _header._lists._start); + _list_meta = reinterpret_cast(_file + header._lists._start); + // SIL: TODO: Remove this, since it has to get parsed in _lists/_globals. Just needs an interior offset or a separate section from the compiler. const char *ptr = _list_meta; - if (list_flag flag = _header.read_list_flag(ptr); flag != null_flag) { + if (list_flag flag = read_list_flag(ptr); flag != null_flag) { // skip list definitions auto list_id = flag.list_id; while (*ptr != 0) { @@ -268,33 +268,33 @@ void story_impl::setup_pointers() ++ptr; } ++ptr; // skip flag name - } while ((flag = _header.read_list_flag(ptr)) != null_flag); + } while ((flag = read_list_flag(ptr)) != null_flag); _lists = reinterpret_cast(ptr); - inkAssert(ptr - _list_meta <= _header._lists._bytes); + inkAssert(ptr - _list_meta <= header._lists._bytes); } } - if (_header._containers._bytes) + if (header._containers._bytes) { - _num_containers = _header._containers._bytes / sizeof(container_data_t); - _container_data = reinterpret_cast(_file + _header._containers._start); + _num_containers = header._containers._bytes / sizeof(container_data_t); + _container_data = reinterpret_cast(_file + header._containers._start); } - if (_header._container_map._bytes) + if (header._container_map._bytes) { - _container_map_size = _header._container_map._bytes / sizeof(container_map_t); - _container_map = reinterpret_cast(_file + _header._container_map._start); + _container_map_size = header._container_map._bytes / sizeof(container_map_t); + _container_map = reinterpret_cast(_file + header._container_map._start); } - if (_header._container_hash._bytes) + if (header._container_hash._bytes) { - _container_hash_size = _header._container_hash._bytes / sizeof(container_hash_t); - _container_hash = reinterpret_cast(_file + _header._container_hash._start); + _container_hash_size = header._container_hash._bytes / sizeof(container_hash_t); + _container_hash = reinterpret_cast(_file + header._container_hash._start); } - if (_header._instructions._bytes) - _instruction_data = _file + _header._instructions._start; + if (header._instructions._bytes) + _instruction_data = _file + header._instructions._start; // Debugging info /*{ diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index a7328978..1403acec 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -68,8 +68,6 @@ class story_impl : public story virtual runner new_runner_from_snapshot(const snapshot&, globals store = nullptr, unsigned idx = 0) override; - const ink::internal::header& get_header() const { return _header; } - private: void setup_pointers(); @@ -78,8 +76,6 @@ class story_impl : public story uint8_t* _file; size_t _length; - ink::internal::header _header; - // string table const char* _string_table = nullptr; diff --git a/shared/private/header.h b/shared/private/header.h index 81f63ed7..67f51857 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -37,15 +37,9 @@ namespace ink::internal { } } - bool verify(); + bool verify() const; - list_flag read_list_flag(const char*& ptr) const { - list_flag result = *reinterpret_cast(ptr); - ptr += sizeof(list_flag); - return result; - } - struct section_t { uint32_t _start = 0; diff --git a/shared/public/system.h b/shared/public/system.h index 7a01a8c7..7a3a8c90 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -115,6 +115,12 @@ struct list_flag { bool operator!=(const list_flag& o) const { return ! (*this == o); } }; +inline list_flag read_list_flag(const char*& ptr) { + list_flag result = *reinterpret_cast(ptr); + ptr += sizeof(list_flag); + return result; +} + /** value of an unset list_flag */ constexpr list_flag null_flag{-1, -1}; /** value representing an empty list */ 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 11/32] 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 3958835a531e043883297803c9f08ed3e13366f9 Mon Sep 17 00:00:00 2001 From: Will Vale <66674079+willvale@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:03:57 +1300 Subject: [PATCH 12/32] Allow manual dispatch of build workflow. --- .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 13/32] 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 14/32] 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 15/32] 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 30e05b6a9baa67a7d5b61e16e975c5130fcc060a Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:23:04 +1300 Subject: [PATCH 16/32] Fix warning - store command flags as integral. The choice flags (5b) don't fit into the container flags space (4b), but that's OK because we never put them there. --- inkcpp/runner_impl.cpp | 2 +- inkcpp_compiler/binary_emitter.cpp | 4 +++- shared/private/header.h | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 95a39532..66b42156 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -347,7 +347,7 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) // Named knots/stitches need special handling - their visit counts are updated wherever the story enters them, // and we always need to know which knot we're in for tagging. const container_data_t& container = _story->container_data(id); - if (container._flags & CommandFlag::CONTAINER_MARKER_IS_KNOT) { + if (container._flags & uint8_t(CommandFlag::CONTAINER_MARKER_IS_KNOT)) { // If the previous IP wasn't in this container, record the new visit. if (track_knot_visit && !container.contains(current_offset)) _globals->visit(id); diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index 915adda2..2005af1c 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -429,7 +429,9 @@ void binary_emitter::build_container_data(std::vector& data, c d._parent = parent; d._start_offset = context->offset; d._end_offset = context->end_offset; - d._flags = CommandFlag(_instructions.get(context->offset + 1)); + const uint8_t flags = _instructions.get(context->offset + 1); + inkAssert(flags < 16); + d._flags = flags; // Since we might be skipping tree levels, we need to be explicit about the parent. parent = context->counter_index; diff --git a/shared/private/header.h b/shared/private/header.h index 67f51857..0d497ece 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -96,7 +96,7 @@ namespace ink::internal { container_t _parent; /// Container flags (saves looking up via instruction data) - CommandFlag _flags : 4; + uint32_t _flags : 4; /// Instruction offset to the start instruction (enter marker) of this container. uint32_t _start_offset : 28; From 4d926c61ad9e11c3522614e7fb843c83b95ada9c Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:32:30 +1300 Subject: [PATCH 17/32] Remove profiling code. --- inkcpp/runner_impl.cpp | 5 ----- inkcpp/story_impl.cpp | 4 ---- 2 files changed, 9 deletions(-) diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 66b42156..83054848 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -15,9 +15,6 @@ #include "system.h" #include "value.h" -#include "..\..\..\sil\sil\main.h" -#include "..\..\..\sil\sil\profile.h" - #include namespace ink::runtime @@ -294,8 +291,6 @@ void runner_impl::clear_tags(tags_clear_level which) void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) { - SIL_PROFILE("ink::jump"); - // Optimization: if we are _is_falling, then we can // _should be_ able to safely assume that there is nothing to do here. A falling // divert should only be taking us from a container to that same container's end point diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index b96bf024..d26dc71d 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -13,10 +13,6 @@ #include "snapshot_interface.h" #include "version.h" -#include "..\..\..\sil\sil\main.h" -#include "..\..\..\sil\sil\profile.h" - - namespace ink::runtime { #ifdef INK_ENABLE_STL From 4f33a1ebe5010aa369eeda0c1e83b659975bafb6 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Thu, 20 Nov 2025 00:07:18 +1300 Subject: [PATCH 18/32] Fix non-deref of empty container. --- inkcpp_compiler/binary_emitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index 2005af1c..c096adc4 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -219,7 +219,7 @@ void binary_emitter::handle_nop(int index_in_parent) template void binary_emitter::emit_section(std::ostream& stream, const std::vector& data) const { - stream.write(reinterpret_cast(&*data.begin()), data.size() * sizeof(type)); + stream.write(reinterpret_cast(data.data()), data.size() * sizeof(type)); close_section(stream); } From 3bf0ca0a0d662f5c2e5afbbf6a209951ab12871b Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:20:46 +1300 Subject: [PATCH 19/32] inkcpp_test fixes Fix lists test (data was terminated early, postpone termination until writing out lists, and only do it if there are lists) Correctly truncate end of story ignoring section padding. Fix descent parsing for container hash and accept potential duplicate entries (for now) as they won't hurt the search. --- inkcpp/story_impl.cpp | 5 +++++ inkcpp_compiler/binary_emitter.cpp | 35 ++++++++++++++++-------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index d26dc71d..537b9e4a 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -269,6 +269,8 @@ void story_impl::setup_pointers() _lists = reinterpret_cast(ptr); inkAssert(ptr - _list_meta <= header._lists._bytes); } + else + _list_meta = nullptr; } if (header._containers._bytes) @@ -292,6 +294,9 @@ void story_impl::setup_pointers() if (header._instructions._bytes) _instruction_data = _file + header._instructions._start; + inkAssert(end() >= _instruction_data + header._instructions._bytes); + _length = _instruction_data + header._instructions._bytes - _file; + // Debugging info /*{ const uint32_t* iter = nullptr; diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index c096adc4..0ec20bf1 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -252,6 +252,10 @@ void binary_emitter::output(std::ostream& out) // Sort map on ascending hash code. std::sort(container_hash.begin(), container_hash.end()); + // If there are any lists, terminate the data correctly. Otherwise leave an empty section. + if (_lists.pos() > 0) + _lists.write(null_flag); + // Fill in header ink::internal::header header; header.ink_version_number = _ink_version; @@ -446,30 +450,32 @@ void binary_emitter::build_container_hash_map( std::vector& hash_map, std::vector& data, const std::string& name, const container_data* context ) const { - // If there's a non-empty name, hash it. - if (!name.empty()) - { + // Search named children first. + for (auto child : context->named_children) { + // Get the child's name in the hierarchy + std::string child_name = name.empty() ? child.first : (name + "." + child.first); + // Hash name. We only do this at the named child level. In theory we could support indexed children as well. // The root is anonymous so the fact that it's skipped is not an issue. - const hash_t name_hash = hash_string(name.c_str()); + const hash_t child_name_hash = hash_string(child_name.c_str()); // Store hash in the data. - if (context->counter_index != ~0) { - data[context->counter_index]._hash = name_hash; + if (child.second->counter_index != ~0) { + data[child.second->counter_index]._hash = child_name_hash; } // Append the name hash and offset - hash_map.push_back( {name_hash, context->offset} ); - } - - // Search named children only. - for (auto child : context->named_children) { - // Get the child's name in the hierarchy - std::string child_name = name.empty() ? child.first : (name + "." + child.first); + hash_map.push_back( {child_name_hash, child.second->offset} ); // Recurse build_container_hash_map(hash_map, data, child_name, child.second); } + + // Search indexed children (which duplicates named childen...) + // TODO: Merge duplicate child arrays, very error-prone. + for (auto child : context->indexed_children) { + build_container_hash_map(hash_map, data, name, child.second); + } } void binary_emitter::set_list_meta(const list_data& list_defs) @@ -492,8 +498,5 @@ void binary_emitter::set_list_meta(const list_data& list_defs) _lists.write(reinterpret_cast(flag.name->c_str()), flag.name->size() + 1); } _lists.write(null_flag); - - // Extra terminator since we don't write them between sections. - _lists.write(null_flag); } } // namespace ink::compiler::internal From eb68bad18309aaca253ef9daa84638aa7f801d65 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Thu, 20 Nov 2025 21:04:40 +1300 Subject: [PATCH 20/32] Fix remaining unit test We do need to process the new container in jump() or we lose context about what kind of jump it is. In that case the normal flow could track a knot that we've tunnelled inside, losing the knot tags. --- inkcpp/runner_impl.cpp | 45 +++++++++++++++++++++++++++++++---------- shared/private/header.h | 3 +++ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 83054848..4efb5eea 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -329,8 +329,8 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) const container_data_t& dest_container = _story->container_data(dest_id); const bool jump_to_start = dest_offset == dest_container._start_offset; - // Update visit counts for new containers on the stack. If we jump directly to the start of a container, - // don't update it (last on the stack) as the normal instruction flow will process the start marker next. + // Build stack and record any new knots we enter. + // If we jump directly to the start of a container, don't update it (last on the stack) and process it below. for (uint32_t d = 0; d < depth - jump_to_start; ++d) { // Read temporary stack in forward order. @@ -340,17 +340,40 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) _container.push(id); // Named knots/stitches need special handling - their visit counts are updated wherever the story enters them, - // and we always need to know which knot we're in for tagging. - const container_data_t& container = _story->container_data(id); - if (container._flags & uint8_t(CommandFlag::CONTAINER_MARKER_IS_KNOT)) { - // If the previous IP wasn't in this container, record the new visit. - if (track_knot_visit && !container.contains(current_offset)) - _globals->visit(id); - - // Update current knot, the knot closest to the top of the stack will end up current. - _current_knot_id = id; + // and we generally need to know which knot we're in, for tagging, unless we're jumping to a tunnel or similar + // which suppresses knot tracking. + if (track_knot_visit) { + // If this is a knot that we previously weren't in, record the new visit. + const container_data_t& container = _story->container_data(id); + if (container.knot() && !container.contains(current_offset)) { + // Record visit count + if (record_visits) + _globals->visit(id); + + // Update current knot, the knot closest to the top of the stack will end up current. + _current_knot_id = id; + _entered_knot = true; + } + } + } + + // Finally, if we're jumping to the start of a container, enter it while we have the context to do so. + if (jump_to_start) { + // Add to top of stack + _container.push(dest_id); + + // Record visit + if (record_visits) + _globals->visit(dest_id); + + // Record knot, if + if (track_knot_visit && dest_container.knot()) { + _current_knot_id = dest_id; _entered_knot = true; } + + // Consume instruction so we don't process it again during normal flow. + _ptr += 6; } } diff --git a/shared/private/header.h b/shared/private/header.h index 0d497ece..f1f53082 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -109,5 +109,8 @@ namespace ink::internal { /// Check to see if the instruction offset is part of the instructions for this container. Note that this is inclusive not exclusive. bool contains(uint32_t offset) const { return offset >= _start_offset && offset <= _end_offset; } + + /// Check to see if this is a knot container. + bool knot() const { return _flags & uint8_t(CommandFlag::CONTAINER_MARKER_IS_KNOT); } }; } From afe3e1eceead8f9199cefc967ac67fd1b097f718 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Fri, 21 Nov 2025 19:53:49 +1300 Subject: [PATCH 21/32] Fix UTF-8 test Use source path macro. Include globals.h so we can destruct the impl properly. --- inkcpp_test/UTF8.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inkcpp_test/UTF8.cpp b/inkcpp_test/UTF8.cpp index 1a03c55d..f3390189 100644 --- a/inkcpp_test/UTF8.cpp +++ b/inkcpp_test/UTF8.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -17,7 +18,7 @@ SCENARIO("a story supports UTF-8", "[utf-8]") auto ink = story::from_file("UTF8Story.bin"); runner thread = ink->new_runner(); - std::ifstream demoFile("ink/UTF-8-demo.txt"); + std::ifstream demoFile(INK_TEST_RESOURCE_DIR "UTF-8-demo.txt"); if (!demoFile.is_open()) { throw std::runtime_error("cannot open UTF-8 demo file"); } From 88dc8d0560dec590f6073375515a46610104f71f Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Fri, 21 Nov 2025 19:55:59 +1300 Subject: [PATCH 22/32] Rework jump slightly and fix tests. Try and do everything in one loop - cleaner - while traversing original stack. Honour the visit and knot options properly (according to tests, at least) --- inkcpp/runner_impl.cpp | 92 ++++++++++++++++++----------------------- shared/private/header.h | 3 ++ 2 files changed, 44 insertions(+), 51 deletions(-) diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 4efb5eea..ac97326f 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -300,6 +300,10 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) if (dest == _ptr) return; + // Discard old stack, preserving save region. + while (!_container.empty()) + _container.pop(); + // Record location and jump. const uint32_t current_offset = _ptr - _story->instructions(); _ptr = dest; @@ -312,68 +316,54 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) if (dest_id == ~0) return; - // Count stack depth and assemble stack in reverse order by traversing container tree. - container_t stack[64]; + // Are we entering the new container at its start? + using container_data_t = ink::internal::container_data_t; + const container_data_t& dest_container = _story->container_data(dest_id); + if (dest_offset == dest_container._start_offset) { + // Record direct jump to non-knot if requested. (Knots handled below.) + if (record_visits && !dest_container.knot()) { + _globals->visit(dest_id); + } + + // Consume instruction so we don't process it again during normal flow. (We need to do this here to know if it should be tracked or not.) + _ptr += 6; + } + + // Assemble temp stack in reverse order by traversing container tree. + container_t stack[abs(config::limitContainerDepth)]; uint32_t depth = 0; - for (container_t id = dest_id; id != ~0; id = _story->container_data(id)._parent) { - inkAssert(depth < 64); + for (container_t id = dest_id; id != ~0; /* advance in body */) { + // Append to stack. + inkAssert(depth < abs(config::limitContainerDepth)); stack[depth++] = id; - } - // Discard existing stack, preserving save region. - while (!_container.empty()) - _container.pop(); + // Find container for this ID. + const container_data_t& container = _story->container_data(id); - // Are we entering the new container at its start? - using container_data_t = ink::internal::container_data_t; - const container_data_t& dest_container = _story->container_data(dest_id); - const bool jump_to_start = dest_offset == dest_container._start_offset; + // Is this a new knot? + if (container.knot() && !container.contains(current_offset)) { + // Named knots/stitches need special handling - their visit counts are updated wherever the story enters them, + // and we generally need to know which knot we're in, for tagging, unless we're jumping to a tunnel or similar + // which suppresses knot tracking. + if (record_visits) { + _globals->visit(id); + } - // Build stack and record any new knots we enter. - // If we jump directly to the start of a container, don't update it (last on the stack) and process it below. - for (uint32_t d = 0; d < depth - jump_to_start; ++d) - { - // Read temporary stack in forward order. - const container_t id = stack[depth-1-d]; - - // Push container onto stack. - _container.push(id); - - // Named knots/stitches need special handling - their visit counts are updated wherever the story enters them, - // and we generally need to know which knot we're in, for tagging, unless we're jumping to a tunnel or similar - // which suppresses knot tracking. - if (track_knot_visit) { - // If this is a knot that we previously weren't in, record the new visit. - const container_data_t& container = _story->container_data(id); - if (container.knot() && !container.contains(current_offset)) { - // Record visit count - if (record_visits) - _globals->visit(id); - - // Update current knot, the knot closest to the top of the stack will end up current. + // If tracking, update with the first knot we encounter, which is the one closest to the top of the new stack. + if (track_knot_visit) { _current_knot_id = id; _entered_knot = true; + track_knot_visit = false; } } - } - // Finally, if we're jumping to the start of a container, enter it while we have the context to do so. - if (jump_to_start) { - // Add to top of stack - _container.push(dest_id); - - // Record visit - if (record_visits) - _globals->visit(dest_id); - - // Record knot, if - if (track_knot_visit && dest_container.knot()) { - _current_knot_id = dest_id; - _entered_knot = true; - } + // Next one. + id = container._parent; + } - // Consume instruction so we don't process it again during normal flow. - _ptr += 6; + // Reverse order onto final stack. + for (uint32_t d = 0; d < depth; ++d) { + _container.push(stack[depth-1-d]); } } diff --git a/shared/private/header.h b/shared/private/header.h index f1f53082..3c864af5 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -112,5 +112,8 @@ namespace ink::internal { /// Check to see if this is a knot container. bool knot() const { return _flags & uint8_t(CommandFlag::CONTAINER_MARKER_IS_KNOT); } + + /// Check to see if this is a container which tracks visits. + bool visit() const { return _flags & uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_VISITS); } }; } From 5f3a179b7382bffb58eee851cda390095e36fcd5 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Fri, 21 Nov 2025 22:19:14 +1300 Subject: [PATCH 23/32] Fix restrictive numeric casts/assignments. 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 | 16 +++++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) 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..d1422ef2 100644 --- a/inkcpp/operations.h +++ b/inkcpp/operations.h @@ -86,15 +86,13 @@ 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...}); } From adefe3ee0b7b9f3be23790d76b7f80775133bddf Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Fri, 21 Nov 2025 22:30:55 +1300 Subject: [PATCH 24/32] Clangformat fixes. --- inkcpp/operations.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/inkcpp/operations.h b/inkcpp/operations.h index d1422ef2..7546a015 100644 --- a/inkcpp/operations.h +++ b/inkcpp/operations.h @@ -88,8 +88,11 @@ ink::runtime::internal::value { 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"); + 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; From 48bac0ff665189ebd70762c4a782a1ff8bdf09a3 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Fri, 21 Nov 2025 23:26:39 +1300 Subject: [PATCH 25/32] Fix knot visit counts This only came up in my story, re-visiting a relatively simple knot, so it's odd that there's no test for it. It's a bug in inkcpp::master, but it was easier to fix it here after simplifying jump. --- inkcpp/runner_impl.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index ac97326f..dd7a18de 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -329,6 +329,9 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) _ptr += 6; } + // If we're tracking knots, we only want the first one. + bool first_knot = track_knot_visit; + // Assemble temp stack in reverse order by traversing container tree. container_t stack[abs(config::limitContainerDepth)]; uint32_t depth = 0; @@ -345,15 +348,18 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) // Named knots/stitches need special handling - their visit counts are updated wherever the story enters them, // and we generally need to know which knot we're in, for tagging, unless we're jumping to a tunnel or similar // which suppresses knot tracking. - if (record_visits) { + // + // Ink has a rule about incrementing visit counts when you jump to the top of a knot, which seems to need to + // override inkcpp's knot_visit flag. + if (track_knot_visit || container._start_offset == dest_offset) { _globals->visit(id); } // If tracking, update with the first knot we encounter, which is the one closest to the top of the new stack. - if (track_knot_visit) { + if (first_knot) { _current_knot_id = id; _entered_knot = true; - track_knot_visit = false; + first_knot = false; } } From 3f7f6f2a825220ad88bdcb8da60ccce4b3f0443d Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 28 Nov 2025 11:54:47 +0100 Subject: [PATCH 26/32] 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 710617926c2552983ef4ac4634949d497946a37c Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Sun, 14 Dec 2025 14:22:37 +1300 Subject: [PATCH 27/32] Fix a variety of cast problems Allow conversions from bool to string. Added test case for allowed and forbidden casts. Added missing include to UTF8 test. --- inkcpp/string_operations.h | 3 ++ inkcpp/string_utils.h | 6 +++ inkcpp_test/Fixes.cpp | 31 ++++++++++++++ inkcpp_test/UTF8.cpp | 1 + inkcpp_test/ink/134_restrictive_casts.ink | 51 +++++++++++++++++++++++ 5 files changed, 92 insertions(+) create mode 100644 inkcpp_test/ink/134_restrictive_casts.ink diff --git a/inkcpp/string_operations.h b/inkcpp/string_operations.h index a0f8ff49..007cb846 100644 --- a/inkcpp/string_operations.h +++ b/inkcpp/string_operations.h @@ -23,6 +23,9 @@ namespace ink::runtime::internal { 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; }; } 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..edf60976 100644 --- a/inkcpp_test/Fixes.cpp +++ b/inkcpp_test/Fixes.cpp @@ -126,3 +126,34 @@ 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(), std::cerr< +#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 be2b6f7f4d8e1e77e065c75ba76cf9b4a1b6d64e Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Sun, 14 Dec 2025 14:34:32 +1300 Subject: [PATCH 28/32] Clangformat fixes Also removed debug trace left in by mistake. --- inkcpp_test/Fixes.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/inkcpp_test/Fixes.cpp b/inkcpp_test/Fixes.cpp index edf60976..57101d87 100644 --- a/inkcpp_test/Fixes.cpp +++ b/inkcpp_test/Fixes.cpp @@ -145,14 +145,15 @@ SCENARIO("Casting during redefinition is too strict _ #134", "[fixes]") 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. + // 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(), std::cerr<getline(), ink::ink_exception); } } } From e1ca8ba9bafe308636223301ffe70b1e1326ab43 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Sun, 21 Dec 2025 13:59:20 +1300 Subject: [PATCH 29/32] Emit list metadata in separate section and remove duplicate parsing. --- inkcpp/story_impl.cpp | 43 +++++++++--------------------- inkcpp_compiler/binary_emitter.cpp | 27 +++++++++++++------ inkcpp_compiler/binary_emitter.h | 1 + shared/private/header.h | 1 + 4 files changed, 33 insertions(+), 39 deletions(-) diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index 537b9e4a..d702783d 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -239,61 +239,42 @@ void story_impl::setup_pointers() if (header._strings._bytes) _string_table = reinterpret_cast(_file + header._strings._start); - if (header._lists._bytes) + // Address list sections if they exist + if (header._list_meta._bytes) { - _list_meta = reinterpret_cast(_file + header._lists._start); - - // SIL: TODO: Remove this, since it has to get parsed in _lists/_globals. Just needs an interior offset or a separate section from the compiler. - const char *ptr = _list_meta; - if (list_flag flag = read_list_flag(ptr); flag != null_flag) { - // skip list definitions - auto list_id = flag.list_id; - while (*ptr != 0) { - ++ptr; - } - ++ptr; // skip list name - do { - if (flag.list_id != list_id) { - list_id = flag.list_id; - while (*ptr != 0) { - ++ptr; - } - ++ptr; // skip list name - } - while (*ptr != 0) { - ++ptr; - } - ++ptr; // skip flag name - } while ((flag = read_list_flag(ptr)) != null_flag); - - _lists = reinterpret_cast(ptr); - inkAssert(ptr - _list_meta <= header._lists._bytes); - } - else - _list_meta = nullptr; + _list_meta = reinterpret_cast(_file + header._list_meta._start); + + // Lists require metadata + if (header._lists._bytes) + _lists = reinterpret_cast(_file + header._lists._start); } + // Address containers section if it exists if (header._containers._bytes) { _num_containers = header._containers._bytes / sizeof(container_data_t); _container_data = reinterpret_cast(_file + header._containers._start); } + // Address container map if it exists if (header._container_map._bytes) { _container_map_size = header._container_map._bytes / sizeof(container_map_t); _container_map = reinterpret_cast(_file + header._container_map._start); } + // Address container hash if it exists if (header._container_hash._bytes) { _container_hash_size = header._container_hash._bytes / sizeof(container_hash_t); _container_hash = reinterpret_cast(_file + header._container_hash._start); } + // Address instructions, which we hope exist! if (header._instructions._bytes) _instruction_data = _file + header._instructions._start; + // Shrink file length to fit exact length of instructions section. inkAssert(end() >= _instruction_data + header._instructions._bytes); _length = _instruction_data + header._instructions._bytes - _file; diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index 0ec20bf1..f717ec01 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -252,9 +252,15 @@ void binary_emitter::output(std::ostream& out) // Sort map on ascending hash code. std::sort(container_hash.begin(), container_hash.end()); - // If there are any lists, terminate the data correctly. Otherwise leave an empty section. - if (_lists.pos() > 0) - _lists.write(null_flag); + // If there's list meta data... + if (_list_meta.pos() > 0) { + // If there are any lists, terminate the data correctly. Otherwise leave an empty section. + if (_lists.pos() > 0) + _lists.write(null_flag); + } + else + // No meta data -> no lists. + _lists.reset(); // Fill in header ink::internal::header header; @@ -264,6 +270,7 @@ void binary_emitter::output(std::ostream& out) // Fill in sections uint32_t offset = sizeof(header); header._strings.setup(offset, _strings.pos()); + header._list_meta.setup(offset, _list_meta.pos()); header._lists.setup(offset, _lists.pos()); header._containers.setup(offset, container_data.size() * sizeof(container_data_t)); header._container_map.setup(offset, _container_map.size() * sizeof(container_map_t)); @@ -277,6 +284,9 @@ void binary_emitter::output(std::ostream& out) // Write the string table emit_section(out, _strings); + // Write lists meta data and defined lists + emit_section(out, _list_meta); + // Write lists meta data and defined lists emit_section(out, _lists); @@ -301,6 +311,7 @@ void binary_emitter::initialize() // Reset binary data stores _strings.reset(); _list_count = 0; + _list_meta.reset(); _lists.reset(); _instructions.reset(); @@ -488,15 +499,15 @@ void binary_emitter::set_list_meta(const list_data& list_defs) auto list_names = list_defs.get_list_names().begin(); int list_id = -1; for (const auto& flag : flags) { - _lists.write(flag.flag); + _list_meta.write(flag.flag); if (flag.flag.list_id != list_id) { list_id = flag.flag.list_id; - _lists.write(reinterpret_cast(list_names->data()), list_names->size()); + _list_meta.write(reinterpret_cast(list_names->data()), list_names->size()); ++list_names; - _lists.write('\0'); + _list_meta.write('\0'); } - _lists.write(reinterpret_cast(flag.name->c_str()), flag.name->size() + 1); + _list_meta.write(reinterpret_cast(flag.name->c_str()), flag.name->size() + 1); } - _lists.write(null_flag); + _list_meta.write(null_flag); } } // namespace ink::compiler::internal diff --git a/inkcpp_compiler/binary_emitter.h b/inkcpp_compiler/binary_emitter.h index 5765b4a2..5400130c 100644 --- a/inkcpp_compiler/binary_emitter.h +++ b/inkcpp_compiler/binary_emitter.h @@ -68,6 +68,7 @@ namespace ink::compiler::internal binary_stream _strings; uint32_t _list_count = 0; + binary_stream _list_meta; binary_stream _lists; binary_stream _instructions; diff --git a/shared/private/header.h b/shared/private/header.h index 3c864af5..abf12624 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -55,6 +55,7 @@ namespace ink::internal { // File section sizes. Each section is aligned to Alignment section_t _strings; + section_t _list_meta; section_t _lists; section_t _containers; section_t _container_map; From 9d653ae2632f3d72e97ecc1e80ba34a06ac6be82 Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Tue, 23 Dec 2025 21:38:52 +1300 Subject: [PATCH 30/32] Don't keep trying to terminate a stream which failed. --- inkcpp_compiler/binary_emitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index f717ec01..432df999 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -233,7 +233,7 @@ void binary_emitter::emit_section(std::ostream& stream, const binary_stream& dat void binary_emitter::close_section(std::ostream& stream) const { // Write zeroes until aligned. - while (stream.tellp() % ink::internal::header::Alignment) + while (!stream.fail() && (stream.tellp() % ink::internal::header::Alignment)) stream.put('\0'); } From 31885d47a8db743fc2658fc3d15306bb644bdcbe Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Tue, 23 Dec 2025 22:03:51 +1300 Subject: [PATCH 31/32] Run Clangformat on affected files. And set lines to LF. --- inkcpp/container_operations.cpp | 56 ++++---- inkcpp/header.cpp | 35 ++--- inkcpp/runner_impl.cpp | 56 ++++---- inkcpp/runner_impl.h | 2 +- inkcpp/story_impl.cpp | 41 +++--- inkcpp/story_impl.h | 35 +++-- inkcpp_compiler/binary_emitter.cpp | 51 ++++---- inkcpp_compiler/binary_emitter.h | 123 ++++++++++-------- inkcpp_compiler/emitter.cpp | 82 ++++++------ inkcpp_compiler/emitter.h | 136 +++++++++---------- shared/private/header.h | 201 ++++++++++++++--------------- shared/public/system.h | 3 +- 12 files changed, 422 insertions(+), 399 deletions(-) diff --git a/inkcpp/container_operations.cpp b/inkcpp/container_operations.cpp index 9d3c7306..6b7aa6d5 100644 --- a/inkcpp/container_operations.cpp +++ b/inkcpp/container_operations.cpp @@ -13,36 +13,34 @@ #include -namespace ink::runtime::internal { +namespace ink::runtime::internal +{ - void operation::operator()( - basic_eval_stack& stack, value* vals) - { - container_t id; - bool success = _story.find_container_id(vals[0].get(), id); - inkAssert(success, "failed to find container to read visit count!"); - stack.push(value{}.set( - static_cast(_visit_counts.visits( id ) - ))); - } - - void operation::operator()( - basic_eval_stack& stack, value* vals) - { - container_t id; - bool success = _story.find_container_id(vals[0].get(), id); - inkAssert(success, "failed to find container to read turn count!"); - stack.push(value{}.set( - static_cast(_visit_counts.turns(id) - ))); - } +void operation::operator()( + basic_eval_stack& stack, value* vals +) +{ + container_t id; + bool success = _story.find_container_id(vals[0].get(), id); + inkAssert(success, "failed to find container to read visit count!"); + stack.push(value{}.set(static_cast(_visit_counts.visits(id)))); +} - void operation::operator() - (basic_eval_stack& stack, value* vals) - { - stack.push(value{}.set(static_cast( - _runner.num_choices() - ))); - } +void operation::operator()( + basic_eval_stack& stack, value* vals +) +{ + container_t id; + bool success = _story.find_container_id(vals[0].get(), id); + inkAssert(success, "failed to find container to read turn count!"); + stack.push(value{}.set(static_cast(_visit_counts.turns(id)))); +} +void operation::operator()( + basic_eval_stack& stack, value* vals +) +{ + stack.push(value{}.set(static_cast(_runner.num_choices()))); } + +} // namespace ink::runtime::internal diff --git a/inkcpp/header.cpp b/inkcpp/header.cpp index 88a517ab..e92ee2a1 100644 --- a/inkcpp/header.cpp +++ b/inkcpp/header.cpp @@ -7,26 +7,27 @@ #include "header.h" #include "version.h" -namespace ink::internal { +namespace ink::internal +{ - bool header::verify() const - { - if (endian() == endian_types::none) { - inkFail("Header magic number was wrong!"); - return false; - } - - if (endian() == endian_types::differ) { - inkFail("Can't load content with different endian-ness!"); - return false; - } +bool header::verify() const +{ + if (endian() == endian_types::none) { + inkFail("Header magic number was wrong!"); + return false; + } - if (ink_bin_version_number != InkBinVersion) { - inkFail("InkCpp-version mismatch: file was compiled with different InkCpp-version!"); - return false; - } + if (endian() == endian_types::differ) { + inkFail("Can't load content with different endian-ness!"); + return false; + } - return true; + if (ink_bin_version_number != InkBinVersion) { + inkFail("InkCpp-version mismatch: file was compiled with different InkCpp-version!"); + return false; } + return true; } + +} // namespace ink::internal diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index dd7a18de..acc73d77 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -301,31 +301,32 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) return; // Discard old stack, preserving save region. - while (!_container.empty()) + while (! _container.empty()) _container.pop(); // Record location and jump. const uint32_t current_offset = _ptr - _story->instructions(); - _ptr = dest; + _ptr = dest; // Find the container at or before dest, which will become the top of the post-jump stack. - const uint32_t dest_offset = dest - _story->instructions(); - const container_t dest_id = _story->find_container_for(dest_offset); + const uint32_t dest_offset = dest - _story->instructions(); + const container_t dest_id = _story->find_container_for(dest_offset); // If there's no destination container, stop. if (dest_id == ~0) return; // Are we entering the new container at its start? - using container_data_t = ink::internal::container_data_t; + using container_data_t = ink::internal::container_data_t; const container_data_t& dest_container = _story->container_data(dest_id); if (dest_offset == dest_container._start_offset) { // Record direct jump to non-knot if requested. (Knots handled below.) - if (record_visits && !dest_container.knot()) { + if (record_visits && ! dest_container.knot()) { _globals->visit(dest_id); } - // Consume instruction so we don't process it again during normal flow. (We need to do this here to know if it should be tracked or not.) + // Consume instruction so we don't process it again during normal flow. (We need to do this here + // to know if it should be tracked or not.) _ptr += 6; } @@ -334,7 +335,7 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) // Assemble temp stack in reverse order by traversing container tree. container_t stack[abs(config::limitContainerDepth)]; - uint32_t depth = 0; + uint32_t depth = 0; for (container_t id = dest_id; id != ~0; /* advance in body */) { // Append to stack. inkAssert(depth < abs(config::limitContainerDepth)); @@ -344,22 +345,25 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) const container_data_t& container = _story->container_data(id); // Is this a new knot? - if (container.knot() && !container.contains(current_offset)) { - // Named knots/stitches need special handling - their visit counts are updated wherever the story enters them, - // and we generally need to know which knot we're in, for tagging, unless we're jumping to a tunnel or similar + if (container.knot() && ! container.contains(current_offset)) { + // Named knots/stitches need special handling - their visit counts are updated wherever the + // story enters them, + // and we generally need to know which knot we're in, for tagging, unless we're jumping to a + //tunnel or similar // which suppresses knot tracking. // - // Ink has a rule about incrementing visit counts when you jump to the top of a knot, which seems to need to - // override inkcpp's knot_visit flag. + // Ink has a rule about incrementing visit counts when you jump to the top of a knot, which + // seems to need to override inkcpp's knot_visit flag. if (track_knot_visit || container._start_offset == dest_offset) { _globals->visit(id); } - // If tracking, update with the first knot we encounter, which is the one closest to the top of the new stack. + // If tracking, update with the first knot we encounter, which is the one closest to the top + // of the new stack. if (first_knot) { _current_knot_id = id; - _entered_knot = true; - first_knot = false; + _entered_knot = true; + first_knot = false; } } @@ -369,7 +373,7 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) // Reverse order onto final stack. for (uint32_t d = 0; d < depth; ++d) { - _container.push(stack[depth-1-d]); + _container.push(stack[depth - 1 - d]); } } @@ -485,13 +489,13 @@ runner_impl::line_type runner_impl::getline() // Advance interpreter one line and write to output advance_line(); -#ifdef INK_ENABLE_STL +# ifdef INK_ENABLE_STL line_type result{_output.get()}; -#elif defined(INK_ENABLE_UNREAL) +# elif defined(INK_ENABLE_UNREAL) line_type result{ANSI_TO_TCHAR(_output.get_alloc(_globals->strings(), _globals->lists()))}; -#else -# error unsupported constraints for getline -#endif +# else +# error unsupported constraints for getline +# endif // Fall through the fallback choice, if available if (! has_choices() && _fallback_choice) { @@ -504,11 +508,11 @@ runner_impl::line_type runner_impl::getline() runner_impl::line_type runner_impl::getall() { -#ifdef INK_ENABLE_STL +# ifdef INK_ENABLE_STL if (_debug_stream != nullptr) { _debug_stream->clear(); } -#endif +# endif line_type result{}; @@ -1374,7 +1378,9 @@ void runner_impl::step() _container.push(index); // Increment visit count - if (uint8_t(flag) & (uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_VISITS)|uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_TURNS))) { + if (uint8_t(flag) + & (uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_VISITS) + | uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_TURNS))) { _globals->visit(index); } if (flag & CommandFlag::CONTAINER_MARKER_IS_KNOT) { diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index b597f31f..9196f1f7 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -340,7 +340,7 @@ class runner_impl functions _functions; // Container stack - internal::managed_restorable_stack _container; bool _is_falling = false; diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index d702783d..0e8ec748 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -101,7 +101,6 @@ story_impl::~story_impl() const char* story_impl::string(uint32_t index) const { return _string_table + index; } - bool story_impl::find_container_id(uint32_t offset, container_t& container_id) const { // Find inmost container. @@ -112,17 +111,17 @@ bool story_impl::find_container_id(uint32_t offset, container_t& container_id) c } // Search sorted looking for the target or the largest value smaller than target. -template -static const entry* upper_bound(const entry *sorted, uint32_t count, uint32_t key) +template +static const entry* upper_bound(const entry* sorted, uint32_t count, uint32_t key) { if (count == 0) return nullptr; - uint32_t begin = 0; - uint32_t end = count; + uint32_t begin = 0; + uint32_t end = count; - while (begin < end) { - const uint32_t mid = begin + (end - begin + 1) / 2; + while (begin < end) { + const uint32_t mid = begin + (end - begin + 1) / 2; const uint32_t mid_key = sorted[mid].key(); if (mid_key > key) @@ -145,12 +144,11 @@ container_t story_impl::find_container_for(uint32_t offset) const // (in which case the offset is contained within) or the end of a container, in which case // the offset is inside that container's parent. - // If we're not inside the container, walk out to find the actual parent. Normally we'd - // know that the parent contained the child, but the containers are sparse so we might + // If we're not inside the container, walk out to find the actual parent. Normally we'd + // know that the parent contained the child, but the containers are sparse so we might // not have anything. container_t id = entry ? entry->_id : ~0; - while (id != ~0) - { + while (id != ~0) { const container_data_t& data = container_data(id); if (data._start_offset <= offset && data._end_offset >= offset) return id; @@ -172,7 +170,7 @@ CommandFlag story_impl::container_flag(ip_t offset) const } ip_t story_impl::find_offset_for(hash_t path) const -{ +{ // Hash map contains hashes in even slots, offsets in odd. const container_hash_t* entry = upper_bound(_container_hash, _container_hash_size, path); @@ -232,16 +230,15 @@ runner story_impl::new_runner_from_snapshot(const snapshot& data, globals store, void story_impl::setup_pointers() { const ink::internal::header& header = *reinterpret_cast(_file); - if (!header.verify()) + if (! header.verify()) return; // Locate sections if (header._strings._bytes) - _string_table = reinterpret_cast(_file + header._strings._start); + _string_table = reinterpret_cast(_file + header._strings._start); // Address list sections if they exist - if (header._list_meta._bytes) - { + if (header._list_meta._bytes) { _list_meta = reinterpret_cast(_file + header._list_meta._start); // Lists require metadata @@ -250,24 +247,22 @@ void story_impl::setup_pointers() } // Address containers section if it exists - if (header._containers._bytes) - { + if (header._containers._bytes) { _num_containers = header._containers._bytes / sizeof(container_data_t); _container_data = reinterpret_cast(_file + header._containers._start); } // Address container map if it exists - if (header._container_map._bytes) - { + if (header._container_map._bytes) { _container_map_size = header._container_map._bytes / sizeof(container_map_t); _container_map = reinterpret_cast(_file + header._container_map._start); } // Address container hash if it exists - if (header._container_hash._bytes) - { + if (header._container_hash._bytes) { _container_hash_size = header._container_hash._bytes / sizeof(container_hash_t); - _container_hash = reinterpret_cast(_file + header._container_hash._start); + _container_hash + = reinterpret_cast(_file + header._container_hash._start); } // Address instructions, which we hope exist! diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index 1403acec..3bebebf0 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -40,21 +40,30 @@ class story_impl : public story const char* list_meta() const { return _list_meta; } - // Find the innermost container containing offset. If offset is the start of a container, return that container. + // Find the innermost container containing offset. If offset is the start of a container, return + // that container. container_t find_container_for(uint32_t offset) const; - // Find the container which starts exactly at offset. Return false if this isn't the start of a container. + // Find the container which starts exactly at offset. Return false if this isn't the start of a + // container. bool find_container_id(uint32_t offset, container_t& container_id) const; using container_data_t = ink::internal::container_data_t; using container_hash_t = ink::internal::container_hash_t; - using container_map_t = ink::internal::container_map_t; + using container_map_t = ink::internal::container_map_t; // Look up the details of the given container - const container_data_t& container_data(container_t id) const { inkAssert(id < _num_containers); return _container_data[id]; } + const container_data_t& container_data(container_t id) const + { + inkAssert(id < _num_containers); + return _container_data[id]; + } // Look up the instruction pointer for the start of the given container - ip_t container_offset(container_t id) const { return _instruction_data + container_data(id)._start_offset; } + ip_t container_offset(container_t id) const + { + return _instruction_data + container_data(id)._start_offset; + } // Get container flag from container offset (either start or end) CommandFlag container_flag(ip_t offset) const; @@ -73,26 +82,26 @@ class story_impl : public story private: // file information - uint8_t* _file; - size_t _length; + uint8_t* _file; + size_t _length; // string table const char* _string_table = nullptr; const char* _list_meta = nullptr; - const list_flag* _lists = nullptr; + const list_flag* _lists = nullptr; // Information about containers. const container_data_t* _container_data = nullptr; - uint32_t _num_containers = 0; + uint32_t _num_containers = 0; // How to find containers from instruction offsets. - const container_map_t* _container_map = nullptr; - uint32_t _container_map_size = 0; + const container_map_t* _container_map = nullptr; + uint32_t _container_map_size = 0; // How to find containers from string hashes. - const container_hash_t *_container_hash = nullptr; - uint32_t _container_hash_size = 0; + const container_hash_t* _container_hash = nullptr; + uint32_t _container_hash_size = 0; // instruction info ip_t _instruction_data = nullptr; diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index 432df999..9405eaad 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -216,16 +216,16 @@ void binary_emitter::handle_nop(int index_in_parent) _current->noop_offsets.insert({index_in_parent, _instructions.pos()}); } -template +template void binary_emitter::emit_section(std::ostream& stream, const std::vector& data) const { - stream.write(reinterpret_cast(data.data()), data.size() * sizeof(type)); + stream.write(reinterpret_cast(data.data()), data.size() * sizeof(type)); close_section(stream); } void binary_emitter::emit_section(std::ostream& stream, const binary_stream& data) const { - inkAssert((stream.tellp() & (ink::internal::header::Alignment-1)) == 0); + inkAssert((stream.tellp() & (ink::internal::header::Alignment - 1)) == 0); data.write_to(stream); close_section(stream); } @@ -233,14 +233,14 @@ void binary_emitter::emit_section(std::ostream& stream, const binary_stream& dat void binary_emitter::close_section(std::ostream& stream) const { // Write zeroes until aligned. - while (!stream.fail() && (stream.tellp() % ink::internal::header::Alignment)) + while (! stream.fail() && (stream.tellp() % ink::internal::header::Alignment)) stream.put('\0'); } void binary_emitter::output(std::ostream& out) { // Create container data - std::vector container_data; + std::vector container_data; container_data.resize(_max_container_index); build_container_data(container_data, ~0, _root); @@ -257,15 +257,14 @@ void binary_emitter::output(std::ostream& out) // If there are any lists, terminate the data correctly. Otherwise leave an empty section. if (_lists.pos() > 0) _lists.write(null_flag); - } - else + } else // No meta data -> no lists. _lists.reset(); - // Fill in header + // Fill in header ink::internal::header header; - header.ink_version_number = _ink_version; - header.ink_bin_version_number = ink::InkBinVersion; + header.ink_version_number = _ink_version; + header.ink_bin_version_number = ink::InkBinVersion; // Fill in sections uint32_t offset = sizeof(header); @@ -278,7 +277,7 @@ void binary_emitter::output(std::ostream& out) header._instructions.setup(offset, _instructions.pos()); // Write the header - out.write(reinterpret_cast(&header), sizeof(header)); + out.write(reinterpret_cast(&header), sizeof(header)); close_section(out); // Write the string table @@ -295,7 +294,7 @@ void binary_emitter::output(std::ostream& out) // Write out container map emit_section(out, _container_map); - + // Write container hash list emit_section(out, container_hash); @@ -435,21 +434,22 @@ void binary_emitter::process_paths() } } -void binary_emitter::build_container_data(std::vector& data, container_t parent, const container_data *context) const +void binary_emitter::build_container_data( + std::vector& data, container_t parent, const container_data* context +) const { // Build data for this container - if (context->counter_index != ~0) - { + if (context->counter_index != ~0) { container_data_t& d = data[context->counter_index]; - d._parent = parent; - d._start_offset = context->offset; - d._end_offset = context->end_offset; + d._parent = parent; + d._start_offset = context->offset; + d._end_offset = context->end_offset; const uint8_t flags = _instructions.get(context->offset + 1); inkAssert(flags < 16); - d._flags = flags; + d._flags = flags; // Since we might be skipping tree levels, we need to be explicit about the parent. - parent = context->counter_index; + parent = context->counter_index; } // Recurse @@ -458,7 +458,8 @@ void binary_emitter::build_container_data(std::vector& data, c } void binary_emitter::build_container_hash_map( - std::vector& hash_map, std::vector& data, const std::string& name, const container_data* context + std::vector& hash_map, std::vector& data, + const std::string& name, const container_data* context ) const { // Search named children first. @@ -466,9 +467,9 @@ void binary_emitter::build_container_hash_map( // Get the child's name in the hierarchy std::string child_name = name.empty() ? child.first : (name + "." + child.first); - // Hash name. We only do this at the named child level. In theory we could support indexed children as well. - // The root is anonymous so the fact that it's skipped is not an issue. - const hash_t child_name_hash = hash_string(child_name.c_str()); + // Hash name. We only do this at the named child level. In theory we could support indexed + // children as well. The root is anonymous so the fact that it's skipped is not an issue. + const hash_t child_name_hash = hash_string(child_name.c_str()); // Store hash in the data. if (child.second->counter_index != ~0) { @@ -476,7 +477,7 @@ void binary_emitter::build_container_hash_map( } // Append the name hash and offset - hash_map.push_back( {child_name_hash, child.second->offset} ); + hash_map.push_back({child_name_hash, child.second->offset}); // Recurse build_container_hash_map(hash_map, data, child_name, child.second); diff --git a/inkcpp_compiler/binary_emitter.h b/inkcpp_compiler/binary_emitter.h index 5400130c..36dd4fa1 100644 --- a/inkcpp_compiler/binary_emitter.h +++ b/inkcpp_compiler/binary_emitter.h @@ -12,71 +12,82 @@ namespace ink::compiler::internal { - struct container_data; - class list_data; +struct container_data; +class list_data; - // binary emitter - class binary_emitter : public emitter - { - public: - binary_emitter(); - virtual ~binary_emitter(); +// binary emitter +class binary_emitter : public emitter +{ +public: + binary_emitter(); + virtual ~binary_emitter(); - // Begin emitter - virtual uint32_t start_container(int index_in_parent, const std::string& name) override; - virtual uint32_t end_container() override; - virtual int function_container_arguments(const std::string& name) override; - virtual void write_raw(Command command, CommandFlag flag = CommandFlag::NO_FLAGS, const char* payload = nullptr, ink::size_t payload_size = 0) override; - virtual void write_path(Command command, CommandFlag flag, const std::string& path, bool useCountIndex = false) override; - virtual void write_variable(Command command, CommandFlag flag, const std::string& name) override; - virtual void write_string(Command command, CommandFlag flag, const std::string& string) override; - virtual void handle_nop(int index_in_parent) override; - virtual uint32_t fallthrough_divert() override; - virtual void patch_fallthroughs(uint32_t position) override; - virtual void set_list_meta(const list_data& list_defs) override; - virtual void write_list(Command command, CommandFlag flag, const std::vector& entries) override; - // End emitter + // Begin emitter + virtual uint32_t start_container(int index_in_parent, const std::string& name) override; + virtual uint32_t end_container() override; + virtual int function_container_arguments(const std::string& name) override; + virtual void write_raw( + Command command, CommandFlag flag = CommandFlag::NO_FLAGS, const char* payload = nullptr, + ink::size_t payload_size = 0 + ) override; + virtual void write_path( + Command command, CommandFlag flag, const std::string& path, bool useCountIndex = false + ) override; + virtual void write_variable(Command command, CommandFlag flag, const std::string& name) override; + virtual void write_string(Command command, CommandFlag flag, const std::string& string) override; + virtual void handle_nop(int index_in_parent) override; + virtual uint32_t fallthrough_divert() override; + virtual void patch_fallthroughs(uint32_t position) override; + virtual void set_list_meta(const list_data& list_defs) override; + virtual void + write_list(Command command, CommandFlag flag, const std::vector& entries) override; + // End emitter - // write out the emitters data - virtual void output(std::ostream&); + // write out the emitters data + virtual void output(std::ostream&); - protected: - virtual void initialize() override; - virtual void finalize() override; - virtual void setContainerIndex(container_t index) override; +protected: + virtual void initialize() override; + virtual void finalize() override; + virtual void setContainerIndex(container_t index) override; - private: - void process_paths(); +private: + void process_paths(); - template - void emit_section(std::ostream& out, const std::vector& data) const; - void emit_section(std::ostream& out, const binary_stream& stream) const; - void close_section(std::ostream& out) const; + template + void emit_section(std::ostream& out, const std::vector& data) const; + void emit_section(std::ostream& out, const binary_stream& stream) const; + void close_section(std::ostream& out) const; - using container_data_t = ink::internal::container_data_t; - using container_map_t = ink::internal::container_map_t; - using container_hash_t = ink::internal::container_hash_t; + using container_data_t = ink::internal::container_data_t; + using container_map_t = ink::internal::container_map_t; + using container_hash_t = ink::internal::container_hash_t; - void build_container_data(std::vector& data, container_t parent, const container_data* context) const; + void build_container_data( + std::vector& data, container_t parent, const container_data* context + ) const; - void build_container_hash_map(std::vector& hash, std::vector& data, const std::string&, const container_data* context) const; + void build_container_hash_map( + std::vector& hash, std::vector& data, const std::string&, + const container_data* context + ) const; - private: - container_data* _root; - container_data* _current; - compilation_results* _results; +private: + container_data* _root; + container_data* _current; + compilation_results* _results; - binary_stream _strings; - uint32_t _list_count = 0; - binary_stream _list_meta; - binary_stream _lists; - binary_stream _instructions; + binary_stream _strings; + uint32_t _list_count = 0; + binary_stream _list_meta; + binary_stream _lists; + binary_stream _instructions; - // positon to write address - // path as string - // if path may not exists (used for function fallbackes) - // container data - // use count index? - std::vector> _paths; - }; -} + // positon to write address + // path as string + // if path may not exists (used for function fallbackes) + // container data + // use count index? + std::vector> _paths; +}; +} // namespace ink::compiler::internal diff --git a/inkcpp_compiler/emitter.cpp b/inkcpp_compiler/emitter.cpp index c116b322..39e27fd3 100644 --- a/inkcpp_compiler/emitter.cpp +++ b/inkcpp_compiler/emitter.cpp @@ -8,55 +8,51 @@ namespace ink::compiler::internal { - void emitter::start(int ink_version, compilation_results* results) - { - // store - _ink_version = ink_version; - set_results(results); - - // reset - _container_map.clear(); - _max_container_index = 0; - - // initialize - initialize(); - } +void emitter::start(int ink_version, compilation_results* results) +{ + // store + _ink_version = ink_version; + set_results(results); - void emitter::finish(container_t max_container_index) - { - // store max index - _max_container_index = max_container_index; + // reset + _container_map.clear(); + _max_container_index = 0; - // finalize - finalize(); - } + // initialize + initialize(); +} - void emitter::add_start_to_container_map(uint32_t offset, container_t index) - { - if (_container_map.rbegin() != _container_map.rend()) - { - if (_container_map.rbegin()->_offset > offset) - { - warn() << "Container map written out of order. Wrote container at offset " - << offset << " after container with offset " << _container_map.rbegin()->_offset << std::flush; - } - } +void emitter::finish(container_t max_container_index) +{ + // store max index + _max_container_index = max_container_index; - _container_map.push_back( {offset, index} ); - setContainerIndex(index); - } + // finalize + finalize(); +} - void emitter::add_end_to_container_map(uint32_t offset, container_t index) - { - if (_container_map.rbegin() != _container_map.rend()) - { - if (_container_map.rbegin()->_offset > offset) - { - warn() << "Container map written out of order. Wrote container at offset " - << offset << " after container with offset " << _container_map.rbegin()->_offset << std::flush; - } +void emitter::add_start_to_container_map(uint32_t offset, container_t index) +{ + if (_container_map.rbegin() != _container_map.rend()) { + if (_container_map.rbegin()->_offset > offset) { + warn() << "Container map written out of order. Wrote container at offset " << offset + << " after container with offset " << _container_map.rbegin()->_offset << std::flush; } + } + + _container_map.push_back({offset, index}); + setContainerIndex(index); +} - _container_map.push_back( {offset, index} ); +void emitter::add_end_to_container_map(uint32_t offset, container_t index) +{ + if (_container_map.rbegin() != _container_map.rend()) { + if (_container_map.rbegin()->_offset > offset) { + warn() << "Container map written out of order. Wrote container at offset " << offset + << " after container with offset " << _container_map.rbegin()->_offset << std::flush; + } } + + _container_map.push_back({offset, index}); } +} // namespace ink::compiler::internal diff --git a/inkcpp_compiler/emitter.h b/inkcpp_compiler/emitter.h index 96d0f2a9..fa3eec21 100644 --- a/inkcpp_compiler/emitter.h +++ b/inkcpp_compiler/emitter.h @@ -15,92 +15,98 @@ namespace ink::compiler::internal { - class list_data; +class list_data; - // Abstract base class for emitters which write ink commands to a file - class emitter : public reporter - { - public: - virtual ~emitter() { } +// Abstract base class for emitters which write ink commands to a file +class emitter : public reporter +{ +public: + virtual ~emitter() {} - // starts up the emitter (and calls initialize) - void start(int ink_version, compilation_results* results = nullptr); + // starts up the emitter (and calls initialize) + void start(int ink_version, compilation_results* results = nullptr); - // tells the emitter compilation is done (and calls finalize) - void finish(container_t max_container_index); + // tells the emitter compilation is done (and calls finalize) + void finish(container_t max_container_index); - // start a container - virtual uint32_t start_container(int index_in_parent, const std::string& name) = 0; + // start a container + virtual uint32_t start_container(int index_in_parent, const std::string& name) = 0; - // ends a container - virtual uint32_t end_container() = 0; + // ends a container + virtual uint32_t end_container() = 0; - // checks if _root contains a container named name to check - // if name is in valid internal function name - // @return number of arguments functions takes (arity) - // @retval -1 if the function was not found - virtual int function_container_arguments(const std::string& name) = 0; + // checks if _root contains a container named name to check + // if name is in valid internal function name + // @return number of arguments functions takes (arity) + // @retval -1 if the function was not found + virtual int function_container_arguments(const std::string& name) = 0; - // Writes a command with an optional payload - virtual void write_raw(Command command, CommandFlag flag = CommandFlag::NO_FLAGS, const char* payload = nullptr, ink::size_t payload_size = 0) = 0; + // Writes a command with an optional payload + virtual void write_raw( + Command command, CommandFlag flag = CommandFlag::NO_FLAGS, const char* payload = nullptr, + ink::size_t payload_size = 0 + ) = 0; - // Writes a command with a path as the payload - virtual void write_path(Command command, CommandFlag flag, const std::string& path, bool useCountIndex = false) = 0; + // Writes a command with a path as the payload + virtual void write_path( + Command command, CommandFlag flag, const std::string& path, bool useCountIndex = false + ) = 0; - // Writes a command with a variable as the payload - virtual void write_variable(Command command, CommandFlag flag, const std::string& name) = 0; + // Writes a command with a variable as the payload + virtual void write_variable(Command command, CommandFlag flag, const std::string& name) = 0; - // Writes a command with a string payload - virtual void write_string(Command command, CommandFlag flag, const std::string& string) = 0; + // Writes a command with a string payload + virtual void write_string(Command command, CommandFlag flag, const std::string& string) = 0; - // write a command with a list payload - virtual void write_list(Command commmand, CommandFlag flag, const std::vector& list) = 0; + // write a command with a list payload + virtual void write_list(Command commmand, CommandFlag flag, const std::vector& list) + = 0; - // Callback for nop commands - virtual void handle_nop(int index_in_parent) = 0; + // Callback for nop commands + virtual void handle_nop(int index_in_parent) = 0; - // adds a fallthrough divert - virtual uint32_t fallthrough_divert() = 0; + // adds a fallthrough divert + virtual uint32_t fallthrough_divert() = 0; - // Patches a fallthrough divert at the given position to divert to the current position - virtual void patch_fallthroughs(uint32_t position) = 0; + // Patches a fallthrough divert at the given position to divert to the current position + virtual void patch_fallthroughs(uint32_t position) = 0; - // Adds a container start marker to the container map - void add_start_to_container_map(uint32_t offset, container_t index); + // Adds a container start marker to the container map + void add_start_to_container_map(uint32_t offset, container_t index); - // Adds a container end marker to the container map - void add_end_to_container_map(uint32_t offset, container_t index); + // Adds a container end marker to the container map + void add_end_to_container_map(uint32_t offset, container_t index); - // add list definitions - virtual void set_list_meta(const list_data& lists_defs) = 0; + // add list definitions + virtual void set_list_meta(const list_data& lists_defs) = 0; - // Helpers - template - void write(Command command, const T& param, CommandFlag flag = CommandFlag::NO_FLAGS) - { - static_assert(sizeof(T) == 4, "Parameters must be 4 bytes long"); - write_raw(command, flag, (const char*)(¶m), sizeof(T)); - } + // Helpers + template + void write(Command command, const T& param, CommandFlag flag = CommandFlag::NO_FLAGS) + { + static_assert(sizeof(T) == 4, "Parameters must be 4 bytes long"); + write_raw(command, flag, ( const char* ) (¶m), sizeof(T)); + } - protected: - // Initialize (clear state, get ready for a new file) - virtual void initialize() = 0; +protected: + // Initialize (clear state, get ready for a new file) + virtual void initialize() = 0; - // Finalize (do any post processing necessary) - virtual void finalize() = 0; + // Finalize (do any post processing necessary) + virtual void finalize() = 0; - // Set container index for visit tracking - virtual void setContainerIndex(container_t index) = 0; + // Set container index for visit tracking + virtual void setContainerIndex(container_t index) = 0; - protected: - using container_map_t = ink::internal::container_map_t; - typedef std::vector container_map; +protected: + using container_map_t = ink::internal::container_map_t; + typedef std::vector container_map; - // container map - container_map _container_map; - container_t _max_container_index; + // container map + container_map _container_map; + container_t _max_container_index; - // ink version - int _ink_version; - }; -} + // ink version + int _ink_version; +}; +} // namespace ink::compiler::internal diff --git a/shared/private/header.h b/shared/private/header.h index abf12624..43383f15 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -9,112 +9,111 @@ #include "system.h" #include "command.h" -namespace ink::internal { - - struct header { - - static constexpr uint32_t InkBinMagic = ('I' << 24) | ('N' << 16) | ('K' << 8) | 'B'; - static constexpr uint32_t InkBinMagic_Differ = ('B' << 24) | ('K' << 16) | ('N' << 8) | 'I'; - static constexpr uint32_t Alignment = 16; - - uint32_t ink_bin_magic = InkBinMagic; - uint16_t ink_version_number = 0; - uint16_t ink_bin_version_number = 0; - - enum class endian_types : uint8_t { - none, - same, - differ - }; - - constexpr endian_types endian() const - { - switch (ink_bin_magic) - { - case InkBinMagic : return endian_types::same; - case InkBinMagic_Differ: return endian_types::differ; - default: return endian_types::none; - } - } - - bool verify() const; - - - struct section_t - { - uint32_t _start = 0; - uint32_t _bytes = 0; - - void setup(uint32_t& offset, uint32_t bytes) - { - _start = (offset + Alignment - 1) & ~(Alignment-1); - _bytes = bytes; - offset = _start + _bytes; - } - }; - - // File section sizes. Each section is aligned to Alignment - section_t _strings; - section_t _list_meta; - section_t _lists; - section_t _containers; - section_t _container_map; - section_t _container_hash; - section_t _instructions; - }; - - // One entry in the container hash. Used to translate paths into story locations. - struct container_hash_t - { - // Hash of the container's path string. - hash_t _hash; - - // Offset to the start of this container. - uint32_t _offset; +namespace ink::internal +{ - uint32_t key() const { return _hash; } - bool operator<(const container_hash_t& other) const { return _hash < other._hash; } - }; +struct header { - // One entry in the container map. Used to work out which container a story location is in. - struct container_map_t - { - // Offset to the start of this container's instructions. - uint32_t _offset; + static constexpr uint32_t InkBinMagic = ('I' << 24) | ('N' << 16) | ('K' << 8) | 'B'; + static constexpr uint32_t InkBinMagic_Differ = ('B' << 24) | ('K' << 16) | ('N' << 8) | 'I'; + static constexpr uint32_t Alignment = 16; - // Container index. - container_t _id; + uint32_t ink_bin_magic = InkBinMagic; + uint16_t ink_version_number = 0; + uint16_t ink_bin_version_number = 0; - uint32_t key() const { return _offset; } - bool operator<(const container_map_t& other) const { return _offset < other._offset; } - }; - - // One entry in the container data. Describes containers. - struct container_data_t - { - /// Parent container, or ~0 if this is the root. - // TODO: Pack into 28 with explicit invalid container_t, since we expect fewer containers than instructions. - container_t _parent; + enum class endian_types : uint8_t { + none, + same, + differ + }; - /// Container flags (saves looking up via instruction data) - uint32_t _flags : 4; + constexpr endian_types endian() const + { + switch (ink_bin_magic) { + case InkBinMagic: return endian_types::same; + case InkBinMagic_Differ: return endian_types::differ; + default: return endian_types::none; + } + } - /// Instruction offset to the start instruction (enter marker) of this container. - uint32_t _start_offset : 28; + bool verify() const; - /// Instruction offset to the end instruction (leave marker) of this container - uint32_t _end_offset; + struct section_t { + uint32_t _start = 0; + uint32_t _bytes = 0; - /// Container hash. - uint32_t _hash; - - /// Check to see if the instruction offset is part of the instructions for this container. Note that this is inclusive not exclusive. - bool contains(uint32_t offset) const { return offset >= _start_offset && offset <= _end_offset; } - - /// Check to see if this is a knot container. - bool knot() const { return _flags & uint8_t(CommandFlag::CONTAINER_MARKER_IS_KNOT); } - - /// Check to see if this is a container which tracks visits. - bool visit() const { return _flags & uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_VISITS); } - }; -} + void setup(uint32_t& offset, uint32_t bytes) + { + _start = (offset + Alignment - 1) & ~(Alignment - 1); + _bytes = bytes; + offset = _start + _bytes; + } + }; + + // File section sizes. Each section is aligned to Alignment + section_t _strings; + section_t _list_meta; + section_t _lists; + section_t _containers; + section_t _container_map; + section_t _container_hash; + section_t _instructions; +}; + +// One entry in the container hash. Used to translate paths into story locations. +struct container_hash_t { + // Hash of the container's path string. + hash_t _hash; + + // Offset to the start of this container. + uint32_t _offset; + + uint32_t key() const { return _hash; } + + bool operator<(const container_hash_t& other) const { return _hash < other._hash; } +}; + +// One entry in the container map. Used to work out which container a story location is in. +struct container_map_t { + // Offset to the start of this container's instructions. + uint32_t _offset; + + // Container index. + container_t _id; + + uint32_t key() const { return _offset; } + + bool operator<(const container_map_t& other) const { return _offset < other._offset; } +}; + +// One entry in the container data. Describes containers. +struct container_data_t { + /// Parent container, or ~0 if this is the root. + // TODO: Pack into 28 with explicit invalid container_t, since we expect fewer containers than + // instructions. + container_t _parent; + + /// Container flags (saves looking up via instruction data) + uint32_t _flags : 4; + + /// Instruction offset to the start instruction (enter marker) of this container. + uint32_t _start_offset : 28; + + /// Instruction offset to the end instruction (leave marker) of this container + uint32_t _end_offset; + + /// Container hash. + uint32_t _hash; + + /// Check to see if the instruction offset is part of the instructions for this container. Note + /// that this is inclusive not exclusive. + bool contains(uint32_t offset) const { return offset >= _start_offset && offset <= _end_offset; } + + /// Check to see if this is a knot container. + bool knot() const { return _flags & uint8_t(CommandFlag::CONTAINER_MARKER_IS_KNOT); } + + /// Check to see if this is a container which tracks visits. + bool visit() const { return _flags & uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_VISITS); } +}; +} // namespace ink::internal diff --git a/shared/public/system.h b/shared/public/system.h index df0e13f9..d8a417d9 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -115,7 +115,8 @@ struct list_flag { bool operator!=(const list_flag& o) const { return ! (*this == o); } }; -inline list_flag read_list_flag(const char*& ptr) { +inline list_flag read_list_flag(const char*& ptr) +{ list_flag result = *reinterpret_cast(ptr); ptr += sizeof(list_flag); return result; From 46569c365d1d47800e8edfda4bd3332bb446ba8b Mon Sep 17 00:00:00 2001 From: willvale <66674079+willvale@users.noreply.github.com> Date: Tue, 23 Dec 2025 22:15:23 +1300 Subject: [PATCH 32/32] Missed one. --- inkcpp/runner_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index acc73d77..aa6e3f34 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -349,7 +349,7 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) // Named knots/stitches need special handling - their visit counts are updated wherever the // story enters them, // and we generally need to know which knot we're in, for tagging, unless we're jumping to a - //tunnel or similar + // tunnel or similar // which suppresses knot tracking. // // Ink has a rule about incrementing visit counts when you jump to the top of a knot, which