173 lines
4.1 KiB
C++
173 lines
4.1 KiB
C++
#pragma once
|
|
|
|
namespace lt::ecs {
|
|
|
|
/**
|
|
*
|
|
* @ref https://programmingpraxis.com/2012/03/09/sparse-sets/
|
|
*/
|
|
template<typename Identifier_T = uint32_t>
|
|
class TypeErasedSparseSet
|
|
{
|
|
public:
|
|
TypeErasedSparseSet() = default;
|
|
|
|
TypeErasedSparseSet(TypeErasedSparseSet &&) = default;
|
|
|
|
TypeErasedSparseSet(const TypeErasedSparseSet &) = default;
|
|
|
|
auto operator=(TypeErasedSparseSet &&) -> TypeErasedSparseSet & = default;
|
|
|
|
auto operator=(const TypeErasedSparseSet &) -> TypeErasedSparseSet & = default;
|
|
|
|
virtual ~TypeErasedSparseSet() = default;
|
|
|
|
virtual void remove(Identifier_T identifier) = 0;
|
|
};
|
|
|
|
template<typename Value_T, typename Identifier_T = uint32_t>
|
|
class SparseSet: public TypeErasedSparseSet<Identifier_T>
|
|
{
|
|
public:
|
|
using Dense_T = std::pair<Identifier_T, Value_T>;
|
|
|
|
static constexpr auto max_capacity = size_t { 1'000'000 };
|
|
|
|
static constexpr auto null_identifier = std::numeric_limits<Identifier_T>().max();
|
|
|
|
explicit SparseSet(size_t initial_capacity = 1)
|
|
{
|
|
ensure(
|
|
initial_capacity <= max_capacity,
|
|
"Failed to create SparseSet: capacity too large ({} > {})",
|
|
initial_capacity,
|
|
max_capacity
|
|
);
|
|
|
|
m_dense.reserve(initial_capacity);
|
|
m_sparse.resize(initial_capacity, null_identifier);
|
|
}
|
|
|
|
auto insert(Identifier_T identifier, Value_T value) -> Dense_T &
|
|
{
|
|
if (m_sparse.size() < identifier + 1)
|
|
{
|
|
auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2);
|
|
new_capacity = std::min(new_capacity, max_capacity);
|
|
|
|
// log_dbg("Increasing sparse vector size:", m_dead_count);
|
|
// log_dbg("\tdead_count: {}", m_dead_count);
|
|
// log_dbg("\talive_count: {}", m_alive_count);
|
|
// log_dbg("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
|
|
|
|
m_sparse.resize(new_capacity, null_identifier);
|
|
}
|
|
|
|
++m_alive_count;
|
|
m_sparse[identifier] = m_dense.size();
|
|
return m_dense.emplace_back(identifier, std::move(value));
|
|
}
|
|
|
|
/** @warn invalidates begin/end iterators
|
|
*
|
|
* @todo(Light): make it not invalidate the iterators >:c
|
|
*/
|
|
void remove(Identifier_T identifier) override
|
|
{
|
|
auto &idx = m_sparse[identifier];
|
|
auto &[entity, component] = m_dense[idx];
|
|
|
|
auto &[last_entity, last_component] = m_dense.back();
|
|
auto &last_idx = m_sparse[last_entity];
|
|
|
|
// removed entity is in dense's back, just pop and invalidate sparse[identifier]
|
|
if (entity == last_entity)
|
|
{
|
|
idx = null_identifier;
|
|
m_dense.pop_back();
|
|
}
|
|
else
|
|
{
|
|
// swap dense's 'back' to 'removed'
|
|
std::swap(component, last_component);
|
|
entity = last_entity;
|
|
|
|
// make sparse point to new idx
|
|
last_idx = idx;
|
|
|
|
// pop dense and invalidate sparse[identifier]
|
|
idx = null_identifier;
|
|
m_dense.pop_back();
|
|
}
|
|
|
|
++m_dead_count;
|
|
--m_alive_count;
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
m_dense.clear();
|
|
m_sparse.clear();
|
|
m_alive_count = 0;
|
|
}
|
|
|
|
[[nodiscard]] auto contains(Identifier_T identifier) const -> bool
|
|
{
|
|
return m_sparse.size() > identifier //
|
|
&& m_sparse[identifier] != null_identifier //
|
|
&& m_dense[m_sparse[identifier]].first == identifier;
|
|
}
|
|
|
|
auto begin() -> std::vector<Dense_T>::iterator
|
|
{
|
|
return m_dense.begin();
|
|
}
|
|
|
|
auto end() -> std::vector<Dense_T>::iterator
|
|
{
|
|
return m_dense.end();
|
|
}
|
|
|
|
[[nodiscard]] auto at(Identifier_T identifier) const -> const Dense_T &
|
|
{
|
|
return m_dense.at(m_sparse.at(identifier));
|
|
}
|
|
|
|
[[nodiscard]] auto at(Identifier_T identifier) -> Dense_T &
|
|
{
|
|
return m_dense.at(m_sparse.at(identifier));
|
|
}
|
|
|
|
/** @warn unsafe, for bound-checked access: use `.at` */
|
|
[[nodiscard]] auto &&operator[](this auto &&self, Identifier_T identifier)
|
|
{
|
|
using Self_T = decltype(self);
|
|
return std::forward<Self_T>(self).m_dense[std::forward<Self_T>(self).m_sparse[identifier]];
|
|
}
|
|
|
|
[[nodiscard]] auto get_size() const noexcept -> size_t
|
|
{
|
|
return m_alive_count;
|
|
}
|
|
|
|
[[nodiscard]] auto get_capacity() const noexcept -> size_t
|
|
{
|
|
return m_sparse.capacity();
|
|
}
|
|
|
|
[[nodiscard]] auto is_empty() const noexcept -> bool
|
|
{
|
|
return m_dense.empty();
|
|
}
|
|
|
|
private:
|
|
std::vector<Dense_T> m_dense;
|
|
|
|
std::vector<Identifier_T> m_sparse;
|
|
|
|
size_t m_alive_count {};
|
|
|
|
size_t m_dead_count {};
|
|
};
|
|
|
|
} // namespace lt::ecs
|