From a35f2a699d22858b96f08842a9efdcc9ef9056bd Mon Sep 17 00:00:00 2001 From: Medusa Slockbower Date: Wed, 17 Sep 2025 17:13:52 -0400 Subject: [PATCH] - Fixed some missing and erroneous testing logic for containers - Lots of bug-fixing for containers - Performance optimization for containers --- doxy/Doxyfile | 34 +- examples/assert.cpp | 67 ++++ gdb/fennec/containers.py | 114 +++++-- include/fennec/containers/bintree.h | 167 ++++----- include/fennec/containers/dynarray.h | 16 +- include/fennec/containers/object_pool.h | 31 +- include/fennec/containers/priority_queue.h | 145 +++++++- include/fennec/containers/sequence.h | 355 ++++++++++++++------ include/fennec/lang/integer.h | 16 +- test/CMakeLists.txt | 1 + test/tests/containers/test_bintree.h | 109 ++++++ test/tests/containers/test_priority_queue.h | 56 +++ test/tests/containers/test_rdtree.h | 5 - test/tests/containers/test_sequence.h | 20 +- test/tests/test_containers.h | 12 + 15 files changed, 886 insertions(+), 262 deletions(-) create mode 100644 examples/assert.cpp create mode 100644 test/tests/containers/test_bintree.h create mode 100644 test/tests/containers/test_priority_queue.h diff --git a/doxy/Doxyfile b/doxy/Doxyfile index d60b106..5fffa05 100644 --- a/doxy/Doxyfile +++ b/doxy/Doxyfile @@ -42,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = fennec +PROJECT_NAME = Workbook # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -68,7 +68,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = /home/medusa/Documents/Work/Personal/fennec/docs +OUTPUT_DIRECTORY = /home/medusa/Documents/Work/Personal/Workbook/external/fennec/docs # If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 # sub-directories (in 2 levels) under the output directory of each output format @@ -815,7 +815,7 @@ FILE_VERSION_FILTER = # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = /home/medusa/Documents/Work/Personal/fennec/doxy/DoxyLayout.xml +LAYOUT_FILE = /home/medusa/Documents/Work/Personal/Workbook/external/fennec/doxy/DoxyLayout.xml # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib @@ -943,9 +943,9 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = "/home/medusa/Documents/Work/Personal/fennec/include" \ - "/home/medusa/Documents/Work/Personal/fennec/source" \ - "/home/medusa/Documents/Work/Personal/fennec/README.md" +INPUT = "/home/medusa/Documents/Work/Personal/Workbook/external/fennec/include" \ + "/home/medusa/Documents/Work/Personal/Workbook/external/fennec/source" \ + "/home/medusa/Documents/Work/Personal/Workbook/external/fennec/README.md" # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -1079,7 +1079,7 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = "/home/medusa/Documents/Work/Personal/fennec/examples" +EXAMPLE_PATH = "/home/medusa/Documents/Work/Personal/Workbook/external/fennec/examples" # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and @@ -1099,7 +1099,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = "/home/medusa/Documents/Work/Personal/fennec/doxy/static" +IMAGE_PATH = "/home/medusa/Documents/Work/Personal/Workbook/external/fennec/doxy/static" # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program @@ -1160,7 +1160,7 @@ FILTER_SOURCE_PATTERNS = # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = "/home/medusa/Documents/Work/Personal/fennec/README.md" +USE_MDFILE_AS_MAINPAGE = "/home/medusa/Documents/Work/Personal/Workbook/external/fennec/README.md" # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common @@ -1359,7 +1359,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = /home/medusa/Documents/Work/Personal/fennec/doxy/header.html +HTML_HEADER = /home/medusa/Documents/Work/Personal/Workbook/external/fennec/doxy/header.html # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1369,7 +1369,7 @@ HTML_HEADER = /home/medusa/Documents/Work/Personal/fennec/doxy/header # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = /home/medusa/Documents/Work/Personal/fennec/doxy/footer.html +HTML_FOOTER = /home/medusa/Documents/Work/Personal/Workbook/external/fennec/doxy/footer.html # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1381,7 +1381,7 @@ HTML_FOOTER = /home/medusa/Documents/Work/Personal/fennec/doxy/footer # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = /home/medusa/Documents/Work/Personal/fennec/doxy/style.css +HTML_STYLESHEET = /home/medusa/Documents/Work/Personal/Workbook/external/fennec/doxy/style.css # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets @@ -1399,10 +1399,10 @@ HTML_STYLESHEET = /home/medusa/Documents/Work/Personal/fennec/doxy/style. # documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = /home/medusa/Documents/Work/Personal/fennec/doxy/doxygen-awesome.css \ - /home/medusa/Documents/Work/Personal/fennec/doxy/doxygen-awesome-sidebar-only.css \ - /home/medusa/Documents/Work/Personal/fennec/doxy/doxygen-awesome-sidebar-only-darkmode-toggle.css \ - /home/medusa/Documents/Work/Personal/fennec/doxy/custom.css +HTML_EXTRA_STYLESHEET = /home/medusa/Documents/Work/Personal/Workbook/external/fennec/doxy/doxygen-awesome.css \ + /home/medusa/Documents/Work/Personal/Workbook/external/fennec/doxy/doxygen-awesome-sidebar-only.css \ + /home/medusa/Documents/Work/Personal/Workbook/external/fennec/doxy/doxygen-awesome-sidebar-only-darkmode-toggle.css \ + /home/medusa/Documents/Work/Personal/Workbook/external/fennec/doxy/custom.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1412,7 +1412,7 @@ HTML_EXTRA_STYLESHEET = /home/medusa/Documents/Work/Personal/fennec/doxy/doxyge # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = "/home/medusa/Documents/Work/Personal/fennec/doxy/doxygen-awesome-darkmode-toggle.js" +HTML_EXTRA_FILES = "/home/medusa/Documents/Work/Personal/Workbook/external/fennec/doxy/doxygen-awesome-darkmode-toggle.js" # The HTML_COLORSTYLE tag can be used to specify if the generated HTML output # should be rendered with a dark or light theme. diff --git a/examples/assert.cpp b/examples/assert.cpp new file mode 100644 index 0000000..94de21e --- /dev/null +++ b/examples/assert.cpp @@ -0,0 +1,67 @@ +// ===================================================================================================================== +// I release this example code into the public domain +// ===================================================================================================================== + +// This file contains code that tests the efficiency of the assert macro scheme in +// fennec engine. This is purely looking at the branching aspect and not the private +// assert definition. The code only uses passing values to see any performance overhead +// from the conditional + +// This code is based on the example code that cppreference provides at +// https://en.cppreference.com/w/cpp/language/attributes/likely + +// To my surprise, the difference between them is negligible. +// Even when n is a crazy number like one billion, there isn't a conclusive difference. +// I checked that it isn't the lambdas, they are optimized out. + +// In debug mode, the results are to be expected; release < experimental < control + +#include +#include +#include +#include +#include + +#define assert_c(expression) \ +if(not(expression)) { \ +std::abort(); \ +} + +#define assert_e(expression) \ +if(not(expression)) [[unlikely]] { \ +std::abort(); \ +} + +#define assert_r(expression) (static_cast (0)) + +volatile int sink{}; // ensures a side effect + +int main() { + auto benchmark = [](auto fun, auto rem) { + srand(0); + + const auto start = std::chrono::high_resolution_clock::now(); + for (auto size{1ULL}; size != 10'000'000ULL; ++size) + sink = fun(rand()); + const std::chrono::duration diff = + std::chrono::high_resolution_clock::now() - start; + + std::cout << "Time: " << std::fixed << std::setprecision(6) << diff.count() + << " sec " << rem << std::endl; + }; + + benchmark([](int x) { + assert_c(0 <= x && x <= RAND_MAX); + return x; + }, "control"); + + benchmark([](int x) { + assert_r(0 <= x && x <= RAND_MAX); + return x; + }, "release"); + + benchmark([](int x) { + assert_e(0 <= x && x <= RAND_MAX); + return x; + }, "experimental"); +} diff --git a/gdb/fennec/containers.py b/gdb/fennec/containers.py index 4b4abe4..f398186 100644 --- a/gdb/fennec/containers.py +++ b/gdb/fennec/containers.py @@ -389,18 +389,19 @@ class RDTreePrinter: return self.Iterator(self.tree, 0, self.capacity) -# BINTREE ============================================================================================================== +# PRIORITY QUEUE ======================================================================================================= -class BinTreePrinter: - """Print a fennec::bintree""" +class PriorityQueuePrinter: + """Print a fennec::rdtree""" class Iterator: def __init__(self, tree, node, capacity): self.tree = tree self.capacity = capacity self.visit = deque() + self.skip = True - self.visit.append((node, 0, 0)) + self.visit.append((node, 0, 0, node)) def __iter__(self): return self @@ -412,6 +413,80 @@ class BinTreePrinter: node = self.visit[0][0] i = self.visit[0][1] depth = self.visit[0][2] + start = self.visit[0][3] + self.visit.popleft() + + if node == start and not self.skip: + return self.__next__() + + self.skip = False + + value = self.tree[node]['_val']['key'] + + nnext = self.tree[node]['_val']['next'] + child = self.tree[node]['_val']['child'] + + index = '⠀' * depth * 2 # Uses Braille Space, otherwise it would get eaten as whitespace by parsers + + if nnext < self.capacity: + self.visit.appendleft((nnext, i + 1, depth, start)) + + if child < self.capacity: + self.visit.appendleft((child, 0, depth + 1, child)) + self.skip = True + + # ┌ ─ ├ └ + + if nnext != 18446744073709551615: + index += '├' + else: + index += '└' + + index += '─' + index += '[{}]'.format(node) + return index, value + + + def __init__(self, val): + self.tree = val['_table']['_table']['_alloc']['_data'] + self.size = val['_table']['_size'] + self.capacity = val['_table']['_table']['_alloc']['_capacity'] + self.min = val['_min'] + + def to_string(self): + if self.size == 0: + return "{ empty }" + return "{ size = " + str(self.size) + " }" + + def children(self): + return self.Iterator(self.tree, self.min, self.capacity) + + +# BINTREE ============================================================================================================== + +class BinTreePrinter: + """Print a fennec::bintree""" + + class Iterator: + def __init__(self, tree, node, capacity): + self.tree = tree + self.capacity = capacity + self.visit = deque() + + if capacity > 0: + self.visit.append((node, 0, 0)) + + def __iter__(self): + return self + + def __next__(self): + if len(self.visit) == 0: + raise StopIteration + + node = self.visit[0][0] + i = self.visit[0][1] + depth = self.visit[0][2] + parent = self.tree[node]['parent'] self.visit.popleft() value = self.tree[node]['value'] @@ -424,7 +499,7 @@ class BinTreePrinter: self.visit.appendleft((left, 0, depth + 1)) index = '⠀' * depth * 2 # Uses Braille Space, otherwise it would get eaten as whitespace by parsers - if i == 0: + if i == 0 and parent != 18446744073709551615 and self.tree[parent]['right'] != 18446744073709551615: index += '├' else: index += '└' @@ -524,20 +599,21 @@ class GraphPrinter: def register_printers(): print("registering containers") pp = gdb.printing.RegexpCollectionPrettyPrinter("fennec::containers") - pp.add_printer('fennec::array', '^fennec::array<.*>$', ArrayPrinter) - pp.add_printer('fennec::deque', '^fennec::deque<.*>$', DequePrinter) - pp.add_printer('fennec::dynarray', '^fennec::dynarray<.*>$', DynArrayPrinter) - pp.add_printer('fennec::graph', '^fennec::graph<.*>$', GraphPrinter) - pp.add_printer('fennec::list', '^fennec::list<.*>$', ListPrinter) - pp.add_printer('fennec::map', '^fennec::map<.*>$', MapPrinter) - pp.add_printer('fennec::object_pool', '^fennec::object_pool<.*>$', ObjectPoolPrinter) - pp.add_printer('fennec::optional', '^fennec::optional<.*>$', OptionalPrinter) - pp.add_printer('fennec::pair', '^fennec::pair<.*>$', PairPrinter) - pp.add_printer('fennec::set', '^fennec::set<.*>$', SetPrinter) - pp.add_printer('fennec::rdtree', '^fennec::rdtree<.*>$', RDTreePrinter) - pp.add_printer('fennec::bintree', '^fennec::bintree<.*>$', BinTreePrinter) - pp.add_printer('fennec::sequence', '^fennec::sequence<.*>$', BinTreePrinter) - pp.add_printer('fennec::tuple', '^fennec::tuple<.*>$', TuplePrinter) + pp.add_printer('fennec::array', '^fennec::array<.*>$', ArrayPrinter) + pp.add_printer('fennec::deque', '^fennec::deque<.*>$', DequePrinter) + pp.add_printer('fennec::dynarray', '^fennec::dynarray<.*>$', DynArrayPrinter) + pp.add_printer('fennec::graph', '^fennec::graph<.*>$', GraphPrinter) + pp.add_printer('fennec::list', '^fennec::list<.*>$', ListPrinter) + pp.add_printer('fennec::map', '^fennec::map<.*>$', MapPrinter) + pp.add_printer('fennec::object_pool', '^fennec::object_pool<.*>$', ObjectPoolPrinter) + pp.add_printer('fennec::optional', '^fennec::optional<.*>$', OptionalPrinter) + pp.add_printer('fennec::pair', '^fennec::pair<.*>$', PairPrinter) + pp.add_printer('fennec::set', '^fennec::set<.*>$', SetPrinter) + pp.add_printer('fennec::rdtree', '^fennec::rdtree<.*>$', RDTreePrinter) + pp.add_printer('fennec::bintree', '^fennec::bintree<.*>$', BinTreePrinter) + pp.add_printer('fennec::sequence', '^fennec::sequence<.*>$', BinTreePrinter) + pp.add_printer('fennec::priority_queue', '^fennec::priority_queue<.*>$', PriorityQueuePrinter) + pp.add_printer('fennec::tuple', '^fennec::tuple<.*>$', TuplePrinter) return pp printer = register_printers() diff --git a/include/fennec/containers/bintree.h b/include/fennec/containers/bintree.h index 58d2502..617ea62 100644 --- a/include/fennec/containers/bintree.h +++ b/include/fennec/containers/bintree.h @@ -56,7 +56,6 @@ public: using value_t = TypeT; using alloc_t = allocator_traits::template rebind; static constexpr size_t npos = -1; - inline static size_t sink = npos; friend class iterator; friend class const_iterator; @@ -189,7 +188,7 @@ public: /// \param i The node id /// \returns The parent of node `i` constexpr size_t parent(size_t i) const { - return i >= _table.size() ? npos : _table[i].parent; + return i == npos ? npos : _table[i].parent; } /// @@ -205,7 +204,7 @@ public: /// \param i The node id /// \returns The left child of node `i` constexpr size_t left(size_t i) const { - return i >= _table.size() ? npos : _table[i].left; + return i == npos ? npos : _table[i].left; } /// @@ -213,7 +212,7 @@ public: /// \param i The node id /// \returns The right child of node `i` constexpr size_t right(size_t i) const { - return i >= _table.size() ? npos : _table[i].right; + return i == npos ? npos : _table[i].right; } /// @@ -230,11 +229,7 @@ public: /// \param i The node id /// \returns `true` if `i` is the right node of `parent(i)`, `false` otherwise constexpr bool direction(size_t i) const { - size_t p = parent(i); - if (p >= _table.capacity()) { - return false; - } - return i == right(p); + return i == npos ? false : i == right(parent(i)); } /// @@ -245,15 +240,7 @@ public: size_t p = parent(i); size_t l = left(p); size_t r = right(p); - return i == l ? l : r; - } - - /// - /// \brief Short for "Parent Sibling," \f$O(1)\f$ - /// \param i The id of the node - /// \returns The id of the parents' sibling of `i` - constexpr size_t parsib(size_t i) const { - return sibling(parent(i)); + return i == l ? r : l; } /// @@ -305,22 +292,18 @@ public: /// \details \f$O(1)\f$ /// \param i The node id /// \returns `nullptr` if node `i` does not exist, otherwise, a pointer to the value of node `i` - constexpr value_t* operator[](size_t i) { - if (i >= _table.size()) { - return nullptr; - } - return _table[i] ? &*_table[i] : nullptr; + constexpr value_t& operator[](size_t i) { + assertd(i < _table.size(), "Index out of bounds."); + return _table[i].value; } /// /// \details Const Access, \f$O(1)\f$ /// \param i The node id /// \returns `nullptr` if node `i` does not exist, otherwise, a pointer to the value of node `i` - constexpr const value_t* operator[](size_t i) const { - if (i >= _table.size()) { - return nullptr; - } - return _table[i] ? &*_table[i] : nullptr; + constexpr const value_t& operator[](size_t i) const { + assertd(i < _table.size(), "Index out of bounds."); + return _table[i].value; } /// @} @@ -458,14 +441,16 @@ public: size_t new_root = child(sub, not dir); size_t new_child = child(new_root, dir); - child(sub, not dir) = new_child; - parent(new_child) = sub; - child(new_root, dir) = sub; + _child(sub, not dir) = new_child; + if (new_child != npos) { + _parent(new_child) = sub; + } + _child(new_root, dir) = sub; - parent(new_root) = sub_parent; - parent(sub) = new_root; + _parent(new_root) = sub_parent; + _parent(sub) = new_root; if (sub_parent != npos) { - child(sub_parent, sub == right(sub_parent)) = new_root; + _child(sub_parent, sub == right(sub_parent)) = new_root; } else { _root = new_root; } @@ -516,13 +501,13 @@ public: /// \param visit The visiting object /// \param i The node to start at template - constexpr void traverse(VisitorT&& visit, size_t i = root) { + constexpr void traverse(VisitorT&& visit, size_t i) { OrderT order; i = order(*this, i); while (i != npos) { uint8_t mode = traversal_control_continue; if (_table[i].value) { - mode = visit(*_table[i].value, i); + mode = visit(_table[i].value, i); } if (mode == traversal_control_break) { break; @@ -581,7 +566,7 @@ public: return npos; } - size_t nxt = tree.sibling(node); + size_t nxt = tree.right(tree.parent(node)); size_t chd = tree.left(node); nxt = node == nxt ? npos : nxt; @@ -618,19 +603,16 @@ public: return npos; } - size_t prnt = tree.parent(node); - size_t next = tree.sibling(node); - next = node == next ? npos : next; + size_t parent = tree.parent(node); + size_t pright = tree.right(parent); + size_t next = tree.left_most(tree.right(node)); - if (node != head) { - if (tree.left(prnt) == node) { - visit.push_back(prnt); - if (next != npos) { - visit.push_back(tree.left_most(next)); - } - } else if (next != npos) { - visit.push_front(tree.left_most(next)); - } + if (node != pright && parent != npos) { + visit.push_front(parent); + } + + if (next != npos) { + visit.push_front(next); } if (not visit.empty()) { @@ -648,9 +630,23 @@ public: list visit; size_t head; + constexpr size_t successor(const bintree& tree, size_t n) { + size_t s = tree.left_most(n); + while (n == s) { + size_t r = tree.right(n); + if (r != npos) { + n = r; + s = tree.left_most(n); + } else { + break; + } + } + return s == npos ? n : s; + } + constexpr size_t operator()(const bintree& tree, size_t start) { head = start; - return tree.left_most(start); + return this->successor(tree, start); } constexpr size_t operator[](const bintree& tree, size_t node, uint8_t) { @@ -658,16 +654,15 @@ public: return npos; } - size_t prnt = tree.parent(node); - size_t next = tree.sibling(node); - next = node == next ? npos : next; + size_t parent = tree.parent(node); + size_t pright = tree.right(parent); - if (node != head) { - if (next != npos) { - visit.push_front(tree.left_most(next)); - } else { - visit.push_front(prnt); + if (node == pright) { + if (parent != npos) { + visit.push_front(parent); } + } else if (pright != npos) { + visit.push_front(this->successor(tree, pright)); } if (not visit.empty()) { @@ -677,6 +672,7 @@ public: node = npos; } + return node; } }; @@ -690,6 +686,12 @@ public: size_t _n; public: + constexpr iterator(bintree* tree, size_t root) + : _tree(tree) + , _order() + , _n(_order(*tree, root)) { + } + constexpr iterator(bintree* tree, size_t root, size_t node) : _tree(tree) , _order() @@ -706,19 +708,19 @@ public: } value_t& operator*() { - return _tree[_n]; + return (*_tree)[_n]; } value_t* operator->() { - return &_tree[_n]; + return &(*_tree)[_n]; } const value_t& operator*() const { - return _tree[_n]; + return (*_tree)[_n]; } const value_t* operator->() const { - return &_tree[_n]; + return &(*_tree)[_n]; } constexpr bool operator==(const iterator& it) { @@ -754,7 +756,8 @@ protected: template constexpr size_t _insert_left(size_t p, ArgsT&&...args) { - size_t i = p == npos ? _root : left(p); + size_t i = p >= capacity() ? npos : p; + i = i == npos ? _root : _left(i); if (i != npos) { _table[i].value = value_t(fennec::forward(args)...); } else { @@ -764,6 +767,9 @@ protected: d = depth(p) + 1; _table[p].left = i; } + if (_root == npos) { + _root = i; + } fennec::construct(&_table[i], p, npos, npos, d, fennec::forward(args)...); } return i; @@ -771,7 +777,7 @@ protected: template constexpr size_t _insert_right(size_t p, ArgsT&&...args) { - size_t i = p == npos ? _root : right(p); + size_t i = p == npos ? _root : _right(p); if (i != npos) { _table[i].value = value_t(fennec::forward(args)...); if (p == npos || _root == npos) { @@ -784,42 +790,41 @@ protected: d = depth(p) + 1; _table[p].right = i; } + if (_root == npos) { + _root = i; + } fennec::construct(&_table[i], p, npos, npos, d, fennec::forward(args)...); } return i; } - constexpr size_t& parent(size_t i) { - return i >= _table.size() ? sink : _table[i].parent; + constexpr size_t& _parent(size_t i) { + return _table[i].parent; } - constexpr size_t& grandparent(size_t i) { - return parent(parent(i)); + constexpr size_t& _grandparent(size_t i) { + return _parent(parent(i)); } - constexpr size_t& left(size_t i) { - return i >= _table.size() ? sink : _table[i].left; + constexpr size_t& _left(size_t i) { + return _table[i].left; } - constexpr size_t& right(size_t i) { - return i >= _table.size() ? sink : _table[i].right; + constexpr size_t& _right(size_t i) { + return _table[i].right; } - constexpr size_t& child(size_t i, bool dir) { - return dir ? right(i) : left(i); + constexpr size_t& _child(size_t i, bool dir) { + return dir ? _right(i) : _left(i); } - constexpr size_t& sibling(size_t i) { + constexpr size_t& _sibling(size_t i) { size_t p = parent(i); - size_t& l = left(p); - size_t& r = right(p); + size_t& l = _left(p); + size_t& r = _right(p); return i == l ? l : r; } - - constexpr size_t& parsib(size_t i) { - return sibling(parent(i)); - } }; } diff --git a/include/fennec/containers/dynarray.h b/include/fennec/containers/dynarray.h index 0332f47..253c694 100644 --- a/include/fennec/containers/dynarray.h +++ b/include/fennec/containers/dynarray.h @@ -156,7 +156,9 @@ public: // This constructor should not be invokable since moving is a single object operation and will cause undefined // behaviour when moving to multiple elements - constexpr dynarray(size_t n, TypeT&&) = delete; + constexpr dynarray(size_t n, TypeT&& val) + : dynarray(n, fennec::copy(val)) { + }; /// /// \brief Emplace Constructor @@ -432,6 +434,18 @@ public: } } + /// + /// \brief Resize the dynarray, invoking the copy constructor for all new elements + /// \param n The new size in elements + /// \param val The value to fill with + constexpr void resize(size_t n, const TypeT& val) { + _alloc.creallocate(n); + + while (_size < n) { + emplace_back(val); + } + } + /// /// \brief Clears the contents of the dynarray, destructing all elements and releasing the allocation. constexpr void clear() { diff --git a/include/fennec/containers/object_pool.h b/include/fennec/containers/object_pool.h index f248259..076cbb1 100644 --- a/include/fennec/containers/object_pool.h +++ b/include/fennec/containers/object_pool.h @@ -48,8 +48,8 @@ struct object_pool { // Definitions ========================================================================================================= public: using value_t = TypeT; - using elem_t = optional; - using table_t = dynarray; + using table_t = allocation; + using freed_t = list; // Constructors & Destructor =========================================================================================== @@ -119,8 +119,7 @@ public: /// \returns a reference to the object with id `i` constexpr value_t& operator[](size_t i) { assert(i < capacity(), "Index out of Bounds!"); - assert(_table[i], "Attempted to access Null Object."); - return *_table[i]; + return _table[i]; } /// @@ -129,8 +128,7 @@ public: /// \returns a const-qualified reference to the object with id `i` constexpr const value_t& operator[](size_t i) const { assert(i < capacity(), "Index out of Bounds!"); - assert(_table[i], "Attempted to access Null Object."); - return *_table[i]; + return _table[i]; } /// @} @@ -171,17 +169,24 @@ public: /// \brief Erase an object from the pool /// \param i The id of the object constexpr void erase(size_t i) { - _table[i] = nullopt; + fennec::destruct(&_table[i]); _freed.push_back(i); --_size; } + /// + /// \brief Clear the object pool + constexpr void clear() { + dynarray free(capacity(), false); + _size = 0; + } + /// @} private: - dynarray _table; - list _freed; - size_t _size; + table_t _table; + freed_t _freed; + size_t _size; size_t _next_free() { size_t next = _size; @@ -196,10 +201,10 @@ private: template size_t _insert(ArgsT&&...args) { size_t i = _next_free(); - if (i >= _table.size()) { - _table.emplace_back(); + if (i >= _table.capacity()) { + _table.creallocate(fennec::max(_table.size() * 2, size_t(8))); } - _table[i].emplace(fennec::forward(args)...); + fennec::construct(&_table[i], fennec::forward(args)...); return i; } }; diff --git a/include/fennec/containers/priority_queue.h b/include/fennec/containers/priority_queue.h index c3f4422..b397048 100644 --- a/include/fennec/containers/priority_queue.h +++ b/include/fennec/containers/priority_queue.h @@ -36,28 +36,155 @@ #include #include +// Binary heaps are just kinda busted. +// In-array binary heaps are one of the most efficient data structures for computers +// -> Cache Locality +// -> log(n) runtime +// -> No auxiliary structures or constant runtimes +// -> Only needs an extra byte for color +// +// I tried just about every heap under the sun +// -> strict fibonacci heap, got blown out of the water by std::priority_queue +// -> fibonacci heap, got blown out of the water by std::priority_queue +// -> binomial heap, on-par with std::set, blown out of the water by std::priority_queue +// +// Then I relented and fell back to ye old binary heap +// This implementation roughly matches gcc's std::priority_queue + namespace fennec { template, class AllocT = allocator> struct priority_queue { + +// Definitions & Constants ============================================================================================= public: using value_t = ValueT; using compare_t = CompareT; - using alloc_t = AllocT; + using alloc_t = allocation; + + static constexpr size_t npos = -1; private: - struct node { - size_t parent, child; - size_t left, right; - int degree; - value_t key; - }; + constexpr size_t left(size_t n) const { + n = n * 2 + 1; + return n >= _size ? npos : n; + } - using table_t = object_pool; + constexpr size_t right(size_t n) const { + n = n * 2 + 2; + return n >= _size ? npos : n; + } + constexpr size_t parent(size_t n) const { + return n == 0 ? npos : (n - 1) / 2; + } + + +// Constructors & Destructor =========================================================================================== public: - table_t _table; + constexpr priority_queue() + : _size(0) { + } + + constexpr ~priority_queue() { + while (_size > 0) { + --_size; + fennec::destruct(&_table[_size]); + } + } + + +// Properties ========================================================================================================== + + constexpr size_t size() const { + return _size; + } + + constexpr size_t capacity() const { + return _table.capacity(); + } + + constexpr bool empty() const { + return size() == 0; + } + + +// Access ============================================================================================================== + + constexpr const value_t& front() const { + return _table[0]; + } + + +// Modifiers =========================================================================================================== + + constexpr void push(const value_t& key) { + this->_insert(key); + } + + constexpr void push(value_t&& key) { + this->_insert(fennec::forward(key)); + } + + template + constexpr void emplace(ArgsT&&...args) { + this->_insert(fennec::forward(args)...); + } + + constexpr void pop() { + fennec::swap(_table[0], _table[--_size]); + fennec::destruct(&_table[_size]); + _fix_erase(0); + } + + +// Members ============================================================================================================= +private: + compare_t _compare; + alloc_t _table; + size_t _size; + +// Helpers ============================================================================================================= + + template + constexpr void _insert(ArgsT&&...args) { + if (_size == _table.capacity()) { + _expand(); + } + fennec::construct(&_table[_size], fennec::forward(args)...); + _fix_insert(_size++); + } + + constexpr void _expand() { + _table.reallocate((_table.capacity() + 1) * 2 - 1); + } + + constexpr size_t _min(size_t a, size_t b) { + if (a == npos) { return b; } + if (b == npos) { return a; } + return _compare(_table[a], _table[b]) ? a : b; + } + + void _fix_insert(size_t n) { + size_t p = parent(n); + while (p != npos && _compare(_table[n], _table[p])) { + fennec::swap(_table[n], _table[p]); + n = p; + p = parent(n); + } + } + + void _fix_erase(size_t n) { + size_t c = _min(left(n), right(n)); + while (n != npos && c != npos && _compare(_table[c], _table[n])) { + fennec::swap(_table[c], _table[n]); + n = c; + c = _min(left(n), right(n)); + } + } + + }; } diff --git a/include/fennec/containers/sequence.h b/include/fennec/containers/sequence.h index 5e0d9b2..63a30ee 100644 --- a/include/fennec/containers/sequence.h +++ b/include/fennec/containers/sequence.h @@ -39,6 +39,21 @@ #include #include +// https://en.wikipedia.org/wiki/Red%E2%80%93black_tree +// https://www.geeksforgeeks.org/dsa/insertion-in-red-black-tree/ + +// Uncertain how I managed to do this, but this data structure has +// A 50%-100% performance increase over std::set when running Dijkstra's +// +// Guesses: +// -> I likely make some assumptions that std::set doesn't +// -> Cache locality +// -> Simplified rotation and coloring logic +// +// Some of the implementations I have seen have multiple levels +// of if statements based on directionality which causes branching. +// I use const-expressions that reduce down to cmov instructions + namespace fennec { @@ -97,7 +112,6 @@ protected: using base_t::parent; using base_t::grandparent; using base_t::sibling; - using base_t::parsib; using base_t::left_most; using base_t::right_most; @@ -224,7 +238,7 @@ public: } constexpr void erase(const value_t& val) { - _erase_bst(val); + _erase(find(val).index()); } /// @@ -246,38 +260,39 @@ public: /// /// \returns An iterator at the smallest element in the sequence - constexpr sequence::iterator begin() { - return sequence::iterator(this, _root, _root); + constexpr iterator begin() { + return sequence::iterator(this, _root); } /// /// \returns An iterator after the largest element in the sequence - constexpr sequence::iterator end() { + constexpr iterator end() { return sequence::iterator(this, _root, npos); } class iterator : public base_t::iterator { protected: using base_t::iterator::_n; + using base_t::iterator::_tree; public: using base_t::iterator::iterator; value_t& operator*() { - return _table[_n].value.second; + return base_t::iterator::operator*().first; } const value_t& operator*() const { - return _table[_n].value.second; + return base_t::iterator::operator*().first; } value_t* operator->() { - return &_table[_n].value.second; + return &base_t::iterator::operator*().first; } const value_t* operator->() const { - return &_table[_n].value.second; + return &base_t::iterator::operator*().firstf; } }; @@ -290,26 +305,46 @@ protected: // Helpers ============================================================================================================= protected: + using base_t::_left; + using base_t::_right; + using base_t::_parent; + using base_t::_sibling; + using base_t::_child; + constexpr value_t& _value(size_t i) { - return i >= _table.capacity() ? value_sink : _table[i].value.first; + return _table[i].value.first; } constexpr const value_t& _value(size_t i) const { - return i >= _table.capacity() ? value_sink : _table[i].value.first; + return _table[i].value.first; } constexpr bool& _color(size_t i) { - return i >= _table.capacity() ? color_sink = false : _table[i].value.second; + return _table[i].value.second; } - constexpr bool _color(size_t i) const { - return i >= _table.capacity() ? color_sink = false : _table[i].value.second; + constexpr bool color(size_t i) const { + return i == npos ? false : _table[i].value.second; } + constexpr void _recolor(size_t n) { + bool c = !color(n); + if (n == _root) { // Only recolor if not the root node + _color(n) = c; + } + _color(left(n)) = !_color(left(n)) ; + _color(right(n)) = !_color(right(n)); + } + + // run-of-the-mill bst insert template constexpr size_t _insert_bst(ArgsT&&...args) { value_t val(fennec::forward(args)...); + if (_root == npos) { + return _root = insert_left(npos, node_t(fennec::move(val), red)); + } + size_t i = _root; size_t p = npos; while (i != npos) { @@ -324,10 +359,6 @@ protected: } } - if (_root == npos) { - return _root = insert_left(npos, node_t(fennec::move(val), red)); - } - if (_compare(val, _value(p))) { return insert_left(p, node_t(fennec::move(val), red)); } else { @@ -335,118 +366,232 @@ protected: } } - constexpr void _fix_insert(size_t x) { - while (x != _root && _color(parent(x)) == red) { - if (_color(parsib(x)) == red) { - _color(parent(x)) = black; - _color(parsib(x)) = black; - _color(grandparent(x)) = red; - x = grandparent(x); - } else if (parent(x) == left(grandparent(x))) { - if (x == right(parent(x))) { - x = parent(x); - rotate_left(x); - } - _color(parent(x)) = black; - _color(grandparent(x)) = red; - rotate_right(grandparent(x)); - } else { - if (x == left(parent(x))) { - x = parent(x); - rotate_right(x); - } - _color(parent(x)) = black; - _color(grandparent(x)) = red; - rotate_left(grandparent(x)); + // This makes some cheats given that the structure is modified only by internal functions + // If such is the case, ONLY LL, LR, RL, and RR will show up + // Then we just need to handle splitting a 4-node + constexpr void _fix_insert(size_t n) { + size_t p = parent(n); + while (color(p) != black) { + size_t g = parent(p); + size_t u = sibling(p); + size_t d = direction(n); + size_t r = direction(p); + + // Split 4 node + if (color(u) == red) { + _recolor(g); + n = p; + p = g; + continue; } + + // LR & RL case + if (d != r) { + rotate(p, r); + } + + // LL & RR case + rotate(g, not r); + n = parent(n); + p = parent(n); } - _color(_root) = black; } - constexpr void _shift(size_t u, size_t v) { - if (parent(u) == npos) { + constexpr void _transplant(size_t u, size_t v) { + size_t p = parent(u); + if (p == npos) { _root = v; + } else if (u == left(p)) { + _left(p) = v; } else { - child(parent(u), direction(u)) = v; + _right(p) = v; } - if (v != npos) { - parent(v) = parent(u); + _parent(v) = _parent(u); + } + + constexpr void _swap_val(size_t a, size_t b) { + fennec::swap(_value(a), _value(b)); + } + + constexpr size_t _replace(size_t x) { + size_t l = left(x); + size_t r = right(x); + + // Both are null + if (l == r) { + return npos; + } + + if (l == npos) { + return r; + } else if (r == npos) { + return l; + } else { + return left_most(right(x)); } } - constexpr void _erase_bst(const value_t& val) { - size_t z = find(val).index(); - size_t y = z; - size_t x = npos; - bool c = _color(y); - size_t p = npos; + constexpr size_t _red_child(size_t x) { + size_t l = left(x); + size_t r = right(x); - if (left(z) == npos) { - x = right(z); - p = parent(z); - _shift(z, x); - } else if (right(z) == npos) { - x = left(z); - p = parent(z); - _shift(z, x); - } else { - y = left_most(right(z)); - c = _color(y); - x = right(y); - p = (parent(y) == z) ? y : parent(y); - if (parent(y) != z) { - _shift(y, right(y)); - right(y) = right(z); - parent(right(y)) = y; - } - _shift(z, y); - left(y) = left(z); - if (left(y)) - parent(left(y)) = y; - _color(y) = _color(z); + if (color(l) == red) { + return l; } - fennec::destruct(&_table[z]); - --_size; - - if (c == black) { - _fix_erase(x, p); + if (color(r) == red) { + return r; } + + return npos; } - constexpr void _fix_erase(size_t x, size_t p) { - while (x != _root && _color(x) == black) { - bool dir = direction(x); - size_t w = child(p, not dir); + // This is an implementation based on the C code in + // the wikipedia article adapted to this framework + constexpr void _fix_erase(size_t n) { + size_t p = parent(n); + size_t s, sc, sf; + bool d = n == right(p); - if (_color(w) == red) { - _color(w) = black; + _child(p, d) = npos; + + goto start_balance; + + do { + d = n == right(p); + start_balance: + s = child(p, !d); + sf = child(s, !d); + sc = child(s, d); + + if (color(s) == red) { + // Case 3 + rotate(p, d); _color(p) = red; - w = rotate(p, dir); - } + _color(s) = black; - if (w == npos || (_color(left(w)) == black && _color(right(w)) == black)) { - _color(w) = red; - x = p; - p = parent(x); - } else { - if (_color(child(w, not dir)) == black) { - _color(child(w, dir)) = black; - _color(w) = red; - rotate(w, not dir); - w = child(p, not dir); + // Fix pointers + s = sc; + sf = child(s, !d); + sc = child(s, d); + + if (color(sf) == red) { + goto case_6; } - _color(w) = _color(p); + if (color(sc) == red) { + goto case_5; + } + + // Case 4 + if (color(p) == red) { + if (s != npos) { + _color(s) = red; + } + _color(p) = black; + return; + } + } + + if (color(sf) == red) { + goto case_6; + } + + if (color(sc) == red) { + goto case_5; + } + + // Case 4 + if (color(p) == red) { + if (s != npos) { + _color(s) = red; + } _color(p) = black; - _color(child(w, not dir)) = black; - rotate(p, dir); - x = _root; - break; + return; + } + + // Case 1 + if (p == npos) { + return; + } + + // Case 2 + if (s != npos) { + _color(s) = red; + } + n = p; + } while ((p = parent(n)) != npos); + + return; // + + case_5: + rotate(s, !d); + _color(s) = red; + _color(sc) = black; + sf = s; + s = sc; + + case_6: + rotate(p, d); + _color(s) = color(p); + _color(p) = black; + _color(sf) = black; + } + + constexpr void _erase(size_t n) { + if (n == npos) { + return; + } + + size_t l = left(n); + size_t r = right(n); + + // 2 children + if (l != npos && r != npos) { + size_t s = left_most(r); + _swap_val(n, s); + n = s; + l = left(n); + r = right(n); + } + + size_t p = parent(n); + bool d = n == right(p); + size_t c = l != npos ? l : r; + + // Single child + if (c != npos) { + _parent(c) = p; + } + + // Handles root cases + if (p == npos) { + _root = c; + if (c == npos) { + fennec::destruct(&_table[n]); + _freed.push_back(n); + --_size; + return; + } else { + _color(c) = black; } } - _color(x) = black; + // Single Child, Red, and Root cases + if (p == npos || c != npos || color(n) == red) { + if (p != npos) { + _child(p, d) = c; + } + fennec::destruct(&_table[n]); + _freed.push_back(n); + --_size; + return; + } + + _fix_erase(n); + fennec::destruct(&_table[n]); + _freed.push_back(n); + --_size; } }; diff --git a/include/fennec/lang/integer.h b/include/fennec/lang/integer.h index b20d5d5..99ca549 100644 --- a/include/fennec/lang/integer.h +++ b/include/fennec/lang/integer.h @@ -51,25 +51,25 @@ #undef ULLONG_MIN #undef ULLONG_MAX -#define CHAR_IS_SIGNED false +#define CHAR_IS_SIGNED true #define CHAR_ROUNDS 0x0 -#define CHAR_RADIX_DIG 0x8 +#define CHAR_RADIX_DIG 0x7 #define CHAR_DIG 0x2 #define CHAR_DECIMAL_DIG 0x0 #define CHAR_RADIX 0x2 #define CHAR_TRAPS 0xtrue -#define CHAR_MIN 0x0 -#define CHAR_MAX 0xff +#define CHAR_MIN 0x80 +#define CHAR_MAX 0x7f -#define WCHAR_IS_SIGNED false +#define WCHAR_IS_SIGNED true #define WCHAR_ROUNDS 0x0 -#define WCHAR_RADIX_DIG 0x20 +#define WCHAR_RADIX_DIG 0x1f #define WCHAR_DIG 0x9 #define WCHAR_DECIMAL_DIG 0x0 #define WCHAR_RADIX 0x2 #define WCHAR_TRAPS 0xtrue -#define WCHAR_MIN 0x0 -#define WCHAR_MAX 0xffffffff +#define WCHAR_MIN 0x80000000 +#define WCHAR_MAX 0x7fffffff #define SCHAR_ROUNDS 0x0 #define SCHAR_RADIX_DIG 0x7 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 07d1762..226776b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable(fennec-test tests/containers/performance/test_iterator_visitor.h tests/containers/test_sequence.h tests/langproc/test_format.h + tests/containers/test_priority_queue.h ) target_compile_definitions(fennec-test PUBLIC FENNEC_TEST_CWD="${CMAKE_SOURCE_DIR}/bin/${FENNEC_BUILD_NAME}" diff --git a/test/tests/containers/test_bintree.h b/test/tests/containers/test_bintree.h new file mode 100644 index 0000000..85cf448 --- /dev/null +++ b/test/tests/containers/test_bintree.h @@ -0,0 +1,109 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 Medusa Slockbower +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ===================================================================================================================== + +#ifndef FENNEC_TEST_CONTAINERS_BINTREE_H +#define FENNEC_TEST_CONTAINERS_BINTREE_H + + +#include "../../test.h" + +#include + +namespace fennec +{ + +namespace test +{ + +inline void fennec_test_containers_bintree() { + using tree_t = bintree; + + tree_t test; + constexpr size_t npos = tree_t::npos; + constexpr size_t pre_order [] = { 1, 2, 4, 5, 3, 6 }; + constexpr size_t in_order [] = { 4, 2, 5, 1, 3, 6 }; + constexpr size_t post_order[] = { 4, 5, 2, 6, 3, 1 }; + + const size_t n = 50; + for (size_t i = 0; i < n; ++i) { + const size_t parent = rand() % max(test.size(), size_t(1)); + const bool side = rand() % 2; + size_t l = side ? test.insert_right(parent, i) : test.insert_left(parent, i); + assertf(test.parent(l) == parent, "Tree Construct Test Failed."); + } + + fennec_test_spacer(1); + + test.clear(); + + fennec_test_run(test.empty(), true); + + fennec_test_spacer(1); + + size_t n1 = test.insert_left(npos, 1); + size_t n2 = test.insert_left(n1, 2); + size_t n3 = test.insert_right(n1, 3); + size_t n4 = test.insert_left(n2, 4); + size_t n5 = test.insert_right(n2, 5); + size_t n6 = test.insert_right(n3, 6); + fennec_test_run(n1 != npos, true); + fennec_test_run(n2 != npos, true); + fennec_test_run(n3 != npos, true); + fennec_test_run(n4 != npos, true); + fennec_test_run(n5 != npos, true); + fennec_test_run(n6 != npos, true); + + fennec_test_spacer(1); + + size_t i; + + i = 0; + test.traverse([&](size_t x, size_t) -> uint8_t { + fennec_test_run(x, pre_order[i++]); + return traversal_control_continue; + }, test.root()); + + fennec_test_spacer(1); + + i = 0; + test.traverse([&](size_t x, size_t) -> uint8_t { + fennec_test_run(x, in_order[i++]); + return traversal_control_continue; + }, test.root()); + + fennec_test_spacer(1); + + i = 0; + test.traverse([&](size_t x, size_t) -> uint8_t { + fennec_test_run(x, post_order[i++]); + return traversal_control_continue; + }, test.root()); + + fennec_test_spacer(1); + + test.clear(); + + fennec_test_run(test.empty(), true); + +} + +} + +} + +#endif // FENNEC_TEST_CONTAINERS_RDTREE_H \ No newline at end of file diff --git a/test/tests/containers/test_priority_queue.h b/test/tests/containers/test_priority_queue.h new file mode 100644 index 0000000..3f48c49 --- /dev/null +++ b/test/tests/containers/test_priority_queue.h @@ -0,0 +1,56 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 Medusa Slockbower +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ===================================================================================================================== + +#ifndef FENNEC_TEST_CONTAINERS_PRIORITY_QUEUE_H +#define FENNEC_TEST_CONTAINERS_PRIORITY_QUEUE_H + +#include "../../test.h" + +#include +#include +#include + +namespace fennec::test +{ + + inline void fennec_test_containers_priority_queue() { + + using type_t = decltype(rand()); + sequence ref; + priority_queue test; + + size_t n = 50; + for (size_t i = 0; i < n; ++i) { + type_t v = rand(); + test.push(v); + ref.insert(v); + } + + fennec_test_run(test.size(), n); + + for (type_t x : ref) { + assert(x == test.front(), "Failed Priority Queue Test!"); + test.pop(); + } + + std::cout << "passed" << std::endl; + } + +} + +#endif // FENNEC_TEST_CONTAINERS_PRIORITY_QUEUE_H diff --git a/test/tests/containers/test_rdtree.h b/test/tests/containers/test_rdtree.h index 7a08dfa..da471bd 100644 --- a/test/tests/containers/test_rdtree.h +++ b/test/tests/containers/test_rdtree.h @@ -55,11 +55,6 @@ inline void fennec_test_containers_rdtree() { fennec_test_spacer(1); - test.traverse([](size_t i, size_t n) -> uint8_t { - assertf(i + 1 == n, "Tree Traverse Test Failed"); - return traversal_control_continue; - }); - test.erase(0); fennec_test_run(test.empty(), true); diff --git a/test/tests/containers/test_sequence.h b/test/tests/containers/test_sequence.h index 2a74c8a..91ef57e 100644 --- a/test/tests/containers/test_sequence.h +++ b/test/tests/containers/test_sequence.h @@ -39,17 +39,29 @@ namespace test { inline void fennec_test_containers_sequence() { - dynarray ref; - sequence test; + using type_t = decltype(rand()); + dynarray ref; + sequence test; const size_t n = 50; for (size_t i = 0; i < n; ++i) { - size_t v = rand(); + type_t v = rand(); ref.push_back(v); test.insert(v); } - for (size_t v : ref) { + fennec_test_run(test.size(), n); + + type_t p = -1; + size_t c = 0; + for (type_t x : test) { + assertf(x > p, "Failed Sequence Test!"); + p = x; + ++c; + } + fennec_test_run(c, n); + + for (type_t v : ref) { assertf(test.contains(v), "Failed Sequence Test!"); test.erase(v); } diff --git a/test/tests/test_containers.h b/test/tests/test_containers.h index eb63a0d..8ec58b1 100644 --- a/test/tests/test_containers.h +++ b/test/tests/test_containers.h @@ -20,6 +20,7 @@ #define FENNEC_TEST_CONTAINERS_H #include "containers/test_array.h" +#include "containers/test_bintree.h" #include "containers/test_deque.h" #include "containers/test_dynarray.h" #include "containers/test_graph.h" @@ -27,6 +28,7 @@ #include "containers/test_map.h" #include "containers/test_object_pool.h" #include "containers/test_optional.h" +#include "containers/test_priority_queue.h" #include "containers/test_rdtree.h" #include "containers/test_sequence.h" #include "containers/test_set.h" @@ -72,11 +74,21 @@ namespace fennec::test fennec_test_containers_set(); fennec_test_spacer(3); + fennec_test_subheader("bintree tests"); + fennec_test_spacer(2); + fennec_test_containers_bintree(); + fennec_test_spacer(3); + fennec_test_subheader("sequence tests"); fennec_test_spacer(2); fennec_test_containers_sequence(); fennec_test_spacer(3); + fennec_test_subheader("priority queue tests"); + fennec_test_spacer(2); + fennec_test_containers_priority_queue(); + fennec_test_spacer(3); + fennec_test_subheader("map tests"); fennec_test_spacer(2); fennec_test_containers_map();