Files
fennec/include/fennec/containers/list.h
2025-12-17 01:11:28 -05:00

661 lines
15 KiB
C++

// =====================================================================================================================
// 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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================
///
/// \file list.h
/// \brief A header containing the definition for a linked list of values
///
///
/// \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 <fennec/containers/dynarray.h>
#include <fennec/containers/list.h>
#include <fennec/containers/optional.h>
#include <fennec/memory/allocator.h>
#include <fennec/math/common.h>
namespace fennec
{
///
///
/// \brief Data Structure defining lists of elements
/// \details
/// 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 | ⛔ |
/// | dynamic | ✅ |
/// | homogenous | ✅ |
/// | distinct | ⛔ |
/// | ordered | ⛔ |
/// | space | \f$O(N)\f$ |
/// | linear | ✅ |
/// | access | \f$O(1)\f$ |
/// | find | \f$O(N)\f$ |
/// | insertion | \f$O(N)\f$ |
/// | deletion | \f$O(N)\f$ |
///
/// \tparam TypeT value type
template<class TypeT, class Alloc = allocator<TypeT>>
struct list {
// Definitions =========================================================================================================
private:
struct node;
public:
/// \brief Alias for the allocator type, rebound to list nodes
using alloc_t = typename allocator_traits<Alloc>::template rebind<node>;
using value_t = TypeT; ///< Alias for the value type
static constexpr size_t npos = -1; ///< Constant representing a non-existant position
class iterator; ///< Iterator type for forward iteration over the list
class const_iterator; ///< Iterator type for forward iteration over a constant list
// Constructors & Destructor ===========================================================================================
/// \name Constructors & Destructor
/// @{
///
/// \brief Default Constructor, initializes an empty list.
constexpr list()
: _table(), _freed(), _root(npos), _last(npos), _size(0) {
}
///
/// \brief Copy Constructor, copies all elements in `l` with optimized layout
/// \param l The list to copy
constexpr list(const list& l)
: list() {
for (const value_t& it : l) {
this->push_back(it);
}
}
///
/// \brief Move Constructor, takes ownership of the list
/// \param l The list to move
constexpr list(list&& l) noexcept
: _table(fennec::move(l._table))
, _freed(fennec::move(l._freed))
, _root(l._root)
, _last(l._last)
, _size(l._size) {
}
///
/// \brief Destructor, destructs all elements then releases the allocation.
constexpr ~list() {
for (size_t i = 0; i < capacity(); ++i) {
_table[i].value = nullopt;
}
}
/// @}
// Assignment ==========================================================================================================
/// \name Assignment
/// @{
///
/// \brief Copy Assignment Operator
/// \param l the list to copy
/// \returns `this` after having copied all elements of `l`
constexpr list& operator=(const list& l) {
this->clear();
for (const value_t& it : l) {
this->push_back(it);
}
return *this;
}
///
/// \brief Move Assignment Operator
/// \param l the list to copy
/// \returns `this` after having taken ownership over the contents of `l`
constexpr list& operator=(list&& l) noexcept {
this->clear();
_table = fennec::move(l._table);
_freed = fennec::move(l._freed);
_root = l._root; _last = l._last;
_size = l._size;
return *this;
}
/// @}
// Properties ==========================================================================================================
/// \name 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 ==============================================================================================================
/// \name 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);
assertd(n != npos, "Index out of Bounds");
return *_table[n].value;
}
///
/// \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);
assertd(n != npos, "Index out of Bounds");
return *_table[n].value;
}
///
/// \brief Access Front Element
/// \returns A reference to the first element in the list
constexpr value_t& front() {
return *_table[_root].value;
}
///
/// \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].value;
}
///
/// \brief Access Back Element
/// \returns A reference to the last element in the list
constexpr value_t& back() {
return *_table[_last].value;
}
///
/// \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].value;
}
/// @}
// Modifiers ===========================================================================================================
/// \name Modifiers
/// @{
///
/// \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<value_t>(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<value_t>(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<typename...ArgsT>
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<ArgsT>(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<value_t>(x));
}
///
/// \brief Emplace Front
/// \param args Arguments to construct with
/// \tparam ArgsT Argument types
template<typename...ArgsT>
constexpr size_t emplace_front(ArgsT&&...args) {
return this->_insert(_root, fennec::forward<ArgsT>(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<value_t>(x));
}
///
/// \brief Emplace Back
/// \param args Arguments to construct with
/// \tparam ArgsT Argument types
template<typename...ArgsT>
constexpr size_t emplace_back(ArgsT&&...args) {
return this->_insert(npos, fennec::forward<ArgsT>(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);
}
///
/// \brief Clears the list, destructing all elements in order
constexpr void clear() {
size_t i = _root;
while (i != npos) {
fennec::destruct(&_table[i]);
i = this->_next(i);
}
_table.deallocate();
}
/// @}
// ITERATOR ============================================================================================================
/// \name Iteration
/// @{
///
/// \brief C++ Iterator Specification `begin()`
/// \returns An iterator for the first element in the list
constexpr iterator begin() {
return iterator(this, _root);
}
///
/// \brief C++ Iterator Specification `end()`
/// \returns An iterator for the end of the list
constexpr iterator end() {
return iterator(this, npos);
}
///
/// \brief Const C++ Iterator Specification `begin()`
/// \returns A const iterator for the first element in the list
constexpr const_iterator begin() const {
return const_iterator(this, _root);
}
///
/// \brief Const C++ Iterator Specification `end()`
/// \returns A const iterator for the end of the list
constexpr const_iterator end() const {
return const_iterator(this, npos);
}
/// @}
///
/// \brief Iterator Class
class iterator {
public:
~iterator() {
_list = nullptr;
}
// prefix operator
constexpr friend iterator& operator++(iterator& rhs) {
rhs._n = rhs._list->_next(rhs._n);
return rhs;
}
constexpr friend iterator operator++(iterator& lhs, int) {
iterator prev = lhs;
++lhs;
return prev;
}
constexpr value_t& operator*() {
return *(_list->_table[_n].value);
}
constexpr value_t* operator->() {
return &*(_list->_table[_n].value);
}
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) {
}
};
///
/// \brief Iterator Class for Const Access
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].value);
}
constexpr const value_t* operator->() {
return &*(_list->_table[_n].value);
}
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) {
}
};
private:
allocation<node, alloc_t> _table;
dynarray<size_t> _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<value_t> value;
size_t prev, next;
constexpr node()
: value()
, prev(npos)
, next(npos) {
}
constexpr node(size_t p, size_t n)
: value()
, prev(p)
, next(n) {
}
constexpr node(size_t p, size_t n, value_t&& val)
: value(fennec::forward<value_t>(val))
, prev(p)
, next(n) {
}
constexpr ~node() = default;
constexpr void clear() {
value = nullopt;
prev = npos;
next = npos;
}
};
constexpr size_t _next(size_t n) const {
return _table[n].next;
}
constexpr size_t _prev(size_t n) const {
return _table[n].prev;
}
constexpr 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;
}
constexpr size_t _next_free() {
if (not _freed.empty()) {
size_t n = _freed.back();
_freed.pop_back();
return n;
}
return _size;
}
template<typename...ArgsT>
constexpr size_t _insert(size_t n, ArgsT&&...args) {
if (size() == capacity()) {
_expand();
}
size_t i = _next_free();
++_size;
fennec::construct(&_table[i].value, fennec::forward<ArgsT>(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].value);
_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;
}
};
}
#endif // FENNEC_CONTAINERS_LIST_H