- Renamed fproc -> langproc (I'll probably never settle on a naming convention for this) - Refactored set to use median psl
492 lines
10 KiB
C++
492 lines
10 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 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 <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 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<class TypeT, class Alloc = allocator<TypeT>>
|
|
struct list {
|
|
public:
|
|
using alloc_t = typename allocator_traits<Alloc>::template rebind<TypeT>;
|
|
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<value_t>(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<value_t>(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<typename...ArgsT>
|
|
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<ArgsT>(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<value_t>(x));
|
|
}
|
|
|
|
template<typename...ArgsT>
|
|
void emplace_front(ArgsT...args) {
|
|
this->emplace(0, fennec::forward<ArgsT>(args)...);
|
|
}
|
|
|
|
void push_back(const value_t& x) {
|
|
this->insert(_size, x);
|
|
}
|
|
|
|
void push_back(value_t&& x) {
|
|
this->insert(_size, fennec::forward<value_t>(x));
|
|
}
|
|
|
|
template<typename...ArgsT>
|
|
void emplace_back(ArgsT...args) {
|
|
this->emplace(_size, fennec::forward<ArgsT>(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<elem_t, 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> 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<value_t>(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
|