- Documented and Debugged containers

- Attempted to setup gdb prettywriters
This commit is contained in:
2025-08-07 19:03:34 -04:00
parent 0f721f57ea
commit 2cb41e1437
19 changed files with 1047 additions and 403 deletions

1
.gdbinit Normal file
View File

@@ -0,0 +1 @@
source fennec/gdb/printers.py

View File

@@ -61,6 +61,13 @@ add_library(fennec STATIC
include/fennec/core/engine.h source/core/engine.cpp
include/fennec/core/event.h source/core/event.cpp
include/fennec/core/system.h
# SCENE ================================================================================================================
include/fennec/scene/scene.h
include/fennec/scene/component.h
# CONTAINERS ===========================================================================================================
include/fennec/containers/array.h
@@ -193,11 +200,6 @@ add_library(fennec STATIC
# EXTRA SOURCES ========================================================================================================
${FENNEC_EXTRA_SOURCES}
include/fennec/scene/scene.h
include/fennec/scene/component.h
include/fennec/core/system.h
include/fennec/renderers/opengl/lib/vertex_array.h
include/fennec/renderers/opengl/texture.h
)
add_dependencies(fennec metaprogramming)

View File

@@ -42,6 +42,8 @@ if(FENNEC_GRAPHICS_WANT_EGL)
include/fennec/platform/opengl/lib/fwd.h
include/fennec/platform/opengl/lib/enum.h
include/fennec/platform/opengl/lib/buffer.h
include/fennec/platform/opengl/lib/texture.h
include/fennec/platform/opengl/lib/vertex_array.h
include/fennec/platform/opengl/egl/context.h source/platform/opengl/egl/context.cpp
)

26
gdb/list.py Normal file
View File

@@ -0,0 +1,26 @@
import sys
class ListPrinter:
"""Print a fennec::list"""
class Iterator:
def __init__(self, head):
self.node = head
def __iter__(self):
return self
def __next__(self):
if self.node == sys.maxsize:
raise StopIteration
value = self.node['*data']
self.node = self.node['_data[next]']
return value
def __init__(self, val):
self.val = val
def to_string(self):
return "fennec::list"
def children(self):
return enumerate(self.Iterator(self.val['_data[_root]']))

10
gdb/printers.py Normal file
View File

@@ -0,0 +1,10 @@
from list import ListPrinter
def lookup_function(val):
if str(val.type) == "fennec::list":
return ListPrinter(val)
return None
gdb.pretty_printers.append(lookup_function)

View File

@@ -45,7 +45,7 @@ namespace fennec
/// \details
/// | Property | Value |
/// |:--------:|:-----------------------:|
/// | stable | |
/// | stable | \emoji heavy_check_mark |
/// | access | \f$O(1)\f$ |
/// | space | \f$O(N)\f$ |
///

View File

