Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
helios::ecs::details::Entities Class Reference

Entity manager responsible for entity creation, destruction, and validation. More...

#include <entities_manager.hpp>

Public Member Functions

 Entities ()=default
 
 Entities (const Entities &)=delete
 
 Entities (Entities &&) noexcept
 
 ~Entities ()=default
 
Entitiesoperator= (const Entities &)=delete
 
Entitiesoperator= (Entities &&) noexcept
 
void FlushReservedEntities ()
 Creates reserved entities in the metadata.
 
void Clear () noexcept
 Clears all entities.
 
void Reserve (size_t count)
 Reserves space for entities to minimize allocations.
 
Entity ReserveEntity ()
 Reserves an entity ID that can be used immediately.
 
Entity CreateEntity ()
 Creates a new entity.
 
template<std::output_iterator< Entity > OutputIt>
OutputIt CreateEntities (size_t count, OutputIt out)
 
void Destroy (Entity entity)
 Destroys an entity by incrementing its generation.
 
template<std::ranges::range R>
requires std::same_as<std::ranges::range_value_t<R>, Entity>
void Destroy (const R &entities)
 Destroys an entity by incrementing its generation.
 
bool IsValid (Entity entity) const noexcept
 Checks if an entity exists and is valid.
 
size_t Count () const noexcept
 Gets the current number of living entities.
 
Entity::GenerationType GetGeneration (Entity::IndexType index) const noexcept
 Returns current generation value for a given index (or kInvalidGeneration if out of range).
 

Detailed Description

Entity manager responsible for entity creation, destruction, and validation.

Manages entity lifecycle with generation counters to handle entity recycling safely.

Note
Thread-safe only for validation operations.

Definition at line 26 of file entities_manager.hpp.

Constructor & Destructor Documentation

◆ Entities() [1/3]

◆ Entities() [2/3]

helios::ecs::details::Entities::Entities ( const Entities )
delete

◆ Entities() [3/3]

helios::ecs::details::Entities::Entities ( Entities &&  other)
inlinenoexcept

Definition at line 158 of file entities_manager.hpp.

159 : generations_(std::move(other.generations_)),
160 free_indices_(std::move(other.free_indices_)),
161 entity_count_(other.entity_count_.load(std::memory_order_relaxed)),
162 next_index_(other.next_index_.load(std::memory_order_relaxed)),
163 free_cursor_(other.free_cursor_.load(std::memory_order_relaxed)) {
164 other.entity_count_.store(0, std::memory_order_relaxed);
165 other.next_index_.store(0, std::memory_order_relaxed);
166 other.free_cursor_.store(0, std::memory_order_relaxed);
167}

◆ ~Entities()

Member Function Documentation

◆ Clear()

void helios::ecs::details::Entities::Clear ( )
inlinenoexcept

Clears all entities.

Destroys all entities and resets the manager state.

Note
Not thread-safe, should be called from main thread only.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/ecs/details/entities_manager.hpp.

Definition at line 183 of file entities_manager.hpp.

183 {
184 std::ranges::fill(generations_, Entity::kInvalidGeneration);
185 free_indices_.clear();
186 next_index_.store(0, std::memory_order_relaxed);
187 free_cursor_.store(0, std::memory_order_relaxed);
188 entity_count_.store(0, std::memory_order_relaxed);
189}
static constexpr GenerationType kInvalidGeneration
Definition entity.hpp:27

◆ Count()

size_t helios::ecs::details::Entities::Count ( ) const
inlinenoexcept

Gets the current number of living entities.

Returns count of entities that are currently alive.

Note
Thread-safe.
Returns
Number of living entities
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/ecs/details/entities_manager.hpp.

Definition at line 140 of file entities_manager.hpp.

140{ return entity_count_.load(std::memory_order_relaxed); }

◆ CreateEntities()

template<std::output_iterator< Entity > OutputIt>
OutputIt helios::ecs::details::Entities::CreateEntities ( size_t  count,
OutputIt  out 
)
inline
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/ecs/details/entities_manager.hpp.

