From 6a09af560b13e778c65636a1210bdf8ed1f13f30 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Mon, 1 Mar 2021 12:42:38 +0100 Subject: [PATCH 01/11] Add definition of limits in config --- inkcpp/globals_impl.h | 3 +-- inkcpp/runner_impl.h | 15 +++++++-------- shared/public/config.h | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/inkcpp/globals_impl.h b/inkcpp/globals_impl.h index bf6ce785..c8eba57a 100644 --- a/inkcpp/globals_impl.h +++ b/inkcpp/globals_impl.h @@ -90,10 +90,9 @@ namespace ink::runtime::internal // Allocated string table (shared by all runners using this global store) mutable string_table _strings; - // Global variables (TODO: Max 50?) // Implemented as a stack (slow lookup) because it has save/restore functionality. // If I could create an avl tree with save/restore, that'd be great but seems super complex. - internal::stack<50> _variables; + internal::stack _variables; bool _globals_initialized; }; } diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index a92efc10..3cfefe31 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -144,23 +144,22 @@ namespace ink::runtime::internal ip_t _done; // when we last hit a done // Output stream - internal::stream<200> _output; + internal::stream _output; // Runtime stack. Used to store temporary variables and callstack - internal::stack<50> _stack; + internal::stack _stack; // Evaluation stack bool bEvaluationMode = false; - internal::eval_stack<20> _eval; + internal::eval_stack _eval; bool bSavedEvaluationMode = false; // Keeps track of what threads we're inside - internal::restorable_stack _threads; - internal::fixed_restorable_array _threadDone; + internal::restorable_stack _threads; + internal::fixed_restorable_array _threadDone; // Choice list - static const size_t MAX_CHOICES = 10; - choice _choices[MAX_CHOICES]; + choice _choices[config::maxChoices]; size_t _num_choices = 0; // Tag list @@ -172,7 +171,7 @@ namespace ink::runtime::internal functions _functions; // Container set - internal::restorable_stack _container; + internal::restorable_stack _container; bool _is_falling = false; bool _saved = false; diff --git a/shared/public/config.h b/shared/public/config.h index ea8fd974..bfaddb5c 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -8,4 +8,17 @@ #endif // Only turn on if you have json.hpp and you want to use it with the compiler -// #define INK_EXPOSE_JSON \ No newline at end of file +// #define INK_EXPOSE_JSON + +namespace ink::config { + static constexpr unsigned limitGlobalVariables = 50; + static constexpr unsigned limitThreadDepth = 20; + static constexpr unsigned limitEvalStackDepth = 20; + static constexpr unsigned limitContainerDepth = 20 + // temporary variables and callstack; + static constexpr unsigned limitRuntimeStack = 50; + // max number of elements in one output (a string is one element) + static constexpr unsigned limitOutputSize = 200; + // max number of choices per choice + static constexpr unsigned maxChoices = 10; +} From 1999b2a16168475025e0d4f1c76b6a20e03a3a3c Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Mon, 1 Mar 2021 12:48:16 +0100 Subject: [PATCH 02/11] Minor fix --- inkcpp/runner_impl.cpp | 2 +- shared/public/config.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 3ed15cf5..d7fa3a75 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -50,7 +50,7 @@ namespace ink::runtime::internal choice& runner_impl::add_choice() { - inkAssert(_num_choices < MAX_CHOICES, "Ran out of choice storage!"); + inkAssert(_num_choices < config::maxChoices, "Ran out of choice storage!"); return _choices[_num_choices++]; } diff --git a/shared/public/config.h b/shared/public/config.h index bfaddb5c..b9c7cc9c 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -14,7 +14,7 @@ namespace ink::config { static constexpr unsigned limitGlobalVariables = 50; static constexpr unsigned limitThreadDepth = 20; static constexpr unsigned limitEvalStackDepth = 20; - static constexpr unsigned limitContainerDepth = 20 + static constexpr unsigned limitContainerDepth = 20; // temporary variables and callstack; static constexpr unsigned limitRuntimeStack = 50; // max number of elements in one output (a string is one element) From ecac38f2ceb6db8eaebbde9c5b5ede4986868102 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Mon, 1 Mar 2021 14:18:56 +0100 Subject: [PATCH 03/11] Dynamic choice handling --- inkcpp/array.h | 46 ++++++++++++++++++++++++++++++++++++++++++ inkcpp/runner_impl.cpp | 17 ++++++++-------- inkcpp/runner_impl.h | 7 +++---- shared/public/config.h | 17 +++++++++------- shared/public/system.h | 6 ++++++ 5 files changed, 74 insertions(+), 19 deletions(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index acd4a3f2..82ce7884 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -4,6 +4,52 @@ namespace ink::runtime::internal { + template + class managed_array { + static constexpr uint32_t initialCapacity = N > 0 ? N : -N; + static constexpr bool dynamic = N < 0; + public: + managed_array() : _capacity{initialCapacity}, _size{0}{ + if constexpr (dynamic) { + _data = new T[initialCapacity]; + } else { + _data = _buffer; + } + } + + const T& operator[](size_t i) const { return _data[i]; } + T& operator[](size_t i) { return _data[i]; } + const T* data() const { return _data; } + T* data() { return _data; } + const T* begin() const { return _data; } + T* begin() { return _data; } + const T* end() const { return _data + _size; } + T* end() { return _data + _size; } + + const size_t size() const { return _size; } + // void push(const T& t) { if (_size == _capacity) { extend(); } _data[_size++] = t; } + T& push() { if (_size == _capacity) { extend(); } return _data[_size++]; } + void clear() { _size = 0; } + private: + void extend() { + size_t new_capacity = 1.5f * _capacity; + if (new_capacity < 5) { new_capacity = 5; } + T* new_data = new T[new_capacity]; + + for(size_t i = 0; i < _capacity; ++i) { + new_data[i] = _data[i]; + } + + delete[] _data; + _data = new_data; + _capacity = new_capacity; + } + + T _buffer[dynamic ? 0 : initialCapacity]; + T* _data; + size_t _capacity; + size_t _size; + }; template class basic_restorable_array { diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index d7fa3a75..f43790bd 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -50,14 +50,15 @@ namespace ink::runtime::internal choice& runner_impl::add_choice() { - inkAssert(_num_choices < config::maxChoices, "Ran out of choice storage!"); - return _choices[_num_choices++]; + inkAssert(config::maxChoices < 0 || _choices.size() < config::maxChoices, + "Ran out of choice storage!"); + return _choices.push(); } void runner_impl::clear_choices() { - // TODO: Garbage collection? - _num_choices = 0; + // TODO: Garbage collection? ? which garbage ? + _choices.clear(); } void runner_impl::clear_tags() @@ -367,7 +368,7 @@ namespace ink::runtime::internal void runner_impl::choose(size_t index) { - inkAssert(index < _num_choices, "Choice index out of range"); + inkAssert(index < _choices.size(), "Choice index out of range"); // Get the choice const auto& c = _choices[index]; @@ -892,7 +893,7 @@ namespace ink::runtime::internal for(;sc;--sc) { _output << stack[sc-1]; } // Create choice and record it - add_choice().setup(_output, _globals->strings(), _num_choices, path, current_thread()); + add_choice().setup(_output, _globals->strings(), _choices.size(), path, current_thread()); } break; case Command::START_CONTAINER_MARKER: { @@ -1036,7 +1037,7 @@ namespace ink::runtime::internal _threadDone.clear(nullptr); bEvaluationMode = false; _saved = false; - _num_choices = 0; + _choices.clear(); _ptr = nullptr; _done = nullptr; _container.clear(); @@ -1050,7 +1051,7 @@ namespace ink::runtime::internal _eval.mark_strings(strings); // Take into account choice text - for (int i = 0; i < _num_choices; i++) + for (int i = 0; i < _choices.size(); i++) strings.mark_used(_choices[i]._text); } diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 3cfefe31..814797ab 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -37,10 +37,10 @@ namespace ink::runtime::internal virtual bool can_continue() const override; // Begin iterating choices - virtual const choice* begin() const override { return _choices; } + virtual const choice* begin() const override { return _choices.begin(); } // End iterating choices - virtual const choice* end() const override { return _choices + _num_choices; } + virtual const choice* end() const override { return _choices.end(); } // Chooses a choice by index virtual void choose(size_t index) override; @@ -159,8 +159,7 @@ namespace ink::runtime::internal internal::fixed_restorable_array _threadDone; // Choice list - choice _choices[config::maxChoices]; - size_t _num_choices = 0; + managed_array _choices; // Tag list static const size_t MAX_TAGS = 100; diff --git a/shared/public/config.h b/shared/public/config.h index b9c7cc9c..cbd30085 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -11,14 +11,17 @@ // #define INK_EXPOSE_JSON namespace ink::config { - static constexpr unsigned limitGlobalVariables = 50; - static constexpr unsigned limitThreadDepth = 20; - static constexpr unsigned limitEvalStackDepth = 20; - static constexpr unsigned limitContainerDepth = 20; + /// set limitations which are required to minimize heap allocations. + /// if required you can set them to -x then, the system will use dynamic + /// allocation for this type, with an initial size of x. + static constexpr int limitGlobalVariables = 50; + static constexpr int limitThreadDepth = 20; + static constexpr int limitEvalStackDepth = 20; + static constexpr int limitContainerDepth = 20; // temporary variables and callstack; - static constexpr unsigned limitRuntimeStack = 50; + static constexpr int limitRuntimeStack = 50; // max number of elements in one output (a string is one element) - static constexpr unsigned limitOutputSize = 200; + static constexpr int limitOutputSize = 200; // max number of choices per choice - static constexpr unsigned maxChoices = 10; + static constexpr int maxChoices = 10; } diff --git a/shared/public/system.h b/shared/public/system.h index 820e3e83..5547f016 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -121,6 +121,12 @@ namespace ink { template struct always_false { static constexpr bool value = false; }; + template + struct enable_if { + using type = T; + }; + template + struct enable_if {}; } #ifdef INK_ENABLE_STL From 0a4fda1b622346426884dfa73b49d6cc17cc7650 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Mon, 1 Mar 2021 16:07:48 +0100 Subject: [PATCH 04/11] Add dynamic allocation for stacks --- inkcpp/array.h | 38 ++++++++++++++------------ inkcpp/collections/restorable.h | 4 ++- inkcpp/globals_impl.h | 2 +- inkcpp/runner_impl.h | 6 ++-- inkcpp/simple_restorable_stack.h | 2 +- inkcpp/stack.h | 47 +++++++++++++++++++++++++++++--- shared/public/config.h | 6 ++-- shared/public/system.h | 1 + 8 files changed, 76 insertions(+), 30 deletions(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index 82ce7884..aca2eec1 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -4,10 +4,8 @@ namespace ink::runtime::internal { - template + template class managed_array { - static constexpr uint32_t initialCapacity = N > 0 ? N : -N; - static constexpr bool dynamic = N < 0; public: managed_array() : _capacity{initialCapacity}, _size{0}{ if constexpr (dynamic) { @@ -27,29 +25,35 @@ namespace ink::runtime::internal T* end() { return _data + _size; } const size_t size() const { return _size; } - // void push(const T& t) { if (_size == _capacity) { extend(); } _data[_size++] = t; } + const size_t capacity() const { return _capacity; } T& push() { if (_size == _capacity) { extend(); } return _data[_size++]; } void clear() { _size = 0; } - private: - void extend() { - size_t new_capacity = 1.5f * _capacity; - if (new_capacity < 5) { new_capacity = 5; } - T* new_data = new T[new_capacity]; - - for(size_t i = 0; i < _capacity; ++i) { - new_data[i] = _data[i]; - } - delete[] _data; - _data = new_data; - _capacity = new_capacity; - } + void extend(); + private: T _buffer[dynamic ? 0 : initialCapacity]; T* _data; size_t _capacity; size_t _size; }; + + template + void managed_array::extend() + { + size_t new_capacity = 1.5f * _capacity; + if (new_capacity < 5) { new_capacity = 5; } + T* new_data = new T[new_capacity]; + + for(size_t i = 0; i < _capacity; ++i) { + new_data[i] = _data[i]; + } + + delete[] _data; + _data = new_data; + _capacity = new_capacity; + } + template class basic_restorable_array { diff --git a/inkcpp/collections/restorable.h b/inkcpp/collections/restorable.h index 173e6b5a..b13793ce 100644 --- a/inkcpp/collections/restorable.h +++ b/inkcpp/collections/restorable.h @@ -327,7 +327,9 @@ namespace ink::runtime::internal protected: // Called when we run out of space in buffer. - virtual void overflow(ElementType*& buffer, size_t& size) { throw 0; /* TODO: What to do here? Throw something more useful? */ } + virtual void overflow(ElementType*& buffer, size_t& size) { + throw ink_exception("Restorable run out of memory!"); + } private: template diff --git a/inkcpp/globals_impl.h b/inkcpp/globals_impl.h index c8eba57a..407052e0 100644 --- a/inkcpp/globals_impl.h +++ b/inkcpp/globals_impl.h @@ -92,7 +92,7 @@ namespace ink::runtime::internal // Implemented as a stack (slow lookup) because it has save/restore functionality. // If I could create an avl tree with save/restore, that'd be great but seems super complex. - internal::stack _variables; + internal::stack _variables; bool _globals_initialized; }; } diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 814797ab..69104283 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -147,11 +147,11 @@ namespace ink::runtime::internal internal::stream _output; // Runtime stack. Used to store temporary variables and callstack - internal::stack _stack; + internal::stack _stack; // Evaluation stack bool bEvaluationMode = false; - internal::eval_stack _eval; + internal::eval_stack _eval; bool bSavedEvaluationMode = false; // Keeps track of what threads we're inside @@ -159,7 +159,7 @@ namespace ink::runtime::internal internal::fixed_restorable_array _threadDone; // Choice list - managed_array _choices; + managed_array _choices; // Tag list static const size_t MAX_TAGS = 100; diff --git a/inkcpp/simple_restorable_stack.h b/inkcpp/simple_restorable_stack.h index 7c300db5..df1647de 100644 --- a/inkcpp/simple_restorable_stack.h +++ b/inkcpp/simple_restorable_stack.h @@ -190,4 +190,4 @@ namespace ink::runtime::internal // Just reset save position _save = InvalidIndex; } -} \ No newline at end of file +} diff --git a/inkcpp/stack.h b/inkcpp/stack.h index 29292bb9..74fd357e 100644 --- a/inkcpp/stack.h +++ b/inkcpp/stack.h @@ -2,6 +2,7 @@ #include "value.h" #include "collections/restorable.h" +#include "array.h" namespace ink { @@ -82,8 +83,12 @@ namespace ink static const hash_t NulledHashId = ~0; }; - // stack for call history and temporary variables - template + /** + * @brief stack for call history and temporary variables + * @tparam N initial capacity of stack + * @tparam Dynamic weather or not expand if stack is full + */ + template class stack : public basic_stack { public: @@ -93,6 +98,26 @@ namespace ink entry _stack[N]; }; + template + class stack : public basic_stack + { + public: + stack() : basic_stack(nullptr, 0) {} + protected: + virtual void overflow(entry*& buffer, size_t& size) override { + if (!buffer) { + buffer = _stack.data(); + size = _stack.capacity(); + } else { + _stack.extend(); + buffer = _stack.data(); + size = _stack.capacity(); + } + } + private: + managed_array _stack; + }; + class basic_eval_stack : protected restorable { protected: @@ -125,14 +150,28 @@ namespace ink void forget(); }; - template + template class eval_stack : public basic_eval_stack { public: - eval_stack() : basic_eval_stack(&_stack[0], N) { } + eval_stack() : basic_eval_stack(_stack, N) { } private: value _stack[N]; }; + template + class eval_stack : public basic_eval_stack + { + public: + eval_stack() : basic_eval_stack(nullptr, 0) {} + protected: + virtual void overflow(value*& buffer, size_t& size) override { + _stack.extend(); + buffer = _stack.data(); + size = _stack.capacity(); + } + private: + managed_array _stack; + }; } } } diff --git a/shared/public/config.h b/shared/public/config.h index cbd30085..29d9af83 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -14,12 +14,12 @@ namespace ink::config { /// set limitations which are required to minimize heap allocations. /// if required you can set them to -x then, the system will use dynamic /// allocation for this type, with an initial size of x. - static constexpr int limitGlobalVariables = 50; + static constexpr int limitGlobalVariables = -1; static constexpr int limitThreadDepth = 20; - static constexpr int limitEvalStackDepth = 20; + static constexpr int limitEvalStackDepth = -1; static constexpr int limitContainerDepth = 20; // temporary variables and callstack; - static constexpr int limitRuntimeStack = 50; + static constexpr int limitRuntimeStack = -1; // max number of elements in one output (a string is one element) static constexpr int limitOutputSize = 200; // max number of choices per choice diff --git a/shared/public/system.h b/shared/public/system.h index 5547f016..0cfc7a82 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -119,6 +119,7 @@ namespace ink namespace runtime::internal { + constexpr unsigned abs(int i) { return i < 0 ? -i : i; } template struct always_false { static constexpr bool value = false; }; template From 916bcc51ec86cd86c71189fe0805fcf8cd711a08 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Mon, 1 Mar 2021 16:09:51 +0100 Subject: [PATCH 05/11] Add sane defaults --- shared/public/config.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/public/config.h b/shared/public/config.h index 29d9af83..97e26455 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -14,12 +14,12 @@ namespace ink::config { /// set limitations which are required to minimize heap allocations. /// if required you can set them to -x then, the system will use dynamic /// allocation for this type, with an initial size of x. - static constexpr int limitGlobalVariables = -1; + static constexpr int limitGlobalVariables = -50; static constexpr int limitThreadDepth = 20; - static constexpr int limitEvalStackDepth = -1; + static constexpr int limitEvalStackDepth = -20; static constexpr int limitContainerDepth = 20; // temporary variables and callstack; - static constexpr int limitRuntimeStack = -1; + static constexpr int limitRuntimeStack = -20; // max number of elements in one output (a string is one element) static constexpr int limitOutputSize = 200; // max number of choices per choice From d836c0e5921fb31ef60d8484179281c2e122709b Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Mon, 1 Mar 2021 19:10:24 +0100 Subject: [PATCH 06/11] Make number of threads potential dynamic --- inkcpp/array.h | 35 +++++++++++++++--- inkcpp/globals_impl.cpp | 2 +- inkcpp/runner_impl.cpp | 13 ++----- inkcpp/runner_impl.h | 63 +++++++++++++++++++++++++++++++- inkcpp/simple_restorable_stack.h | 12 ++++-- inkcpp_test/Array.cpp | 6 +-- shared/public/config.h | 4 +- shared/public/system.h | 9 +++++ 8 files changed, 119 insertions(+), 25 deletions(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index aca2eec1..8adac0c1 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -94,6 +94,10 @@ namespace ink::runtime::internal protected: inline T* buffer() { return _array; } + void set_new_buffer(T* buffer, size_t capacity) { + _array = buffer; + _capacity = capacity; + } private: inline void check_index(size_t index) const { inkAssert(index < capacity(), "Index out of range!"); } @@ -102,14 +106,14 @@ namespace ink::runtime::internal bool _saved; // real values live here - T* const _array; + T* _array; // we store values here when we're in save mode // they're copied on a call to forget() T* const _temp; // size of both _array and _temp - const size_t _capacity; + size_t _capacity; // null const T _null; @@ -211,11 +215,31 @@ namespace ink::runtime::internal template class allocated_restorable_array : public basic_restorable_array { + using base = basic_restorable_array; public: - allocated_restorable_array(size_t capacity, const T &nullValue) - : basic_restorable_array(new T[capacity * 2], capacity * 2, nullValue) - { + allocated_restorable_array(const T& initial, const T& nullValue) + : basic_restorable_array(0, 0, nullValue), _initialValue{initial} + {} + allocated_restorable_array(size_t capacity, const T& initial, const T &nullValue) + : basic_restorable_array(new T[capacity * 2], capacity * 2, nullValue), + _initialValue{initial} + { _buffer = this->buffer(); + this->clear(_initialValue); + } + + void resize(size_t n) { + size_t new_capacity = 2 * n; + T* new_buffer = new T[new_capacity]; + for(size_t i = 0; i < base::capacity(); ++i) { + new_buffer[i] = _buffer[i]; + } + for(size_t i = base::capacity(); i < new_capacity; ++i) { + new_buffer[i] = _initialValue; + } + delete[] _buffer; + _buffer = new_buffer; + this->set_new_buffer(_buffer, new_capacity); } ~allocated_restorable_array() @@ -225,6 +249,7 @@ namespace ink::runtime::internal } private: + T _initialValue; T* _buffer; }; } diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 8637eb8a..4733802c 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -6,7 +6,7 @@ namespace ink::runtime::internal { globals_impl::globals_impl(const story_impl* story) : _num_containers(story->num_containers()) - , _visit_counts(_num_containers, ~0) + , _visit_counts(_num_containers, 0, ~0) , _owner(story) , _runners_start(nullptr) , _globals_initialized(false) diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index f43790bd..b9e95970 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -247,8 +247,8 @@ namespace ink::runtime::internal } runner_impl::runner_impl(const story_impl* data, globals global) - : _story(data), _globals(global.cast()), _container(~0), _threads(~0), - _threadDone(nullptr, (ip_t)~0), _backup(nullptr), _done(nullptr), _choices() + : _story(data), _globals(global.cast()), _container(~0), + _backup(nullptr), _done(nullptr), _choices() { _ptr = _story->instructions(); bEvaluationMode = false; @@ -381,7 +381,7 @@ namespace ink::runtime::internal if (choiceThread == ~0) prev = _done; else - prev = _threadDone.get(choiceThread); + prev = _threads.get(choiceThread); // Make sure we have a previous pointer inkAssert(prev != nullptr, "No 'done' point recorded before finishing choice output"); @@ -393,7 +393,6 @@ namespace ink::runtime::internal // Collapse callstacks to the correct thread _stack.collapse_to_thread(choiceThread); _threads.clear(); - _threadDone.clear(nullptr); // Jump to destination and clear choice list jump(_story->instructions() + _choices[index].path()); @@ -1024,7 +1023,7 @@ namespace ink::runtime::internal _done = ptr; } else { - _threadDone.set(curr, ptr); + _threads.set(curr, ptr); } } @@ -1034,7 +1033,6 @@ namespace ink::runtime::internal _output.clear(); _stack.clear(); _threads.clear(); - _threadDone.clear(nullptr); bEvaluationMode = false; _saved = false; _choices.clear(); @@ -1067,7 +1065,6 @@ namespace ink::runtime::internal _globals->save(); _eval.save(); _threads.save(); - _threadDone.save(); bSavedEvaluationMode = bEvaluationMode; // Not doing this anymore. There can be lingering stack entries from function returns @@ -1085,7 +1082,6 @@ namespace ink::runtime::internal _globals->restore(); _eval.restore(); _threads.restore(); - _threadDone.restore(); bEvaluationMode = bSavedEvaluationMode; // Not doing this anymore. There can be lingering stack entries from function returns @@ -1106,7 +1102,6 @@ namespace ink::runtime::internal _globals->forget(); _eval.forget(); _threads.forget(); - _threadDone.forget(); // Nothing to do for eval stack. It should just stay as it is diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 69104283..537d8eb8 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -155,8 +155,53 @@ namespace ink::runtime::internal bool bSavedEvaluationMode = false; // Keeps track of what threads we're inside - internal::restorable_stack _threads; - internal::fixed_restorable_array _threadDone; + class threads : public internal::simple_restorable_stack { + using base = internal::simple_restorable_stack; + static constexpr bool dynamic = config::limitThreadDepth < 0; + static constexpr size_t N = abs(config::limitThreadDepth); + public: + threads() + : base(nullptr, 0, ~0), + _threadDone(nullptr, reinterpret_cast(~0)) { + _threadDone.clear(nullptr); + } + void clear() { + base::clear(); + _threadDone.clear(nullptr); + } + void save() { + base::save(); + _threadDone.save(); + } + void restore() { + base::restore(); + _threadDone.restore(); + } + void forget() { + base::forget(); + _threadDone.forget(); + } + + void set(size_t index, const ip_t& value) { + _threadDone.set(index, value); + } + const ip_t& get(size_t index) const { + return _threadDone.get(index); + } + const ip_t& operator[](size_t index) const { return get(index); } + + protected: + virtual void overflow(thread_t*& buffer, size_t& size) override; + private: + managed_array _stack; + template + void resize(size_t size) {} + if_t, + internal::fixed_restorable_array> _threadDone; + using resize_fn = void(internal::allocated_restorable_array::*)(size_t); + static constexpr resize_fn _resize = &allocated_restorable_array::resize; + } _threads; // Choice list managed_array _choices; @@ -177,6 +222,20 @@ namespace ink::runtime::internal prng _rng{}; }; + template<> + inline void runner_impl::threads::resize(size_t size) { _threadDone.resize(size); } + inline void runner_impl::threads::overflow(thread_t*& buffer, size_t& size) { + if constexpr (dynamic) { + if(buffer) { + _stack.extend(); + } + buffer = _stack.data(); + size = _stack.capacity(); + resize(size); + } else { + base::overflow(buffer, size); + } + } template<> inline const char* runner_impl::read(); diff --git a/inkcpp/simple_restorable_stack.h b/inkcpp/simple_restorable_stack.h index df1647de..a0f39b73 100644 --- a/inkcpp/simple_restorable_stack.h +++ b/inkcpp/simple_restorable_stack.h @@ -25,9 +25,13 @@ namespace ink::runtime::internal void save(); void restore(); void forget(); + + virtual void overflow(T*& buffer, size_t& size) { + throw ink_exception("Stack overflow!"); + } private: - T* const _buffer; - const size_t _size; + T* _buffer; + size_t _size; const T _null; const static size_t InvalidIndex = ~0; @@ -58,7 +62,9 @@ namespace ink::runtime::internal _pos = _save; } - inkAssert(_pos < _size, "Stack overflow!"); + if (_pos >= _size) { + overflow(_buffer, _size); + } // Push onto the top of the stack _buffer[_pos++] = value; diff --git a/inkcpp_test/Array.cpp b/inkcpp_test/Array.cpp index ed1ec2ed..146e3127 100644 --- a/inkcpp_test/Array.cpp +++ b/inkcpp_test/Array.cpp @@ -12,7 +12,7 @@ SCENARIO("a restorable array can hold values", "[array]") GIVEN("an empty array") { const ink::size_t length = 10; - test_array array = test_array(length, ~0); + test_array array = test_array(length, 0, ~0); THEN("the default values should be zero") { @@ -50,7 +50,7 @@ SCENARIO("a restorable array can save/restore/forget", "[array]") GIVEN("a saved array with a few values") { // Load up the array - test_array array = test_array(5, ~0); + test_array array = test_array(5, 0, ~0); array.set(0, 0); array.set(1, 1); array.set(2, 2); @@ -112,4 +112,4 @@ SCENARIO("a restorable array can save/restore/forget", "[array]") } } } -} \ No newline at end of file +} diff --git a/shared/public/config.h b/shared/public/config.h index 97e26455..edfb8bb9 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -15,7 +15,7 @@ namespace ink::config { /// if required you can set them to -x then, the system will use dynamic /// allocation for this type, with an initial size of x. static constexpr int limitGlobalVariables = -50; - static constexpr int limitThreadDepth = 20; + static constexpr int limitThreadDepth = -10; static constexpr int limitEvalStackDepth = -20; static constexpr int limitContainerDepth = 20; // temporary variables and callstack; @@ -23,5 +23,5 @@ namespace ink::config { // max number of elements in one output (a string is one element) static constexpr int limitOutputSize = 200; // max number of choices per choice - static constexpr int maxChoices = 10; + static constexpr int maxChoices = -10; } diff --git a/shared/public/system.h b/shared/public/system.h index 0cfc7a82..af5fb3dc 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -120,8 +120,17 @@ namespace ink namespace runtime::internal { constexpr unsigned abs(int i) { return i < 0 ? -i : i; } + template struct always_false { static constexpr bool value = false; }; + + template + struct if_type { using type = T1; }; + template + struct if_type { using type = T2; }; + template + using if_t = typename if_type::type; + template struct enable_if { using type = T; From 8591ca96c0d6f25764cf7884d70070370ff49fa0 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Tue, 2 Mar 2021 12:23:32 +0100 Subject: [PATCH 07/11] Adapt all stacks to be opt out dynamic - see `config.h` for usage --- inkcpp/array.h | 52 +++++++++++++++++++++----------- inkcpp/runner_impl.h | 35 +++++++++++++-------- inkcpp/simple_restorable_stack.h | 27 ++++++++++++++--- inkcpp_test/Stack.cpp | 14 +++++---- shared/public/config.h | 5 +-- shared/public/system.h | 10 +++--- 6 files changed, 96 insertions(+), 47 deletions(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index 8adac0c1..facdceb7 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -9,31 +9,48 @@ namespace ink::runtime::internal public: managed_array() : _capacity{initialCapacity}, _size{0}{ if constexpr (dynamic) { - _data = new T[initialCapacity]; - } else { - _data = _buffer; + _dynamic_data = new T[initialCapacity]; } } - const T& operator[](size_t i) const { return _data[i]; } - T& operator[](size_t i) { return _data[i]; } - const T* data() const { return _data; } - T* data() { return _data; } - const T* begin() const { return _data; } - T* begin() { return _data; } - const T* end() const { return _data + _size; } - T* end() { return _data + _size; } + const T& operator[](size_t i) const { return data()[i]; } + T& operator[](size_t i) { return data()[i]; } + const T* data() const { + if constexpr (dynamic) { + return _dynamic_data; + } else { + return _static_data; + } + } + T* data() { + if constexpr (dynamic) { + return _dynamic_data; + } else { + return _static_data; + } + } + const T* begin() const { return data(); } + T* begin() { return data(); } + const T* end() const { return data() + _size; } + T* end() { return data() + _size; } const size_t size() const { return _size; } const size_t capacity() const { return _capacity; } - T& push() { if (_size == _capacity) { extend(); } return _data[_size++]; } + T& push() { + if constexpr (dynamic) { + if (_size == _capacity) { extend(); } + } else { + ink_assert(_size <= _capacity, "Stack Overflow!"); + } + return data()[_size++]; + } void clear() { _size = 0; } void extend(); private: - T _buffer[dynamic ? 0 : initialCapacity]; - T* _data; + T _static_data[dynamic ? 0 : initialCapacity]; + T* _dynamic_data = nullptr; size_t _capacity; size_t _size; }; @@ -41,16 +58,17 @@ namespace ink::runtime::internal template void managed_array::extend() { + static_assert(dynamic, "Can only extend if array is dynamic!"); size_t new_capacity = 1.5f * _capacity; if (new_capacity < 5) { new_capacity = 5; } T* new_data = new T[new_capacity]; for(size_t i = 0; i < _capacity; ++i) { - new_data[i] = _data[i]; + new_data[i] = _dynamic_data[i]; } - delete[] _data; - _data = new_data; + delete[] _dynamic_data; + _dynamic_data = new_data; _capacity = new_capacity; } diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 537d8eb8..8bd3e4c3 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -160,11 +160,21 @@ namespace ink::runtime::internal static constexpr bool dynamic = config::limitThreadDepth < 0; static constexpr size_t N = abs(config::limitThreadDepth); public: + template = true > threads() : base(nullptr, 0, ~0), _threadDone(nullptr, reinterpret_cast(~0)) { + static_assert(sizeof...(D) == 0, "Don't use explicit template arguments!"); + } + template = true > + threads() + : _stack{}, + base(_stack.data(), N, ~0), + _threadDone(nullptr, reinterpret_cast(~0)) { + static_assert(sizeof...(D) == 0, "Don't use explicit template arguments"); _threadDone.clear(nullptr); } + void clear() { base::clear(); _threadDone.clear(nullptr); @@ -191,16 +201,18 @@ namespace ink::runtime::internal const ip_t& operator[](size_t index) const { return get(index); } protected: - virtual void overflow(thread_t*& buffer, size_t& size) override; + virtual void overflow(thread_t*& buffer, size_t& size) override final; private: - managed_array _stack; - template - void resize(size_t size) {} - if_t, - internal::fixed_restorable_array> _threadDone; - using resize_fn = void(internal::allocated_restorable_array::*)(size_t); - static constexpr resize_fn _resize = &allocated_restorable_array::resize; + internal::fixed_restorable_array>; + template + void resize(size_t size, int) { (_threadDone.*A)(size); } + void resize(size_t size, long) {} + + + managed_array _stack; + array_type _threadDone; } _threads; // Choice list @@ -215,15 +227,14 @@ namespace ink::runtime::internal functions _functions; // Container set - internal::restorable_stack _container; + internal::managed_restorable_stack _container; bool _is_falling = false; bool _saved = false; prng _rng{}; }; - template<> - inline void runner_impl::threads::resize(size_t size) { _threadDone.resize(size); } + inline void runner_impl::threads::overflow(thread_t*& buffer, size_t& size) { if constexpr (dynamic) { if(buffer) { @@ -231,7 +242,7 @@ namespace ink::runtime::internal } buffer = _stack.data(); size = _stack.capacity(); - resize(size); + resize(size, 0); } else { base::overflow(buffer, size); } diff --git a/inkcpp/simple_restorable_stack.h b/inkcpp/simple_restorable_stack.h index a0f39b73..83ac8d89 100644 --- a/inkcpp/simple_restorable_stack.h +++ b/inkcpp/simple_restorable_stack.h @@ -1,6 +1,7 @@ #pragma once #include "system.h" +#include "array.h" namespace ink::runtime::internal { @@ -40,16 +41,32 @@ namespace ink::runtime::internal size_t _save = InvalidIndex, _jump = InvalidIndex; }; - template - class restorable_stack : public simple_restorable_stack + template + class managed_restorable_stack : public simple_restorable_stack { + using base = simple_restorable_stack; public: - restorable_stack(const T& null) : simple_restorable_stack(_stack, N, null) { } + template = true> + managed_restorable_stack(const T& null) : simple_restorable_stack(nullptr, 0, null) { } + template = true> + managed_restorable_stack(const T& null) : + _stack{}, simple_restorable_stack(_stack.data(), N, null) {} + virtual void overflow(T*& buffer, size_t& size) override final { + if constexpr (dynamic) { + if (buffer) { + _stack.extend(); + } + buffer = _stack.data(); + size = _stack.capacity(); + } else { + base::overflow(buffer, size); + } + } private: - // stack - T _stack[N]; + managed_array _stack; }; + template inline void simple_restorable_stack::push(const T& value) { diff --git a/inkcpp_test/Stack.cpp b/inkcpp_test/Stack.cpp index 248838a4..e0bc8c22 100644 --- a/inkcpp_test/Stack.cpp +++ b/inkcpp_test/Stack.cpp @@ -1,9 +1,11 @@ #include "catch.hpp" -#include "..//inkcpp/simple_restorable_stack.h" +#include "../inkcpp/simple_restorable_stack.h" using ink::runtime::internal::simple_restorable_stack; -using ink::runtime::internal::restorable_stack; + +template +using fixed_restorable_stack = ink::runtime::internal::managed_restorable_stack; template void stack_matches(const simple_restorable_stack& stack, T (&expected)[SIZE]) @@ -21,7 +23,7 @@ void stack_matches(const simple_restorable_stack& stack, T (&expected)[SIZE]) SCENARIO("simple_restorable_stack can be pushed and popped", "[stack]") { GIVEN("An empty stack") { - restorable_stack stack(~0); + fixed_restorable_stack stack(~0); WHEN("items are added") { stack.push(1); @@ -68,7 +70,7 @@ SCENARIO("simple_restorable_stack can be pushed and popped", "[stack]") { SCENARIO("simple_restorable_stack supports save/restore", "[stack]") { GIVEN("a stack with a few items that has been saved") { - restorable_stack stack(~0); + fixed_restorable_stack stack(~0); stack.push(1); stack.push(2); @@ -187,7 +189,7 @@ SCENARIO("simple_restorable_stack supports save/restore", "[stack]") { GIVEN("a stack with one entry that has been saved") { - restorable_stack stack(~0); + fixed_restorable_stack stack(~0); stack.push(0); stack.save(); @@ -219,4 +221,4 @@ SCENARIO("simple_restorable_stack supports save/restore", "[stack]") { } } -} \ No newline at end of file +} diff --git a/shared/public/config.h b/shared/public/config.h index edfb8bb9..83e68765 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -17,11 +17,12 @@ namespace ink::config { static constexpr int limitGlobalVariables = -50; static constexpr int limitThreadDepth = -10; static constexpr int limitEvalStackDepth = -20; - static constexpr int limitContainerDepth = 20; + static constexpr int limitContainerDepth = -20; // temporary variables and callstack; static constexpr int limitRuntimeStack = -20; // max number of elements in one output (a string is one element) + // no dynamic support now! static constexpr int limitOutputSize = 200; // max number of choices per choice - static constexpr int maxChoices = -10; + static constexpr int maxChoices = 10; } diff --git a/shared/public/system.h b/shared/public/system.h index af5fb3dc..aab44b61 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -131,12 +131,12 @@ namespace ink template using if_t = typename if_type::type; - template - struct enable_if { - using type = T; - }; + template + struct enable_if { }; template - struct enable_if {}; + struct enable_if { using type = T; }; + template + using enable_if_t = typename enable_if::type; } #ifdef INK_ENABLE_STL From c08a37a4eca6a4621f5d9c1ce4d8d541e76ef741 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 3 Mar 2021 13:28:59 +0100 Subject: [PATCH 08/11] Remove array of size 0 --- inkcpp/array.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index facdceb7..c7d3c62c 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -49,7 +49,7 @@ namespace ink::runtime::internal void extend(); private: - T _static_data[dynamic ? 0 : initialCapacity]; + if_t _static_data[dynamic ? 1 : initialCapacity]; T* _dynamic_data = nullptr; size_t _capacity; size_t _size; From 5c12a6f90ade0a002daba5c706fe56809794049a Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Tue, 11 May 2021 12:29:54 +0200 Subject: [PATCH 09/11] Catch never used dynamic array (handling nullptr data) --- inkcpp/array.h | 20 ++++++++++++++------ inkcpp/runner_impl.cpp | 10 +++++----- inkcpp/runner_impl.h | 4 +--- shared/public/config.h | 2 ++ 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index c7d3c62c..7a08caab 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -33,6 +33,8 @@ namespace ink::runtime::internal T* begin() { return data(); } const T* end() const { return data() + _size; } T* end() { return data() + _size; } + const T& back() const { return end()[-1]; } + T& back() { return end()[-1]; } const size_t size() const { return _size; } const size_t capacity() const { return _capacity; } @@ -236,7 +238,8 @@ namespace ink::runtime::internal using base = basic_restorable_array; public: allocated_restorable_array(const T& initial, const T& nullValue) - : basic_restorable_array(0, 0, nullValue), _initialValue{initial} + : basic_restorable_array(0, 0, nullValue), _initialValue{initial}, + _buffer{nullptr} {} allocated_restorable_array(size_t capacity, const T& initial, const T &nullValue) : basic_restorable_array(new T[capacity * 2], capacity * 2, nullValue), @@ -249,21 +252,26 @@ namespace ink::runtime::internal void resize(size_t n) { size_t new_capacity = 2 * n; T* new_buffer = new T[new_capacity]; - for(size_t i = 0; i < base::capacity(); ++i) { - new_buffer[i] = _buffer[i]; + if (_buffer) { + for(size_t i = 0; i < base::capacity(); ++i) { + new_buffer[i] = _buffer[i]; + } + delete[] _buffer; } for(size_t i = base::capacity(); i < new_capacity; ++i) { new_buffer[i] = _initialValue; } - delete[] _buffer; + _buffer = new_buffer; this->set_new_buffer(_buffer, new_capacity); } ~allocated_restorable_array() { - delete[] _buffer; - _buffer = nullptr; + if(_buffer) { + delete[] _buffer; + _buffer = nullptr; + } } private: diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index b9e95970..55083022 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -63,7 +63,7 @@ namespace ink::runtime::internal void runner_impl::clear_tags() { - _num_tags = 0; + _tags.clear(); } void runner_impl::jump(ip_t dest, bool record_visits) @@ -409,17 +409,17 @@ namespace ink::runtime::internal bool runner_impl::has_tags() const { - return _num_tags > 0; + return _tags.size() > 0; } size_t runner_impl::num_tags() const { - return _num_tags; + return _tags.size(); } const char* runner_impl::get_tag(size_t index) const { - inkAssert(index < _num_tags, "Tag index exceeds _num_tags"); + inkAssert(index < _tags.size(), "Tag index exceeds _num_tags"); return _tags[index]; } @@ -978,7 +978,7 @@ namespace ink::runtime::internal } break; case Command::TAG: { - _tags[_num_tags++] = read(); + _tags.push() = read(); } break; default: inkAssert(false, "Unrecognized command!"); diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 8bd3e4c3..8dd79d2e 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -219,9 +219,7 @@ namespace ink::runtime::internal managed_array _choices; // Tag list - static const size_t MAX_TAGS = 100; - const char* _tags[MAX_TAGS]; - size_t _num_tags = 0; + managed_array _tags; // TODO: Move to story? Both? functions _functions; diff --git a/shared/public/config.h b/shared/public/config.h index 83e68765..9fa8e95d 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -18,6 +18,8 @@ namespace ink::config { static constexpr int limitThreadDepth = -10; static constexpr int limitEvalStackDepth = -20; static constexpr int limitContainerDepth = -20; + /// number of simultaneous active tags + static constexpr int limitActiveTags = 10; // temporary variables and callstack; static constexpr int limitRuntimeStack = -20; // max number of elements in one output (a string is one element) From 8cd6458f53e18aba2dc29ca7846b257a5d2bebbf Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Tue, 11 May 2021 12:55:21 +0200 Subject: [PATCH 10/11] Fix missing extend of _temp array --- inkcpp/array.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index 7a08caab..e0c198c7 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -116,7 +116,8 @@ namespace ink::runtime::internal inline T* buffer() { return _array; } void set_new_buffer(T* buffer, size_t capacity) { _array = buffer; - _capacity = capacity; + _temp = buffer + capacity/2; + _capacity = capacity/2; } private: @@ -130,7 +131,7 @@ namespace ink::runtime::internal // we store values here when we're in save mode // they're copied on a call to forget() - T* const _temp; + T* _temp; // size of both _array and _temp size_t _capacity; From f9341241d0fbcabc4654b5ce3f72a219d6061da8 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Tue, 11 May 2021 23:31:41 +0200 Subject: [PATCH 11/11] initelize temp buffer correctly --- inkcpp/array.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index e0c198c7..4a576069 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -239,12 +239,13 @@ namespace ink::runtime::internal using base = basic_restorable_array; public: allocated_restorable_array(const T& initial, const T& nullValue) - : basic_restorable_array(0, 0, nullValue), _initialValue{initial}, + : basic_restorable_array(0, 0, nullValue), _initialValue{initial}, _nullValue{nullValue}, _buffer{nullptr} {} allocated_restorable_array(size_t capacity, const T& initial, const T &nullValue) : basic_restorable_array(new T[capacity * 2], capacity * 2, nullValue), - _initialValue{initial} + _initialValue{initial}, + _nullValue{nullValue} { _buffer = this->buffer(); this->clear(_initialValue); @@ -256,11 +257,14 @@ namespace ink::runtime::internal if (_buffer) { for(size_t i = 0; i < base::capacity(); ++i) { new_buffer[i] = _buffer[i]; + // copy temp + new_buffer[i + base::capacity()] = _buffer[i + base::capacity()]; } delete[] _buffer; } for(size_t i = base::capacity(); i < new_capacity; ++i) { new_buffer[i] = _initialValue; + new_buffer[i+base::capacity()] = _nullValue; } _buffer = new_buffer; @@ -277,6 +281,7 @@ namespace ink::runtime::internal private: T _initialValue; + T _nullValue; T* _buffer; }; }