@@ -43,8 +43,8 @@ namespace fennec
/// \brief wrapper for dynamically sized arrays
/// \details
/// | Property | Value |
/// |-----------|--------------|
/// | stable | \emoji anger |
/// |-----------|:----------:|
/// | stable | \emoji x |
/// | access | \f$O(1)\f$ |
/// | insertion | \f$O(N)\f$ |
/// | deletion | \f$O(N)\f$ |
@@ -54,9 +54,15 @@ namespace fennec
template<class TypeT, class Alloc = allocator<TypeT>>
class dynarray {
public:
// Definitions =========================================================================================================
using element_t = TypeT;
using alloc_t = Alloc;
// Constructors ========================================================================================================
///
/// \brief Default Constructor, initializes an empty allocation.
constexpr dynarray() : _alloc(8), _size(0) {}
@@ -67,11 +73,18 @@ public:
constexpr dynarray(const alloc_t& alloc)
: _alloc(8, alloc)
, _size(0) {
}
///
/// \brief Sized Allocation, create an allocation with a size of `n` elements, initialized with the default constructor.
/// \brief Alloc Move Constructor, initialize empty allocation with allocator instance.
/// \param alloc An allocator object to copy, for instances where the allocator needs to be initialized with some data.
constexpr dynarray(alloc_t&& alloc) noexcept
: _alloc(8, alloc)
, _size(0) {
}
///
/// \brief Sized Allocation, create an allocation of size `n` elements, initialized with the default constructor.
constexpr dynarray(size_t n)
: _alloc(n)
, _size(n)
@@ -83,9 +96,9 @@ public:
}
///
/// \brief
/// \param n
/// \param alloc
/// \brief Sized Allocation Alloc Constructor, initializes a dynarray with allocator `alloc` and `n` elements using the default constructor.
/// \param n The number of elements
/// \param alloc The allocator object to copy
constexpr dynarray(size_t n, const alloc_t& alloc)
: _alloc(n, alloc)
, _size(n) {
@@ -96,7 +109,20 @@ public:
}
///
/// \brief Create an allocation of size `n`, with each element constructed using the copy constructor
/// \brief Sized Allocation Alloc Move Constructor, initializes a dynarray with allocator `alloc` and `n` elements using the default constructor.
/// \param n The number of elements
/// \param alloc The allocator object to copy
constexpr dynarray(size_t n, alloc_t&& alloc)
: _alloc(n, alloc)
, _size(n) {
element_t* addr = _alloc.data();
for(; n > 0; --n, ++addr) {
fennec::construct(addr);
}
}
///
/// \brief Sized Allocation Copy Constructor, Create an allocation of size `n` elements, with each element constructed using the copy constructor
/// \brief n the number of elements
constexpr dynarray(size_t n, const TypeT& val)
: _alloc(n)
@@ -107,8 +133,15 @@ public:
}
}
// This constructor should not be invokable since moving is a single object operation and will cause undefined
// behaviour when moving to multiple elements
constexpr dynarray(size_t n, TypeT&&) = delete;
///
/// \brief Emplace Constructor
/// \tparam ArgsT A sequence of argument types
/// \param n The number of objects to create
/// \param args The arguments to create each object with
template<typename...ArgsT>
constexpr dynarray(size_t n, ArgsT&&...args) {
element_t* addr = _alloc.data();
@@ -117,49 +150,109 @@ public:
}
}
///
/// \brief Default Destructor, destructs all elements and frees the underlying allocation
constexpr ~dynarray() {
element_t* addr = _alloc.data();
if (addr == nullptr) return;
for(int n = _size; n > 0; --n, ++addr) {
fennec::destruct(addr);
}
}
// Properties ==========================================================================================================
///
/// \returns The size of the dynarray in elements
constexpr size_t size() const {
return _size;
}
///
/// \returns The current capacity, in elements, of the underlying allocation
constexpr size_t capacity() const {
return _alloc.capacity();
}
///
/// \returns True when there are no elements active, otherwise false
constexpr bool empty() const {
return _size == 0;
}
// Element Access ======================================================================================================
///
/// \brief Array Access Operator
/// \param i The index to access
/// \returns A reference to the element at index `i`
constexpr TypeT& operator[](int i) {
assertd(i >= 0 and size_t(i) < _size, "Array Out of Bounds");
return _alloc.data()[i];
}
///
/// \brief Array Access Operator (const)
/// \param i The index to access
/// \returns A const qualified reference to the element at index `i`
constexpr const TypeT& operator[](int i) const {
assertd(i >= 0 and size_t(i) < _size, "Array Out of Bounds");
return _alloc.data()[i];
}
constexpr TypeT* begin() { return _alloc.data(); }
constexpr TypeT* end() { return begin() + _size; }
///
/// \returns Reference to the first element in the dynarray
constexpr TypeT& front() {
return this->operator[](0);
}
constexpr const TypeT* begin() const { return _alloc; }
constexpr const TypeT* end() const { return begin() + _size; }
///
/// \returns A const-qualified reference to the first element in the dynarray
constexpr const TypeT& front() const {
return this->operator[](0);
}
///
/// \returns A reference to the last element in the dynarray
constexpr TypeT& back() {
return this->operator[](size() - 1);
}
///
/// \returns A const-qualified reference to the last element in the dynarray
constexpr const TypeT& back() const {
return this->operator[](size() - 1);
}
///
/// \brief "Iterator" Begin Function
/// \returns A pointer to the first element in the dynarray
constexpr TypeT* begin() { return _alloc.data(); }
///
/// \brief "Iterator" End Function
/// \return A pointer to the address after the last element in the dynarray
constexpr TypeT* end() { return begin() + _size; }
///
/// \brief Const "Iterator" Begin Function
/// \returns A const qualified pointer to the first element in the dynarray
constexpr const TypeT* begin() const { return _alloc; }
///
/// \brief Const "Iterator" End Function
/// \return A const qualified pointer to the address after the last element in the dynarray
constexpr const TypeT* end() const { return begin() + _size; }
// Insertion & Deletion ================================================================================================
///
/// \brief Move Insertion
/// \param i index to insert at
/// \param val the value to initialize with
constexpr void insert(size_t i, TypeT&& val) {
// Grow if the size has reached the capacity of the allocation
@@ -180,6 +273,10 @@ public:
++_size;
}
///
/// \brief Copy Insertion
/// \param i index to insert at
/// \param val the value to initialize with
constexpr void insert(size_t i, const TypeT& val) {
// Grow if the size has reached the capacity of the allocation
@@ -190,9 +287,10 @@ public:
// Move the data if we are not inserting at the end of the array
if((i = min(i, _size)) < _size) {
fennec::memmove(
(void*)(_alloc.data() + i)
, (void*)(_alloc.data() + i + 1)
, (_size - i) * sizeof(TypeT));
(void*)(_alloc.data() + i),
(void*)(_alloc.data() + i + 1),
(_size - i) * sizeof(TypeT)
);
}
// Insert the element
@@ -200,6 +298,11 @@ public:
++_size;
}
///
/// \brief Emplace Insertion
/// \param i index to insert at
/// \param args Arguments to construct with
/// \tparam ArgsT Argument types
template<typename...ArgsT>
constexpr void emplace(size_t i, ArgsT&&...args) {
@@ -221,31 +324,41 @@ public:
++_size;
}
///
/// \brief Push Back Copy
/// \param val Value to initialize with
constexpr void push_back(const TypeT& val) {
dynarray::insert(_size, val);
}
///
/// \brief Push Back Move
/// \param val Value to initialize with
constexpr void push_back(TypeT&& val) {
dynarray::insert(_size, fennec::forward<TypeT>(val));
}
constexpr void pop_back() {
fennec::destruct(&_alloc[_size--]);
}
///
/// \brief Emplace Back
/// \tparam ArgsT Argument Types
/// \param args Arguments to construct with
template<typename...ArgsT>
constexpr void emplace_back(ArgsT...args) {
dynarray::emplace(_size, fennec::forward<ArgsT>(args)...);
}
///
/// \brief Erase last element
constexpr void pop_back() {
fennec::destruct(&_alloc[--_size]);
}
///
/// \brief Resize the dynarray, invoking the default constructor for all new elements
/// \param n The new size in elements
constexpr void resize(size_t n) {
_alloc.reallocate(n);
if (_size < n) {
_size = n;
return;
}
while (_size < n) {
emplace_back();
}

View File

@@ -45,41 +45,76 @@ namespace fennec
///
/// \brief wrapper for 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 | \emoji anger |
/// | access | \f$O(1)\f$ |
/// | insertion | \f$O(1)\f$ |
/// | deletion | \f$O(1)\f$ |
/// |:----------|:-------------------------------------:|
/// | stable | \emoji x |
/// | access | \f$O(N)\f$ or \f$O(1)\f$ (front/back) |
/// | insertion | \f$O(N)\f$ or \f$O(1)\f$ (iterator) |
/// | deletion | \f$O(N)\f$ or \f$O(1)\f$ (iterator) |
/// | 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;
// Definitions =========================================================================================================
private:
struct node;
public:
using alloc_t = typename allocator_traits<Alloc>::template rebind<TypeT>;
using value_t = TypeT;
using elem_t = node;
static constexpr size_t npos = -1;
class iterator;
class const_iterator;
// Constructors ========================================================================================================
///
/// \brief Default Constructor, initializes an empty list.
constexpr list()
: _table(), _freed(), _root(npos), _last(npos), _size(0) {
}
constexpr ~list() = default;
///
/// \brief Destructor, destructs all elements then releases the allocation.
constexpr ~list() {
for (size_t i = 0; i < capacity(); ++i) {
_table[i].data = nullopt;
}
}
constexpr bool size() const { return _size; }
constexpr bool capacity() const { return _table.capacity(); }
// 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 ==============================================================================================================
///
/// \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);
@@ -87,6 +122,12 @@ public:
return *_table[n].data;
}
///
/// \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);
@@ -94,216 +135,176 @@ public:
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);
}
///
/// \brief Access Front Element
/// \returns A reference to the first element in the list
constexpr value_t& front() {
return *_table[_root].data;
}
///
/// \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].data;
}
///
/// \brief Access Back Element
/// \returns A reference to the last element in the list
constexpr value_t& back() {
return *_table[_last].data;
}
///
/// \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].data;
}
// Insertions & Deletions ==============================================================================================
///
/// \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);
}
// ITERATOR ============================================================================================================
///
/// \brief Iterator Class
class iterator {
public:
~iterator() {
@@ -352,6 +353,8 @@ public:
}
};
///
/// \brief Iterator Class for Const Access
class const_iterator {
public:
~const_iterator() {
@@ -400,18 +403,26 @@ public:
}
};
///
/// \returns An iterator for the first element in the list
constexpr iterator begin() {
return iterator(this, _root);
}
///
/// \returns An iterator for the end of the list
constexpr iterator end() {
return iterator(this, npos);
}
///
/// \returns A const iterator for the first element in the list
constexpr const_iterator begin() const {
return const_iterator(this, _root);
}
///
/// \returns A const iterator for the end of the list
constexpr const_iterator end() const {
return const_iterator(this, npos);
}
@@ -458,15 +469,15 @@ private:
}
};
size_t _next(size_t n) const {
constexpr size_t _next(size_t n) const {
return _table[n].next;
}
size_t _prev(size_t n) const {
constexpr size_t _prev(size_t n) const {
return _table[n].prev;
}
size_t _walk(size_t i) const {
constexpr size_t _walk(size_t i) const {
size_t n = _root;
if (n == npos) return n;
while (i > 0 && n != npos) {
@@ -475,15 +486,68 @@ private:
return n;
}
size_t _next_free() {
constexpr size_t _next_free() {
if (not _freed.empty()) {
size_t n = _freed.back();
_freed.pop_back();
return n;
}
_table[_size];
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].data, 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].data);
_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;
}
};
}

