Skip to main content

engine/ecs/EntityManager.ixx File

Central manager for entity lifecycle and component storage. More...

Included Headers

#include <cassert> #include <generator> #include <memory> #include <ranges> #include <vector> #include <helios/helios_config.h> #include <helios.engine.ecs.ComponentOpsRegistry> #include <helios.engine.ecs.EntityHandle> #include <helios.engine.ecs.EntityRegistry> #include <helios.engine.ecs.ComponentOps> #include <helios.engine.ecs.Traits> #include <helios.engine.ecs.SparseSet> #include <helios.core.data> #include <helios.engine.ecs.types>

Namespaces Index

namespacehelios
namespaceengine

Main engine module aggregating core infrastructure and game systems. More...

namespaceecs

Core Entity-Component-System architecture. More...

Classes Index

classEntityManager

Manages entities and their associated components. More...

Description

Central manager for entity lifecycle and component storage.

File Listing

The file content with the documentation metadata removed is:

1/**
2 * @file EntityManager.ixx
3 * @brief Central manager for entity lifecycle and component storage.
4 */
5module;
6
7#include <cassert>
8#include <generator>
9#include <memory>
10#include <ranges>
11#include <vector>
12#include <helios/helios_config.h>
13
14export module helios.engine.ecs.EntityManager;
15
16import helios.engine.ecs.types;
17import helios.core.data;
18import helios.engine.ecs.SparseSet;
19import helios.engine.ecs.Traits;
20import helios.engine.ecs.EntityRegistry;
21import helios.engine.ecs.EntityHandle;
22import helios.engine.ecs.ComponentOps;
23import helios.engine.ecs.ComponentOpsRegistry;
24
25namespace {
26 }
27
28export namespace helios::engine::ecs {
29 using namespace helios::engine::ecs;
30
31 /**
32 * @brief Manages entities and their associated components.
33 *
34 * `EntityManager` provides a unified interface for creating entities and
35 * attaching/retrieving components. It delegates handle management to an
36 * `EntityRegistry` and stores component data in type-specific `SparseSet`
37 * containers.
38 *
39 * ## Responsibilities
40 *
41 * - **Entity Creation:** Delegates to `EntityRegistry` for handle allocation.
42 * - **Entity Destruction:** Removes all components and invalidates the handle.
43 * - **Component Storage:** Maintains a vector of `SparseSet` instances, one per
44 * component type, indexed by `TypeIndexer`.
45 *
46 * ## Usage
47 *
48 * ```cpp
49 * EntityRegistry registry;
50 * EntityManager manager(registry);
51 *
52 * auto entity = manager.create();
53 * auto* transform = manager.emplace<TransformComponent>(entity, glm::vec3{0.0f});
54 *
55 * if (manager.has<TransformComponent>(entity)) {
56 * auto* t = manager.get<TransformComponent>(entity);
57 * }
58 *
59 * manager.remove<TransformComponent>(entity); // Remove single component
60 * manager.destroy(entity); // Destroy entity and all components
61 * ```
62 *
63 * @see EntityRegistry
64 * @see SparseSet
65 * @see EntityHandle
66 */
68 /**
69 * @brief Component storage indexed by type ID.
70 *
71 * @odo sort after size()
72 */
73 std::vector<std::unique_ptr<SparseSetBase>> components_;
74
75 /**
76 * @brief Reference to the entity registry for handle management.
77 */
78 EntityRegistry& registry_;
79
80 /**
81 * @brief initial capacity for the underlying sparsesets.
82 */
83 const size_t capacity_;
84
85 public:
86
87 /**
88 * @brief Constructs an EntityManager with the given registry.
89 *
90 * @param registry The EntityRegistry used for handle allocation and validation.
91 */
92 explicit EntityManager(EntityRegistry& registry, size_t capacity = ENTITY_MANAGER_DEFAULT_CAPACITY)
93 : registry_(registry), capacity_(capacity) {}
94
95 /**
96 * @brief Creates a new entity.
97 *
98 * Delegates to the underlying `EntityRegistry` to allocate a new handle.
99 *
100 * @return A valid `EntityHandle` for the newly created entity.
101 *
102 * @see EntityRegistry::create
103 */
104 [[nodiscard]] EntityHandle create() {
105 return registry_.create();
106 }
107
108 /**
109 * @brief Checks if an entity handle is valid.
110 *
111 * @param handle The handle to validate.
112 *
113 * @return `true` if the handle refers to a living entity.
114 */
115 [[nodiscard]] bool isValid(const EntityHandle handle) const noexcept {
116 return registry_.isValid(handle);
117 }
118
119 /**
120 * @brief Checks if an entity ID is valid.
121 *
122 * @param entityId The entity ID to validate.
123 *
124 * @return `true` if the entity ID refers to a living entity.
125 */
126 [[nodiscard]] bool isValid(const EntityId entityId) const noexcept {
127 return registry_.isValid(handle(entityId));
128 }
129
130 /**
131 * @brief Destroys an entity and invalidates its handle.
132 *
133 * @details Increments the entity's version in the registry, making all
134 * existing handles to this entity stale. Does automatically remove
135 * its components from storage.
136 *
137 * @param handle The handle of the entity to destroy.
138 *
139 * @return `true` if the entity was destroyed, `false` if already invalid.
140 */
141 [[nodiscard]] bool destroy(const EntityHandle handle) {
142
143 if (!registry_.isValid(handle)) {
144 return false;
145 }
146
147 for (size_t i = 0; i < components_.size(); i++) {
148
149 if (!components_[i]) {
150 continue;
151 }
152 const auto typeId = ComponentTypeId{i};
153 if (void* rawCmp = raw(handle, typeId)) {
154 if (const auto& ops = helios::engine::ecs::ComponentOpsRegistry::ops(typeId); ops.onRemove) {
155 ops.onRemove(rawCmp);
156 }
157 }
158
159 components_[i]->remove(handle.entityId);
160 }
161
162 registry_.destroy(handle);
163
164 return true;
165 }
166
167 /**
168 * @brief Retrieves a component for the given entity.
169 *
170 * @tparam T The component type to retrieve.
171 *
172 * @param handle The entity whose component to retrieve.
173 *
174 * @return Pointer to the component, or `nullptr` if the entity is invalid
175 * or does not have the requested component.
176 */
177 template<typename T>
178 [[nodiscard]] T* get(const EntityHandle handle) const {
179 if (!has<T>(handle)) {
180 return nullptr;
181 }
182
183 const auto entityId = handle.entityId;
184 const auto typeId = ComponentTypeId::id<T>().value();
185
186 auto* sparseSet = static_cast<SparseSet<T>*>(components_[typeId].get());
187
188 return sparseSet->get(entityId);
189 }
190
191 /**
192 * @brief Returns the SparseSet for a component type.
193 *
194 * @tparam T The component type.
195 *
196 * @return Pointer to the SparseSet, or `nullptr` if the type has no storage.
197 */
198 template<typename T>
199 [[nodiscard]] SparseSet<T>* getSparseSet() {
200
201 const auto typeId = ComponentTypeId::id<T>().value();
202
203 if (typeId >= components_.size()) {
204 return nullptr;
205 }
206
207 return static_cast<SparseSet<T>*>(components_[typeId].get());
208 }
209
210 /**
211 * @brief Returns the SparseSet for a component type (const).
212 *
213 * @tparam T The component type.
214 *
215 * @return Const pointer to the SparseSet, or `nullptr` if the type has no storage.
216 */
217 template<typename T>
218 [[nodiscard]] const SparseSet<T>* getSparseSet() const {
219
220 const auto typeId = ComponentTypeId::id<T>().value();
221
222 if (typeId >= components_.size()) {
223 return nullptr;
224 }
225
226 return static_cast<SparseSet<T>*>(components_[typeId].get());
227 }
228
229 /**
230 * @brief Checks whether an entity has a specific component.
231 *
232 * @tparam T The component type to check for.
233 *
234 * @param handle The entity to query.
235 *
236 * @return `true` if the entity has the component, `false` if the handle
237 * is invalid or the component is not attached.
238 */
239 template<typename T>
240 [[nodiscard]] bool has(const EntityHandle handle) const {
241 if (!registry_.isValid(handle)) {
242 return false;
243 }
244
245 const auto typeId = ComponentTypeId::id<T>().value();
246
247 if (typeId < components_.size() && components_[typeId]) {
248 return components_[typeId]->contains(handle.entityId);
249 }
250
251 return false;
252 }
253
254 /**
255 * @brief Checks whether an entity has a component by type ID.
256 *
257 * @param handle The entity to query.
258 * @param typeId The component type identifier.
259 *
260 * @return `true` if the entity has the component, `false` otherwise.
261 */
262 [[nodiscard]] bool has(const EntityHandle handle, const helios::engine::ecs::types::ComponentTypeId typeId) const {
263 if (!registry_.isValid(handle)) {
264 return false;
265 }
266
267 const auto tvalue = typeId.value();
268
269 if (tvalue < components_.size() && components_[tvalue]) {
270 return components_[tvalue]->contains(handle.entityId);
271 }
272
273 return false;
274 }
275
276 /**
277 * @brief Enables a component by type ID.
278 *
279 * @details Calls the component's `enable()` method if registered.
280 *
281 * @param handle The entity whose component to enable.
282 * @param typeId The component type identifier.
283 */
285 enable(handle, typeId, true);
286 }
287
288 /**
289 * @brief Disables a component by type ID.
290 *
291 * @details Calls the component's `disable()` method if registered.
292 *
293 * @param handle The entity whose component to disable.
294 * @param typeId The component type identifier.
295 */
297 enable(handle, typeId, false);
298 }
299
300 /**
301 * @brief Enables or disables a component by type ID.
302 *
303 * @param handle The entity whose component to modify.
304 * @param typeId The component type identifier.
305 * @param enable `true` to enable, `false` to disable.
306 */
307 void enable(const EntityHandle handle, const helios::engine::ecs::types::ComponentTypeId typeId, const bool enable) const {
308
309 if (!has(handle, typeId)) {
310 return;
311 }
312
313 void* rawCmp = raw(handle, typeId);
314 if (!rawCmp) {
315 return;
316 }
317 const auto& ops = helios::engine::ecs::ComponentOpsRegistry::ops(typeId);
318
319 if (enable && ops.enable) {
320 ops.enable(rawCmp);
321 } else if (!enable && ops.disable) {
322 ops.disable(rawCmp);
323 }
324 }
325
326 /**
327 * @brief Constructs and attaches a component to an entity.
328 *
329 * If the component type has not been registered yet, a new `SparseSet`
330 * is created. The component is constructed in-place with the provided
331 * arguments. Return nullptre if the component already exists or if the
332 * handle was invalid.
333 *
334 * @tparam T The component type to emplace.
335 * @tparam Args Constructor argument types.
336 *
337 * @param handle The entity to attach the component to.
338 * @param args Arguments forwarded to the component constructor.
339 *
340 * @return Pointer to the newly created component, or `nullptr` if the
341 * handle is invalid.
342 */
343 template<typename T, typename... Args>
344 T* emplace(const EntityHandle handle, Args&& ...args) {
345
346 if (!registry_.isValid(handle)) {
347 return nullptr;
348 }
349
350 const auto entityId = handle.entityId;
351
352 const auto typeId = ComponentTypeId::id<T>().value();
353
354 if (typeId >= components_.size()) {
355 components_.resize(typeId + 1);
356 }
357
358 if (!components_[typeId]) {
359 components_[typeId] = std::make_unique<SparseSet<T>>(capacity_);
360 }
361
362 auto* sparseSet = static_cast<SparseSet<T>*>(components_[typeId].get());
363
364 if (sparseSet->contains(entityId)) {
365 return nullptr;
366 }
367
368 return sparseSet->emplace(entityId, std::forward<Args>(args)...);
369 }
370
371 /**
372 * @brief Returns existing component or creates a new one.
373 *
374 * @tparam T The component type.
375 * @tparam Args Constructor argument types.
376 *
377 * @param handle The entity.
378 * @param args Arguments forwarded to the constructor if creating.
379 *
380 * @return Pointer to the existing or newly created component,
381 * or `nullptr` if the handle is invalid.
382 */
383 template<typename T, typename... Args>
384 T* emplaceOrGet(const EntityHandle handle, Args&& ...args) {
385
386 if (!registry_.isValid(handle)) {
387 return nullptr;
388 }
389
390 auto* raw = emplace<T>(handle, std::forward<Args>(args)...);
391
392 if (!raw) {
393 return get<T>(handle);
394 }
395
396 return nullptr;
397 }
398
399 /**
400 * @brief Removes a specific component from an entity.
401 *
402 * Unlike `destroy()`, this only removes a single component type while
403 * keeping the entity and other components intact.
404 *
405 * @tparam T The component type to remove.
406 *
407 * @param handle The entity whose component to remove.
408 *
409 * @return `true` if the component was removed, `false` if the handle was
410 * invalid, the component was not attached, or removal was blocked
411 * by `onRemove`.
412 *
413 * @see destroy
414 * @see SparseSet::remove
415 */
416 template<typename T>
417 bool remove(const EntityHandle& handle) {
418
419 if (!has<T>(handle)) {
420 return false;
421 }
422
423 const auto typeId = ComponentTypeId::id<T>();
424 void* rawCmp = raw(handle, typeId);
425 if (!rawCmp) {
426 return false;
427 }
428 const auto& ops = helios::engine::ecs::ComponentOpsRegistry::ops(typeId);
429
430 if (ops.onRemove && !ops.onRemove(rawCmp)) {
431 return false;
432 }
433
434 return components_[typeId.value()]->remove(handle.entityId);
435 }
436
437 /**
438 * @brief Returns a generator over all component type IDs attached to an entity.
439 *
440 * @param handle The entity to query.
441 *
442 * @return Generator yielding ComponentTypeId for each attached component.
443 */
444 std::generator<helios::engine::ecs::types::ComponentTypeId>
446 if (!registry_.isValid(handle)) {
447 co_return;
448 }
449
450 for (size_t i = 0; i < components_.size(); i++) {
451 if (components_[i] && components_[i]->contains(handle.entityId)) {
452 co_yield ComponentTypeId{i};
453 }
454 }
455 }
456
457 /**
458 * @brief Clones all components from source entity to target entity.
459 *
460 * @details Iterates through all components on the source and copies them
461 * to the target using the registered clone function. Skips components
462 * that already exist on the target.
463 *
464 * @param source The entity to clone from.
465 * @param target The entity to clone to.
466 */
467 void clone(const EntityHandle source, const EntityHandle target) {
468
469 if (!registry_.isValid(source)) {
470 return;
471 }
472
473 for (auto typeId : componentTypeIds(source)) {
474
475 if (!has(target, typeId)) {
476
477 const auto& ops = ComponentOpsRegistry::ops(typeId);
478
479 const void* sourceCmp = raw(source, typeId);
480
481 if (sourceCmp && ops.clone) {
482 ops.clone(this, sourceCmp, &target);
483 }
484
485 }
486
487 }
488 }
489
490 /**
491 * @brief Returns raw void pointer to a component.
492 *
493 * @param handle The entity.
494 * @param typeId The component type identifier.
495 *
496 * @return Raw pointer to the component, or `nullptr` if not found.
497 */
498 [[nodiscard]] void* raw(const EntityHandle handle, const helios::engine::ecs::types::ComponentTypeId typeId ) const {
499 if (!has(handle, typeId)) {
500 return nullptr;
501 }
502
503 return components_[typeId.value()]->raw(handle.entityId);
504 }
505
506 /**
507 * @brief Reconstructs an EntityHandle from an EntityId.
508 *
509 * @param entityId The entity ID.
510 *
511 * @return EntityHandle with current version from the registry.
512 */
513 [[nodiscard]] EntityHandle handle(const EntityId entityId) const {
514 return EntityHandle{entityId, registry_.version(entityId)};
515 }
516
517
518 };
519
520
521}

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.