From 2cb41e143773273be5b588bcceba325b8ffd4c23 Mon Sep 17 00:00:00 2001 From: Medusa Slockbower Date: Thu, 7 Aug 2025 19:03:34 -0400 Subject: [PATCH] - Documented and Debugged containers - Attempted to setup gdb prettywriters --- .gdbinit | 1 + CMakeLists.txt | 12 +- cmake/opengl.cmake | 2 + gdb/list.py | 26 ++ gdb/printers.py | 10 + include/fennec/containers/array.h | 2 +- include/fennec/containers/dynarray.h | 171 ++++++++-- include/fennec/containers/list.h | 494 +++++++++++++++------------ include/fennec/containers/map.h | 74 +++- include/fennec/containers/optional.h | 59 +++- include/fennec/containers/pair.h | 67 ++++ include/fennec/containers/rdtree.h | 173 ++++++---- include/fennec/containers/set.h | 201 ++++++++--- planning/3D_GRAPHICS.md | 15 +- planning/CONTAINERS.md | 14 +- test/CMakeLists.txt | 2 + test/tests/containers/test_list.h | 54 +++ test/tests/containers/test_rdtree.h | 61 ++++ test/tests/test_containers.h | 12 + 19 files changed, 1047 insertions(+), 403 deletions(-) create mode 100644 .gdbinit create mode 100644 gdb/list.py create mode 100644 gdb/printers.py create mode 100644 test/tests/containers/test_list.h create mode 100644 test/tests/containers/test_rdtree.h diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000..8306fea --- /dev/null +++ b/.gdbinit @@ -0,0 +1 @@ +source fennec/gdb/printers.py \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ee8014d..4f908e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,13 @@ add_library(fennec STATIC include/fennec/core/engine.h source/core/engine.cpp include/fennec/core/event.h source/core/event.cpp + include/fennec/core/system.h + + +# SCENE ================================================================================================================ + include/fennec/scene/scene.h + include/fennec/scene/component.h + # CONTAINERS =========================================================================================================== include/fennec/containers/array.h @@ -193,11 +200,6 @@ add_library(fennec STATIC # EXTRA SOURCES ======================================================================================================== ${FENNEC_EXTRA_SOURCES} - include/fennec/scene/scene.h - include/fennec/scene/component.h - include/fennec/core/system.h - include/fennec/renderers/opengl/lib/vertex_array.h - include/fennec/renderers/opengl/texture.h ) add_dependencies(fennec metaprogramming) diff --git a/cmake/opengl.cmake b/cmake/opengl.cmake index f6fded0..2f66ffe 100644 --- a/cmake/opengl.cmake +++ b/cmake/opengl.cmake @@ -42,6 +42,8 @@ if(FENNEC_GRAPHICS_WANT_EGL) include/fennec/platform/opengl/lib/fwd.h include/fennec/platform/opengl/lib/enum.h include/fennec/platform/opengl/lib/buffer.h + include/fennec/platform/opengl/lib/texture.h + include/fennec/platform/opengl/lib/vertex_array.h include/fennec/platform/opengl/egl/context.h source/platform/opengl/egl/context.cpp ) diff --git a/gdb/list.py b/gdb/list.py new file mode 100644 index 0000000..eed36e4 --- /dev/null +++ b/gdb/list.py @@ -0,0 +1,26 @@ +import sys +class ListPrinter: + """Print a fennec::list""" + + class Iterator: + def __init__(self, head): + self.node = head + + def __iter__(self): + return self + + def __next__(self): + if self.node == sys.maxsize: + raise StopIteration + value = self.node['*data'] + self.node = self.node['_data[next]'] + return value + + def __init__(self, val): + self.val = val + + def to_string(self): + return "fennec::list" + + def children(self): + return enumerate(self.Iterator(self.val['_data[_root]'])) \ No newline at end of file diff --git a/gdb/printers.py b/gdb/printers.py new file mode 100644 index 0000000..9bfe5b0 --- /dev/null +++ b/gdb/printers.py @@ -0,0 +1,10 @@ + +from list import ListPrinter + +def lookup_function(val): + if str(val.type) == "fennec::list": + return ListPrinter(val) + return None + + +gdb.pretty_printers.append(lookup_function) \ No newline at end of file diff --git a/include/fennec/containers/array.h b/include/fennec/containers/array.h index b6b6911..183a544 100644 --- a/include/fennec/containers/array.h +++ b/include/fennec/containers/array.h @@ -45,7 +45,7 @@ namespace fennec /// \details /// | Property | Value | /// |:--------:|:-----------------------:| -/// | stable | ✔ | +/// | stable | \emoji heavy_check_mark | /// | access | \f$O(1)\f$ | /// | space | \f$O(N)\f$ | /// diff --git a/include/fennec/containers/dynarray.h b/include/fennec/containers/dynarray.h index 441839d..0a9cfdf 100644 --- a/include/fennec/containers/dynarray.h +++ b/include/fennec/containers/dynarray.h @@ -42,21 +42,27 @@ namespace fennec /// /// \brief wrapper for dynamically sized arrays /// \details -/// | Property | Value | -/// |-----------|--------------| -/// | stable | \emoji anger | -/// | access | \f$O(1)\f$ | -/// | insertion | \f$O(N)\f$ | -/// | deletion | \f$O(N)\f$ | -/// | space | \f$O(N)\f$ | +/// | Property | Value | +/// |-----------|:----------:| +/// | stable | \emoji x | +/// | access | \f$O(1)\f$ | +/// | insertion | \f$O(N)\f$ | +/// | deletion | \f$O(N)\f$ | +/// | space | \f$O(N)\f$ | /// /// \tparam TypeT value type template> class dynarray { public: + +// Definitions ========================================================================================================= + using element_t = TypeT; using alloc_t = Alloc; + +// Constructors ======================================================================================================== + /// /// \brief Default Constructor, initializes an empty allocation. constexpr dynarray() : _alloc(8), _size(0) {} @@ -67,11 +73,18 @@ public: constexpr dynarray(const alloc_t& alloc) : _alloc(8, alloc) , _size(0) { - } /// - /// \brief Sized Allocation, create an allocation with a size of `n` elements, initialized with the default constructor. + /// \brief Alloc Move Constructor, initialize empty allocation with allocator instance. + /// \param alloc An allocator object to copy, for instances where the allocator needs to be initialized with some data. + constexpr dynarray(alloc_t&& alloc) noexcept + : _alloc(8, alloc) + , _size(0) { + } + + /// + /// \brief Sized Allocation, create an allocation of size `n` elements, initialized with the default constructor. constexpr dynarray(size_t n) : _alloc(n) , _size(n) @@ -83,9 +96,9 @@ public: } /// - /// \brief - /// \param n - /// \param alloc + /// \brief Sized Allocation Alloc Constructor, initializes a dynarray with allocator `alloc` and `n` elements using the default constructor. + /// \param n The number of elements + /// \param alloc The allocator object to copy constexpr dynarray(size_t n, const alloc_t& alloc) : _alloc(n, alloc) , _size(n) { @@ -96,7 +109,20 @@ public: } /// - /// \brief Create an allocation of size `n`, with each element constructed using the copy constructor + /// \brief Sized Allocation Alloc Move Constructor, initializes a dynarray with allocator `alloc` and `n` elements using the default constructor. + /// \param n The number of elements + /// \param alloc The allocator object to copy + constexpr dynarray(size_t n, alloc_t&& alloc) + : _alloc(n, alloc) + , _size(n) { + element_t* addr = _alloc.data(); + for(; n > 0; --n, ++addr) { + fennec::construct(addr); + } + } + + /// + /// \brief Sized Allocation Copy Constructor, Create an allocation of size `n` elements, with each element constructed using the copy constructor /// \brief n the number of elements constexpr dynarray(size_t n, const TypeT& val) : _alloc(n) @@ -107,8 +133,15 @@ 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; + /// + /// \brief Emplace Constructor + /// \tparam ArgsT A sequence of argument types + /// \param n The number of objects to create + /// \param args The arguments to create each object with template constexpr dynarray(size_t n, ArgsT&&...args) { element_t* addr = _alloc.data(); @@ -117,49 +150,109 @@ public: } } + /// + /// \brief Default Destructor, destructs all elements and frees the underlying allocation constexpr ~dynarray() { element_t* addr = _alloc.data(); + if (addr == nullptr) return; for(int n = _size; n > 0; --n, ++addr) { fennec::destruct(addr); } } + +// Properties ========================================================================================================== + + /// + /// \returns The size of the dynarray in elements constexpr size_t size() const { return _size; } + /// + /// \returns The current capacity, in elements, of the underlying allocation constexpr size_t capacity() const { return _alloc.capacity(); } + /// + /// \returns True when there are no elements active, otherwise false constexpr bool empty() const { return _size == 0; } + +// Element Access ====================================================================================================== + + /// + /// \brief Array Access Operator + /// \param i The index to access + /// \returns A reference to the element at index `i` constexpr TypeT& operator[](int i) { assertd(i >= 0 and size_t(i) < _size, "Array Out of Bounds"); return _alloc.data()[i]; } + /// + /// \brief Array Access Operator (const) + /// \param i The index to access + /// \returns A const qualified reference to the element at index `i` constexpr const TypeT& operator[](int i) const { assertd(i >= 0 and size_t(i) < _size, "Array Out of Bounds"); return _alloc.data()[i]; } - constexpr TypeT* begin() { return _alloc.data(); } - constexpr TypeT* end() { return begin() + _size; } + /// + /// \returns Reference to the first element in the dynarray + constexpr TypeT& front() { + return this->operator[](0); + } - constexpr const TypeT* begin() const { return _alloc; } - constexpr const TypeT* end() const { return begin() + _size; } + /// + /// \returns A const-qualified reference to the first element in the dynarray + constexpr const TypeT& front() const { + return this->operator[](0); + } + /// + /// \returns A reference to the last element in the dynarray constexpr TypeT& back() { return this->operator[](size() - 1); } + /// + /// \returns A const-qualified reference to the last element in the dynarray constexpr const TypeT& back() const { return this->operator[](size() - 1); } + /// + /// \brief "Iterator" Begin Function + /// \returns A pointer to the first element in the dynarray + constexpr TypeT* begin() { return _alloc.data(); } + + /// + /// \brief "Iterator" End Function + /// \return A pointer to the address after the last element in the dynarray + constexpr TypeT* end() { return begin() + _size; } + + /// + /// \brief Const "Iterator" Begin Function + /// \returns A const qualified pointer to the first element in the dynarray + constexpr const TypeT* begin() const { return _alloc; } + + /// + /// \brief Const "Iterator" End Function + /// \return A const qualified pointer to the address after the last element in the dynarray + constexpr const TypeT* end() const { return begin() + _size; } + + +// Insertion & Deletion ================================================================================================ + + /// + /// \brief Move Insertion + /// \param i index to insert at + /// \param val the value to initialize with constexpr void insert(size_t i, TypeT&& val) { // Grow if the size has reached the capacity of the allocation @@ -180,6 +273,10 @@ public: ++_size; } + /// + /// \brief Copy Insertion + /// \param i index to insert at + /// \param val the value to initialize with constexpr void insert(size_t i, const TypeT& val) { // Grow if the size has reached the capacity of the allocation @@ -190,9 +287,10 @@ public: // Move the data if we are not inserting at the end of the array if((i = min(i, _size)) < _size) { fennec::memmove( - (void*)(_alloc.data() + i) - , (void*)(_alloc.data() + i + 1) - , (_size - i) * sizeof(TypeT)); + (void*)(_alloc.data() + i), + (void*)(_alloc.data() + i + 1), + (_size - i) * sizeof(TypeT) + ); } // Insert the element @@ -200,6 +298,11 @@ public: ++_size; } + /// + /// \brief Emplace Insertion + /// \param i index to insert at + /// \param args Arguments to construct with + /// \tparam ArgsT Argument types template constexpr void emplace(size_t i, ArgsT&&...args) { @@ -221,31 +324,41 @@ public: ++_size; } + /// + /// \brief Push Back Copy + /// \param val Value to initialize with constexpr void push_back(const TypeT& val) { dynarray::insert(_size, val); } + /// + /// \brief Push Back Move + /// \param val Value to initialize with constexpr void push_back(TypeT&& val) { dynarray::insert(_size, fennec::forward(val)); } - constexpr void pop_back() { - fennec::destruct(&_alloc[_size--]); - } - + /// + /// \brief Emplace Back + /// \tparam ArgsT Argument Types + /// \param args Arguments to construct with template constexpr void emplace_back(ArgsT...args) { dynarray::emplace(_size, fennec::forward(args)...); } + /// + /// \brief Erase last element + constexpr void pop_back() { + fennec::destruct(&_alloc[--_size]); + } + + /// + /// \brief Resize the dynarray, invoking the default constructor for all new elements + /// \param n The new size in elements constexpr void resize(size_t n) { _alloc.reallocate(n); - if (_size < n) { - _size = n; - return; - } - while (_size < n) { emplace_back(); } diff --git a/include/fennec/containers/list.h b/include/fennec/containers/list.h index 65df561..7c90504 100644 --- a/include/fennec/containers/list.h +++ b/include/fennec/containers/list.h @@ -45,41 +45,76 @@ namespace fennec /// /// \brief wrapper for lists of elements /// \details -/// | Property | Value | -/// |-----------|--------------| -/// | stable | \emoji anger | -/// | access | \f$O(1)\f$ | -/// | insertion | \f$O(1)\f$ | -/// | deletion | \f$O(1)\f$ | -/// | space | \f$O(N)\f$ | +/// This data-structure behaves like a linked list, but does not use pointers. Instead, it is in-array. This creates the +/// following properties: +/// +/// | Property | Value | +/// |:----------|:-------------------------------------:| +/// | stable | \emoji x | +/// | access | \f$O(N)\f$ or \f$O(1)\f$ (front/back) | +/// | insertion | \f$O(N)\f$ or \f$O(1)\f$ (iterator) | +/// | deletion | \f$O(N)\f$ or \f$O(1)\f$ (iterator) | +/// | space | \f$O(N)\f$ | /// /// \tparam TypeT value type template> struct list { -public: - using alloc_t = typename allocator_traits::template rebind; - using value_t = TypeT; - static constexpr size_t npos = -1; - class iterator; - class const_iterator; +// Definitions ========================================================================================================= private: struct node; public: - using elem_t = node; + using alloc_t = typename allocator_traits::template rebind; + using value_t = TypeT; + using elem_t = node; + static constexpr size_t npos = -1; + class iterator; + class const_iterator; + + +// Constructors ======================================================================================================== + + /// + /// \brief Default Constructor, initializes an empty list. constexpr list() : _table(), _freed(), _root(npos), _last(npos), _size(0) { } - constexpr ~list() = default; + /// + /// \brief Destructor, destructs all elements then releases the allocation. + constexpr ~list() { + for (size_t i = 0; i < capacity(); ++i) { + _table[i].data = nullopt; + } + } - constexpr bool size() const { return _size; } - constexpr bool capacity() const { return _table.capacity(); } - constexpr bool empty() const { return _root == npos; } +// Properties ========================================================================================================== + + /// + /// \returns The size of the list in elements. + constexpr size_t size() const { return _size; } + + /// + /// \returns The capacity of the list in elements. + constexpr size_t capacity() const { return _table.capacity(); } + + /// + /// \returns `true` when the list is empty, `false` otherwise. + constexpr bool empty() const { return _root == npos; } + + +// Access ============================================================================================================== + + /// + /// \brief Array Access Operator + /// \param i Index to access + /// \returns A reference to the element at `i` + /// + /// \details \f$O(N)\f$ constexpr value_t& operator[](int i) { assertd(i >= 0 && size_t(i) < _size, "Index out of Bounds"); size_t n = _walk(i); @@ -87,6 +122,12 @@ public: return *_table[n].data; } + /// + /// \brief Const Array Access Operator + /// \param i Index to access + /// \returns A const-qualified reference to the element at `i` + /// + /// \details \f$O(N)\f$ constexpr const value_t& operator[](int i) const { assertd(i >= 0 && size_t(i) < _size, "Index out of Bounds"); size_t n = _walk(i); @@ -94,216 +135,176 @@ public: return *_table[n].data; } - void insert(const iterator& it, const value_t& x) { - if (size() == capacity()) { - _expand(); - } - - size_t n = it._n; - size_t p = _next_free(); - fennec::construct(&_table[p].data, x); - if (n == npos) { - if (empty()) { - _root = p; - _table[p].next = npos; - _table[p].prev = npos; - } else { - _table[p].prev = n; - _table[p].next = npos; - _last = n; - } - return; - } - _table[p].next = n; - _table[p].prev = _prev(n); - _table[n].prev = p; - ++_size; - } - - void insert(const iterator& it, value_t&& x) { - if (size() == capacity()) { - _expand(); - } - - size_t n = it._n; - size_t p = _next_free(); - fennec::construct(&_table[p].data, fennec::forward(x)); - if (n == npos) { - if (empty()) { - _root = p; - _table[p].next = npos; - _table[p].prev = npos; - } else { - _table[p].prev = n; - _table[p].next = npos; - _last = n; - } - return; - } - _table[p].next = n; - _table[p].prev = _prev(n); - _table[n].prev = p; - ++_size; - } - - void insert(size_t i, const value_t& x) { - assert(i <= size(), "Index out of Bounds"); - if (size() == capacity()) { - _expand(); - } - - size_t n = _walk(min(i, size_t(size() - 1))); - size_t p = _next_free(); - fennec::construct(&_table[p].data, x); - if (n == npos) { - if (empty()) { - _root = p; - _table[p].next = npos; - _table[p].prev = npos; - } else { - _table[p].prev = n; - _table[p].next = npos; - _last = n; - } - return; - } - _table[p].next = n; - _table[p].prev = _prev(n); - _table[n].prev = p; - ++_size; - } - - void insert(size_t i, value_t&& x) { - assert(i <= size(), "Index out of Bounds"); - if (size() == capacity()) { - _expand(); - } - - size_t n = _walk(min(i, size_t(size() - 1))); - size_t p = _next_free(); - fennec::construct(&_table[p].data, fennec::forward(x)); - if (n == npos) { - if (empty()) { - _root = p; - _table[p].next = npos; - _table[p].prev = npos; - } else { - _table[p].prev = n; - _table[p].next = npos; - _last = n; - } - return; - } - _table[p].next = n; - _table[p].prev = _prev(n); - _table[n].prev = p; - ++_size; - } - - template - void emplace(size_t i, ArgsT&&...args) { - assert(i <= size(), "Index out of Bounds"); - if (size() == capacity()) { - _expand(); - } - - size_t n = _walk(min(i, size_t(size() - 1))); - size_t p = _next_free(); - fennec::construct(&_table[p].data, fennec::forward(args)...); - if (n == npos) { - if (empty()) { - _root = p; - _table[p].next = npos; - _table[p].prev = npos; - } else { - _table[p].prev = n; - _table[p].next = npos; - _last = n; - } - return; - } - _table[p].next = n; - _table[p].prev = _prev(n); - _table[n].prev = p; - ++_size; - } - - void push_front(const value_t& x) { - this->insert(0, x); - } - - void push_front(value_t&& x) { - this->insert(0, fennec::forward(x)); - } - - template - void emplace_front(ArgsT...args) { - this->emplace(0, fennec::forward(args)...); - } - - void push_back(const value_t& x) { - this->insert(_size, x); - } - - void push_back(value_t&& x) { - this->insert(_size, fennec::forward(x)); - } - - template - void emplace_back(ArgsT...args) { - this->emplace(_size, fennec::forward(args)...); - } - - void erase(size_t i) { - size_t j = _walk(i); - if (j == npos) return; - if (not _table[j].data) return; - - // Get the prev and next indices - size_t p = _prev(j); - size_t n = _next(j); - - // clear the node - _table[j].data = nullopt; - _table[j].prev = npos; - _table[j].next = npos; - - // Fix prev and next nodes - if (p != npos) _table[p].next = n; - else _root = n; - if (n != npos) _table[n].prev = p; - else _last = p; - - // Mark node as freed - _freed.push_back(j); - } - - void pop_front() { - erase(0); - } - - void pop_back() { - erase(_size - 1); - } - + /// + /// \brief Access Front Element + /// \returns A reference to the first element in the list constexpr value_t& front() { return *_table[_root].data; } + /// + /// \brief Const Access Front Element + /// \returns A const-qualified reference to the first element in the list constexpr const value_t& front() const { return *_table[_root].data; } + /// + /// \brief Access Back Element + /// \returns A reference to the last element in the list constexpr value_t& back() { return *_table[_last].data; } + /// + /// \brief Const Access Back Element + /// \returns A const-qualified reference to the last element in the list constexpr const value_t& back() const { return *_table[_last].data; } + +// Insertions & Deletions ============================================================================================== + + /// + /// \brief Copy Insertion + /// \param it Location to insert at + /// \param x value to copy + /// + /// \details \f$O(1)\f$ + constexpr size_t insert(const iterator& it, const value_t& x) { + return this->_insert(it._n, x); + } + + /// + /// \brief Move Insertion + /// \param it Location to insert at + /// \param x value to move + /// + /// \details \f$O(1)\f$ + constexpr size_t insert(const iterator& it, value_t&& x) { + return this->_insert(it._n, fennec::forward(x)); + } + + /// + /// \brief Copy Insertion + /// \param i Index to insert at + /// \param x value to copy + /// + /// \details \f$O(N)\f$ + constexpr size_t insert(size_t i, const value_t& x) { + assert(i <= size(), "Index out of Bounds"); + + size_t n = _walk(min(i, size_t(size() - 1))); + return this->_insert(n, x); + } + + /// + /// \brief Move Insertion + /// \param i Index to insert at + /// \param x value to move + /// + /// \details \f$O(N)\f$ + constexpr size_t insert(size_t i, value_t&& x) { + assert(i <= size(), "Index out of Bounds"); + + size_t n = _walk(min(i, size_t(size() - 1))); + return this->_insert(n, fennec::forward(x)); + } + + /// + /// \brief Emplace Insertion + /// \tparam ArgsT Argument types + /// \param i Index to insert at + /// \param args Arguments to construct with + /// + /// \details \f$O(N)\f$ + template + constexpr size_t emplace(size_t i, ArgsT&&...args) { + assert(i <= size(), "Index out of Bounds"); + + size_t n = _walk(min(i, size_t(size() - 1))); + return this->_insert(n, fennec::forward(args)...); + } + + /// + /// \brief Push Front Copy + /// \param x Value to copy + constexpr size_t push_front(const value_t& x) { + return this->_insert(_root, x); + } + + /// + /// \brief Push Front Move + /// \param x Value to move + constexpr size_t push_front(value_t&& x) { + return this->_insert(_root, fennec::forward(x)); + } + + /// + /// \brief Emplace Front + /// \param args Arguments to construct with + /// \tparam ArgsT Argument types + template + constexpr size_t emplace_front(ArgsT&&...args) { + return this->_insert(_root, fennec::forward(args)...); + } + + /// + /// \brief Push Back Copy + /// \param x Value to copy + constexpr size_t push_back(const value_t& x) { + return this->_insert(npos, x); + } + + /// + /// \brief Push Back Move + /// \param x Value to move + constexpr size_t push_back(value_t&& x) { + return this->_insert(npos, fennec::forward(x)); + } + + /// + /// \brief Emplace Back + /// \param args Arguments to construct with + /// \tparam ArgsT Argument types + template + constexpr size_t emplace_back(ArgsT&&...args) { + return this->_insert(npos, fennec::forward(args)...); + } + + /// + /// \brief Erase Element + /// \param i Index to erase + constexpr void erase(size_t i) { + assert(i < size(), "Index out of Bounds!"); + size_t n = _walk(i); + _erase(n); + } + + /// + /// \brief Erase Element + /// \param it Location to Erase + constexpr void erase(const iterator& it) { + _erase(it._n); + } + + /// + /// \brief Pop Front, erases first element + constexpr void pop_front() { + _erase(_root); + } + + /// + /// \brief Pop Back, erases first element + constexpr void pop_back() { + _erase(_last); + } + // ITERATOR ============================================================================================================ + /// + /// \brief Iterator Class class iterator { public: ~iterator() { @@ -352,6 +353,8 @@ public: } }; + /// + /// \brief Iterator Class for Const Access class const_iterator { public: ~const_iterator() { @@ -400,18 +403,26 @@ public: } }; + /// + /// \returns An iterator for the first element in the list constexpr iterator begin() { return iterator(this, _root); } + /// + /// \returns An iterator for the end of the list constexpr iterator end() { return iterator(this, npos); } + /// + /// \returns A const iterator for the first element in the list constexpr const_iterator begin() const { return const_iterator(this, _root); } + /// + /// \returns A const iterator for the end of the list constexpr const_iterator end() const { return const_iterator(this, npos); } @@ -458,15 +469,15 @@ private: } }; - size_t _next(size_t n) const { + constexpr size_t _next(size_t n) const { return _table[n].next; } - size_t _prev(size_t n) const { + constexpr size_t _prev(size_t n) const { return _table[n].prev; } - size_t _walk(size_t i) const { + constexpr size_t _walk(size_t i) const { size_t n = _root; if (n == npos) return n; while (i > 0 && n != npos) { @@ -475,15 +486,68 @@ private: return n; } - size_t _next_free() { + constexpr size_t _next_free() { if (not _freed.empty()) { size_t n = _freed.back(); _freed.pop_back(); return n; } - _table[_size]; return _size; } + + template + constexpr size_t _insert(size_t n, ArgsT&&...args) { + if (size() == capacity()) { + _expand(); + } + + size_t i = _next_free(); + ++_size; + fennec::construct(&_table[i].data, fennec::forward(args)...); + + if (_root == npos) { + _table[i].prev = npos; + _table[i].next = npos; + _root = _last = i; + return i; + } + + if (n == npos) { + _table[_last].next = i; + _table[i].prev = _last; + _table[i].next = npos; + _last = i; + return i; + } + + _table[i].prev = _prev(n); + _table[i].next = n; + _table[n].prev = i; + _root = n == _root ? i : _root; + return i; + } + + constexpr void _erase(size_t n) { + if (n == npos) return; + + fennec::destruct(&_table[n].data); + _freed.push_back(n); + --_size; + + size_t prev = _prev(n); + size_t next = _next(n); + + if (prev != npos) { + _table[prev].next = next; + } + + if (next != npos) { + _table[next].prev = prev; + } + + _root = (n == _root) ? next : _root; + _last = (n == _last) ? prev : _last; + } }; } diff --git a/include/fennec/containers/map.h b/include/fennec/containers/map.h index fc41edc..e596653 100644 --- a/include/fennec/containers/map.h +++ b/include/fennec/containers/map.h @@ -25,14 +25,12 @@ namespace fennec { -// TODO: Document - /* Ramblings * * Definitions: * user = Programmer using this data structure * - * The STL unordered-map is very contrived. Some of its functionality encourages younger programmers to use + * The STL maps are very contrived. Some of its functionality encourages younger programmers to use * the exception model. Ideally, I would like this structure to never throw an error with typical use. * * The array access operator is, in my opinion, poorly implemented. I do not think that this operator should handle @@ -40,13 +38,21 @@ namespace fennec * data structure modifies contents by inherently calling operator[]. * * Currently, I am considering implementing this as the following: - * Access will be handled only via operator[]. Return value will be an optional which forces user validation. + * Access will be handled only via operator[]. Return value will be a pointer which forces user validation. * Insertions will be handled only via an insert/emplace function. * Deletions will be handled only via an erase function. */ +/// +/// \brief Map for Pairing Values to Keys +/// \tparam KeyT The Key Type +/// \tparam ValueT The Value Type +/// \tparam Hash The Hash to Use +/// \tparam Alloc The Allocator to Use template, typename Alloc = allocator>> struct map { + +// Definitions ========================================================================================================= public: using key_t = KeyT; using value_t = ValueT; @@ -68,9 +74,24 @@ public: } }; + +// Constructors ======================================================================================================== + + /// + /// \brief Default Constructor, initializes empty map constexpr map() = default; + + /// + /// \brief Destructor, Destructs all elements and releases the allocation constexpr ~map() = default; + +// Access ============================================================================================================== + + /// + /// \brief Key Access Operator + /// \param key Key value to access + /// \returns A pointer to the value associated with `key`, `nullptr` if `key` is not present. constexpr value_t* operator[](const KeyT& key) { union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers pair root; @@ -87,6 +108,10 @@ public: return &_set.at(it)->second; } + /// + /// \brief Key Const Access Operator + /// \param key Key value to access + /// \returns A const-qualified pointer to the value associated with `key`, `nullptr` if `key` is not present. constexpr const value_t* operator[](const KeyT& key) const { union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers pair root; @@ -103,8 +128,13 @@ public: return &_set.at(it)->second; } - template - constexpr value_t* operator[](ArgsT&&...args) { + /// + /// \brief Argument Key Access Operator + /// \tparam ArgT Argument Type + /// \param arg Argument to construct the key with + /// \returns A pointer to the value associated with `key`, `nullptr` if `key` is not present. + template + constexpr value_t* operator[](ArgT&& arg) { union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers pair root; pair val; @@ -112,7 +142,7 @@ public: ~U() { fennec::destruct(&root); } - } trick = { .root = { key_t(fennec::forward(args)...), 0 } }; // Only initialize root + } trick = { .root = { key_t(fennec::forward(arg)), 0 } }; // Only initialize root auto it = _set.find(trick.val); if (it == _set.end()) { return nullptr; @@ -120,8 +150,13 @@ public: return &_set.at(it)->second; } - template - constexpr const value_t* operator[](ArgsT&&...args) const { + /// + /// \brief Argument Key Const Access Operator + /// \tparam ArgT Argument Type + /// \param arg Argument to construct the key with + /// \returns A const-qualified pointer to the value associated with `key`, `nullptr` if `key` is not present. + template + constexpr const value_t* operator[](ArgT&& arg) const { union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers pair root; pair val; @@ -129,7 +164,7 @@ public: ~U() { fennec::destruct(&root); } - } trick = { .root = { key_t(fennec::forward(args)...), 0 } }; // Only initialize root + } trick = { .root = { key_t(fennec::forward(arg)), 0 } }; // Only initialize root auto it = _set.find(trick.val); if (it == _set.end()) { return nullptr; @@ -137,6 +172,12 @@ public: return &_set.at(it)->second; } + +// Insertion & Deletion ================================================================================================ + + /// + /// \brief Key-Value Insertion + /// \param pair a pair containing the key and its value constexpr void insert(elem_t&& pair) { auto it = _set.find(pair); if (it == _set.end()) { @@ -146,11 +187,17 @@ public: _set.insert(fennec::forward(pair)); } + /// + /// \brief Key-Value Insertion + /// \param args Arguments for constructing the key-value pair template constexpr void emplace(ArgsT&&...args) { _set.insert(elem_t(args...)); } + /// + /// \brief Erase a key + /// \param key key to erase constexpr void erase(KeyT&& key) { union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers pair root; @@ -163,11 +210,18 @@ public: _set.erase(trick.val); } + /// + /// \brief Erase a key + /// \param key key to erase constexpr void erase(const KeyT& key) { KeyT val = key; erase(fennec::move(val)); } + /// + /// \brief Argument Erase + /// \tparam ArgsT Argument Types + /// \param args Arguments to construct a key to erase template constexpr void erase(ArgsT&&...args) { union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers diff --git a/include/fennec/containers/optional.h b/include/fennec/containers/optional.h index 5498cc4..9de3d57 100644 --- a/include/fennec/containers/optional.h +++ b/include/fennec/containers/optional.h @@ -37,13 +37,17 @@ constexpr nullopt_t nullopt_v = {}; /// \tparam T template struct optional { + +// Definitions ========================================================================================================= public: -// Constructors ======================================================================================================== using reference_t = T&; using pointer_t = T*; using const_reference_t = T&; using const_pointer_t = const T*; + +// Constructors ======================================================================================================== + /// /// \brief Default Constructor constexpr optional() @@ -59,18 +63,18 @@ public: } /// - /// \brief Fundamental Type Constructor + /// \brief Type Copy Constructor /// \param val the value to initialize the underlying object with - constexpr optional(T val) requires(is_fundamental_v or is_pointer_v) + constexpr optional(const T& val) : _val(val) , _set(true) { } /// - /// \brief Type Copy Constructor + /// \brief Type Move Constructor /// \param val the value to initialize the underlying object with - constexpr optional(const T& val) - : _val(val) + constexpr optional(T&& val) + : _val(fennec::forward(val)) , _set(true) { } @@ -97,6 +101,12 @@ public: opt = nullopt; } + template + constexpr optional(ArgsT&&...args) + : _val(fennec::forward(args)...) + , _set(true) { + } + constexpr ~optional() { if constexpr(is_fundamental_v) { return; @@ -111,7 +121,7 @@ public: /// /// \brief Fundamental Type Assignment - /// \val The value to set with + /// \param val The value to set with constexpr optional& operator=(nullopt_t) { if constexpr(not is_fundamental_v) { if (_set) { @@ -125,7 +135,7 @@ public: /// /// \brief Type Copy Assignment - /// \val The value to set with + /// \param val The value to set with constexpr optional& operator=(const T& val) requires is_copy_constructible_v and is_copy_assignable_v { if (_set) { _val = val; @@ -138,12 +148,12 @@ public: /// /// \brief Type Move Assignment - /// \val The value to set with + /// \param val The value to set with constexpr optional& operator=(T&& val) requires is_move_constructible_v and is_move_assignable_v { if (_set) { - _val = fennec::move(val); + _val = fennec::forward(val); } else { - fennec::construct(&_val, fennec::move(val)); + fennec::construct(&_val, fennec::forward(val)); _set = true; } return *this; @@ -151,7 +161,7 @@ public: /// /// \brief Copy Assignment - /// \val The optional to copy + /// \param opt The optional to copy constexpr optional& operator=(const optional& opt) requires is_copy_constructible_v and is_copy_assignable_v { if (_set != opt._set) { _set = opt._set; @@ -169,7 +179,7 @@ public: /// /// \brief Move Assignment - /// \val The optional to move + /// \param opt The optional to move constexpr optional& operator=(optional&& opt) noexcept requires is_move_constructible_v and is_move_assignable_v { if (_set != opt._set) { _set = opt._set; @@ -185,6 +195,9 @@ public: return *this; } + /// + /// \brief Emplace Assignment + /// \val The optional to move template constexpr T& emplace(ArgsT&&...args) { if (_set) { @@ -196,6 +209,8 @@ public: return _val; } + /// + /// \brief Reset the Optional void reset() { this->operator=(nullopt); } @@ -203,33 +218,51 @@ public: // Operators =========================================================================================================== + /// + /// \brief Implicit Boolean Check, returns `true` when there is a value contained constexpr operator bool() const { return _set; } + /// + /// \returns A pointer to the value, `nullptr` if there is no value constexpr pointer_t operator->() noexcept { return _set ? &_val : nullptr; } + /// + /// \returns A const-qualified pointer to the value, `nullptr` if there is no value constexpr const_pointer_t operator->() const noexcept { return _set ? &_val : nullptr; } + /// + /// \brief Dereference Operator + /// \returns A reference to the value constexpr T& operator*() & noexcept { assertd(_set, "Attempted to reference the value of an unset optional"); return _val; } + /// + /// \brief Const Dereference Operator + /// \returns A const-qualified reference to the value constexpr const T& operator*() const& noexcept { assertd(_set, "Attempted to reference the value of an unset optional"); return _val; } + /// + /// \brief Dereference Operator + /// \returns A reference to the value constexpr T&& operator*() && noexcept { assertd(_set, "Attempted to reference the value of an unset optional"); return _val; } + /// + /// \brief Const Dereference Operator + /// \returns A const-qualified reference to the value constexpr const T&& operator*() const&& noexcept { assertd(_set, "Attempted to reference the value of an unset optional"); return _val; diff --git a/include/fennec/containers/pair.h b/include/fennec/containers/pair.h index 3d0cc30..04a4ce4 100644 --- a/include/fennec/containers/pair.h +++ b/include/fennec/containers/pair.h @@ -28,57 +28,124 @@ namespace fennec // TODO: Document +/// +/// \brief Struct for holding a pair of values +/// \tparam T0 The type of the first value +/// \tparam T1 The type of the second value template struct pair { + +// Constructors ======================================================================================================== + + /// + /// \brief Default Constructor, invokes default constructor for both elements constexpr pair() = default; + + /// + /// \brief Destructor, invokes destructor for both elements constexpr ~pair() = default; + /// + /// \brief Pair Copy Constructor + /// \param x Value to copy for the first element + /// \param y Value to copy for the first element constexpr pair(const T0& x, const T1& y) : first(x) , second(y) { } + /// + /// \brief Pair Move Constructor + /// \param x Value to move for the first element + /// \param y Value to move for the first element constexpr pair(T0&& x, T1&& y) noexcept : first(fennec::forward(x)) , second(fennec::forward(y)) { } + /// + /// \brief Pair Implicit Constructor + /// \param arg1 Value to initialize the first element + /// \param arg2 Value to initialize the first element template constexpr pair(Arg1T&& arg1, Arg2T&& arg2) : first(fennec::forward(arg1)) , second(fennec::forward(arg2)) { } + /// + /// \brief Copy Constructor, copies both elements constexpr pair(const pair&) = default; + + /// + /// \brief Move Constructor, moves both elements constexpr pair(pair&&) noexcept = default; + /// + /// \brief Copy Assignment, copies both elements constexpr pair& operator=(const pair&) = default; + + /// + /// \brief Move Assignment, moves both elements constexpr pair& operator=(pair&&) noexcept = default; + +// Comparison ========================================================================================================== + + /// + /// \brief Equality Operator + /// \param p Pair to compare with + /// \returns `true` when both elements of each pair are equal constexpr bool operator==(const pair& p) const { return first == p.first and second == p.second; } + /// + /// \brief Inequality Operator + /// \param p Pair to compare with + /// \returns `true` when either element of each pair are equal constexpr bool operator!=(const pair& p) const { return first != p.first or second != p.second; } + /// + /// \brief Less Than Operator + /// \param p Pair to compare with + /// \returns lexical comparison of both elements, i.e. returns `true` when the first element is less, or they are + /// equal and the second element is less constexpr bool operator<(const pair& p) const { return first < p.first or (first == p.first and second < p.second); } + /// + /// \brief Less Equal Operator + /// \param p Pair to compare with + /// \returns lexical comparison of both elements, i.e. returns `true` when the first element is less, or they are + /// equal and the second element is less or equal constexpr bool operator<=(const pair& p) const { return first < p.first or (first == p.first and second <= p.second); } + /// + /// \brief Greater Than Operator + /// \param p Pair to compare with + /// \returns lexical comparison of both elements, i.e. returns `true` when the first element is greater, or they are + /// equal and the second element is greater constexpr bool operator>(const pair& p) const { return first > p.first or (first == p.first and second > p.second); } + /// + /// \brief Greater Equal Operator + /// \param p Pair to compare with + /// \returns lexical comparison of both elements, i.e. returns `true` when the first element is greater, or they are + /// equal and the second element is greater or equal constexpr bool operator>=(const pair& p) const { return first > p.first or (first == p.first and second >= p.second); } +// Members ============================================================================================================= + T0 first; T1 second; }; diff --git a/include/fennec/containers/rdtree.h b/include/fennec/containers/rdtree.h index ddc3997..abf1a7b 100644 --- a/include/fennec/containers/rdtree.h +++ b/include/fennec/containers/rdtree.h @@ -26,9 +26,15 @@ namespace fennec { +/// +/// \brief Rooted-Directed Tree +/// \tparam TypeT Data type +/// \tparam AllocT Allocator Type template> struct rdtree { -private: + +// Definitions ========================================================================================================= +protected: struct node; public: @@ -63,7 +69,7 @@ public: template explicit constexpr rdtree(ArgsT&&...args) : _data(), _freed(), _size(1) { - _data.callocate(10); + _data.callocate(8); fennec::construct(&_data[0], npos, npos, npos, npos, fennec::forward(args)...); } @@ -78,51 +84,84 @@ public: // Assignment ========================================================================================================== - friend constexpr rdtree& operator=(rdtree& lhs, const rdtree& rhs) { - for (value_t* it : lhs._data) { + constexpr rdtree& operator=(const rdtree& rhs) { + for (value_t* it : this->_data) { fennec::destruct(it); } - lhs._data = rhs._data; - lhs._freed = rhs._freed; - lhs._size = rhs._size; - return lhs; + _data = rhs._data; + _freed = rhs._freed; + _size = rhs._size; + return *this; } - friend constexpr rdtree& operator=(rdtree& lhs, rdtree&& rhs) noexcept { - for (value_t* it : lhs._data) { + constexpr rdtree& operator=(rdtree&& rhs) noexcept { + for (value_t* it : _data) { fennec::destruct(it); } - lhs._data = fennec::move(rhs._data); - lhs._freed = fennec::move(rhs._freed); - lhs._size = rhs._size; - return lhs; + _data = fennec::move(rhs._data); + _freed = fennec::move(rhs._freed); + _size = rhs._size; + return *this; + } + + +// Properties ========================================================================================================== + + constexpr size_t size() const { + return _size; + } + + constexpr size_t capacity() const { + return _data.capacity(); + } + + constexpr bool empty() const { + return _size == 0; } // Access ============================================================================================================== + /// + /// \param i The id of the node to check + /// \returns The id of the parent node constexpr size_t parent(size_t i) const { - return _data[i].parent; + return i == npos ? npos : _data[i].parent; } + /// + /// \param i The id of the node to check + /// \returns The id of the child node constexpr size_t child(size_t i) const { - return _data[i].child; + return i == npos ? npos : _data[i].child; } + /// + /// \param i The id of the node to check + /// \returns The id of the next node constexpr size_t next(size_t i) const { - return _data[i].next; + return i == npos ? npos : _data[i].next; } + /// + /// \param i The id of the node to check + /// \returns The id of the previous node constexpr size_t prev(size_t i) const { - return _data[i].prev; + return i == npos ? npos : _data[i].prev; } + /// + /// \param i The id of the node to access + /// \returns A reference to the value of the node wrapped in an optional constexpr optional& operator[](size_t i) { - return *_data[i].value; + return _data[i].value; } + /// + /// \param i The id of the node to access + /// \returns A const-qualified reference to the value of the node wrapped in an optional constexpr const optional& operator[](size_t i) const { - return *_data[i].value; + return _data[i].value; } @@ -134,21 +173,7 @@ public: /// \param val the value to insert /// \returns the index of the created node constexpr size_t insert(size_t parent, const value_t& val) { - if (parent == npos || _size == 0) { - if (_size == 0) { - fennec::construct(&_data[root], npos, npos, npos, npos, val); - _size = 1; - } else { - _data[root].value = val; - } - return root; - } - - size_t i = _next_free(); - size_t n = child(parent); - _data[parent].child = i; - fennec::construct(&_data[i], parent, npos, n, npos); - return i; + return this->_insert(parent, val); } /// @@ -157,42 +182,14 @@ public: /// \param val the value to insert /// \returns the index of the created node constexpr size_t insert(size_t parent, value_t&& val) { - if (parent == npos || _size == 0) { - if (_size == 0) { - fennec::construct(&_data[root], npos, npos, npos, npos, fennec::forward(val)); - _size = 1; - } else { - _data[root].value = fennec::forward(val); - } - return root; - } - - size_t i = _next_free(); - size_t n = child(parent); - _data[parent].child = i; - fennec::construct(&_data[i], parent, npos, n, npos); - return i; + return this->_insert(parent, fennec::forward(val)); } /// /// \brief Erase a node in the tree and all of it's children /// \param i the index of the node constexpr void erase(size_t i) { - list queue; - if (child(i) != npos) { - queue.push_back(i); - } - - while (not queue.empty()) { - size_t n = queue.front(); queue.pop_front(); - if (next(n) != npos) queue.push_back(next(n)); - if (child(n) != npos) queue.push_back(child(n)); - fennec::destruct(&_data[n]); - _freed.push_back(n); - } - - fennec::destruct(&_data[i]); - _freed.push_back(i); + _erase(i); } @@ -206,13 +203,57 @@ protected: } size_t _next_free() { - size_t next = _size + 1; + size_t next = _size++; if (not _freed.empty()) { next = _freed.back(); _freed.pop_back(); } + if (_size >= capacity()) { + _expand(); + } return next; } + + template + constexpr size_t _insert(size_t p, ArgsT&&...args) { + if (_size == 0) { + fennec::construct(&_data[root], npos, npos, npos, npos, fennec::forward(args)...); + _size = 1; + return root; + } + + if (p == npos) { + _data[root].value = value_t(fennec::forward(args)...); + return root; + } + + size_t i = _next_free(); + size_t n = child(p); + _data[p].child = i; + if (n != npos) _data[n].prev = i; + fennec::construct(&_data[i], p, npos, npos, n, fennec::forward(args)...); + return i; + } + + constexpr void _erase(size_t i) { + list queue; + size_t j = 0; + queue.push_back(child(i)); + while (queue.empty() == false) { + size_t n = queue.front(); queue.pop_front(); + if (n == npos) continue; + queue.push_back(next(n)); + queue.push_back(child(n)); + fennec::destruct(&_data[n]); + _freed.push_back(n); + --_size; + ++j; + } + + fennec::destruct(&_data[i]); + _freed.push_back(i); + --_size; + } }; } diff --git a/include/fennec/containers/set.h b/include/fennec/containers/set.h index ca252e4..548c69d 100644 --- a/include/fennec/containers/set.h +++ b/include/fennec/containers/set.h @@ -31,15 +31,30 @@ namespace fennec { -// TODO: Document - -template, class Equals = equality, class Alloc = allocator> +/// +/// +/// \brief wrapper for sets of elements +/// \details +/// This data-structure behaves like a set, but does not use pointers, instead storing the table in-array +/// +/// | Property | Value | +/// |:----------|:----------:| +/// | stable | \emoji x | +/// | access | \f$O(1)\f$ | +/// | insertion | \f$O(1)\f$ | +/// | deletion | \f$O(1)\f$ | +/// | space | \f$O(1)\f$ | +/// +/// \tparam TypeT The type to contain +template, class Equals = equality, class Alloc = allocator> struct set { + +// Definitions ========================================================================================================= public: - using alloc_t = typename allocator_traits::template rebind; + using alloc_t = typename allocator_traits::template rebind; using hash_t = Hash; using equal_t = Equals; - using elem_t = T; + using elem_t = TypeT; class iterator; static constexpr size_t npos = -1; @@ -54,7 +69,11 @@ private: constexpr ~node() = default; }; +// Constructors ======================================================================================================== public: + + /// + /// \brief Default Constructor, initializes empty set constexpr set() : _alloc() , _hash() @@ -63,6 +82,8 @@ public: , _load(default_load) { }; + /// + /// \brief Hash Copy Constructor, initializes empty set with a hash constexpr set(const hash_t& hash) : _alloc() , _hash(hash) @@ -71,6 +92,8 @@ public: , _load(default_load) { } + /// + /// \brief Hash Move Constructor, initializes empty set with a hash constexpr set(hash_t&& hash) noexcept : _alloc() , _hash(hash) @@ -79,6 +102,8 @@ public: , _load(default_load) { } + /// + /// \brief Alloc Copy Constructor, initializes empty set with an allocator constexpr set(const alloc_t& alloc) : _alloc(alloc) , _hash() @@ -87,6 +112,8 @@ public: , _load(default_load) { } + /// + /// \brief Alloc Move Constructor, initializes empty set with an allocator constexpr set(alloc_t&& alloc) noexcept : _alloc(alloc) , _hash() @@ -95,6 +122,8 @@ public: , _load(default_load) { } + /// + /// \brief Hash Alloc Copy Constructor, initializes empty set with a hash and allocator constexpr set(const hash_t& hash, const alloc_t& alloc) : _alloc(alloc) , _hash(hash) @@ -103,6 +132,8 @@ public: , _load(default_load) { } + /// + /// \brief Hash Copy Alloc Move Constructor, initializes empty set with a hash and allocator constexpr set(const hash_t& hash, alloc_t&& alloc) noexcept : _alloc(alloc) , _hash(hash) @@ -111,6 +142,8 @@ public: , _load(default_load) { } + /// + /// \brief Hash Move Alloc Move Constructor, initializes empty set with a hash and allocator constexpr set(hash_t&& hash, alloc_t&& alloc) noexcept : _alloc(alloc) , _hash(hash) @@ -119,6 +152,8 @@ public: , _load(default_load) { } + /// + /// \brief Hash Move Alloc Copy Constructor, initializes empty set with a hash and allocator constexpr set(hash_t&& hash, const alloc_t& alloc) noexcept : _alloc(alloc) , _hash(hash) @@ -127,6 +162,9 @@ public: , _load(default_load) { } + /// + /// \brief Set Copy Constructor + /// \param set Set to copy constexpr set(const set& set) : _alloc(set._alloc) , _hash(set._hash) @@ -135,6 +173,9 @@ public: , _load(set._load) { } + /// + /// \brief Set Move Constructor + /// \param set Set to move constexpr set(set&& set) noexcept : _alloc(fennec::move(set._alloc)) , _hash(fennec::move(set._hash)) @@ -143,51 +184,36 @@ public: , _load(set._load) { } - constexpr ~set() = default; + /// + /// \brief Destructor, destructs all elements and releases the allocation + constexpr ~set() { + for (size_t i = 0; i < capacity(); ++i) { + _alloc[i].value = nullopt; + } + } + +// Properties ========================================================================================================== + + /// + /// \returns Size of the set in elements constexpr size_t size() const { return _size; } + /// + /// \returns Capacity of the set in elements constexpr size_t capacity() const { return _alloc.capacity(); } - constexpr void insert(elem_t&& val) { - if (_size == 0 or double(_size) / capacity() >= _load) { // expand when full - _expand(); - } - elem_t value = fennec::forward(val); - size_t i = _hash(value) % capacity(); // Initial search index - int psl = 0; - while (_alloc[i].value) { // Search for empty cell - if (_equal(*_alloc[i].value, val)) { // Check to see if this element is already inserted - return; - } - if (psl > _alloc[i].psl) { // When psl is higher, swap - _sumpsl += psl - _alloc[i].psl; - fennec::swap(_alloc[i].psl, psl); - fennec::swap(*_alloc[i].value, value); - } - i = (i + 1) % capacity(); ++psl; - } - _alloc[i].value = fennec::move(value); - _sumpsl += (_alloc[i].psl = psl); - ++_size; - } - - constexpr void insert(const elem_t& val) { - elem_t value = val; // Copy Constructor invoked here - this->insert(fennec::move(value)); // Only invokes moves - } - - template - constexpr void emplace(ArgsT&&...args) { - elem_t value = elem_t(fennec::forward(args)...); // Constructor invoked here - this->insert(fennec::move(value)); // Only invokes moves - } +// Access ============================================================================================================== + /// + /// \brief Find an Element + /// \param val Value to find + /// \returns An iterator at the location of the value constexpr iterator find(const elem_t& val) const { size_t s = _hash(val) % capacity(); // Initial search index int psl = (_size != 0) ? _sumpsl / _size : 0; // Initial psl @@ -229,24 +255,83 @@ public: return iterator(this, npos); } - constexpr elem_t* at(const iterator& it) { - size_t i = it._i; - if (i >= capacity()) return nullptr; - if (not _alloc[i].value) return nullptr; - return &*_alloc[i].value; - } - - constexpr const elem_t* at(const iterator& it) const { - size_t i = it._i; - if (i >= capacity()) return nullopt; - if (not _alloc[i].value) return nullopt; - return &*_alloc[i].value; - } - + /// + /// \brief Check if a set contains a value + /// \param val Value to check + /// \returns `true` if `val` can be found, `false` otherwise constexpr bool contains(const elem_t& val) const { return this->find(val) != end(); } + /// + /// \brief Iterator Access + /// \param it Location to access + /// \returns A pointer to the element, `nullptr` if not found. + /// The value should not be changed in a manner that will change the hash of the element. + constexpr elem_t* at(const iterator& it) { + if (not _alloc[it._i].value) return nullptr; + return &*_alloc[it._i].value; + } + + /// + /// \brief Iterator Const Access + /// \param it Location to access + /// \returns A const-qualified pointer to the element, `nullptr` if not found. + constexpr const elem_t* at(const iterator& it) const { + if (not _alloc[it._i].value) return nullptr; + return &*_alloc[it._i].value; + } + +// Insertion & Deletion ================================================================================================ + + /// + /// \brief Move Insertion + /// \param val Value to insert + constexpr void insert(elem_t&& val) { + if (_size == 0 or double(_size) / capacity() >= _load) { // expand when full + _expand(); + } + + elem_t value = fennec::forward(val); + size_t i = _hash(value) % capacity(); // Initial search index + int psl = 0; + while (_alloc[i].value) { // Search for empty cell + if (_equal(*_alloc[i].value, val)) { // Check to see if this element is already inserted + return; + } + if (psl > _alloc[i].psl) { // When psl is higher, swap + _sumpsl += psl - _alloc[i].psl; + fennec::swap(_alloc[i].psl, psl); + fennec::swap(*_alloc[i].value, value); + } + i = (i + 1) % capacity(); ++psl; + } + _alloc[i].value = fennec::move(value); + _sumpsl += (_alloc[i].psl = psl); + ++_size; + } + + /// + /// \brief Copy Insertion + /// \param val Value to insert + constexpr void insert(const elem_t& val) { + elem_t value = val; // Copy Constructor invoked here + this->insert(fennec::move(value)); // Only invokes moves + } + + /// + /// \brief Emplace Insertion + /// \tparam ArgsT Argument types + /// \param args Arguments to construct with + template + constexpr void emplace(ArgsT&&...args) { + elem_t value = elem_t(fennec::forward(args)...); // Constructor invoked here + this->insert(fennec::move(value)); // Only invokes moves + } + + /// + /// \brief Element Erase + /// \param it Location to erase constexpr void erase(iterator it) { size_t i = it._i; if (i >= capacity()) { @@ -269,6 +354,9 @@ public: } } + /// + /// \brief Element Erase + /// \param val Value to erase constexpr void erase(const elem_t& val) { this->erase(this->find(val)); } @@ -276,6 +364,8 @@ public: // ITERATOR ============================================================================================================ + /// + /// \brief Class for Iterating the Set class iterator { public: constexpr ~iterator() { @@ -303,6 +393,11 @@ public: return *_set->_alloc[_i].value; } + constexpr const elem_t* operator->() const { + if (not _set->_alloc[_i].value) return nullptr; + return &*_set->_alloc[_i].value; + } + constexpr bool operator==(const iterator& it) { return _set == it._set and _i == it._i; } diff --git a/planning/3D_GRAPHICS.md b/planning/3D_GRAPHICS.md index 48c111b..ebc3181 100644 --- a/planning/3D_GRAPHICS.md +++ b/planning/3D_GRAPHICS.md @@ -59,10 +59,17 @@ struct Object } ``` -Textures for 3D rendering are stored in various buffers with sizes of powers of 2. -Ratios of `1:1` and `2:1` are allowed. The `2:1` ratio is specifically for spherical and cylindrical projection. -UVs may be transformed to use a `2:1` as if it were `1:2`. -Cubemaps and 3D textures may only be `1:1`, I would be concerned if you are using any other ratio. +Objects are identified with a manually provided type and ID. Object types include: + - Static (transform does not change after creation) + - Dynamic (transform changes after creation) + - Forward Static (object is forward rendered and its transform does not change after creation) + +A user should never have to specify these and should be automatically generated by the respective components. + +Textures for 3D rendering are stored in various buffers with sizes of powers of 2. Ratios of `1:1` +and `2:1` are allowed. The `2:1` ratio is specifically for spherical and cylindrical projection. UVs +may be transformed to use a `2:1` as if it were `1:2`. +Cubemaps and 3D textures may only be `1:1`. - 8-Bit R Texture `4096, 2048, 1024, 512` (8) - 8-Bit RG Texture `4096, 2048, 1024, 512` (8) diff --git a/planning/CONTAINERS.md b/planning/CONTAINERS.md index 3189d97..b78f532 100644 --- a/planning/CONTAINERS.md +++ b/planning/CONTAINERS.md @@ -35,16 +35,16 @@ Library and Template Library. | any | ❌ | ❌ | | bitset | ❌ | ❌ | | array | ✔ | ✔ | -| dynarray (`std::vector`) | ✔ | ✔ | +| dynarray (`std::vector`) | ⭕ | ⭕ | | list | ✔ | ✔ | | set (`std::unordered_set`) | ✔ | ✔ | -| ordered_set (`std::set`) | ✔ | ✔ | +| ordered_set (`std::set`) | ❌ | ❌ | | map (`std::unordered_map`) | ✔ | ✔ | -| ordered_map (`std::map`) | ✔ | ✔ | -| multiset (`std::unordered_multiset`) | ✔ | ✔ | -| ordered_multiset (`std::multiset`) | ✔ | ✔ | -| multimap (`std::unordered_multimap`) | ✔ | ✔ | -| ordered_multimap (`std::multimap`) | ✔ | ✔ | +| ordered_map (`std::map`) | ❌ | ❌ | +| multiset (`std::unordered_multiset`) | ❌ | ❌ | +| ordered_multiset (`std::multiset`) | ❌ | ❌ | +| multimap (`std::unordered_multimap`) | ❌ | ❌ | +| ordered_multimap (`std::multimap`) | ❌ | ❌ | ### fennec diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e92764e..aff97e5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -32,6 +32,8 @@ add_executable(fennec-test main.cpp tests/containers/test_array.h tests/containers/test_set.h tests/containers/test_map.h + tests/containers/test_rdtree.h + tests/containers/test_list.h ) target_compile_definitions(fennec-test PUBLIC FENNEC_TEST_CWD="${CMAKE_SOURCE_DIR}/bin/${FENNEC_BUILD_NAME}" diff --git a/test/tests/containers/test_list.h b/test/tests/containers/test_list.h new file mode 100644 index 0000000..d0530cc --- /dev/null +++ b/test/tests/containers/test_list.h @@ -0,0 +1,54 @@ +// ===================================================================================================================== +// 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 TS_CONTAINERS_TEST_LIST_H +#define TS_CONTAINERS_TEST_LIST_H + +#include "../../test.h" + +#include + +namespace fennec +{ + +namespace test +{ + +inline void fennec_test_containers_list() { + + list test; + + const size_t n = 10000; + for (size_t i = 0; i < n; ++i) { + const size_t p = rand() % (i + 1); + assertf(test.insert(p, i) == i, "List Construct Test Failed."); + } + + while (test.empty() == false) { + test.pop_back(); + } + + fennec_test_run(test.empty(), true); + +} + +} + +} + +#endif // TS_CONTAINERS_TEST_LIST_H \ No newline at end of file diff --git a/test/tests/containers/test_rdtree.h b/test/tests/containers/test_rdtree.h new file mode 100644 index 0000000..7305b9f --- /dev/null +++ b/test/tests/containers/test_rdtree.h @@ -0,0 +1,61 @@ +// ===================================================================================================================== +// 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_RDTREE_H +#define FENNEC_TEST_CONTAINERS_RDTREE_H + +#include "../../test.h" + +#include + +namespace fennec +{ + +namespace test +{ + +inline void fennec_test_containers_rdtree() { + + rdtree test; + + const size_t n = 10000; + for (size_t i = 0; i < n; ++i) { + const size_t parent = rand() % (i + 1); + const size_t child = rdtree::npos; + const size_t prev = rdtree::npos; + const size_t next = test.child(parent); + size_t l; + assertf((l = test.insert(parent, i)) == i + 1, "Tree Construct Test Failed."); + assertf(test.parent(l) == parent, "Tree Construct Test Failed."); + assertf(test.child(l) == child, "Tree Construct Test Failed."); + assertf(test.prev(l) == prev, "Tree Construct Test Failed."); + assertf(test.next(l) == next, "Tree Construct Test Failed."); + assertf(next == rdtree::npos || test.prev(next) == l, "Tree Construct Test Failed"); + } + + test.erase(0); + + fennec_test_run(test.empty(), true); + +} + +} + +} + +#endif // FENNEC_TEST_CONTAINERS_RDTREE_H \ No newline at end of file diff --git a/test/tests/test_containers.h b/test/tests/test_containers.h index bbbde31..0aaad91 100644 --- a/test/tests/test_containers.h +++ b/test/tests/test_containers.h @@ -21,8 +21,10 @@ #include "containers/test_array.h" #include "containers/test_dynarray.h" +#include "containers/test_list.h" #include "containers/test_map.h" #include "containers/test_optional.h" +#include "containers/test_rdtree.h" #include "containers/test_set.h" namespace fennec::test @@ -45,6 +47,11 @@ namespace fennec::test fennec_test_containers_dynarray(); fennec_test_spacer(3); + fennec_test_subheader("list tests"); + fennec_test_spacer(2); + fennec_test_containers_list(); + fennec_test_spacer(3); + fennec_test_subheader("set tests"); fennec_test_spacer(2); fennec_test_containers_set(); @@ -53,6 +60,11 @@ namespace fennec::test fennec_test_subheader("map tests"); fennec_test_spacer(2); fennec_test_containers_map(); + fennec_test_spacer(3); + + fennec_test_subheader("rdtree tests"); + fennec_test_spacer(2); + fennec_test_containers_rdtree(); // TODO }