View File

@@ -25,14 +25,12 @@
namespace fennec
{
// TODO: Document
/* Ramblings
*
* Definitions:
* user = Programmer using this data structure
*
* The STL unordered-map is very contrived. Some of its functionality encourages younger programmers to use
* The STL maps are very contrived. Some of its functionality encourages younger programmers to use
* the exception model. Ideally, I would like this structure to never throw an error with typical use.
*
* The array access operator is, in my opinion, poorly implemented. I do not think that this operator should handle
@@ -40,13 +38,21 @@ namespace fennec
* data structure modifies contents by inherently calling operator[].
*
* Currently, I am considering implementing this as the following:
* Access will be handled only via operator[]. Return value will be an optional which forces user validation.
* Access will be handled only via operator[]. Return value will be a pointer which forces user validation.
* Insertions will be handled only via an insert/emplace function.
* Deletions will be handled only via an erase function.
*/
///
/// \brief Map for Pairing Values to Keys
/// \tparam KeyT The Key Type
/// \tparam ValueT The Value Type
/// \tparam Hash The Hash to Use
/// \tparam Alloc The Allocator to Use
template<typename KeyT, typename ValueT, typename Hash = hash<KeyT>, typename Alloc = allocator<pair<KeyT, ValueT>>>
struct map {
// Definitions =========================================================================================================
public:
using key_t = KeyT;
using value_t = ValueT;
@@ -68,9 +74,24 @@ public:
}
};
// Constructors ========================================================================================================
///
/// \brief Default Constructor, initializes empty map
constexpr map() = default;
///
/// \brief Destructor, Destructs all elements and releases the allocation
constexpr ~map() = default;
// Access ==============================================================================================================
///
/// \brief Key Access Operator
/// \param key Key value to access
/// \returns A pointer to the value associated with `key`, `nullptr` if `key` is not present.
constexpr value_t* operator[](const KeyT& key) {
union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers
pair<KeyT, char[sizeof(ValueT)]> root;
@@ -87,6 +108,10 @@ public:
return &_set.at(it)->second;
}
///
/// \brief Key Const Access Operator
/// \param key Key value to access
/// \returns A const-qualified pointer to the value associated with `key`, `nullptr` if `key` is not present.
constexpr const value_t* operator[](const KeyT& key) const {
union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers
pair<KeyT, char[sizeof(ValueT)]> root;
@@ -103,8 +128,13 @@ public:
return &_set.at(it)->second;
}
template<typename...ArgsT>
constexpr value_t* operator[](ArgsT&&...args) {
///
/// \brief Argument Key Access Operator
/// \tparam ArgT Argument Type
/// \param arg Argument to construct the key with
/// \returns A pointer to the value associated with `key`, `nullptr` if `key` is not present.
template<typename ArgT>
constexpr value_t* operator[](ArgT&& arg) {
union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers
pair<KeyT, char[sizeof(ValueT)]> root;
pair<KeyT, ValueT> val;
@@ -112,7 +142,7 @@ public:
~U() {
fennec::destruct(&root);
}
} trick = { .root = { key_t(fennec::forward<ArgsT>(args)...), 0 } }; // Only initialize root
} trick = { .root = { key_t(fennec::forward<ArgT>(arg)), 0 } }; // Only initialize root
auto it = _set.find(trick.val);
if (it == _set.end()) {
return nullptr;
@@ -120,8 +150,13 @@ public:
return &_set.at(it)->second;
}
template<typename...ArgsT>
constexpr const value_t* operator[](ArgsT&&...args) const {
///
/// \brief Argument Key Const Access Operator
/// \tparam ArgT Argument Type
/// \param arg Argument to construct the key with
/// \returns A const-qualified pointer to the value associated with `key`, `nullptr` if `key` is not present.
template<typename ArgT>
constexpr const value_t* operator[](ArgT&& arg) const {
union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers
pair<KeyT, char[sizeof(ValueT)]> root;
pair<KeyT, ValueT> val;
@@ -129,7 +164,7 @@ public:
~U() {
fennec::destruct(&root);
}
} trick = { .root = { key_t(fennec::forward<ArgsT>(args)...), 0 } }; // Only initialize root
} trick = { .root = { key_t(fennec::forward<ArgT>(arg)), 0 } }; // Only initialize root
auto it = _set.find(trick.val);
if (it == _set.end()) {
return nullptr;
@@ -137,6 +172,12 @@ public:
return &_set.at(it)->second;
}
// Insertion & Deletion ================================================================================================
///
/// \brief Key-Value Insertion
/// \param pair a pair containing the key and its value
constexpr void insert(elem_t&& pair) {
auto it = _set.find(pair);
if (it == _set.end()) {
@@ -146,11 +187,17 @@ public:
_set.insert(fennec::forward<elem_t>(pair));
}
///
/// \brief Key-Value Insertion
/// \param args Arguments for constructing the key-value pair
template<typename...ArgsT>
constexpr void emplace(ArgsT&&...args) {
_set.insert(elem_t(args...));
}
///
/// \brief Erase a key
/// \param key key to erase
constexpr void erase(KeyT&& key) {
union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers
pair<KeyT, char[sizeof(ValueT)]> root;
@@ -163,11 +210,18 @@ public:
_set.erase(trick.val);
}
///
/// \brief Erase a key
/// \param key key to erase
constexpr void erase(const KeyT& key) {
KeyT val = key;
erase(fennec::move(val));
}
///
/// \brief Argument Erase
/// \tparam ArgsT Argument Types
/// \param args Arguments to construct a key to erase
template<typename...ArgsT>
constexpr void erase(ArgsT&&...args) {
union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers

View File

@@ -37,13 +37,17 @@ constexpr nullopt_t nullopt_v = {};
/// \tparam T
template<typename T>
struct optional {
// Definitions =========================================================================================================
public:
// Constructors ========================================================================================================
using reference_t = T&;
using pointer_t = T*;
using const_reference_t = T&;
using const_pointer_t = const T*;
// Constructors ========================================================================================================
///
/// \brief Default Constructor
constexpr optional()
@@ -59,18 +63,18 @@ public:
}
///
/// \brief Fundamental Type Constructor
/// \brief Type Copy Constructor
/// \param val the value to initialize the underlying object with
constexpr optional(T val) requires(is_fundamental_v<T> or is_pointer_v<T>)
constexpr optional(const T& val)
: _val(val)
, _set(true) {
}
///
/// \brief Type Copy Constructor
/// \brief Type Move Constructor
/// \param val the value to initialize the underlying object with
constexpr optional(const T& val)
: _val(val)
constexpr optional(T&& val)
: _val(fennec::forward<T>(val))
, _set(true) {
}
@@ -97,6 +101,12 @@ public:
opt = nullopt;
}
template<typename...ArgsT>
constexpr optional(ArgsT&&...args)
: _val(fennec::forward<ArgsT>(args)...)
, _set(true) {
}
constexpr ~optional() {
if constexpr(is_fundamental_v<T>) {
return;
@@ -111,7 +121,7 @@ public:
///
/// \brief Fundamental Type Assignment
/// \val The value to set with
/// \param val The value to set with
constexpr optional& operator=(nullopt_t) {
if constexpr(not is_fundamental_v<T>) {
if (_set) {
@@ -125,7 +135,7 @@ public:
///
/// \brief Type Copy Assignment
/// \val The value to set with
/// \param val The value to set with
constexpr optional& operator=(const T& val) requires is_copy_constructible_v<T> and is_copy_assignable_v<T> {
if (_set) {
_val = val;
@@ -138,12 +148,12 @@ public:
///
/// \brief Type Move Assignment
/// \val The value to set with
/// \param val The value to set with
constexpr optional& operator=(T&& val) requires is_move_constructible_v<T> and is_move_assignable_v<T> {
if (_set) {
_val = fennec::move(val);
_val = fennec::forward<T>(val);
} else {
fennec::construct(&_val, fennec::move(val));
fennec::construct(&_val, fennec::forward<T>(val));
_set = true;
}
return *this;
@@ -151,7 +161,7 @@ public:
///
/// \brief Copy Assignment
/// \val The optional to copy
/// \param opt The optional to copy
constexpr optional& operator=(const optional& opt) requires is_copy_constructible_v<T> and is_copy_assignable_v<T> {
if (_set != opt._set) {
_set = opt._set;
@@ -169,7 +179,7 @@ public:
///
/// \brief Move Assignment
/// \val The optional to move
/// \param opt The optional to move
constexpr optional& operator=(optional&& opt) noexcept requires is_move_constructible_v<T> and is_move_assignable_v<T> {
if (_set != opt._set) {
_set = opt._set;
@@ -185,6 +195,9 @@ public:
return *this;
}
///
/// \brief Emplace Assignment
/// \val The optional to move
template<typename...ArgsT>
constexpr T& emplace(ArgsT&&...args) {
if (_set) {
@@ -196,6 +209,8 @@ public:
return _val;
}
///
/// \brief Reset the Optional
void reset() {
this->operator=(nullopt);
}
@@ -203,33 +218,51 @@ public:
// Operators ===========================================================================================================
///
/// \brief Implicit Boolean Check, returns `true` when there is a value contained
constexpr operator bool() const {
return _set;
}
///
/// \returns A pointer to the value, `nullptr` if there is no value
constexpr pointer_t operator->() noexcept {
return _set ? &_val : nullptr;
}
///
/// \returns A const-qualified pointer to the value, `nullptr` if there is no value
constexpr const_pointer_t operator->() const noexcept {
return _set ? &_val : nullptr;
}
///
/// \brief Dereference Operator
/// \returns A reference to the value
constexpr T& operator*() & noexcept {
assertd(_set, "Attempted to reference the value of an unset optional");
return _val;
}
///
/// \brief Const Dereference Operator
/// \returns A const-qualified reference to the value
constexpr const T& operator*() const& noexcept {
assertd(_set, "Attempted to reference the value of an unset optional");
return _val;
}
///
/// \brief Dereference Operator
/// \returns A reference to the value
constexpr T&& operator*() && noexcept {
assertd(_set, "Attempted to reference the value of an unset optional");
return _val;
}
///
/// \brief Const Dereference Operator
/// \returns A const-qualified reference to the value
constexpr const T&& operator*() const&& noexcept {
assertd(_set, "Attempted to reference the value of an unset optional");
return _val;

View File

@@ -28,57 +28,124 @@ namespace fennec
// TODO: Document
///
/// \brief Struct for holding a pair of values
/// \tparam T0 The type of the first value
/// \tparam T1 The type of the second value
template<typename T0, typename T1>
struct pair {
// Constructors ========================================================================================================
///
/// \brief Default Constructor, invokes default constructor for both elements
constexpr pair() = default;
///
/// \brief Destructor, invokes destructor for both elements
constexpr ~pair() = default;
///
/// \brief Pair Copy Constructor
/// \param x Value to copy for the first element
/// \param y Value to copy for the first element
constexpr pair(const T0& x, const T1& y)
: first(x)
, second(y) {
}
///
/// \brief Pair Move Constructor
/// \param x Value to move for the first element
/// \param y Value to move for the first element
constexpr pair(T0&& x, T1&& y) noexcept
: first(fennec::forward<T0>(x))
, second(fennec::forward<T1>(y)) {
}
///
/// \brief Pair Implicit Constructor
/// \param arg1 Value to initialize the first element
/// \param arg2 Value to initialize the first element
template<typename Arg1T, typename Arg2T>
constexpr pair(Arg1T&& arg1, Arg2T&& arg2)
: first(fennec::forward<Arg1T>(arg1))
, second(fennec::forward<Arg2T>(arg2)) {
}
///
/// \brief Copy Constructor, copies both elements
constexpr pair(const pair&) = default;
///
/// \brief Move Constructor, moves both elements
constexpr pair(pair&&) noexcept = default;
///
/// \brief Copy Assignment, copies both elements
constexpr pair& operator=(const pair&) = default;
///
/// \brief Move Assignment, moves both elements
constexpr pair& operator=(pair&&) noexcept = default;
// Comparison ==========================================================================================================
///
/// \brief Equality Operator
/// \param p Pair to compare with
/// \returns `true` when both elements of each pair are equal
constexpr bool operator==(const pair& p) const {
return first == p.first and second == p.second;
}
///
/// \brief Inequality Operator
/// \param p Pair to compare with
/// \returns `true` when either element of each pair are equal
constexpr bool operator!=(const pair& p) const {
return first != p.first or second != p.second;
}
///
/// \brief Less Than Operator
/// \param p Pair to compare with
/// \returns lexical comparison of both elements, i.e. returns `true` when the first element is less, or they are
/// equal and the second element is less
constexpr bool operator<(const pair& p) const {
return first < p.first or (first == p.first and second < p.second);
}
///
/// \brief Less Equal Operator
/// \param p Pair to compare with
/// \returns lexical comparison of both elements, i.e. returns `true` when the first element is less, or they are
/// equal and the second element is less or equal
constexpr bool operator<=(const pair& p) const {
return first < p.first or (first == p.first and second <= p.second);
}
///
/// \brief Greater Than Operator
/// \param p Pair to compare with
/// \returns lexical comparison of both elements, i.e. returns `true` when the first element is greater, or they are
/// equal and the second element is greater
constexpr bool operator>(const pair& p) const {
return first > p.first or (first == p.first and second > p.second);
}
///
/// \brief Greater Equal Operator
/// \param p Pair to compare with
/// \returns lexical comparison of both elements, i.e. returns `true` when the first element is greater, or they are
/// equal and the second element is greater or equal
constexpr bool operator>=(const pair& p) const {
return first > p.first or (first == p.first and second >= p.second);
}
// Members =============================================================================================================
T0 first;
T1 second;
};

View File

@@ -26,9 +26,15 @@
namespace fennec
{
///
/// \brief Rooted-Directed Tree
/// \tparam TypeT Data type
/// \tparam AllocT Allocator Type
template<typename TypeT, typename AllocT = allocator<TypeT>>
struct rdtree {
private:
// Definitions =========================================================================================================
protected:
struct node;
public:
@@ -63,7 +69,7 @@ public:
template<typename...ArgsT>
explicit constexpr rdtree(ArgsT&&...args)
: _data(), _freed(), _size(1) {
_data.callocate(10);
_data.callocate(8);
fennec::construct(&_data[0], npos, npos, npos, npos, fennec::forward<ArgsT>(args)...);
}
@@ -78,51 +84,84 @@ public:
// Assignment ==========================================================================================================
friend constexpr rdtree& operator=(rdtree& lhs, const rdtree& rhs) {
for (value_t* it : lhs._data) {
constexpr rdtree& operator=(const rdtree& rhs) {
for (value_t* it : this->_data) {
fennec::destruct(it);
}
lhs._data = rhs._data;
lhs._freed = rhs._freed;
lhs._size = rhs._size;
return lhs;
_data = rhs._data;
_freed = rhs._freed;
_size = rhs._size;
return *this;
}
friend constexpr rdtree& operator=(rdtree& lhs, rdtree&& rhs) noexcept {
for (value_t* it : lhs._data) {
constexpr rdtree& operator=(rdtree&& rhs) noexcept {
for (value_t* it : _data) {
fennec::destruct(it);
}
lhs._data = fennec::move(rhs._data);
lhs._freed = fennec::move(rhs._freed);
lhs._size = rhs._size;
return lhs;
_data = fennec::move(rhs._data);
_freed = fennec::move(rhs._freed);
_size = rhs._size;
return *this;
}
// Properties ==========================================================================================================
constexpr size_t size() const {
return _size;
}
constexpr size_t capacity() const {
return _data.capacity();
}
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 {
return _data[i].parent;
return i == npos ? npos : _data[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) const {
return _data[i].child;
return i == npos ? npos : _data[i].child;
}
///
/// \param i The id of the node to check
/// \returns The id of the next node
constexpr size_t next(size_t i) const {
return _data[i].next;
return i == npos ? npos : _data[i].next;
}
///
/// \param i The id of the node to check
/// \returns The id of the previous node
constexpr size_t prev(size_t i) const {
return _data[i].prev;
return i == npos ? npos : _data[i].prev;
}
///
/// \param i The id of the node to access
/// \returns A reference to the value of the node wrapped in an optional
constexpr optional<value_t>& operator[](size_t i) {
return *_data[i].value;
return _data[i].value;
}
///
/// \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 optional<value_t>& operator[](size_t i) const {
return *_data[i].value;
return _data[i].value;
}
@@ -134,21 +173,7 @@ public:
/// \param val the value to insert
/// \returns the index of the created node
constexpr size_t insert(size_t parent, const value_t& val) {
if (parent == npos || _size == 0) {
if (_size == 0) {
fennec::construct(&_data[root], npos, npos, npos, npos, val);
_size = 1;
} else {
_data[root].value = val;
}
return root;
}
size_t i = _next_free();
size_t n = child(parent);
_data[parent].child = i;
fennec::construct(&_data[i], parent, npos, n, npos);
return i;
return this->_insert(parent, val);
}
///
@@ -157,42 +182,14 @@ public:
/// \param val the value to insert
/// \returns the index of the created node
constexpr size_t insert(size_t parent, value_t&& val) {
if (parent == npos || _size == 0) {
if (_size == 0) {
fennec::construct(&_data[root], npos, npos, npos, npos, fennec::forward<value_t>(val));
_size = 1;
} else {
_data[root].value = fennec::forward<value_t>(val);
}
return root;
}
size_t i = _next_free();
size_t n = child(parent);
_data[parent].child = i;
fennec::construct(&_data[i], parent, npos, n, npos);
return i;
return this->_insert(parent, fennec::forward<value_t>(val));
}
///
/// \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) {
list<size_t> queue;
if (child(i) != npos) {
queue.push_back(i);
}
while (not queue.empty()) {
size_t n = queue.front(); queue.pop_front();
if (next(n) != npos) queue.push_back(next(n));
if (child(n) != npos) queue.push_back(child(n));
fennec::destruct(&_data[n]);
_freed.push_back(n);
}
fennec::destruct(&_data[i]);
_freed.push_back(i);
_erase(i);
}
@@ -206,13 +203,57 @@ protected:
}
size_t _next_free() {
size_t next = _size + 1;
size_t next = _size++;
if (not _freed.empty()) {
next = _freed.back();
_freed.pop_back();
}
if (_size >= capacity()) {
_expand();
}
return next;
}
template<typename...ArgsT>
constexpr size_t _insert(size_t p, ArgsT&&...args) {
if (_size == 0) {
fennec::construct(&_data[root], npos, npos, npos, npos, fennec::forward<ArgsT>(args)...);
_size = 1;
return root;
}
if (p == npos) {
_data[root].value = value_t(fennec::forward<ArgsT>(args)...);
return root;
}
size_t i = _next_free();
size_t n = child(p);
_data[p].child = i;
if (n != npos) _data[n].prev = i;
fennec::construct(&_data[i], p, npos, npos, n, fennec::forward<ArgsT>(args)...);
return i;
}
constexpr void _erase(size_t i) {
list<size_t> queue;
size_t j = 0;
queue.push_back(child(i));
while (queue.empty() == false) {
size_t n = queue.front(); queue.pop_front();
if (n == npos) continue;
queue.push_back(next(n));
queue.push_back(child(n));
fennec::destruct(&_data[n]);
_freed.push_back(n);
--_size;
++j;
}
fennec::destruct(&_data[i]);
_freed.push_back(i);
--_size;
}
};
}

View File

@@ -31,15 +31,30 @@
namespace fennec
{
// TODO: Document
template<typename T, class Hash = hash<T>, class Equals = equality<T>, class Alloc = allocator<T>>
///
///
/// \brief wrapper for sets of elements
/// \details
/// This data-structure behaves like a set, but does not use pointers, instead storing the table in-array
///
/// | Property | Value |
/// |:----------|:----------:|
/// | stable | \emoji x |
/// | access | \f$O(1)\f$ |
/// | insertion | \f$O(1)\f$ |
/// | deletion | \f$O(1)\f$ |
/// | space | \f$O(1)\f$ |
///
/// \tparam TypeT The type to contain
template<typename TypeT, class Hash = hash<TypeT>, class Equals = equality<TypeT>, class Alloc = allocator<TypeT>>
struct set {
// Definitions =========================================================================================================
public:
using alloc_t = typename allocator_traits<Alloc>::template rebind<T>;
using alloc_t = typename allocator_traits<Alloc>::template rebind<TypeT>;
using hash_t = Hash;
using equal_t = Equals;
using elem_t = T;
using elem_t = TypeT;
class iterator;
static constexpr size_t npos = -1;
@@ -54,7 +69,11 @@ private:
constexpr ~node() = default;
};
// Constructors ========================================================================================================
public:
///
/// \brief Default Constructor, initializes empty set
constexpr set()
: _alloc()
, _hash()
@@ -63,6 +82,8 @@ public:
, _load(default_load) {
};
///
/// \brief Hash Copy Constructor, initializes empty set with a hash
constexpr set(const hash_t& hash)
: _alloc()
, _hash(hash)
@@ -71,6 +92,8 @@ public:
, _load(default_load) {
}
///
/// \brief Hash Move Constructor, initializes empty set with a hash
constexpr set(hash_t&& hash) noexcept
: _alloc()
, _hash(hash)
@@ -79,6 +102,8 @@ public:
, _load(default_load) {
}
///
/// \brief Alloc Copy Constructor, initializes empty set with an allocator
constexpr set(const alloc_t& alloc)
: _alloc(alloc)
, _hash()
@@ -87,6 +112,8 @@ public:
, _load(default_load) {
}
///
/// \brief Alloc Move Constructor, initializes empty set with an allocator
constexpr set(alloc_t&& alloc) noexcept
: _alloc(alloc)
, _hash()
@@ -95,6 +122,8 @@ public:
, _load(default_load) {
}
///
/// \brief Hash Alloc Copy Constructor, initializes empty set with a hash and allocator
constexpr set(const hash_t& hash, const alloc_t& alloc)
: _alloc(alloc)
, _hash(hash)
@@ -103,6 +132,8 @@ public:
, _load(default_load) {
}
///
/// \brief Hash Copy Alloc Move Constructor, initializes empty set with a hash and allocator
constexpr set(const hash_t& hash, alloc_t&& alloc) noexcept
: _alloc(alloc)
, _hash(hash)
@@ -111,6 +142,8 @@ public:
, _load(default_load) {
}
///
/// \brief Hash Move Alloc Move Constructor, initializes empty set with a hash and allocator
constexpr set(hash_t&& hash, alloc_t&& alloc) noexcept
: _alloc(alloc)
, _hash(hash)
@@ -119,6 +152,8 @@ public:
, _load(default_load) {
}
///
/// \brief Hash Move Alloc Copy Constructor, initializes empty set with a hash and allocator
constexpr set(hash_t&& hash, const alloc_t& alloc) noexcept
: _alloc(alloc)
, _hash(hash)
@@ -127,6 +162,9 @@ public:
, _load(default_load) {
}
///
/// \brief Set Copy Constructor
/// \param set Set to copy
constexpr set(const set& set)
: _alloc(set._alloc)
, _hash(set._hash)
@@ -135,6 +173,9 @@ public:
, _load(set._load) {
}
///
/// \brief Set Move Constructor
/// \param set Set to move
constexpr set(set&& set) noexcept
: _alloc(fennec::move(set._alloc))
, _hash(fennec::move(set._hash))
@@ -143,51 +184,36 @@ public:
, _load(set._load) {
}
constexpr ~set() = default;
///
/// \brief Destructor, destructs all elements and releases the allocation
constexpr ~set() {
for (size_t i = 0; i < capacity(); ++i) {
_alloc[i].value = nullopt;
}
}
// Properties ==========================================================================================================
///
/// \returns Size of the set in elements
constexpr size_t size() const {
return _size;
}
///
/// \returns Capacity of the set in elements
constexpr size_t capacity() const {
return _alloc.capacity();
}
constexpr void insert(elem_t&& val) {
if (_size == 0 or double(_size) / capacity() >= _load) { // expand when full
_expand();
}
elem_t value = fennec::forward<elem_t>(val);
size_t i = _hash(value) % capacity(); // Initial search index
int psl = 0;
while (_alloc[i].value) { // Search for empty cell
if (_equal(*_alloc[i].value, val)) { // Check to see if this element is already inserted
return;
}
if (psl > _alloc[i].psl) { // When psl is higher, swap
_sumpsl += psl - _alloc[i].psl;
fennec::swap(_alloc[i].psl, psl);
fennec::swap(*_alloc[i].value, value);
}
i = (i + 1) % capacity(); ++psl;
}
_alloc[i].value = fennec::move(value);
_sumpsl += (_alloc[i].psl = psl);
++_size;
}
constexpr void insert(const elem_t& val) {
elem_t value = val; // Copy Constructor invoked here
this->insert(fennec::move(value)); // Only invokes moves
}
template<typename...ArgsT>
constexpr void emplace(ArgsT&&...args) {
elem_t value = elem_t(fennec::forward<ArgsT>(args)...); // Constructor invoked here
this->insert(fennec::move(value)); // Only invokes moves
}
// Access ==============================================================================================================
///
/// \brief Find an Element
/// \param val Value to find
/// \returns An iterator at the location of the value
constexpr iterator find(const elem_t& val) const {
size_t s = _hash(val) % capacity(); // Initial search index
int psl = (_size != 0) ? _sumpsl / _size : 0; // Initial psl
@@ -229,24 +255,83 @@ public:
return iterator(this, npos);
}
constexpr elem_t* at(const iterator& it) {
size_t i = it._i;
if (i >= capacity()) return nullptr;
if (not _alloc[i].value) return nullptr;
return &*_alloc[i].value;
}
constexpr const elem_t* at(const iterator& it) const {
size_t i = it._i;
if (i >= capacity()) return nullopt;
if (not _alloc[i].value) return nullopt;
return &*_alloc[i].value;
}
///
/// \brief Check if a set contains a value
/// \param val Value to check
/// \returns `true` if `val` can be found, `false` otherwise
constexpr bool contains(const elem_t& val) const {
return this->find(val) != end();
}
///
/// \brief Iterator Access
/// \param it Location to access
/// \returns A pointer to the element, `nullptr` if not found.
/// The value should not be changed in a manner that will change the hash of the element.
constexpr elem_t* at(const iterator& it) {
if (not _alloc[it._i].value) return nullptr;
return &*_alloc[it._i].value;
}
///
/// \brief Iterator Const Access
/// \param it Location to access
/// \returns A const-qualified pointer to the element, `nullptr` if not found.
constexpr const elem_t* at(const iterator& it) const {
if (not _alloc[it._i].value) return nullptr;
return &*_alloc[it._i].value;
}
// Insertion & Deletion ================================================================================================
///
/// \brief Move Insertion
/// \param val Value to insert
constexpr void insert(elem_t&& val) {
if (_size == 0 or double(_size) / capacity() >= _load) { // expand when full
_expand();
}
elem_t value = fennec::forward<elem_t>(val);
size_t i = _hash(value) % capacity(); // Initial search index
int psl = 0;
while (_alloc[i].value) { // Search for empty cell
if (_equal(*_alloc[i].value, val)) { // Check to see if this element is already inserted
return;
}
if (psl > _alloc[i].psl) { // When psl is higher, swap
_sumpsl += psl - _alloc[i].psl;
fennec::swap(_alloc[i].psl, psl);
fennec::swap(*_alloc[i].value, value);
}
i = (i + 1) % capacity(); ++psl;
}
_alloc[i].value = fennec::move(value);
_sumpsl += (_alloc[i].psl = psl);
++_size;
}
///
/// \brief Copy Insertion
/// \param val Value to insert
constexpr void insert(const elem_t& val) {
elem_t value = val; // Copy Constructor invoked here
this->insert(fennec::move(value)); // Only invokes moves
}
///
/// \brief Emplace Insertion
/// \tparam ArgsT Argument types
/// \param args Arguments to construct with
template<typename...ArgsT>
constexpr void emplace(ArgsT&&...args) {
elem_t value = elem_t(fennec::forward<ArgsT>(args)...); // Constructor invoked here
this->insert(fennec::move(value)); // Only invokes moves
}
///
/// \brief Element Erase
/// \param it Location to erase
constexpr void erase(iterator it) {
size_t i = it._i;
if (i >= capacity()) {
@@ -269,6 +354,9 @@ public:
}
}
///
/// \brief Element Erase
/// \param val Value to erase
constexpr void erase(const elem_t& val) {
this->erase(this->find(val));
}
@@ -276,6 +364,8 @@ public:
// ITERATOR ============================================================================================================
///
/// \brief Class for Iterating the Set
class iterator {
public:
constexpr ~iterator() {
@@ -303,6 +393,11 @@ public:
return *_set->_alloc[_i].value;
}
constexpr const elem_t* operator->() const {
if (not _set->_alloc[_i].value) return nullptr;
return &*_set->_alloc[_i].value;
}
constexpr bool operator==(const iterator& it) {
return _set == it._set and _i == it._i;
}

View File

@@ -59,10 +59,17 @@ struct Object
}
```
Textures for 3D rendering are stored in various buffers with sizes of powers of 2.
Ratios of `1:1` and `2:1` are allowed. The `2:1` ratio is specifically for spherical and cylindrical projection.
UVs may be transformed to use a `2:1` as if it were `1:2`.
Cubemaps and 3D textures may only be `1:1`, I would be concerned if you are using any other ratio.
Objects are identified with a manually provided type and ID. Object types include:
- Static (transform does not change after creation)
- Dynamic (transform changes after creation)
- Forward Static (object is forward rendered and its transform does not change after creation)
A user should never have to specify these and should be automatically generated by the respective components.
Textures for 3D rendering are stored in various buffers with sizes of powers of 2. Ratios of `1:1`
and `2:1` are allowed. The `2:1` ratio is specifically for spherical and cylindrical projection. UVs
may be transformed to use a `2:1` as if it were `1:2`.
Cubemaps and 3D textures may only be `1:1`.
- 8-Bit R Texture `4096, 2048, 1024, 512` (8)
- 8-Bit RG Texture `4096, 2048, 1024, 512` (8)

View File

@@ -35,16 +35,16 @@ Library and Template Library.
| any | ❌ | ❌ |
| bitset | ❌ | ❌ |
| array | ✔ | ✔ |
| dynarray (`std::vector`) | | |
| dynarray (`std::vector`) | | |
| list | ✔ | ✔ |
| set (`std::unordered_set`) | ✔ | ✔ |
| ordered_set (`std::set`) | | |
| ordered_set (`std::set`) | | |
| map (`std::unordered_map`) | ✔ | ✔ |
| ordered_map (`std::map`) | | |
| multiset (`std::unordered_multiset`) | | |
| ordered_multiset (`std::multiset`) | | |
| multimap (`std::unordered_multimap`) | | |
| ordered_multimap (`std::multimap`) | | |
| ordered_map (`std::map`) | | |
| multiset (`std::unordered_multiset`) | | |
| ordered_multiset (`std::multiset`) | | |
| multimap (`std::unordered_multimap`) | | |
| ordered_multimap (`std::multimap`) | | |
### fennec

View File

@@ -32,6 +32,8 @@ add_executable(fennec-test main.cpp
tests/containers/test_array.h
tests/containers/test_set.h
tests/containers/test_map.h
tests/containers/test_rdtree.h
tests/containers/test_list.h
)
target_compile_definitions(fennec-test PUBLIC FENNEC_TEST_CWD="${CMAKE_SOURCE_DIR}/bin/${FENNEC_BUILD_NAME}"

View File

@@ -0,0 +1,54 @@
// =====================================================================================================================
// 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/>.
// =====================================================================================================================
#ifndef TS_CONTAINERS_TEST_LIST_H
#define TS_CONTAINERS_TEST_LIST_H
#include "../../test.h"
#include <fennec/containers/list.h>
namespace fennec
{
namespace test
{
inline void fennec_test_containers_list() {
list<size_t> test;
const size_t n = 10000;
for (size_t i = 0; i < n; ++i) {
const size_t p = rand() % (i + 1);
assertf(test.insert(p, i) == i, "List Construct Test Failed.");
}
while (test.empty() == false) {
test.pop_back();
}
fennec_test_run(test.empty(), true);
}
}
}
#endif // TS_CONTAINERS_TEST_LIST_H

View File

@@ -0,0 +1,61 @@
// =====================================================================================================================
// 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/>.
// =====================================================================================================================
#ifndef FENNEC_TEST_CONTAINERS_RDTREE_H
#define FENNEC_TEST_CONTAINERS_RDTREE_H
#include "../../test.h"
#include <fennec/containers/rdtree.h>
namespace fennec
{
namespace test
{
inline void fennec_test_containers_rdtree() {
rdtree<size_t> test;
const size_t n = 10000;
for (size_t i = 0; i < n; ++i) {
const size_t parent = rand() % (i + 1);
const size_t child = rdtree<size_t>::npos;
const size_t prev = rdtree<size_t>::npos;
const size_t next = test.child(parent);
size_t l;
assertf((l = test.insert(parent, i)) == i + 1, "Tree Construct Test Failed.");
assertf(test.parent(l) == parent, "Tree Construct Test Failed.");
assertf(test.child(l) == child, "Tree Construct Test Failed.");
assertf(test.prev(l) == prev, "Tree Construct Test Failed.");
assertf(test.next(l) == next, "Tree Construct Test Failed.");
assertf(next == rdtree<size_t>::npos || test.prev(next) == l, "Tree Construct Test Failed");
}
test.erase(0);
fennec_test_run(test.empty(), true);
}
}
}
#endif // FENNEC_TEST_CONTAINERS_RDTREE_H

View File

@@ -21,8 +21,10 @@
#include "containers/test_array.h"
#include "containers/test_dynarray.h"
#include "containers/test_list.h"
#include "containers/test_map.h"
#include "containers/test_optional.h"
#include "containers/test_rdtree.h"
#include "containers/test_set.h"
namespace fennec::test
@@ -45,6 +47,11 @@ namespace fennec::test
fennec_test_containers_dynarray();
fennec_test_spacer(3);
fennec_test_subheader("list tests");
fennec_test_spacer(2);
fennec_test_containers_list();
fennec_test_spacer(3);
fennec_test_subheader("set tests");
fennec_test_spacer(2);
fennec_test_containers_set();
@@ -53,6 +60,11 @@ namespace fennec::test
fennec_test_subheader("map tests");
fennec_test_spacer(2);
fennec_test_containers_map();
fennec_test_spacer(3);
fennec_test_subheader("rdtree tests");
fennec_test_spacer(2);
fennec_test_containers_rdtree();
// TODO
}