// ===================================================================================================================== // 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 . // ===================================================================================================================== /// /// \file list.h /// \brief List of elements /// /// /// \details /// \author Medusa Slockbower /// /// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) /// /// #ifndef FENNEC_CONTAINERS_LIST_H #define FENNEC_CONTAINERS_LIST_H #include #include #include #include #include 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$ | /// /// \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; private: struct node; public: using elem_t = node; constexpr list() : _table(), _freed(), _root(npos), _last(npos), _size(0) { } constexpr ~list() = default; constexpr bool size() const { return _size; } constexpr bool capacity() const { return _table.capacity(); } constexpr bool empty() const { return _root == npos; } constexpr value_t& operator[](int i) { assertd(i >= 0 && size_t(i) < _size, "Index out of Bounds"); size_t n = _walk(i); assertd(n != npos, "Index out of Bounds"); return *_table[n].data; } constexpr const value_t& operator[](int i) const { assertd(i >= 0 && size_t(i) < _size, "Index out of Bounds"); size_t n = _walk(i); assertd(n != npos, "Index out of Bounds"); 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); } constexpr value_t& front() { return *_table[_root].data; } constexpr const value_t& front() const { return *_table[_root].data; } constexpr value_t& back() { return *_table[_last].data; } constexpr const value_t& back() const { return *_table[_last].data; } // ITERATOR ============================================================================================================ class iterator { public: ~iterator() { _list = nullptr; } // prefix operator constexpr friend iterator& operator++(iterator& rhs) { if (rhs._list->_next(rhs._n) < rhs._list->capacity()) { return rhs; } rhs._n = npos; return rhs; } constexpr friend iterator operator++(iterator& lhs, int) { iterator prev = lhs; ++lhs; return prev; } constexpr value_t& operator*() { return *(_list->_table[_n].data); } constexpr value_t* operator->() { return &*(_list->_table[_n].data); } constexpr bool operator==(const iterator& it) { return _list == it._list and _n == it._n; } constexpr bool operator!=(const iterator& it) { return _list != it._list or _n != it._n; } private: list* _list; size_t _n; friend list; iterator(list* ls, size_t n) : _list(ls) , _n(n) { } }; class const_iterator { public: ~const_iterator() { _list = nullptr; } // prefix operator constexpr friend const_iterator& operator++(const_iterator& rhs) { if (rhs._list->_next(rhs._n) < rhs._list->capacity()) { return rhs; } rhs._n = npos; return rhs; } constexpr friend const_iterator operator++(const_iterator& lhs, int) { const_iterator prev = lhs; ++lhs; return prev; } constexpr const value_t& operator*() { return *(_list->_table[_n].data); } constexpr const value_t* operator->() { return &*(_list->_table[_n].data); } constexpr bool operator==(const const_iterator& it) { return _list == it._list and _n == it._n; } constexpr bool operator!=(const const_iterator& it) { return _list != it._list or _n != it._n; } private: const list* _list; size_t _n; friend list; const_iterator(const list* ls, size_t n) : _list(ls) , _n(n) { } }; constexpr iterator begin() { return iterator(this, _root); } constexpr iterator end() { return iterator(this, npos); } constexpr const_iterator begin() const { return const_iterator(this, _root); } constexpr const_iterator end() const { return const_iterator(this, npos); } private: allocation _table; dynarray _freed; size_t _root, _last, _size; friend class iterator; constexpr void _expand() { _table.creallocate(fennec::max(_table.capacity(), size_t(1)) * 2); } struct node { optional data; size_t prev, next; constexpr node() : data() , prev(npos) , next(npos) { } constexpr node(size_t p, size_t n) : data() , prev(p) , next(n) { } constexpr node(size_t p, size_t n, value_t&& val) : data(fennec::forward(val)) , prev(p) , next(n) { } constexpr ~node() = default; constexpr void clear() { data = nullopt; prev = npos; next = npos; } }; size_t _next(size_t n) const { return _table[n].next; } size_t _prev(size_t n) const { return _table[n].prev; } size_t _walk(size_t i) const { size_t n = _root; if (n == npos) return n; while (i > 0 && n != npos) { n = _next(n); --i; } return n; } size_t _next_free() { if (not _freed.empty()) { size_t n = _freed.back(); _freed.pop_back(); return n; } _table[_size]; return _size; } }; } #endif // FENNEC_CONTAINERS_LIST_H