Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
sparse_storage.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/assert.hpp>
7
8#include <algorithm>
9#include <concepts>
10#include <cstddef>
11#include <span>
12#include <type_traits>
13#include <utility>
14
15namespace helios::ecs {
16
17/**
18 * @brief Concrete sparse-set storage for a single component type
19 * (non-polymorphic).
20 * @details Wraps a `SparseSet<T, Entity::IndexType>` and provides
21 * entity-oriented API. Not designed to be a base class.
22 * @tparam T Component type
23 */
24template <ComponentTrait T>
26public:
27 constexpr SparseComponentStorage() = default;
29 constexpr SparseComponentStorage(SparseComponentStorage&&) noexcept = default;
30 constexpr ~SparseComponentStorage() = default;
31
32 constexpr SparseComponentStorage& operator=(const SparseComponentStorage&) =
33 default;
34 constexpr SparseComponentStorage& operator=(
35 SparseComponentStorage&&) noexcept = default;
36
37 /// @brief Removes all component instances.
38 constexpr void Clear() noexcept { storage_.Clear(); }
39
40 /**
41 * @brief Removes the component for the given entity.
42 * @warning Triggers assertion in next cases:
43 * - Entity is invalid.
44 * - Entity does not have the component.
45 * @param entity Entity
46 */
47 constexpr void Remove(Entity entity);
48
49 /**
50 * @brief Tries to remove the component for the given entity.
51 * @warning Triggers assertion if entity is invalid.
52 * @param entity Entity
53 * @return True if removed, false if entity did not have the component
54 */
55 constexpr bool TryRemove(Entity entity);
56
57 /**
58 * @brief Inserts or replaces a component for the given entity.
59 * @warning Triggers assertion if entity is invalid.
60 * @tparam U Component type, must be the same as `T`
61 * @param entity Entity
62 * @param component Component value
63 */
64 template <ComponentTrait U = T>
65 requires std::same_as<std::remove_cvref_t<U>, T>
66 constexpr void Set(Entity entity, U&& component);
67
68 /**
69 * @brief Tries to insert a component. Returns false if entity already has it.
70 * @warning Triggers assertion if entity is invalid.
71 * tparam U Component type, must be the same as `T`
72 * @param entity Entity
73 * @param component Component value
74 * @return True if inserted
75 */
76 template <ComponentTrait U = T>
77 requires std::same_as<std::remove_cvref_t<U>, T>
78 constexpr bool TrySet(Entity entity, U&& component);
79
80 /**
81 * @brief Constructs a component in-place for the given entity.
82 * @warning Triggers assertion if entity is invalid.
83 * @tparam Args Constructor argument types
84 * @param entity Entity
85 * @param args Arguments
86 */
87 template <typename... Args>
88 requires std::constructible_from<T, Args...>
89 constexpr void Emplace(Entity entity, Args&&... args);
90
91 /**
92 * @brief Tries to emplace a component. Returns false if entity already has
93 * it.
94 * @warning Triggers assertion if entity is invalid.
95 * @tparam Args Constructor argument types
96 * @param entity Entity
97 * @param args Arguments
98 * @return True if emplaced
99 */
100 template <typename... Args>
101 requires std::constructible_from<T, Args...>
102 constexpr bool TryEmplace(Entity entity, Args&&... args);
103
104 /**
105 * @brief Gets a mutable reference to the component for the entity.
106 * @warning Triggers assertion in next cases:
107 * - Entity is invalid.
108 * - Entity does not have the component.
109 * @param entity Entity
110 * @return Mutable reference to component
111 */
112 [[nodiscard]] constexpr T& Get(Entity entity) noexcept;
113
114 /**
115 * @brief Gets a const reference to the component for the entity.
116 * @warning Triggers assertion in next cases:
117 * - Entity is invalid
118 * - Entity does not have the component
119 * @param entity Entity
120 * @return Const reference to component
121 */
122 [[nodiscard]] constexpr const T& Get(Entity entity) const noexcept;
123
124 /**
125 * @brief Tries to get a mutable pointer to the component for the entity.
126 * @warning Triggers assertion if entity is invalid.
127 * @param entity Entity
128 * @return Pointer to component or `nullptr` if entity does not have the
129 * component
130 */
131 [[nodiscard]] constexpr T* TryGet(Entity entity) noexcept;
132
133 /**
134 * @brief Tries to get a const pointer to the component for the entity.
135 * @warning Triggers assertion if entity is invalid.
136 * @param entity Entity
137 * @return Const pointer to component or `nullptr` if entity does not have the
138 * component
139 */
140 [[nodiscard]] constexpr const T* TryGet(Entity entity) const noexcept;
141
142 /**
143 * @brief Checks if the entity has the component.
144 * @warning Triggers assertion if entity is invalid.
145 * @param entity Entity
146 * @return True if entity has the component, false otherwise
147 */
148 [[nodiscard]] constexpr bool Contains(Entity entity) const noexcept;
149
150 /**
151 * @brief Gets the number of stored components.
152 * @return Number of components
153 */
154 [[nodiscard]] constexpr size_t Size() const noexcept {
155 return storage_.Size();
156 }
157
158 /**
159 * @brief Gets a span over all dense component data.
160 * @return Span of components
161 */
162 [[nodiscard]] constexpr auto Data() noexcept -> std::span<T> {
163 return storage_.Data();
164 }
165
166 /**
167 * @brief Gets a span over all dense component data (const).
168 * @return Span of const components
169 */
170 [[nodiscard]] constexpr auto Data() const noexcept -> std::span<const T> {
171 return storage_.Data();
172 }
173
174private:
175 using SparseSetType = container::SparseSet<T, Entity::IndexType>;
176
177 SparseSetType storage_;
178};
179
180template <ComponentTrait T>
182 HELIOS_ASSERT(entity.Valid(), "Entity '{}' is invalid!", entity);
183 HELIOS_ASSERT(storage_.Contains(entity.Index()),
184 "Entity '{}' does not have sparse component '{}'!", entity,
186 storage_.Remove(entity.Index());
187}
188
189template <ComponentTrait T>
191 HELIOS_ASSERT(entity.Valid(), "Entity '{}' is invalid!", entity);
192 if (!storage_.Contains(entity.Index())) {
193 return false;
194 }
195 storage_.Remove(entity.Index());
196 return true;
197}
198
199template <ComponentTrait T>
200template <ComponentTrait U>
201 requires std::same_as<std::remove_cvref_t<U>, T>
202constexpr void SparseComponentStorage<T>::Set(Entity entity, U&& component) {
203 HELIOS_ASSERT(entity.Valid(), "Entity '{}' is invalid!", entity);
204 if (storage_.Contains(entity.Index())) {
205 storage_.Get(entity.Index()) = std::forward<U>(component);
206 } else {
207 storage_.Insert(entity.Index(), std::forward<U>(component));
208 }
209}
210
211template <ComponentTrait T>
212template <ComponentTrait U>
213 requires std::same_as<std::remove_cvref_t<U>, T>
214constexpr bool SparseComponentStorage<T>::TrySet(Entity entity, U&& component) {
215 HELIOS_ASSERT(entity.Valid(), "Entity '{}' is invalid!", entity);
216 if (storage_.Contains(entity.Index())) {
217 return false;
218 }
219 storage_.Insert(entity.Index(), std::forward<U>(component));
220 return true;
221}
222
223template <ComponentTrait T>
224template <typename... Args>
225 requires std::constructible_from<T, Args...>
227 Args&&... args) {
228 HELIOS_ASSERT(entity.Valid(), "Entity '{}' is invalid!", entity);
229 if (storage_.Contains(entity.Index())) {
230 auto& slot = storage_.Get(entity.Index());
231 std::destroy_at(&slot);
232 std::construct_at(&slot, std::forward<Args>(args)...);
233 } else {
234 storage_.Emplace(entity.Index(), std::forward<Args>(args)...);
235 }
236}
237
238template <ComponentTrait T>
239template <typename... Args>
240 requires std::constructible_from<T, Args...>
242 Args&&... args) {
243 HELIOS_ASSERT(entity.Valid(), "Entity '{}' is invalid!", entity);
244 if (storage_.Contains(entity.Index())) {
245 return false;
246 }
247 storage_.Emplace(entity.Index(), std::forward<Args>(args)...);
248 return true;
249}
250
251template <ComponentTrait T>
252constexpr T& SparseComponentStorage<T>::Get(Entity entity) noexcept {
253 HELIOS_ASSERT(entity.Valid(), "Entity '{}' is invalid!", entity);
254 HELIOS_ASSERT(storage_.Contains(entity.Index()),
255 "Entity '{}' does not have sparse component '{}'!", entity,
257 return storage_.Get(entity.Index());
258}
259
260template <ComponentTrait T>
262 Entity entity) const noexcept {
263 HELIOS_ASSERT(entity.Valid(), "Entity '{}' is invalid!", entity);
264 HELIOS_ASSERT(storage_.Contains(entity.Index()),
265 "Entity '{}' does not have sparse component '{}'!", entity,
267 return storage_.Get(entity.Index());
268}
269
270template <ComponentTrait T>
271constexpr T* SparseComponentStorage<T>::TryGet(Entity entity) noexcept {
272 HELIOS_ASSERT(entity.Valid(), "Entity '{}' is invalid!", entity);
273 return storage_.TryGet(entity.Index());
274}
275
276template <ComponentTrait T>
278 Entity entity) const noexcept {
279 HELIOS_ASSERT(entity.Valid(), "Entity '{}' is invalid!", entity);
280 return storage_.TryGet(entity.Index());
281}
282
283template <ComponentTrait T>
285 Entity entity) const noexcept {
286 HELIOS_ASSERT(entity.Valid(), "Entity '{}' is invalid!", entity);
287 return storage_.Contains(entity.Index());
288}
289
290} // namespace helios::ecs
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
A sparse set data structure for efficient mapping of sparse indices to dense storage of values.
Unique identifier for entities with generation counter to handle recycling.
Definition entity.hpp:22
constexpr bool Valid() const noexcept
Checks if the entity is valid.
Definition entity.hpp:64
constexpr IndexType Index() const noexcept
Gets the index component of the entity.
Definition entity.hpp:82
constexpr bool TrySet(Entity entity, U &&component)
Tries to insert a component. Returns false if entity already has it.
constexpr bool Contains(Entity entity) const noexcept
Checks if the entity has the component.
constexpr void Clear() noexcept
Removes all component instances.
constexpr size_t Size() const noexcept
Gets the number of stored components.
constexpr auto Data() noexcept -> std::span< T >
Gets a span over all dense component data.
constexpr T & Get(Entity entity) noexcept
Gets a mutable reference to the component for the entity.
constexpr bool TryRemove(Entity entity)
Tries to remove the component for the given entity.
constexpr void Emplace(Entity entity, Args &&... args)
Constructs a component in-place for the given entity.
constexpr void Set(Entity entity, U &&component)
Inserts or replaces a component for the given entity.
constexpr auto Data() const noexcept -> std::span< const T >
Gets a span over all dense component data (const).
constexpr SparseComponentStorage(SparseComponentStorage &&) noexcept=default
constexpr T * TryGet(Entity entity) noexcept
Tries to get a mutable pointer to the component for the entity.
constexpr SparseComponentStorage(const SparseComponentStorage &)=default
constexpr bool TryEmplace(Entity entity, Args &&... args)
Tries to emplace a component. Returns false if entity already has it.
constexpr void Remove(Entity entity)
Removes the component for the given entity.
constexpr SparseComponentStorage()=default
constexpr std::string_view ComponentNameOf() noexcept
Component name for debugging and serialization.
Definition component.hpp:76
STL namespace.