// =====================================================================================================================
// 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