2025-09-20 17:33:02 +02:00

203 lines
4.9 KiB
C++

#include "log.hpp"
#include <expected>
#include <iostream>
#include <stack>
#include <stdexcept>
namespace array { // TODO rename to container or something
template <typename U>
concept Deletable = requires(U u) {
{ delete u } -> std::same_as<void>;
};
//
// Unordered dynamic container with contiguous memory
// with fast deleting and adding
//
template <typename T> class Pool {
// Pair type and helper functions
using pair_t = std::pair<bool, T>;
static T &get_item(pair_t &X) { return std::get<1>(X); }
static bool is_valid(const pair_t &X) { return std::get<0>(X); }
static bool &validity(pair_t &X) { return std::get<0>(X); }
public:
// Forward declaration for Iterator
class Iterator;
Pool(size_t prealloc_size = 16) {
LOG_DEBUG(".");
Realloc(prealloc_size);
}
Pool(const Pool &p) = delete;
Pool(Pool &&p) = delete;
~Pool() {
LOG_DEBUG(".");
delete[] m_Pool;
}
template <typename U> void Add(U &&u) {
EnsureAddCapacity(1);
auto idx = m_FreeIdx.top();
get_item(m_Pool[idx]) = std::forward<U>(u);
validity(m_Pool[idx]) = true;
m_FreeIdx.pop();
m_Size++;
}
void Remove(Iterator &it) {
// Calculate the index from the iterator's pointer
size_t index = it.ptr - m_Pool;
// Validate the iterator points to a valid element
if (index >= m_Capacity || !is_valid(m_Pool[index])) {
throw std::logic_error("Invalid iterator for removal");
}
// Delete if deletable
if constexpr (Deletable<T>)
delete get_item(m_Pool[index]);
// Mark as invalid and add to free list
validity(m_Pool[index]) = false;
m_FreeIdx.push(index);
m_Size--;
// Advance iterator to next valid element
it.ptr++;
it.iter_until_valid();
}
void Remove(size_t index) {
index_check(index);
if constexpr (Deletable<T>)
delete get_item(m_Pool[index]);
validity(m_Pool[index]) = false;
m_Size--;
}
T &operator[](size_t index) {
index_check(index);
return get_item(m_Pool[index]);
}
const T &operator[](size_t index) const {
index_check(index);
return get_item(m_Pool[index]);
}
friend std::ostream &operator<<(std::ostream &os, const Pool &obj) {
os << "Pool( Size: " << obj.m_Size << ", capacity: " << obj.m_Capacity
<< "\n";
for (size_t i = 0; i < obj.m_Capacity; i++) {
os << "\t" << (std::get<bool>(obj.m_Pool[i]) ? "VALID" : "INVALID")
<< "\n";
}
os << "\n)";
return os;
}
auto Size() const { return m_Size; }
auto Capacity() const { return m_Capacity; }
// Iterating related
class Iterator {
friend class Pool; // Allow Pool to access private members
public:
Iterator(pair_t *start, pair_t *stop) : ptr(start), end_ptr(stop) {
iter_until_valid();
}
T &operator*() const { return get_item(*ptr); }
Iterator &operator++() {
if (ptr != end_ptr) {
ptr++;
iter_until_valid();
}
return *this;
}
bool operator!=(const Iterator &other) const { return ptr != other.ptr; }
bool operator==(const Iterator &other) const { return ptr == other.ptr; }
private:
pair_t *ptr = nullptr;
pair_t *end_ptr = nullptr;
void iter_until_valid() {
while (ptr < end_ptr && !is_valid(*ptr)) {
ptr++;
}
}
};
Iterator begin() { return Iterator(m_Pool, m_Pool + m_Capacity); }
Iterator end() { return Iterator(m_Pool + m_Capacity, m_Pool + m_Capacity); }
private:
pair_t *m_Pool = nullptr;
// TODO use unique_ptr
std::stack<size_t> m_FreeIdx;
size_t m_Capacity = 0;
size_t m_Size = 0;
void index_check(size_t index) const {
if (index >= m_Capacity) {
throw std::out_of_range("Out of range");
}
if (!is_valid(m_Pool[index])) {
throw std::logic_error("Invalid item");
}
}
void Realloc(size_t requested_capacity) {
// Calculate new capacity
size_t new_capacity = m_Capacity > 0 ? m_Capacity : 1;
while (new_capacity < requested_capacity) {
new_capacity *= 2;
}
LOG_DEBUG("Realloc from ", m_Capacity, " to ", new_capacity);
// Alloc new pool and copy from old pool
pair_t *new_pool = new pair_t[new_capacity];
size_t new_idx = 0;
size_t old_capacity = m_Capacity;
for (size_t old_idx = 0; old_idx < old_capacity; old_idx++) {
if (is_valid(m_Pool[old_idx])) {
new_pool[new_idx++] = std::move(m_Pool[old_idx]);
}
}
// Remaining new items are all free
while (new_idx < new_capacity) {
m_FreeIdx.push(new_idx);
validity(new_pool[new_idx++]) = false;
}
// Swap and free the old pool
std::swap(m_Pool, new_pool);
delete[] new_pool;
// Update state variables
m_Capacity = new_capacity;
// m_Size stays the same
}
void EnsureAddCapacity(size_t additional_elements) {
if (m_Size + additional_elements > m_Capacity) {
Realloc(m_Size + additional_elements);
}
}
};
} /* namespace array */