Definition at line 271 of file entities_manager.hpp.

271 {
272 if (count == 0) [[unlikely]] {
273 return out;
274 }
275
276 // Try to satisfy as many as possible from the free list first
277 size_t remaining = count;
278 int64_t cursor = free_cursor_.load(std::memory_order_relaxed);
279 // Use std::max with explicit signed type to avoid non-standard integer literal suffix
280 const size_t available_free = static_cast<size_t>(std::max<int64_t>(int64_t{0}, cursor));
281 const size_t from_free_list = std::min(remaining, available_free);
282
283 if (from_free_list > 0) {
284 const int64_t new_cursor = cursor - static_cast<int64_t>(from_free_list);
285 if (free_cursor_.compare_exchange_strong(cursor, new_cursor, std::memory_order_relaxed)) {
286 // Successfully claimed indices from free list
287 for (size_t i = 0; i < from_free_list; ++i) {
288 const size_t free_index = static_cast<size_t>(new_cursor) + i;
289 if (free_index >= free_indices_.size()) {
290 continue;
291 }
292
293 const Entity::IndexType index = free_indices_[free_index];
294 if (index >= generations_.size()) {
295 continue;
296 }
297
298 const Entity::GenerationType generation = generations_[index];
299 *out = CreateEntityWithId(index, generation);
300 ++out;
301 }
303 }
304 }
305
306 // Create new entities for remaining count
307 if (remaining > 0) {
309 next_index_.fetch_add(static_cast<Entity::IndexType>(remaining), std::memory_order_relaxed);
311
312 // Ensure generations array is large enough
313 if (end_index > generations_.size()) {
314 generations_.resize(end_index, Entity::kInvalidGeneration);
315 }
316
317 // Create entities with generation 1
318 for (Entity::IndexType index = start_index; index < end_index; ++index) {
319 generations_[index] = 1;
320 *out = CreateEntityWithId(index, 1);
321 ++out;
322 }
323 }
324
325 return out;
326}
uint32_t IndexType
Definition entity.hpp:23
uint32_t GenerationType
Definition entity.hpp:24
BasicQuery< World, Allocator, Components... > Query
Type alias for query with mutable world access.
Definition query.hpp:2481

◆ CreateEntity()

Entity helios::ecs::details::Entities::CreateEntity ( )

Creates a new entity.

Reuses dead entity slots when available, otherwise creates new ones.

Note
Not thread-safe, should be called from main thread only during World::Update().
Returns
Newly created entity with valid index and generation
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/ecs/details/entities_manager.hpp.

Definition at line 35 of file entities_manager.cpp.

35 {
38
39 // Try to reuse a recycled index first
40 int64_t cursor = free_cursor_.load(std::memory_order_relaxed);
41 if (cursor > 0) {
42 // Attempt to pop from free list
43 const int64_t new_cursor = cursor - 1;
44 if (free_cursor_.compare_exchange_strong(cursor, new_cursor, std::memory_order_relaxed)) {
45 // Successfully claimed an index from the free list
46 const auto free_index = static_cast<size_t>(new_cursor);
47 if (free_index < free_indices_.size()) {
48 index = free_indices_[free_index];
49 if (index < generations_.size()) {
50 generation = generations_[index];
51 return CreateEntityWithId(index, generation);
52 }
53 }
54 }
55 }
56
57 // No free indices available, allocate a new one
58 index = next_index_.fetch_add(1, std::memory_order_relaxed);
59
60 // Ensure generations array is large enough
61 if (index >= generations_.size()) {
62 generations_.resize(index + 1, Entity::kInvalidGeneration);
63 }
64
65 // Start with generation 1 for new entities
66 generation = 1;
67 generations_[index] = generation;
68
69 return CreateEntityWithId(index, generation);
70}
static constexpr IndexType kInvalidIndex
Definition entity.hpp:26

◆ Destroy() [1/2]

template<std::ranges::range R>
requires std::same_as<std::ranges::range_value_t<R>, Entity>
void helios::ecs::details::Entities::Destroy ( const R entities)
inline

