diff --git a/.clang-format b/.clang-format index e6172843..46f0e3aa 100644 --- a/.clang-format +++ b/.clang-format @@ -110,3 +110,4 @@ ReflowComments: true RemoveBracesLLVM: false UseCRLF: false DeriveLineEnding: false +LineEnding: LF diff --git a/CMakeLists.txt b/CMakeLists.txt index 463ea95a..490af3e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,8 @@ mark_as_advanced(PYBIND11_USE_CROSSCOMPILING) enable_testing() # Project setup -project(inkcpp VERSION 0.1.8) -SET(CMAKE_CXX_STANDARD 20) +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) diff --git a/Documentation/cmake_example/CMakeLists.txt b/Documentation/cmake_example/CMakeLists.txt index 59cf0570..1271d230 100644 --- a/Documentation/cmake_example/CMakeLists.txt +++ b/Documentation/cmake_example/CMakeLists.txt @@ -5,7 +5,7 @@ find_package(inkcpp CONFIG REQUIRED) # for CXX builds add_executable(main_cpp main.cpp) -set_property(TARGET main_cpp PROPERTY CXX_STANDARD 20) +set_property(TARGET main_cpp PROPERTY CXX_STANDARD 17) target_link_libraries(main_cpp inkcpp inkcpp_compiler) # for C builds diff --git a/inkcpp/array.h b/inkcpp/array.h index 0494d980..a28a8b83 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -6,6 +6,7 @@ */ #pragma once +#include "config.h" #include "snapshot_interface.h" #include "system.h" #include "traits.h" @@ -28,6 +29,11 @@ class managed_array : public snapshot_interface } } + config::statistics::container statistics() const + { + return {static_cast(_capacity), static_cast(_size)}; + } + virtual ~managed_array() { if constexpr (dynamic) { diff --git a/inkcpp/avl_array.h b/inkcpp/avl_array.h index 6dc98f3a..fe1b3e54 100644 --- a/inkcpp/avl_array.h +++ b/inkcpp/avl_array.h @@ -46,788 +46,814 @@ #include - /** * \param Key The key type. The type (class) must provide a 'less than' and 'equal to' operator * \param T The Data type * \param size_type Container size type * \param Size Container size - * \param Fast If true every node stores an extra parent index. This increases memory but speed up insert/erase by factor 10 + * \param Fast If true every node stores an extra parent index. This increases memory but speed up + * insert/erase by factor 10 */ -template +template< + typename Key, typename T, typename size_type, bool dynamic, size_type Size, + const bool Fast = true> class avl_array { - // child index pointer class - typedef struct tag_child_type { - size_type left; - size_type right; - } child_type; - - // node storage, due to possible structure packing effects, single arrays are used instead of a 'node' structure - Key key_[Size]; // node key - T val_[Size]; // node value - std::int8_t balance_[Size]; // subtree balance - child_type child_[Size]; // node childs - size_type size_; // actual size - size_type root_; // root node - size_type parent_[Fast ? Size : 1]; // node parent, use one element if not needed (zero sized array is not allowed) - - // invalid index (like 'nullptr' in a pointer implementation) - static const size_type INVALID_IDX = Size; - - // iterator class - template - class tag_avl_array_iterator - { - template - using if_t = ink::runtime::internal::if_t; - if_t - instance_; // array instance - size_type idx_; // actual node - - friend avl_array; // avl_array may access index pointer - - public: - - // ctor - tag_avl_array_iterator(if_t instance = nullptr, size_type idx = 0U) - : instance_(instance) - , idx_(idx) - { } - - template> - tag_avl_array_iterator(const tag_avl_array_iterator& itr) - : instance_(itr.instance_) - , idx_(itr.idx_) - {} - - inline tag_avl_array_iterator& operator=(const tag_avl_array_iterator& other) - { - instance_ = other.instance_; - idx_ = other.idx_; - return *this; - } - - inline bool operator==(const tag_avl_array_iterator& rhs) const - { return idx_ == rhs.idx_; } - - inline bool operator!=(const tag_avl_array_iterator& rhs) const - { return !(*this == rhs); } - - // dereference - access value - inline if_t operator*() const - { return val(); } - - // access value - inline if_t val() const - { return instance_->val_[idx_]; } - - // access key - inline const Key& key() const - { return instance_->key_[idx_]; } - - // returns unique number for each entry - // the numbers are unique as long no operation are executed - // on the avl - inline size_t temp_identifier() const { return instance_->size() - idx_ - 1; } - - // preincrement - tag_avl_array_iterator& operator++() - { - // end reached? - if (idx_ >= Size) { - return *this; - } - // take left most child of right child, if not existent, take parent - size_type i = instance_->child_[idx_].right; - if (i != instance_->INVALID_IDX) { - // successor is the furthest left node of right subtree - for (; i != instance_->INVALID_IDX; i = instance_->child_[i].left) { - idx_ = i; - } - } - else { - // have already processed the left subtree, and - // there is no right subtree. move up the tree, - // looking for a parent for which nodePtr is a left child, - // stopping if the parent becomes NULL. a non-NULL parent - // is the successor. if parent is NULL, the original node - // was the last node inorder, and its successor - // is the end of the list - i = instance_->get_parent(idx_); - while ((i != instance_->INVALID_IDX) && (idx_ == instance_->child_[i].right)) { - idx_ = i; - i = instance_->get_parent(idx_); - } - idx_ = i; - } - return *this; - } - - // postincrement - inline tag_avl_array_iterator operator++(int) - { - tag_avl_array_iterator _copy = *this; - ++(*this); - return _copy; - } - }; + // child index pointer class + typedef struct tag_child_type { + size_type left; + size_type right; + } child_type; + + // node storage, due to possible structure packing effects, single arrays are used instead of a + // 'node' structure + ink::runtime::internal::if_t key_; + ink::runtime::internal::if_t val_; + ink::runtime::internal::if_t balance_; + ink::runtime::internal::if_t child_; + size_type size_; // actual size + size_t _capacity; + size_type root_; // root node + ink::runtime::internal::if_t parent_; + + // invalid index (like 'nullptr' in a pointer implementation) + static const size_type INVALID_IDX = ~static_cast(0); + + // iterator class + template + class tag_avl_array_iterator + { + template + using if_t = ink::runtime::internal::if_t; + if_t instance_; // array instance + size_type idx_; // actual node + + friend avl_array; // avl_array may access index pointer + + public: + // ctor + tag_avl_array_iterator( + if_t instance = nullptr, size_type idx = 0U + ) + : instance_(instance) + , idx_(idx) + { + } + + template> + tag_avl_array_iterator(const tag_avl_array_iterator& itr) + : instance_(itr.instance_) + , idx_(itr.idx_) + { + } + + inline tag_avl_array_iterator& operator=(const tag_avl_array_iterator& other) + { + instance_ = other.instance_; + idx_ = other.idx_; + return *this; + } + + inline bool operator==(const tag_avl_array_iterator& rhs) const { return idx_ == rhs.idx_; } + + inline bool operator!=(const tag_avl_array_iterator& rhs) const { return ! (*this == rhs); } + + // dereference - access value + inline if_t operator*() const { return val(); } + + // access value + inline if_t val() const { return instance_->val_[idx_]; } + + // access key + inline const Key& key() const { return instance_->key_[idx_]; } + + // returns unique number for each entry + // the numbers are unique as long no operation are executed + // on the avl + inline size_t temp_identifier() const { return instance_->size() - idx_ - 1; } + + // preincrement + tag_avl_array_iterator& operator++() + { + // end reached? + if (idx_ >= INVALID_IDX) { + return *this; + } + // take left most child of right child, if not existent, take parent + size_type i = instance_->child_[idx_].right; + if (i != instance_->INVALID_IDX) { + // successor is the furthest left node of right subtree + for (; i != instance_->INVALID_IDX; i = instance_->child_[i].left) { + idx_ = i; + } + } else { + // have already processed the left subtree, and + // there is no right subtree. move up the tree, + // looking for a parent for which nodePtr is a left child, + // stopping if the parent becomes NULL. a non-NULL parent + // is the successor. if parent is NULL, the original node + // was the last node inorder, and its successor + // is the end of the list + i = instance_->get_parent(idx_); + while ((i != instance_->INVALID_IDX) && (idx_ == instance_->child_[i].right)) { + idx_ = i; + i = instance_->get_parent(idx_); + } + idx_ = i; + } + return *this; + } + + // postincrement + inline tag_avl_array_iterator operator++(int) + { + tag_avl_array_iterator _copy = *this; + ++(*this); + return _copy; + } + }; public: + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef Key key_type; + typedef tag_avl_array_iterator iterator; + typedef tag_avl_array_iterator const_iterator; + + // ctor + avl_array() + : size_(0U) + , root_(INVALID_IDX) + , _capacity(Size) + { + if constexpr (dynamic) { + key_ = new Key[Size]; + val_ = new T[Size]; + balance_ = new std::int8_t[Size]; + child_ = new child_type[Size]; + if constexpr (Fast) { + parent_ = new size_type[Size]; + } + } + } + + ~avl_array() + { + if constexpr (dynamic) { + delete[] key_; + delete[] val_; + delete[] balance_; + delete[] child_; + } + } + + // iterators + inline iterator begin() + { + size_type i = INVALID_IDX; + if (root_ != INVALID_IDX) { + // find smallest element, it's the farthest node left from root + for (i = root_; child_[i].left != INVALID_IDX; i = child_[i].left) + ; + } + return iterator(this, i); + } + + inline const_iterator begin() const + { + return const_iterator(const_cast(*this).begin()); + } + + inline iterator end() { return iterator(this, INVALID_IDX); } + + inline const_iterator end() const { return const_iterator(this, INVALID_IDX); } + + // capacity + inline size_type size() const { return size_; } + + inline bool empty() const { return size_ == static_cast(0); } + + inline size_type max_size() const + { + if constexpr (dynamic) { + return _capacity; + } else { + return Size; + } + } + + /** + * Clear the container + */ + inline void clear() + { + size_ = 0U; + root_ = INVALID_IDX; + } + + void extend() + { + size_t new_size = _capacity * 1.5; + if (new_size < 5) { + new_size = 5; + } + { + Key* new_data = new Key[new_size]; + for (size_type i = 0; i < _capacity; ++i) { + new_data[i] = key_[i]; + } + delete[] key_; + key_ = new_data; + } + { + T* new_data = new T[new_size]; + for (size_type i = 0; i < _capacity; ++i) { + new_data[i] = val_[i]; + } + delete[] val_; + val_ = new_data; + } + { + std::int8_t* new_data = new std::int8_t[new_size]; + for (size_type i = 0; i < _capacity; ++i) { + new_data[i] = balance_[i]; + } + delete[] balance_; + balance_ = new_data; + } + { + child_type* new_data = new child_type[new_size]; + for (size_type i = 0; i < _capacity; ++i) { + new_data[i] = child_[i]; + } + delete[] child_; + child_ = new_data; + } + if constexpr (Fast) { + size_type* new_data = new size_type[new_size]; + for (size_type i = 0; i < _capacity; ++i) { + new_data[i] = parent_[i]; + } + delete[] parent_; + parent_ = new_data; + } + _capacity = new_size; + } + + /** + * Insert or update an element + * \param key The key to insert. If the key already exists, it is updated + * \param val Value to insert or update + * \return True if the key was successfully inserted or updated, false if container is full + */ + bool insert(const key_type& key, const value_type& val) + { + if (root_ == INVALID_IDX) { + key_[size_] = key; + val_[size_] = val; + balance_[size_] = 0; + child_[size_] = {INVALID_IDX, INVALID_IDX}; + set_parent(size_, INVALID_IDX); + root_ = size_++; + return true; + } + + for (size_type i = root_; i != INVALID_IDX; + i = (key < key_[i]) ? child_[i].left : child_[i].right) { + if (key < key_[i]) { + if (child_[i].left == INVALID_IDX) { + if (size_ >= max_size()) { + // container is full + if constexpr (dynamic) { + extend(); + } else { + return false; + } + } + key_[size_] = key; + val_[size_] = val; + balance_[size_] = 0; + child_[size_] = {INVALID_IDX, INVALID_IDX}; + set_parent(size_, i); + child_[i].left = size_++; + insert_balance(i, 1); + return true; + } + } else if (key_[i] == key) { + // found same key, update node + val_[i] = val; + return true; + } else { + if (child_[i].right == INVALID_IDX) { + if (size_ >= max_size()) { + // container is full + if constexpr (dynamic) { + extend(); + } else { + return false; + } + } + key_[size_] = key; + val_[size_] = val; + balance_[size_] = 0; + child_[size_] = {INVALID_IDX, INVALID_IDX}; + set_parent(size_, i); + child_[i].right = size_++; + insert_balance(i, -1); + return true; + } + } + } + // node doesn't fit (should not happen) - discard it anyway + return false; + } + + /** + * Find an element + * \param key The key to find + * \param val If key is found, the value of the element is set + * \return True if key was found + */ + inline bool find(const key_type& key, value_type& val) const + { + for (size_type i = root_; i != INVALID_IDX;) { + if (key < key_[i]) { + i = child_[i].left; + } else if (key == key_[i]) { + // found key + val = val_[i]; + return true; + } else { + i = child_[i].right; + } + } + // key not found + return false; + } + + /** + * Find an element and return an iterator as result + * \param key The key to find + * \return Iterator if key was found, else end() is returned + */ + inline iterator find(const key_type& key) + { + for (size_type i = root_; i != INVALID_IDX;) { + if (key < key_[i]) { + i = child_[i].left; + } else if (key == key_[i]) { + // found key + return iterator(this, i); + } else { + i = child_[i].right; + } + } + // key not found, return end() iterator + return end(); + } + + inline const_iterator find(const key_type& key) const + { + return const_iterator(const_cast(*this).find(key)); + } + + /** + * Count elements with a specific key + * Searches the container for elements with a key equivalent to key and returns the number of + * matches. Because all elements are unique, the function can only return 1 (if the element is + * found) or zero (otherwise). + * \param key The key to find/count + * \return 0 if key was not found, 1 if key was found + */ + inline size_type count(const key_type& key) { return find(key) != end() ? 1U : 0U; } + + /** + * Remove element by key + * \param key The key of the element to remove + * \return True if the element ws removed, false if key was not found + */ + inline bool erase(const key_type& key) { return erase(find(key)); } + + /** + * Remove element by iterator position + * THIS ERASE OPERATION INVALIDATES ALL ITERATORS! + * \param position The iterator position of the element to remove + * \return True if the element was successfully removed, false if error + */ + bool erase(iterator position) + { + if (empty() || (position == end())) { + return false; + } + + const size_type node = position.idx_; + const size_type left = child_[node].left; + const size_type right = child_[node].right; + + if (left == INVALID_IDX) { + if (right == INVALID_IDX) { + if (node == root_) { + root_ = INVALID_IDX; + } else { + const size_type parent = get_parent(node); + if (child_[parent].left == node) { + child_[parent].left = INVALID_IDX; + delete_balance(parent, -1); + } else { + child_[parent].right = INVALID_IDX; + delete_balance(parent, 1); + } + } + } else { + const size_type parent = get_parent(node); + if (parent != INVALID_IDX) { + child_[parent].left == node ? child_[parent].left = right : child_[parent].right = right; + } else { + root_ = right; + } + + set_parent(right, parent); + + delete_balance(right, 0); + } + } else if (right == INVALID_IDX) { + const size_type parent = get_parent(node); + if (parent != INVALID_IDX) { + child_[parent].left == node ? child_[parent].left = left : child_[parent].right = left; + } else { + root_ = left; + } + + set_parent(left, parent); + + delete_balance(left, 0); + } else { + size_type successor = right; + if (child_[successor].left == INVALID_IDX) { + const size_type parent = get_parent(node); + child_[successor].left = left; + balance_[successor] = balance_[node]; + set_parent(successor, parent); + set_parent(left, successor); + + if (node == root_) { + root_ = successor; + } else { + if (child_[parent].left == node) { + child_[parent].left = successor; + } else { + child_[parent].right = successor; + } + } + delete_balance(successor, 1); + } else { + while (child_[successor].left != INVALID_IDX) { + successor = child_[successor].left; + } + + const size_type parent = get_parent(node); + const size_type successor_parent = get_parent(successor); + const size_type successor_right = child_[successor].right; + + if (child_[successor_parent].left == successor) { + child_[successor_parent].left = successor_right; + } else { + child_[successor_parent].right = successor_right; + } + + set_parent(successor_right, successor_parent); + set_parent(successor, parent); + set_parent(right, successor); + set_parent(left, successor); + child_[successor].left = left; + child_[successor].right = right; + balance_[successor] = balance_[node]; + + if (node == root_) { + root_ = successor; + } else { + if (child_[parent].left == node) { + child_[parent].left = successor; + } else { + child_[parent].right = successor; + } + } + delete_balance(successor_parent, -1); + } + } + size_--; + + // relocate the node at the end to the deleted node, if it's not the deleted one + if (node != size_) { + if (root_ == size_) { + root_ = node; + } else { + const size_type parent = get_parent(size_); + if (parent != INVALID_IDX) { // should never be invalid, but anyway for security + child_[parent].left == size_ ? child_[parent].left = node : child_[parent].right = node; + } + } + + // correct childs parent + set_parent(child_[size_].left, node); + set_parent(child_[size_].right, node); + + // move content + replace(node, size_); + } + + return true; + } + + /** + * Integrity (self) test + * \return True if the tree intergity is correct, false if error (should not happen normally) + */ + bool test() const + { + // check root + if (empty() && (root_ != INVALID_IDX)) { + // invalid root + return false; + } + if (size() && root_ >= size()) { + // root out of bounds + return false; + } + + // check tree + for (size_type i = 0U; i < size(); ++i) { + if ((child_[i].left != INVALID_IDX) + && (! (key_[child_[i].left] < key_[i]) || (key_[child_[i].left] == key_[i]))) { + // wrong key order to the left + return false; + } + if ((child_[i].right != INVALID_IDX) + && ((key_[child_[i].right] < key_[i]) || (key_[child_[i].right] == key_[i]))) { + // wrong key order to the right + return false; + } + const size_type parent = get_parent(i); + if ((i != root_) && (parent == INVALID_IDX)) { + // no parent + return false; + } + if ((i == root_) && (parent != INVALID_IDX)) { + // invalid root parent + return false; + } + } + // check passed + return true; + } + + ///////////////////////////////////////////////////////////////////////////// + // Helper functions - typedef T value_type; - typedef T* pointer; - typedef const T* const_pointer; - typedef T& reference; - typedef const T& const_reference; - typedef Key key_type; - typedef tag_avl_array_iterator iterator; - typedef tag_avl_array_iterator const_iterator; - - - // ctor - avl_array() - : size_(0U) - , root_(Size) - { } - - - // iterators - inline iterator begin() - { - size_type i = INVALID_IDX; - if (root_ != INVALID_IDX) { - // find smallest element, it's the farthest node left from root - for (i = root_; child_[i].left != INVALID_IDX; i = child_[i].left); - } - return iterator(this, i); - } - - inline const_iterator begin() const - { - return const_iterator(const_cast(*this).begin()); - } - - inline iterator end() - { return iterator(this, INVALID_IDX); } - - inline const_iterator end() const - { - return const_iterator(this, INVALID_IDX); - } - - - // capacity - inline size_type size() const - { return size_; } - - inline bool empty() const - { return size_ == static_cast(0); } - - inline size_type max_size() const - { return Size; } - - - /** - * Clear the container - */ - inline void clear() - { - size_ = 0U; - root_ = INVALID_IDX; - } - - - /** - * Insert or update an element - * \param key The key to insert. If the key already exists, it is updated - * \param val Value to insert or update - * \return True if the key was successfully inserted or updated, false if container is full - */ - bool insert(const key_type& key, const value_type& val) - { - if (root_ == INVALID_IDX) { - key_[size_] = key; - val_[size_] = val; - balance_[size_] = 0; - child_[size_] = { INVALID_IDX, INVALID_IDX }; - set_parent(size_, INVALID_IDX); - root_ = size_++; - return true; - } - - for (size_type i = root_; i != INVALID_IDX; i = (key < key_[i]) ? child_[i].left : child_[i].right) { - if (key < key_[i]) { - if (child_[i].left == INVALID_IDX) { - if (size_ >= max_size()) { - // container is full - return false; - } - key_[size_] = key; - val_[size_] = val; - balance_[size_] = 0; - child_[size_] = { INVALID_IDX, INVALID_IDX }; - set_parent(size_, i); - child_[i].left = size_++; - insert_balance(i, 1); - return true; - } - } - else if (key_[i] == key) { - // found same key, update node - val_[i] = val; - return true; - } - else { - if (child_[i].right == INVALID_IDX) { - if (size_ >= max_size()) { - // container is full - return false; - } - key_[size_] = key; - val_[size_] = val; - balance_[size_] = 0; - child_[size_] = { INVALID_IDX, INVALID_IDX }; - set_parent(size_, i); - child_[i].right = size_++; - insert_balance(i, -1); - return true; - } - } - } - // node doesn't fit (should not happen) - discard it anyway - return false; - } - - - /** - * Find an element - * \param key The key to find - * \param val If key is found, the value of the element is set - * \return True if key was found - */ - inline bool find(const key_type& key, value_type& val) const - { - for (size_type i = root_; i != INVALID_IDX;) { - if (key < key_[i]) { - i = child_[i].left; - } - else if (key == key_[i]) { - // found key - val = val_[i]; - return true; - } - else { - i = child_[i].right; - } - } - // key not found - return false; - } - - - /** - * Find an element and return an iterator as result - * \param key The key to find - * \return Iterator if key was found, else end() is returned - */ - inline iterator find(const key_type& key) - { - for (size_type i = root_; i != INVALID_IDX;) { - if (key < key_[i]) { - i = child_[i].left; - } else if (key == key_[i]) { - // found key - return iterator(this, i); - } - else { - i = child_[i].right; - } - } - // key not found, return end() iterator - return end(); - } - - inline const_iterator find(const key_type& key) const - { - return const_iterator(const_cast(*this).find(key)); - } - - - /** - * Count elements with a specific key - * Searches the container for elements with a key equivalent to key and returns the number of matches. - * Because all elements are unique, the function can only return 1 (if the element is found) or zero (otherwise). - * \param key The key to find/count - * \return 0 if key was not found, 1 if key was found - */ - inline size_type count(const key_type& key) - { - return find(key) != end() ? 1U : 0U; - } - - - /** - * Remove element by key - * \param key The key of the element to remove - * \return True if the element ws removed, false if key was not found - */ - inline bool erase(const key_type& key) - { - return erase(find(key)); - } - - - /** - * Remove element by iterator position - * THIS ERASE OPERATION INVALIDATES ALL ITERATORS! - * \param position The iterator position of the element to remove - * \return True if the element was successfully removed, false if error - */ - bool erase(iterator position) - { - if (empty() || (position == end())) { - return false; - } - - const size_type node = position.idx_; - const size_type left = child_[node].left; - const size_type right = child_[node].right; - - if (left == INVALID_IDX) { - if (right == INVALID_IDX) { - if (node == root_) { - root_ = INVALID_IDX; - } - else { - const size_type parent = get_parent(node); - if (child_[parent].left == node) { - child_[parent].left = INVALID_IDX; - delete_balance(parent, -1); - } - else { - child_[parent].right = INVALID_IDX; - delete_balance(parent, 1); - } - } - } - else { - const size_type parent = get_parent(node); - child_[parent].left == node ? child_[parent].left = right : child_[parent].right = right; - - set_parent(right, parent); - - delete_balance(right, 0); - } - } - else if (right == INVALID_IDX) { - const size_type parent = get_parent(node); - child_[parent].left == node ? child_[parent].left = left : child_[parent].right = left; - - set_parent(left, parent); - - delete_balance(left, 0); - } - else { - size_type successor = right; - if (child_[successor].left == INVALID_IDX) { - const size_type parent = get_parent(node); - child_[successor].left = left; - balance_[successor] = balance_[node]; - set_parent(successor, parent); - set_parent(left, successor); - - if (node == root_) { - root_ = successor; - } - else { - if (child_[parent].left == node) { - child_[parent].left = successor; - } - else { - child_[parent].right = successor; - } - } - delete_balance(successor, 1); - } - else { - while (child_[successor].left != INVALID_IDX) { - successor = child_[successor].left; - } - - const size_type parent = get_parent(node); - const size_type successor_parent = get_parent(successor); - const size_type successor_right = child_[successor].right; - - if (child_[successor_parent].left == successor) { - child_[successor_parent].left = successor_right; - } - else { - child_[successor_parent].right = successor_right; - } - - set_parent(successor_right, successor_parent); - set_parent(successor, parent); - set_parent(right, successor); - set_parent(left, successor); - child_[successor].left = left; - child_[successor].right = right; - balance_[successor] = balance_[node]; - - if (node == root_) { - root_ = successor; - } - else { - if (child_[parent].left == node) { - child_[parent].left = successor; - } - else { - child_[parent].right = successor; - } - } - delete_balance(successor_parent, -1); - } - } - size_--; - - // relocate the node at the end to the deleted node, if it's not the deleted one - if (node != size_) { - if (root_ == size_) { - root_ = node; - } - else { - const size_type parent = get_parent(size_); - if (parent != INVALID_IDX) { // should never be invalid, but anyway for security - child_[parent].left == size_ ? child_[parent].left = node : child_[parent].right = node; - } - } - - // correct childs parent - set_parent(child_[size_].left, node); - set_parent(child_[size_].right, node); - - // move content - replace(node, size_); - } - - return true; - } - - - /** - * Integrity (self) test - * \return True if the tree intergity is correct, false if error (should not happen normally) - */ - bool test() const - { - // check root - if (empty() && (root_ != INVALID_IDX)) { - // invalid root - return false; - } - if (size() && root_ >= size()) { - // root out of bounds - return false; - } - - // check tree - for (size_type i = 0U; i < size(); ++i) - { - if ((child_[i].left != INVALID_IDX) && (!(key_[child_[i].left] < key_[i]) || (key_[child_[i].left] == key_[i]))) { - // wrong key order to the left - return false; - } - if ((child_[i].right != INVALID_IDX) && ((key_[child_[i].right] < key_[i]) || (key_[child_[i].right] == key_[i]))) { - // wrong key order to the right - return false; - } - const size_type parent = get_parent(i); - if ((i != root_) && (parent == INVALID_IDX)) { - // no parent - return false; - } - if ((i == root_) && (parent != INVALID_IDX)) { - // invalid root parent - return false; - } - } - // check passed - return true; - } - - - ///////////////////////////////////////////////////////////////////////////// - // Helper functions private: - - // find parent element - inline size_type get_parent(size_type node) const - { - if (Fast) { - return parent_[node]; - } - else { - const Key key_node = key_[node]; - for (size_type i = root_; i != INVALID_IDX; i = (key_node < key_[i]) ? child_[i].left : child_[i].right) { - if ((child_[i].left == node) || (child_[i].right == node)) { - // found parent - return i; - } - } - // parent not found - return INVALID_IDX; - } - } - - - // set parent element (only in Fast version) - inline void set_parent(size_type node, size_type parent) - { - if (Fast) { - if (node != INVALID_IDX) { - parent_[node] = parent; - } - } - } - - - inline void replace(size_type target, size_type source) - { - key_[target] = key_[source]; - val_[target] = val_[source]; - balance_[target] = balance_[source]; - child_[target] = child_[source]; - set_parent(target, get_parent(source)); - } - - - void insert_balance(size_type node, std::int8_t balance) - { - while (node != INVALID_IDX) { - balance = (balance_[node] += balance); - - if (balance == 0) { - return; - } - else if (balance == 2) { - if (balance_[child_[node].left] == 1) { - rotate_right(node); - } - else { - rotate_left_right(node); - } - return; - } - else if (balance == -2) { - if (balance_[child_[node].right] == -1) { - rotate_left(node); - } - else { - rotate_right_left(node); - } - return; - } - - const size_type parent = get_parent(node); - if (parent != INVALID_IDX) { - balance = child_[parent].left == node ? 1 : -1; - } - node = parent; - } - } - - - void delete_balance(size_type node, std::int8_t balance) - { - while (node != INVALID_IDX) { - balance = (balance_[node] += balance); - - if (balance == -2) { - if (balance_[child_[node].right] <= 0) { - node = rotate_left(node); - if (balance_[node] == 1) { - return; - } - } - else { - node = rotate_right_left(node); - } - } - else if (balance == 2) { - if (balance_[child_[node].left] >= 0) { - node = rotate_right(node); - if (balance_[node] == -1) { - return; - } - } - else { - node = rotate_left_right(node); - } - } - else if (balance != 0) { - return; - } - - if (node != INVALID_IDX) { - const size_type parent = get_parent(node); - if (parent != INVALID_IDX) { - balance = child_[parent].left == node ? -1 : 1; - } - node = parent; - } - } - } - - - size_type rotate_left(size_type node) - { - const size_type right = child_[node].right; - const size_type right_left = child_[right].left; - const size_type parent = get_parent(node); - - set_parent(right, parent); - set_parent(node, right); - set_parent(right_left, node); - child_[right].left = node; - child_[node].right = right_left; - - if (node == root_) { - root_ = right; - } - else if (child_[parent].right == node) { - child_[parent].right = right; - } - else { - child_[parent].left = right; - } - - balance_[right]++; - balance_[node] = -balance_[right]; - - return right; - } - - - size_type rotate_right(size_type node) - { - const size_type left = child_[node].left; - const size_type left_right = child_[left].right; - const size_type parent = get_parent(node); - - set_parent(left, parent); - set_parent(node, left); - set_parent(left_right, node); - child_[left].right = node; - child_[node].left = left_right; - - if (node == root_) { - root_ = left; - } - else if (child_[parent].left == node) { - child_[parent].left = left; - } - else { - child_[parent].right = left; - } - - balance_[left]--; - balance_[node] = -balance_[left]; - - return left; - } - - - size_type rotate_left_right(size_type node) - { - const size_type left = child_[node].left; - const size_type left_right = child_[left].right; - const size_type left_right_right = child_[left_right].right; - const size_type left_right_left = child_[left_right].left; - const size_type parent = get_parent(node); - - set_parent(left_right, parent); - set_parent(left, left_right); - set_parent(node, left_right); - set_parent(left_right_right, node); - set_parent(left_right_left, left); - child_[node].left = left_right_right; - child_[left].right = left_right_left; - child_[left_right].left = left; - child_[left_right].right = node; - - if (node == root_) { - root_ = left_right; - } - else if (child_[parent].left == node) { - child_[parent].left = left_right; - } - else { - child_[parent].right = left_right; - } - - if (balance_[left_right] == 0) { - balance_[node] = 0; - balance_[left] = 0; - } - else if (balance_[left_right] == -1) { - balance_[node] = 0; - balance_[left] = 1; - } - else { - balance_[node] = -1; - balance_[left] = 0; - } - balance_[left_right] = 0; - - return left_right; - } - - - size_type rotate_right_left(size_type node) - { - const size_type right = child_[node].right; - const size_type right_left = child_[right].left; - const size_type right_left_left = child_[right_left].left; - const size_type right_left_right = child_[right_left].right; - const size_type parent = get_parent(node); - - set_parent(right_left, parent); - set_parent(right, right_left); - set_parent(node, right_left); - set_parent(right_left_left, node); - set_parent(right_left_right, right); - child_[node].right = right_left_left; - child_[right].left = right_left_right; - child_[right_left].right = right; - child_[right_left].left = node; - - if (node == root_) { - root_ = right_left; - } - else if (child_[parent].right == node) { - child_[parent].right = right_left; - } - else { - child_[parent].left = right_left; - } - - if (balance_[right_left] == 0) { - balance_[node] = 0; - balance_[right] = 0; - } - else if (balance_[right_left] == 1) { - balance_[node] = 0; - balance_[right] = -1; - } - else { - balance_[node] = 1; - balance_[right] = 0; - } - balance_[right_left] = 0; - - return right_left; - } + // find parent element + inline size_type get_parent(size_type node) const + { + if (Fast) { + return parent_[node]; + } else { + const Key key_node = key_[node]; + for (size_type i = root_; i != INVALID_IDX; + i = (key_node < key_[i]) ? child_[i].left : child_[i].right) { + if ((child_[i].left == node) || (child_[i].right == node)) { + // found parent + return i; + } + } + // parent not found + return INVALID_IDX; + } + } + + // set parent element (only in Fast version) + inline void set_parent(size_type node, size_type parent) + { + if (Fast) { + if (node != INVALID_IDX) { + parent_[node] = parent; + } + } + } + + inline void replace(size_type target, size_type source) + { + key_[target] = key_[source]; + val_[target] = val_[source]; + balance_[target] = balance_[source]; + child_[target] = child_[source]; + set_parent(target, get_parent(source)); + } + + void insert_balance(size_type node, std::int8_t balance) + { + while (node != INVALID_IDX) { + balance = (balance_[node] += balance); + + if (balance == 0) { + return; + } else if (balance == 2) { + if (balance_[child_[node].left] == 1) { + rotate_right(node); + } else { + rotate_left_right(node); + } + return; + } else if (balance == -2) { + if (balance_[child_[node].right] == -1) { + rotate_left(node); + } else { + rotate_right_left(node); + } + return; + } + + const size_type parent = get_parent(node); + if (parent != INVALID_IDX) { + balance = child_[parent].left == node ? 1 : -1; + } + node = parent; + } + } + + void delete_balance(size_type node, std::int8_t balance) + { + while (node != INVALID_IDX) { + balance = (balance_[node] += balance); + + if (balance == -2) { + if (balance_[child_[node].right] <= 0) { + node = rotate_left(node); + if (balance_[node] == 1) { + return; + } + } else { + node = rotate_right_left(node); + } + } else if (balance == 2) { + if (balance_[child_[node].left] >= 0) { + node = rotate_right(node); + if (balance_[node] == -1) { + return; + } + } else { + node = rotate_left_right(node); + } + } else if (balance != 0) { + return; + } + + if (node != INVALID_IDX) { + const size_type parent = get_parent(node); + if (parent != INVALID_IDX) { + balance = child_[parent].left == node ? -1 : 1; + } + node = parent; + } + } + } + + size_type rotate_left(size_type node) + { + const size_type right = child_[node].right; + const size_type right_left = child_[right].left; + const size_type parent = get_parent(node); + + set_parent(right, parent); + set_parent(node, right); + set_parent(right_left, node); + child_[right].left = node; + child_[node].right = right_left; + + if (node == root_) { + root_ = right; + } else if (child_[parent].right == node) { + child_[parent].right = right; + } else { + child_[parent].left = right; + } + + balance_[right]++; + balance_[node] = -balance_[right]; + + return right; + } + + size_type rotate_right(size_type node) + { + const size_type left = child_[node].left; + const size_type left_right = child_[left].right; + const size_type parent = get_parent(node); + + set_parent(left, parent); + set_parent(node, left); + set_parent(left_right, node); + child_[left].right = node; + child_[node].left = left_right; + + if (node == root_) { + root_ = left; + } else if (child_[parent].left == node) { + child_[parent].left = left; + } else { + child_[parent].right = left; + } + + balance_[left]--; + balance_[node] = -balance_[left]; + + return left; + } + + size_type rotate_left_right(size_type node) + { + const size_type left = child_[node].left; + const size_type left_right = child_[left].right; + const size_type left_right_right = child_[left_right].right; + const size_type left_right_left = child_[left_right].left; + const size_type parent = get_parent(node); + + set_parent(left_right, parent); + set_parent(left, left_right); + set_parent(node, left_right); + set_parent(left_right_right, node); + set_parent(left_right_left, left); + child_[node].left = left_right_right; + child_[left].right = left_right_left; + child_[left_right].left = left; + child_[left_right].right = node; + + if (node == root_) { + root_ = left_right; + } else if (child_[parent].left == node) { + child_[parent].left = left_right; + } else { + child_[parent].right = left_right; + } + + if (balance_[left_right] == 0) { + balance_[node] = 0; + balance_[left] = 0; + } else if (balance_[left_right] == -1) { + balance_[node] = 0; + balance_[left] = 1; + } else { + balance_[node] = -1; + balance_[left] = 0; + } + balance_[left_right] = 0; + + return left_right; + } + + size_type rotate_right_left(size_type node) + { + const size_type right = child_[node].right; + const size_type right_left = child_[right].left; + const size_type right_left_left = child_[right_left].left; + const size_type right_left_right = child_[right_left].right; + const size_type parent = get_parent(node); + + set_parent(right_left, parent); + set_parent(right, right_left); + set_parent(node, right_left); + set_parent(right_left_left, node); + set_parent(right_left_right, right); + child_[node].right = right_left_left; + child_[right].left = right_left_right; + child_[right_left].right = right; + child_[right_left].left = node; + + if (node == root_) { + root_ = right_left; + } else if (child_[parent].right == node) { + child_[parent].right = right_left; + } else { + child_[parent].left = right_left; + } + + if (balance_[right_left] == 0) { + balance_[node] = 0; + balance_[right] = 0; + } else if (balance_[right_left] == 1) { + balance_[node] = 0; + balance_[right] = -1; + } else { + balance_[node] = 1; + balance_[right] = 0; + } + balance_[right_left] = 0; + + return right_left; + } }; -#endif // _AVL_ARRAY_H_ +#endif // _AVL_ARRAY_H_ diff --git a/inkcpp/collections/restorable.h b/inkcpp/collections/restorable.h index 622a59a9..4d7f38d1 100644 --- a/inkcpp/collections/restorable.h +++ b/inkcpp/collections/restorable.h @@ -13,394 +13,408 @@ namespace ink::runtime::internal { - struct entry; - template - constexpr auto EmptyNullPredicate = [](const ElementType&) { return false; }; +struct entry; +template +constexpr auto EmptyNullPredicate = [](const ElementType&) { + return false; +}; + +// Iterator type used with restorable +template +class restorable_iter +{ +public: + // Create an iterator moving from start (inclusive) to end (exclusive) + restorable_iter(ElementType* start, ElementType* end) + : _current(start) + , _end(end) + { + } - // Iterator type used with restorable - template - class restorable_iter + // Move to the next non-null element + template)> + bool next(IsNullPredicate isNull = EmptyNullPredicate) { - public: - // Create an iterator moving from start (inclusive) to end (exclusive) - restorable_iter(ElementType* start, ElementType* end) - : _current(start), _end(end) { } - - // Move to the next non-null element - template)> - bool next(IsNullPredicate isNull = EmptyNullPredicate) - { - if (_current != _end) - { - // Determine direction of iteration - int dir = _end - _current > 0 ? 1 : -1; - - // Move pointer - _current += dir; + if (_current != _end) { + // Determine direction of iteration + int dir = _end - _current > 0 ? 1 : -1; + + // Move pointer + _current += dir; - // Make sure to skip over null items - while (isNull(*_current) && _current != _end) { - _current += dir; - } + // Make sure to skip over null items + while (isNull(*_current) && _current != _end) { + _current += dir; } + } - // If we've hit the end, return false - if (_current == _end) - return false; + // If we've hit the end, return false + if (_current == _end) + return false; - // Otherwise, iteration is valid - return true; - } + // Otherwise, iteration is valid + return true; + } - // Get current element - inline ElementType* get() { return _current; } - - // Get current element (const) - inline const ElementType* get() const { return _current; } - - // Is iteration complete (opposite of is valid) - inline bool done() const { return _current == _end; } - - private: - // Current point of iteration - ElementType* _current; - - // End point (non-valid) - ElementType* _end; - }; - - /** - * A special base class for collections which have save/restore/forget functionality - * - * In order to properly handle Ink's glue system, we need to be able to execute beyond - * the end of the line and "peek" to see if a glue command is executed. If one is, we keep - * executing (as the next line will be glued to the current). If we don't, or find more content - * before finding glue, then in actuality, we never should have executed beyond the newline. We - * need to *restore* back to our state before we moved past the end of line. Collections inheriting - * from this class gain this functionality. - */ - template - class restorable : public snapshot_interface - { - public: - restorable(ElementType* buffer, size_t size) - : _buffer(buffer), _size(size), _pos(0), _jump(~0), _save(~0) - { } - - // Checks if we have a save state - bool is_saved() const { return _save != ~0; } - - // Creates a save point which can later be restored to or forgotten - void save() - { - inkAssert(_save == ~0, "Collection is already saved. You should never call save twice. Ignoring."); - if (_save != ~0) { - return; - } + // Get current element + inline ElementType* get() { return _current; } - // Set the save and jump points to the current position. - _save = _jump = _pos; - } + // Get current element (const) + inline const ElementType* get() const { return _current; } - // Restore to the last save point - void restore() - { - inkAssert(_save != ~0, "Collection can't be restored because it's not saved. Ignoring."); - if (_save == ~0) { - return; - } + // Is iteration complete (opposite of is valid) + inline bool done() const { return _current == _end; } - // Move our position back to the saved position - _pos = _save; +private: + // Current point of iteration + ElementType* _current; - // Clear save point - _save = _jump = ~0; - } + // End point (non-valid) + ElementType* _end; +}; - // Forget the save point and continue with the current data - template - void forget(NullifyMethod nullify) - { - inkAssert(_save != ~0, "Can't forget save point because there is none. Ignoring."); - if (_save == ~0) { - return; - } +/** + * A special base class for collections which have save/restore/forget functionality + * + * In order to properly handle Ink's glue system, we need to be able to execute beyond + * the end of the line and "peek" to see if a glue command is executed. If one is, we keep + * executing (as the next line will be glued to the current). If we don't, or find more content + * before finding glue, then in actuality, we never should have executed beyond the newline. We + * need to *restore* back to our state before we moved past the end of line. Collections inheriting + * from this class gain this functionality. + */ +template +class restorable : public snapshot_interface +{ +public: + restorable(ElementType* buffer, size_t size) + : _buffer(buffer) + , _size(size) + , _pos(0) + , _jump(~0) + , _save(~0) + { + } - // If we're behind the save point but past the jump point - if (_save != _jump && _pos > _jump) - { - // Nullify everything between the jump point and the save point - for (size_t i = _jump; i < _save; ++i) - nullify(_buffer[i]); - } + config::statistics::container statistics() const + { + return {static_cast(_size), static_cast(_pos)}; + } + + // Checks if we have a save state + bool is_saved() const { return _save != ~0; } - // Reset save position - _save = _jump = ~0; + // Creates a save point which can later be restored to or forgotten + void save() + { + inkAssert( + _save == ~0, "Collection is already saved. You should never call save twice. Ignoring." + ); + if (_save != ~0) { + return; } - using iterator = restorable_iter; - using const_iterator = restorable_iter; - - // Iterator that begins at the end of the stack - iterator begin() { return iterator(&_buffer[_pos - 1], _buffer - 1); } - const_iterator begin() const { return iterator(&_buffer[_pos - 1], _buffer - 1); } - - // Iterator that points to the element past the beginning of the stack - iterator end() { return iterator(_buffer - 1, _buffer - 1); } - iterator end() const { return const_iterator(_buffer - 1, _buffer - 1); } - - // Push element onto the top of collection - ElementType& push(const ElementType& elem) - { - // Don't destroy saved data. Jump over it - if (_pos < _save && _save != ~0) - { - _jump = _pos; - _pos = _save; - } + // Set the save and jump points to the current position. + _save = _jump = _pos; + } - // Overflow check - if (_pos >= _size) - overflow(_buffer, _size); + // Restore to the last save point + void restore() + { + inkAssert(_save != ~0, "Collection can't be restored because it's not saved. Ignoring."); + if (_save == ~0) { + return; + } - // Push onto the top - _buffer[_pos++] = elem; + // Move our position back to the saved position + _pos = _save; - // Return reference - return _buffer[_pos - 1]; + // Clear save point + _save = _jump = ~0; + } + + // Forget the save point and continue with the current data + template + void forget(NullifyMethod nullify) + { + inkAssert(_save != ~0, "Can't forget save point because there is none. Ignoring."); + if (_save == ~0) { + return; } - // Pop an element off the top of the collection - template - const ElementType& pop(IsNullPredicate isNull) - { - // Make sure we have something to pop - inkAssert(_pos > 0, "Can not pop. No elements to pop!"); + // If we're behind the save point but past the jump point + if (_save != _jump && _pos > _jump) { + // Nullify everything between the jump point and the save point + for (size_t i = _jump; i < _save; ++i) + nullify(_buffer[i]); + } - // Jump over save data - if (_pos == _save) - _pos = _jump; + // Reset save position + _save = _jump = ~0; + } - // Move over empty data - while (isNull(_buffer[_pos - 1])) - _pos--; + using iterator = restorable_iter; + using const_iterator = restorable_iter; - // Decrement and return - _pos--; - return _buffer[_pos]; - } + // Iterator that begins at the end of the stack + iterator begin() { return iterator(&_buffer[_pos - 1], _buffer - 1); } - template - const ElementType& top(IsNullPredicate isNull) const - { - inkAssert(_pos > 0, "Can not top. No elememnts to show!"); - auto pos = _pos; - if (_pos == _save) - pos = _jump; - while(isNull(_buffer[pos-1])) - --pos; - return _buffer[pos-1]; - } + const_iterator begin() const { return iterator(&_buffer[_pos - 1], _buffer - 1); } + + // Iterator that points to the element past the beginning of the stack + iterator end() { return iterator(_buffer - 1, _buffer - 1); } - bool is_empty() const { return _pos == 0; } + iterator end() const { return const_iterator(_buffer - 1, _buffer - 1); } - void clear() - { - _pos = 0; - _save = _jump = ~0; + // Push element onto the top of collection + ElementType& push(const ElementType& elem) + { + // Don't destroy saved data. Jump over it + if (_pos < _save && _save != ~0) { + _jump = _pos; + _pos = _save; } - // Forward iterate - template - void for_each(CallbackMethod callback, IsNullPredicate isNull) const - { - if (_pos == 0) { - return; - } + // Overflow check + if (_pos >= _size) + overflow(_buffer, _size); - // Start at the beginning - size_t i = 0; - do - { - // Jump over saved data - if (i == _jump) - i = _save; - - // Run callback - if(!isNull(_buffer[i])) - callback(_buffer[i]); - - // Move forward one element - i++; - } while (i < _pos); - } + // Push onto the top + _buffer[_pos++] = elem; - template - const ElementType* find(Predicate predicate) const - { - if (_pos == 0) { - return nullptr; - } + // Return reference + return _buffer[_pos - 1]; + } - // Start at the beginning - size_t i = 0; - do - { - // Jump over saved data - if (i == _jump) - i = _save; + // Pop an element off the top of the collection + template + const ElementType& pop(IsNullPredicate isNull) + { + // Make sure we have something to pop + inkAssert(_pos > 0, "Can not pop. No elements to pop!"); + + // Jump over save data + if (_pos == _save) + _pos = _jump; - // Run callback - if (!isNull(_buffer[i]) && predicate(_buffer[i])) - return &_buffer[i]; + // Move over empty data + while (isNull(_buffer[_pos - 1])) + _pos--; - // Move forward one element - i++; - } while (i < _pos); + // Decrement and return + _pos--; + return _buffer[_pos]; + } - return nullptr; + template + const ElementType& top(IsNullPredicate isNull) const + { + inkAssert(_pos > 0, "Can not top. No elememnts to show!"); + auto pos = _pos; + if (_pos == _save) + pos = _jump; + while (isNull(_buffer[pos - 1])) + --pos; + return _buffer[pos - 1]; + } + + bool is_empty() const { return _pos == 0; } + + void clear() + { + _pos = 0; + _save = _jump = ~0; + } + + // Forward iterate + template + void for_each(CallbackMethod callback, IsNullPredicate isNull) const + { + if (_pos == 0) { + return; } - template - void for_each_all(CallbackMethod callback) const - { - // no matter if we're saved or not, we iterate everything - int len = (_save == ~0 || _pos > _save) ? _pos : _save; + // Start at the beginning + size_t i = 0; + do { + // Jump over saved data + if (i == _jump) + i = _save; - // Iterate - for (int i = 0; i < len; i++) + // Run callback + if (! isNull(_buffer[i])) callback(_buffer[i]); + + // Move forward one element + i++; + } while (i < _pos); + } + + template + const ElementType* find(Predicate predicate) const + { + if (_pos == 0) { + return nullptr; } - // Reverse iterate - template - void reverse_for_each(CallbackMethod callback, IsNullPredicate isNull) const - { - if (_pos == 0) { - return; - } + // Start at the beginning + size_t i = 0; + do { + // Jump over saved data + if (i == _jump) + i = _save; - // Start at the end - size_t i = _pos; - do - { - // Move back one element - i--; + // Run callback + if (! isNull(_buffer[i]) && predicate(_buffer[i])) + return &_buffer[i]; - // Run callback - if (!isNull(_buffer[i])) - callback(_buffer[i]); + // Move forward one element + i++; + } while (i < _pos); - // Jump over saved data - if (i == _save) - i = _jump; + return nullptr; + } - } while (i > 0); - } + template + void for_each_all(CallbackMethod callback) const + { + // no matter if we're saved or not, we iterate everything + int len = (_save == ~0 || _pos > _save) ? _pos : _save; - // Reverse find - template - ElementType* reverse_find(Predicate predicate) { - return reverse_find_impl(predicate); - } + // Iterate + for (int i = 0; i < len; i++) + callback(_buffer[i]); + } - template - const ElementType* reverse_find(Predicate predicate) const { - return reverse_find_impl(predicate); + // Reverse iterate + template + void reverse_for_each(CallbackMethod callback, IsNullPredicate isNull) const + { + if (_pos == 0) { + return; } - template - size_t size(IsNullPredicate isNull) const - { - if (_pos == 0) { - return 0; - } + // Start at the end + size_t i = _pos; + do { + // Move back one element + i--; - size_t count = 0; + // Run callback + if (! isNull(_buffer[i])) + callback(_buffer[i]); - // Start at the end - size_t i = _pos; - do - { - // Move back one element - i--; + // Jump over saved data + if (i == _save) + i = _jump; - // Run callback - if(!isNull(_buffer[i])) - count++; + } while (i > 0); + } - // Jump over saved data - if (i == _save) - i = _jump; + // Reverse find + template + ElementType* reverse_find(Predicate predicate) + { + return reverse_find_impl(predicate); + } - } while (i > 0); + template + const ElementType* reverse_find(Predicate predicate) const + { + return reverse_find_impl(predicate); + } - return count; + template + size_t size(IsNullPredicate isNull) const + { + if (_pos == 0) { + return 0; } - // snapshot interface - virtual size_t snap(unsigned char* data, const snapper&) const; - const unsigned char* snap_load(const unsigned char* data, const loader&); + size_t count = 0; - protected: - // Called when we run out of space in buffer. - virtual void overflow(ElementType*& buffer, size_t& size) { - inkFail("Restorable run out of memory!"); - } + // Start at the end + size_t i = _pos; + do { + // Move back one element + i--; - private: + // Run callback + if (! isNull(_buffer[i])) + count++; - template - ElementType* reverse_find_impl(Predicate predicate) const - { - if (_pos == 0) { - return nullptr; - } + // Jump over saved data + if (i == _save) + i = _jump; - // Start at the end - size_t i = _pos; - do - { - // Move back one element - i--; + } while (i > 0); - // Run callback - if (predicate(_buffer[i])) - return &_buffer[i]; + return count; + } - // Jump over saved data - if (i == _save) - i = _jump; + // snapshot interface + virtual size_t snap(unsigned char* data, const snapper&) const; + const unsigned char* snap_load(const unsigned char* data, const loader&); - } while (i > 0); +protected: + // Called when we run out of space in buffer. + virtual void overflow(ElementType*& buffer, size_t& size) + { + inkFail("Restorable run out of memory!"); + } +private: + template + ElementType* reverse_find_impl(Predicate predicate) const + { + if (_pos == 0) { return nullptr; } - // Data buffer. Collection is stored here - ElementType* _buffer; - - // Size of the _buffer array - size_t _size; - - // Set to the next empty position in the buffer. - size_t _pos; - - // Jump and save points. Used when we've been saved. - size_t _jump; - size_t _save; - }; - template<> - size_t restorable::snap(unsigned char* data, const snapper& snapper) const; - template<> - size_t restorable::snap(unsigned char* data, const snapper& snapper) const; - template<> - size_t restorable::snap(unsigned char* data, const snapper&) const; - - template<> - const unsigned char* restorable::snap_load(const unsigned char* data, const loader&); - template<> - const unsigned char* restorable::snap_load(const unsigned char* data, const loader&); - template<> - const unsigned char* restorable::snap_load(const unsigned char* data, const loader&); -} + // Start at the end + size_t i = _pos; + do { + // Move back one element + i--; + + // Run callback + if (predicate(_buffer[i])) + return &_buffer[i]; + + // Jump over saved data + if (i == _save) + i = _jump; + + } while (i > 0); + + return nullptr; + } + + // Data buffer. Collection is stored here + ElementType* _buffer; + + // Size of the _buffer array + size_t _size; + + // Set to the next empty position in the buffer. + size_t _pos; + + // Jump and save points. Used when we've been saved. + size_t _jump; + size_t _save; +}; + +template<> +size_t restorable::snap(unsigned char* data, const snapper& snapper) const; +template<> +size_t restorable::snap(unsigned char* data, const snapper& snapper) const; +template<> +size_t restorable::snap(unsigned char* data, const snapper&) const; + +template<> +const unsigned char* restorable::snap_load(const unsigned char* data, const loader&); +template<> +const unsigned char* restorable::snap_load(const unsigned char* data, const loader&); +template<> +const unsigned char* restorable::snap_load(const unsigned char* data, const loader&); +} // namespace ink::runtime::internal diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 1714d685..6daed149 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -188,7 +188,7 @@ bool globals_impl::set_var(hash_t name, const ink::runtime::value& val) void globals_impl::internal_observe(hash_t name, callback_base* callback) { - _callbacks.push() = Callback{.name = name, .operation = callback}; + _callbacks.push() = Callback{name, callback}; if (_globals_initialized) { value* p_var = _variables.get(name); inkAssert( @@ -286,4 +286,12 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa ptr = _variables.snap_load(ptr, loader); return ptr; } + +config::statistics::global globals_impl::statistics() const +{ + return { + _variables.statistics(), _callbacks.statistics(), _lists.statistics(), _strings.statistics() + }; +} + } // namespace ink::runtime::internal diff --git a/inkcpp/globals_impl.h b/inkcpp/globals_impl.h index a67b580d..d9f8a303 100644 --- a/inkcpp/globals_impl.h +++ b/inkcpp/globals_impl.h @@ -95,6 +95,8 @@ class globals_impl final void restore(); void forget(); + config::statistics::global statistics() const override; + private: // Store the number of containers. This is the length of most of our lists const uint32_t _num_containers; diff --git a/inkcpp/include/globals.h b/inkcpp/include/globals.h index 3d095553..4163ec24 100644 --- a/inkcpp/include/globals.h +++ b/inkcpp/include/globals.h @@ -6,6 +6,7 @@ */ #pragma once +#include "config.h" #include "types.h" #include "functional.h" @@ -65,6 +66,9 @@ class globals_interface internal_observe(hash_string(name), new internal::callback(callback)); } + /** Get usage statistics for global. */ + virtual config::statistics::global statistics() const = 0; + /** create a snapshot of the current runtime state. * (inclusive all runners assoziated with this globals) */ diff --git a/inkcpp/include/runner.h b/inkcpp/include/runner.h index 03582b0e..68400d49 100644 --- a/inkcpp/include/runner.h +++ b/inkcpp/include/runner.h @@ -260,6 +260,8 @@ class runner_interface */ virtual hash_t get_current_knot() const = 0; + /** Get usage statistics for the runner. */ + virtual config::statistics::runner statistics() const = 0; protected: /** internal bind implementation. not for calling. diff --git a/inkcpp/list_impl.cpp b/inkcpp/list_impl.cpp index 26e71b2f..a9d2bccd 100644 --- a/inkcpp/list_impl.cpp +++ b/inkcpp/list_impl.cpp @@ -40,7 +40,7 @@ void list_impl::next(const char*& flag_name, const char*& list_name, int& i, boo return; } - list_flag flag{.list_id = static_cast(i >> 16), .flag = static_cast(i & 0xFF)}; + list_flag flag{static_cast(i >> 16), static_cast(i & 0xFF)}; if (flag_name != nullptr) { ++flag.flag; } diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index fd267602..831a6fd4 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -5,6 +5,7 @@ * https://github.com/JBenda/inkcpp for full license details. */ #include "list_table.h" +#include "config.h" #include "system.h" #include "traits.h" #include "header.h" @@ -51,7 +52,7 @@ list_table::list_table(const char* data, const ink::internal::header& header) } ++ptr; // skip string } - _flag_names.push() = ptr; + _flag_names.push() = ptr; _flag_values.push() = flag.flag; ++_list_end.back(); while (*ptr) { @@ -152,13 +153,13 @@ char* list_table::toString(char* out, const list& l) const { char* itr = out; - const data_t* entry = getPtr(l.lid); - int last_value = 0; - int last_list = -1; - bool first = true; - int min_value = 0; - int min_id = -1; - int min_list = -1; + const data_t* entry = getPtr(l.lid); + int last_value = 0; + int last_list = -1; + bool first = true; + int min_value = 0; + int min_id = -1; + int min_list = -1; while (1) { bool change = false; @@ -463,6 +464,7 @@ int list_table::count(list_flag lf) const } return 1; } + int list_table::count(list l) const { int count = 0; @@ -661,7 +663,8 @@ list_flag list_table::lrnd(list lh, prng& rng) const if (count++ == n) { return list_flag{ static_cast(i), - static_cast(j - listBegin(i))}; + static_cast(j - listBegin(i)) + }; } } } @@ -700,8 +703,8 @@ optional list_table::toFlag(const char* flag_name) const for (int i = list_begin; i != _list_end[list.list_id]; ++i) { if (str_equal(flag_name, _flag_names[i])) { return { - list_flag{.list_id = list.list_id, .flag = static_cast(i - list_begin)} - }; + list_flag{list.list_id, static_cast(i - list_begin)} + }; } } } else { @@ -718,9 +721,8 @@ optional list_table::toFlag(const char* flag_name) const begin = *list_itr; } return { - list_flag{ - .list_id = static_cast(lid), .flag = static_cast(fid - begin)} - }; + list_flag{static_cast(lid), static_cast(fid - begin)} + }; } } } @@ -778,13 +780,13 @@ list_interface* list_table::handout_list(list l) /// @sa list_table::toString(char*,const list&) std::ostream& list_table::write(std::ostream& os, list l) const { - const data_t* entry = getPtr(l.lid); - int last_value = 0; - int last_list = -1; - bool first = true; - int min_value = 0; - int min_id = -1; - int min_list = -1; + const data_t* entry = getPtr(l.lid); + int last_value = 0; + int last_list = -1; + bool first = true; + int min_value = 0; + int min_id = -1; + int min_list = -1; while (1) { bool change = false; @@ -838,4 +840,14 @@ const unsigned char* list_table::snap_load(const unsigned char* ptr, const loade return ptr; } +config::statistics::list_table list_table::statistics() const +{ + return { + _list_handouts.statistics(), + _list_end.statistics(), + _flag_names.statistics(), + _entry_state.statistics(), + }; +} + } // namespace ink::runtime::internal diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index 03827ac1..7abcca39 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -6,6 +6,7 @@ */ #pragma once +#include "config.h" #include "system.h" #include "array.h" #include "snapshot_impl.h" @@ -55,6 +56,9 @@ class list_table : public snapshot_interface int lid; ///< id of list to handle }; + /** Get usage statistics for the list_table. */ + config::statistics::list_table statistics() const; + /// creates an empty list list create(); diff --git a/inkcpp/numeric_operations.h b/inkcpp/numeric_operations.h index 674aa27f..489919b4 100644 --- a/inkcpp/numeric_operations.h +++ b/inkcpp/numeric_operations.h @@ -508,7 +508,9 @@ class operation : public operation_b void operator()(basic_eval_stack& stack, value* vals) { - stack.push(value{}.set(! vals[0].get())); + stack.push( + value{}.set(-static_cast(vals[0].get())) + ); } }; diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index af773653..43fa35b5 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -22,6 +22,22 @@ basic_stream::basic_stream(value* buffer, size_t len) { } +void basic_stream::initelize_data(value* buffer, size_t size) +{ + inkAssert( + _data == nullptr && _max == 0, + "Try to double initialize a basic_stream." + "To extend the size use overflow()" + ); + _data = buffer; + _max = size; +} + +void basic_stream::overflow(value*& buffer, size_t& size, size_t target) +{ + inkFail("Stack overflow!"); +} + void basic_stream::append(const value& in) { // newline after glue -> no newline @@ -56,7 +72,9 @@ void basic_stream::append(const value& in) return; // Add to data stream - inkAssert(_size < _max, "Output stream overflow"); + if (_size >= _max) { + overflow(_data, _max); + } _data[_size++] = in; // Special: Incoming glue. Trim whitespace/newlines prior @@ -524,6 +542,9 @@ const unsigned char* basic_stream::snap_load(const unsigned char* ptr, const loa ptr = snap_read(ptr, _last_char); ptr = snap_read(ptr, _size); ptr = snap_read(ptr, _save); + if (_size >= _max) { + overflow(_data, _max, _size); + } inkAssert(_max >= _size, "output is to small to hold stored data"); for (auto itr = _data; itr != _data + _size; ++itr) { ptr = itr->snap_load(ptr, loader); diff --git a/inkcpp/output.h b/inkcpp/output.h index b49853a3..c091d8f8 100644 --- a/inkcpp/output.h +++ b/inkcpp/output.h @@ -6,9 +6,10 @@ */ #pragma once -#include "value.h" +#include "config.h" #include "platform.h" #include "snapshot_impl.h" +#include "value.h" namespace ink { @@ -23,6 +24,8 @@ namespace runtime { protected: basic_stream(value*, size_t); + void initelize_data(value*, size_t); + virtual void overflow(value*& buffer, size_t& size, size_t target = 0); public: // Constant to identify an invalid position in the stream @@ -154,17 +157,35 @@ namespace runtime basic_stream& operator>>(basic_stream&, std::string&); #endif - template + template class stream : public basic_stream { + using base = basic_stream; + public: stream() - : basic_stream(&_buffer[0], N) + : basic_stream(nullptr, 0) + { + base::initelize_data(_buffer.data(), N); + } + + config::statistics::container statistics() const { return _buffer.statistics(); } + + virtual void overflow(value*& buffer, size_t& size, size_t target = 0) override { + if constexpr (dynamic) { + if (buffer) { + _buffer.extend(target); + } + buffer = _buffer.data(); + size = _buffer.capacity(); + } else { + base::overflow(buffer, size); + } } private: - value _buffer[N]; + managed_array _buffer; }; } // namespace internal } // namespace runtime diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index e51572a1..05ab406b 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -354,7 +354,7 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) break; } if (_container.empty() || _container.top().id != id) { - _container.push({.id = id, .offset = offset}); + _container.push({id, offset - _story->instructions()}); } else { _container.pop(); if (_container.size() < comm_end) { @@ -373,7 +373,7 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) _entered_knot = true; } _ptr += 6; - _container.push({.id = id, .offset = offset}); + _container.push({id, offset - _story->instructions()}); if (reversed && comm_end == _container.size() - 1) { ++comm_end; } @@ -385,11 +385,11 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) if (record_visits) { const ContainerData* iData = nullptr; size_t level = _container.size(); - while (_container.iter(iData) - && (level > comm_end - || _story->container_flag(iData->offset) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST) - ) { - auto parrent_offset = iData->offset; + 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"); // 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 @@ -1394,7 +1394,7 @@ void runner_impl::step() // Keep track of current container auto index = read(); // offset points to command, command has size 6 - _container.push({.id = index, .offset = _ptr - 6}); + _container.push({index, _ptr - _story->instructions() - 6}); // Increment visit count if (flag & CommandFlag::CONTAINER_MARKER_TRACK_VISITS @@ -1659,4 +1659,11 @@ std::ostream& operator<<(std::ostream& out, runner_impl& in) } #endif +config::statistics::runner runner_impl::statistics() const +{ + return {_threads.statistics(), _eval.statistics(), _container.statistics(), + _tags.statistics(), _stack.statistics(), _ref_stack.statistics(), + _output.statistics(), _choices.statistics()}; +} + } // namespace ink::runtime::internal diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 57b0201e..26f4e44a 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -25,6 +25,7 @@ #include "choice.h" #include "executioner.h" +#include namespace ink::runtime::internal { @@ -49,6 +50,8 @@ class runner_impl runner_impl(const story_impl*, globals); virtual ~runner_impl(); + config::statistics::runner statistics() const override; + // used by the globals object to do garbage collection void mark_used(string_table&, list_table&) const; @@ -306,7 +309,7 @@ class runner_impl ip_t _done = nullptr; // when we last hit a done // Output stream - internal::stream _output; + internal::stream < abs(config::limitOutputSize), config::limitOutputSize<0> _output; // Runtime stack. Used to store temporary variables and callstack internal::stack < abs(config::limitRuntimeStack), config::limitRuntimeStack<0> _stack; @@ -319,7 +322,7 @@ class runner_impl bool _saved_evaluation_mode = false; // Keeps track of what threads we're inside - threads < config::limitContainerDepth<0, abs(config::limitThreadDepth)> _threads; + threads < config::limitThreadDepth<0, abs(config::limitThreadDepth)> _threads; // Choice list managed_restorable_array < snap_choice, config::maxChoices<0, abs(config::maxChoices)> _choices; @@ -337,7 +340,7 @@ class runner_impl // Container set struct ContainerData { container_t id = ~0u; - ip_t offset = 0; + ptrdiff_t offset = 0; bool operator==(const ContainerData& oth) const { return oth.id == id && oth.offset == offset; } diff --git a/inkcpp/simple_restorable_stack.h b/inkcpp/simple_restorable_stack.h index 5f3e4865..db20ef93 100644 --- a/inkcpp/simple_restorable_stack.h +++ b/inkcpp/simple_restorable_stack.h @@ -6,6 +6,7 @@ */ #pragma once +#include "config.h" #include "system.h" #include "array.h" #include "snapshot_impl.h" @@ -27,6 +28,10 @@ class simple_restorable_stack : public snapshot_interface virtual ~simple_restorable_stack() = default; + config::statistics::container statistics() const + { + return {static_cast(_size), static_cast(_pos)}; + } void push(const T& value); T pop(); diff --git a/inkcpp/stack.h b/inkcpp/stack.h index fbf47d20..66dd0ae2 100644 --- a/inkcpp/stack.h +++ b/inkcpp/stack.h @@ -6,6 +6,7 @@ */ #pragma once +#include "config.h" #include "value.h" #include "collections/restorable.h" #include "array.h" @@ -39,6 +40,8 @@ namespace runtime using base = restorable; public: + inline config::statistics::container statistics() const { return base::statistics(); } + virtual ~basic_stack() = default; // Sets existing value, or creates a new one at this callstack entry @@ -167,6 +170,8 @@ namespace runtime public: virtual ~basic_eval_stack() = default; + config::statistics::container statistics() const { return base::statistics(); } + // Push value onto the stack void push(const value&); diff --git a/inkcpp/string_table.cpp b/inkcpp/string_table.cpp index 3103fc24..40dda321 100644 --- a/inkcpp/string_table.cpp +++ b/inkcpp/string_table.cpp @@ -5,6 +5,7 @@ * https://github.com/JBenda/inkcpp for full license details. */ #include "string_table.h" +#include "config.h" namespace ink::runtime::internal { @@ -146,4 +147,13 @@ size_t string_table::get_id(const char* string) const inkAssert(iter != _table.end(), "Try to fetch not contained string!"); return iter.temp_identifier(); } + +config::statistics::string_table string_table::statistics() const +{ + return config::statistics::string_table{ + {static_cast(_table.max_size()), static_cast(_table.size())}, + }; +} + + } // namespace ink::runtime::internal diff --git a/inkcpp/string_table.h b/inkcpp/string_table.h index 480bcacb..ff6752e6 100644 --- a/inkcpp/string_table.h +++ b/inkcpp/string_table.h @@ -7,6 +7,7 @@ #pragma once #include "avl_array.h" +#include "config.h" #include "system.h" #include "snapshot_impl.h" @@ -40,8 +41,12 @@ class string_table final : public snapshot_interface // deletes all unused strings void gc(); + /** Get usage statistics for the string_table. */ + config::statistics::string_table statistics() const; + private: - avl_array _table; - static constexpr const char* EMPTY_STRING = "\x03"; + avl_array < const char*, bool, ink::size_t, + config::limitStringTable<0, abs(config::limitStringTable)> _table; + static constexpr const char* EMPTY_STRING = "\x03"; }; } // namespace ink::runtime::internal diff --git a/inkcpp_c/inkcpp.cpp b/inkcpp_c/inkcpp.cpp index 2c4fcc24..c12ea018 100644 --- a/inkcpp_c/inkcpp.cpp +++ b/inkcpp_c/inkcpp.cpp @@ -17,37 +17,32 @@ using namespace ink::runtime; InkValue inkvar_to_c(value& val) { + InkValue value{}; switch (val.type) { case value::Type::Bool: - return InkValue{ - .bool_v = val.get(), - .type = InkValue::ValueTypeBool, - }; + value.bool_v = val.get(); + value.type = InkValue::ValueTypeBool; + return value; case value::Type::Uint32: - return InkValue{ - .uint32_v = val.get(), - .type = InkValue::ValueTypeUint32, - }; + value.uint32_v = val.get(); + value.type = InkValue::ValueTypeUint32; + return value; case value::Type::Int32: - return InkValue{ - .int32_v = val.get(), - .type = InkValue::ValueTypeInt32, - }; + value.int32_v = val.get(); + value.type = InkValue::ValueTypeInt32; + return value; case value::Type::String: - return InkValue{ - .string_v = val.get(), - .type = InkValue::ValueTypeString, - }; + value.string_v = val.get(); + value.type = InkValue::ValueTypeString; + return value; case value::Type::Float: - return InkValue{ - .float_v = val.get(), - .type = InkValue::ValueTypeFloat, - }; + value.float_v = val.get(); + value.type = InkValue::ValueTypeFloat; + return value; case value::Type::List: - return InkValue{ - .list_v = reinterpret_cast(val.get()), - .type = InkValue::ValueTypeList, - }; + value.list_v = reinterpret_cast(val.get()); + value.type = InkValue::ValueTypeList; + return value; } inkFail("Undefined value type can not be translated"); return InkValue{}; @@ -118,11 +113,7 @@ extern "C" { { list_interface::iterator itr = reinterpret_cast(self)->begin(); *iter = InkListIter{ - ._data = &itr._list, - ._i = itr._i, - ._single_list = itr._one_list_iterator, - .flag_name = itr._flag_name, - .list_name = itr._list_name, + &itr._list, itr._i, itr._one_list_iterator, itr._flag_name, itr._list_name, }; return itr != reinterpret_cast(self)->end(); } @@ -131,11 +122,7 @@ extern "C" { { list_interface::iterator itr = reinterpret_cast(self)->begin(list_name); *iter = InkListIter{ - ._data = &itr._list, - ._i = itr._i, - ._single_list = itr._one_list_iterator, - .flag_name = itr._flag_name, - .list_name = itr._list_name, + &itr._list, itr._i, itr._one_list_iterator, itr._flag_name, itr._list_name, }; return itr != reinterpret_cast(self)->end(); } @@ -280,15 +267,21 @@ extern "C" { ); } + constexpr InkValue ink_value_none() + { + InkValue value{}; + value.type = InkValue::Type::ValueTypeNone; + return value; + } + void ink_globals_observe(HInkGlobals* self, const char* variable_name, InkObserver observer) { reinterpret_cast(self)->get()->observe( variable_name, [observer](value new_value, ink::optional old_value) { observer( - inkvar_to_c(new_value), old_value.has_value() - ? inkvar_to_c(old_value.value()) - : InkValue{.type = InkValue::Type::ValueTypeNone} + inkvar_to_c(new_value), + old_value.has_value() ? inkvar_to_c(old_value.value()) : ink_value_none() ); } ); @@ -299,9 +292,7 @@ extern "C" { ink::optional o_val = reinterpret_cast(self)->get()->get(variable_name); if (! o_val.has_value()) { - return InkValue{ - .type = InkValue::ValueTypeNone, - }; + return ink_value_none(); } else { return inkvar_to_c(o_val.value()); } diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index 01ef40f2..b9bd4813 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -12,7 +12,66 @@ #include #include +#include "config.h" #include "test.h" +#include "types.h" + +size_t depth = 0; + +std::ostream& operator<<(std::ostream& os, const ink::config::statistics::container& c) +{ + os << "(" << c.size << "/" << c.capacity << ")"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const ink::config::statistics::list_table& lt) +{ + os << "\n"; + depth += 1; + os << std::string(depth, '\t') << "editable_lists" << lt.editable_lists << "\n"; + os << std::string(depth, '\t') << "list_types" << lt.list_types << "\n"; + os << std::string(depth, '\t') << "flags" << lt.flags << "\n"; + os << std::string(depth, '\t') << "lists" << lt.lists << "\n"; + depth -= 1; + return os; +} + +std::ostream& operator<<(std::ostream& os, const ink::config::statistics::string_table& st) +{ + os << "\n"; + depth += 1; + os << std::string(depth, '\t') << "string_refs" << st.string_refs << "\n"; + depth -= 1; + return os; +} + +std::ostream& operator<<(std::ostream& os, const ink::config::statistics::runner& r) +{ + os << "\n"; + depth += 1; + os << std::string(depth, '\t') << "threads" << r.threads << "\n"; + os << std::string(depth, '\t') << "evaluation_stack" << r.evaluation_stack << "\n"; + os << std::string(depth, '\t') << "container_stack" << r.container_stack << "\n"; + os << std::string(depth, '\t') << "active_tags" << r.active_tags << "\n"; + os << std::string(depth, '\t') << "runtime_stack" << r.runtime_stack << "\n"; + os << std::string(depth, '\t') << "runtime_ref_stack" << r.runtime_ref_stack << "\n"; + os << std::string(depth, '\t') << "output" << r.output << "\n"; + os << std::string(depth, '\t') << "choices" << r.choices << "\n"; + depth -= 1; + return os; +} + +std::ostream& operator<<(std::ostream& os, const ink::config::statistics::global& g) +{ + os << "\n"; + depth += 1; + os << std::string(depth, '\t') << "variables" << g.variables << "\n"; + os << std::string(depth, '\t') << "variables_observers" << g.variables_observers << "\n"; + os << std::string(depth, '\t') << "lists" << g.lists; + os << std::string(depth, '\t') << "strings" << g.strings; + depth -= 1; + return os; +} void usage() { @@ -24,6 +83,7 @@ void usage() << "\t--ommit-choice-tags:\tdo not print tags after choices, primarly used to be compatible " "with inkclecat output" << "\t--inklecate :\toverwrites INKLECATE enviroment variable\n" + << "\t--statistics:\tprints memory statistics before each choice\n" << endl; } @@ -39,6 +99,7 @@ int main(int argc, const char** argv) std::string outputFilename; bool playMode = false, testMode = false, testDirectory = false, ommit_choice_tags = false; std::string snapshotFile; + bool show_statistics = false; const char* inklecateOverwrite = nullptr; for (int i = 1; i < argc - 1; i++) { std::string option = argv[i]; @@ -63,6 +124,8 @@ int main(int argc, const char** argv) ++i; inklecateOverwrite = argv[i]; } + } else if (option == "--statistics") { + show_statistics = true; } else { std::cerr << "Unrecognized option: '" << option << "'\n"; } @@ -150,13 +213,15 @@ int main(int argc, const char** argv) std::unique_ptr myInk{story::from_file(outputFilename.c_str())}; // Start runner - runner thread; + runner thread; + globals variables; if (snapshotFile.size()) { auto snap_ptr = snapshot::from_file(snapshotFile.c_str()); thread = myInk->new_runner_from_snapshot(*snap_ptr); delete snap_ptr; } else { - thread = myInk->new_runner(); + variables = myInk->new_globals(); + thread = myInk->new_runner(variables); } while (true) { @@ -189,6 +254,11 @@ int main(int argc, const char** argv) std::cout << std::endl; } + if (show_statistics) { + std::cout << "runner:" << thread->statistics() << "globals:" << variables->statistics() + << std::endl; + } + int c = 0; std::cout << "?> "; std::cin >> c; diff --git a/inkcpp_compiler/CMakeLists.txt b/inkcpp_compiler/CMakeLists.txt index 962a9d82..b7293f44 100644 --- a/inkcpp_compiler/CMakeLists.txt +++ b/inkcpp_compiler/CMakeLists.txt @@ -25,10 +25,6 @@ set_target_properties(inkcpp_compiler PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS target_link_libraries(inkcpp_compiler PRIVATE inkcpp_shared) target_link_libraries(inkcpp_compiler_o PRIVATE inkcpp_shared) -# Make sure this project and all dependencies use the C++17 standard -target_compile_features(inkcpp_compiler PUBLIC cxx_std_17) -target_compile_features(inkcpp_compiler PUBLIC cxx_std_17) - # Unreal installation list(REMOVE_ITEM SOURCES "json.hpp") configure_file("json.hpp" "${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/ThirdParty/Private/json.hpp" COPYONLY) diff --git a/inkcpp_compiler/command.cpp b/inkcpp_compiler/command.cpp index 08221812..7de81b51 100644 --- a/inkcpp_compiler/command.cpp +++ b/inkcpp_compiler/command.cpp @@ -8,100 +8,104 @@ namespace ink { - // Command strings used by compiler - const char* CommandStrings[] = { - "inkcpp_STR", - "inkcpp_INT", - "inkcpp_BOOL", - "inkcpp_FLOAT", - "inkcpp_VALUE_POINTER", - "inkcpp_DIVERT_VAL", - "inkcpp_LIST", - "\n", - "<>", - "void", - "inkcpp_TAG", - "inkcpp_DIVERT", - "inkcpp_DIVERT_TO_VARIABLE", - "inkcpp_TUNNEL", - "inkcpp_FUNCTION", - "done", - "end", - "->->", - "~ret", +// Command strings used by compiler +const char* CommandStrings[] + = {"inkcpp_STR", + "inkcpp_INT", + "inkcpp_BOOL", + "inkcpp_FLOAT", + "inkcpp_VALUE_POINTER", + "inkcpp_DIVERT_VAL", + "inkcpp_LIST", + "\n", + "<>", + "void", + "inkcpp_TAG", + "inkcpp_DIVERT", + "inkcpp_DIVERT_TO_VARIABLE", + "inkcpp_TUNNEL", + "inkcpp_FUNCTION", + "done", + "end", + "->->", + "~ret", - "inkcpp_DEFINE_TEMP", - "inkcpp_SET_VARIABLE", + "inkcpp_DEFINE_TEMP", + "inkcpp_SET_VARIABLE", - "ev", - "/ev", - "out", - "pop", - "du", - "inkcpp_PUSH_VARIABLE_VALUE", - "visit", - "turn", - "inkcpp_READ_COUNT", - "seq", - "srnd", + "ev", + "/ev", + "out", + "pop", + "du", + "inkcpp_PUSH_VARIABLE_VALUE", + "visit", + "turn", + "inkcpp_READ_COUNT", + "seq", + "srnd", - "str", - "/str", - "#", - "/#", + "str", + "/str", + "#", + "/#", - "inkcpp_CHOICE", - "thread", + "inkcpp_CHOICE", + "thread", - "range", + "range", - "+", - "-", - "/", - "*", - "%", - "rnd", - "==", - ">", - "<", - ">=", - "<=", - "!=", - "&&", - "||", - "MIN", - "MAX", - "?", - "!?", - "L^", - "listInt", + "+", + "-", + "/", + "*", + "%", + "rnd", + "==", + ">", + "<", + ">=", + "<=", + "!=", + "&&", + "||", + "MIN", + "MAX", + "?", + "!?", + "L^", + "listInt", - "!", - "~", - "LIST_COUNT", - "LIST_MIN", - "LIST_MAX", - "readc", - "turns", - "lrnd", - "FLOOR", - "CEILING", - "INT", - "LIST_ALL", - "LIST_INVERT", - "LIST_VALUE", - "choiceCnt", + "!", + "~", + "_", + "LIST_COUNT", + "LIST_MIN", + "LIST_MAX", + "readc", + "turns", + "lrnd", + "FLOOR", + "CEILING", + "INT", + "LIST_ALL", + "LIST_INVERT", + "LIST_VALUE", + "choiceCnt", - "START_CONTAINER", - "END_CONTAINER", + "START_CONTAINER", + "END_CONTAINER", - "CALL_EXTERNAL" - }; + "CALL_EXTERNAL"}; - template - struct equal { - static_assert(A == B, "Not equal!"); - }; - equal dum; - static_assert(sizeof(CommandStrings) / sizeof(const char*) == (int)Command::NUM_COMMANDS, "CommandStrings list muss match Command enumeration"); -} +template +struct equal { + static_assert(A == B, "Not equal!"); +}; + +equal dum; +static_assert( + sizeof(CommandStrings) / sizeof(const char*) == ( int ) Command::NUM_COMMANDS, + "CommandStrings list muss match Command enumeration" +); +} // namespace ink diff --git a/inkcpp_compiler/list_data.cpp b/inkcpp_compiler/list_data.cpp index 4062f4af..8f9905c3 100644 --- a/inkcpp_compiler/list_data.cpp +++ b/inkcpp_compiler/list_data.cpp @@ -30,8 +30,9 @@ void list_data::new_flag(const std::string& flag_name, int value) _flags.emplace_back( &flag_name, list_flag{ - .list_id = static_cast(_list_name.size() - 1), - .flag = static_cast(value)} + static_cast(_list_name.size() - 1), + static_cast(value) + } ); } diff --git a/inkcpp_test/Fixes.cpp b/inkcpp_test/Fixes.cpp index fb1149ce..e49c8c92 100644 --- a/inkcpp_test/Fixes.cpp +++ b/inkcpp_test/Fixes.cpp @@ -5,8 +5,9 @@ #include #include #include +#include -#include +#include using namespace ink::runtime; @@ -34,3 +35,69 @@ SCENARIO("string_table fill up #97", "[fixes]") } } } + +SCENARIO("unknown command _ #109", "[fixes]") +{ + GIVEN("story") + { + std::stringstream ss; + ss << "{\"inkVersion\":21,\"root\":[[\"ev\",{\"VAR?\":\"boolvar\"},\"out\",\"/" + "ev\",\"\\n\",\"ev\",{\"VAR?\":\"boolvar\"},\"_\",\"out\",\"/" + "ev\",\"\\n\",\"ev\",{\"VAR?\":\"boolvar\"},\"_\",\"/" + "ev\",[{\"->\":\".^.b\",\"c\":true},{\"b\":[\"^ first boolvar " + "\",{\"->\":\"0.16\"},null]}],\"nop\",\"\\n\",[\"ev\",{\"VAR?\":\"boolvar\"},\"/" + "ev\",{\"->\":\".^.b\",\"c\":true},{\"b\":[\"\\n\",\"^second " + "boolvar\",\"\\n\",{\"->\":\"0.19\"},null]}],\"nop\",\"\\n\",\"end\",[\"done\",{\"#n\":" + "\"g-0\"}],null],\"done\",{\"global " + "decl\":[\"ev\",true,{\"VAR=\":\"boolvar\"},\"/ev\",\"end\",null]}],\"listDefs\":{}}"; + + WHEN("Run") + { + std::stringstream out; + ink::compiler::compilation_results res; + ink::compiler::run(ss, out, &res); + unsigned char* data; + std::string out_str = out.str(); + data = new unsigned char[out_str.size()]; + for (size_t i = 0; i < out_str.size(); ++i) { + data[i] = out_str[i]; + } + auto ink = story::from_binary(data, out_str.size()); + globals globStore = ink->new_globals(); + runner main = ink->new_runner(globStore); + std::string story = main->getall(); + THEN("expect correct output") + { + REQUIRE(res.warnings.size() == 0); + REQUIRE(res.errors.size() == 0); + REQUIRE( + story == + R"(true +-1 +first boolvar +second boolvar +)" + ); + } + } + } +} + +SCENARIO("snapshot failed inside execution _ #111", "[fixes]") +{ + GIVEN("story with multiline output with a knot") + { + auto ink = story::from_file(INK_TEST_RESOURCE_DIR "111_crash.bin"); + auto ink2 = story::from_file(INK_TEST_RESOURCE_DIR "111_crash.bin"); + runner thread = ink->new_runner(); + WHEN("run store and reload") + { + auto line = thread->getline(); + THEN("outputs first line") { REQUIRE(line == "First line of text\n"); } + auto snapshot = thread->create_snapshot(); + runner thread2 = ink2->new_runner_from_snapshot(*snapshot); + line = thread->getline(); + THEN("outputs second line") { REQUIRE(line == "Second line of test\n"); } + } + } +} diff --git a/inkcpp_test/Value.cpp b/inkcpp_test/Value.cpp index d3aea713..e2615cfd 100644 --- a/inkcpp_test/Value.cpp +++ b/inkcpp_test/Value.cpp @@ -19,7 +19,7 @@ using ink::runtime::internal::value_type; using ink::runtime::internal::string_table; using ink::runtime::internal::list_table; using ink::runtime::internal::prng; -using stream = ink::runtime::internal::stream<128>; +using stream = ink::runtime::internal::stream<128, false>; using ink::runtime::internal::executer; using eval_stack = ink::runtime::internal::eval_stack<28, false>; using ink::Command; @@ -29,28 +29,31 @@ using ink::runtime::internal::globals_impl; using ink::runtime::globals; using ink::runtime::runner; -void cp_str(char* dst, const char* src) { - while(*src) { *dst++ = *src++; } +void cp_str(char* dst, const char* src) +{ + while (*src) { + *dst++ = *src++; + } *dst = 0; } SCENARIO("compare concatenated values") { - string_table str_table; - list_table lst_table{}; - prng rng; - eval_stack stack; + string_table str_table; + list_table lst_table{}; + prng rng; + eval_stack stack; story_impl story(INK_TEST_RESOURCE_DIR "ListStory.bin"); - globals globs_ptr = story.new_globals(); - runner run = story.new_runner(globs_ptr); - globals_impl& globs = *globs_ptr.cast(); - executer ops(rng, story, globs, globs.strings(), globs.lists(), *run); - + globals globs_ptr = story.new_globals(); + runner run = story.new_runner(globs_ptr); + globals_impl& globs = *globs_ptr.cast(); + executer ops(rng, story, globs, globs.strings(), globs.lists(), *run); + GIVEN("just single strings") { - const char str_1[] = "Hello World!"; + const char str_1[] = "Hello World!"; const char str_1_again[] = "Hello World!"; - const char str_2[] = "Bye World!"; + const char str_2[] = "Bye World!"; WHEN("equal") { stack.push(value{}.set(str_1)); @@ -79,17 +82,19 @@ SCENARIO("compare concatenated values") GIVEN("string and numbers") { stream out{}; - char* str_hello = str_table.create(6); + char* str_hello = str_table.create(6); cp_str(str_hello, "hello"); char* str_5hello = str_table.create(7); cp_str(str_5hello, "5hello"); char* str_4 = str_table.create(2); cp_str(str_4, "4"); char* str_32_4 = str_table.create(33); - for (int i = 0; i < 32; ++i) { str_32_4[i] = '4'; } + for (int i = 0; i < 32; ++i) { + str_32_4[i] = '4'; + } str_32_4[32] = 0; - int int_4 = 4; + int int_4 = 4; int int_45 = 45; WHEN("concatenated string representation matches (2 fields)") { @@ -142,8 +147,8 @@ SCENARIO("compare concatenated values") } GIVEN("numbers") { - int i5 = 5; - int i8 = 8; + int i5 = 5; + int i8 = 8; float f5 = 5.f; WHEN("numbers are same") { diff --git a/inkcpp_test/ink/111_crash.ink b/inkcpp_test/ink/111_crash.ink new file mode 100644 index 00000000..88cbb4b9 --- /dev/null +++ b/inkcpp_test/ink/111_crash.ink @@ -0,0 +1,15 @@ +VAR test = false + +-> start + +=== start === +First line of text +Second line of test + +* Choice +~ test = true +* Choice + +- end + +-> END diff --git a/setup.py b/setup.py index e71d4150..8c5ed571 100644 --- a/setup.py +++ b/setup.py @@ -153,7 +153,7 @@ def build_extension(self, ext: CMakeExtension) -> None: setup( name="inkcpp-py", - version="0.1.8", + version="0.1.9", author="Julian Benda", author_email="julian.benda@ovgu.de", description="Python bindings for InkCPP a Inkle runtime written in C++", diff --git a/shared/private/command.h b/shared/private/command.h index 425966dd..a055ebc9 100644 --- a/shared/private/command.h +++ b/shared/private/command.h @@ -98,6 +98,7 @@ enum class Command : uint8_t { // == Unary operators UNARY_OPERATORS_START, NOT = UNARY_OPERATORS_START, + INVERT, NEGATE, LIST_COUNT, LIST_MIN, diff --git a/shared/public/config.h b/shared/public/config.h index 2dc23ddb..4bb0877f 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -30,24 +30,64 @@ static constexpr int limitEvalStackDepth = -20; static constexpr int limitContainerDepth = -20; /** number of lists which can be accessed with get_var * before the story must continue - * @attention list vars are only valid until the story continous! + * @attention list vars are only valid until the story continuous! */ static constexpr int limitEditableLists = -5; /// number of simultaneous active tags static constexpr int limitActiveTags = -10; -// temporary variables and callstack; -static constexpr int limitRuntimeStack = -20; -// references and callstack -static constexpr int limitReferenceStack = -20; +// temporary variables and call stack; + +static constexpr int limitRuntimeStack = -20; +// references and call stack +static constexpr int limitReferenceStack = -20; // max number of elements in one output (a string is one element) -// no dynamic support now! (FIXME) -static constexpr int limitOutputSize = 200; +static constexpr int limitOutputSize = -100; +// maximum number of text fragments between choices +static constexpr int limitStringTable = -100; // max number of choices per choice -static constexpr int maxChoices = -10; +static constexpr int maxChoices = -10; // max number of list types, and there total amount of flags -static constexpr int maxListTypes = -20; -static constexpr int maxFlags = -200; -// number of max initelized lists -static constexpr int maxLists = -50; -static constexpr int maxArrayCallArity = 10; +static constexpr int maxListTypes = -20; +static constexpr int maxFlags = -200; +// number of max initialized lists +static constexpr int maxLists = -50; +// max number of arguments for external functions (dynamic not possible) +static constexpr int maxArrayCallArity = 10; + +namespace statistics +{ + struct container { + int capacity; + int size; + }; + + struct list_table { + container editable_lists; /** based on @ref limitEditableLists */ + container list_types; /** based on @ref maxListTypes */ + container flags; /** based on @ref maxFlags */ + container lists; /** based on @ref maxLists */ + }; + + struct string_table { + container string_refs; /** based on @ref limitStringTable */ + }; + + struct global { + container variables; /** based on @ref limitGlobalVariables */ + container variables_observers; /** based on @ref limitGlobalVariableObservers */ + list_table lists; + string_table strings; + }; + + struct runner { + container threads; /** based on @ref limitThreadDepth */ + container evaluation_stack; /** based on @ref limitEvalStackDepth */ + container container_stack; /** based on @ref limitContainerDepth */ + container active_tags; /** based on @ref limitActiveTags */ + container runtime_stack; /** based on @ref limitContainerDepth */ + container runtime_ref_stack; /** based on @ref limitReferenceStack */ + container output; /** based on @ref limitOutputSize */ + container choices; /** based on @ref limitContainerDepth */ + }; +} // namespace statistics } // namespace ink::config