Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
world.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cstdarg>
5#include <cstdint>
6#include <type_traits>
7
8#include "gaia/cnt/darray.h"
9#include "gaia/cnt/darray_ext.h"
10#include "gaia/cnt/map.h"
11#include "gaia/cnt/sarray_ext.h"
12#include "gaia/cnt/set.h"
13#include "gaia/config/profiler.h"
14#include "gaia/core/hashing_policy.h"
15#include "gaia/core/hashing_string.h"
16#include "gaia/core/span.h"
17#include "gaia/core/string.h"
18#include "gaia/core/utility.h"
19#include "gaia/ecs/api.h"
20#include "gaia/ecs/archetype.h"
21#include "gaia/ecs/archetype_common.h"
22#include "gaia/ecs/archetype_graph.h"
23#include "gaia/ecs/chunk.h"
24#include "gaia/ecs/chunk_allocator.h"
25#include "gaia/ecs/command_buffer_fwd.h"
26#include "gaia/ecs/common.h"
27#include "gaia/ecs/component.h"
28#include "gaia/ecs/component_cache.h"
29#include "gaia/ecs/component_cache_item.h"
30#include "gaia/ecs/component_getter.h"
31#include "gaia/ecs/component_setter.h"
32#include "gaia/ecs/entity_container.h"
33#include "gaia/ecs/id.h"
34#include "gaia/ecs/query.h"
35#include "gaia/ecs/query_cache.h"
36#include "gaia/ecs/query_common.h"
37#include "gaia/ecs/query_info.h"
38#include "gaia/ecs/ser_binary.h"
39#include "gaia/mem/mem_alloc.h"
40#include "gaia/ser/ser_rt.h"
41#include "gaia/util/logging.h"
42
43namespace gaia {
44 namespace ecs {
45 class SystemBuilder;
46 class World;
47
48 class GAIA_API World final {
49 friend class ECSSystem;
50 friend class ECSSystemManager;
51 friend CommandBufferST;
52 friend CommandBufferMT;
53 friend void lock(World&);
54 friend void unlock(World&);
55
56 BinarySerializer m_binarySerializer;
57 ser::ISerializer* m_pSerializer{};
58
59 using TFunc_Void_With_Entity = void(Entity);
60 static void func_void_with_entity([[maybe_unused]] Entity entity) {}
61
64
66 ComponentCache m_compCache;
68 QueryCache m_queryCache;
74 QuerySerMap m_querySerMap;
75 uint32_t m_nextQuerySerId = 0;
76
78 EntityToArchetypeMap m_entityToArchetypeMap;
87 PairMap m_entityToAsTargets;
95 PairMap m_entityToAsRelations;
97 PairMap m_relationsToTargets;
99 PairMap m_targetsToRelations;
100
102 ArchetypeDArray m_archetypes;
104 ArchetypeMapByHash m_archetypesByHash;
106 ArchetypeMapById m_archetypesById;
107
109 Archetype* m_pRootArchetype = nullptr;
111 Archetype* m_pEntityArchetype = nullptr;
113 Archetype* m_pCompArchetype = nullptr;
115 ArchetypeId m_nextArchetypeId = 0;
116
118 EntityContainers m_recs;
119
122
124 cnt::set<ArchetypeLookupKey> m_reqArchetypesToDel;
126 cnt::set<EntityLookupKey> m_reqEntitiesToDel;
127
129 CommandBufferST* m_pCmdBufferST;
131 CommandBufferMT* m_pCmdBufferMT;
133 ecs::Query m_systemsQuery;
134
136 cnt::set<EntityLookupKey> m_entitiesToDel;
140 ArchetypeDArray m_archetypesToDel;
142 uint32_t m_defragLastArchetypeIdx = 0;
144 uint32_t m_defragEntitiesPerTick = 100;
145
147 uint32_t m_worldVersion = 0;
148
149 uint32_t m_structuralChangesLocked = 0;
150
151 public:
152 World():
153 // Command buffer for the main thread
154 m_pCmdBufferST(cmd_buffer_st_create(*this)),
155 // Command buffer safe for concurrent access
156 m_pCmdBufferMT(cmd_buffer_mt_create(*this)) {
157 init();
158 }
159
160 ~World() {
161 done();
162 cmd_buffer_destroy(*m_pCmdBufferST);
163 cmd_buffer_destroy(*m_pCmdBufferMT);
164 }
165
166 World(World&&) = delete;
167 World(const World&) = delete;
168 World& operator=(World&&) = delete;
169 World& operator=(const World&) = delete;
170
171 //----------------------------------------------------------------------
172
176 template <bool UseCache = true>
177 auto query() {
178 if constexpr (UseCache) {
179 Query q(
180 *const_cast<World*>(this), m_queryCache,
181 //
182 m_nextArchetypeId, m_worldVersion, m_archetypesById, m_entityToArchetypeMap, m_archetypes);
183 return q;
184 } else {
185 QueryUncached q(
186 *const_cast<World*>(this),
187 //
188 m_nextArchetypeId, m_worldVersion, m_archetypesById, m_entityToArchetypeMap, m_archetypes);
189 return q;
190 }
191 }
192
193 //----------------------------------------------------------------------
194
195 GAIA_NODISCARD EntityContainer& fetch(Entity entity) {
196 // Valid entity
197 GAIA_ASSERT(valid(entity));
198 // Wildcard pairs are not a real entity so we can't accept them
199 GAIA_ASSERT(!entity.pair() || !is_wildcard(entity));
200 return m_recs[entity];
201 }
202
203 GAIA_NODISCARD const EntityContainer& fetch(Entity entity) const {
204 // Valid entity
205 GAIA_ASSERT(valid(entity));
206 // Wildcard pairs are not a real entity so we can't accept them
207 GAIA_ASSERT(!entity.pair() || !is_wildcard(entity));
208 return m_recs[entity];
209 }
210
211 GAIA_NODISCARD static bool is_req_del(const EntityContainer& ec) {
212 if ((ec.flags & EntityContainerFlags::DeleteRequested) != 0)
213 return true;
214 if (ec.pArchetype != nullptr && ec.pArchetype->is_req_del())
215 return true;
216
217 return false;
218 }
219
220 //----------------------------------------------------------------------
221
222 void set_serializer(ser::ISerializer* pSerializer) {
223 if (pSerializer == nullptr) {
224 // Always use the binary serializer as the default
225 m_pSerializer = &m_binarySerializer;
226 return;
227 }
228
229 m_pSerializer = pSerializer;
230 }
231
232 ser::ISerializer* get_serializer() const {
233 return m_pSerializer;
234 }
235
236 //----------------------------------------------------------------------
237
238 struct EntityBuilder final {
239 friend class World;
240
241 World& m_world;
247 uint32_t m_rowSrc;
254
255 cnt::sarray_ext<Entity, 32> tl_new_comps;
256 cnt::sarray_ext<Entity, 32> tl_del_comps;
257
258 EntityBuilder(World& world, Entity entity, EntityContainer& ec):
259 m_world(world), m_pArchetypeSrc(ec.pArchetype), m_pChunkSrc(ec.pChunk), m_rowSrc(ec.row),
260 m_pArchetype(ec.pArchetype), m_entity(entity) {
261 // Make sure entity matches the provided entity container record
262 GAIA_ASSERT(ec.pChunk->entity_view()[ec.row] == entity);
263 }
264
265 EntityBuilder(World& world, Entity entity): m_world(world), m_entity(entity) {
266 const auto& ec = world.fetch(entity);
267 m_pArchetypeSrc = ec.pArchetype;
268 m_pChunkSrc = ec.pChunk;
269 m_rowSrc = ec.row;
270
271 m_pArchetype = ec.pArchetype;
272 }
273
274 EntityBuilder(const EntityBuilder&) = default;
275 EntityBuilder(EntityBuilder&&) = delete;
276 EntityBuilder& operator=(const EntityBuilder&) = delete;
277 EntityBuilder& operator=(EntityBuilder&&) = delete;
278
280 commit();
281 }
282
285 void commit() {
286 // No requests to change the archetype were made
287 if (m_pArchetype == nullptr)
288 return;
289
290 // Change in archetype detected
291 if (m_pArchetypeSrc != m_pArchetype) {
292 auto& ec = m_world.fetch(m_entity);
293 GAIA_ASSERT(ec.pArchetype == m_pArchetypeSrc);
294
295 // Trigger remove hooks if there are any
296 trigger_del_hooks();
297
298 // Now that we have the final archetype move the entity to it
299 m_world.move_entity_raw(m_entity, ec, *m_pArchetype);
300
301 // Update the entity string pointer if necessary
302 if (m_targetNameKey.str() != nullptr) {
303 const auto compIdx = ec.pChunk->comp_idx(GAIA_ID(EntityDesc));
304 // No need to update version, entity move did it already.
305 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut_gen<false>(compIdx, ec.row));
306 GAIA_ASSERT(core::check_alignment(pDesc));
307 *pDesc = {m_targetNameKey.str(), m_targetNameKey.len()};
308 }
309
310 // Trigger add hooks if there are any
311 trigger_add_hooks();
312
313 m_pArchetypeSrc = ec.pArchetype;
314 m_pChunkSrc = ec.pChunk;
315 m_rowSrc = ec.row;
316 }
317 // Archetype is still the same. Make sure no chunk movement has happened.
318 else {
319#if GAIA_ASSERT_ENABLED
320 auto& ec = m_world.fetch(m_entity);
321 GAIA_ASSERT(ec.pChunk == m_pChunkSrc);
322#endif
323
324 // Update the entity string pointer if necessary
325 if (m_targetNameKey.str() != nullptr) {
326 const auto compIdx = m_pChunkSrc->comp_idx(GAIA_ID(EntityDesc));
327 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<true>(compIdx, m_rowSrc));
328 GAIA_ASSERT(core::check_alignment(pDesc));
329 *pDesc = {m_targetNameKey.str(), m_targetNameKey.len()};
330 }
331 }
332
333 // Finalize the builder by reseting the archetype pointer
334 m_pArchetype = nullptr;
335 m_targetNameKey = {};
336 }
337
345 void name(const char* name, uint32_t len = 0) {
346 name_inter<true>(name, len);
347 }
348
360 void name_raw(const char* name, uint32_t len = 0) {
361 name_inter<false>(name, len);
362 }
363
365 void del_name() {
366 if (m_entity.pair())
367 return;
368
369 const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
370 if (compIdx == BadIndex)
371 return;
372
373 {
374 const auto* pDesc = reinterpret_cast<const EntityDesc*>(m_pChunkSrc->comp_ptr(compIdx, m_rowSrc));
375 GAIA_ASSERT(core::check_alignment(pDesc));
376 if (pDesc->name == nullptr)
377 return;
378 }
379
380 // No need to update version, commit() will do it
381 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<false>(compIdx, m_rowSrc));
382 del_inter(EntityNameLookupKey(pDesc->name, pDesc->len, 0));
383
384 del_inter(GAIA_ID(EntityDesc));
385 m_targetNameKey = {};
386 }
387
391 GAIA_PROF_SCOPE(EntityBuilder::add);
392 GAIA_ASSERT(m_world.valid(m_entity));
393 GAIA_ASSERT(m_world.valid(entity));
394
395 add_inter(entity);
396 return *this;
397 }
398
402 GAIA_PROF_SCOPE(EntityBuilder::add);
403 GAIA_ASSERT(m_world.valid(m_entity));
404 GAIA_ASSERT(m_world.valid(pair.first()));
405 GAIA_ASSERT(m_world.valid(pair.second()));
406
407 add_inter(pair);
408 return *this;
409 }
410
414 EntityBuilder& as(Entity entityBase) {
415 return add(Pair(Is, entityBase));
416 }
417
422 GAIA_NODISCARD bool as(Entity entity, Entity entityBase) const {
423 return static_cast<const World&>(m_world).is(entity, entityBase);
424 }
425
428 return add(Pair(ChildOf, parent));
429 }
430
432 template <typename T>
434 if constexpr (is_pair<T>::value) {
435 const auto rel = m_world.add<typename T::rel>().entity;
436 const auto tgt = m_world.add<typename T::tgt>().entity;
437 const Entity ent = Pair(rel, tgt);
438 add_inter(ent);
439 return ent;
440 } else {
441 return m_world.add<T>().entity;
442 }
443 }
444
445#if GAIA_USE_VARIADIC_API
446 template <typename... T>
447 EntityBuilder& add() {
448 (verify_comp<T>(), ...);
449 (add(register_component<T>()), ...);
450 return *this;
451 }
452#else
453 template <typename T>
454 EntityBuilder& add() {
455 verify_comp<T>();
456 add(register_component<T>());
457 return *this;
458 }
459#endif
460
464 GAIA_PROF_SCOPE(EntityBuilder::del);
465 GAIA_ASSERT(m_world.valid(m_entity));
466 GAIA_ASSERT(m_world.valid(entity));
467 del_inter(entity);
468 return *this;
469 }
470
474 GAIA_PROF_SCOPE(EntityBuilder::add);
475 GAIA_ASSERT(m_world.valid(m_entity));
476 GAIA_ASSERT(m_world.valid(pair.first()));
477 GAIA_ASSERT(m_world.valid(pair.second()));
478 del_inter(pair);
479 return *this;
480 }
481
482#if GAIA_USE_VARIADIC_API
483 template <typename... T>
484 EntityBuilder& del() {
485 (verify_comp<T>(), ...);
486 (del(register_component<T>()), ...);
487 return *this;
488 }
489#else
490 template <typename T>
491 EntityBuilder& del() {
492 verify_comp<T>();
493 del(register_component<T>());
494 return *this;
495 }
496#endif
497
498 private:
501 void trigger_add_hooks() {
502#if GAIA_ENABLE_ADD_DEL_HOOKS
503 m_world.lock();
504
505 for (auto entity: tl_new_comps) {
506 const auto& item = m_world.comp_cache().get(entity);
507 const auto& hooks = ComponentCache::hooks(item);
508 if (hooks.func_add != nullptr)
509 hooks.func_add(m_world, item, m_entity);
510 }
511 tl_new_comps.clear();
512
513 m_world.unlock();
514#endif
515 }
516
519 void trigger_del_hooks() {
520#if GAIA_ENABLE_ADD_DEL_HOOKS
521 m_world.lock();
522
523 for (auto entity: tl_del_comps) {
524 const auto& item = m_world.comp_cache().get(entity);
525 const auto& hooks = ComponentCache::hooks(item);
526 if (hooks.func_del != nullptr)
527 hooks.func_del(m_world, item, m_entity);
528 }
529 tl_del_comps.clear();
530
531 m_world.unlock();
532#endif
533 }
534
535 bool handle_add_entity(Entity entity) {
536 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> targets;
537
538 const auto& ecMain = m_world.fetch(entity);
539
540 // Handle entity combinations that can't be together
541 if ((ecMain.flags & EntityContainerFlags::HasCantCombine) != 0) {
542 m_world.targets(entity, CantCombine, [&targets](Entity target) {
543 targets.push_back(target);
544 });
545 for (auto e: targets) {
546 if (m_pArchetype->has(e)) {
547#if GAIA_ASSERT_ENABLED
548 GAIA_ASSERT2(false, "Trying to add an entity which can't be combined with the source");
549 print_archetype_entities(m_world, *m_pArchetype, entity, true);
550#endif
551 return false;
552 }
553 }
554 }
555
556 // Handle exclusivity
557 if (entity.pair()) {
558 // Check if (rel, tgt)'s rel part is exclusive
559 const auto& ecRel = m_world.m_recs.entities[entity.id()];
560 if ((ecRel.flags & EntityContainerFlags::IsExclusive) != 0) {
561 auto rel = Entity(
562 entity.id(), ecRel.data.gen, (bool)ecRel.data.ent, (bool)ecRel.data.pair,
563 (EntityKind)ecRel.data.kind);
564 auto tgt = m_world.get(entity.gen());
565
566 // Make sure to remove the (rel, tgt0) so only the new (rel, tgt1) remains.
567 // However, before that we need to make sure there only exists one target at most.
568 targets.clear();
569 m_world.targets_if(m_entity, rel, [&targets](Entity target) {
570 targets.push_back(target);
571 // Stop the moment we have more than 1 target. This kind of scenario is not supported
572 // and can happen only if Exclusive is added after multiple relationships already exist.
573 return targets.size() < 2;
574 });
575
576 const auto targetsCnt = targets.size();
577 if (targetsCnt > 1) {
578#if GAIA_ASSERT_ENABLED
579 GAIA_ASSERT2(
580 false, "Trying to add a pair with exclusive relationship but there are multiple targets present. "
581 "Make sure to add the Exclusive property before any relationships with it are created.");
582 print_archetype_entities(m_world, *m_pArchetype, entity, true);
583#endif
584 return false;
585 }
586
587 // Remove the previous relationship if possible.
588 // We avoid self-removal.
589 const auto tgtNew = *targets.begin();
590 if (targetsCnt == 1 && tgt != tgtNew) {
591 // Exclusive relationship replaces the previous one.
592 // We need to check if the old one can be removed.
593 // This is what del_inter does on the inside.
594 // It first checks if entity can be deleted and calls handle_del afterwards.
595 if (!can_del(entity)) {
596#if GAIA_ASSERT_ENABLED
597 GAIA_ASSERT2(
598 false, "Trying to replace an exclusive relationship but the entity which is getting removed has "
599 "dependencies.");
600 print_archetype_entities(m_world, *m_pArchetype, entity, true);
601#endif
602 return false;
603 }
604
605 handle_del(ecs::Pair(rel, tgtNew));
606 }
607 }
608 }
609
610 // Handle requirements
611 {
612 targets.clear();
613 m_world.targets(entity, Requires, [&targets](Entity target) {
614 targets.push_back(target);
615 });
616
617 for (auto e: targets) {
618 auto* pArchetype = m_pArchetype;
619 handle_add<false>(e);
620 if (m_pArchetype != pArchetype)
621 handle_add_entity(e);
622 }
623 }
624
625 return true;
626 }
627
628 GAIA_NODISCARD bool has_Requires_tgt(Entity entity) const {
629 // Don't allow to delete entity if something in the archetype requires it
630 auto ids = m_pArchetype->ids_view();
631 for (auto e: ids) {
632 if (m_world.has(e, Pair(Requires, entity)))
633 return true;
634 }
635
636 return false;
637 }
638
639 static void set_flag(EntityContainerFlagsType& flags, EntityContainerFlags flag, bool enable) {
640 if (enable)
641 flags |= flag;
642 else
643 flags &= ~flag;
644 }
645
646 void set_flag(Entity entity, EntityContainerFlags flag, bool enable) {
647 auto& ec = m_world.fetch(entity);
648 set_flag(ec.flags, flag, enable);
649 }
650
651 void try_set_flags(Entity entity, bool enable) {
652 auto& ecMain = m_world.fetch(m_entity);
653 try_set_CantCombine(ecMain, entity, enable);
654
655 auto& ec = m_world.fetch(entity);
656 try_set_Is(ec, entity, enable);
657 try_set_IsExclusive(ecMain, entity, enable);
658 try_set_IsSingleton(ecMain, entity, enable);
659 try_set_OnDelete(ecMain, entity, enable);
660 try_set_OnDeleteTarget(entity, enable);
661 }
662
663 void try_set_Is(EntityContainer& ec, Entity entity, bool enable) {
664 if (!entity.pair() || entity.id() != Is.id())
665 return;
666
667 set_flag(ec.flags, EntityContainerFlags::HasAliasOf, enable);
668 }
669
670 void try_set_CantCombine(EntityContainer& ec, Entity entity, bool enable) {
671 if (!entity.pair() || entity.id() != CantCombine.id())
672 return;
673
674 GAIA_ASSERT(entity != m_entity);
675
676 // Setting the flag can be done right away.
677 // One bit can only contain information about one pair but there
678 // can be any amount of CanCombine pairs formed with an entity.
679 // Therefore, when resetting the flag, we first need to check if there
680 // are any other targets with this flag set and only reset the flag
681 // if there is only one present.
682 if (enable)
683 set_flag(ec.flags, EntityContainerFlags::HasCantCombine, true);
684 else if ((ec.flags & EntityContainerFlags::HasCantCombine) != 0) {
685 uint32_t targets = 0;
686 m_world.targets(m_entity, CantCombine, [&targets]([[maybe_unused]] Entity entity) {
687 ++targets;
688 });
689 if (targets == 1)
690 set_flag(ec.flags, EntityContainerFlags::HasCantCombine, false);
691 }
692 }
693
694 void try_set_IsExclusive(EntityContainer& ec, Entity entity, bool enable) {
695 if (entity.pair() || entity.id() != Exclusive.id())
696 return;
697
698 set_flag(ec.flags, EntityContainerFlags::IsExclusive, enable);
699 }
700
701 void try_set_OnDeleteTarget(Entity entity, bool enable) {
702 if (!entity.pair())
703 return;
704
705 const auto rel = m_world.get(entity.id());
706 const auto tgt = m_world.get(entity.gen());
707
708 // Adding a pair to an entity with OnDeleteTarget relationship.
709 // We need to update the target entity's flags.
710 if (m_world.has(rel, Pair(OnDeleteTarget, Delete)))
711 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Delete, enable);
712 else if (m_world.has(rel, Pair(OnDeleteTarget, Remove)))
713 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Remove, enable);
714 else if (m_world.has(rel, Pair(OnDeleteTarget, Error)))
715 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Error, enable);
716 }
717
718 void try_set_OnDelete(EntityContainer& ec, Entity entity, bool enable) {
719 if (entity == Pair(OnDelete, Delete))
720 set_flag(ec.flags, EntityContainerFlags::OnDelete_Delete, enable);
721 else if (entity == Pair(OnDelete, Remove))
722 set_flag(ec.flags, EntityContainerFlags::OnDelete_Remove, enable);
723 else if (entity == Pair(OnDelete, Error))
724 set_flag(ec.flags, EntityContainerFlags::OnDelete_Error, enable);
725 }
726
727 void try_set_IsSingleton(EntityContainer& ec, Entity entity, bool enable) {
728 const bool isSingleton = enable && m_entity == entity;
729 set_flag(ec.flags, EntityContainerFlags::IsSingleton, isSingleton);
730 }
731
732 void handle_DependsOn(Entity entity, bool enable) {
733 (void)entity;
734 (void)enable;
735 // auto& ec = m_world.fetch(entity);
736 // if (enable) {
737 // // Calculate the depth in the dependency tree
738 // uint32_t depth = 1;
739
740 // auto e = entity;
741 // if (m_world.valid(e)) {
742 // while (true) {
743 // auto tgt = m_world.target(e, DependsOn);
744 // if (tgt == EntityBad)
745 // break;
746
747 // ++depth;
748 // e = tgt;
749 // }
750 // }
751 // ec.depthDependsOn = (uint8_t)depth;
752
753 // // Update depth for all entities depending on this one
754 // auto q = m_world.query<false>();
755 // q.all(ecs::Pair(DependsOn, m_entity)) //
756 // .each([&](Entity dependingEntity) {
757 // auto& ecDependingEntity = m_world.fetch(dependingEntity);
758 // ecDependingEntity.depthDependsOn += (uint8_t)depth;
759 // });
760 // } else {
761 // // Update depth for all entities depending on this one
762 // auto q = m_world.query<false>();
763 // q.all(ecs::Pair(DependsOn, m_entity)) //
764 // .each([&](Entity dependingEntity) {
765 // auto& ecDependingEntity = m_world.fetch(dependingEntity);
766 // ecDependingEntity.depthDependsOn -= ec.depthDependsOn;
767 // });
768
769 // // Reset the depth
770 // ec.depthDependsOn = 0;
771 // }
772 }
773
774 template <bool IsBootstrap>
775 bool handle_add(Entity entity) {
776#if GAIA_ASSERT_ENABLED
777 World::verify_add(m_world, *m_pArchetype, m_entity, entity);
778#endif
779
780 // Don't add the same entity twice
781 if (m_pArchetype->has(entity))
782 return false;
783
784 try_set_flags(entity, true);
785
786 // Update the Is relationship base counter if necessary
787 if (entity.pair() && entity.id() == Is.id()) {
788 auto e = m_world.get(entity.gen());
789
790 EntityLookupKey entityKey(m_entity);
791 EntityLookupKey eKey(e);
792
793 // m_entity -> {..., e}
794 auto& entity_to_e = m_world.m_entityToAsTargets[entityKey];
795 entity_to_e.insert(eKey);
796 // e -> {..., m_entity}
797 auto& e_to_entity = m_world.m_entityToAsRelations[eKey];
798 e_to_entity.insert(entityKey);
799
800 // Make sure the relation entity is registered as archetype so queries can find it
801 // auto& ec = m_world.fetch(tgt);
802 // m_world.add_entity_archetype_pair(m_entity, ec.pArchetype);
803
804 // Cached queries might need to be invalidated.
805 m_world.invalidate_queries_for_entity({Is, e});
806 }
807
808 m_pArchetype = m_world.foc_archetype_add(m_pArchetype, entity);
809
810 if constexpr (!IsBootstrap) {
811 handle_DependsOn(entity, true);
812 if (entity.comp())
813 tl_new_comps.push_back(entity);
814 }
815
816 return true;
817 }
818
819 void handle_del(Entity entity) {
820#if GAIA_ASSERT_ENABLED
821 World::verify_del(m_world, *m_pArchetype, m_entity, entity);
822#endif
823
824 // Don't delete what has not beed added
825 if (!m_pArchetype->has(entity))
826 return;
827
828 try_set_flags(entity, false);
829 handle_DependsOn(entity, false);
830
831 // Update the Is relationship base counter if necessary
832 if (entity.pair() && entity.id() == Is.id()) {
833 auto e = m_world.get(entity.gen());
834
835 EntityLookupKey entityKey(m_entity);
836 EntityLookupKey eKey(e);
837
838 // Cached queries might need to be invalidated.
839 m_world.invalidate_queries_for_entity({Is, e});
840
841 // m_entity -> {..., e}
842 {
843 const auto it = m_world.m_entityToAsTargets.find(entityKey);
844 GAIA_ASSERT(it != m_world.m_entityToAsTargets.end());
845 auto& set = it->second;
846 GAIA_ASSERT(!set.empty());
847 set.erase(eKey);
848
849 // Remove the record if it is not referenced anymore
850 if (set.empty())
851 m_world.m_entityToAsTargets.erase(it);
852 }
853
854 // e -> {..., m_entity}
855 {
856 const auto it = m_world.m_entityToAsRelations.find(eKey);
857 GAIA_ASSERT(it != m_world.m_entityToAsRelations.end());
858 auto& set = it->second;
859 GAIA_ASSERT(!set.empty());
860 set.erase(entityKey);
861
862 // Remove the record if it is not referenced anymore
863 if (set.empty())
864 m_world.m_entityToAsRelations.erase(it);
865 }
866 }
867
868 m_pArchetype = m_world.foc_archetype_del(m_pArchetype, entity);
869
870 if (entity.comp())
871 tl_del_comps.push_back(entity);
872 }
873
874 void add_inter(Entity entity) {
875 GAIA_ASSERT(!is_wildcard(entity));
876
877 if (entity.pair()) {
878 // Make sure the entity container record exists if it is a pair
879 m_world.assign_pair(entity, *m_world.m_pEntityArchetype);
880 }
881
882 if (!handle_add_entity(entity))
883 return;
884
885 handle_add<false>(entity);
886 }
887
888 void add_inter_init(Entity entity) {
889 GAIA_ASSERT(!is_wildcard(entity));
890
891 if (entity.pair()) {
892 // Make sure the entity container record exists if it is a pair
893 m_world.assign_pair(entity, *m_world.m_pEntityArchetype);
894 }
895
896 if (!handle_add_entity(entity))
897 return;
898
899 handle_add<true>(entity);
900 }
901
902 GAIA_NODISCARD bool can_del(Entity entity) const noexcept {
903 if (has_Requires_tgt(entity))
904 return false;
905
906 return true;
907 }
908
909 bool del_inter(Entity entity) {
910 if (!can_del(entity))
911 return false;
912
913 handle_del(entity);
914 return true;
915 }
916
917 void del_inter(EntityNameLookupKey key) {
918 const auto it = m_world.m_nameToEntity.find(key);
919 // If the assert is hit it means the pointer to the name string was invalidated or became dangling.
920 // That should not be possible for strings managed internally so the only other option is user-managed
921 // strings are broken.
922 GAIA_ASSERT(it != m_world.m_nameToEntity.end());
923 if (it != m_world.m_nameToEntity.end()) {
924 // Release memory allocated for the string if we own it
925 if (it->first.owned())
926 mem::mem_free((void*)key.str());
927
928 m_world.m_nameToEntity.erase(it);
929 }
930 }
931
932 template <bool IsOwned>
933 void name_inter(const char* name, uint32_t len) {
935 GAIA_ASSERT(!m_entity.pair());
936 if (m_entity.pair())
937 return;
938
939 // When nullptr is passed for the name it means the user wants to delete the current name
940 if (name == nullptr) {
941 GAIA_ASSERT(len == 0);
942 del_name();
943 return;
944 }
945
946 GAIA_ASSERT(len < ComponentCacheItem::MaxNameLength);
947
948 // Make sure the name does not contain a dot because this character is reserved for
949 // hierarchical lookups, e.g. "parent.child.subchild".
950#if GAIA_ASSERT_ENABLED
951 GAIA_FOR(len) {
952 const bool hasInvalidCharacter = name[i] == '.';
953 GAIA_ASSERT(!hasInvalidCharacter && "Character '.' can't be used in entity names");
954 if (hasInvalidCharacter)
955 return;
956 }
957#endif
958
959 EntityNameLookupKey key(
960 name, len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len, IsOwned);
961
962 // Make sure the name is unique. Ignore setting the same name twice on the same entity.
963 // If it is not, there is nothing to do.
964 auto it = m_world.m_nameToEntity.find(key);
965 if (it == m_world.m_nameToEntity.end()) {
966 // If we already had some name, remove the pair from the map first.
967 if (m_targetNameKey.str() != nullptr) {
968 del_inter(m_targetNameKey);
969 } else {
970 const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
971 if (compIdx != BadIndex) {
972 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut(compIdx, m_rowSrc));
973 GAIA_ASSERT(core::check_alignment(pDesc));
974 if (pDesc->name != nullptr) {
975 del_inter(EntityNameLookupKey(pDesc->name, pDesc->len, 0));
976 pDesc->name = nullptr;
977 }
978 } else {
979 // Make sure EntityDesc is added to the entity.
980 add_inter(GAIA_ID(EntityDesc));
981 }
982 }
983
984 // Insert the new pair
985 it = m_world.m_nameToEntity.emplace(key, m_entity).first;
986 } else {
987#if GAIA_ASSERT_ENABLED
988 if (it->second != m_entity)
989 GAIA_ASSERT(false && "Trying to set non-unique name for an entity");
990#endif
991
992 // Attempts to set the same name again, or not a unique name, will be dropped.
993 return;
994 }
995
996 if constexpr (IsOwned) {
997 // Allocate enough storage for the name
998 char* entityStr = (char*)mem::mem_alloc(key.len() + 1);
999 memcpy((void*)entityStr, (const void*)name, key.len() + 1);
1000 entityStr[key.len()] = 0;
1001
1002 m_targetNameKey = EntityNameLookupKey(entityStr, key.len(), 1, {key.hash()});
1003
1004 // Update the map so it points to the newly allocated string.
1005 // We replace the pointer we provided in try_emplace with an internally allocated string.
1006 auto p = robin_hood::pair(std::make_pair(m_targetNameKey, m_entity));
1007 it->swap(p);
1008 } else {
1009 m_targetNameKey = key;
1010
1011 // We tell the map the string is non-owned.
1012 auto p = robin_hood::pair(std::make_pair(key, m_entity));
1013 it->swap(p);
1014 }
1015 }
1016 };
1017
1018 //----------------------------------------------------------------------
1019
1020 GAIA_NODISCARD ComponentCache& comp_cache_mut() {
1021 return m_compCache;
1022 }
1023 GAIA_NODISCARD const ComponentCache& comp_cache() const {
1024 return m_compCache;
1025 }
1026
1027 GAIA_NODISCARD QuerySerMap& query_ser_map() {
1028 return m_querySerMap;
1029 }
1030
1031 //----------------------------------------------------------------------
1032
1036 GAIA_NODISCARD bool valid(Entity entity) const {
1037 return entity.pair() //
1038 ? valid_pair(entity)
1039 : valid_entity(entity);
1040 }
1041
1042 //----------------------------------------------------------------------
1043
1047 GAIA_NODISCARD Entity get(EntityId id) const {
1048 GAIA_ASSERT(valid_entity_id(id));
1049
1050 const auto& ec = m_recs.entities[id];
1051 return Entity(id, ec.data.gen, (bool)ec.data.ent, (bool)ec.data.pair, (EntityKind)ec.data.kind);
1052 }
1053
1054 template <typename T>
1055 GAIA_NODISCARD Entity get() const {
1056 static_assert(!is_pair<T>::value, "Pairs can't be registered as components");
1057
1058 using CT = component_type_t<T>;
1059 using FT = typename CT::TypeFull;
1060
1061 const auto* pItem = comp_cache().find<FT>();
1062 GAIA_ASSERT(pItem != nullptr);
1063 return pItem->entity;
1064 }
1065
1066 //----------------------------------------------------------------------
1067
1073 return EntityBuilder(*this, entity);
1074 }
1075
1079 GAIA_NODISCARD Entity add(EntityKind kind = EntityKind::EK_Gen) {
1080 return add(*m_pEntityArchetype, true, false, kind);
1081 }
1082
1086 template <typename Func = TFunc_Void_With_Entity>
1087 void add_n(uint32_t count, Func func = func_void_with_entity) {
1088 add_entity_n(*m_pEntityArchetype, count, func);
1089 }
1090
1097 template <typename Func = TFunc_Void_With_Entity>
1098 void add_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
1099 auto& ec = m_recs.entities[entity.id()];
1100
1101 GAIA_ASSERT(ec.pArchetype != nullptr);
1102 GAIA_ASSERT(ec.pChunk != nullptr);
1103
1104 add_entity_n(*ec.pArchetype, count, func);
1105 }
1106
1110 template <typename T>
1111 GAIA_NODISCARD const ComponentCacheItem& add() {
1112 static_assert(!is_pair<T>::value, "Pairs can't be registered as components");
1113
1114 using CT = component_type_t<T>;
1115 using FT = typename CT::TypeFull;
1116 constexpr auto kind = CT::Kind;
1117
1118 const auto* pItem = comp_cache().find<FT>();
1119 if (pItem != nullptr)
1120 return *pItem;
1121
1122 const auto entity = add(*m_pCompArchetype, false, false, kind);
1123
1124 const auto& item = comp_cache_mut().add<FT>(entity);
1125 sset<Component>(item.entity) = item.comp;
1126
1127 // Make sure the default component entity name points to the cache item name.
1128 // The name is deleted when the component cache item is deleted.
1129 name_raw(item.entity, item.name.str(), item.name.len());
1130
1131 return item;
1132 }
1133
1138 void add(Entity entity, Entity object) {
1139 EntityBuilder(*this, entity).add(object);
1140 }
1141
1147 void add(Entity entity, Pair pair) {
1148 EntityBuilder(*this, entity).add(pair);
1149 }
1150
1156 template <typename T>
1157 void add(Entity entity) {
1158 EntityBuilder(*this, entity).add<T>();
1159 }
1160
1168 template <typename T>
1169 void add(Entity entity, Entity object, T&& value) {
1170 static_assert(core::is_raw_v<T>);
1171
1172 EntityBuilder(*this, entity).add(object);
1173
1174 const auto& ec = fetch(entity);
1175 // Make sure the idx is 0 for unique entities
1176 const auto idx = uint16_t(ec.row * (1U - (uint32_t)object.kind()));
1177 ComponentSetter{{ec.pChunk, idx}}.set(object, GAIA_FWD(value));
1178 }
1179
1186 template <typename T, typename U = typename actual_type_t<T>::Type>
1187 void add(Entity entity, U&& value) {
1188 EntityBuilder builder(*this, entity);
1189 auto object = builder.register_component<T>();
1190 builder.add(object);
1191 builder.commit();
1192
1193 const auto& ec = m_recs.entities[entity.id()];
1194 // Make sure the idx is 0 for unique entities
1195 const auto idx = uint16_t(ec.row * (1U - (uint32_t)object.kind()));
1196 ComponentSetter{{ec.pChunk, idx}}.set<T>(GAIA_FWD(value));
1197 }
1198
1199 //----------------------------------------------------------------------
1200
1205 void clear(Entity entity) {
1206 GAIA_ASSERT(!entity.pair());
1207 GAIA_ASSERT(valid(entity));
1208
1209 EntityBuilder eb(*this, entity);
1210
1211 // Remove back to front because it's better for the archetype graph
1212 auto ids = eb.m_pArchetype->ids_view();
1213 for (uint32_t i = (uint32_t)ids.size() - 1; i != (uint32_t)-1; --i)
1214 eb.del(ids[i]);
1215
1216 eb.commit();
1217 }
1218
1219 //----------------------------------------------------------------------
1220
1228 GAIA_NODISCARD Entity copy(Entity srcEntity) {
1229 GAIA_ASSERT(!srcEntity.pair());
1230 GAIA_ASSERT(valid(srcEntity));
1231
1232 auto& ec = m_recs.entities[srcEntity.id()];
1233 GAIA_ASSERT(ec.pArchetype != nullptr);
1234 GAIA_ASSERT(ec.pChunk != nullptr);
1235
1236 auto* pDstArchetype = ec.pArchetype;
1237
1238 // Names have to be unique so if we see that EntityDesc is present during copy
1239 // we navigate towards a version of the archetype without the EntityDesc.
1240 if (pDstArchetype->has<EntityDesc>()) {
1241 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
1242
1243 const auto dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
1244 auto& ecDst = m_recs.entities[dstEntity.id()];
1245
1246 Chunk::copy_foreign_entity_data(ec.pChunk, ec.row, ecDst.pChunk, ecDst.row);
1247 return dstEntity;
1248 }
1249
1250 // No description associated with the entity, direct copy is possible
1251 const auto dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
1252 Chunk::copy_entity_data(srcEntity, dstEntity, m_recs);
1253 return dstEntity;
1254 }
1255
1265 template <typename Func = TFunc_Void_With_Entity>
1266 void copy_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
1267 GAIA_ASSERT(!entity.pair());
1268 GAIA_ASSERT(valid(entity));
1269
1270 auto& ec = m_recs.entities[entity.id()];
1271
1272 GAIA_ASSERT(ec.pChunk != nullptr);
1273 GAIA_ASSERT(ec.pArchetype != nullptr);
1274
1275 auto* pSrcChunk = ec.pChunk;
1276
1277 auto* pDstArchetype = ec.pArchetype;
1278 if (pDstArchetype->has<EntityDesc>()) {
1279 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
1280
1281 // Entities array might get reallocated after m_recs.entities.alloc
1282 // so instead of fetching the container again we simply cache the row
1283 // of our source entity.
1284 const auto srcRow = ec.row;
1285
1286 EntityContainerCtx ctx{true, false, EntityKind::EK_Gen};
1287
1288 uint32_t left = count;
1289 do {
1290 auto* pDstChunk = pDstArchetype->foc_free_chunk();
1291 const uint32_t originalChunkSize = pDstChunk->size();
1292 const uint32_t freeSlotsInChunk = pDstChunk->capacity() - originalChunkSize;
1293 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
1294
1295 GAIA_FOR(toCreate) {
1296 const auto entityNew = m_recs.entities.alloc(&ctx);
1297 auto& ecNew = m_recs.entities[entityNew.id()];
1298 store_entity(ecNew, entityNew, pDstArchetype, pDstChunk);
1299
1300#if GAIA_ASSERT_ENABLED
1301 GAIA_ASSERT(ecNew.pChunk == pDstChunk);
1302 auto entityExpected = pDstChunk->entity_view()[ecNew.row];
1303 GAIA_ASSERT(entityExpected == entityNew);
1304#endif
1305
1306 Chunk::copy_foreign_entity_data(pSrcChunk, srcRow, pDstChunk, ecNew.row);
1307 }
1308
1309 // Call functors
1310 if constexpr (std::is_invocable_v<Func, CopyIter&>) {
1311 CopyIter it;
1312 it.set_world(this);
1313 it.set_archetype(pDstArchetype);
1314 it.set_chunk(pDstChunk);
1315 it.set_range((uint16_t)originalChunkSize, (uint16_t)toCreate);
1316 func(it);
1317 } else {
1318 auto entities = pDstChunk->entity_view();
1319 GAIA_FOR2(originalChunkSize, pDstChunk->size()) func(entities[i]);
1320 }
1321
1322 pDstChunk->update_versions();
1323
1324 left -= toCreate;
1325 } while (left > 0);
1326 } else {
1327 pDstArchetype = ec.pArchetype;
1328
1329 // Entities array might get reallocated after m_recs.entities.alloc
1330 // so instead of fetching the container again we simply cache the row
1331 // of our source entity.
1332 const auto srcRow = ec.row;
1333
1334 EntityContainerCtx ctx{true, false, EntityKind::EK_Gen};
1335
1336 uint32_t left = count;
1337 do {
1338 auto* pDstChunk = pDstArchetype->foc_free_chunk();
1339 const uint32_t originalChunkSize = pDstChunk->size();
1340 const uint32_t freeSlotsInChunk = pDstChunk->capacity() - originalChunkSize;
1341 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
1342
1343 GAIA_FOR(toCreate) {
1344 const auto entityNew = m_recs.entities.alloc(&ctx);
1345 auto& ecNew = m_recs.entities[entityNew.id()];
1346 store_entity(ecNew, entityNew, pDstArchetype, pDstChunk);
1347
1348#if GAIA_ASSERT_ENABLED
1349 GAIA_ASSERT(ecNew.pChunk == pDstChunk);
1350 auto entityExpected = pDstChunk->entity_view()[ecNew.row];
1351 GAIA_ASSERT(entityExpected == entityNew);
1352#endif
1353 }
1354
1355 // New entities were added, try updating the free chunk index
1356 pDstArchetype->try_update_free_chunk_idx();
1357
1358 // Call constructors for the generic components on the newly added entity if necessary
1359 pDstChunk->call_gen_ctors(originalChunkSize, toCreate);
1360
1361 // Copy data
1362 {
1363 GAIA_PROF_SCOPE(World::copy_n_entity_data);
1364
1365 auto srcRecs = pSrcChunk->comp_rec_view();
1366
1367 // Copy generic component data from reference entity to our new entity
1368 GAIA_FOR(pSrcChunk->size_generic()) {
1369 const auto& rec = srcRecs[i];
1370 if (rec.comp.size() == 0U)
1371 continue;
1372
1373 const auto* pSrc = (const void*)pSrcChunk->comp_ptr(i);
1374 GAIA_FOR_(toCreate, rowOffset) {
1375 auto* pDst = (void*)pDstChunk->comp_ptr_mut(i);
1376 rec.pItem->copy(
1377 pDst, pSrc, originalChunkSize + rowOffset, srcRow, pDstChunk->capacity(), pSrcChunk->capacity());
1378 }
1379 }
1380 }
1381
1382 // Call functors
1383 if constexpr (std::is_invocable_v<Func, CopyIter&>) {
1384 CopyIter it;
1385 it.set_world(this);
1386 it.set_archetype(pDstArchetype);
1387 it.set_chunk(pDstChunk);
1388 it.set_range((uint16_t)originalChunkSize, (uint16_t)toCreate);
1389 func(it);
1390 } else {
1391 auto entities = pDstChunk->entity_view();
1392 GAIA_FOR2(originalChunkSize, pDstChunk->size()) func(entities[i]);
1393 }
1394
1395 pDstChunk->update_versions();
1396
1397 left -= toCreate;
1398 } while (left > 0);
1399 }
1400 }
1401
1402 //----------------------------------------------------------------------
1403
1406 void del(Entity entity) {
1407 if (!entity.pair()) {
1408 // Delete all relationships associated with this entity (if any)
1409 del_inter(Pair(entity, All));
1410 del_inter(Pair(All, entity));
1411 }
1412
1413 del_inter(entity);
1414 }
1415
1420 void del(Entity entity, Entity object) {
1421 EntityBuilder(*this, entity).del(object);
1422 }
1423
1429 void del(Entity entity, Pair pair) {
1430 EntityBuilder(*this, entity).del(pair);
1431 }
1432
1438 template <typename T>
1439 void del(Entity entity) {
1440 using CT = component_type_t<T>;
1441 using FT = typename CT::TypeFull;
1442 EntityBuilder(*this, entity).del<FT>();
1443 }
1444
1445 //----------------------------------------------------------------------
1446
1448 void as(Entity entity, Entity entityBase) {
1449 // Make sure entityBase has an archetype of its own
1450 add(entityBase, entityBase);
1451 // Form the relationship
1452 add(entity, Pair(Is, entityBase));
1453 }
1454
1459 GAIA_NODISCARD bool is(Entity entity, Entity entityBase) const {
1460 return is_inter<false>(entity, entityBase);
1461 }
1462
1469 GAIA_NODISCARD bool in(Entity entity, Entity entityBase) const {
1470 return is_inter<true>(entity, entityBase);
1471 }
1472
1473 GAIA_NODISCARD bool is_base(Entity target) const {
1474 GAIA_ASSERT(valid_entity(target));
1475
1476 // Pairs are not supported
1477 if (target.pair())
1478 return false;
1479
1480 const auto it = m_entityToAsRelations.find(EntityLookupKey(target));
1481 return it != m_entityToAsRelations.end();
1482 }
1483
1484 //----------------------------------------------------------------------
1485
1487 void child(Entity entity, Entity parent) {
1488 add(entity, Pair(ChildOf, parent));
1489 }
1490
1493 GAIA_NODISCARD bool child(Entity entity, Entity parent) const {
1494 return has(entity, Pair(ChildOf, parent));
1495 }
1496
1497 //----------------------------------------------------------------------
1498
1504 template <
1505 typename T
1506#if GAIA_ENABLE_HOOKS
1507 ,
1508 bool TriggerHooks
1509#endif
1510 >
1511 void modify(Entity entity) {
1512 GAIA_ASSERT(valid(entity));
1513
1514 auto& ec = m_recs.entities[entity.id()];
1515 ec.pChunk->template modify<
1516 T
1517#if GAIA_ENABLE_HOOKS
1518 ,
1519 TriggerHooks
1520#endif
1521 >();
1522 }
1523
1524 //----------------------------------------------------------------------
1525
1531 GAIA_NODISCARD ComponentSetter acc_mut(Entity entity) {
1532 GAIA_ASSERT(valid(entity));
1533
1534 const auto& ec = m_recs.entities[entity.id()];
1535 return ComponentSetter{{ec.pChunk, ec.row}};
1536 }
1537
1544 template <typename T>
1545 GAIA_NODISCARD decltype(auto) set(Entity entity) {
1546 static_assert(!is_pair<T>::value);
1547 return acc_mut(entity).mut<T>();
1548 }
1549
1556 template <typename T>
1557 GAIA_NODISCARD decltype(auto) sset(Entity entity) {
1558 static_assert(!is_pair<T>::value);
1559 return acc_mut(entity).smut<T>();
1560 }
1561
1562 //----------------------------------------------------------------------
1563
1570 template <typename T>
1571 GAIA_NODISCARD decltype(auto) mut(Entity entity) {
1572 static_assert(!is_pair<T>::value);
1573 return acc_mut(entity).mut<T>();
1574 }
1575
1576 //----------------------------------------------------------------------
1577
1584 GAIA_ASSERT(valid(entity));
1585
1586 const auto& ec = m_recs.entities[entity.id()];
1587 return ComponentGetter{ec.pChunk, ec.row};
1588 }
1589
1597 template <typename T>
1598 GAIA_NODISCARD decltype(auto) get(Entity entity) const {
1599 return acc(entity).get<T>();
1600 }
1601
1602 //----------------------------------------------------------------------
1603
1607 GAIA_NODISCARD bool has(Entity entity) const {
1608 // Pair
1609 if (entity.pair()) {
1610 if (entity == Pair(All, All))
1611 return true;
1612
1613 if (is_wildcard(entity)) {
1614 if (!m_entityToArchetypeMap.contains(EntityLookupKey(entity)))
1615 return false;
1616
1617 // If the pair is found, both entities forming it need to be found as well
1618 GAIA_ASSERT(has(get(entity.id())) && has(get(entity.gen())));
1619
1620 return true;
1621 }
1622
1623 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
1624 if (it == m_recs.pairs.end())
1625 return false;
1626
1627 const auto& ec = it->second;
1628 if (is_req_del(ec))
1629 return false;
1630
1631#if GAIA_ASSERT_ENABLED
1632 // If the pair is found, both entities forming it need to be found as well
1633 GAIA_ASSERT(has(get(entity.id())) && has(get(entity.gen())));
1634
1635 // Index of the entity must fit inside the chunk
1636 auto* pChunk = ec.pChunk;
1637 GAIA_ASSERT(pChunk != nullptr && ec.row < pChunk->size());
1638#endif
1639
1640 return true;
1641 }
1642
1643 // Regular entity
1644 {
1645 // Entity ID has to fit inside the entity array
1646 if (entity.id() >= m_recs.entities.size())
1647 return false;
1648
1649 // Index of the entity must fit inside the chunk
1650 const auto& ec = m_recs.entities[entity.id()];
1651 if (is_req_del(ec))
1652 return false;
1653
1654 auto* pChunk = ec.pChunk;
1655 return pChunk != nullptr && ec.row < pChunk->size();
1656 }
1657 }
1658
1662 GAIA_NODISCARD bool has(Pair pair) const {
1663 return has((Entity)pair);
1664 }
1665
1672 GAIA_NODISCARD bool has(Entity entity, Entity object) const {
1673 const auto& ec = fetch(entity);
1674 if (is_req_del(ec))
1675 return false;
1676
1677 const auto* pArchetype = ec.pArchetype;
1678
1679 if (object.pair()) {
1680 // Early exit if there are no pairs on the archetype
1681 if (pArchetype->pairs() == 0)
1682 return false;
1683
1684 EntityId rel = object.id();
1685 EntityId tgt = object.gen();
1686
1687 // (*,*)
1688 if (rel == All.id() && tgt == All.id())
1689 return true;
1690
1691 // (X,*)
1692 if (rel != All.id() && tgt == All.id()) {
1693 auto ids = pArchetype->ids_view();
1694 for (auto id: ids) {
1695 if (!id.pair())
1696 continue;
1697 if (id.id() == rel)
1698 return true;
1699 }
1700
1701 return false;
1702 }
1703
1704 // (*,X)
1705 if (rel == All.id() && tgt != All.id()) {
1706 auto ids = pArchetype->ids_view();
1707 for (auto id: ids) {
1708 if (!id.pair())
1709 continue;
1710 if (id.gen() == tgt)
1711 return true;
1712 }
1713
1714 return false;
1715 }
1716 }
1717
1718 return pArchetype->has(object);
1719 }
1720
1727 GAIA_NODISCARD bool has(Entity entity, Pair pair) const {
1728 return has(entity, (Entity)pair);
1729 }
1730
1737 template <typename T>
1738 GAIA_NODISCARD bool has(Entity entity) const {
1739 GAIA_ASSERT(valid(entity));
1740
1741 const auto& ec = m_recs.entities[entity.id()];
1742 if (is_req_del(ec))
1743 return false;
1744
1745 return ec.pArchetype->has<T>();
1746 }
1747
1748 //----------------------------------------------------------------------
1749
1759 void name(Entity entity, const char* name, uint32_t len = 0) {
1760 EntityBuilder(*this, entity).name(name, len);
1761 }
1762
1776 void name_raw(Entity entity, const char* name, uint32_t len = 0) {
1777 EntityBuilder(*this, entity).name_raw(name, len);
1778 }
1779
1784 GAIA_NODISCARD const char* name(Entity entity) const {
1785 if (entity.pair())
1786 return nullptr;
1787
1788 const auto& ec = m_recs.entities[entity.id()];
1789 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
1790 if (compIdx == BadIndex) {
1791 // If no EntityDesc is assigned it is still possible to extract a name from
1792 // the entity. Components always come with a compile-time string associated
1793 // with them.
1794 if (!entity.entity())
1795 return m_compCache.get(entity).name.str();
1796
1797 return nullptr;
1798 }
1799
1800 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
1801 GAIA_ASSERT(core::check_alignment(pDesc));
1802 return pDesc->name;
1803 }
1804
1809 GAIA_NODISCARD const char* name(EntityId entityId) const {
1810 auto entity = get(entityId);
1811 return name(entity);
1812 }
1813
1821 GAIA_NODISCARD Entity get(const char* name, uint32_t len = 0) const {
1822 if (name == nullptr || name[0] == 0)
1823 return EntityBad;
1824
1825 Entity parent = EntityBad;
1826 Entity child = EntityBad;
1827 uint32_t posDot = 0;
1828
1829 // If no length was given, we have to find it ourselves
1830 if (len == 0) {
1831 while (name[len] != '\0')
1832 ++len;
1833 }
1834 std::span<const char> str(name, len);
1835
1836 // Localize a dot in the string. If not present, use the entire string.
1837 {
1838 posDot = core::get_index(str, '.');
1839 if (posDot == BadIndex)
1840 return name_to_entity(str);
1841
1842 if (posDot == 0)
1843 return EntityBad;
1844
1845 parent = name_to_entity(str.subspan(0, posDot));
1846 if (parent == EntityBad)
1847 return EntityBad;
1848 }
1849
1850 str = str.subspan(posDot + 1);
1851 while (!str.empty()) {
1852 posDot = core::get_index(str, '.');
1853
1854 // A) No more dots in the string, use the entire substring.
1855 if (posDot == BadIndex) {
1856 child = name_to_entity(str);
1857
1858 // If the entity is not found, there is nothing for us to do anymore.
1859 // If the parent-child relationship does not exist there is nothing for us to do anymore.
1860 if (child == EntityBad || !this->child(child, parent))
1861 return EntityBad;
1862
1863 return child;
1864 }
1865
1866 if (posDot == 0)
1867 return EntityBad;
1868
1869 // B) More dots in the string
1870 child = name_to_entity(str.subspan(0, posDot));
1871
1872 // If the entity is not found, there is nothing for us to do anymore.
1873 // If the parent-child relationship does not exist there is nothing for us to do anymore.
1874 if (child == EntityBad || !this->child(child, parent))
1875 return EntityBad;
1876
1877 // Current child becomes the parent for the next step
1878 parent = child;
1879
1880 str = str.subspan(posDot + 1);
1881 }
1882
1883 return parent;
1884 }
1885
1886 GAIA_NODISCARD Entity get_inter(const char* name, uint32_t len = 0) const {
1887 if (name == nullptr || name[0] == 0)
1888 return EntityBad;
1889
1890 auto key = EntityNameLookupKey(name, len, 0);
1891
1892 const auto it = m_nameToEntity.find(key);
1893 if (it != m_nameToEntity.end())
1894 return it->second;
1895
1896 // Name not found. This might be a component so check the component cache
1897 const auto* pItem = m_compCache.find(name, len);
1898 if (pItem != nullptr)
1899 return pItem->entity;
1900
1901 // No entity with the given name exists. Return a bad entity
1902 return EntityBad;
1903 }
1904
1905 //----------------------------------------------------------------------
1906
1910 GAIA_NODISCARD const cnt::set<EntityLookupKey>* relations(Entity target) const {
1911 const auto it = m_targetsToRelations.find(EntityLookupKey(target));
1912 if (it == m_targetsToRelations.end())
1913 return nullptr;
1914
1915 return &it->second;
1916 }
1917
1923 GAIA_NODISCARD Entity relation(Entity entity, Entity target) const {
1924 GAIA_ASSERT(valid(entity));
1925 if (!valid(target))
1926 return EntityBad;
1927
1928 const auto& ec = fetch(entity);
1929 const auto* pArchetype = ec.pArchetype;
1930
1931 // Early exit if there are no pairs on the archetype
1932 if (pArchetype->pairs() == 0)
1933 return EntityBad;
1934
1935 auto ids = pArchetype->ids_view();
1936 for (auto e: ids) {
1937 if (!e.pair())
1938 continue;
1939 if (e.gen() != target.id())
1940 continue;
1941
1942 const auto& ecRel = m_recs.entities[e.id()];
1943 auto relation = ecRel.pChunk->entity_view()[ecRel.row];
1944 return relation;
1945 }
1946
1947 return EntityBad;
1948 }
1949
1955 template <typename Func>
1956 void relations(Entity entity, Entity target, Func func) const {
1957 GAIA_ASSERT(valid(entity));
1958 if (!valid(target))
1959 return;
1960
1961 const auto& ec = fetch(entity);
1962 const auto* pArchetype = ec.pArchetype;
1963
1964 // Early exit if there are no pairs on the archetype
1965 if (pArchetype->pairs() == 0)
1966 return;
1967
1968 auto ids = pArchetype->ids_view();
1969 for (auto e: ids) {
1970 if (!e.pair())
1971 continue;
1972 if (e.gen() != target.id())
1973 continue;
1974
1975 const auto& ecRel = m_recs.entities[e.id()];
1976 auto relation = ecRel.pChunk->entity_view()[ecRel.row];
1977 func(relation);
1978 }
1979 }
1980
1987 template <typename Func>
1988 void relations_if(Entity entity, Entity target, Func func) const {
1989 GAIA_ASSERT(valid(entity));
1990 if (!valid(target))
1991 return;
1992
1993 const auto& ec = fetch(entity);
1994 const auto* pArchetype = ec.pArchetype;
1995
1996 // Early exit if there are no pairs on the archetype
1997 if (pArchetype->pairs() == 0)
1998 return;
1999
2000 auto ids = pArchetype->ids_view();
2001 for (auto e: ids) {
2002 if (!e.pair())
2003 continue;
2004 if (e.gen() != target.id())
2005 continue;
2006
2007 const auto& ecRel = m_recs.entities[e.id()];
2008 auto relation = ecRel.pChunk->entity_view()[ecRel.row];
2009 if (!func(relation))
2010 return;
2011 }
2012 }
2013
2014 template <typename Func>
2015 void as_relations_trav(Entity target, Func func) const {
2016 GAIA_ASSERT(valid(target));
2017 if (!valid(target))
2018 return;
2019
2020 const auto it = m_entityToAsRelations.find(EntityLookupKey(target));
2021 if (it == m_entityToAsRelations.end())
2022 return;
2023
2024 const auto& set = it->second;
2025 for (auto relation: set) {
2026 func(relation.entity());
2027 as_relations_trav(relation.entity(), func);
2028 }
2029 }
2030
2031 template <typename Func>
2032 GAIA_NODISCARD bool as_relations_trav_if(Entity target, Func func) const {
2033 GAIA_ASSERT(valid(target));
2034 if (!valid(target))
2035 return false;
2036
2037 const auto it = m_entityToAsRelations.find(EntityLookupKey(target));
2038 if (it == m_entityToAsRelations.end())
2039 return false;
2040
2041 const auto& set = it->second;
2042 for (auto relation: set) {
2043 if (func(relation.entity()))
2044 return true;
2045 if (as_relations_trav_if(relation.entity(), func))
2046 return true;
2047 }
2048
2049 return false;
2050 }
2051
2052 //----------------------------------------------------------------------
2053
2057 GAIA_NODISCARD const cnt::set<EntityLookupKey>* targets(Entity relation) const {
2058 const auto it = m_relationsToTargets.find(EntityLookupKey(relation));
2059 if (it == m_relationsToTargets.end())
2060 return nullptr;
2061
2062 return &it->second;
2063 }
2064
2070 GAIA_NODISCARD Entity target(Entity entity, Entity relation) const {
2071 GAIA_ASSERT(valid(entity));
2072 if (!valid(relation))
2073 return EntityBad;
2074
2075 const auto& ec = fetch(entity);
2076 const auto* pArchetype = ec.pArchetype;
2077
2078 // Early exit if there are no pairs on the archetype
2079 if (pArchetype->pairs() == 0)
2080 return EntityBad;
2081
2082 auto ids = pArchetype->ids_view();
2083 for (auto e: ids) {
2084 if (!e.pair())
2085 continue;
2086 if (e.id() != relation.id())
2087 continue;
2088
2089 const auto& ecTarget = m_recs.entities[e.gen()];
2090 auto target = ecTarget.pChunk->entity_view()[ecTarget.row];
2091 return target;
2092 }
2093
2094 return EntityBad;
2095 }
2096
2102 template <typename Func>
2103 void targets(Entity entity, Entity relation, Func func) const {
2104 GAIA_ASSERT(valid(entity));
2105 if (!valid(relation))
2106 return;
2107
2108 const auto& ec = fetch(entity);
2109 const auto* pArchetype = ec.pArchetype;
2110
2111 // Early exit if there are no pairs on the archetype
2112 if (pArchetype->pairs() == 0)
2113 return;
2114
2115 auto ids = pArchetype->ids_view();
2116 for (auto e: ids) {
2117 if (!e.pair())
2118 continue;
2119 if (e.id() != relation.id())
2120 continue;
2121
2122 const auto& ecTarget = m_recs.entities[e.gen()];
2123 auto target = ecTarget.pChunk->entity_view()[ecTarget.row];
2124 func(target);
2125 }
2126 }
2127
2134 template <typename Func>
2135 void targets_if(Entity entity, Entity relation, Func func) const {
2136 GAIA_ASSERT(valid(entity));
2137 if (!valid(relation))
2138 return;
2139
2140 const auto& ec = fetch(entity);
2141 const auto* pArchetype = ec.pArchetype;
2142
2143 // Early exit if there are no pairs on the archetype
2144 if (pArchetype->pairs() == 0)
2145 return;
2146
2147 auto ids = pArchetype->ids_view();
2148 for (auto e: ids) {
2149 if (!e.pair())
2150 continue;
2151 if (e.id() != relation.id())
2152 continue;
2153
2154 const auto& ecTarget = m_recs.entities[e.gen()];
2155 auto target = ecTarget.pChunk->entity_view()[ecTarget.row];
2156 if (!func(target))
2157 return;
2158 }
2159 }
2160
2161 template <typename Func>
2162 void as_targets_trav(Entity relation, Func func) const {
2163 GAIA_ASSERT(valid(relation));
2164 if (!valid(relation))
2165 return;
2166
2167 const auto it = m_entityToAsTargets.find(EntityLookupKey(relation));
2168 if (it == m_entityToAsTargets.end())
2169 return;
2170
2171 const auto& set = it->second;
2172 for (auto target: set) {
2173 func(target.entity());
2174 as_targets_trav(target.entity(), func);
2175 }
2176 }
2177
2178 template <typename Func>
2179 bool as_targets_trav_if(Entity relation, Func func) const {
2180 GAIA_ASSERT(valid(relation));
2181 if (!valid(relation))
2182 return false;
2183
2184 const auto it = m_entityToAsTargets.find(EntityLookupKey(relation));
2185 if (it == m_entityToAsTargets.end())
2186 return false;
2187
2188 const auto& set = it->second;
2189 for (auto target: set) {
2190 if (func(target.entity()))
2191 return true;
2192 if (as_targets_trav(target.entity(), func))
2193 return true;
2194 }
2195
2196 return false;
2197 }
2198
2199 //----------------------------------------------------------------------
2200
2201 CommandBufferST& cmd_buffer_st() const {
2202 return *m_pCmdBufferST;
2203 }
2204
2205 CommandBufferMT& cmd_buffer_mt() const {
2206 return *m_pCmdBufferMT;
2207 }
2208
2209 //----------------------------------------------------------------------
2210
2211#if GAIA_SYSTEMS_ENABLED
2212
2214 void systems_init();
2215
2217 void systems_run();
2218
2221 SystemBuilder system();
2222
2223#endif
2224
2225 //----------------------------------------------------------------------
2226
2231 void enable(Entity entity, bool enable) {
2232 GAIA_ASSERT(valid(entity));
2233
2234 auto& ec = m_recs.entities[entity.id()];
2235 auto& archetype = *ec.pArchetype;
2236#if GAIA_ASSERT_ENABLED
2237 verify_enable(*this, archetype, entity);
2238#endif
2239 archetype.enable_entity(ec.pChunk, ec.row, enable, m_recs);
2240 }
2241
2246 GAIA_NODISCARD bool enabled(Entity entity) const {
2247 GAIA_ASSERT(valid(entity));
2248
2249 const auto& ec = m_recs.entities[entity.id()];
2250 const bool entityStateInContainer = !ec.data.dis;
2251#if GAIA_ASSERT_ENABLED
2252 const bool entityStateInChunk = ec.pChunk->enabled(ec.row);
2253 GAIA_ASSERT(entityStateInChunk == entityStateInContainer);
2254#endif
2255 return entityStateInContainer;
2256 }
2257
2258 //----------------------------------------------------------------------
2259
2263 GAIA_NODISCARD Chunk* get_chunk(Entity entity) const {
2264 GAIA_ASSERT(entity.id() < m_recs.entities.size());
2265 const auto& ec = m_recs.entities[entity.id()];
2266 return ec.pChunk;
2267 }
2268
2274 GAIA_NODISCARD Chunk* get_chunk(Entity entity, uint32_t& row) const {
2275 GAIA_ASSERT(entity.id() < m_recs.entities.size());
2276 const auto& ec = m_recs.entities[entity.id()];
2277 row = ec.row;
2278 return ec.pChunk;
2279 }
2280
2283 GAIA_NODISCARD uint32_t size() const {
2284 return m_recs.entities.item_count();
2285 }
2286
2289 GAIA_NODISCARD uint32_t& world_version() {
2290 return m_worldVersion;
2291 }
2292
2297 void set_max_lifespan(Entity entity, uint32_t lifespan = Archetype::MAX_ARCHETYPE_LIFESPAN) {
2298 if (!valid(entity))
2299 return;
2300
2301 auto& ec = fetch(entity);
2302 const auto prevLifespan = ec.pArchetype->max_lifespan();
2303 ec.pArchetype->set_max_lifespan(lifespan);
2304
2305 if (prevLifespan == 0) {
2306 // The archetype used to be immortal but not anymore
2307 try_enqueue_archetype_for_deletion(*ec.pArchetype);
2308 }
2309 }
2310
2311 //----------------------------------------------------------------------
2312
2315 void update() {
2316 systems_run();
2317
2318 // Finish deleting entities
2319 del_finalize();
2320
2321 // Run garbage collector
2322 gc();
2323
2324 util::log_flush();
2325
2326 // Signal the end of the frame
2327 GAIA_PROF_FRAME();
2328 }
2329
2331 void cleanup() {
2332 cleanup_inter();
2333
2334 // Reinit
2335 m_pRootArchetype = nullptr;
2336 m_pEntityArchetype = nullptr;
2337 m_pCompArchetype = nullptr;
2338 m_nextArchetypeId = 0;
2339 m_defragLastArchetypeIdx = 0;
2340 m_worldVersion = 0;
2341 init();
2342 }
2343
2346 void defrag_entities_per_tick(uint32_t value) {
2347 m_defragEntitiesPerTick = value;
2348 }
2349
2350 //--------------------------------------------------------------------------------
2351
2353 void diag_archetypes() const {
2354 GAIA_LOG_N("Archetypes:%u", (uint32_t)m_archetypes.size());
2355 for (auto* pArchetype: m_archetypes)
2356 Archetype::diag(*this, *pArchetype);
2357 }
2358
2361 void diag_components() const {
2362 comp_cache().diag();
2363 }
2364
2367 void diag_entities() const {
2368 validate_entities();
2369
2370 GAIA_LOG_N("Deleted entities: %u", (uint32_t)m_recs.entities.get_free_items());
2371 if (m_recs.entities.get_free_items() != 0U) {
2372 GAIA_LOG_N(" --> %u", (uint32_t)m_recs.entities.get_next_free_item());
2373
2374 uint32_t iters = 0;
2375 auto fe = m_recs.entities[m_recs.entities.get_next_free_item()].idx;
2376 while (fe != IdentifierIdBad) {
2377 GAIA_LOG_N(" --> %u", m_recs.entities[fe].idx);
2378 fe = m_recs.entities[fe].idx;
2379 ++iters;
2380 if (iters > m_recs.entities.get_free_items())
2381 break;
2382 }
2383
2384 if ((iters == 0U) || iters > m_recs.entities.get_free_items())
2385 GAIA_LOG_E(" Entities recycle list contains inconsistent data!");
2386 }
2387 }
2388
2390 void diag() const {
2391 diag_archetypes();
2392 diag_components();
2393 diag_entities();
2394 }
2395
2396 private:
2398 void cleanup_inter() {
2399 GAIA_PROF_SCOPE(World::cleanup_inter);
2400
2401 // Clear entities
2402 m_recs.entities = {};
2403 m_recs.pairs = {};
2404
2405 // Clear archetypes
2406 {
2407 // Delete all allocated chunks and their parent archetypes
2408 for (auto* pArchetype: m_archetypes)
2409 Archetype::destroy(pArchetype);
2410
2411 m_entityToAsRelations = {};
2412 m_entityToAsTargets = {};
2413 m_targetsToRelations = {};
2414 m_relationsToTargets = {};
2415
2416 m_archetypes = {};
2417 m_archetypesById = {};
2418 m_archetypesByHash = {};
2419
2420 m_reqArchetypesToDel = {};
2421 m_reqEntitiesToDel = {};
2422
2423 m_entitiesToDel = {};
2424 m_chunksToDel = {};
2425 m_archetypesToDel = {};
2426 }
2427
2428 // Clear caches
2429 {
2430 m_entityToArchetypeMap = {};
2431 m_queryCache.clear();
2432 }
2433
2434 // Clear entity names
2435 {
2436 for (auto& pair: m_nameToEntity) {
2437 if (!pair.first.owned())
2438 continue;
2439 // Release any memory allocated for owned names
2440 mem::mem_free((void*)pair.first.str());
2441 }
2442 m_nameToEntity = {};
2443 }
2444
2445 // Clear component cache
2446 m_compCache.clear();
2447 }
2448
2449 GAIA_NODISCARD static bool valid(const EntityContainer& ec, [[maybe_unused]] Entity entityExpected) {
2450 if (is_req_del(ec))
2451 return false;
2452
2453 // The entity in the chunk must match the index in the entity container
2454 auto* pChunk = ec.pChunk;
2455 if (pChunk == nullptr || ec.row >= pChunk->size())
2456 return false;
2457
2458#if GAIA_ASSERT_ENABLED
2459 const auto entityPresent = ec.pChunk->entity_view()[ec.row];
2460 GAIA_ASSERT(entityExpected == entityPresent);
2461 if (entityExpected != entityPresent)
2462 return false;
2463#endif
2464
2465 return true;
2466 }
2467
2470 GAIA_NODISCARD bool valid_pair(Entity entity) const {
2471 if (entity == EntityBad)
2472 return false;
2473
2474 GAIA_ASSERT(entity.pair());
2475 if (!entity.pair())
2476 return false;
2477
2478 // Ignore wildcards because they can't be attached to entities
2479 if (is_wildcard(entity))
2480 return true;
2481
2482 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
2483 if (it == m_recs.pairs.end())
2484 return false;
2485
2486 const auto& ec = it->second;
2487 return valid(ec, entity);
2488 }
2489
2492 GAIA_NODISCARD bool valid_entity(Entity entity) const {
2493 if (entity == EntityBad)
2494 return false;
2495
2496 GAIA_ASSERT(!entity.pair());
2497 if (entity.pair())
2498 return false;
2499
2500 // Entity ID has to fit inside the entity array
2501 if (entity.id() >= m_recs.entities.size())
2502 return false;
2503
2504 const auto& ec = m_recs.entities[entity.id()];
2505 return valid(ec, entity);
2506 }
2507
2511 GAIA_NODISCARD bool valid_entity_id(EntityId entityId) const {
2512 if (entityId == EntityBad.id())
2513 return false;
2514
2515 // Entity ID has to fit inside the entity array
2516 if (entityId >= m_recs.entities.size())
2517 return false;
2518
2519 const auto& ec = m_recs.entities[entityId];
2520 if (ec.data.pair != 0)
2521 return false;
2522
2523 return valid(
2524 ec, Entity(entityId, ec.data.gen, (bool)ec.data.ent, (bool)ec.data.pair, (EntityKind)ec.data.kind));
2525 }
2526
2530 void lock() {
2531 GAIA_ASSERT(m_structuralChangesLocked != (uint32_t)-1);
2532 ++m_structuralChangesLocked;
2533 }
2534
2538 void unlock() {
2539 GAIA_ASSERT(m_structuralChangesLocked > 0);
2540 --m_structuralChangesLocked;
2541 }
2542
2543 public:
2545 GAIA_NODISCARD bool locked() const {
2546 return m_structuralChangesLocked != 0;
2547 }
2548
2555 void save() {
2556 auto& s = *m_pSerializer;
2557
2558 s.reset();
2559
2560 // Version number, currently unused
2561 s.save((uint32_t)0);
2562
2563 // Store the index of the last core component.
2564 // TODO: As this changes, we will have to modify entity ids accordingly.
2565 const auto lastCoreComponentId = GAIA_ID(LastCoreComponent).id();
2566 s.save(lastCoreComponentId);
2567
2568 // Entities
2569 {
2570 auto saveEntityContainer = [&](const EntityContainer& ec) {
2571 s.save(ec.idx);
2572 s.save(ec.dataRaw);
2573 s.save(ec.row);
2574 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) == 0);
2575 s.save(ec.flags); // ignore Load
2576
2577#if GAIA_USE_SAFE_ENTITY
2578 s.save(ec.refCnt);
2579#else
2580 s.save((uint32_t)0);
2581#endif
2582
2583 uint32_t archetypeIdx = ec.pArchetype->list_idx();
2584 s.save(archetypeIdx);
2585 uint32_t chunkIdx = ec.pChunk->idx();
2586 s.save(chunkIdx);
2587 };
2588
2589 const auto recEntities = (uint32_t)m_recs.entities.size();
2590 const auto newEntities = recEntities - lastCoreComponentId;
2591 s.save(newEntities);
2592 GAIA_FOR2(lastCoreComponentId, recEntities) {
2593 const auto& ec = m_recs.entities[i];
2594 saveEntityContainer(ec);
2595 }
2596
2597 {
2598 uint32_t pairsCnt = 0;
2599 for (const auto& pair: m_recs.pairs) {
2600 // Skip core pairs
2601 if (pair.first.entity().id() < lastCoreComponentId && pair.first.entity().gen() < lastCoreComponentId)
2602 continue;
2603
2604 ++pairsCnt;
2605 }
2606 s.save(pairsCnt);
2607 }
2608 {
2609 for (const auto& pair: m_recs.pairs) {
2610 // Skip core pairs
2611 if (pair.first.entity().id() < lastCoreComponentId && pair.first.entity().gen() < lastCoreComponentId)
2612 continue;
2613
2614 saveEntityContainer(pair.second);
2615 }
2616 }
2617
2618 s.save(m_recs.entities.m_nextFreeIdx);
2619 s.save(m_recs.entities.m_freeItems);
2620 }
2621
2622 // World
2623 {
2624 s.save((uint32_t)m_archetypes.size());
2625 for (auto* pArchetype: m_archetypes) {
2626 s.save((uint32_t)pArchetype->ids_view().size());
2627 for (auto e: pArchetype->ids_view())
2628 s.save(e);
2629
2630 pArchetype->save(*m_pSerializer);
2631 }
2632
2633 s.save(m_worldVersion);
2634 }
2635
2636 // Entity names
2637 {
2638 s.save((uint32_t)m_nameToEntity.size());
2639 for (const auto& pair: m_nameToEntity) {
2640 s.save(pair.second);
2641 const bool isOwnedStr = pair.first.owned();
2642 s.save(isOwnedStr);
2643
2644 // For owner string we copy the entire string into the buffer
2645 if (isOwnedStr) {
2646 const auto* str = pair.first.str();
2647 const uint32_t len = pair.first.len();
2648 s.save(len);
2649 s.save_raw(str, len, ser::serialization_type_id::c8);
2650 }
2651 // Non-owned strings will only store the pointer.
2652 // However, if it is a component, we do not store anything at all because we can reconstruct
2653 // the name from our component cache.
2654 else if (!pair.second.comp()) {
2655 const auto* str = pair.first.str();
2656 const uint32_t len = pair.first.len();
2657 s.save(len);
2658 const auto ptr_val = (uint64_t)str;
2659 s.save_raw(&ptr_val, sizeof(ptr_val), ser::serialization_type_id::u64);
2660 }
2661 }
2662 }
2663 }
2664
2671 bool load(ser::ISerializer* pOutputSerializer = nullptr) {
2672 auto& s = (pOutputSerializer == nullptr) ? m_binarySerializer : *pOutputSerializer;
2673
2674 // Move back to the beginning of the stream
2675 s.seek(0);
2676
2677 // Version number, currently unused
2678 uint32_t version = 0;
2679 s.load(version);
2680 if (version != 0) {
2681 GAIA_LOG_E("Unsupported world version %u. Expected 0.", version);
2682 return false;
2683 }
2684
2685 // Store the index of the last core component. As they change, we will have to modify entity ids accordingly.
2686 uint32_t lastCoreComponentId = 0;
2687 s.load(lastCoreComponentId);
2688
2689 // Entities
2690 {
2691 auto loadEntityContainer = [&](EntityContainer& ec) {
2692 s.load(ec.idx);
2693 s.load(ec.dataRaw);
2694 s.load(ec.row);
2695 s.load(ec.flags);
2696 ec.flags |= EntityContainerFlags::Load;
2697
2698#if GAIA_USE_SAFE_ENTITY
2699 s.load(ec.refCnt);
2700#else
2701 s.load(ec.unused);
2702 // if this value is different from zero, it means we are trying to load data
2703 // that was previously saved with GAIA_USE_SAFE_ENTITY. It's probably not a good idea
2704 // because if your program used reference counting it probably won't work correctly.
2705 GAIA_ASSERT(ec.unused == 0);
2706#endif
2707
2708 // Store the archetype idx inside the pointer. We will decode this once archetypes are created.
2709 uint32_t archetypeIdx = 0;
2710 s.load(archetypeIdx);
2711 ec.pArchetype = (Archetype*)((uintptr_t)archetypeIdx);
2712 // Store the chunk idx inside the pointer. We will decode this once chunks are created.
2713 uint32_t chunkIdx = 0;
2714 s.load(chunkIdx);
2715 ec.pChunk = (Chunk*)((uintptr_t)chunkIdx);
2716 };
2717
2718 uint32_t newEntities = 0;
2719 s.load(newEntities);
2720 GAIA_FOR(newEntities) {
2721 EntityContainer ec;
2722 loadEntityContainer(ec);
2723
2724 m_recs.entities.m_items.add_item(GAIA_MOV(ec));
2725 }
2726
2727 uint32_t pairsCnt = 0;
2728 s.load(pairsCnt);
2729 GAIA_FOR(pairsCnt) {
2730 EntityContainer ec{};
2731 loadEntityContainer(ec);
2732 Entity pair(ec.idx, ec.data.gen);
2733 m_recs.pairs.emplace(EntityLookupKey(pair), GAIA_MOV(ec));
2734 }
2735
2736 s.load(m_recs.entities.m_nextFreeIdx);
2737 s.load(m_recs.entities.m_freeItems);
2738 }
2739
2740 // World
2741 {
2742 uint32_t archetypesSize = 0;
2743 s.load(archetypesSize);
2744 m_archetypes.reserve(archetypesSize);
2745 GAIA_FOR(archetypesSize) {
2746 uint32_t idsSize = 0;
2747 s.load(idsSize);
2748 Entity ids[ChunkHeader::MAX_COMPONENTS];
2749 GAIA_FOR_(idsSize, j) {
2750 s.load(ids[j]);
2751 }
2752
2753 // Calculate the lookup hash
2754 const auto hashLookup = calc_lookup_hash({&ids[0], idsSize}).hash;
2755
2756 auto* pArchetype = find_archetype({hashLookup}, {&ids[0], idsSize});
2757 if (pArchetype == nullptr) {
2758 // Create the archetype
2759 pArchetype = create_archetype({&ids[0], idsSize});
2760 pArchetype->set_hashes({hashLookup});
2761
2762 // No need to do anything with the archetype graph. It will build itself naturally.
2763 // pArchetype->build_graph_edges(pArchetypeRight, entity);
2764
2765 // Register the archetype in the world
2766 reg_archetype(pArchetype);
2767 }
2768
2769 // Load archetype data
2770 pArchetype->load(s);
2771 }
2772
2773 s.load(m_worldVersion);
2774 }
2775
2776 // Update entity records.
2777 // We previously encoded the archetype id into refCnt.
2778 // Now we need to convert it back to the pointer.
2779 {
2780 for (auto& ec: m_recs.entities) {
2781 if ((ec.flags & EntityContainerFlags::Load) == 0)
2782 continue;
2783 ec.flags &= ~EntityContainerFlags::Load; // Clear the load flag
2784
2785 const auto archetypeIdx = (ArchetypeId)((uintptr_t)ec.pArchetype); // Decode the archetype idx
2786 ec.pArchetype = m_archetypes[archetypeIdx];
2787 const uint32_t chunkIdx = (uint32_t)((uintptr_t)ec.pChunk); // Decode the chunk idx
2788 ec.pChunk = ec.pArchetype->chunks()[chunkIdx];
2789 }
2790 for (auto& pair: m_recs.pairs) {
2791 auto& ec = pair.second;
2792
2793 if ((ec.flags & EntityContainerFlags::Load) == 0)
2794 continue;
2795 ec.flags &= ~EntityContainerFlags::Load; // Clear the load flag
2796
2797 const auto archetypeIdx = (ArchetypeId)((uintptr_t)ec.pArchetype); // Decode the archetype idx
2798 ec.pArchetype = m_archetypes[archetypeIdx];
2799 const uint32_t chunkIdx = (uint32_t)((uintptr_t)ec.pChunk); // Decode the chunk idx
2800 ec.pChunk = ec.pArchetype->chunks()[chunkIdx];
2801 }
2802 }
2803
2804 // Entity names
2805 {
2806 uint32_t cnt = 0;
2807 s.load(cnt);
2808 GAIA_FOR(cnt) {
2809 Entity entity;
2810 s.load(entity);
2811 // entity.data.gen = 0; // Reset generation to zero
2812
2813 const auto& ec = fetch(entity);
2814 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
2815 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
2816 GAIA_ASSERT(core::check_alignment(pDesc));
2817
2818 bool isOwned = false;
2819 s.load(isOwned);
2820 if (!isOwned) {
2821 if (entity.comp()) {
2822 // Make components point back to their component cache record because if we save the world and load
2823 // it back in runtime, EntityDesc would still point to the old pointers to component names.
2824 const auto& ci = comp_cache().get(entity);
2825 pDesc->name = ci.name.str();
2826 // Length should still be the same. Only the pointer has changed.
2827 GAIA_ASSERT(pDesc->len == ci.name.len());
2828 m_nameToEntity.try_emplace(EntityNameLookupKey(pDesc->name, pDesc->len, 0), entity);
2829 } else {
2830 uint32_t len = 0;
2831 s.load(len);
2832 uint64_t ptr_val = 0;
2833 s.load_raw(&ptr_val, sizeof(ptr_val), ser::serialization_type_id::u64);
2834
2835 // Simply point to whereever the original pointer pointed to
2836 pDesc->name = (const char*)ptr_val;
2837 pDesc->len = len;
2838 m_nameToEntity.try_emplace(EntityNameLookupKey(pDesc->name, pDesc->len, 0), entity);
2839 }
2840
2841 continue;
2842 }
2843
2844 uint32_t len = 0;
2845 s.load(len);
2846
2847 // Get a pointer to where the string begins and seek to the end of the string
2848 const char* entityStr = (const char*)(s.data() + s.tell());
2849 s.seek(s.tell() + len);
2850
2851 // Make sure EntityDesc does not point anywhere right now.
2852 {
2853 pDesc->name = nullptr;
2854 pDesc->len = 0;
2855 }
2856
2857 // Name the entity using an owned string
2858 name(entity, entityStr, len);
2859 }
2860 }
2861
2862 return false;
2863 }
2864
2865 private:
2867 void sort_archetypes() {
2868 struct sort_cond {
2869 bool operator()(const Archetype* a, const Archetype* b) const {
2870 return a->id() < b->id();
2871 }
2872 };
2873
2874 core::sort(m_archetypes, sort_cond{}, [&](uint32_t left, uint32_t right) {
2875 Archetype* tmp = m_archetypes[left];
2876
2877 m_archetypes[right]->list_idx(left);
2878 m_archetypes[left]->list_idx(right);
2879
2880 m_archetypes.data()[left] = (Archetype*)m_archetypes[right];
2881 m_archetypes.data()[right] = tmp;
2882 });
2883 }
2884
2888 void remove_chunk(Archetype& archetype, Chunk& chunk) {
2889 archetype.del(&chunk);
2890 try_enqueue_archetype_for_deletion(archetype);
2891 }
2892
2897 void remove_entity(Archetype& archetype, Chunk& chunk, uint16_t row) {
2898 archetype.remove_entity(chunk, row, m_recs);
2899 try_enqueue_chunk_for_deletion(archetype, chunk);
2900 }
2901
2903 void del_empty_chunks() {
2904 GAIA_PROF_SCOPE(World::del_empty_chunks);
2905
2906 for (uint32_t i = 0; i < m_chunksToDel.size();) {
2907 auto* pArchetype = m_chunksToDel[i].pArchetype;
2908 auto* pChunk = m_chunksToDel[i].pChunk;
2909
2910 // Revive reclaimed chunks
2911 if (!pChunk->empty()) {
2912 pChunk->revive();
2913 revive_archetype(*pArchetype);
2914 core::swap_erase(m_chunksToDel, i);
2915 continue;
2916 }
2917
2918 // Skip chunks which still have some lifespan left
2919 if (pChunk->progress_death()) {
2920 ++i;
2921 continue;
2922 }
2923
2924 // Delete unused chunks that are past their lifespan
2925 remove_chunk(*pArchetype, *pChunk);
2926 core::swap_erase(m_chunksToDel, i);
2927 }
2928 }
2929
2931 void del_empty_archetype(Archetype* pArchetype) {
2932 GAIA_PROF_SCOPE(World::del_empty_archetype);
2933
2934 GAIA_ASSERT(pArchetype != nullptr);
2935 GAIA_ASSERT(pArchetype->empty() || pArchetype->is_req_del());
2936 GAIA_ASSERT(!pArchetype->dying() || pArchetype->is_req_del());
2937
2938 unreg_archetype(pArchetype);
2939 Archetype::destroy(pArchetype);
2940 }
2941
2943 void del_empty_archetypes() {
2944 GAIA_PROF_SCOPE(World::del_empty_archetypes);
2945
2946 cnt::sarray_ext<Archetype*, 512> tmp;
2947
2948 // Remove all dead archetypes from query caches.
2949 // Because the number of cached queries is way higher than the number of archetypes
2950 // we want to remove, we flip the logic around and iterate over all query caches
2951 // and match against our lists.
2952 // Note, all archetype pointers in the tmp array are invalid at this point and can
2953 // be used only for comparison. They can't be dereferenced.
2954 auto remove_from_queries = [&]() {
2955 if (tmp.empty())
2956 return;
2957
2958 // TODO: How to speed this up? If there are 1k cached queries is it still going to
2959 // be fast enough or do we get spikes? Probably a linked list for query cache
2960 // would be a way to go.
2961 for (auto& info: m_queryCache) {
2962 for (auto* pArchetype: tmp)
2963 info.remove(pArchetype);
2964 }
2965
2966 for (auto* pArchetype: tmp)
2967 del_empty_archetype(pArchetype);
2968 tmp.clear();
2969 };
2970
2971 for (uint32_t i = 0; i < m_archetypesToDel.size();) {
2972 auto* pArchetype = m_archetypesToDel[i];
2973
2974 // Skip reclaimed archetypes or archetypes that became immortal
2975 if (!pArchetype->empty() || pArchetype->max_lifespan() == 0) {
2976 revive_archetype(*pArchetype);
2977 core::swap_erase(m_archetypesToDel, i);
2978 continue;
2979 }
2980
2981 // Skip archetypes which still have some lifespan left unless
2982 // they are force-deleted.
2983 if (!pArchetype->is_req_del() && pArchetype->progress_death()) {
2984 ++i;
2985 continue;
2986 }
2987
2988 tmp.push_back(pArchetype);
2989
2990 // Remove the unused archetypes
2991 core::swap_erase(m_archetypesToDel, i);
2992
2993 // Clear what we have once the capacity is reached
2994 if (tmp.size() == tmp.max_size())
2995 remove_from_queries();
2996 }
2997
2998 remove_from_queries();
2999 }
3000
3001 void revive_archetype(Archetype& archetype) {
3002 archetype.revive();
3003 m_reqArchetypesToDel.erase(ArchetypeLookupKey(archetype.lookup_hash(), &archetype));
3004 }
3005
3006 void try_enqueue_chunk_for_deletion(Archetype& archetype, Chunk& chunk) {
3007 if (chunk.dying() || !chunk.empty())
3008 return;
3009
3010 // When the chunk is emptied we want it to be removed. We can't do it
3011 // rowB away and need to wait for world::gc() to be called.
3012 //
3013 // However, we need to prevent the following:
3014 // 1) chunk is emptied, add it to some removal list
3015 // 2) chunk is reclaimed
3016 // 3) chunk is emptied, add it to some removal list again
3017 //
3018 // Therefore, we have a flag telling us the chunk is already waiting to
3019 // be removed. The chunk might be reclaimed before garbage collection happens
3020 // but it simply ignores such requests. This way we always have at most one
3021 // record for removal for any given chunk.
3022 chunk.start_dying();
3023
3024 m_chunksToDel.push_back({&archetype, &chunk});
3025 }
3026
3027 void try_enqueue_archetype_for_deletion(Archetype& archetype) {
3028 if (!archetype.ready_to_die())
3029 return;
3030
3031 // When the chunk is emptied we want it to be removed. We can't do it
3032 // rowB away and need to wait for world::gc() to be called.
3033 //
3034 // However, we need to prevent the following:
3035 // 1) archetype is emptied, add it to some removal list
3036 // 2) archetype is reclaimed
3037 // 3) archetype is emptied, add it to some removal list again
3038 //
3039 // Therefore, we have a flag telling us the chunk is already waiting to
3040 // be removed. The archetype might be reclaimed before garbage collection happens
3041 // but it simply ignores such requests. This way we always have at most one
3042 // record for removal for any given chunk.
3043 archetype.start_dying();
3044
3045 m_archetypesToDel.push_back(&archetype);
3046 }
3047
3050 void defrag_chunks(uint32_t maxEntities) {
3051 GAIA_PROF_SCOPE(World::defrag_chunks);
3052
3053 const auto maxIters = m_archetypes.size();
3054 // There has to be at least the root archetype present
3055 GAIA_ASSERT(maxIters > 0);
3056
3057 GAIA_FOR(maxIters) {
3058 const auto idx = (m_defragLastArchetypeIdx + 1) % maxIters;
3059 auto* pArchetype = m_archetypes[idx];
3060 defrag_archetype(*pArchetype, maxEntities);
3061 if (maxEntities == 0)
3062 return;
3063
3064 m_defragLastArchetypeIdx = idx;
3065 }
3066 }
3067
3072 void defrag_archetype(Archetype& archetype, uint32_t& maxEntities) {
3073 // Assuming the following chunk layout:
3074 // Chunk_1: 10/10
3075 // Chunk_2: 1/10
3076 // Chunk_3: 7/10
3077 // Chunk_4: 10/10
3078 // Chunk_5: 9/10
3079 // After full defragmentation we end up with:
3080 // Chunk_1: 10/10
3081 // Chunk_2: 10/10 (7 entities from Chunk_3 + 2 entities from Chunk_5)
3082 // Chunk_3: 0/10 (empty, ready for removal)
3083 // Chunk_4: 10/10
3084 // Chunk_5: 7/10
3085 // TODO: Implement mask of semi-full chunks so we can pick one easily when searching
3086 // for a chunk to fill with a new entity and when defragmenting.
3087 // NOTE 1:
3088 // Even though entity movement might be present during defragmentation, we do
3089 // not update the world version here because no real structural changes happen.
3090 // All entities and components remain intact, they just move to a different place.
3091 // NOTE 2:
3092 // Entities belonging to chunks with uni components are locked to their chunk.
3093 // Therefore, we won't defragment them unless their uni components contain matching
3094 // values.
3095
3096 if (maxEntities == 0)
3097 return;
3098
3099 const auto& chunks = archetype.chunks();
3100 if (chunks.size() < 2)
3101 return;
3102
3103 uint32_t front = 0;
3104 uint32_t back = chunks.size() - 1;
3105
3106 auto* pDstChunk = chunks[front];
3107 auto* pSrcChunk = chunks[back];
3108
3109 // Find the first semi-full chunk in the front
3110 while (front < back && (pDstChunk->full() || !pDstChunk->is_semi()))
3111 pDstChunk = chunks[++front];
3112 // Find the last semi-full chunk in the back
3113 while (front < back && (pSrcChunk->empty() || !pSrcChunk->is_semi()))
3114 pSrcChunk = chunks[--back];
3115
3116 const auto& props = archetype.props();
3117 const bool hasUniEnts =
3118 props.cntEntities > 0 && archetype.ids_view()[props.cntEntities - 1].kind() == EntityKind::EK_Uni;
3119
3120 // Find the first semi-empty chunk in the back
3121 while (front < back) {
3122 pDstChunk = chunks[front];
3123 pSrcChunk = chunks[back];
3124
3125 const uint32_t entitiesInSrcChunk = pSrcChunk->size();
3126 const uint32_t spaceInDstChunk = pDstChunk->capacity() - pDstChunk->size();
3127 const uint32_t entitiesToMoveSrc = core::get_min(entitiesInSrcChunk, maxEntities);
3128 const uint32_t entitiesToMove = core::get_min(entitiesToMoveSrc, spaceInDstChunk);
3129
3130 // Make sure uni components have matching values
3131 if (hasUniEnts) {
3132 auto rec = pSrcChunk->comp_rec_view();
3133 bool res = true;
3134 GAIA_FOR2(props.genEntities, props.cntEntities) {
3135 const auto* pSrcVal = (const void*)pSrcChunk->comp_ptr(i, 0);
3136 const auto* pDstVal = (const void*)pDstChunk->comp_ptr(i, 0);
3137 if (rec[i].pItem->cmp(pSrcVal, pDstVal)) {
3138 res = false;
3139 break;
3140 }
3141 }
3142
3143 // When there is not a match we move to the next chunk
3144 if (!res) {
3145 pDstChunk = chunks[++front];
3146 goto next_iteration;
3147 }
3148 }
3149
3150 GAIA_FOR(entitiesToMove) {
3151 const auto lastSrcEntityIdx = entitiesInSrcChunk - i - 1;
3152 const auto entity = pSrcChunk->entity_view()[lastSrcEntityIdx];
3153
3154 auto& ec = m_recs[entity];
3155
3156 const auto srcRow = ec.row;
3157 const auto dstRow = pDstChunk->add_entity(entity);
3158 const bool wasEnabled = !ec.data.dis;
3159
3160 // Make sure the old entity becomes enabled now
3161 archetype.enable_entity(pSrcChunk, srcRow, true, m_recs);
3162 // We go back-to-front in the chunk so enabling the entity is not expected to change its row
3163 GAIA_ASSERT(srcRow == ec.row);
3164
3165 // Move data from the old chunk to the new one
3166 pDstChunk->move_entity_data(entity, dstRow, m_recs);
3167
3168 // Remove the entity record from the old chunk.
3169 // Normally we'd call remove_entity but we don't want to trigger world
3170 // version updated all the time. It's enough to do it just once at the
3171 // end of defragmentation.
3172 // remove_entity(archetype, *pSrcChunk, srcRow);
3173 archetype.remove_entity_raw(*pSrcChunk, srcRow, m_recs);
3174 try_enqueue_chunk_for_deletion(archetype, *pSrcChunk);
3175
3176 // Bring the entity container record up-to-date
3177 ec.pChunk = pDstChunk;
3178 ec.row = (uint16_t)dstRow;
3179
3180 // Transfer the original enabled state to the new chunk
3181 archetype.enable_entity(pDstChunk, dstRow, wasEnabled, m_recs);
3182 }
3183
3184 // Update world versions
3185 if (entitiesToMove > 0) {
3186 pSrcChunk->update_world_version();
3187 pDstChunk->update_world_version();
3188 update_version(m_worldVersion);
3189 }
3190
3191 maxEntities -= entitiesToMove;
3192 if (maxEntities == 0)
3193 return;
3194
3195 // The source is empty, find another semi-empty source
3196 if (pSrcChunk->empty()) {
3197 while (front < back) {
3198 if (chunks[--back]->is_semi())
3199 break;
3200 }
3201 }
3202
3203 next_iteration:
3204 // The destination chunk is full, we need to move to the next one.
3205 // The idea is to fill the destination as much as possible.
3206 while (front < back && pDstChunk->full())
3207 pDstChunk = chunks[++front];
3208 }
3209 }
3210
3215 GAIA_NODISCARD Archetype* find_archetype(Archetype::LookupHash hashLookup, EntitySpan ids) {
3216 auto tmpArchetype = ArchetypeLookupChecker(ids);
3217 ArchetypeLookupKey key(hashLookup, &tmpArchetype);
3218
3219 // Search for the archetype in the map
3220 const auto it = m_archetypesByHash.find(key);
3221 if (it == m_archetypesByHash.end())
3222 return nullptr;
3223
3224 auto* pArchetype = it->second;
3225 return pArchetype;
3226 }
3227
3231 void add_entity_archetype_pair(Entity entity, Archetype* pArchetype) {
3232 GAIA_ASSERT(entity != Pair(All, All));
3233
3234 EntityLookupKey entityKey(entity);
3235 const auto it = m_entityToArchetypeMap.find(entityKey);
3236 if (it == m_entityToArchetypeMap.end()) {
3237 m_entityToArchetypeMap.try_emplace(entityKey, ArchetypeDArray{pArchetype});
3238 return;
3239 }
3240
3241 auto& archetypes = it->second;
3242 if (!core::has(archetypes, pArchetype))
3243 archetypes.push_back(pArchetype);
3244 }
3245
3249 void del_entity_archetype_pair(Pair pair, Entity entityToRemove) {
3250 GAIA_ASSERT(pair != Pair(All, All));
3251
3252 auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
3253 auto& archetypes = it->second;
3254
3255 // Remove any reference to the found archetype from the array.
3256 // We don't know the archetype so we remove any archetype that contains our entity.
3257 for (uint32_t i = archetypes.size() - 1; i != (uint32_t)-1; --i) {
3258 const auto* pArchetype = archetypes[i];
3259 if (!pArchetype->has(entityToRemove))
3260 continue;
3261
3262 core::swap_erase_unsafe(archetypes, i);
3263 }
3264
3265 // NOTE: No need to delete keys with empty archetype arrays.
3266 // There are only 2 such keys: (*, tgt), (src, *).
3267 // If no more items are present in the array, remove the map key.
3268 // if (archetypes.empty())
3269 // m_entityToArchetypeMap.erase(it); DON'T
3270 }
3271
3274 void del_entity_archetype_pairs(Entity entity) {
3275 // TODO: Optimize. Either switch to an array or add an index to the map value.
3276 // Otherwise all these lookups make deleting entities slow.
3277
3278 GAIA_ASSERT(entity != Pair(All, All));
3279
3280 m_entityToArchetypeMap.erase(EntityLookupKey(entity));
3281
3282 if (entity.pair()) {
3283 const auto first = get(entity.id());
3284 const auto second = get(entity.gen());
3285
3286 // (*, tgt)
3287 del_entity_archetype_pair(Pair(All, second), entity);
3288 // (src, *)
3289 del_entity_archetype_pair(Pair(first, All), entity);
3290 }
3291 }
3292
3296 GAIA_NODISCARD Archetype* create_archetype(EntitySpan entities) {
3297 GAIA_ASSERT(m_nextArchetypeId < (decltype(m_nextArchetypeId))-1);
3298 auto* pArchetype = Archetype::create(*this, m_nextArchetypeId++, m_worldVersion, entities);
3299
3300 for (auto entity: entities) {
3301 add_entity_archetype_pair(entity, pArchetype);
3302
3303 // If the entity is a pair, make sure to create special wildcard records for it
3304 // as well so wildcard queries can find the archetype.
3305 if (entity.pair()) {
3306 const auto first = get(entity.id());
3307 const auto second = get(entity.gen());
3308
3309 // (*, tgt)
3310 add_entity_archetype_pair(Pair(All, second), pArchetype);
3311 // (src, *)
3312 add_entity_archetype_pair(Pair(first, All), pArchetype);
3313 }
3314 }
3315
3316 return pArchetype;
3317 }
3318
3321 void reg_archetype(Archetype* pArchetype) {
3322 GAIA_ASSERT(pArchetype != nullptr);
3323
3324 // // Make sure hashes were set already
3325 // GAIA_ASSERT(
3326 // (m_archetypesById.empty() || pArchetype == m_pRootArchetype) || (pArchetype->lookup_hash().hash != 0));
3327
3328 // Make sure the archetype is not registered yet
3329 GAIA_ASSERT(pArchetype->list_idx() == BadIndex);
3330
3331 // Register the archetype
3332 [[maybe_unused]] const auto it0 =
3333 m_archetypesById.emplace(ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash()), pArchetype);
3334 [[maybe_unused]] const auto it1 =
3335 m_archetypesByHash.emplace(ArchetypeLookupKey(pArchetype->lookup_hash(), pArchetype), pArchetype);
3336
3337 GAIA_ASSERT(it0.second);
3338 GAIA_ASSERT(it1.second);
3339
3340 pArchetype->list_idx(m_archetypes.size());
3341 m_archetypes.emplace_back(pArchetype);
3342 }
3343
3346 void unreg_archetype(Archetype* pArchetype) {
3347 GAIA_ASSERT(pArchetype != nullptr);
3348
3349 // Make sure hashes were set already
3350 GAIA_ASSERT(
3351 (m_archetypesById.empty() || pArchetype == m_pRootArchetype) || (pArchetype->lookup_hash().hash != 0));
3352
3353 // Make sure the archetype was registered already
3354 GAIA_ASSERT(pArchetype->list_idx() != BadIndex);
3355
3356 // Break graph connections
3357 {
3358 auto& edgeLefts = pArchetype->left_edges();
3359 for (auto& itLeft: edgeLefts)
3360 remove_edge_from_archetype(pArchetype, itLeft.second, itLeft.first.entity());
3361 }
3362
3363 auto tmpArchetype = ArchetypeLookupChecker(pArchetype->ids_view());
3364 [[maybe_unused]] const auto res0 =
3365 m_archetypesById.erase(ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash()));
3366 [[maybe_unused]] const auto res1 =
3367 m_archetypesByHash.erase(ArchetypeLookupKey(pArchetype->lookup_hash(), &tmpArchetype));
3368 GAIA_ASSERT(res0 != 0);
3369 GAIA_ASSERT(res1 != 0);
3370
3371 const auto idx = pArchetype->list_idx();
3372 GAIA_ASSERT(idx == core::get_index(m_archetypes, pArchetype));
3373 core::swap_erase(m_archetypes, idx);
3374 if (!m_archetypes.empty() && idx != m_archetypes.size())
3375 m_archetypes[idx]->list_idx(idx);
3376 }
3377
3378#if GAIA_ASSERT_ENABLED
3379 static void print_archetype_entities(const World& world, const Archetype& archetype, Entity entity, bool adding) {
3380 auto ids = archetype.ids_view();
3381
3382 GAIA_LOG_W("Currently present:");
3383 GAIA_EACH(ids) {
3384 GAIA_LOG_W("> [%u] %s [%s]", i, entity_name(world, ids[i]), EntityKindString[(uint32_t)ids[i].kind()]);
3385 }
3386
3387 GAIA_LOG_W("Trying to %s:", adding ? "add" : "del");
3388 GAIA_LOG_W("> %s [%s]", entity_name(world, entity), EntityKindString[(uint32_t)entity.kind()]);
3389 }
3390
3391 static void verify_add(const World& world, Archetype& archetype, Entity entity, Entity addEntity) {
3392 // Make sure the world is not locked
3393 if (world.locked()) {
3394 GAIA_ASSERT2(false, "Trying to add an entity while the world is locked");
3395 GAIA_LOG_W("Trying to add an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
3396 print_archetype_entities(world, archetype, entity, false);
3397 return;
3398 }
3399
3400 // Makes sure no wildcard entities are added
3401 if (is_wildcard(addEntity)) {
3402 GAIA_ASSERT2(false, "Adding wildcard pairs is not supported");
3403 print_archetype_entities(world, archetype, addEntity, true);
3404 return;
3405 }
3406
3407 // Make sure not to add too many entities/components
3408 auto ids = archetype.ids_view();
3409 if GAIA_UNLIKELY (ids.size() + 1 >= ChunkHeader::MAX_COMPONENTS) {
3410 GAIA_ASSERT2(false, "Trying to add too many entities to entity!");
3411 GAIA_LOG_W("Trying to add an entity to entity [%u:%u] but there's no space left!", entity.id(), entity.gen());
3412 print_archetype_entities(world, archetype, addEntity, true);
3413 return;
3414 }
3415 }
3416
3417 static void verify_del(const World& world, Archetype& archetype, Entity entity, Entity delEntity) {
3418 // Make sure the world is not locked
3419 if (world.locked()) {
3420 GAIA_ASSERT2(false, "Trying to delete an entity while the world is locked");
3421 GAIA_LOG_W("Trying to delete an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
3422 print_archetype_entities(world, archetype, entity, false);
3423 return;
3424 }
3425
3426 // Make sure the entity is present on the archetype
3427 if GAIA_UNLIKELY (!archetype.has(delEntity)) {
3428 GAIA_ASSERT2(false, "Trying to remove an entity which wasn't added");
3429 GAIA_LOG_W("Trying to del an entity from entity [%u:%u] but it was never added", entity.id(), entity.gen());
3430 print_archetype_entities(world, archetype, delEntity, false);
3431 return;
3432 }
3433 }
3434
3435 static void verify_enable(const World& world, Archetype& archetype, Entity entity) {
3436 if (world.locked()) {
3437 GAIA_ASSERT2(false, "Trying to enable/disable an entity while the world is locked");
3438 GAIA_LOG_W("Trying to enable/disable an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
3439 print_archetype_entities(world, archetype, entity, false);
3440 }
3441 }
3442
3443 static void verify_move(const World& world, Archetype& archetype, Entity entity) {
3444 if (world.locked()) {
3445 GAIA_ASSERT2(false, "Trying to move an entity while the world is locked");
3446 GAIA_LOG_W("Trying to move an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
3447 print_archetype_entities(world, archetype, entity, false);
3448 }
3449 }
3450#endif
3451
3457 GAIA_NODISCARD Archetype* foc_archetype_add(Archetype* pArchetypeLeft, Entity entity) {
3458 // Check if the component is found when following the "add" edges
3459 {
3460 const auto edge = pArchetypeLeft->find_edge_right(entity);
3461 if (edge != ArchetypeIdHashPairBad) {
3462 auto it = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
3463 // The edge must exist at this point
3464 GAIA_ASSERT(it != m_archetypesById.end());
3465
3466 auto* pArchetypeRight = it->second;
3467 GAIA_ASSERT(pArchetypeRight != nullptr);
3468 return pArchetypeRight;
3469 }
3470 }
3471
3472 // Prepare a joint array of components of old + the newly added component
3473 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
3474 {
3475 auto entsOld = pArchetypeLeft->ids_view();
3476 const auto entsOldCnt = entsOld.size();
3477 entsNew.resize((uint32_t)entsOld.size() + 1);
3478 GAIA_FOR(entsOldCnt) entsNew[i] = entsOld[i];
3479 entsNew[(uint32_t)entsOld.size()] = entity;
3480 }
3481
3482 // Make sure to sort the components so we receive the same hash no matter the order in which components
3483 // are provided Bubble sort is okay. We're dealing with at most ChunkHeader::MAX_COMPONENTS items.
3484 sort(entsNew, SortComponentCond{});
3485
3486 // Once sorted we can calculate the hashes
3487 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
3488 auto* pArchetypeRight = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
3489 if (pArchetypeRight == nullptr) {
3490 pArchetypeRight = create_archetype({entsNew.data(), entsNew.size()});
3491 pArchetypeRight->set_hashes({hashLookup});
3492 pArchetypeLeft->build_graph_edges(pArchetypeRight, entity);
3493 reg_archetype(pArchetypeRight);
3494 }
3495
3496 return pArchetypeRight;
3497 }
3498
3504 GAIA_NODISCARD Archetype* foc_archetype_del(Archetype* pArchetypeRight, Entity entity) {
3505 // Check if the component is found when following the "del" edges
3506 {
3507 const auto edge = pArchetypeRight->find_edge_left(entity);
3508 if (edge != ArchetypeIdHashPairBad)
3509 return m_archetypesById[edge];
3510 }
3511
3512 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
3513 auto entsOld = pArchetypeRight->ids_view();
3514
3515 // Find the intersection
3516 for (const auto e: entsOld) {
3517 if (e == entity)
3518 continue;
3519
3520 entsNew.push_back(e);
3521 }
3522
3523 // Verify there was a change
3524 GAIA_ASSERT(entsNew.size() != entsOld.size());
3525
3526 // Calculate the hashes
3527 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
3528 auto* pArchetype = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
3529 if (pArchetype == nullptr) {
3530 pArchetype = create_archetype({entsNew.data(), entsNew.size()});
3531 pArchetype->set_hashes({hashLookup});
3532 pArchetype->build_graph_edges(pArchetypeRight, entity);
3533 reg_archetype(pArchetype);
3534 }
3535
3536 return pArchetype;
3537 }
3538
3541 GAIA_NODISCARD const auto& archetypes() const {
3542 return m_archetypesById;
3543 }
3544
3548 GAIA_NODISCARD Archetype& archetype(Entity entity) {
3549 const auto& ec = fetch(entity);
3550 return *ec.pArchetype;
3551 }
3552
3555 void del_name(EntityContainer& ec, Entity entity) {
3556 EntityBuilder(*this, entity, ec).del_name();
3557 }
3558
3561 void del_name(Entity entity) {
3562 EntityBuilder(*this, entity).del_name();
3563 }
3564
3567 void del_entity(Entity entity, bool invalidate) {
3568 if (entity.pair() || entity == EntityBad)
3569 return;
3570
3571 auto& ec = fetch(entity);
3572 del_entity_inter(ec, entity, invalidate);
3573 }
3574
3577 void del_entity(EntityContainer& ec, Entity entity, bool invalidate) {
3578 if (entity.pair() || entity == EntityBad)
3579 return;
3580
3581 del_entity_inter(ec, entity, invalidate);
3582 }
3583
3586 void del_entity_inter(EntityContainer& ec, Entity entity, bool invalidate) {
3587 GAIA_ASSERT(entity.id() > GAIA_ID(LastCoreComponent).id());
3588
3589 // if (!is_req_del(ec))
3590 {
3591 if (m_recs.entities.item_count() == 0)
3592 return;
3593
3594#if GAIA_ASSERT_ENABLED
3595 auto* pChunk = ec.pChunk;
3596 GAIA_ASSERT(pChunk != nullptr);
3597#endif
3598
3599 // Remove the entity from its chunk.
3600 // We call del_name first because remove_entity calls component destructors.
3601 // If the call was made inside invalidate_entity we would access a memory location
3602 // which has already been destructed which is not nice.
3603 del_name(ec, entity);
3604 remove_entity(*ec.pArchetype, *ec.pChunk, ec.row);
3605 }
3606
3607 // Invalidate on-demand.
3608 // We delete as a separate step in the delayed deletion.
3609 if (invalidate)
3610 invalidate_entity(entity);
3611 }
3612
3617 void del_entities(Archetype& archetype) {
3618 for (auto* pChunk: archetype.chunks()) {
3619 auto ids = pChunk->entity_view();
3620 for (auto e: ids) {
3621 if (!valid(e))
3622 continue;
3623
3624#if GAIA_ASSERT_ENABLED
3625 const auto& ec = fetch(e);
3626
3627 // We should never end up trying to delete a forbidden-to-delete entity
3628 GAIA_ASSERT((ec.flags & EntityContainerFlags::OnDeleteTarget_Error) == 0);
3629#endif
3630
3631 del_entity(e, true);
3632 }
3633
3634 validate_chunk(pChunk);
3635
3636 // If the chunk was already dying we need to remove it from the delete list
3637 // because we can delete it right away.
3638 // TODO: Instead of searching for it we could store a delete index in the chunk
3639 // header. This way the lookup is O(1) instead of O(N) and it will help
3640 // with edge-cases (tons of chunks removed at the same time).
3641 if (pChunk->dying()) {
3642 const auto idx = core::get_index(m_chunksToDel, {&archetype, pChunk});
3643 if (idx != BadIndex)
3644 core::swap_erase(m_chunksToDel, idx);
3645 }
3646
3647 remove_chunk(archetype, *pChunk);
3648 }
3649
3650 validate_entities();
3651 }
3652
3654 void del_inter(Entity entity) {
3655 auto on_delete = [this](Entity entityToDel) {
3656 auto& ec = fetch(entityToDel);
3657 handle_del_entity(ec, entityToDel);
3658 };
3659
3660 if (is_wildcard(entity)) {
3661 const auto rel = get(entity.id());
3662 const auto tgt = get(entity.gen());
3663
3664 // (*,*)
3665 if (rel == All && tgt == All) {
3666 GAIA_ASSERT2(false, "Not supported yet");
3667 }
3668 // (*,X)
3669 else if (rel == All) {
3670 if (const auto* pTargets = relations(tgt)) {
3671 // handle_del might invalidate the targets map so we need to make a copy
3672 // TODO: this is suboptimal at best, needs to be optimized
3673 cnt::darray_ext<Entity, 64> tmp;
3674 for (auto key: *pTargets)
3675 tmp.push_back(key.entity());
3676 for (auto e: tmp)
3677 on_delete(Pair(e, tgt));
3678 }
3679 }
3680 // (X,*)
3681 else if (tgt == All) {
3682 if (const auto* pRelations = targets(rel)) {
3683 // handle_del might invalidate the targets map so we need to make a copy
3684 // TODO: this is suboptimal at best, needs to be optimized
3685 cnt::darray_ext<Entity, 64> tmp;
3686 for (auto key: *pRelations)
3687 tmp.push_back(key.entity());
3688 for (auto e: tmp)
3689 on_delete(Pair(rel, e));
3690 }
3691 }
3692 } else {
3693 on_delete(entity);
3694 }
3695 }
3696
3697 // Force-delete all entities from the requested archetypes along with the archetype itself
3698 void del_finalize_archetypes() {
3699 GAIA_PROF_SCOPE(World::del_finalize_archetypes);
3700
3701 for (auto& key: m_reqArchetypesToDel) {
3702 auto* pArchetype = key.archetype();
3703 if (pArchetype == nullptr)
3704 continue;
3705
3706 del_entities(*pArchetype);
3707
3708 // Now that all entities are deleted, all their chunks are requested to get deleted
3709 // and in turn the archetype itself as well. Therefore, it is added to the archetype
3710 // delete list and picked up by del_empty_archetypes. No need to call deletion from here.
3711 // > del_empty_archetype(pArchetype);
3712 }
3713 m_reqArchetypesToDel.clear();
3714 }
3715
3717 void del_finalize_entities() {
3718 GAIA_PROF_SCOPE(World::del_finalize_entities);
3719
3720 for (auto it = m_reqEntitiesToDel.begin(); it != m_reqEntitiesToDel.end();) {
3721 const auto e = it->entity();
3722
3723 // Entities that form archetypes need to stay until the archetype itself is gone
3724 if (m_entityToArchetypeMap.contains(*it)) {
3725 ++it;
3726 continue;
3727 }
3728
3729 // Requested entities are partially deleted. We only need to invalidate them.
3730 invalidate_entity(e);
3731
3732 it = m_reqEntitiesToDel.erase(it);
3733 }
3734 }
3735
3737 void del_finalize() {
3738 GAIA_PROF_SCOPE(World::del_finalize);
3739
3740 del_finalize_archetypes();
3741 del_finalize_entities();
3742 }
3743
3744 GAIA_NODISCARD bool archetype_cond_match(Archetype& archetype, Pair cond, Entity target) const {
3745 // E.g.:
3746 // target = (All, entity)
3747 // cond = (OnDeleteTarget, delete)
3748 // Delete the entity if it matches the cond
3749 auto ids = archetype.ids_view();
3750
3751 if (target.pair()) {
3752 for (auto e: ids) {
3753 // Find the pair which matches (All, entity)
3754 if (!e.pair())
3755 continue;
3756 if (e.gen() != target.gen())
3757 continue;
3758
3759 const auto& ec = m_recs.entities[e.id()];
3760 const auto entity = ec.pChunk->entity_view()[ec.row];
3761 if (!has(entity, cond))
3762 continue;
3763
3764 return true;
3765 }
3766 } else {
3767 for (auto e: ids) {
3768 if (e.pair())
3769 continue;
3770 if (!has(e, cond))
3771 continue;
3772
3773 return true;
3774 }
3775 }
3776
3777 return false;
3778 }
3779
3781 void move_to_archetype(Archetype& srcArchetype, Archetype& dstArchetype) {
3782 GAIA_ASSERT(&srcArchetype != &dstArchetype);
3783
3784 bool updated = false;
3785
3786 for (auto* pSrcChunk: srcArchetype.chunks()) {
3787 auto srcEnts = pSrcChunk->entity_view();
3788 if (srcEnts.empty())
3789 continue;
3790
3791 // Copy entities back-to-front to avoid unnecessary data movements.
3792 // TODO: Handle disabled entities efficiently.
3793 // If there are disabled entities, we still do data movements if there already
3794 // are enabled entities in the chunk.
3795 // TODO: If the header was of some fixed size, e.g. if we always acted as if we had
3796 // ChunkHeader::MAX_COMPONENTS, certain data movements could be done pretty much instantly.
3797 // E.g. when removing tags or pairs, we would simply replace the chunk pointer
3798 // with a pointer to another one. The same goes for archetypes. Component data
3799 // would not have to move at all internal chunk header pointers would remain unchanged.
3800
3801 uint32_t i = (uint32_t)srcEnts.size();
3802 while (i != 0) {
3803 auto* pDstChunk = dstArchetype.foc_free_chunk();
3804 const uint32_t dstSpaceLeft = pDstChunk->capacity() - pDstChunk->size();
3805 const uint32_t cnt = core::get_min(dstSpaceLeft, i);
3806 for (uint32_t j = 0; j < cnt; ++j) {
3807 auto e = srcEnts[i - j - 1];
3808 move_entity(e, fetch(e), dstArchetype, *pDstChunk);
3809 }
3810
3811 pDstChunk->update_world_version();
3812
3813 GAIA_ASSERT(cnt <= i);
3814 i -= cnt;
3815 }
3816
3817 pSrcChunk->update_world_version();
3818 updated = true;
3819 }
3820
3821 if (updated)
3822 update_version(m_worldVersion);
3823 }
3824
3827 GAIA_NODISCARD Archetype* calc_dst_archetype_ent(Archetype* pArchetype, Entity entity) {
3828 GAIA_ASSERT(!is_wildcard(entity));
3829
3830 auto ids = pArchetype->ids_view();
3831 for (auto id: ids) {
3832 if (id != entity)
3833 continue;
3834
3835 return foc_archetype_del(pArchetype, id);
3836 }
3837
3838 return nullptr;
3839 }
3840
3844 GAIA_NODISCARD Archetype* calc_dst_archetype_all_ent(Archetype* pArchetype, Entity entity) {
3845 GAIA_ASSERT(is_wildcard(entity));
3846
3847 Archetype* pDstArchetype = pArchetype;
3848
3849 auto ids = pArchetype->ids_view();
3850 for (auto id: ids) {
3851 if (!id.pair() || id.gen() != entity.gen())
3852 continue;
3853
3854 pDstArchetype = foc_archetype_del(pDstArchetype, id);
3855 }
3856
3857 return pArchetype != pDstArchetype ? pDstArchetype : nullptr;
3858 }
3859
3863 GAIA_NODISCARD Archetype* calc_dst_archetype_ent_all(Archetype* pArchetype, Entity entity) {
3864 GAIA_ASSERT(is_wildcard(entity));
3865
3866 Archetype* pDstArchetype = pArchetype;
3867
3868 auto ids = pArchetype->ids_view();
3869 for (auto id: ids) {
3870 if (!id.pair() || id.id() != entity.id())
3871 continue;
3872
3873 pDstArchetype = foc_archetype_del(pDstArchetype, id);
3874 }
3875
3876 return pArchetype != pDstArchetype ? pDstArchetype : nullptr;
3877 }
3878
3882 GAIA_NODISCARD Archetype* calc_dst_archetype_all_all(Archetype* pArchetype, [[maybe_unused]] Entity entity) {
3883 GAIA_ASSERT(is_wildcard(entity));
3884
3885 Archetype* pDstArchetype = pArchetype;
3886 bool found = false;
3887
3888 auto ids = pArchetype->ids_view();
3889 for (auto id: ids) {
3890 if (!id.pair())
3891 continue;
3892
3893 pDstArchetype = foc_archetype_del(pDstArchetype, id);
3894 found = true;
3895 }
3896
3897 return found ? pDstArchetype : nullptr;
3898 }
3899
3903 GAIA_NODISCARD Archetype* calc_dst_archetype(Archetype* pArchetype, Entity entity) {
3904 if (entity.pair()) {
3905 auto rel = entity.id();
3906 auto tgt = entity.gen();
3907
3908 // Removing a wildcard pair. We need to find all pairs matching it.
3909 if (rel == All.id() || tgt == All.id()) {
3910 // (first, All) means we need to match (first, A), (first, B), ...
3911 if (rel != All.id() && tgt == All.id())
3912 return calc_dst_archetype_ent_all(pArchetype, entity);
3913
3914 // (All, second) means we need to match (A, second), (B, second), ...
3915 if (rel == All.id() && tgt != All.id())
3916 return calc_dst_archetype_all_ent(pArchetype, entity);
3917
3918 // (All, All) means we need to match all relationships
3919 return calc_dst_archetype_all_all(pArchetype, EntityBad);
3920 }
3921 }
3922
3923 // Non-wildcard pair or entity
3924 return calc_dst_archetype_ent(pArchetype, entity);
3925 }
3926
3927 void req_del(Archetype& archetype) {
3928 if (archetype.is_req_del())
3929 return;
3930
3931 archetype.req_del();
3932 m_reqArchetypesToDel.insert(ArchetypeLookupKey(archetype.lookup_hash(), &archetype));
3933 }
3934
3935 void req_del(EntityContainer& ec, Entity entity) {
3936 if (is_req_del(ec))
3937 return;
3938
3939 del_entity(ec, entity, false);
3940
3941 ec.req_del();
3942 m_reqEntitiesToDel.insert(EntityLookupKey(entity));
3943 }
3944
3946 void req_del_entities_with(Entity entity) {
3947 GAIA_PROF_SCOPE(World::req_del_entities_with);
3948
3949 GAIA_ASSERT(entity != Pair(All, All));
3950
3951 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
3952 if (it == m_entityToArchetypeMap.end())
3953 return;
3954
3955 const auto& archetypes = it->second;
3956 for (auto* pArchetype: archetypes)
3957 req_del(*pArchetype);
3958 }
3959
3962 void req_del_entities_with(Entity entity, Pair cond) {
3963 GAIA_PROF_SCOPE(World::req_del_entities_with);
3964
3965 GAIA_ASSERT(entity != Pair(All, All));
3966
3967 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
3968 if (it == m_entityToArchetypeMap.end())
3969 return;
3970
3971 const auto& archetypes = it->second;
3972 for (auto* pArchetype: archetypes) {
3973 // Evaluate the condition if a valid pair is given
3974 if (!archetype_cond_match(*pArchetype, cond, entity))
3975 continue;
3976
3977 req_del(*pArchetype);
3978 }
3979 }
3980
3982 void rem_from_entities(Entity entity) {
3983 GAIA_PROF_SCOPE(World::rem_from_entities);
3984
3985 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
3986 if (it == m_entityToArchetypeMap.end())
3987 return;
3988
3989 // Invalidate the singleton status if necessary
3990 if (!entity.pair()) {
3991 auto& ec = fetch(entity);
3992 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0) {
3993 auto ids = ec.pArchetype->ids_view();
3994 const auto idx = core::get_index(ids, entity);
3995 if (idx != BadIndex)
3996 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::IsSingleton, false);
3997 }
3998 }
3999
4000 // Update archetypes of all affected entities
4001 const auto& archetypes = it->second;
4002 for (auto* pArchetype: archetypes) {
4003 if (pArchetype->is_req_del())
4004 continue;
4005
4006 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
4007 if (pDstArchetype != nullptr)
4008 move_to_archetype(*pArchetype, *pDstArchetype);
4009 }
4010 }
4011
4014 void rem_from_entities(Entity entity, Pair cond) {
4015 GAIA_PROF_SCOPE(World::rem_from_entities);
4016
4017 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
4018 if (it == m_entityToArchetypeMap.end())
4019 return;
4020
4021 // Invalidate the singleton status if necessary
4022 if (!entity.pair()) {
4023 auto& ec = fetch(entity);
4024 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0) {
4025 auto ids = ec.pArchetype->ids_view();
4026 const auto idx = core::get_index(ids, entity);
4027 if (idx != BadIndex)
4028 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::IsSingleton, false);
4029 }
4030 }
4031
4032 const auto& archetypes = it->second;
4033 for (auto* pArchetype: archetypes) {
4034 if (pArchetype->is_req_del())
4035 continue;
4036
4037 // Evaluate the condition if a valid pair is given
4038 if (!archetype_cond_match(*pArchetype, cond, entity))
4039 continue;
4040
4041 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
4042 if (pDstArchetype != nullptr)
4043 move_to_archetype(*pArchetype, *pDstArchetype);
4044 }
4045 }
4046
4057 void handle_del_entity(EntityContainer& ec, Entity entity) {
4058 GAIA_PROF_SCOPE(World::handle_del_entity);
4059
4060 GAIA_ASSERT(!is_wildcard(entity));
4061
4062 if (entity.pair()) {
4063 if ((ec.flags & EntityContainerFlags::OnDelete_Error) != 0) {
4064 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted");
4065 GAIA_LOG_E(
4066 "Trying to delete a pair [%u.%u] %s [%s] that is forbidden from being deleted", entity.id(),
4067 entity.gen(), name(entity), EntityKindString[entity.kind()]);
4068 return;
4069 }
4070
4071 const auto tgt = get(entity.gen());
4072 const auto& ecTgt = fetch(tgt);
4073 if ((ecTgt.flags & EntityContainerFlags::OnDeleteTarget_Error) != 0) {
4074 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted (target restriction)");
4075 GAIA_LOG_E(
4076 "Trying to delete a pair [%u.%u] %s [%s] that is forbidden from being deleted (target restriction)",
4077 entity.id(), entity.gen(), name(entity), EntityKindString[entity.kind()]);
4078 return;
4079 }
4080
4081#if GAIA_USE_SAFE_ENTITY
4082 // Decrement the ref count at this point.
4083 if ((ec.flags & EntityContainerFlags::RefDecreased) == 0) {
4084 --ec.refCnt;
4085 ec.flags |= EntityContainerFlags::RefDecreased;
4086 }
4087
4088 // Don't delete so long something still references us
4089 if (ec.refCnt != 0)
4090 return;
4091#endif
4092
4093 if ((ecTgt.flags & EntityContainerFlags::OnDeleteTarget_Delete) != 0) {
4094 // Delete all entities referencing this one as a relationship pair's target
4095 req_del_entities_with(Pair(All, tgt), Pair(OnDeleteTarget, Delete));
4096 } else {
4097 // Remove from all entities referencing this one as a relationship pair's target
4098 rem_from_entities(Pair(All, tgt));
4099 }
4100
4101 // This entity has been requested to be deleted already. Nothing more for us to do here
4102 if (is_req_del(ec))
4103 return;
4104
4105 if ((ec.flags & EntityContainerFlags::OnDelete_Delete) != 0) {
4106 // Delete all references to the entity
4107 req_del_entities_with(entity);
4108 } else {
4109 // Entities are only removed by default
4110 rem_from_entities(entity);
4111 }
4112 } else {
4113 if ((ec.flags & EntityContainerFlags::OnDelete_Error) != 0) {
4114 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted");
4115 GAIA_LOG_E(
4116 "Trying to delete an entity [%u.%u] %s [%s] that is forbidden from being deleted", entity.id(),
4117 entity.gen(), name(entity), EntityKindString[entity.kind()]);
4118 return;
4119 }
4120
4121 if ((ec.flags & EntityContainerFlags::OnDeleteTarget_Error) != 0) {
4122 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted (a pair's target)");
4123 GAIA_LOG_E(
4124 "Trying to delete an entity [%u.%u] %s [%s] that is forbidden from being deleted (a pair's target)",
4125 entity.id(), entity.gen(), name(entity), EntityKindString[entity.kind()]);
4126 return;
4127 }
4128
4129#if GAIA_USE_SAFE_ENTITY
4130 // Decrement the ref count at this point.
4131 if ((ec.flags & EntityContainerFlags::RefDecreased) == 0) {
4132 --ec.refCnt;
4133 ec.flags |= EntityContainerFlags::RefDecreased;
4134 }
4135
4136 // Don't delete so long something still references us
4137 if (ec.refCnt != 0)
4138 return;
4139#endif
4140
4141 if ((ec.flags & EntityContainerFlags::OnDeleteTarget_Delete) != 0) {
4142 // Delete all entities referencing this one as a relationship pair's target
4143 req_del_entities_with(Pair(All, entity), Pair(OnDeleteTarget, Delete));
4144 } else {
4145 // Remove from all entities referencing this one as a relationship pair's target
4146 rem_from_entities(Pair(All, entity));
4147 }
4148
4149 // This entity is has been requested to be deleted already. Nothing more for us to do here
4150 if (is_req_del(ec))
4151 return;
4152
4153 if ((ec.flags & EntityContainerFlags::OnDelete_Delete) != 0) {
4154 // Delete all references to the entity
4155 req_del_entities_with(entity);
4156 } else {
4157 // Entities are only removed by default
4158 rem_from_entities(entity);
4159 }
4160 }
4161
4162 // Mark the entity with the "delete requested" flag
4163 req_del(ec, entity);
4164
4165#if GAIA_USE_WEAK_ENTITY
4166 auto invalidateWeakEntity = [](WeakEntityTracker* pTracker) {
4167 GAIA_ASSERT(pTracker->pWeakEntity->m_pTracker == pTracker);
4168 pTracker->pWeakEntity->m_pTracker = nullptr;
4169 pTracker->pWeakEntity->m_entity = EntityBad;
4170 delete pTracker;
4171 };
4172
4173 // Invalidate WeakEntities
4174 if (ec.pWeakTracker != nullptr) {
4175 auto* pTracker = ec.pWeakTracker->next;
4176 while (pTracker != nullptr) {
4177 invalidateWeakEntity(pTracker);
4178 pTracker = pTracker->next;
4179 }
4180 pTracker = ec.pWeakTracker->prev;
4181 while (pTracker != nullptr) {
4182 invalidateWeakEntity(pTracker);
4183 pTracker = pTracker->prev;
4184 }
4185 invalidateWeakEntity(ec.pWeakTracker);
4186 }
4187#endif
4188 }
4189
4194 void remove_edge_from_archetype(Archetype* pArchetype, ArchetypeGraphEdge edgeLeft, Entity edgeEntity) {
4195 GAIA_ASSERT(pArchetype != nullptr);
4196
4197 const auto edgeLeftIt = m_archetypesById.find(ArchetypeIdLookupKey(edgeLeft.id, edgeLeft.hash));
4198 if (edgeLeftIt == m_archetypesById.end())
4199 return;
4200
4201 auto* pArchetypeLeft = edgeLeftIt->second;
4202 GAIA_ASSERT(pArchetypeLeft != nullptr);
4203
4204 // Remove the connection with the current archetype
4205 pArchetypeLeft->del_graph_edges(pArchetype, edgeEntity);
4206
4207 // Traverse all archetypes on the right
4208 auto& archetypesRight = pArchetype->right_edges();
4209 for (auto& it: archetypesRight) {
4210 const auto& edgeRight = it.second;
4211 const auto edgeRightIt = m_archetypesById.find(ArchetypeIdLookupKey(edgeRight.id, edgeRight.hash));
4212 if (edgeRightIt == m_archetypesById.end())
4213 continue;
4214
4215 auto* pArchetypeRight = edgeRightIt->second;
4216
4217 // Remove the connection with the current archetype
4218 pArchetype->del_graph_edges(pArchetypeRight, it.first.entity());
4219 }
4220 }
4221
4222 void remove_edges(Entity entityToRemove) {
4223 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entityToRemove));
4224 if (it == m_entityToArchetypeMap.end())
4225 return;
4226
4227 const auto& archetypes = it->second;
4228 for (auto* pArchetype: archetypes)
4229 remove_edge_from_archetype(pArchetype, pArchetype->find_edge_left(entityToRemove), entityToRemove);
4230 }
4231
4232 void remove_edges_from_pairs(Entity entity) {
4233 if (entity.pair())
4234 return;
4235
4236 // Make sure to remove all pairs containing the entity
4237 // (X, something)
4238 const auto* tgts = targets(entity);
4239 if (tgts != nullptr) {
4240 for (auto target: *tgts)
4241 remove_edges(Pair(entity, target.entity()));
4242 }
4243 // (something, X)
4244 const auto* rels = relations(entity);
4245 if (rels != nullptr) {
4246 for (auto relation: *rels)
4247 remove_edges(Pair(relation.entity(), entity));
4248 }
4249 }
4250
4253 void del_graph_edges(Entity entity) {
4254 remove_edges(entity);
4255 remove_edges_from_pairs(entity);
4256 }
4257
4258 void del_reltgt_tgtrel_pairs(Entity entity) {
4259 auto delPair = [](PairMap& map, Entity source, Entity remove) {
4260 auto itTargets = map.find(EntityLookupKey(source));
4261 if (itTargets != map.end()) {
4262 auto& targets = itTargets->second;
4263 targets.erase(EntityLookupKey(remove));
4264 }
4265 };
4266
4267 if (entity.pair()) {
4268 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
4269 if (it != m_recs.pairs.end()) {
4270 // Delete the container record
4271 m_recs.pairs.erase(it);
4272
4273 // Update pairs
4274 auto rel = get(entity.id());
4275 auto tgt = get(entity.gen());
4276
4277 delPair(m_relationsToTargets, rel, tgt);
4278 delPair(m_relationsToTargets, All, tgt);
4279 delPair(m_targetsToRelations, tgt, rel);
4280 delPair(m_targetsToRelations, All, rel);
4281 }
4282 } else {
4283 // Update the container record
4284 auto& ec = m_recs.entities.free(entity);
4285
4286 // If this is a singleton entity its archetype needs to be deleted
4287 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0)
4288 req_del(*ec.pArchetype);
4289
4290 ec.pArchetype = nullptr;
4291 ec.pChunk = nullptr;
4292 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::DeleteRequested, false);
4293
4294 // Update pairs
4295 delPair(m_relationsToTargets, All, entity);
4296 delPair(m_targetsToRelations, All, entity);
4297 m_relationsToTargets.erase(EntityLookupKey(entity));
4298 m_targetsToRelations.erase(EntityLookupKey(entity));
4299 }
4300 }
4301
4304 void invalidate_entity(Entity entity) {
4305 del_graph_edges(entity);
4306 del_reltgt_tgtrel_pairs(entity);
4307 del_entity_archetype_pairs(entity);
4308 }
4309
4314 void store_entity(EntityContainer& ec, Entity entity, Archetype* pArchetype, Chunk* pChunk) {
4315 GAIA_ASSERT(pArchetype != nullptr);
4316 GAIA_ASSERT(pChunk != nullptr);
4317 GAIA_ASSERT(
4318 !locked() && "Entities can't be stored while the world is locked "
4319 "(structural changes are forbidden during this time!)");
4320
4321 ec.pArchetype = pArchetype;
4322 ec.pChunk = pChunk;
4323 ec.row = pChunk->add_entity(entity);
4324 GAIA_ASSERT(entity.pair() || ec.data.gen == entity.gen());
4325 ec.data.dis = 0;
4326 }
4327
4333 void move_entity(Entity entity, EntityContainer& ec, Archetype& dstArchetype, Chunk& dstChunk) {
4334 GAIA_PROF_SCOPE(World::move_entity);
4335
4336 auto* pDstChunk = &dstChunk;
4337 auto* pSrcChunk = ec.pChunk;
4338
4339 GAIA_ASSERT(pDstChunk != pSrcChunk);
4340
4341 const auto srcRow0 = ec.row;
4342 const auto dstRow = pDstChunk->add_entity(entity);
4343 const bool wasEnabled = !ec.data.dis;
4344
4345 auto& srcArchetype = *ec.pArchetype;
4346#if GAIA_ASSERT_ENABLED
4347 verify_move(*this, srcArchetype, entity);
4348#endif
4349
4350 // Make sure the old entity becomes enabled now
4351 srcArchetype.enable_entity(pSrcChunk, srcRow0, true, m_recs);
4352 // Enabling the entity might have changed its chunk index so fetch it again
4353 const auto srcRow = ec.row;
4354
4355 // Move data from the old chunk to the new one
4356 if (dstArchetype.id() == srcArchetype.id()) {
4357 pDstChunk->move_entity_data(entity, dstRow, m_recs);
4358 } else {
4359 pDstChunk->move_foreign_entity_data(pSrcChunk, srcRow, pDstChunk, dstRow);
4360 }
4361
4362 // Remove the entity record from the old chunk
4363 remove_entity(srcArchetype, *pSrcChunk, srcRow);
4364
4365 // An entity might have moved, try updating the free chunk index
4366 dstArchetype.try_update_free_chunk_idx();
4367
4368 // Bring the entity container record up-to-date
4369 ec.pArchetype = &dstArchetype;
4370 ec.pChunk = pDstChunk;
4371 ec.row = (uint16_t)dstRow;
4372
4373 // Make the enabled state in the new chunk match the original state
4374 dstArchetype.enable_entity(pDstChunk, dstRow, wasEnabled, m_recs);
4375
4376 // End-state validation
4377 GAIA_ASSERT(valid(entity));
4378 validate_chunk(pSrcChunk);
4379 validate_chunk(pDstChunk);
4380 validate_entities();
4381 }
4382
4385 void move_entity_raw(Entity entity, EntityContainer& ec, Archetype& dstArchetype) {
4386 // Update the old chunk's world version first
4387 ec.pChunk->update_world_version();
4388
4389 auto* pDstChunk = dstArchetype.foc_free_chunk();
4390 move_entity(entity, ec, dstArchetype, *pDstChunk);
4391
4392 // Update world versions
4393 pDstChunk->update_world_version();
4394 update_version(m_worldVersion);
4395 }
4396
4399 Chunk* move_entity(Entity entity, Archetype& dstArchetype) {
4400 // Archetypes need to be different
4401 auto& ec = fetch(entity);
4402 if (ec.pArchetype == &dstArchetype)
4403 return nullptr;
4404
4405 // Update the old chunk's world version first
4406 ec.pChunk->update_world_version();
4407
4408 auto* pDstChunk = dstArchetype.foc_free_chunk();
4409 move_entity(entity, ec, dstArchetype, *pDstChunk);
4410
4411 // Update world versions
4412 pDstChunk->update_world_version();
4413 update_version(m_worldVersion);
4414
4415 return pDstChunk;
4416 }
4417
4418 void validate_archetype_edges([[maybe_unused]] const Archetype* pArchetype) const {
4419#if GAIA_ECS_VALIDATE_ARCHETYPE_GRAPH && GAIA_ASSERT_ENABLED
4420 GAIA_ASSERT(pArchetype != nullptr);
4421
4422 // Validate left edges
4423 const auto& archetypesLeft = pArchetype->left_edges();
4424 for (const auto& it: archetypesLeft) {
4425 const auto& edge = it.second;
4426 const auto edgeIt = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
4427 if (edgeIt == m_archetypesById.end())
4428 continue;
4429
4430 const auto entity = it.first.entity();
4431 const auto* pArchetypeRight = edgeIt->second;
4432
4433 // Edge must be found
4434 const auto edgeRight = pArchetypeRight->find_edge_right(entity);
4435 GAIA_ASSERT(edgeRight != ArchetypeIdHashPairBad);
4436
4437 // The edge must point to pArchetype
4438 const auto it2 = m_archetypesById.find(ArchetypeIdLookupKey(edgeRight.id, edgeRight.hash));
4439 GAIA_ASSERT(it2 != m_archetypesById.end());
4440 const auto* pArchetype2 = it2->second;
4441 GAIA_ASSERT(pArchetype2 == pArchetype);
4442 }
4443
4444 // Validate right edges
4445 const auto& archetypesRight = pArchetype->right_edges();
4446 for (const auto& it: archetypesRight) {
4447 const auto& edge = it.second;
4448 const auto edgeIt = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
4449 if (edgeIt == m_archetypesById.end())
4450 continue;
4451
4452 const auto entity = it.first.entity();
4453 const auto* pArchetypeRight = edgeIt->second;
4454
4455 // Edge must be found
4456 const auto edgeLeft = pArchetypeRight->find_edge_left(entity);
4457 GAIA_ASSERT(edgeLeft != ArchetypeIdHashPairBad);
4458
4459 // The edge must point to pArchetype
4460 const auto it2 = m_archetypesById.find(ArchetypeIdLookupKey(edgeLeft.id, edgeLeft.hash));
4461 GAIA_ASSERT(it2 != m_archetypesById.end());
4462 const auto* pArchetype2 = it2->second;
4463 GAIA_ASSERT(pArchetype2 == pArchetype);
4464 }
4465#endif
4466 }
4467
4469 void validate_entities() const {
4470#if GAIA_ECS_VALIDATE_ENTITY_LIST
4471 m_recs.entities.validate();
4472#endif
4473 }
4474
4476 void validate_chunk([[maybe_unused]] Chunk* pChunk) const {
4477#if GAIA_ECS_VALIDATE_CHUNKS && GAIA_ASSERT_ENABLED
4478 GAIA_ASSERT(pChunk != nullptr);
4479
4480 if (!pChunk->empty()) {
4481 // Make sure a proper amount of entities reference the chunk
4482 uint32_t cnt = 0;
4483 for (const auto& ec: m_recs.entities) {
4484 if (ec.pChunk != pChunk)
4485 continue;
4486 ++cnt;
4487 }
4488 for (const auto& pair: m_recs.pairs) {
4489 if (pair.second.pChunk != pChunk)
4490 continue;
4491 ++cnt;
4492 }
4493 GAIA_ASSERT(cnt == pChunk->size());
4494 } else {
4495 // Make sure no entities reference the chunk
4496 for (const auto& ec: m_recs.entities) {
4497 GAIA_ASSERT(ec.pChunk != pChunk);
4498 }
4499 for (const auto& pair: m_recs.pairs) {
4500 GAIA_ASSERT(pair.second.pChunk != pChunk);
4501 }
4502 }
4503#endif
4504 }
4505
4509 template <bool CheckIn>
4510 GAIA_NODISCARD bool is_inter(Entity entity, Entity entityBase) const {
4511 GAIA_ASSERT(valid_entity(entity));
4512 GAIA_ASSERT(valid_entity(entityBase));
4513
4514 // Pairs are not supported
4515 if (entity.pair() || entityBase.pair())
4516 return false;
4517
4518 if constexpr (!CheckIn) {
4519 if (entity == entityBase)
4520 return true;
4521 }
4522
4523 const auto& ec = m_recs.entities[entity.id()];
4524 const auto* pArchetype = ec.pArchetype;
4525
4526 // Early exit if there are no Is relationship pairs on the archetype
4527 if (pArchetype->pairs_is() == 0)
4528 return false;
4529
4530 for (uint32_t i = 0; i < pArchetype->pairs_is(); ++i) {
4531 auto e = pArchetype->entity_from_pairs_as_idx(i);
4532 const auto& ecTarget = m_recs.entities[e.gen()];
4533 auto target = ecTarget.pChunk->entity_view()[ecTarget.row];
4534 if (target == entityBase)
4535 return true;
4536
4537 if (is_inter<CheckIn>(target, entityBase))
4538 return true;
4539 }
4540
4541 return false;
4542 }
4543
4545 template <bool CheckIn, typename Func>
4546 void as_up_trav(Entity entity, Func func) {
4547 GAIA_ASSERT(valid_entity(entity));
4548
4549 // Pairs are not supported
4550 if (entity.pair())
4551 return;
4552
4553 if constexpr (!CheckIn) {
4554 func(entity);
4555 }
4556
4557 const auto& ec = m_recs.entities[entity.id()];
4558 const auto* pArchetype = ec.pArchetype;
4559
4560 // Early exit if there are no Is relationship pairs on the archetype
4561 if (pArchetype->pairs_is() == 0)
4562 return;
4563
4564 for (uint32_t i = 0; i < pArchetype->pairs_is(); ++i) {
4565 auto e = pArchetype->entity_from_pairs_as_idx(i);
4566 const auto& ecTarget = m_recs.entities[e.gen()];
4567 auto target = ecTarget.pChunk->entity_view()[ecTarget.row];
4568 func(target);
4569
4570 as_up_trav<CheckIn>(target, func);
4571 }
4572 }
4573
4574 template <typename T>
4575 const ComponentCacheItem& reg_core_entity(Entity id, Archetype* pArchetype) {
4576 auto comp = add(*pArchetype, id.entity(), id.pair(), id.kind());
4577 const auto& ci = comp_cache_mut().add<T>(id);
4578 GAIA_ASSERT(ci.entity == id);
4579 GAIA_ASSERT(comp == id);
4580 (void)comp;
4581 return ci;
4582 }
4583
4584 template <typename T>
4585 const ComponentCacheItem& reg_core_entity(Entity id) {
4586 return reg_core_entity<T>(id, m_pRootArchetype);
4587 }
4588
4589 void init();
4590
4591 void done() {
4592 cleanup_inter();
4593
4594#if GAIA_ECS_CHUNK_ALLOCATOR
4595 ChunkAllocator::get().flush();
4596#endif
4597 }
4598
4602 void assign_entity(Entity entity, Archetype& archetype) {
4603 GAIA_ASSERT(!entity.pair());
4604
4605 auto* pChunk = archetype.foc_free_chunk();
4606 store_entity(m_recs.entities[entity.id()], entity, &archetype, pChunk);
4607 pChunk->update_versions();
4608 archetype.try_update_free_chunk_idx();
4609
4610 // Call constructors for the generic components on the newly added entity if necessary
4611 pChunk->call_gen_ctors(pChunk->size() - 1, 1);
4612
4613#if GAIA_ASSERT_ENABLED
4614 const auto& ec = m_recs.entities[entity.id()];
4615 GAIA_ASSERT(ec.pChunk == pChunk);
4616 auto entityExpected = pChunk->entity_view()[ec.row];
4617 GAIA_ASSERT(entityExpected == entity);
4618#endif
4619 }
4620
4624 void assign_pair(Entity entity, Archetype& archetype) {
4625 GAIA_ASSERT(entity.pair());
4626
4627 // Pairs are always added to m_pEntityArchetype initially and this can't change.
4628 GAIA_ASSERT(&archetype == m_pEntityArchetype);
4629
4630 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
4631 if (it != m_recs.pairs.end())
4632 return;
4633
4634 // Update the container record
4635 EntityContainer ec{};
4636 ec.idx = entity.id();
4637 ec.data.gen = entity.gen();
4638 ec.data.pair = 1;
4639 ec.data.ent = 1;
4640 ec.data.kind = EntityKind::EK_Gen;
4641
4642 auto* pChunk = archetype.foc_free_chunk();
4643 store_entity(ec, entity, &archetype, pChunk);
4644 pChunk->update_versions();
4645 archetype.try_update_free_chunk_idx();
4646
4647 m_recs.pairs.emplace(EntityLookupKey(entity), GAIA_MOV(ec));
4648
4649 // Update pair mappings
4650 const auto rel = get(entity.id());
4651 const auto tgt = get(entity.gen());
4652
4653 auto addPair = [](PairMap& map, Entity source, Entity add) {
4654 auto& ents = map[EntityLookupKey(source)];
4655 ents.insert(EntityLookupKey(add));
4656 };
4657
4658 addPair(m_relationsToTargets, rel, tgt);
4659 addPair(m_relationsToTargets, All, tgt);
4660 addPair(m_targetsToRelations, tgt, rel);
4661 addPair(m_targetsToRelations, All, rel);
4662 }
4663
4670 GAIA_NODISCARD Entity add(Archetype& archetype, bool isEntity, bool isPair, EntityKind kind) {
4671 EntityContainerCtx ctx{isEntity, isPair, kind};
4672 const auto entity = m_recs.entities.alloc(&ctx);
4673 assign_entity(entity, archetype);
4674 return entity;
4675 }
4676
4682 template <typename Func>
4683 void add_entity_n(Archetype& archetype, uint32_t count, Func func) {
4684 EntityContainerCtx ctx{true, false, EntityKind::EK_Gen};
4685
4686 uint32_t left = count;
4687 do {
4688 auto* pChunk = archetype.foc_free_chunk();
4689 const uint32_t originalChunkSize = pChunk->size();
4690 const uint32_t freeSlotsInChunk = pChunk->capacity() - originalChunkSize;
4691 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
4692
4693 GAIA_FOR(toCreate) {
4694 const auto entityNew = m_recs.entities.alloc(&ctx);
4695 auto& ecNew = m_recs.entities[entityNew.id()];
4696 store_entity(ecNew, entityNew, &archetype, pChunk);
4697
4698#if GAIA_ASSERT_ENABLED
4699 GAIA_ASSERT(ecNew.pChunk == pChunk);
4700 auto entityExpected = pChunk->entity_view()[ecNew.row];
4701 GAIA_ASSERT(entityExpected == entityNew);
4702#endif
4703 }
4704
4705 // New entities were added, try updating the free chunk index
4706 archetype.try_update_free_chunk_idx();
4707
4708 // Call constructors for the generic components on the newly added entity if necessary
4709 pChunk->call_gen_ctors(originalChunkSize, toCreate);
4710
4711 // Call functors
4712 {
4713 auto entities = pChunk->entity_view();
4714 GAIA_FOR2(originalChunkSize, pChunk->size()) func(entities[i]);
4715 }
4716
4717 pChunk->update_versions();
4718
4719 left -= toCreate;
4720 } while (left > 0);
4721 }
4722
4724 void gc() {
4725 GAIA_PROF_SCOPE(World::gc);
4726
4727 del_empty_chunks();
4728 defrag_chunks(m_defragEntitiesPerTick);
4729 del_empty_archetypes();
4730 }
4731
4732 public:
4733 QuerySerBuffer& query_buffer(QueryId& serId) {
4734 // No serialization id set on the query, try creating a new record
4735 if GAIA_UNLIKELY (serId == QueryIdBad) {
4736#if GAIA_ASSERT_ENABLED
4737 uint32_t safetyCounter = 0;
4738#endif
4739
4740 while (true) {
4741#if GAIA_ASSERT_ENABLED
4742 // Make sure we don't cross some safety threshold
4743 ++safetyCounter;
4744 GAIA_ASSERT(safetyCounter < 100000);
4745#endif
4746
4747 serId = ++m_nextQuerySerId;
4748 // Make sure we do not overflow
4749 GAIA_ASSERT(serId != 0);
4750
4751 // If the id is already found, try again.
4752 // Note, this is essentially never going to repeat. We would have to prepare millions if
4753 // not billions of queries for which we only added inputs but never queried them.
4754 auto ret = m_querySerMap.try_emplace(serId);
4755 if (!ret.second)
4756 continue;
4757
4758 return ret.first->second;
4759 };
4760 }
4761
4762 return m_querySerMap[serId];
4763 }
4764
4765 void query_buffer_reset(QueryId& serId) {
4766 auto it = m_querySerMap.find(serId);
4767 if (it == m_querySerMap.end())
4768 return;
4769
4770 m_querySerMap.erase(it);
4771 serId = QueryIdBad;
4772 }
4773
4774 void invalidate_queries_for_entity(Pair is_pair) {
4775 GAIA_ASSERT(is_pair.first() == Is);
4776
4777 // We still need to handle invalidation "down-the-tree".
4778 // E.g. following setup:
4779 // q = w.query().all({Is,animal});
4780 // w.as(wolf, carnivore);
4781 // w.as(carnivore, animal);
4782 // q.each() ...; // animal, carnivore, wolf
4783 // w.del(wolf, {Is,carnivore}) // wolf is no longer a carnivore and thus no longer an animal
4784 // After this deletion, we need to invalidate "q" because wolf is no longer an animal
4785 // and we don't want q to include it.
4786 // q.each() ...; // animal
4787
4788 auto e = is_pair.second();
4789 as_up_trav<false>(e, [&](Entity target) {
4790 // Invalidate all queries that contain everything in our path.
4791 m_queryCache.invalidate_queries_for_entity(EntityLookupKey(Pair{Is, target}));
4792 });
4793 }
4794
4795 Entity name_to_entity(std::span<const char> exprRaw) const {
4796 auto expr = core::trim(exprRaw);
4797
4798 if (expr[0] == '(') {
4799 if (expr.back() != ')') {
4800 GAIA_ASSERT2(false, "Expression '(' not terminated");
4801 return EntityBad;
4802 }
4803
4804 const auto idStr = expr.subspan(1, expr.size() - 2);
4805 const auto commaIdx = core::get_index(idStr, ',');
4806
4807 const auto first = name_to_entity(idStr.subspan(0, commaIdx));
4808 if (first == EntityBad)
4809 return EntityBad;
4810 const auto second = name_to_entity(idStr.subspan(commaIdx + 1));
4811 if (second == EntityBad)
4812 return EntityBad;
4813
4814 return ecs::Pair(first, second);
4815 }
4816
4817 {
4818 auto idStr = core::trim(expr);
4819
4820 // Wildcard character
4821 if (idStr.size() == 1 && idStr[0] == '*')
4822 return All;
4823
4824 return get_inter(idStr.data(), (uint32_t)idStr.size());
4825 }
4826 }
4827
4828 Entity expr_to_entity(va_list& args, std::span<const char> exprRaw) const {
4829 auto expr = core::trim(exprRaw);
4830
4831 if (expr[0] == '%') {
4832 if (expr[1] != 'e') {
4833 GAIA_ASSERT2(false, "Expression '%' not terminated");
4834 return EntityBad;
4835 }
4836
4837 auto id = (Identifier)va_arg(args, unsigned long long);
4838 return Entity(id);
4839 }
4840
4841 if (expr[0] == '(') {
4842 if (expr.back() != ')') {
4843 GAIA_ASSERT2(false, "Expression '(' not terminated");
4844 return EntityBad;
4845 }
4846
4847 const auto idStr = expr.subspan(1, expr.size() - 2);
4848 const auto commaIdx = core::get_index(idStr, ',');
4849
4850 const auto first = expr_to_entity(args, idStr.subspan(0, commaIdx));
4851 if (first == EntityBad)
4852 return EntityBad;
4853 const auto second = expr_to_entity(args, idStr.subspan(commaIdx + 1));
4854 if (second == EntityBad)
4855 return EntityBad;
4856
4857 return ecs::Pair(first, second);
4858 }
4859
4860 {
4861 auto idStr = core::trim(expr);
4862
4863 // Wildcard character
4864 if (idStr.size() == 1 && idStr[0] == '*')
4865 return All;
4866
4867 // Anything else is a component name
4868 const auto* pItem = m_compCache.find(idStr.data(), (uint32_t)idStr.size());
4869 if (pItem == nullptr) {
4870 GAIA_ASSERT2(false, "Component not found");
4871 GAIA_LOG_W("Component '%.*s' not found", (uint32_t)idStr.size(), idStr.data());
4872 return EntityBad;
4873 }
4874
4875 return pItem->entity;
4876 }
4877 }
4878 };
4879
4880 using EntityBuilder = World::EntityBuilder;
4881 } // namespace ecs
4882} // namespace gaia
4883
4884#include "api.inl"
4885#include "system.inl"
4886
4887namespace gaia {
4888 namespace ecs {
4889 inline void World::init() {
4890 // Use the default serializer
4891 set_serializer(nullptr);
4892
4893 // Register the root archetype
4894 {
4895 m_pRootArchetype = create_archetype({});
4896 m_pRootArchetype->set_hashes({calc_lookup_hash({})});
4897 reg_archetype(m_pRootArchetype);
4898 }
4899
4900 (void)reg_core_entity<Core_>(Core);
4901
4902 // Entity archetype matches the root archetype for now
4903 m_pEntityArchetype = m_pRootArchetype;
4904
4905 // Register the component archetype (entity + EntityDesc + Component)
4906 {
4907 Archetype* pCompArchetype{};
4908 {
4909 const auto id = GAIA_ID(EntityDesc);
4910 const auto& ci = reg_core_entity<EntityDesc>(id);
4911 EntityBuilder(*this, id).add_inter_init(ci.entity);
4912 sset<EntityDesc>(id) = {ci.name.str(), ci.name.len()};
4913 pCompArchetype = m_recs.entities[id.id()].pArchetype;
4914 }
4915 {
4916 const auto id = GAIA_ID(Component);
4917 const auto& ci = reg_core_entity<Component>(id, pCompArchetype);
4918 EntityBuilder(*this, id).add_inter_init(ci.entity);
4919 acc_mut(id)
4920 // Entity descriptor
4921 .sset<EntityDesc>({ci.name.str(), ci.name.len()})
4922 // Component
4923 .sset<Component>(ci.comp);
4924 m_pCompArchetype = m_recs.entities[id.id()].pArchetype;
4925 }
4926 }
4927
4928 // Core components.
4929 // Their order must correspond to the value sequence in id.h.
4930 {
4931 (void)reg_core_entity<OnDelete_>(OnDelete);
4932 (void)reg_core_entity<OnDeleteTarget_>(OnDeleteTarget);
4933 (void)reg_core_entity<Remove_>(Remove);
4934 (void)reg_core_entity<Delete_>(Delete);
4935 (void)reg_core_entity<Error_>(Error);
4936 (void)reg_core_entity<Requires_>(Requires);
4937 (void)reg_core_entity<CantCombine_>(CantCombine);
4938 (void)reg_core_entity<Exclusive_>(Exclusive);
4939 (void)reg_core_entity<Acyclic_>(Acyclic);
4940 (void)reg_core_entity<Traversable_>(Traversable);
4941 (void)reg_core_entity<All_>(All);
4942 (void)reg_core_entity<ChildOf_>(ChildOf);
4943 (void)reg_core_entity<Is_>(Is);
4944 (void)reg_core_entity<System_>(System);
4945 (void)reg_core_entity<DependsOn_>(DependsOn);
4946
4947 (void)reg_core_entity<_Var0>(Var0);
4948 (void)reg_core_entity<_Var1>(Var1);
4949 (void)reg_core_entity<_Var2>(Var2);
4950 (void)reg_core_entity<_Var3>(Var3);
4951 (void)reg_core_entity<_Var4>(Var4);
4952 (void)reg_core_entity<_Var5>(Var5);
4953 (void)reg_core_entity<_Var6>(Var6);
4954 (void)reg_core_entity<_Var7>(Var7);
4955 }
4956
4957 // Add special properties for core components.
4958 // Their order must correspond to the value sequence in id.h.
4959 {
4960 EntityBuilder(*this, Core) //
4961 .add(Core)
4962 .add(Pair(OnDelete, Error));
4963 EntityBuilder(*this, GAIA_ID(EntityDesc)) //
4964 .add(Core)
4965 .add(Pair(OnDelete, Error));
4966 EntityBuilder(*this, GAIA_ID(Component)) //
4967 .add(Core)
4968 .add(Pair(OnDelete, Error));
4969 EntityBuilder(*this, OnDelete) //
4970 .add(Core)
4971 .add(Exclusive)
4972 .add(Pair(OnDelete, Error));
4973 EntityBuilder(*this, OnDeleteTarget) //
4974 .add(Core)
4975 .add(Exclusive)
4976 .add(Pair(OnDelete, Error));
4977 EntityBuilder(*this, Remove) //
4978 .add(Core)
4979 .add(Pair(OnDelete, Error));
4980 EntityBuilder(*this, Delete) //
4981 .add(Core)
4982 .add(Pair(OnDelete, Error));
4983 EntityBuilder(*this, Error) //
4984 .add(Core)
4985 .add(Pair(OnDelete, Error));
4986 EntityBuilder(*this, All) //
4987 .add(Core)
4988 .add(Pair(OnDelete, Error));
4989 EntityBuilder(*this, Requires) //
4990 .add(Core)
4991 .add(Acyclic)
4992 .add(Pair(OnDelete, Error));
4993 EntityBuilder(*this, CantCombine) //
4994 .add(Core)
4995 .add(Acyclic)
4996 .add(Pair(OnDelete, Error));
4997 EntityBuilder(*this, Exclusive) //
4998 .add(Core)
4999 .add(Pair(OnDelete, Error))
5000 .add(Acyclic);
5001 EntityBuilder(*this, Acyclic) //
5002 .add(Core)
5003 .add(Pair(OnDelete, Error));
5004 EntityBuilder(*this, Traversable) //
5005 .add(Core)
5006 .add(Pair(OnDelete, Error));
5007
5008 EntityBuilder(*this, ChildOf) //
5009 .add(Core)
5010 .add(Acyclic)
5011 .add(Exclusive)
5012 .add(Traversable)
5013 .add(Pair(OnDelete, Error))
5014 .add(Pair(OnDeleteTarget, Delete));
5015 EntityBuilder(*this, Is) //
5016 .add(Core)
5017 .add(Acyclic)
5018 .add(Pair(OnDelete, Error));
5019
5020 EntityBuilder(*this, System) //
5021 .add(Core)
5022 .add(Acyclic)
5023 .add(Pair(OnDelete, Error));
5024 EntityBuilder(*this, DependsOn) //
5025 .add(Core)
5026 .add(Acyclic)
5027 .add(Pair(OnDelete, Error));
5028
5029 EntityBuilder(*this, Var0) //
5030 .add(Core)
5031 .add(Pair(OnDelete, Error));
5032 EntityBuilder(*this, Var1) //
5033 .add(Core)
5034 .add(Pair(OnDelete, Error));
5035 EntityBuilder(*this, Var2) //
5036 .add(Core)
5037 .add(Pair(OnDelete, Error));
5038 EntityBuilder(*this, Var3) //
5039 .add(Core)
5040 .add(Pair(OnDelete, Error));
5041 EntityBuilder(*this, Var4) //
5042 .add(Core)
5043 .add(Pair(OnDelete, Error));
5044 EntityBuilder(*this, Var5) //
5045 .add(Core)
5046 .add(Pair(OnDelete, Error));
5047 EntityBuilder(*this, Var6) //
5048 .add(Core)
5049 .add(Pair(OnDelete, Error));
5050 EntityBuilder(*this, Var7) //
5051 .add(Core)
5052 .add(Pair(OnDelete, Error));
5053 }
5054
5055 // Remove all archetypes with no chunks. We don't want any leftovers after
5056 // archetype movements.
5057 {
5058 for (uint32_t i = 1; i < m_archetypes.size(); ++i) {
5059 auto* pArchetype = m_archetypes[i];
5060 if (!pArchetype->chunks().empty())
5061 continue;
5062
5063 // Request deletion the standard way.
5064 // We could simply add archetypes into m_archetypesToDel but this way
5065 // we can actually replicate what the system really does on the inside
5066 // and it will require more work at the cost of easier maintenance.
5067 // The amount of archetypes cleanup is very small after init and the code
5068 // only runs after the world is created so this is not a big deal.
5069 req_del(*pArchetype);
5070 }
5071
5072 // Cleanup
5073 {
5074 del_finalize();
5075 while (!m_chunksToDel.empty() || !m_archetypesToDel.empty())
5076 gc();
5077
5078 // Make sure everything has been cleared
5079 GAIA_ASSERT(m_reqArchetypesToDel.empty());
5080 GAIA_ASSERT(m_chunksToDel.empty());
5081 GAIA_ASSERT(m_archetypesToDel.empty());
5082 }
5083
5084 sort_archetypes();
5085
5086 // Make sure archetypes have valid graphs after the cleanup
5087 for (const auto* pArchetype: m_archetypes)
5088 validate_archetype_edges(pArchetype);
5089 }
5090
5091 // Make sure archetype pointers are up-to-date
5092 m_pCompArchetype = m_recs.entities[GAIA_ID(Component).id()].pArchetype;
5093
5094#if GAIA_SYSTEMS_ENABLED
5095 // Initialize the systems query
5096 systems_init();
5097#endif
5098 }
5099
5100 inline GroupId
5101 group_by_func_default([[maybe_unused]] const World& world, const Archetype& archetype, Entity groupBy) {
5102 if (archetype.pairs() > 0) {
5103 auto ids = archetype.ids_view();
5104 for (auto id: ids) {
5105 if (!id.pair() || id.id() != groupBy.id())
5106 continue;
5107
5108 // Consider the pair's target the groupId
5109 return id.gen();
5110 }
5111 }
5112
5113 // No group
5114 return 0;
5115 }
5116 } // namespace ecs
5117} // namespace gaia
5118
5119#if GAIA_SYSTEMS_ENABLED
5120namespace gaia {
5121 namespace ecs {
5122 inline void World::systems_init() {
5123 m_systemsQuery = query()
5124 .all(System)
5125 // sort systems by their dependencies
5126 .sort_by(EntityBad, [](const World& world, const void* pData0, const void* pData1) {
5127 const auto& entity0 = *(Entity*)pData0;
5128 const auto& entity1 = *(Entity*)pData1;
5129 if (world.has(entity0, ecs::Pair(DependsOn, entity1)))
5130 return 1;
5131 if (world.has(entity1, ecs::Pair(DependsOn, entity0)))
5132 return -1;
5133
5134 return (int)entity0.id() - (int)entity1.id();
5135 });
5136 }
5137
5138 inline void World::systems_run() {
5139 m_systemsQuery.each([](ecs::Iter& it) {
5140 auto se_view = it.sview_mut<ecs::System_>(0);
5141 const auto cnt = se_view.size();
5142 GAIA_FOR(cnt) {
5143 auto& sys = se_view[i];
5144 sys.exec();
5145 }
5146 });
5147 }
5148
5149 inline SystemBuilder World::system() {
5150 // Create the system
5151 auto sysEntity = add();
5152 EntityBuilder(*this, sysEntity) //
5153 .add<System_>();
5154
5155 auto ss = acc_mut(sysEntity);
5156 auto& sys = ss.smut<System_>();
5157 {
5158 sys.entity = sysEntity;
5159 sys.query = query();
5160 }
5161 return SystemBuilder(*this, sysEntity);
5162 }
5163 } // namespace ecs
5164} // namespace gaia
5165#endif
Array with variable size of elements of type.
Definition darray_impl.h:25
Array of elements of type.
Definition sarray_ext_impl.h:27
Definition span_impl.h:99
Definition archetype.h:82
void set_hashes(LookupHash hashLookup)
Sets hashes for each component type and lookup.
Definition archetype.h:511
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:674
Definition ser_binary.h:10
Definition chunk.h:29
GAIA_NODISCARD GAIA_FORCEINLINE auto comp_ptr_mut_gen(uint32_t compIdx, uint32_t row)
Returns a read-write span of the component data. Also updates the world version for the component.
Definition chunk.h:297
GAIA_NODISCARD uint32_t comp_idx(Entity entity) const
Returns the internal index of a component based on the provided entity.
Definition chunk.h:1389
Cache for compile-time defined components.
Definition component_cache.h:23
GAIA_NODISCARD const ComponentCacheItem * find(detail::ComponentDescId compDescId) const noexcept
Searches for the component cache item given the compDescId.
Definition component_cache.h:138
GAIA_NODISCARD const ComponentCacheItem & get(detail::ComponentDescId compDescId) const noexcept
Returns the component cache item given the compDescId.
Definition component_cache.h:156
GAIA_NODISCARD GAIA_FORCEINLINE const ComponentCacheItem & add(Entity entity)
Registers the component item for.
Definition component_cache.h:74
Iterator used when copying entities.
Definition chunk_iterator.h:350
void set_range(uint16_t from, uint16_t cnt)
Sets the iterator's range.
Definition chunk_iterator.h:376
Definition query_cache.h:47
void invalidate_queries_for_entity(EntityLookupKey entityKey)
Invalidates all cached queries that work with the given entity This covers the following kinds of que...
Definition query_cache.h:193
Definition world.h:48
GAIA_NODISCARD uint32_t & world_version()
Returns the current version of the world.
Definition world.h:2289
void as(Entity entity, Entity entityBase)
Shortcut for add(entity, Pair(Is, entityBase)
Definition world.h:1448
void defrag_entities_per_tick(uint32_t value)
Sets the maximum number of entities defragmented per world tick.
Definition world.h:2346
void set_max_lifespan(Entity entity, uint32_t lifespan=Archetype::MAX_ARCHETYPE_LIFESPAN)
Sets maximal lifespan of an archetype entity belongs to.
Definition world.h:2297
GAIA_NODISCARD const cnt::set< EntityLookupKey > * targets(Entity relation) const
Returns targets for relation.
Definition world.h:2057
EntityBuilder build(Entity entity)
Starts a bulk add/remove operation on entity.
Definition world.h:1072
void enable(Entity entity, bool enable)
Enables or disables an entire entity.
Definition world.h:2231
void child(Entity entity, Entity parent)
Shortcut for add(entity, Pair(ChildOf, parent)
Definition world.h:1487
void clear(Entity entity)
Removes any component or entity attached to entity.
Definition world.h:1205
GAIA_NODISCARD const ComponentCacheItem & add()
Creates a new component if not found already.
Definition world.h:1111
void add(Entity entity, Entity object)
Attaches entity object to entity entity.
Definition world.h:1138
GAIA_NODISCARD const cnt::set< EntityLookupKey > * relations(Entity target) const
Returns relations for target.
Definition world.h:1910
GAIA_NODISCARD decltype(auto) sset(Entity entity)
Sets the value of the component T on entity without triggering a world version update.
Definition world.h:1557
void add(Entity entity, U &&value)
Attaches a new component T to entity. Also sets its value.
Definition world.h:1187
GAIA_NODISCARD bool is(Entity entity, Entity entityBase) const
Checks if entity inherits from entityBase.
Definition world.h:1459
void modify(Entity entity)
Marks the component T as modified. Best used with acc_mut().sset() or set() to manually trigger an up...
Definition world.h:1511
void add(Entity entity)
Attaches a new component T to entity.
Definition world.h:1157
void del(Entity entity)
Removes an entity along with all data associated with it.
Definition world.h:1406
void name_raw(Entity entity, const char *name, uint32_t len=0)
Assigns a name to entity. Ignored if used with pair. The string is NOT copied. Your are responsible f...
Definition world.h:1776
void del(Entity entity)
Removes a component T from entity.
Definition world.h:1439
GAIA_NODISCARD bool locked() const
Checks if the chunk is locked for structural changes.
Definition world.h:2545
GAIA_NODISCARD decltype(auto) set(Entity entity)
Sets the value of the component T on entity.
Definition world.h:1545
GAIA_NODISCARD Entity get(EntityId id) const
Returns the entity located at the index id.
Definition world.h:1047
void update()
Performs various internal operations related to the end of the frame such as memory cleanup and other...
Definition world.h:2315
void name(Entity entity, const char *name, uint32_t len=0)
Assigns a name to entity. Ignored if used with pair. The string is copied and kept internally.
Definition world.h:1759
void add(Entity entity, Entity object, T &&value)
Attaches object to entity. Also sets its value.
Definition world.h:1169
ComponentGetter acc(Entity entity) const
Starts a bulk get operation on an entity.
Definition world.h:1583
GAIA_NODISCARD const char * name(EntityId entityId) const
Returns the name assigned to entityId.
Definition world.h:1809
GAIA_NODISCARD bool enabled(Entity entity) const
Checks if an entity is enabled.
Definition world.h:2246
bool load(ser::ISerializer *pOutputSerializer=nullptr)
Loads a world state from a buffer. The buffer is sought to 0 before any loading happens....
Definition world.h:2671
void diag_components() const
Performs diagnostics on registered components. Prints basic info about them and reports and detected ...
Definition world.h:2361
void targets_if(Entity entity, Entity relation, Func func) const
Returns the relationship targets for the relation entity on entity.
Definition world.h:2135
GAIA_NODISCARD bool valid(Entity entity) const
Checks if entity is valid.
Definition world.h:1036
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity is currently used by the world.
Definition world.h:1607
auto query()
Provides a query set up to work with the parent world.
Definition world.h:177
GAIA_NODISCARD Entity copy(Entity srcEntity)
Creates a new entity by cloning an already existing one.
Definition world.h:1228
void diag() const
Performs all diagnostics.
Definition world.h:2390
GAIA_NODISCARD Chunk * get_chunk(Entity entity, uint32_t &row) const
Returns a chunk containing the entity. Index of the entity is stored in row.
Definition world.h:2274
GAIA_NODISCARD bool has(Entity entity, Pair pair) const
Checks if entity contains pair.
Definition world.h:1727
GAIA_NODISCARD Entity add(EntityKind kind=EntityKind::EK_Gen)
Creates a new empty entity.
Definition world.h:1079
GAIA_NODISCARD bool has(Pair pair) const
Checks if pair is currently used by the world.
Definition world.h:1662
void save()
Saves contents of the world to a buffer. The buffer is reset, not appended. NOTE: In order for custom...
Definition world.h:2555
GAIA_NODISCARD decltype(auto) get(Entity entity) const
Returns the value stored in the component T on entity.
Definition world.h:1598
void add(Entity entity, Pair pair)
Creates a new entity relationship pair.
Definition world.h:1147
void add_n(uint32_t count, Func func=func_void_with_entity)
Creates count new empty entities.
Definition world.h:1087
void del(Entity entity, Entity object)
Removes an object from entity if possible.
Definition world.h:1420
void diag_entities() const
Performs diagnostics on entities of the world. Also performs validation of internal structures which ...
Definition world.h:2367
GAIA_NODISCARD decltype(auto) mut(Entity entity)
Sets the value of the component T on entity without triggering a world version update.
Definition world.h:1571
void diag_archetypes() const
Performs diagnostics on archetypes. Prints basic info about them and the chunks they contain.
Definition world.h:2353
GAIA_NODISCARD bool child(Entity entity, Entity parent) const
Checks if.
Definition world.h:1493
GAIA_NODISCARD bool in(Entity entity, Entity entityBase) const
Checks if entity is located in entityBase. This is almost the same as "is" with the exception that fa...
Definition world.h:1469
void relations_if(Entity entity, Entity target, Func func) const
Returns the relationship relations for the target entity on entity.
Definition world.h:1988
void copy_n(Entity entity, uint32_t count, Func func=func_void_with_entity)
Creates count new entities by cloning an already existing one.
Definition world.h:1266
void del(Entity entity, Pair pair)
Removes an existing entity relationship pair.
Definition world.h:1429
GAIA_NODISCARD Chunk * get_chunk(Entity entity) const
Returns a chunk containing the entity.
Definition world.h:2263
GAIA_NODISCARD Entity get(const char *name, uint32_t len=0) const
Returns the entity that is assigned a name name. If the name contains the character '....
Definition world.h:1821
void targets(Entity entity, Entity relation, Func func) const
Returns the relationship targets for the relation entity on entity.
Definition world.h:2103
GAIA_NODISCARD bool has(Entity entity, Entity object) const
Checks if entity contains the entity object.
Definition world.h:1672
GAIA_NODISCARD uint32_t size() const
Returns the number of active entities.
Definition world.h:2283
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity contains the component T.
Definition world.h:1738
GAIA_NODISCARD ComponentSetter acc_mut(Entity entity)
Starts a bulk set operation on entity.
Definition world.h:1531
void add_n(Entity entity, uint32_t count, Func func=func_void_with_entity)
Creates count of entities of the same archetype as entity.
Definition world.h:1098
GAIA_NODISCARD const char * name(Entity entity) const
Returns the name assigned to entity.
Definition world.h:1784
GAIA_NODISCARD Entity relation(Entity entity, Entity target) const
Returns the first relationship relation for the target entity on entity.
Definition world.h:1923
void cleanup()
Clears the world so that all its entities and components are released.
Definition world.h:2331
GAIA_NODISCARD Entity target(Entity entity, Entity relation) const
Returns the first relationship target for the relation entity on entity.
Definition world.h:2070
void relations(Entity entity, Entity target, Func func) const
Returns the relationship relations for the target entity on entity.
Definition world.h:1956
Buffer for deferred execution of some operations on entities.
Definition command_buffer.h:45
Wrapper for two Entities forming a relationship pair.
Definition id.h:395
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:202
Definition robin_hood.h:720
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition component_cache_item.h:24
Entity entity
Component entity.
Definition component_cache_item.h:45
Component comp
Unique component identifier.
Definition component_cache_item.h:47
SymbolLookupKey name
Component name.
Definition component_cache_item.h:54
Definition component_getter.h:11
Definition component_setter.h:12
ComponentSetter & set(U &&value)
Sets the value of the component.
Definition component_setter.h:26
Definition entity_container.h:33
Definition entity_container.h:57
uint16_t row
Row at which the entity is stored in the chunk.
Definition entity_container.h:91
Chunk * pChunk
Chunk the entity currently resides in (stable address)
Definition entity_container.h:111
Definition entity_container.h:151
cnt::ilist< EntityContainer, Entity > entities
Implicit list of entities. Used for look-ups only when searching for entities in chunks + data valida...
Definition entity_container.h:157
cnt::map< EntityLookupKey, EntityContainer > pairs
Just like m_recs.entities, but stores pairs. Needs to be a map because pair ids are huge numbers.
Definition entity_container.h:161
Component used to describe the entity name.
Definition id.h:384
Hashmap lookup structure used for Entity.
Definition id.h:336
Definition id.h:225
Definition world.h:238
Chunk * m_pChunkSrc
Original chunk m_entity belonged to.
Definition world.h:245
EntityBuilder & as(Entity entityBase)
Shortcut for add(Pair(Is, entityBase)). Effectively makes an entity inherit from entityBase.
Definition world.h:414
void commit()
Commits all gathered changes and performs an archetype movement.
Definition world.h:285
void del_name()
Removes any name associated with the entity.
Definition world.h:365
EntityBuilder & child(Entity parent)
Shortcut for add(Pair(ChildOf, parent))
Definition world.h:427
Archetype * m_pArchetype
Target archetype we want to move to.
Definition world.h:249
Archetype * m_pArchetypeSrc
Original archetype m_entity belongs to.
Definition world.h:243
GAIA_NODISCARD bool as(Entity entity, Entity entityBase) const
Check if entity inherits from entityBase.
Definition world.h:422
EntityBuilder & add(Pair pair)
Prepares an archetype movement by following the "add" edge of the current archetype.
Definition world.h:401
void name_raw(const char *name, uint32_t len=0)
Assigns a name to entity. Ignored if used with pair. The string is NOT copied. Your are responsible f...
Definition world.h:360
EntityBuilder & add(Entity entity)
Prepares an archetype movement by following the "add" edge of the current archetype.
Definition world.h:390
EntityBuilder & del(Pair pair)
Prepares an archetype movement by following the "del" edge of the current archetype.
Definition world.h:473
Entity register_component()
Takes care of registering the component.
Definition world.h:433
EntityBuilder & del(Entity entity)
Prepares an archetype movement by following the "del" edge of the current archetype.
Definition world.h:463
Entity m_entity
Source entity.
Definition world.h:253
uint32_t m_rowSrc
Original row.
Definition world.h:247
EntityNameLookupKey m_targetNameKey
Target name.
Definition world.h:251
void name(const char *name, uint32_t len=0)
Assigns a name to entity. Ignored if used with pair. The string is copied and kept internally.
Definition world.h:345
Definition id.h:217
Definition ser_rt.h:13
Definition robin_hood.h:418