Destroys an entity by incrementing its generation.

Marks entity as dead and adds its index to the free list for reuse.

Note
Not thread-safe, should be called from main thread only during World::Update(). Triggers assertion if any entity is invalid. Entities that do not exist are ignored.
Template Parameters
RRange type containing Entity elements
Parameters
entitiesEntities to destroy

Definition at line 227 of file entities_manager.hpp.

227 {
228 // If the incoming range is sized we can reserve capacity up front to avoid reallocations.
229 if constexpr (std::ranges::sized_range<R>) {
230 const auto incoming = std::ranges::size(entities);
231 free_indices_.reserve(free_indices_.size() + incoming);
232 }
233
234 // Small local buffer to collect indices we're going to free.
235 // We collect them locally so we can do a single insert into free_indices_,
236 // and do a single atomic update for free_cursor_ and entity_count_.
237 std::vector<Entity::IndexType> batch;
238 if constexpr (std::ranges::sized_range<R>) {
239 batch.reserve(std::ranges::size(entities));
240 } else {
241 batch.reserve(16); // small default to avoid too many reallocs for small ranges
242 }
243
244 // Process each entity: validate, increment generation, collect index.
245 // We increment generation immediately so behaviour matches the single-entity Destroy
246 // (i.e. duplicates in the input will fail the second validation).
247 for (const Entity& entity : entities) {
248 HELIOS_ASSERT(entity.Valid(), "Failed to destroy entities: Entity is invalid!");
249 if (!IsValid(entity)) {
250 // Skip entities already destroyed / wrong generation
251 continue;
252 }
253
254 const Entity::IndexType index = entity.Index();
255 ++generations_[index]; // Invalidate entity
256 batch.push_back(index);
257 }
258
259 if (!batch.empty()) {
260#ifdef HELIOS_CONTAINERS_RANGES_AVALIABLE
261 free_indices_.append_range(batch);
262#else
263 free_indices_.insert(free_indices_.end(), batch.begin(), batch.end());
264#endif
265 free_cursor_.store(static_cast<int64_t>(free_indices_.size()), std::memory_order_relaxed);
266 entity_count_.fetch_sub(batch.size(), std::memory_order_relaxed);
267 }
268}
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
bool IsValid(Entity entity) const noexcept
Checks if an entity exists and is valid.

◆ Destroy() [2/2]

void helios::ecs::details::Entities::Destroy ( Entity  entity)
inline

Destroys an entity by incrementing its generation.

Marks entity as dead and adds its index to the free list for reuse.

Note
Not thread-safe, should be called from main thread only during World::Update(). Triggers assertion if entity is invalid. Ignored if entity do not exist.
Parameters
entityEntity to destroy
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/ecs/details/entities_manager.hpp.

Definition at line 211 of file entities_manager.hpp.

211 {
212 HELIOS_ASSERT(entity.Valid(), "Failed to destroy entity: Entity is invalid!");
213 if (!IsValid(entity)) [[unlikely]] {
214 return;
215 }
216
217 const Entity::IndexType index = entity.Index();
218 ++generations_[index]; // Invalidate entity
219 free_indices_.push_back(index);
220
221 free_cursor_.store(static_cast<int64_t>(free_indices_.size()), std::memory_order_relaxed);
222 entity_count_.fetch_sub(1, std::memory_order_relaxed);
223}

◆ FlushReservedEntities()

void helios::ecs::details::Entities::FlushReservedEntities ( )

Creates reserved entities in the metadata.

Processes all reserved entity IDs and creates their metadata.

Note
Not thread-safe, must be called from single thread only (typically during World::Update()).
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/ecs/details/entities_manager.hpp.

Definition at line 12 of file entities_manager.cpp.

