Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
archetype.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/core_pch.hpp>
4
10
11#include <algorithm>
12#include <cstddef>
13#include <memory>
14#include <span>
15#include <unordered_map>
16#include <unordered_set>
17#include <utility>
18#include <vector>
19
21
22/**
23 * @brief Key for archetype edge transitions.
24 * @details Represents a component add or remove operation for edge caching.
25 */
27 ComponentTypeId component_id = 0; ///< Component type being added or removed
28 bool is_add = true; ///< True for add operation, false for remove
29
30 constexpr bool operator==(const ArchetypeEdgeKey&) const noexcept = default;
31};
32
33/**
34 * @brief Hash function for ArchetypeEdgeKey.
35 */
37 [[nodiscard]] size_t operator()(const ArchetypeEdgeKey& key) const noexcept {
38 // Combine component_id hash with is_add flag using different magic constants
39 const size_t component_hash = std::hash<ComponentTypeId>{}(key.component_id);
40 return key.is_add ? (component_hash ^ 0x9e3779b9UZ) : (component_hash ^ 0x517cc1b7UZ);
41 }
42};
43
44// Forward declaration for edge pointers
45class Archetype;
46
47/**
48 * @brief Represents a unique combination of component types.
49 * @details Archetypes group entities that have the exact same set of components,
50 * enabling efficient queries and batch operations. All entities in an archetype
51 * have identical component signatures, allowing for optimized memory layout
52 * and iteration patterns.
53 *
54 * The archetype maintains:
55 * - A sorted list of component type IDs for the signature
56 * - A list of entities belonging to this archetype
57 * - Fast lookup structures for entity containment checks
58 * - A cached hash value for efficient archetype lookups
59 * - Edge graph for fast archetype transitions when adding/removing components
60 *
61 * @note Not thread-safe.
62 * All operations should be performed from the main thread.
63 */
64class Archetype {
65public:
66 using ComponentTypeSet = std::vector<ComponentTypeId>;
67 using EntityList = std::vector<Entity>;
68 using EdgeMap = std::unordered_map<ArchetypeEdgeKey, Archetype*, ArchetypeEdgeKeyHash>;
69
70 /**
71 * @brief Constructs an archetype with the specified component types.
72 * @details Component types will be sorted internally for consistent ordering.
73 * @param component_types Vector of component type IDs that define this archetype
74 */
76 Archetype(const Archetype&) = delete;
79
82
83 /**
84 * @brief Adds an entity to this archetype.
85 * @details If the entity is already in this archetype, the operation is ignored.
86 * Time complexity: O(1) average case.
87 * @warning Triggers assertion if entity is invalid.
88 * @param entity Entity to add
89 */
90 void AddEntity(Entity entity);
91
92 /**
93 * @brief Removes an entity from this archetype.
94 * @details Uses swap-and-pop technique to maintain dense packing.
95 * If the entity is not in this archetype, the operation is ignored.
96 * Time complexity: O(n) worst case due to linear search, but typically fast.
97 * @warning Triggers assertion if entity is invalid.
98 * @param entity Entity to remove
99 */
100 void RemoveEntity(Entity entity);
101
102 /**
103 * @brief Gets the cached edge for adding a component type.
104 * @details Returns the target archetype for this component addition, if cached.
105 * Time complexity: O(1) average case.
106 * @param component_type Component type being added
107 * @return Pointer to target archetype, or nullptr if edge not cached
108 */
110
111 /**
112 * @brief Gets the cached edge for removing a component type.
113 * @details Returns the target archetype for this component removal, if cached.
114 * Time complexity: O(1) average case.
115 * @param component_type Component type being removed
116 * @return Pointer to target archetype, or nullptr if edge not cached
117 */
119
120 /**
121 * @brief Sets the cached edge for adding a component type.
122 * @details Caches the target archetype for fast future lookups.
123 * @param component_type Component type being added
124 * @param target Target archetype after adding the component
125 */
127
128 /**
129 * @brief Sets the cached edge for removing a component type.
130 * @details Caches the target archetype for fast future lookups.
131 * @param component_type Component type being removed
132 * @param target Target archetype after removing the component (nullptr if no components remain)
133 */
135
136 /**
137 * @brief Checks if this archetype contains the specified entity.
138 * @details Performs fast hash-based lookup.
139 * Time complexity: O(1) average case.
140 * @warning Triggers assertion if entity is invalid.
141 * @param entity Entity to check
142 * @return True if entity is in this archetype, false otherwise
143 */
144 [[nodiscard]] bool Contains(Entity entity) const;
145
146 /**
147 * @brief Checks if this archetype has all specified component types.
148 * @details Uses binary search on the sorted component type array.
149 * Time complexity: O(k * log(n)) where k is number of types to check, n is archetype component count.
150 * @param component_types Component types to check
151 * @return True if archetype has all specified components, false otherwise
152 */
154 return std::ranges::all_of(component_types, [this](ComponentTypeId type_id) {
155 return std::ranges::binary_search(component_types_, type_id);
156 });
157 }
158
159 /**
160 * @brief Checks if this archetype has any of the specified component types.
161 * @details Uses binary search on the sorted component type array.
162 * Time complexity: O(k * log(n)) where k is number of types to check, n is archetype component count.
163 * @param component_types Component types to check
164 * @return True if archetype has any of the specified components, false otherwise
165 */
166 [[nodiscard]] bool HasAnyComponents(std::span<const ComponentTypeId> component_types) const {
167 return std::ranges::any_of(component_types, [this](ComponentTypeId type_id) {
168 return std::ranges::binary_search(component_types_, type_id);
169 });
170 }
171
172 /**
173 * @brief Checks if this archetype has a specific component type.
174 * @details Uses binary search on the sorted component type array.
175 * Time complexity: O(log(n)) where n is archetype component count.
176 * @param component_type Component type to check
177 * @return True if archetype has the specified component, false otherwise
178 */
180 return std::ranges::binary_search(component_types_, component_type);
181 }
182
183 /**
184 * @brief Checks if archetype contains no entities.
185 * @details Simple check of the entity list size.
186 * Time complexity: O(1).
187 * @return True if no entities are in this archetype, false otherwise
188 */
189 [[nodiscard]] bool Empty() const noexcept { return entities_.empty(); }
190
191 /**
192 * @brief Gets all entities in this archetype.
193 * @details Returns a read-only view of entities for safe iteration.
194 * The order of entities may change when entities are removed due to swap-and-pop.
195 * Time complexity: O(1).
196 * @return Span of entities belonging to this archetype
197 */
198 [[nodiscard]] std::span<const Entity> Entities() const noexcept { return entities_; }
199
200 /**
201 * @brief Gets the component types for this archetype.
202 * @details Returns the sorted component type IDs that define this archetype's signature.
203 * Time complexity: O(1).
204 * @return Span of component type IDs in sorted order
205 */
206 [[nodiscard]] std::span<const ComponentTypeId> ComponentTypes() const noexcept { return component_types_; }
207
208 /**
209 * @brief Gets the number of entities in this archetype.
210 * @details Simple count of entities currently in this archetype.
211 * Time complexity: O(1).
212 * @return Entity count
213 */
214 [[nodiscard]] size_t EntityCount() const noexcept { return entities_.size(); }
215
216 /**
217 * @brief Gets the number of component types in this archetype.
218 * @details Simple count of component types defining this archetype.
219 * Time complexity: O(1).
220 * @return Component type count
221 */
222 [[nodiscard]] size_t ComponentCount() const noexcept { return component_types_.size(); }
223
224 /**
225 * @brief Gets the cached hash value for this archetype.
226 * @details Hash is computed once during construction based on component types.
227 * Used for efficient archetype lookups in hash-based containers.
228 * Time complexity: O(1).
229 * @return Hash value based on component type combination
230 */
231 [[nodiscard]] size_t Hash() const noexcept { return hash_; }
232
233 /**
234 * @brief Gets the generation counter for this archetype.
235 * @details Generation is incremented on structural changes to the archetype.
236 * Used for query cache validation.
237 * Time complexity: O(1).
238 * @return Current generation value
239 */
240 [[nodiscard]] size_t GetGeneration() const noexcept { return generation_; }
241
242 /**
243 * @brief Gets the number of cached edges.
244 * @details Returns count of cached archetype transitions.
245 * Time complexity: O(1).
246 * @return Number of cached edges
247 */
248 [[nodiscard]] size_t EdgeCount() const noexcept { return edges_.size(); }
249
250private:
251 /**
252 * @brief Computes hash value for the component type combination.
253 * @details Uses a mixing hash function to combine all component type IDs.
254 * @return Hash value for this archetype's component signature
255 */
256 [[nodiscard]] size_t ComputeHash() const noexcept;
257
258 ComponentTypeSet component_types_; ///< Sorted component type IDs defining this archetype
259 EntityList entities_; ///< Entities belonging to this archetype (dense packed)
260 std::unordered_set<Entity::IndexType> entity_indices_; ///< Fast lookup set for entity containment checks
261 EdgeMap edges_; ///< Cached archetype transitions for component add/remove
262 size_t hash_ = 0; ///< Cached hash value for archetype lookups
263 size_t generation_ = 0; ///< Generation counter for structural changes
264};
265
267 std::ranges::sort(component_types_);
268 hash_ = ComputeHash();
269}
270
271inline void Archetype::AddEntity(Entity entity) {
272 HELIOS_ASSERT(entity.Valid(), "Failed to add entity to archetype: Entity with index '{}' is invalid!",
273 entity.Index());
274
275 if (!entity_indices_.contains(entity.Index())) {
276 entities_.push_back(entity);
277 entity_indices_.insert(entity.Index());
278 }
279}
280
281inline void Archetype::RemoveEntity(Entity entity) {
282 HELIOS_ASSERT(entity.Valid(), "Failed to remove entity from archetype: Entity with index '{}' is invalid!",
283 entity.Index());
284
285 const auto idx_it = entity_indices_.find(entity.Index());
286 if (idx_it == entity_indices_.end()) {
287 return;
288 }
289
290 std::erase_if(entities_, [entity](const Entity& entt) { return entt == entity; });
291 entity_indices_.erase(idx_it);
292}
293
295 const auto it = edges_.find({component_type, true});
296 return it != edges_.end() ? it->second : nullptr;
297}
298
300 const auto it = edges_.find({component_type, false});
301 return it != edges_.end() ? it->second : nullptr;
302}
303
305 edges_[{component_type, true}] = target;
306}
307
309 edges_[{component_type, false}] = target;
310}
311
312inline bool Archetype::Contains(Entity entity) const {
313 HELIOS_ASSERT(entity.Valid(), "Failed to check if archetype contains entity: Entity with index '{}' is invalid!",
314 entity.Index());
315 return entity_indices_.contains(entity.Index());
316}
317
318inline size_t Archetype::ComputeHash() const noexcept {
319 size_t hash = 0;
320 for (const auto& type_id : component_types_) {
321 hash ^= std::hash<ComponentTypeId>{}(type_id) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
322 }
323 return hash;
324}
325
326/**
327 * @brief Manages archetypes and entity-archetype relationships.
328 * @details The archetype manager maintains a registry of all unique component combinations
329 * and tracks which entities belong to which archetypes. This enables efficient queries
330 * by allowing systems to iterate only over entities with specific component combinations.
331 *
332 * Key responsibilities:
333 * - Create and cache archetypes for unique component combinations
334 * - Track entity-to-archetype mappings
335 * - Provide efficient archetype lookup for queries
336 * - Handle entity movement between archetypes when components change
337 * - Cache archetype transitions (edges) for fast component add/remove operations
338 *
339 * @note Not thread-safe.
340 * All operations should be performed from the main thread.
341 */
343public:
344 Archetypes() = default;
345 Archetypes(const Archetypes&) = delete;
348
351
352 /**
353 * @brief Clears all archetypes and entity mappings.
354 * @details Removes all cached archetypes and resets entity-archetype relationships.
355 * Should be called when clearing the world.
356 * Time complexity: O(1).
357 */
358 void Clear() noexcept;
359
360 /**
361 * @brief Resets query cache statistics.
362 */
363 void ResetCacheStats() noexcept { query_cache_.ResetStats(); }
364
365 /**
366 * @brief Updates entity's archetype based on its current components.
367 * @details Removes entity from its current archetype (if any) and adds it to the
368 * archetype matching its new component combination. Creates new archetypes as needed.
369 * Time complexity: O(log(A) + C*log(C)) where A is archetype count, C is component count.
370 * @warning Triggers assertion if entity is invalid.
371 * @param entity Entity to update
372 * @param component_types Current component types for the entity
373 */
374 void UpdateEntityArchetype(Entity entity, std::span<const ComponentTypeId> component_types);
375
376 /**
377 * @brief Moves entity to a new archetype after adding a component.
378 * @details Uses cached edge graph for O(1) lookup when edge is cached.
379 * Falls back to full archetype lookup if edge is not cached, then caches the result.
380 * Time complexity: O(1) amortized with caching, O(C*log(C)) cache miss.
381 * @warning Triggers assertion if entity is invalid.
382 * @param entity Entity to move
383 * @param added_component Component type being added
384 * @param new_component_types Complete set of component types after addition
385 */
386 void MoveEntityOnComponentAdd(Entity entity, ComponentTypeId added_component,
387 std::span<const ComponentTypeId> new_component_types);
388
389 /**
390 * @brief Moves entity to a new archetype after removing a component.
391 * @details Uses cached edge graph for O(1) lookup when edge is cached.
392 * Falls back to full archetype lookup if edge is not cached, then caches the result.
393 * Time complexity: O(1) amortized with caching, O(C*log(C)) cache miss.
394 * @warning Triggers assertion if entity is invalid.
395 * @param entity Entity to move
396 * @param removed_component Component type being removed
397 * @param new_component_types Complete set of component types after removal
398 */
399 void MoveEntityOnComponentRemove(Entity entity, ComponentTypeId removed_component,
400 std::span<const ComponentTypeId> new_component_types);
401
402 /**
403 * @brief Removes entity from its current archetype.
404 * @details If the entity is not in any archetype, the operation is ignored.
405 * Time complexity: O(1) average case for lookup, O(n) for archetype removal.
406 * @warning Triggers assertion if entity is invalid.
407 * @param entity Entity to remove
408 */
409 void RemoveEntity(Entity entity);
410
411 /**
412 * @brief Gets archetype containing the specified entity.
413 * @details Performs fast hash-based lookup of entity-to-archetype mapping.
414 * Time complexity: O(1) average case.
415 * @warning Triggers assertion if entity is invalid.
416 * @param entity Entity to find
417 * @return Pointer to archetype containing the entity, or nullptr if not found
418 */
419 [[nodiscard]] const Archetype* GetEntityArchetype(Entity entity) const;
420
421 /**
422 * @brief Gets mutable archetype containing the specified entity.
423 * @details Performs fast hash-based lookup of entity-to-archetype mapping.
424 * Time complexity: O(1) average case.
425 * @warning Triggers assertion if entity is invalid.
426 * @param entity Entity to find
427 * @return Pointer to archetype containing the entity, or nullptr if not found
428 */
429 [[nodiscard]] Archetype* GetEntityArchetypeMutable(Entity entity);
430
431 /**
432 * @brief Finds archetypes that match the specified component requirements.
433 * @details Searches all archetypes for those that have all required components
434 * and none of the forbidden components. Only returns non-empty archetypes.
435 * Time complexity: O(A * (W*log(C) + X*log(C))) where A is archetype count,
436 * W is with-component count, X is without-component count, C is avg components per archetype.
437 *
438 * @param with_components Component types that must be present
439 * @param without_components Component types that must not be present
440 * @return Vector of references to matching non-empty archetypes
441 */
442 [[nodiscard]] auto FindMatchingArchetypes(std::span<const ComponentTypeId> with_components,
443 std::span<const ComponentTypeId> without_components = {}) const
444 -> std::vector<std::reference_wrapper<const Archetype>>;
445
446 /**
447 * @brief Enables or disables query caching.
448 * @param enable True to enable caching, false to disable
449 */
450 void EnableQueryCache(bool enable) noexcept { use_query_cache_ = enable; }
451
452 /**
453 * @brief Checks if query caching is enabled.
454 * @return True if caching is enabled, false otherwise
455 */
456 [[nodiscard]] bool IsQueryCacheEnabled() const noexcept { return use_query_cache_; }
457
458 /**
459 * @brief Gets query cache statistics.
460 * @return Cache statistics
461 */
462 [[nodiscard]] QueryCacheStats CacheStats() const noexcept { return query_cache_.Stats(); }
463
464 /**
465 * @brief Gets the total number of archetypes.
466 * @details Returns count of unique component combinations currently cached.
467 * Time complexity: O(1).
468 * @return Total archetype count
469 */
470 [[nodiscard]] size_t ArchetypeCount() const noexcept { return archetypes_.size(); }
471
472 /**
473 * @brief Gets total number of cached edges across all archetypes.
474 * @details Returns count of all cached archetype transitions.
475 * Time complexity: O(A) where A is archetype count.
476 * @return Total edge count
477 */
478 [[nodiscard]] size_t TotalEdgeCount() const noexcept;
479
480private:
481 /**
482 * @brief Finds or creates archetype for the specified component types.
483 * @details Uses hash-based lookup to find existing archetypes, or creates new ones.
484 * Component types are sorted before hashing for consistency.
485 * Time complexity: O(C*log(C)) for new archetypes, O(1) average for existing.
486 *
487 * @param component_types Component types for the archetype
488 * @return Reference to the archetype (existing or newly created)
489 */
490 [[nodiscard]] Archetype& GetOrCreateArchetype(std::span<const ComponentTypeId> component_types);
491
492 /**
493 * @brief Generates hash for component type combination.
494 * @details Creates a sorted copy of component types and hashes them consistently.
495 * Time complexity: O(C*log(C)) where C is component count.
496 *
497 * @param component_types Component types to hash
498 * @return Hash value for the component combination
499 */
500 [[nodiscard]] static size_t HashComponentTypes(std::span<const ComponentTypeId> component_types);
501
502 /**
503 * @brief Invalidates query cache when archetypes change.
504 */
505 void InvalidateQueryCache() noexcept;
506
507 /**
508 * @brief Notifies query cache of archetype structural changes.
509 */
510 void NotifyArchetypeStructuralChange() noexcept;
511
512 std::unordered_map<size_t, std::unique_ptr<Archetype>> archetypes_; ///< Hash -> Archetype mapping
513 std::unordered_map<Entity::IndexType, Archetype*> entity_to_archetype_; ///< Entity index -> Archetype mapping
514
515 mutable QueryCacheManager query_cache_; ///< Query result cache
516 size_t structural_version_ = 0; ///< Incremented on structural changes
517 bool use_query_cache_ = true; ///< Whether to use query caching
518};
519
521 archetypes_.clear();
522 entity_to_archetype_.clear();
523 query_cache_.Clear();
524 structural_version_ = 0;
525}
526
527inline void Archetypes::RemoveEntity(Entity entity) {
528 HELIOS_ASSERT(entity.Valid(), "Failed to remove entity from archetypes: Entity with index '{}' is invalid!",
529 entity.Index());
530
531 const auto it = entity_to_archetype_.find(entity.Index());
532 if (it == entity_to_archetype_.end()) {
533 return;
534 }
535
536 it->second->RemoveEntity(entity);
537 entity_to_archetype_.erase(it);
538 // Note: We don't invalidate cache here since entity removal doesn't change archetype structure
539}
540
541inline const Archetype* Archetypes::GetEntityArchetype(Entity entity) const {
542 HELIOS_ASSERT(entity.Valid(), "Failed to get entity archetype: Entity with index '{}' is invalid!", entity.Index());
543
544 const auto it = entity_to_archetype_.find(entity.Index());
545 if (it != entity_to_archetype_.end()) [[likely]] {
546 return it->second;
547 }
548
549 return nullptr;
550}
551
552inline Archetype* Archetypes::GetEntityArchetypeMutable(Entity entity) {
553 HELIOS_ASSERT(entity.Valid(), "Failed to get entity archetype: Entity with index '{}' is invalid!", entity.Index());
554
555 const auto it = entity_to_archetype_.find(entity.Index());
556 if (it != entity_to_archetype_.end()) [[likely]] {
557 return it->second;
558 }
559
560 return nullptr;
561}
562
563inline void Archetypes::InvalidateQueryCache() noexcept {
564 if (use_query_cache_) {
565 query_cache_.InvalidateAll();
566 ++structural_version_;
567 }
568}
569
570inline size_t Archetypes::TotalEdgeCount() const noexcept {
571 size_t count = 0;
572 for (const auto& [_, archetype] : archetypes_) {
573 count += archetype->EdgeCount();
574 }
575 return count;
576}
577
578} // namespace helios::ecs::details
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
iterator end() const noexcept
Gets iterator past the last matching entity.
Definition query.hpp:1603
Unique identifier for entities with generation counter to handle recycling.
Definition entity.hpp:21
constexpr bool Valid() const noexcept
Checks if the entity is valid.
Definition entity.hpp:58
constexpr IndexType Index() const noexcept
Gets the index component of the entity.
Definition entity.hpp:75
Represents a unique combination of component types.
Definition archetype.hpp:64
size_t EntityCount() const noexcept
Gets the number of entities in this archetype.
bool Empty() const noexcept
Checks if archetype contains no entities.
size_t Hash() const noexcept
Gets the cached hash value for this archetype.
std::span< const Entity > Entities() const noexcept
Gets all entities in this archetype.
bool HasComponents(std::span< const ComponentTypeId > component_types) const
Checks if this archetype has all specified component types.
bool HasAnyComponents(std::span< const ComponentTypeId > component_types) const
Checks if this archetype has any of the specified component types.
void SetRemoveEdge(ComponentTypeId component_type, Archetype *target) noexcept
Sets the cached edge for removing a component type.
std::unordered_map< ArchetypeEdgeKey, Archetype *, ArchetypeEdgeKeyHash > EdgeMap
Definition archetype.hpp:68
void SetAddEdge(ComponentTypeId component_type, Archetype *target) noexcept
Sets the cached edge for adding a component type.
size_t GetGeneration() const noexcept
Gets the generation counter for this archetype.
Archetype(const Archetype &)=delete
Archetype * GetRemoveEdge(ComponentTypeId component_type) const noexcept
Gets the cached edge for removing a component type.
std::vector< ComponentTypeId > ComponentTypeSet
Definition archetype.hpp:66
bool HasComponent(ComponentTypeId component_type) const noexcept
Checks if this archetype has a specific component type.
Archetype * GetAddEdge(ComponentTypeId component_type) const noexcept
Gets the cached edge for adding a component type.
void AddEntity(Entity entity)
Adds an entity to this archetype.
std::span< const ComponentTypeId > ComponentTypes() const noexcept
Gets the component types for this archetype.
std::vector< Entity > EntityList
Definition archetype.hpp:67
size_t ComponentCount() const noexcept
Gets the number of component types in this archetype.
size_t EdgeCount() const noexcept
Gets the number of cached edges.
Archetype(Archetype &&) noexcept=default
void RemoveEntity(Entity entity)
Removes an entity from this archetype.
bool Contains(Entity entity) const
Checks if this archetype contains the specified entity.
Manages archetypes and entity-archetype relationships.
Archetypes(Archetypes &&) noexcept=default
bool IsQueryCacheEnabled() const noexcept
Checks if query caching is enabled.
QueryCacheStats CacheStats() const noexcept
Gets query cache statistics.
Archetypes(const Archetypes &)=delete
size_t ArchetypeCount() const noexcept
Gets the total number of archetypes.
void EnableQueryCache(bool enable) noexcept
Enables or disables query caching.
Manages query state caching with Bevy-inspired optimizations.
size_t ComponentTypeId
Type ID for components.
BasicQuery< World, Allocator, Components... > Query
Type alias for query with mutable world access.
Definition query.hpp:2481
STL namespace.
Hash function for ArchetypeEdgeKey.
Definition archetype.hpp:36
size_t operator()(const ArchetypeEdgeKey &key) const noexcept
Definition archetype.hpp:37
Key for archetype edge transitions.
Definition archetype.hpp:26
ComponentTypeId component_id
Component type being added or removed.
Definition archetype.hpp:27
bool is_add
True for add operation, false for remove.
Definition archetype.hpp:28
constexpr bool operator==(const ArchetypeEdgeKey &) const noexcept=default
Statistics for query cache performance.