// Copyright (c) November 2025 Flix-Olivier Dumas. All rights reserved.
// Licensed under the terms described in the LICENSE file.
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
template
class DenseArray {
public:
DenseArray(std::size_t initCapacity = DEFAULT_CAPACITY) noexcept {
dense_.reserve(initCapacity);
}
DenseArray(const DenseArray&) = default;
DenseArray& operator=(const DenseArray&) = default;
DenseArray(DenseArray&&) noexcept = default;
DenseArray& operator=(DenseArray&&) noexcept = default;
~DenseArray() = default;
public:
void push_back(T value) noexcept { dense_.push_back(value); }
void push_back(T&& value) noexcept { dense_.push_back(std::move(value)); }
template
void replace(std::size_t index, U&& value) noexcept {
assert(index < dense_.size());
dense_[index] = std::forward(value);
}
auto emplace_back() ->
std::enable_if_t<
std::is_default_constructible_v,
void> { dense_.emplace_back(); }
void swap_and_pop(std::size_t index) noexcept {
std::swap(dense_[index], dense_[dense_.size() - 1]);
dense_.pop_back();
}
public:
bool is_empty() const noexcept { return dense_.empty(); }
std::size_t capacity() const noexcept { return dense_.capacity(); }
std::size_t size() const noexcept { return dense_.size(); }
std::size_t max_size() const noexcept { return dense_.max_size(); }
void clear() noexcept { dense_.clear(); }
auto begin() noexcept { return dense_.begin(); }
auto end() noexcept { return dense_.end(); }
auto begin() const noexcept { return dense_.begin(); }
auto end() const noexcept { return dense_.end(); }
public:
T& operator[](std::size_t index) noexcept {
assert(index < dense_.size());
return dense_[index];
}
const T& operator[](std::size_t index) const noexcept {
assert(index < dense_.size());
return dense_[index];
}
private:
static constexpr std::size_t DEFAULT_CAPACITY = 262143;
std::vector dense_;
std::vector dense_entities_;
//en gros, faut mapper un component = quel entit pour O(1)
};
template
struct is_index_type {
static constexpr bool value =
std::disjunction_v<
std::is_same,
std::is_same,
std::is_same,
std::is_same
>;
}; template
inline constexpr bool is_index_type_v = is_index_type::value;
template
class SparseSet {
public:
template
void insert(std::size_t id, U&& value) noexcept {
dense_.push_back(std::forward(value));
sparse_[id] = dense_.size() - 1;
//refaire otute la logique avec le noueau dense_entities_ de dense;
}
template
auto emplace_default(U id) noexcept ->
std::enable_if_t<
is_index_type_v,
void> {
dense_.emplace_back();
sparse_[id] = dense_.size() - 1;
}
template
auto emplace(Ts... ids) noexcept ->
std::enable_if_t<
(sizeof...(ids) > 0) &&
(is_index_type_v && ...),
void> { ((emplace_default(ids)), ...); }
//faire version avec variadic
private:
std::vector sparse_;
DenseArray dense_;
//pas optimis, je pourrais faire
//std::vector dense_;
//[entit id][component id] -> std::vector
//on ajoute un component a un entit
//on regarde si il est deja prsent
//en prenant l'element a l'id entit du sparse_
//et en le mettant dans le dense_, si null, on ajoute
//ensuite, quand on veut retirer un lment
//on apelle remove_swap()
//ensuite, on assigne le index du component
//a l'entit retir a l'entit qui pointait
//sur la case .size() - 1 (dernire).
};
// not yet, theres a better version
template
struct Sparse3 {
private:
static constexpr std::size_t DEFAULT_DENSE_CAPACITY = 2048;
static constexpr std::size_t DEFAULT_SPARSE_CAPACITY = 16384;
// EN GROS, LE SPARSE NE DEVRAIS JAMAIS FAIRE CRASH LE PROGRAMME
// FAIRE UTILISE STD::OPTIONAL ET RETOURNER SI LE ID EST TROP GRAND
// NE JAMAIS RESIZE LE TABLEAU SI LE ID EST TROP GRAND, RETIRER LA
// LIGNE QUI FAIT CA
// if (entity_id >= sparse_.size()) sparse_.resize(entity_id + 1, SIZE_MAX);
inline constexpr void check_valid_entity_id(std::size_t entity_id) const {
if (!is_valid_entity_id(entity_id))
throw std::out_of_range(
std::string("Entity ID ") + std::to_string(entity_id) +
" out of bounds (max " + std::to_string(sparse_.size() - 1) + ")"
);
}
inline constexpr bool is_valid_entity_id(std::size_t entity_id) const {
return entity_id < sparse_.size();
}
public:
Sparse3(std::size_t init_dense_capacity = DEFAULT_DENSE_CAPACITY,
std::size_t init_sparse_capacity = DEFAULT_SPARSE_CAPACITY) {
dense_.reserve(init_dense_capacity);
binding_.reserve(init_dense_capacity);
sparse_.reserve(init_sparse_capacity);
sparse_.resize(init_sparse_capacity, SIZE_MAX);
}
Sparse3(const Sparse3&) = default;
Sparse3& operator=(const Sparse3&) = default;
Sparse3(Sparse3&&) noexcept = default;
Sparse3& operator=(Sparse3&&) noexcept = default;
~Sparse3() = default;
public:
template
void insert(std::size_t entity_id, U&& component) noexcept {
dense_.push_back(std::forward(component));
std::size_t component_index = dense_.size() - 1;
if (entity_id >= sparse_.size())
sparse_.resize(entity_id + 1, SIZE_MAX);
sparse_[entity_id] = component_index;
binding_.push_back(entity_id);
}
void emplace_default(std::size_t entity_id) noexcept {
dense_.emplace_back();
std::size_t component_index = dense_.size() - 1;
if (entity_id >= sparse_.size())
sparse_.resize(entity_id + 1, SIZE_MAX);
sparse_[entity_id] = component_index;
binding_.push_back(entity_id);
}
void remove_swap(std::size_t entity_id) {
check_valid_entity_id(entity_id);
std::size_t component_index = sparse_[entity_id];
std::size_t last_component_index = dense_.size() - 1;
std::size_t last_entity_id = binding_[last_component_index];
std::swap(dense_[component_index], dense_[dense_.size() - 1]);
dense_.pop_back();
std::swap(binding_[component_index], binding_[last_component_index]);
binding_.pop_back();
sparse_[last_entity_id] = component_index;
sparse_[entity_id] = SIZE_MAX;
}
public:
bool contains(std::size_t entity_id) const {
return is_valid_entity_id(entity_id) && sparse_[entity_id] != SIZE_MAX;
}
std::size_t count() const noexcept { return dense_.size(); }
std::size_t capacity() const noexcept { return sparse_.capacity(); }
std::size_t is_empty() const noexcept { return sparse_.empty(); }
void clear_sparse() noexcept { sparse_.clear(); }
void clear_dense() noexcept { dense_.clear(); }
void clear_binding() noexcept { binding_.clear(); }
void reserve(std::size_t new_capacity) noexcept { sparse_.reserve(new_capacity); }
void shrink_sparse_to_fit() noexcept { sparse_.shrink_to_fit(); }
void shrink_dense_to_fit() noexcept { dense_.shrink_to_fit(); }
void shrink_binding_to_fit() noexcept { binding_.shrink_to_fit(); }
public:
auto begin() noexcept { return dense_.begin(); }
auto end() noexcept { return dense_.end(); }
auto begin() const noexcept { return dense_.begin(); }
auto end() const noexcept { return dense_.end(); }
public:
T& operator[](std::size_t entity_id) {
check_valid_entity_id(entity_id);
return dense_[sparse_[entity_id]];
}
const T& operator[](std::size_t entity_id) const {
check_valid_entity_id(entity_id);
return dense_[sparse_[entity_id]];
}
T& get(std::size_t entity_id) {
check_valid_entity_id(entity_id);
return dense_[sparse_[entity_id]];
}
private:
std::vector sparse_; // contient un entity_id -> component_id
std::vector binding_; // contient un composante_id -> entity_id
std::vector dense_; //contient un component_id -> component (T)
};
template
struct Sparse {
private:
static constexpr std::size_t DEFAULT_DENSE_CAPACITY = 2048;
static constexpr std::size_t DEFAULT_SPARSE_CAPACITY = 16384;
inline void error_not_enough_capacity(const std::string& context, size_t required, size_t actual) {
std::cerr