12 {
13 const Entity::IndexType current_next_index = next_index_.load(std::memory_order_relaxed);
14
15 // Ensure generations array is large enough
16 if (current_next_index > generations_.size()) {
18 }
19
20 // Initialize any indices that were reserved but not yet created
21 size_t new_entities_count = 0;
22 for (size_t i = 0; i < current_next_index; ++i) {
23 if (generations_[i] == Entity::kInvalidGeneration) {
24 generations_[i] = 1; // Start with generation 1 for new entities
26 }
27 }
28
29 // Update entity count to reflect newly created entities
30 if (new_entities_count > 0) {
31 entity_count_.fetch_add(new_entities_count, std::memory_order_relaxed);
32 }
33}

◆ GetGeneration()

Entity::GenerationType helios::ecs::details::Entities::GetGeneration ( Entity::IndexType  index) const
inlinenoexcept

Returns current generation value for a given index (or kInvalidGeneration if out of range).

Warning
Triggers assertion if index is invalid.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/ecs/details/entities_manager.hpp.

Definition at line 338 of file entities_manager.hpp.

338 {
339 HELIOS_ASSERT(index != Entity::kInvalidIndex, "Failed to get generation: index is invalid!");
340 return index < generations_.size() ? generations_[index] : Entity::kInvalidGeneration;
341}

◆ IsValid()

bool helios::ecs::details::Entities::IsValid ( Entity  entity) const
inlinenoexcept

Checks if an entity exists and is valid.

Validates both the entity structure and its current generation.

Note
Thread-safe for read operations.
Parameters
entityEntity to validate
Returns
True if entity exists and is valid, false otherwise
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/ecs/details/entities_manager.hpp.

Definition at line 328 of file entities_manager.hpp.

328 {
329 if (!entity.Valid()) [[unlikely]] {
330 return false;
331 }
332
333 const Entity::IndexType index = entity.Index();
334 return index < generations_.size() && generations_[index] == entity.Generation() &&
335 generations_[index] != Entity::kInvalidGeneration;
336}

◆ operator=() [1/2]

◆ operator=() [2/2]

Entities & helios::ecs::details::Entities::operator= ( Entities &&  other)
inlinenoexcept

Definition at line 169 of file entities_manager.hpp.

169 {
170 if (this == &other) [[unlikely]] {
171 return *this;
172 }
173
174 generations_ = std::move(other.generations_);
175 free_indices_ = std::move(other.free_indices_);
176 entity_count_.store(other.entity_count_.load(std::memory_order_relaxed), std::memory_order_relaxed);
177 next_index_.store(other.next_index_.load(std::memory_order_relaxed), std::memory_order_relaxed);
178 free_cursor_.store(other.free_cursor_.load(std::memory_order_relaxed), std::memory_order_relaxed);
179
180 return *this;
181}

◆ Reserve()

void helios::ecs::details::Entities::Reserve ( size_t  count)
inline

Reserves space for entities to minimize allocations.

Pre-allocates storage for the specified number of entities.

Note
Not thread-safe, should be called from main thread only.
Parameters
countNumber of entities to reserve space for
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/ecs/details/entities_manager.hpp.

Definition at line 191 of file entities_manager.hpp.

191 {
192 if (count > generations_.size()) {
193 generations_.resize(count, Entity::kInvalidGeneration);
194 }
195 free_indices_.reserve(count);
196}

◆ ReserveEntity()

Entity helios::ecs::details::Entities::ReserveEntity ( )
inline

Reserves an entity ID that can be used immediately.

The actual entity creation is deferred until FlushReservedEntities() is called.

Note
Thread-safe.
Returns
Reserved entity with valid index and generation
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/ecs/details/entities_manager.hpp.

Definition at line 198 of file entities_manager.hpp.

198 {
199 // Atomically reserve an index by incrementing the next available index.
200 // NOTE: Do NOT mutate metadata (e.g. `generations_` or `entity_count_`) here because
201 // this function is thread-safe and may be called concurrently. The actual metadata
202 // initialization for reserved indices is performed in `FlushReservedEntities()` which
203 // must be called from the main thread.
204 const Entity::IndexType index = next_index_.fetch_add(1, std::memory_order_relaxed);
205
206 // Return a placeholder entity with generation 1. This is only a reservation handle;
207 // the real creation/metadata setup happens in FlushReservedEntities().
208 return {index, 1};
209}