663 lines
16 KiB
C++
663 lines
16 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 rdtree.h
|
|
/// \brief A header containing the definition for a tree with a root and directed edges
|
|
///
|
|
///
|
|
/// \details
|
|
/// \author Medusa Slockbower
|
|
///
|
|
/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
|
|
///
|
|
///
|
|
|
|
#ifndef FENNEC_CONTAINERS_RDTREE_H
|
|
#define FENNEC_CONTAINERS_RDTREE_H
|
|
|
|
#include <fennec/containers/list.h>
|
|
#include <fennec/containers/optional.h>
|
|
#include <fennec/containers/traversal.h>
|
|
#include <fennec/memory/allocator.h>
|
|
|
|
namespace fennec
|
|
{
|
|
|
|
///
|
|
/// \brief Rooted-Directed Tree
|
|
/// \tparam TypeT Data type
|
|
/// \tparam AllocT Allocator Type
|
|
template<typename TypeT, typename AllocT = allocator<TypeT>>
|
|
struct rdtree {
|
|
|
|
// Definitions =========================================================================================================
|
|
protected:
|
|
struct node;
|
|
|
|
public:
|
|
using value_t = TypeT;
|
|
using alloc_t = typename allocator_traits<AllocT>::template rebind<node>;
|
|
static constexpr size_t root = 0;
|
|
static constexpr size_t npos = -1;
|
|
|
|
protected:
|
|
struct node {
|
|
optional<value_t> value;
|
|
size_t parent, child, prev, next;
|
|
size_t depth, num_children;
|
|
|
|
constexpr node()
|
|
: value(nullopt)
|
|
, parent(npos), child(npos)
|
|
, prev(npos), next(npos)
|
|
, depth(0), num_children(0) {
|
|
}
|
|
|
|
template<typename...ArgsT>
|
|
constexpr node(size_t p, size_t c, size_t v, size_t n, size_t d, ArgsT&&...args)
|
|
: value(fennec::forward<ArgsT>(args)...)
|
|
, parent(p), child(c), prev(v), next(n)
|
|
, depth(d), num_children(0) {
|
|
}
|
|
|
|
constexpr ~node() {
|
|
parent = npos;
|
|
child = npos;
|
|
prev = npos;
|
|
next = npos;
|
|
depth = 0;
|
|
num_children = 0;
|
|
}
|
|
};
|
|
|
|
public:
|
|
|
|
// Constructors ========================================================================================================
|
|
|
|
/// \name Constructors & Destructor
|
|
/// @{
|
|
|
|
///
|
|
/// \brief Root Constructor, constructs the root node of the tree
|
|
/// \tparam ArgsT The argument types
|
|
/// \param args The arguments to construct the root with
|
|
template<typename...ArgsT>
|
|
explicit constexpr rdtree(ArgsT&&...args)
|
|
: _table(), _freed(), _size(1) {
|
|
_table.creallocate(8);
|
|
fennec::construct(&_table[0], npos, npos, npos, npos, 0, fennec::forward<ArgsT>(args)...);
|
|
}
|
|
|
|
///
|
|
/// \brief Copy Constructor, copies the contents of `tree`
|
|
/// \param tree the rdtree to copy
|
|
constexpr rdtree(const rdtree& tree)
|
|
: _table(tree._table), _freed(tree._freed), _size(tree._size) {
|
|
}
|
|
|
|
///
|
|
/// \brief Move Constructor, takes ownership over the contents of `tree`
|
|
/// \param tree the rdtree to move
|
|
constexpr rdtree(rdtree&& tree) noexcept
|
|
: _table(fennec::move(tree._table)), _freed(fennec::move(tree._freed)), _size(tree._size) {
|
|
}
|
|
|
|
/// @}
|
|
|
|
|
|
// Assignment ==========================================================================================================
|
|
|
|
/// \name Assignment
|
|
/// @{
|
|
|
|
///
|
|
/// \brief Copy Assignment Operator
|
|
/// \param rhs the rdtree to copy
|
|
/// \returns `this` after copying the contents of `rhs`
|
|
constexpr rdtree& operator=(const rdtree& rhs) {
|
|
for (value_t* it : this->_table) {
|
|
fennec::destruct(it);
|
|
}
|
|
_table = rhs._table;
|
|
_freed = rhs._freed;
|
|
_size = rhs._size;
|
|
return *this;
|
|
}
|
|
|
|
///
|
|
/// \brief Move Assignment Operator
|
|
/// \param rhs the rdtree to move
|
|
/// \returns `this` after taking ownership over the contents of `rhs`
|
|
constexpr rdtree& operator=(rdtree&& rhs) noexcept {
|
|
for (value_t* it : _table) {
|
|
fennec::destruct(it);
|
|
}
|
|
_table = fennec::move(rhs._table);
|
|
_freed = fennec::move(rhs._freed);
|
|
_size = rhs._size;
|
|
return *this;
|
|
}
|
|
|
|
/// @}
|
|
|
|
// Properties ==========================================================================================================
|
|
|
|
/// \name Properties
|
|
/// @{
|
|
|
|
///
|
|
/// \returns The number of nodes in the tree
|
|
constexpr size_t size() const {
|
|
return _size;
|
|
}
|
|
|
|
///
|
|
/// \returns The capacity of the underlying allocation
|
|
constexpr size_t capacity() const {
|
|
return _table.capacity();
|
|
}
|
|
|
|
///
|
|
/// \returns `true` when there are no nodes in the tree, `false` otherwise
|
|
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 {
|
|
if (i >= _table.capacity()) return npos;
|
|
return i == npos ? npos : _table[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, size_t n = 0) const {
|
|
if (i >= _table.capacity()) return npos;
|
|
size_t c = i == npos ? npos : _table[i].child;
|
|
if (n != 0)
|
|
return next(c, n == npos ? npos : n - 1);
|
|
return c;
|
|
}
|
|
|
|
///
|
|
/// \param i The id of the node to check
|
|
/// \returns The id of the next node
|
|
constexpr size_t next(size_t i, size_t n = 0) const {
|
|
if (i >= _table.capacity()) return npos;
|
|
if (i == npos) {
|
|
return npos;
|
|
}
|
|
|
|
size_t org = i;
|
|
size_t nxt = _table[i].next;
|
|
while (nxt != npos) {
|
|
i = nxt;
|
|
nxt = _table[i].next;
|
|
if (n != npos) {
|
|
if (n-- == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return i == org && n != npos ? npos : i;
|
|
}
|
|
|
|
///
|
|
/// \param i The id of the node to check
|
|
/// \returns The id of the previous node
|
|
constexpr size_t prev(size_t i, size_t n = 0) const {
|
|
if (i >= _table.capacity()) return npos;
|
|
if (i == npos) {
|
|
return npos;
|
|
}
|
|
|
|
size_t org = i;
|
|
size_t prv = _table[i].prev;
|
|
while (prv != npos) {
|
|
i = prv;
|
|
prv = _table[i].prev;
|
|
if (n != npos) {
|
|
if (n-- == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return i == org && n != npos ? npos : i;
|
|
}
|
|
|
|
///
|
|
/// \param i the node to start at
|
|
/// \returns the left-most child of node `i`
|
|
constexpr size_t left_most(size_t i) const {
|
|
if (i >= _table.capacity()) return npos;
|
|
size_t n = i;
|
|
if ((n = child(n)) == npos) {
|
|
return i;
|
|
}
|
|
while (true) {
|
|
size_t p = n;
|
|
if ((n = child(n)) == npos) {
|
|
return p;
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
/// \param i the node to start at
|
|
/// \returns the right-most child of node `i`
|
|
constexpr size_t right_most(size_t i) const {
|
|
if (i >= _table.capacity()) return npos;
|
|
if ((i = child(i)) == npos) {
|
|
return npos;
|
|
}
|
|
while (true) {
|
|
size_t n;
|
|
while ((n = next(i)) != npos) {
|
|
i = n;
|
|
}
|
|
n = i;
|
|
if ((i = child(i)) == npos) {
|
|
return n;
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
/// \param i The id of the node to check
|
|
/// \returns The depth of the node
|
|
constexpr size_t depth(size_t i) const {
|
|
if (i >= _table.capacity()) return npos;
|
|
return i == npos ? npos : _table[i].depth;
|
|
}
|
|
|
|
///
|
|
/// \param i The id of the node to check
|
|
/// \returns The number of children the node has
|
|
constexpr size_t num_children(size_t i) const {
|
|
if (i >= _table.capacity()) return 0;
|
|
return i == npos ? 0 : _table[i].num_children;
|
|
}
|
|
|
|
///
|
|
/// \returns The next node id were `insert` or `emplace` to be called
|
|
constexpr size_t next_id() const {
|
|
size_t i = _size;
|
|
if (not _freed.empty()) {
|
|
i = _freed.front();
|
|
}
|
|
return i;
|
|
}
|
|
|
|
///
|
|
/// \param i The id of the node to access
|
|
/// \returns A reference to the value of the node wrapped in an optional
|
|
constexpr value_t* operator[](size_t i) {
|
|
auto& it = _table[i].value;
|
|
if (it) {
|
|
return &*_table[i].value;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
///
|
|
/// \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 value_t* operator[](size_t i) const {
|
|
const auto& it = _table[i].value;
|
|
if (it) {
|
|
return &*_table[i].value;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
// Insertion & Deletion ================================================================================================
|
|
|
|
///
|
|
/// \brief Insertion, creates a node in the tree with parent `parent`
|
|
/// \param parent the parent node, if `npos` sets the value of the root node
|
|
/// \param next the next node, as an index relative to the parent
|
|
/// \param val the value to insert
|
|
/// \returns the index of the created node
|
|
constexpr size_t insert(size_t parent, size_t next, const value_t& val) {
|
|
return this->_insert(parent, next, val);
|
|
}
|
|
|
|
///
|
|
/// \brief Insertion, creates a node in the tree with parent `parent`
|
|
/// \param parent the parent node, if `npos` sets the value of the root node
|
|
/// \param next the next node, as an index relative to the parent
|
|
/// \param val the value to insert
|
|
/// \returns the index of the created node
|
|
constexpr size_t insert(size_t parent, size_t next, value_t&& val) {
|
|
return this->_insert(parent, next, fennec::forward<value_t>(val));
|
|
}
|
|
|
|
///
|
|
/// \brief Insertion, creates a node in the tree with parent `parent`
|
|
/// \param parent the parent node, if `npos` sets the value of the root node
|
|
/// \param next the next node, as an index relative to the parent
|
|
/// \param args the args to construct the value to insert
|
|
/// \returns the index of the created node
|
|
template<typename...ArgsT>
|
|
constexpr size_t emplace(size_t parent, size_t next, ArgsT&&...args) {
|
|
return this->_insert(parent, next, fennec::forward<ArgsT>(args)...);
|
|
}
|
|
|
|
///
|
|
/// \brief Swap two nodes
|
|
/// \param i0 The id of the first node
|
|
/// \param i1 The id of the second node
|
|
constexpr void swap(size_t i0, size_t i1) {
|
|
assertf(i0 != root and i1 != root, "Cannot Swap With Root");
|
|
|
|
size_t p0 = parent(i0);
|
|
size_t p1 = parent(i1);
|
|
|
|
fennec::swap(_table[i0].parent, _table[i1].parent);
|
|
fennec::swap(_table[i0].child, _table[i1].child);
|
|
fennec::swap(_table[i0].next, _table[i1].next);
|
|
fennec::swap(_table[i0].prev, _table[i1].prev);
|
|
fennec::swap(_table[i0].depth, _table[i1].depth);
|
|
fennec::swap(_table[i0].num_children, _table[i1].num_children);
|
|
|
|
if (child(p0) == i0) _table[p0].child = i1;
|
|
if (child(p1) == i1) _table[p1].child = i0;
|
|
}
|
|
|
|
|
|
///
|
|
/// \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) {
|
|
_erase(i);
|
|
}
|
|
|
|
|
|
// Traversal ===========================================================================================================
|
|
|
|
///
|
|
/// \brief Traverse the tree using a specified order and visiting functor
|
|
///
|
|
/// \details
|
|
/// The visitor should accept a reference to a value of type `TypeT` and a `size_t` which contains the node's id.
|
|
/// The visitor should return one of the following values in the `fennec::traversal_control_` enum
|
|
///
|
|
/// \tparam OrderT The order with which to traverse the tree.
|
|
/// \tparam VisitorT The visitor, should fulfill the signature `uint8_t visit(TypeT&, size_t)`
|
|
/// \param visit The visiting object
|
|
/// \param i The node to start at
|
|
template<typename OrderT, typename VisitorT>
|
|
constexpr void traverse(VisitorT&& visit, size_t i = root) {
|
|
OrderT order;
|
|
i = order(*this, i);
|
|
while (i != npos) {
|
|
uint8_t mode = traversal_control_continue;
|
|
if (_table[i].value) {
|
|
mode = visit(*_table[i].value, i);
|
|
}
|
|
if (mode == traversal_control_break) {
|
|
break;
|
|
}
|
|
i = order[*this, i, mode];
|
|
}
|
|
}
|
|
|
|
struct breadth_first {
|
|
list<size_t> visit;
|
|
size_t head;
|
|
|
|
constexpr size_t operator()(const rdtree&, size_t start) {
|
|
head = start;
|
|
return start;
|
|
}
|
|
|
|
constexpr size_t operator[](const rdtree& tree, size_t node, uint8_t mode) {
|
|
if (node == npos) {
|
|
return npos;
|
|
}
|
|
|
|
size_t nxt = tree.next(node);
|
|
size_t chd = tree.next(node);
|
|
|
|
if (nxt != npos && node != head) {
|
|
visit.push_front(nxt);
|
|
}
|
|
|
|
if (chd != npos && mode != traversal_control_jump_over) {
|
|
visit.push_back(chd);
|
|
}
|
|
|
|
if (not visit.empty()) {
|
|
node = visit.front();
|
|
visit.pop_front();
|
|
} else {
|
|
node = npos;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
};
|
|
|
|
struct pre_order {
|
|
list<size_t> visit;
|
|
size_t head;
|
|
|
|
constexpr size_t operator()(const rdtree&, size_t start) {
|
|
head = start;
|
|
return start;
|
|
}
|
|
|
|
constexpr size_t operator[](const rdtree& tree, size_t node, uint8_t mode) {
|
|
if (node == npos) {
|
|
return npos;
|
|
}
|
|
|
|
size_t nxt = tree.next(node);
|
|
size_t chd = tree.child(node);
|
|
|
|
if (nxt != npos && node != head) {
|
|
visit.push_front(nxt);
|
|
}
|
|
|
|
if (chd != npos && mode != traversal_control_jump_over) {
|
|
visit.push_front(chd);
|
|
}
|
|
|
|
if (not visit.empty()) {
|
|
node = visit.front();
|
|
visit.pop_front();
|
|
} else {
|
|
node = npos;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
};
|
|
|
|
struct in_order {
|
|
list<size_t> visit;
|
|
size_t head;
|
|
|
|
constexpr size_t operator()(const rdtree& tree, size_t start) {
|
|
head = start;
|
|
return tree.left_most(start);
|
|
}
|
|
|
|
constexpr size_t operator[](const rdtree& tree, size_t node, uint8_t) {
|
|
if (node == npos) {
|
|
return npos;
|
|
}
|
|
|
|
size_t prnt = tree.parent(node);
|
|
size_t next = tree.next(node);
|
|
if (node != head) {
|
|
if (tree.child(prnt) == node) {
|
|
visit.push_back(prnt);
|
|
if (next != npos) {
|
|
visit.push_back(tree.left_most(next));
|
|
}
|
|
} else if (next != npos) {
|
|
visit.push_front(tree.left_most(next));
|
|
}
|
|
}
|
|
|
|
if (not visit.empty()) {
|
|
node = visit.front();
|
|
visit.pop_front();
|
|
} else {
|
|
node = npos;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
};
|
|
|
|
struct post_order {
|
|
list<size_t> visit;
|
|
size_t head;
|
|
|
|
constexpr size_t operator()(const rdtree& tree, size_t start) {
|
|
head = start;
|
|
return tree.left_most(start);
|
|
}
|
|
|
|
constexpr size_t operator[](const rdtree& tree, size_t node, uint8_t) {
|
|
if (node == npos) {
|
|
return npos;
|
|
}
|
|
|
|
size_t prnt = tree.parent(node);
|
|
size_t next = tree.next(node);
|
|
|
|
if (node != head) {
|
|
if (next != npos) {
|
|
visit.push_front(tree.left_most(next));
|
|
} else {
|
|
visit.push_front(prnt);
|
|
}
|
|
}
|
|
|
|
if (not visit.empty()) {
|
|
node = visit.front();
|
|
visit.pop_front();
|
|
} else {
|
|
node = npos;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
};
|
|
|
|
|
|
protected:
|
|
allocation<node, alloc_t> _table;
|
|
list<size_t> _freed;
|
|
size_t _size;
|
|
|
|
void _expand() {
|
|
_table.creallocate(_table.capacity() * 2);
|
|
}
|
|
|
|
size_t _next_free() {
|
|
size_t next = _size;
|
|
if (not _freed.empty()) {
|
|
next = _freed.front();
|
|
_freed.pop_front();
|
|
}
|
|
if (_size >= capacity()) {
|
|
_expand();
|
|
}
|
|
++_size;
|
|
return next;
|
|
}
|
|
|
|
template<typename...ArgsT>
|
|
constexpr size_t _insert(size_t p, size_t n, ArgsT&&...args) {
|
|
if (_size == 0) {
|
|
fennec::construct(&_table[root], npos, npos, npos, npos, 0, fennec::forward<ArgsT>(args)...);
|
|
_size = 1;
|
|
return root;
|
|
}
|
|
|
|
if (p == npos) {
|
|
_table[root].value = value_t(fennec::forward<ArgsT>(args)...);
|
|
_size = _size == 0 ? 1 : _size;
|
|
return root;
|
|
}
|
|
|
|
size_t idx = _next_free();
|
|
size_t nxt = child(p, n);
|
|
size_t prv = n == npos ? npos : prev(n);
|
|
|
|
++_table[p].num_children;
|
|
if ((nxt == child(p) && n != npos) || nxt == npos) {
|
|
_table[p].child = idx;
|
|
}
|
|
|
|
if (n == npos) {
|
|
if (nxt != npos) {
|
|
_table[nxt].next = idx;
|
|
}
|
|
fennec::construct(&_table[idx], p, npos, nxt, npos, depth(p) + 1, fennec::forward<ArgsT>(args)...);
|
|
} else {
|
|
if (nxt != npos) {
|
|
_table[nxt].prev = idx;
|
|
}
|
|
if (prv != npos) {
|
|
_table[prv].next = idx;
|
|
}
|
|
fennec::construct(&_table[idx], p, npos, prv, nxt, depth(p) + 1, fennec::forward<ArgsT>(args)...);
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
constexpr void _erase(size_t i) {
|
|
list<size_t> queue;
|
|
queue.push_back(child(i));
|
|
while (not queue.empty()) {
|
|
size_t n = queue.front(); queue.pop_front();
|
|
if (n == npos) continue;
|
|
queue.push_back(next(n));
|
|
queue.push_back(child(n));
|
|
fennec::destruct(&_table[n]);
|
|
_freed.push_back(n);
|
|
--_size;
|
|
}
|
|
|
|
fennec::destruct(&_table[i]);
|
|
if (i != root) _freed.push_back(i);
|
|
--_size;
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
#endif // FENNEC_CONTAINERS_RDTREE_H
|