Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
archetype.cpp
Go to the documentation of this file.
2
6
7#include <algorithm>
8#include <cstddef>
9#include <memory>
10#include <ranges>
11#include <span>
12#include <utility>
13#include <vector>
14
15namespace helios::ecs::details {
16
17void Archetypes::UpdateEntityArchetype(Entity entity, std::span<const ComponentTypeId> component_types) {
18 HELIOS_ASSERT(entity.Valid(), "Failed to update entity archetype: Entity with index '{}' is invalid!",
19 entity.Index());
20
21 // Remove entity from current archetype
22 const auto current_it = entity_to_archetype_.find(entity.Index());
23 if (current_it != entity_to_archetype_.end()) {
24 current_it->second->RemoveEntity(entity);
25 entity_to_archetype_.erase(current_it);
26 }
27
28 // If entity has no components, don't add to any archetype
29 if (component_types.empty()) {
30 return;
31 }
32
33 // Get or create archetype for new component combination
34 auto& archetype = GetOrCreateArchetype(component_types);
35 const bool is_new_archetype = archetype.EntityCount() == 0;
36
37 archetype.AddEntity(entity);
38 entity_to_archetype_[entity.Index()] = &archetype;
39
40 // Notify query cache of structural changes
41 if (is_new_archetype) {
42 InvalidateQueryCache();
43 } else {
44 NotifyArchetypeStructuralChange();
45 }
46}
47
49 std::span<const ComponentTypeId> new_component_types) {
50 HELIOS_ASSERT(entity.Valid(), "Failed to move entity on component add: Entity with index '{}' is invalid!",
51 entity.Index());
52
54
55 // If entity has no current archetype (first component), use standard path
56 if (current_archetype == nullptr) {
57 auto& target_archetype = GetOrCreateArchetype(new_component_types);
58 const bool is_new_archetype = target_archetype.EntityCount() == 0;
59
60 target_archetype.AddEntity(entity);
61 entity_to_archetype_[entity.Index()] = &target_archetype;
62
63 if (is_new_archetype) {
64 InvalidateQueryCache();
65 } else {
66 NotifyArchetypeStructuralChange();
67 }
68 return;
69 }
70
71 // Try cached edge first
73 if (cached_target != nullptr) {
74 // Fast path: use cached edge
75 current_archetype->RemoveEntity(entity);
76 cached_target->AddEntity(entity);
77 entity_to_archetype_[entity.Index()] = cached_target;
78 NotifyArchetypeStructuralChange();
79 return;
80 }
81
82 // Cache miss: find or create target archetype
83 auto& target_archetype = GetOrCreateArchetype(new_component_types);
84 const bool is_new_archetype = target_archetype.EntityCount() == 0;
85
86 // Cache the edge for future use
88
89 // Move entity
90 current_archetype->RemoveEntity(entity);
91 target_archetype.AddEntity(entity);
92 entity_to_archetype_[entity.Index()] = &target_archetype;
93
94 if (is_new_archetype) {
95 InvalidateQueryCache();
96 } else {
97 NotifyArchetypeStructuralChange();
98 }
99}
100
102 std::span<const ComponentTypeId> new_component_types) {
103 HELIOS_ASSERT(entity.Valid(), "Failed to move entity on component remove: Entity with index '{}' is invalid!",
104 entity.Index());
105
107
108 // If entity has no current archetype, nothing to remove from
109 if (current_archetype == nullptr) {
110 return;
111 }
112
113 // Remove entity from current archetype
114 current_archetype->RemoveEntity(entity);
115 entity_to_archetype_.erase(entity.Index());
116
117 // If no components remain, entity has no archetype
118 if (new_component_types.empty()) {
119 // Cache edge to nullptr (no archetype)
120 current_archetype->SetRemoveEdge(removed_component, nullptr);
121 return;
122 }
123
124 // Try cached edge first
126 if (cached_target != nullptr) {
127 // Fast path: use cached edge
128 cached_target->AddEntity(entity);
129 entity_to_archetype_[entity.Index()] = cached_target;
130 NotifyArchetypeStructuralChange();
131 return;
132 }
133
134 // Cache miss: find or create target archetype
135 auto& target_archetype = GetOrCreateArchetype(new_component_types);
136 const bool is_new_archetype = target_archetype.EntityCount() == 0;
137
138 // Cache the edge for future use
140
141 // Move entity
142 target_archetype.AddEntity(entity);
143 entity_to_archetype_[entity.Index()] = &target_archetype;
144
145 if (is_new_archetype) {
146 InvalidateQueryCache();
147 } else {
148 NotifyArchetypeStructuralChange();
149 }
150}
151
152auto Archetypes::FindMatchingArchetypes(std::span<const ComponentTypeId> with_components,
153 std::span<const ComponentTypeId> without_components) const
154 -> std::vector<std::reference_wrapper<const Archetype>> {
155 // Try to get cached result if caching is enabled
156 if (use_query_cache_) {
157 const auto* cached = query_cache_.TryGetCache(with_components, without_components, structural_version_);
158 if (cached != nullptr) {
159 return cached->matching_archetypes;
160 }
161 }
162
163 // Cache miss or caching disabled - perform search
164 std::vector<std::reference_wrapper<const Archetype>> matching;
165 matching.reserve(archetypes_.size()); // Reserve worst-case size
166
167 for (const auto& [_, archetype] : archetypes_) {
168 if (!archetype->Empty() && (without_components.empty() || !archetype->HasAnyComponents(without_components)) &&
169 (with_components.empty() || archetype->HasComponents(with_components))) {
170 matching.emplace_back(*archetype);
171 }
172 }
173
174 matching.shrink_to_fit(); // Reduce to actual size
175
176 // Store in cache if enabled
177 if (use_query_cache_) {
178 query_cache_.StoreCache(with_components, without_components, matching, structural_version_);
179 }
180
181 return matching;
182}
183
184Archetype& Archetypes::GetOrCreateArchetype(std::span<const ComponentTypeId> component_types) {
185 const size_t hash = HashComponentTypes(component_types);
186 const auto [it, inserted] = archetypes_.try_emplace(hash, nullptr);
187 if (inserted) {
188 std::vector<ComponentTypeId> sorted_types(component_types.begin(), component_types.end());
189 it->second = std::make_unique<Archetype>(std::move(sorted_types));
190 }
191
192 return *it->second;
193}
194
195size_t Archetypes::HashComponentTypes(std::span<const ComponentTypeId> component_types) {
196 // Create a sorted copy for consistent hashing
197 std::vector<ComponentTypeId> sorted_types(component_types.begin(), component_types.end());
198 std::ranges::sort(sorted_types);
199
200 size_t hash = 0;
201 for (const auto& type_id : sorted_types) {
202 hash ^= std::hash<ComponentTypeId>{}(type_id) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
203 }
204 return hash;
205}
206
207void Archetypes::NotifyArchetypeStructuralChange() noexcept {
208 if (use_query_cache_) {
209 query_cache_.NotifyArchetypeChange();
210 ++structural_version_;
211 }
212}
213
214} // namespace helios::ecs::details
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
bool Empty() const noexcept
Checks if any entities match the query.
Definition query.hpp:2299
iterator begin() const
Gets iterator to first matching entity.
Definition query.hpp:2317
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
void MoveEntityOnComponentRemove(Entity entity, ComponentTypeId removed_component, std::span< const ComponentTypeId > new_component_types)
Moves entity to a new archetype after removing a component.
void MoveEntityOnComponentAdd(Entity entity, ComponentTypeId added_component, std::span< const ComponentTypeId > new_component_types)
Moves entity to a new archetype after adding a component.
Definition archetype.cpp:48
void UpdateEntityArchetype(Entity entity, std::span< const ComponentTypeId > component_types)
Updates entity's archetype based on its current components.
Definition archetype.cpp:17
Archetype * GetEntityArchetypeMutable(Entity entity)
Gets mutable archetype containing the specified entity.
auto FindMatchingArchetypes(std::span< const ComponentTypeId > with_components, std::span< const ComponentTypeId > without_components={}) const -> std::vector< std::reference_wrapper< const Archetype > >
Finds archetypes that match the specified component requirements.
void NotifyArchetypeChange()
Notifies the cache of an archetype structural change.
size_t ComponentTypeId
Type ID for components.
BasicQuery< World, Allocator, Components... > Query
Type alias for query with mutable world access.
Definition query.hpp:2481