Nui Engine
A game engine framework
Loading...
Searching...
No Matches
ECS.h
1#pragma once
2#include "Core/Engine/ECS/Entity.h"
3#include "Core/Engine/ECS/Component.h"
4#include "Core/Engine/ECS/System.h"
5#include "Core/Engine/ECS/Event.h"
6#include "Core/Engine/ECS/Context.h"
7
8namespace Nui::ECS
9{
10#pragma region Entity Component Iterator
11 template<typename... Types>
12 inline Internal::EntityComponentIterator<Types...>::EntityComponentIterator(Context* context, U64 index, bool isEnd, bool includePendingDestroy)
13 : m_context(context)
14 , m_index(index)
15 , m_isEnd(isEnd)
16 , m_includePendingDestroy(includePendingDestroy)
17 {
18 if (index >= context->GetEntityCount())
19 {
20 m_isEnd = true;
21 }
22 }
23
24 template<typename... Types>
26 {
27 return m_isEnd || m_index >= m_context->GetEntityCount();
28 }
29
30 template<typename... Types>
32 {
33 if (IsEnd())
34 return nullptr;
35
36 return m_context->GetEntityByIndex(m_index);
37 }
38
39 template<typename... Types>
41 {
42 // Advance the index
43 ++m_index;
44
45 /*
46 Keep advancing the index as long as :
47 - Entity index is under limit
48 - AND EITHER
49 - Entity is not nullptr
50 - OR
51 - Entity has required components
52 - OR
53 - Entity is pending destruction (or not depending on input conditions)
54 */
55
56 while (m_index < m_context->GetEntityCount() // Entity index is under limit
57 && (GetEntity() == nullptr // Current entity is nullptr
58 || !GetEntity()->template Has<Types...>() // Entity has required components
59 || (GetEntity()->IsPendingDestroy() && !m_includePendingDestroy))) // Entity is pending destruction
60 {
61 ++m_index;
62 }
63
64 if (m_index >= m_context->GetEntityCount())
65 m_isEnd = true;
66
67 return *this;
68 }
69#pragma endregion
70
71#pragma region Entity Component View
72 template<typename... Types>
74 : m_first(first)
75 , m_last(last)
76 {
77 /*
78 Ensure 'm_first' points to a valid entity by:
79 - First entity is valid (not nullptr)
80 - OR
81 - First entity is pending destruction AND IncludePendingDestroy is false
82 - OR
83 - First entity has a all required components
84 If th above conditions fail, (using the ++ operator) advance the EntityComponentIterator until we find a valid Entity or reaches end of range
85 */
86 if (m_first.GetEntity() == nullptr
87 || (m_first.GetEntity()->IsPendingDestroy() && !m_first.IncludePendingDestroy())
88 || !m_first.GetEntity()->template Has<Types...>())
89 {
90 ++m_first;
91 }
92 }
93#pragma endregion
94
95#pragma region Entity Iterator
96 inline Internal::EntityIterator::EntityIterator(Context* context, U64 index, bool isEnd, bool includePendingDestroy)
97 : m_context(context)
98 , m_index(index)
99 , m_isEnd(isEnd)
100 , m_includePendingDestroy(includePendingDestroy)
101 {
102 if (index >= context->GetEntityCount())
103 {
104 m_isEnd = true;
105 }
106 }
107
108 inline bool Internal::EntityIterator::IsEnd() const noexcept
109 {
110 return m_isEnd || m_index >= m_context->GetEntityCount();
111 }
112
114 {
115 if (IsEnd())
116 return nullptr;
117
118 return m_context->GetEntityByIndex(m_index);
119 }
120
122 {
123 // Similar to EntityComponentIterator's ++ operator
124
125 ++m_index;
126 while (m_index < m_context->GetEntityCount()
127 && (GetEntity() == nullptr
128 || (GetEntity()->IsPendingDestroy() && !m_includePendingDestroy)))
129 {
130 ++m_index;
131 }
132
133 if (m_index >= m_context->GetEntityCount())
134 m_isEnd = true;
135
136 return *this;
137 }
138#pragma endregion
139
140#pragma region Component Container
141 template<typename T>
143 {
144 auto handle = ComponentHandle<T>(&m_data);
145 entity->GetContext()->EmitEvent<Events::OnComponentRemove<T>>({ entity, handle });
146 }
147#pragma endregion
148
149#pragma region Entity
150 template<typename T, typename ...Args>
151 inline ComponentHandle<T> Entity::Add(Args && ...args)
152 {
153 auto found = m_components.find(GetTypeIndex<T>());
154 if (found != m_components.end())
155 {
156 // If found, update the existing component
157 Internal::ComponentContainer<T>* container = reinterpret_cast<Internal::ComponentContainer<T>*>(found->second.get());
158 container->m_data = T(std::forward<Args>(args)...);
159
160 ComponentHandle<T> handle = ComponentHandle<T>(&container->m_data);
161 m_context->EmitEvent<Events::OnComponentAdd<T>>({ this, handle });
162 return handle;
163 }
164 else
165 {
166 // If not found, create a new component container
167 std::unique_ptr<Internal::ComponentContainer<T>> container = std::make_unique<Internal::ComponentContainer<T>>(T(std::forward<Args>(args)...));
168
169 Internal::ComponentContainer<T>* containerRawPtr = container.get();
170
171 // Insert the new component container into the map
172 m_components.insert({ GetTypeIndex<T>(), std::move(container) });
173
174 // Now that the container is safely stored in the map, we can create the handle
175 ComponentHandle<T> handle(&containerRawPtr->m_data);
176
177 m_context->EmitEvent<Events::OnComponentAdd<T>>({ this, handle });
178 return handle;
179 }
180 }
181
182 template<typename T>
184 {
185 auto found = m_components.find(GetTypeIndex<T>());
186 if (found != m_components.end())
187 {
188 Internal::ComponentContainer<T>* container = reinterpret_cast<Internal::ComponentContainer<T>*>(found->second.get());
189 return ComponentHandle<T>(&container->m_data);
191
192 return ComponentHandle<T>();
193 }
194
195 template<typename T>
196 inline bool Entity::Remove()
197 {
198 auto found = m_components.find(GetTypeIndex<T>());
199 if (found != m_components.end())
200 {
201 found->second->OnRemove(this);
202 found->second->OnDestroy(m_context);
203
204 m_components.erase(found);
205
206 return true;
207 }
208
209 return false;
210 }
211#pragma endregion
212
213#pragma region Context
214 inline Context::Context() = default;
215
217 {
218 // Shutdown systems
219 for (std::unique_ptr<SystemBase>& system : m_systems)
220 {
221 system->OnShutdown(this);
222 }
223
224 // Clear entities
225 for (std::unique_ptr<Entity>& ent : m_entities)
226 {
227 if (!ent->IsPendingDestroy())
228 {
229 ent->m_pendingDestroy = true;
230 EmitEvent<Events::OnEntityDestroy>({ ent.get()});
231 }
232
233 ent.reset();
234 }
235
236 // Shutdown systems
237 for (std::unique_ptr<SystemBase>& system : m_systems)
238 {
239 system.reset();
240 }
241 }
242
244 {
245 if (id == Entity::InvalidId || id > m_lastEntityId)
246 return nullptr;
247
248 // We should likely store entities in a map of id -> entity so that this is faster.
249 for (std::unique_ptr<Entity>& ent : m_entities)
250 {
251 if (ent->GetId() == id)
252 return ent.get();
253 }
254
255 return nullptr;
256 }
257
259 {
260 if (index >= m_entities.size())
261 return nullptr;
262
263 return m_entities[index].get();
264 }
265
267 {
268 ++m_lastEntityId;
269 m_entities.push_back(std::make_unique<Entity>(this, m_lastEntityId));
270
271 Entity* ent = m_entities.front().get();
272
273 EmitEvent<Events::OnEntityCreate>({ ent });
274
275 return ent;
276 }
277
278 inline void Context::DestroyEntity(Entity* e, bool immediate)
279 {
280 if (e == nullptr)
281 return;
282
283 if (e->IsPendingDestroy())
284 {
285 if (immediate)
286 {
287 m_entities.erase(
288 std::remove_if(
289 m_entities.begin(), m_entities.end(),
290 [e] (const std::unique_ptr<Entity>& ptr)
291 {
292 return ptr.get() == e;
293 }),
294 m_entities.end());
295 }
296
297 return;
298 }
299
300 e->m_pendingDestroy = true;
301
302 EmitEvent<Events::OnEntityDestroy>({ e });
303
304 if (immediate)
305 {
306 m_entities.erase(
307 std::remove_if(
308 m_entities.begin(), m_entities.end(),
309 [e] (const std::unique_ptr<Entity>& ptr)
310 {
311 return ptr.get() == e;
312 }),
313 m_entities.end());
314 }
315 }
316
318 {
319 U64 count = 0;
320 m_entities.erase(
321 std::remove_if(
322 m_entities.begin(), m_entities.end(),
323 [&, this](std::unique_ptr<Entity>& ent)
324 {
325 if (ent->IsPendingDestroy())
326 {
327 ++count;
328 ent.reset();
329 return true;
330 }
331
332 return false;
333 }),
334 m_entities.end()
335 );
336
337 return count > 0;
338 }
339
340 inline void Context::Reset()
341 {
342 for (std::unique_ptr<Entity>& ent : m_entities)
343 {
344 if (!ent->IsPendingDestroy())
345 {
346 ent->m_pendingDestroy = true;
348 }
349 }
350
351 m_entities.clear();
352 m_lastEntityId = 0;
353 }
354
356 {
357 // Shutdown all systems, and clear the vector
358 for (std::unique_ptr<SystemBase>& system : m_systems)
359 {
360 system->OnShutdown(this);
361 }
362
363 m_systems.clear();
364 }
365
366 template <typename T, typename... Args>
367 inline T* Context::RegisterSystem(Args&&... args) requires IsSystem<T>
368 {
369 // Check if a system of type T already exists
370 for (const std::unique_ptr<SystemBase>& system : m_systems)
371 {
372 if (dynamic_cast<T*>(system.get()))
373 {
374 return nullptr;
375 }
376 }
377
378 std::unique_ptr<SystemBase> system = std::make_unique<T>(std::forward<Args>(args)...);
379 system->OnInit(this);
380
381 m_systems.push_back(std::move(system));
382
383 return dynamic_cast<T*>(m_systems.back().get());
384 }
385
386 template <typename T>
387 inline void Context::UnregisterSystem() requires IsSystem<T>
388 {
389 // Remove the system from the vector
390 auto it = std::remove_if(
391 m_systems.begin(), m_systems.end(),
392 [&] (std::unique_ptr<SystemBase>& system)
393 {
394 if (dynamic_cast<T*>(system.get()) != nullptr)
395 {
396 system->OnShutdown(this);
397 return true; // Mark for removal
398 }
399
400 return false; // Keep the system
401 });
402
403 // Erase the removed systems from the vector
404 if (it != m_systems.end())
405 {
406 m_systems.erase(it, m_systems.end());
407 }
408 }
409
410 template<typename T>
411 inline T* Context::GetSystem() requires IsSystem<T>
412 {
413 for (std::unique_ptr<SystemBase>& system : m_systems)
414 {
415 if (T* typedSystem = dynamic_cast<T*>(system.get()))
416 {
417 return typedSystem;
418 }
419 }
420 return nullptr;
421 }
422
423 template <typename T>
424 inline void Context::EnableSystem() requires IsSystem<T>
425 {
426 for (std::unique_ptr<SystemBase>& system : m_systems)
427 {
428 if (dynamic_cast<T*>(system.get()) != nullptr)
429 {
430 system->SetIsEnabled(true);
431 }
432 }
433 }
434
435 template <typename T>
436 inline void Context::DisableSystem() requires IsSystem<T>
437 {
438 for (std::unique_ptr<SystemBase>& system : m_systems)
439 {
440 if (dynamic_cast<T*>(system.get()) != nullptr)
441 {
442 system->SetIsEnabled(false);
443 }
444 }
445 }
446
447 template <typename T>
448 inline bool Context::IsSystmEnabled() requires IsSystem<T>
449 {
450 for (std::unique_ptr<SystemBase>& system : m_systems)
451 {
452 if (dynamic_cast<T*>(system.get()) != nullptr)
453 {
454 return system->IsEnabled();
455 }
456 }
457 }
458
459 template<typename T>
461 {
462 TypeIndex index = GetTypeIndex<T>();
463 auto found = m_subscribers.find(index);
464
465 if (found == m_subscribers.end())
466 {
467 // No subscribers yet
468 std::vector<Internal::EventSubscriberBase*> subList;
469 subList.push_back(subscriber);
470 m_subscribers.insert({ index, subList });
471 }
472 else
473 {
474 // Add subscriber
475 found->second.push_back(subscriber);
476 }
477 }
478
479 template<typename T>
481 {
482 TypeIndex index = GetTypeIndex<T>();
483 auto found = m_subscribers.find(index);
484
485 if (found != m_subscribers.end())
486 {
487 found->second.erase(
488 std::remove(
489 found->second.begin(), found->second.end(),
490 subscriber),
491 found->second.end()
492 );
493
494 if (found->second.size() == 0)
495 {
496 m_subscribers.erase(found);
497 }
498 }
499 }
500
501 inline void Context::UnsubscribeAll(void* subscriber)
502 {
503 for (auto& [typeIndex, subList] : m_subscribers)
504 {
505 subList.erase(
506 std::remove(
507 subList.begin(), subList.end(),
508 subscriber
509 ), subList.end()
510 );
511
512 if (subList.size() == 0)
513 {
514 m_subscribers.erase(typeIndex);
515 }
516 }
517 }
518
519 inline Internal::EntityView Context::All(bool includePendingDestroy)
520 {
521 Internal::EntityIterator first(this, 0, false, includePendingDestroy);
522 Internal::EntityIterator last(this, GetEntityCount(), true, includePendingDestroy);
523 return Internal::EntityView(first, last);
524 }
525
526 inline void Context::All(std::function<void(Entity*)> viewFunc, bool includePendingDestroy)
527 {
528 for (auto* ent : All(includePendingDestroy))
529 {
530 viewFunc(ent);
531 }
532 }
533
534 template<typename T>
535 inline void Context::EmitEvent(const T& event)
536 {
537 TypeIndex index = GetTypeIndex<T>();
538 auto found = m_subscribers.find(index);
539
540 if (found != m_subscribers.end())
541 {
542 for (Internal::EventSubscriberBase* subBase : found->second)
543 {
544 EventSubscriber<T>* sub = reinterpret_cast<EventSubscriber<T>*>(subBase);
545 sub->OnEvent(this, event);
546 }
547 }
548 }
549
550 template<typename ...Types>
551 inline Internal::EntityComponentView<Types...> Nui::ECS::Context::Each(bool includePendingDestroy)
552 {
553 Internal::EntityComponentIterator<Types...> first(this, 0, false, includePendingDestroy);
554 Internal::EntityComponentIterator<Types...> last(this, GetEntityCount(), true, includePendingDestroy);
555 return Internal::EntityComponentView<Types...>(first, last);
556 }
557
558 template<typename ...Types>
559 inline void Context::Each(typename std::common_type<std::function<void(Entity*, ComponentHandle<Types>...)>>::type viewFunc, bool includePendingDestroy)
560 {
561 for (Entity* ent : Each<Types...>(includePendingDestroy))
562 {
563 viewFunc(ent, ent->template Get<Types>()...);
564 }
565 }
566
567 inline void Context::Tick(const F64 dt)
568 {
569 //ClearPending();
570
571 for (std::unique_ptr<SystemBase>& system : m_systems)
572 {
573 system->OnUpdate(this, dt);
574 }
575 }
576#pragma endregion
577}
This class provides a handle to a component.
Definition Component.h:52
The ECS context class manages entities, systems, and events in the ECS framework.
Definition Context.h:17
void UnregisterSystem()
Unregisters a system from the ECS context.
Definition ECS.h:387
void UnregisterAllSystems()
Un-registers all systems from the ECS context.
Definition ECS.h:355
Internal::EntityComponentView< Types... > Each(bool includePendingDestroy=false)
Iterates over all entities in the ECS context (used in ranged loops)
Definition ECS.h:551
void DisableSystem()
Disables a system in the ECS context.
Definition ECS.h:436
Internal::EntityView All(bool includePendingDestroy=false)
Iterates over all entities in the ECS context (used in ranged loops)
Definition ECS.h:519
void SubscribeEvent(EventSubscriber< T > *subscriber)
Subscribes to an event in the ECS context.
Definition ECS.h:460
void UnsubscribeAll(void *subscriber)
Un-subscribes from all events in the ECS context (usually called in the system destructor)
Definition ECS.h:501
void Reset()
Resets the ECS context, clearing all entities but keeping the systems.
Definition ECS.h:340
void Tick(const F64 dt)
Updates the ECS context (updates all the systems)
Definition ECS.h:567
Entity * CreateEntity()
Creates a new entity in the ECS context.
Definition ECS.h:266
bool ClearPending()
Clears all entities pending destruction in the ECS context.
Definition ECS.h:317
void EmitEvent(const T &event)
Emits an event in the ECS context to all subscribers.
Definition ECS.h:535
U64 GetEntityCount() const noexcept
Gets the total count of entities in the ECS context.
Definition Context.h:40
virtual ~Context()
Destroys the ECS context.
Definition ECS.h:216
void UnsubscribeEvent(EventSubscriber< T > *subscriber)
Unsubscribes from an event in the ECS context.
Definition ECS.h:480
T * RegisterSystem(Args &&... args)
Registers a new system in the ECS context.
Definition ECS.h:367
bool IsSystmEnabled()
Checks if a system is enabled in the ECS context.
Definition ECS.h:448
void EnableSystem()
Enables a system in the ECS context.
Definition ECS.h:424
void DestroyEntity(Entity *e, bool immediate=false)
Destroys an entity in the ECS context.
Definition ECS.h:278
Entity * GetEntityById(U64 id)
Retrieves an entity by its unique identifier.
Definition ECS.h:243
Context()
Constructs a new ECS context.
Entity * GetEntityByIndex(U64 index)
Retrieves an entity by its index.
Definition ECS.h:258
T * GetSystem()
Gets a system from the ECS context.
Definition ECS.h:411
Class to represent an entity in an ECS Context.
Definition Entity.h:10
ComponentHandle< T > Add(Args &&... args)
Adds a component to the entity.
Definition ECS.h:151
bool Remove()
Gets a component from the entity.
Definition ECS.h:196
static constexpr U64 InvalidId
Invalid entity ID.
Definition Entity.h:16
Context * GetContext() const noexcept
Gets the ECS context associated with the entity.
Definition Entity.h:40
bool IsPendingDestroy() const noexcept
Whether the entity has pending destruction.
Definition Entity.h:52
ComponentHandle< T > Get()
Gets a component from the entity.
Definition ECS.h:183
Base class for all event subscribers.
Definition Event.h:27
virtual void OnEvent(Context *context, const T &event)=0
Pure virtual function for handling event callback.
Iterator class to allow easy iteration over entities with components.
Definition Entity.h:157
bool IsEnd() const noexcept
Checks if the iterator is at the end.
Definition ECS.h:25
EntityComponentIterator< Types... > & operator++()
Moves the iterator to the next entity and skips over entities based on input criteria.
Definition ECS.h:40
Entity * GetEntity() const noexcept
Gets the current entity held by the iterator.
Definition ECS.h:31
bool IncludePendingDestroy() const noexcept
Whether the iterator should include pending destruction entities.
Definition Entity.h:178
EntityComponentIterator(Context *context, U64 index, bool isEnd, bool includePendingDestroy)
Initializes the iterator with necessary information.
Definition ECS.h:12
Class to represent a view over a range of entities in an ECS Context.
Definition Entity.h:273
EntityComponentView(const EntityComponentIterator< Types... > &first, const EntityComponentIterator< Types... > &last)
Initializes the view with iterator range.
Definition ECS.h:73
Iterator class to allow easy iteration over entities.
Definition Entity.h:316
Entity * GetEntity() const noexcept
Gets the current entity held by the iterator.
Definition ECS.h:113
bool IsEnd() const noexcept
Checks if the iterator is at the end.
Definition ECS.h:108
EntityIterator(Context *context, U64 index, bool isEnd, bool includePendingDestroy)
Initializes the iterator with necessary information.
Definition ECS.h:96
EntityIterator & operator++()
Moves the iterator to the next entity and skips over entities based on input criteria.
Definition ECS.h:121
Class to represent a view over a range of entities in an ECS Context.
Definition Entity.h:427
Base class for all event subscribers usually systems.
Definition Event.h:12
A concept that checks if a class derives from SystemBase.
Definition Context.h:11
Event for when component is added.
Definition Event.h:75
Event for when component is removed.
Definition Event.h:93
Template class for component containers (concrete implementation of ComponentContainerBase)
Definition Component.h:31
virtual void OnRemove(Entity *entity) override
Method called when an entity is removed.
Definition ECS.h:142