Gaia-ECS v1.0.0
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 <cctype>
5#include <cstdarg>
6#include <cstddef>
7#include <cstdint>
8#include <cstdio>
9#include <cstdlib>
10#include <cstring>
11#include <type_traits>
12
13#include "gaia/cnt/darray.h"
14#include "gaia/cnt/darray_ext.h"
15#include "gaia/cnt/map.h"
16#include "gaia/cnt/sarray_ext.h"
17#include "gaia/cnt/set.h"
18#include "gaia/config/profiler.h"
19#include "gaia/core/hashing_policy.h"
20#include "gaia/core/hashing_string.h"
21#include "gaia/core/span.h"
22#include "gaia/core/utility.h"
23#include "gaia/ecs/api.h"
24#include "gaia/ecs/archetype.h"
25#include "gaia/ecs/archetype_common.h"
26#include "gaia/ecs/archetype_graph.h"
27#include "gaia/ecs/chunk.h"
28#include "gaia/ecs/chunk_allocator.h"
29#include "gaia/ecs/chunk_header.h"
30#include "gaia/ecs/command_buffer_fwd.h"
31#include "gaia/ecs/common.h"
32#include "gaia/ecs/component.h"
33#include "gaia/ecs/component_cache.h"
34#include "gaia/ecs/component_cache_item.h"
35#include "gaia/ecs/component_cursor.h"
36#include "gaia/ecs/component_getter.h"
37#include "gaia/ecs/component_setter.h"
38#include "gaia/ecs/copy_scratch.h"
39#include "gaia/ecs/entity_container.h"
40#include "gaia/ecs/id.h"
41#include "gaia/ecs/nonfragmenting_relation_store.h"
42#include "gaia/ecs/observer.h"
43#include "gaia/ecs/observer_registry.h"
44#include "gaia/ecs/pair_lookup.h"
45#include "gaia/ecs/query.h"
46#include "gaia/ecs/query_cache.h"
47#include "gaia/ecs/query_common.h"
48#include "gaia/ecs/query_info.h"
49#include "gaia/ecs/relation_mutation_path.h"
50#include "gaia/ecs/sparse_component_store.h"
51#include "gaia/ecs/system.h"
52#include "gaia/ecs/system_schedule_scratch.h"
53#include "gaia/mem/mem_alloc.h"
54#include "gaia/ser/ser_binary.h"
55#if GAIA_JSON_ENABLED
56 #include "gaia/ser/ser_json.h"
57#endif
58#include "gaia/ser/ser_rt.h"
59#include "gaia/util/logging.h"
60#include "gaia/util/str.h"
61
62namespace gaia {
63 namespace ecs {
64#if GAIA_SYSTEMS_ENABLED
65 class SystemBuilder;
66#endif
67#if GAIA_OBSERVERS_ENABLED
68 class ObserverBuilder;
69 class ObserverRegistry;
70#endif
71 class World;
72
73 void world_notify_on_set_entity(World& world, Entity term, Entity entity);
74 template <typename T>
75 decltype(auto) world_direct_entity_arg_raw(World& world, Entity entity);
76 template <typename T>
77 decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id);
78
80 class GAIA_API World final {
81 public:
83 inline static bool s_enableUniqueNameDuplicateAssert = true;
84
85 private:
86 friend CommandBufferST;
87 friend CommandBufferMT;
88#if GAIA_OBSERVERS_ENABLED
89 friend class ObserverRegistry;
90 friend struct ObserverRuntimeData;
91 friend struct ObserverRegistry::DiffDispatcher;
92 friend struct ObserverRegistry::DirectDispatcher;
93 friend struct ObserverRegistry::SharedDispatch;
94#endif
95 friend struct ComponentGetter;
96 friend struct ComponentSetter;
97 friend void lock(World&);
98 friend void unlock(World&);
99 friend QueryMatchScratch& query_match_scratch_acquire(World&);
100 friend void query_match_scratch_release(World&, bool);
101 friend uint32_t world_component_index_bucket_size(const World&, Entity);
102 friend uint32_t world_component_index_comp_idx(const World&, const Archetype&, Entity);
103 friend uint32_t world_component_index_match_count(const World&, const Archetype&, Entity);
104 template <typename T>
105 friend decltype(auto) world_direct_entity_arg(World& world, Entity entity);
106 template <typename T>
107 friend decltype(auto) world_direct_entity_arg_raw(World& world, Entity entity);
108 template <typename T>
109 friend decltype(auto) world_query_entity_arg_by_id(World& world, Entity entity, Entity id);
110 template <typename T>
111 friend decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id);
112 friend void world_finish_write(World& world, Entity term, Entity entity);
113
114 ser::bin_stream m_stream;
115 ser::serializer m_serializer{};
116
117 using TFunc_Void_With_Entity = void(Entity);
120 static void func_void_with_entity([[maybe_unused]] Entity entity) {}
121
122 using EntityNameLookupKey = core::StringLookupKey<256>;
124 using EntityArrayMap = cnt::map<EntityLookupKey, cnt::darray<Entity>>;
125
126 using NonFragmentingRelationStore = detail::NonFragmentingRelationStore;
127 using SparseComponentStoreErased = detail::SparseComponentStoreErased;
128 using RuntimeSparseComponentStore = detail::RuntimeSparseComponentStore;
129 template <typename T>
130 using SparseComponentStore = detail::SparseComponentStore<T>;
131 using CopyIterGroupState = detail::CopyIterGroupState;
132 using PrefabChildEdge = detail::PrefabChildEdge;
133 using PrefabInstantiatePlanNode = detail::PrefabInstantiatePlanNode;
134
135 template <
136 typename TApi, typename TValue, bool DeriveFromValue = std::is_class_v<TValue> && !std::is_final_v<TValue>>
137 class SetWriteProxyTyped;
138
139 template <typename TApi, typename TValue>
140 class SetWriteProxyTyped<TApi, TValue, true>: public TValue {
141 World* m_pWorld = nullptr;
142 Entity m_entity = EntityBad;
143 Entity m_term = EntityBad;
144
146 void commit() {
147 if (m_pWorld == nullptr)
148 return;
149
150 m_pWorld->template write_back_set_typed<TApi, TValue>(m_entity, m_term, static_cast<const TValue&>(*this));
151 m_pWorld = nullptr;
152 }
153
154 public:
160 SetWriteProxyTyped(World& world, Entity entity, Entity term, const TValue& value):
161 TValue(value), m_pWorld(&world), m_entity(entity), m_term(term) {}
162
168 SetWriteProxyTyped(World& world, Entity entity, Entity term, TValue&& value):
169 TValue(GAIA_MOV(value)), m_pWorld(&world), m_entity(entity), m_term(term) {}
170
171 SetWriteProxyTyped(const SetWriteProxyTyped&) = delete;
172 SetWriteProxyTyped& operator=(const SetWriteProxyTyped&) = delete;
173
174 SetWriteProxyTyped(SetWriteProxyTyped&& other) noexcept:
175 TValue(static_cast<TValue&&>(other)), m_pWorld(other.m_pWorld), m_entity(other.m_entity),
176 m_term(other.m_term) {
177 other.m_pWorld = nullptr;
178 }
179
180 ~SetWriteProxyTyped() {
181 commit();
182 }
183
184 SetWriteProxyTyped& operator=(const TValue& value) {
185 static_cast<TValue&>(*this) = value;
186 return *this;
187 }
188
189 SetWriteProxyTyped& operator=(TValue&& value) {
190 static_cast<TValue&>(*this) = GAIA_MOV(value);
191 return *this;
192 }
193
196 GAIA_NODISCARD operator TValue&() {
197 return *this;
198 }
199
202 GAIA_NODISCARD operator const TValue&() const {
203 return *this;
204 }
205 };
206
207 template <typename TApi, typename TValue>
208 class SetWriteProxyTyped<TApi, TValue, false> {
209 World* m_pWorld = nullptr;
210 Entity m_entity = EntityBad;
211 Entity m_term = EntityBad;
212 TValue m_value{};
213
215 void commit() {
216 if (m_pWorld == nullptr)
217 return;
218
219 m_pWorld->template write_back_set_typed<TApi, TValue>(m_entity, m_term, m_value);
220 m_pWorld = nullptr;
221 }
222
223 public:
229 SetWriteProxyTyped(World& world, Entity entity, Entity term, const TValue& value):
230 m_pWorld(&world), m_entity(entity), m_term(term), m_value(value) {}
231
237 SetWriteProxyTyped(World& world, Entity entity, Entity term, TValue&& value):
238 m_pWorld(&world), m_entity(entity), m_term(term), m_value(GAIA_MOV(value)) {}
239
240 SetWriteProxyTyped(const SetWriteProxyTyped&) = delete;
241 SetWriteProxyTyped& operator=(const SetWriteProxyTyped&) = delete;
242
243 SetWriteProxyTyped(SetWriteProxyTyped&& other) noexcept:
244 m_pWorld(other.m_pWorld), m_entity(other.m_entity), m_term(other.m_term), m_value(GAIA_MOV(other.m_value)) {
245 other.m_pWorld = nullptr;
246 }
247
248 ~SetWriteProxyTyped() {
249 commit();
250 }
251
252 SetWriteProxyTyped& operator=(const TValue& value) {
253 m_value = value;
254 return *this;
255 }
256
257 SetWriteProxyTyped& operator=(TValue&& value) {
258 m_value = GAIA_MOV(value);
259 return *this;
260 }
261
264 GAIA_NODISCARD operator TValue&() {
265 return m_value;
266 }
267
270 GAIA_NODISCARD operator const TValue&() const {
271 return m_value;
272 }
273
276 GAIA_NODISCARD TValue* operator->() {
277 return &m_value;
278 }
279
282 GAIA_NODISCARD const TValue* operator->() const {
283 return &m_value;
284 }
285 };
286
287 template <typename TValue, bool DeriveFromValue = std::is_class_v<TValue> && !std::is_final_v<TValue>>
288 class SetWriteProxyObject;
289
290 template <typename TValue>
291 class SetWriteProxyObject<TValue, true>: public TValue {
292 World* m_pWorld = nullptr;
293 Entity m_entity = EntityBad;
294 Entity m_term = EntityBad;
295
297 void commit() {
298 if (m_pWorld == nullptr)
299 return;
300
301 m_pWorld->template write_back_set_object<TValue>(m_entity, m_term, static_cast<const TValue&>(*this));
302 m_pWorld = nullptr;
303 }
304
305 public:
311 SetWriteProxyObject(World& world, Entity entity, Entity term, const TValue& value):
312 TValue(value), m_pWorld(&world), m_entity(entity), m_term(term) {}
313
319 SetWriteProxyObject(World& world, Entity entity, Entity term, TValue&& value):
320 TValue(GAIA_MOV(value)), m_pWorld(&world), m_entity(entity), m_term(term) {}
321
322 SetWriteProxyObject(const SetWriteProxyObject&) = delete;
323 SetWriteProxyObject& operator=(const SetWriteProxyObject&) = delete;
324
325 SetWriteProxyObject(SetWriteProxyObject&& other) noexcept:
326 TValue(static_cast<TValue&&>(other)), m_pWorld(other.m_pWorld), m_entity(other.m_entity),
327 m_term(other.m_term) {
328 other.m_pWorld = nullptr;
329 }
330
331 ~SetWriteProxyObject() {
332 commit();
333 }
334
335 SetWriteProxyObject& operator=(const TValue& value) {
336 static_cast<TValue&>(*this) = value;
337 return *this;
338 }
339
340 SetWriteProxyObject& operator=(TValue&& value) {
341 static_cast<TValue&>(*this) = GAIA_MOV(value);
342 return *this;
343 }
344
347 GAIA_NODISCARD operator TValue&() {
348 return *this;
349 }
350
353 GAIA_NODISCARD operator const TValue&() const {
354 return *this;
355 }
356 };
357
358 template <typename TValue>
359 class SetWriteProxyObject<TValue, false> {
360 World* m_pWorld = nullptr;
361 Entity m_entity = EntityBad;
362 Entity m_term = EntityBad;
363 TValue m_value{};
364
366 void commit() {
367 if (m_pWorld == nullptr)
368 return;
369
370 m_pWorld->template write_back_set_object<TValue>(m_entity, m_term, m_value);
371 m_pWorld = nullptr;
372 }
373
374 public:
380 SetWriteProxyObject(World& world, Entity entity, Entity term, const TValue& value):
381 m_pWorld(&world), m_entity(entity), m_term(term), m_value(value) {}
382
388 SetWriteProxyObject(World& world, Entity entity, Entity term, TValue&& value):
389 m_pWorld(&world), m_entity(entity), m_term(term), m_value(GAIA_MOV(value)) {}
390
391 SetWriteProxyObject(const SetWriteProxyObject&) = delete;
392 SetWriteProxyObject& operator=(const SetWriteProxyObject&) = delete;
393
394 SetWriteProxyObject(SetWriteProxyObject&& other) noexcept:
395 m_pWorld(other.m_pWorld), m_entity(other.m_entity), m_term(other.m_term), m_value(GAIA_MOV(other.m_value)) {
396 other.m_pWorld = nullptr;
397 }
398
399 ~SetWriteProxyObject() {
400 commit();
401 }
402
403 SetWriteProxyObject& operator=(const TValue& value) {
404 m_value = value;
405 return *this;
406 }
407
408 SetWriteProxyObject& operator=(TValue&& value) {
409 m_value = GAIA_MOV(value);
410 return *this;
411 }
412
415 GAIA_NODISCARD operator TValue&() {
416 return m_value;
417 }
418
421 GAIA_NODISCARD operator const TValue&() const {
422 return m_value;
423 }
424
427 GAIA_NODISCARD TValue* operator->() {
428 return &m_value;
429 }
430
433 GAIA_NODISCARD const TValue* operator->() const {
434 return &m_value;
435 }
436 };
437
442 template <typename T>
443 GAIA_NODISCARD decltype(auto) mut_im(Entity entity) {
444 static_assert(!is_pair<T>::value);
445 using FT = typename component_type_t<T>::TypeFull;
446 const auto& item = add<FT>();
447 if constexpr (uses_compile_time_sparse_storage<FT>())
448 return sparse_component_store_mut<FT>(item.entity).mut(entity);
449
450 const auto& ec = m_recs.entities[entity.id()];
451 if constexpr (entity_kind_v<T> == EntityKind::EK_Gen)
452 return ec.pChunk->template set<T>(ec.row);
453 else
454 return ec.pChunk->template set<T>();
455 }
456
462 GAIA_NODISCARD const ComponentCacheItem* component_item(Entity entity, Entity component) const {
463 if (!component.pair())
464 return comp_cache().find(component);
465
466 const auto& ec = fetch(entity);
467 const auto compIdx = core::get_index(ec.pChunk->ids_view(), component);
468 return compIdx != BadIndex ? ec.pChunk->comp_rec_view()[compIdx].pItem : nullptr;
469 }
470
474 GAIA_NODISCARD static bool raw_component_supported(const ComponentCacheItem& item) noexcept {
475 return item.comp.soa() == 0;
476 }
477
481 GAIA_NODISCARD static bool soa_field_supported(const ComponentCacheItem& item) noexcept {
482 return item.comp.soa() != 0 && item.comp.storage_type() == DataStorageType::Table;
483 }
484
490 GAIA_NODISCARD static bool
491 raw_component_payload_args_valid(const ComponentCacheItem& item, const void* data, uint32_t size) noexcept {
492 if (!raw_component_supported(item))
493 return false;
494 if (size != item.comp.size())
495 return false;
496 return size == 0 || data != nullptr;
497 }
498
504 template <typename T>
505 GAIA_NODISCARD decltype(auto) mut_im(Entity entity, Entity object) {
506 static_assert(!is_pair<T>::value);
507 using FT = typename component_type_t<T>::TypeFull;
508 if constexpr (supports_sparse_component_storage<FT>()) {
509 if (can_use_sparse_component_storage<FT>(object))
510 return sparse_component_mut_value<FT>(object, entity);
511 }
512
513 const auto& ec = m_recs.entities[entity.id()];
514 return ec.pChunk->template set<T>(ec.row, object);
515 }
516
522 void finish_write(Entity entity, Entity term) {
523 if (tearing_down() || !valid(entity))
524 return;
525
526 if (sparse_storage_mode(term) != SparseStorageMode::None) {
527 world_notify_on_set_entity(*this, term, entity);
528 return;
529 }
530
531 auto compIdx = uint32_t(BadIndex);
532 {
533 const auto& ec = fetch(entity);
534 compIdx = world_component_index_comp_idx(*this, *ec.pArchetype, term);
535 }
536
537 if (compIdx == BadIndex) {
538 (void) override(entity, term);
539 const auto& ec = fetch(entity);
540 compIdx = world_component_index_comp_idx(*this, *ec.pArchetype, term);
541 if (compIdx == BadIndex)
542 return;
543 }
544
545 const auto& ec = fetch(entity);
546 const auto row = uint16_t(ec.row * (1U - (uint32_t)term.kind()));
547 (void)ec.pChunk->comp_ptr_mut_gen<true>(compIdx, row);
548 world_notify_on_set_entity(*this, term, entity);
549 }
550
557 template <typename TApi, typename TValue>
558 void write_back_set_typed(Entity entity, Entity term, const TValue& value) {
559 using FT = typename component_type_t<TApi>::TypeFull;
560 ::gaia::ecs::update_version(m_worldVersion);
561
562 if constexpr (uses_compile_time_sparse_storage<FT>()) {
563 sparse_component_store_mut<FT>(term).add(entity) = value;
564 world_notify_on_set_entity(*this, term, entity);
565 return;
566 }
567
568 const auto& ec = fetch(entity);
569 const auto row = uint16_t(ec.row * (1U - (uint32_t)term.kind()));
570 ComponentSetter{*this, ec.pChunk, entity, row}.sset<TApi>(value);
571 finish_write(entity, term);
572 }
573
579 template <typename TValue>
580 void write_back_set_object(Entity entity, Entity term, const TValue& value) {
581 using FT = typename component_type_t<TValue>::TypeFull;
582 ::gaia::ecs::update_version(m_worldVersion);
583 if constexpr (supports_sparse_component_storage<FT>()) {
584 if (can_use_sparse_component_storage<FT>(term)) {
585 sparse_component_add_value<FT>(term, entity) = value;
586 finish_write(entity, term);
587 return;
588 }
589 }
590
591 const auto& ec = fetch(entity);
592 const auto row = uint16_t(ec.row * (1U - (uint32_t)term.kind()));
593 ComponentSetter{*this, ec.pChunk, entity, row}.template smut<TValue>(term) = value;
594 finish_write(entity, term);
595 }
596
597 //----------------------------------------------------------------------
598
599 //----------------------------------------------------------------------
600
602 ComponentCache m_compCache;
604 QueryCache m_queryCache;
607 cnt::darray<QueryMatchScratch*> m_queryMatchScratchStack;
609 uint32_t m_queryMatchScratchDepth = 0;
615 QuerySerMap m_querySerMap;
616 uint32_t m_nextQuerySerId = 0;
617
618#if GAIA_OBSERVERS_ENABLED && GAIA_ASSERT_ENABLED
620 uint32_t m_observerCallbackDepth = 0;
621#endif
622
624 EntityToArchetypeMap m_entityToArchetypeMap;
626 EntityToArchetypeVersionMap m_entityToArchetypeMapVersions;
635 PairMap m_entityToAsTargets;
638 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_entityToAsTargetsTravCache;
646 PairMap m_entityToAsRelations;
649 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_entityToAsRelationsTravCache;
652 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_targetsTravCache;
655 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_srcBfsTravCache;
658 mutable cnt::map<EntityLookupKey, uint32_t> m_depthOrderCache;
661 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_sourcesAllCache;
664 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_targetsAllCache;
666 mutable bool m_relationCachesPopulated = false;
668 bool m_hasOnDeleteTargetPolicy = false;
670 bool m_hasCantCombinePolicy = false;
672 bool m_hasRequiresPolicy = false;
674 PairLookup m_pairLookup;
676 cnt::map<EntityLookupKey, NonFragmentingRelationStore> m_nonFragmentingRelationsByRel;
678 cnt::map<EntityLookupKey, SparseComponentStoreErased> m_sparseComponentsByComp;
680 cnt::map<EntityLookupKey, uint32_t> m_relationVersions;
682 Entity m_lastRelationVersionRelation = EntityBad;
684 uint32_t* m_pLastRelationVersion = nullptr;
687 mutable cnt::map<EntityLookupKey, uint32_t> m_srcEntityVersions;
688
689 enum class SparseStorageMode : uint8_t { None, Fragmenting, NonFragmenting };
690
692 ArchetypeDArray m_archetypes;
694 ArchetypeMapByHash m_archetypesByHash;
696 ArchetypeMapById m_archetypesById;
697
699 Archetype* m_pRootArchetype = nullptr;
701 Archetype* m_pEntityArchetype = nullptr;
703 Archetype* m_pCompArchetype = nullptr;
705 ArchetypeId m_nextArchetypeId = 0;
706
708 uint32_t m_emptySpace1 = 0;
709
711 EntityContainers m_recs;
713 cnt::map<EntityNameLookupKey, Entity> m_nameToEntity;
715 cnt::map<EntityNameLookupKey, Entity> m_aliasToEntity;
717 Entity m_componentScope = EntityBad;
719 cnt::darray<Entity> m_componentLookupPath;
721 mutable util::str m_componentScopePathCache;
723 mutable Entity m_componentScopePathCacheEntity = EntityBad;
725 mutable bool m_componentScopePathCacheValid = false;
726
728 cnt::set<ArchetypeLookupKey> m_reqArchetypesToDel;
730 cnt::set<EntityLookupKey> m_reqEntitiesToDel;
731
732#if GAIA_OBSERVERS_ENABLED
734 ObserverRegistry m_observers;
735
737 struct DeferredOnSet {
739 Entity term;
741 Entity entity;
742 };
743
746 cnt::darray<cnt::darray<DeferredOnSet>> m_deferredOnSet;
748 uint32_t m_deferOnSetDepth = 0;
749#endif
750
758 struct DeferredSortInv {
760 Entity entity;
761 };
762
765 cnt::darray<cnt::darray<DeferredSortInv>> m_deferredSortInv;
767 uint32_t m_deferSortInvDepth = 0;
768
769#if GAIA_SYSTEMS_ENABLED
771 SystemRegistry m_systems;
772#endif
773
775 CommandBufferST* m_pCmdBufferST;
777 CommandBufferMT* m_pCmdBufferMT;
779 bool m_teardownActive = false;
781 Query m_systemsQuery;
783 detail::SystemScheduleScratch m_systemScheduleScratch;
785 Sched m_sched{};
787 mutable cnt::darray<uint64_t> m_entityVisitStamps;
789 mutable uint64_t m_entityVisitStamp = 0;
790
791#if GAIA_OBSERVERS_ENABLED
793 cnt::darray_ext<Entity, 16> m_entitiesDeleting;
794#endif
796 cnt::darray<ArchetypeChunkPair> m_chunksToDel;
798 ArchetypeDArray m_archetypesToDel;
800 uint32_t m_defragLastArchetypeIdx = 0;
802 uint32_t m_defragEntitiesPerTick = 100;
803
805 uint32_t m_worldVersion = 0;
807 uint32_t m_enabledHierarchyVersion = 0;
809 uint32_t m_archetypeDeleteVersion = 0;
810
811 uint32_t m_structuralChangesLocked = 0;
812
813 public:
814 World():
815 // Command buffer for the main thread
816 m_pCmdBufferST(cmd_buffer_st_create(*this)),
817 // Command buffer safe for concurrent access
818 m_pCmdBufferMT(cmd_buffer_mt_create(*this)) {
819 init();
820 }
821
822 ~World() {
823 teardown();
824 done();
825 cmd_buffer_destroy(*m_pCmdBufferST);
826 cmd_buffer_destroy(*m_pCmdBufferMT);
827 }
828
829 World(World&&) = delete;
830 World(const World&) = delete;
831 World& operator=(World&&) = delete;
832 World& operator=(const World&) = delete;
833
834 //----------------------------------------------------------------------
835
840 return Query(
841 *const_cast<World*>(this), m_queryCache,
842 //
843 m_nextArchetypeId, m_worldVersion, m_entityToArchetypeMap, m_entityToArchetypeMapVersions, m_archetypes);
844 }
845
850 auto q = query();
851 q.kind(QueryCacheKind::None);
852 return q;
853 }
854
855#if GAIA_ECS_TEST_HOOKS
857 GAIA_NODISCARD bool verify_query_cache() const {
858 return m_queryCache.verify_archetype_tracking();
859 }
860
863 GAIA_NODISCARD uint32_t test_query_cache_count() const {
864 return m_queryCache.test_query_count();
865 }
866#endif
867
868 //----------------------------------------------------------------------
869
872 void set_sched(const Sched& sched) {
873 m_sched = sched;
874 }
875
877 void reset_sched() {
878 m_sched = {};
879 }
880
883 GAIA_NODISCARD const Sched& sched() const {
884 return sched_resolve(m_sched);
885 }
886
887 //----------------------------------------------------------------------
888
892 GAIA_NODISCARD EntityContainer& fetch(Entity entity) {
893#if GAIA_ASSERT_ENABLED
894 // Wildcard pairs are not a real entity so we can't accept them
895 GAIA_ASSERT(!entity.pair() || !is_wildcard(entity));
896 if (!valid(entity)) {
897 // Delete-time cleanup can still reference an exact pair record after one endpoint
898 // has already become invalid. Delete-time observer diffs can also still reference
899 // a delete-requested, queued-for-delete, or stale removed entity record before it
900 // is invalidated. Allow these forms so cleanup and observer dispatch can finish.
901 const bool allowStaleExactPair = entity.pair() && m_recs.pair_record_contains(entity);
902 const bool allowDeleteRequestedEntity =
903 !entity.pair() && entity.id() < m_recs.entities.size() &&
904 m_recs.entities[entity.id()].data.gen == entity.gen() &&
905 (m_recs.entities[entity.id()].flags & EntityContainerFlags::DeleteRequested) != 0;
906 const bool allowQueuedDeleteEntity = !entity.pair() && m_reqEntitiesToDel.contains(EntityLookupKey(entity));
907 const bool allowStaleEntityRecord = !entity.pair() && entity.id() < m_recs.entities.size() &&
908 m_recs.entities[entity.id()].data.gen == entity.gen() &&
909 m_recs.entities[entity.id()].pArchetype != nullptr &&
910 m_recs.entities[entity.id()].pChunk != nullptr;
911 GAIA_ASSERT(
912 allowStaleExactPair || allowDeleteRequestedEntity || allowQueuedDeleteEntity || allowStaleEntityRecord);
913 }
914#endif
915 return m_recs[entity];
916 }
917
921 GAIA_NODISCARD const EntityContainer& fetch(Entity entity) const {
922#if GAIA_ASSERT_ENABLED
923 // Wildcard pairs are not a real entity so we can't accept them
924 GAIA_ASSERT(!entity.pair() || !is_wildcard(entity));
925 if (!valid(entity)) {
926 // Delete-time cleanup can still reference an exact pair record after one endpoint
927 // has already become invalid. Delete-time observer diffs can also still reference
928 // a delete-requested, queued-for-delete, or stale removed entity record before it
929 // is invalidated. Allow these forms so cleanup and observer dispatch can finish.
930 const bool allowStaleExactPair = entity.pair() && m_recs.pair_record_contains(entity);
931 const bool allowDeleteRequestedEntity =
932 !entity.pair() && entity.id() < m_recs.entities.size() &&
933 m_recs.entities[entity.id()].data.gen == entity.gen() &&
934 (m_recs.entities[entity.id()].flags & EntityContainerFlags::DeleteRequested) != 0;
935 const bool allowQueuedDeleteEntity = !entity.pair() && m_reqEntitiesToDel.contains(EntityLookupKey(entity));
936 const bool allowStaleEntityRecord = !entity.pair() && entity.id() < m_recs.entities.size() &&
937 m_recs.entities[entity.id()].data.gen == entity.gen() &&
938 m_recs.entities[entity.id()].pArchetype != nullptr &&
939 m_recs.entities[entity.id()].pChunk != nullptr;
940 GAIA_ASSERT(
941 allowStaleExactPair || allowDeleteRequestedEntity || allowQueuedDeleteEntity || allowStaleEntityRecord);
942 }
943#endif
944 return m_recs[entity];
945 }
946
947 //----------------------------------------------------------------------
948
953 GAIA_NODISCARD static bool is_req_del(const EntityContainer& ec) {
954 if ((ec.flags & EntityContainerFlags::DeleteRequested) != 0)
955 return true;
956 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) == 0);
957 return ec.pArchetype != nullptr && ec.pArchetype->is_req_del();
958 }
959
960#if GAIA_OBSERVERS_ENABLED
961 #if GAIA_ASSERT_ENABLED
963 void observer_callback_enter() {
964 ++m_observerCallbackDepth;
965 }
966
968 void observer_callback_leave() {
969 GAIA_ASSERT(m_observerCallbackDepth != 0);
970 --m_observerCallbackDepth;
971 }
972
974 GAIA_NODISCARD bool observer_callback_active() const {
975 return m_observerCallbackDepth != 0;
976 }
977 #endif
978
982 GAIA_NODISCARD bool entity_deletion_active(Entity entity) const {
983 for (auto deleting: m_entitiesDeleting) {
984 if (deleting == entity)
985 return true;
986 }
987 return false;
988 }
989
992 void entity_deletion_enter(Entity entity) {
993 GAIA_ASSERT(!entity_deletion_active(entity));
994 m_entitiesDeleting.push_back(entity);
995 }
996
999 void entity_deletion_leave(Entity entity) {
1000 GAIA_ASSERT(!m_entitiesDeleting.empty());
1001 GAIA_ASSERT(m_entitiesDeleting.back() == entity);
1002 m_entitiesDeleting.pop_back();
1003 }
1004#endif
1005
1009 GAIA_NODISCARD bool is_dont_fragment(Entity entity) const {
1010 return (fetch(entity).flags & EntityContainerFlags::IsDontFragment) != 0;
1011 }
1012
1016 GAIA_NODISCARD bool relation_is_non_fragmenting(Entity relation) const {
1017 return valid(relation) && !relation.pair() && is_dont_fragment(relation);
1018 }
1019
1024 GAIA_NODISCARD bool relation_uses_non_fragmenting_storage(Entity relation) const {
1025 if (!valid(relation) || relation.pair())
1026 return false;
1027
1028 const auto& ec = fetch(relation);
1029 return (ec.flags & EntityContainerFlags::IsExclusive) != 0 &&
1030 (ec.flags & EntityContainerFlags::IsDontFragment) != 0;
1031 }
1032
1037 GAIA_NODISCARD bool relation_is_hierarchy(Entity relation) const {
1038 if (!valid(relation) || relation.pair())
1039 return false;
1040
1041 return has(relation, Exclusive) && has(relation, Traversable);
1042 }
1043
1048 GAIA_NODISCARD bool relation_is_fragmenting(Entity relation) const {
1049 return valid(relation) && !relation.pair() && !is_dont_fragment(relation);
1050 }
1051
1056 GAIA_NODISCARD bool relation_is_fragmenting_hierarchy(Entity relation) const {
1057 return relation_is_hierarchy(relation) && relation_is_fragmenting(relation);
1058 }
1059
1066 GAIA_NODISCARD bool relation_supports_depth_order(Entity relation) const {
1067 return relation_is_fragmenting(relation);
1068 }
1069
1075 GAIA_NODISCARD bool relation_depth_order_prunes_disabled_subtrees(Entity relation) const {
1076 return relation_is_fragmenting_hierarchy(relation);
1077 }
1078
1083 GAIA_NODISCARD bool component_uses_sparse_storage(Entity component) const {
1084 if (!valid(component) || component.pair() || component.entity())
1085 return false;
1086
1087 const auto* pItem = comp_cache().find(component);
1088 if (pItem == nullptr || component.kind() != EntityKind::EK_Gen || pItem->comp.soa() != 0)
1089 return false;
1090
1091 return gaia::ecs::component_uses_sparse_storage(pItem->comp);
1092 }
1093
1098 GAIA_NODISCARD bool component_is_non_fragmenting(Entity component) const {
1099 if (!component_uses_sparse_storage(component))
1100 return false;
1101
1102 return (fetch(component).flags & EntityContainerFlags::IsDontFragment) != 0;
1103 }
1104
1108 GAIA_NODISCARD SparseStorageMode sparse_storage_mode(Entity component) const {
1109 if (!valid(component) || component.pair() || component.entity())
1110 return SparseStorageMode::None;
1111
1112 const auto* pItem = comp_cache().find(component);
1113 if (pItem == nullptr || component.kind() != EntityKind::EK_Gen || pItem->comp.soa() != 0 ||
1114 !gaia::ecs::component_uses_sparse_storage(pItem->comp))
1115 return SparseStorageMode::None;
1116
1117 if ((fetch(component).flags & EntityContainerFlags::IsDontFragment) != 0)
1118 return SparseStorageMode::NonFragmenting;
1119
1120 return SparseStorageMode::Fragmenting;
1121 }
1122
1128 GAIA_NODISCARD bool
1129 copies_sparse_payload_inter(Entity comp, Entity srcEntity, const SparseComponentStoreErased& store) const {
1130 return store.func_has(store.pStore, srcEntity) && sparse_storage_mode(comp) != SparseStorageMode::None;
1131 }
1132
1139 Entity comp, Entity srcEntity, const SparseComponentStoreErased& store) const {
1140 return copies_sparse_payload_inter(comp, srcEntity, store) &&
1141 sparse_storage_mode(comp) == SparseStorageMode::NonFragmenting;
1142 }
1143
1147 GAIA_NODISCARD bool sparse_copy_adds_id_inter(Entity comp) const {
1148 return sparse_storage_mode(comp) == SparseStorageMode::NonFragmenting;
1149 }
1150
1151 //----------------------------------------------------------------------
1152
1157 auto& item = comp_cache_mut().get(component);
1158 item.comp = comp;
1159
1160 auto& ec = m_recs.entities[component.id()];
1161 if (ec.pArchetype == nullptr || ec.pChunk == nullptr)
1162 return;
1163
1164 const auto compIdx = core::get_index(ec.pArchetype->ids_view(), GAIA_ID(Component));
1165 if (compIdx == BadIndex)
1166 return;
1167
1168 auto* pComp = reinterpret_cast<Component*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
1169 *pComp = comp;
1170 }
1171
1176 void validate_runtime_semantics(const RuntimeTypeDesc& runtimeType) const {
1177#if GAIA_ASSERT_ENABLED
1178 auto validate = [&](Entity semantic) {
1179 if (semantic == EntityBad)
1180 return;
1181 util::str path;
1182 GAIA_ASSERT(build_scope_path(semantic, path));
1183 };
1184 validate(runtimeType.semantic);
1185 GAIA_FOR(runtimeType.fieldCount)
1186 validate(runtimeType.fields[i].semantic);
1187#else
1188 (void)runtimeType;
1189#endif
1190 }
1191
1192#if GAIA_JSON_ENABLED
1198 bool write_runtime_schema_json(ser::ser_json& writer, const char* schemaHash, bool includeRuntimeEntities) const;
1199#endif
1200
1206 void finalize_component_registration(const ComponentCacheItem& item, bool addSparseTrait) {
1207 sync_component_record(item.entity, item.comp);
1208 const auto symbol = item.symbol_name();
1209 name_raw(item.entity, symbol.data(), symbol.size());
1210 if (addSparseTrait && item.comp.storage_type() == DataStorageType::Sparse)
1211 add(item.entity, Sparse);
1212 }
1213
1218 void set_component_dont_fragment(Entity component, EntityContainer& ec) {
1219 if (component.comp())
1220 set_component_sparse_storage(component);
1221
1222 if ((ec.flags & EntityContainerFlags::IsDontFragment) != 0)
1223 return;
1224
1225 ec.flags |= EntityContainerFlags::IsDontFragment;
1226 }
1227
1233 GAIA_ASSERT(valid(component));
1234 GAIA_ASSERT(component.comp());
1235 GAIA_ASSERT(!component.pair());
1236 GAIA_ASSERT(!component.entity());
1237 GAIA_ASSERT(component.kind() == EntityKind::EK_Gen);
1238
1239 const auto& item = comp_cache().get(component);
1240 GAIA_ASSERT(item.entity == component);
1241
1242 if (item.comp.storage_type() == DataStorageType::Sparse)
1243 return;
1244
1245 GAIA_ASSERT(item.comp.soa() == 0);
1246 if (item.comp.soa() != 0)
1247 return;
1248
1249 const auto directTermEntityCnt = count_direct_term_entities_direct(component);
1250 GAIA_ASSERT(directTermEntityCnt == 0);
1251 if (directTermEntityCnt != 0)
1252 return;
1253
1254 auto comp = item.comp;
1255 comp.data.storage = (IdentifierData)DataStorageType::Sparse;
1256 sync_component_record(component, comp);
1257 }
1258
1263 GAIA_NODISCARD bool can_add_component_storage_trait(Entity component) const {
1264 if (!component.comp())
1265 return true;
1266
1267 const auto& item = comp_cache().get(component);
1268 return item.func_create_sparse_store == nullptr || item.comp.storage_type() == DataStorageType::Sparse;
1269 }
1270
1275 template <typename T>
1276 GAIA_NODISCARD static constexpr bool supports_sparse_component_storage() {
1277 using U = typename actual_type_t<T>::Type;
1278 return !is_pair<T>::value && entity_kind_v<T> == EntityKind::EK_Gen && !mem::is_soa_layout_v<U>;
1279 }
1280
1284 template <typename T>
1285 GAIA_NODISCARD static constexpr bool uses_compile_time_sparse_storage() {
1286 using U = typename actual_type_t<T>::Type;
1287 return auto_storage_policy_v<U> == DataStorageType::Sparse;
1288 }
1289
1293 GAIA_NODISCARD SparseStorageMode compile_time_sparse_storage_mode(Entity component) const {
1294 return is_dont_fragment(component) ? SparseStorageMode::NonFragmenting : SparseStorageMode::Fragmenting;
1295 }
1296
1301 template <typename T>
1302 GAIA_NODISCARD bool can_use_sparse_component_storage(Entity object) const {
1303 if constexpr (!supports_sparse_component_storage<T>())
1304 return false;
1305 else {
1306 if (!valid(object) || object.pair())
1307 return false;
1308
1309 const auto* pItem = comp_cache().find(object);
1310 if (pItem == nullptr || pItem->entity != object || !gaia::ecs::component_uses_sparse_storage(pItem->comp))
1311 return false;
1312
1313 using U = typename actual_type_t<T>::Type;
1314 return pItem->comp.size() == (uint32_t)sizeof(U) && pItem->comp.alig() == (uint32_t)alignof(U) &&
1315 pItem->comp.soa() == 0 && object.kind() == entity_kind_v<T>;
1316 }
1317 }
1318
1323 template <typename T>
1324 GAIA_NODISCARD SparseComponentStore<T>* sparse_component_store(Entity component) {
1325 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1326 if (it == m_sparseComponentsByComp.end())
1327 return nullptr;
1328
1329 return static_cast<SparseComponentStore<T>*>(it->second.pStore);
1330 }
1331
1336 template <typename T>
1337 GAIA_NODISCARD const SparseComponentStore<T>* sparse_component_store(Entity component) const {
1338 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1339 if (it == m_sparseComponentsByComp.end())
1340 return nullptr;
1341
1342 return static_cast<const SparseComponentStore<T>*>(it->second.pStore);
1343 }
1344
1349 template <typename T>
1350 GAIA_NODISCARD SparseComponentStore<T>& sparse_component_store_mut(Entity component) {
1351 const auto key = EntityLookupKey(component);
1352 const auto it = m_sparseComponentsByComp.find(key);
1353 if (it != m_sparseComponentsByComp.end())
1354 return *static_cast<SparseComponentStore<T>*>(it->second.pStore);
1355
1356 auto* pStore = new SparseComponentStore<T>();
1357 m_sparseComponentsByComp.emplace(key, make_sparse_component_store_erased(pStore));
1358 return *pStore;
1359 }
1360
1366 template <typename T>
1367 GAIA_NODISCARD decltype(auto) sparse_component_add_value(Entity component, Entity entity) {
1368 using U = typename actual_type_t<T>::Type;
1369 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1370 if (it == m_sparseComponentsByComp.end()) {
1371 const auto* pItem = comp_cache().find(component);
1372 GAIA_ASSERT(pItem != nullptr);
1373 auto& store = sparse_component_store_erased_mut(component, *pItem);
1374 return *(U*)store.func_add(store.pStore, entity);
1375 }
1376 return *(U*)it->second.func_add(it->second.pStore, entity);
1377 }
1378
1384 template <typename T>
1385 GAIA_NODISCARD decltype(auto) sparse_component_mut_value(Entity component, Entity entity) {
1386 using U = typename actual_type_t<T>::Type;
1387 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1388 GAIA_ASSERT(it != m_sparseComponentsByComp.end());
1389 return *(U*)it->second.func_mut(it->second.pStore, entity);
1390 }
1391
1397 template <typename T>
1398 GAIA_NODISCARD decltype(auto) sparse_component_get_value(Entity component, Entity entity) const {
1399 using U = typename actual_type_t<T>::Type;
1400 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1401 GAIA_ASSERT(it != m_sparseComponentsByComp.end());
1402 return *(const U*)it->second.func_get(it->second.pStore, entity);
1403 }
1404
1408 GAIA_NODISCARD const SparseComponentStoreErased* sparse_component_store_erased(Entity component) const {
1409 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1410 return it != m_sparseComponentsByComp.end() ? &it->second : nullptr;
1411 }
1412
1417 GAIA_NODISCARD SparseComponentStoreErased&
1418 sparse_component_store_erased_mut(Entity component, const ComponentCacheItem& item) {
1419 const auto key = EntityLookupKey(component);
1420 const auto it = m_sparseComponentsByComp.find(key);
1421 if (it != m_sparseComponentsByComp.end())
1422 return it->second;
1423 if (item.func_create_sparse_store != nullptr) {
1424 item.func_create_sparse_store(*this, component);
1425 return m_sparseComponentsByComp.find(key)->second;
1426 }
1427
1428 auto* pStore = new RuntimeSparseComponentStore(item);
1429 return m_sparseComponentsByComp.emplace(key, make_sparse_component_store_erased(pStore)).first->second;
1430 }
1431
1437 void finish_sparse_component_add_inter(Entity entity, Entity object, SparseStorageMode mode) {
1438 GAIA_ASSERT(mode != SparseStorageMode::None);
1439
1440#if GAIA_OBSERVERS_ENABLED
1441 auto ctx =
1442 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
1443#endif
1444 if (mode == SparseStorageMode::Fragmenting) {
1445 GAIA_ASSERT(!locked());
1446 EntityBuilder eb(*this, entity);
1447 eb.add_inter_init(object);
1448 eb.commit();
1449 }
1450
1451 notify_add_single(entity, object);
1452#if GAIA_OBSERVERS_ENABLED
1453 m_observers.finish_diff(*this, GAIA_MOV(ctx));
1454#endif
1455 }
1456
1460 for (auto& [compKey, store]: m_sparseComponentsByComp) {
1461 (void)compKey;
1462 store.func_del(store.pStore, entity);
1463 }
1464 }
1465
1469 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1470 if (it == m_sparseComponentsByComp.end())
1471 return;
1472
1473 it->second.func_clear_store(it->second.pStore);
1474 it->second.func_del_store(it->second.pStore);
1475 m_sparseComponentsByComp.erase(it);
1476 }
1477
1482 const auto it = m_nonFragmentingRelationsByRel.find(EntityLookupKey(relation));
1483 if (it == m_nonFragmentingRelationsByRel.end())
1484 return nullptr;
1485
1486 return &it->second;
1487 }
1488
1493 return m_nonFragmentingRelationsByRel[EntityLookupKey(relation)];
1494 }
1495
1501 void
1503 GAIA_ASSERT(relation_uses_non_fragmenting_storage(relation));
1504 if (store.set(source, target))
1505 invalidate_relation_caches(relation);
1506 }
1507
1512 void nonfragmenting_relation_set(Entity source, Entity relation, Entity target) {
1513 auto& store = nonfragmenting_relation_store_mut(relation);
1514 nonfragmenting_relation_set(store, source, relation, target);
1515 }
1516
1522 bool nonfragmenting_relation_del(Entity source, Entity relation, Entity target) {
1523 const auto itStore = m_nonFragmentingRelationsByRel.find(EntityLookupKey(relation));
1524 if (itStore == m_nonFragmentingRelationsByRel.end())
1525 return false;
1526
1527 auto& store = itStore->second;
1528 if (!store.remove(source, target))
1529 return false;
1530
1531 if (store.empty())
1532 m_nonFragmentingRelationsByRel.erase(itStore);
1533
1534 invalidate_relation_caches(relation);
1535
1536 return true;
1537 }
1538
1543 GAIA_NODISCARD bool has_nonfragmenting_relation_pair(Entity source, Entity object) const {
1544 if (!object.pair())
1545 return false;
1546
1547 const auto relId = object.id();
1548 const auto tgtId = object.gen();
1549
1550 if (relId != All.id()) {
1551 const auto relation = get(relId);
1552 if (!relation_uses_non_fragmenting_storage(relation))
1553 return false;
1554
1555 const auto* pStore = nonfragmenting_relation_store(relation);
1556 if (pStore == nullptr)
1557 return false;
1558
1559 const auto target = pStore->target(source);
1560 if (target == EntityBad)
1561 return false;
1562
1563 return tgtId == All.id() || target.id() == tgtId;
1564 }
1565
1566 if (tgtId == All.id()) {
1567 for (const auto& it: m_nonFragmentingRelationsByRel) {
1568 if (it.second.target(source) != EntityBad)
1569 return true;
1570 }
1571 return false;
1572 }
1573
1574 const auto target = get(tgtId);
1575 for (const auto& it: m_nonFragmentingRelationsByRel) {
1576 if (it.second.target(source) == target)
1577 return true;
1578 }
1579
1580 return false;
1581 }
1582
1586 cnt::darray<Entity> relations;
1587 for (const auto& it: m_nonFragmentingRelationsByRel) {
1588 const auto relation = it.first.entity();
1589 if (it.second.target(source) != EntityBad)
1590 relations.push_back(relation);
1591 }
1592 if (relations.empty())
1593 return;
1594
1595 for (auto relation: relations) {
1596 touch_rel_version(relation);
1597 invalidate_queries_for_rel(relation);
1598 (void)nonfragmenting_relation_del(source, relation, EntityBad);
1599 }
1600 clear_relation_caches();
1601 }
1602
1603#if GAIA_OBSERVERS_ENABLED
1606 void del_nonfragmenting_relation_source_observed(Entity source) {
1607 if (!m_observers.has_on_del_observers())
1608 return;
1609
1610 cnt::darray<Entity> pairs;
1611 for (const auto& it: m_nonFragmentingRelationsByRel) {
1612 const auto relation = it.first.entity();
1613 const auto target = it.second.target(source);
1614 if (target != EntityBad)
1615 pairs.push_back(Pair(relation, target));
1616 }
1617 if (pairs.empty())
1618 return;
1619
1620 const auto& ec = fetch(source);
1621 for (auto pair: pairs) {
1622 const auto relation = try_get(pair.id());
1623 const auto target = try_get(pair.gen());
1624 if (relation == EntityBad || target == EntityBad)
1625 continue;
1626
1627 const Entity object = Pair(relation, target);
1628 auto delDiffCtx =
1629 m_observers.prepare_diff(*this, ObserverEvent::OnDel, EntitySpan{&object, 1}, EntitySpan{&source, 1});
1630 if (nonfragmenting_relation_del(source, relation, target))
1631 m_observers.on_del(*this, *ec.pArchetype, EntitySpan{&object, 1}, EntitySpan{&source, 1});
1632 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
1633 }
1634 }
1635#endif
1636
1640 const auto itStore = m_nonFragmentingRelationsByRel.find(EntityLookupKey(relation));
1641 if (itStore == m_nonFragmentingRelationsByRel.end())
1642 return;
1643
1644 cnt::darray<EntityId> sourceIds;
1645 itStore->second.collect_source_ids(sourceIds);
1646
1647 touch_rel_version(relation);
1648 invalidate_queries_for_rel(relation);
1649 for (auto sourceId: sourceIds)
1650 (void)nonfragmenting_relation_del(get(sourceId), relation, EntityBad);
1651 clear_relation_caches();
1652 }
1653
1658 GAIA_NODISCARD bool has_nonfragmenting_relation_target_cond(Entity target, Pair cond) const {
1659 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
1660 if (store.sources(target) == nullptr)
1661 continue;
1662
1663 if (has(relKey.entity(), cond))
1664 return true;
1665 }
1666
1667 return false;
1668 }
1669
1670 //----------------------------------------------------------------------
1671
1673 void set_serializer(std::nullptr_t) {
1674 // Always use the binary serializer as the default.
1675 m_serializer = ser::make_serializer(m_stream);
1676 }
1677
1681 GAIA_ASSERT(serializer.valid());
1682 m_serializer = serializer;
1683 }
1684
1687 template <typename TSerializer>
1688 void set_serializer(TSerializer& serializer) {
1689 set_serializer(ser::make_serializer(serializer));
1690 }
1691
1695 return m_serializer;
1696 }
1697
1698 //----------------------------------------------------------------------
1699
1701 struct EntityBuilder final {
1702 friend class World;
1703
1707 Archetype* m_pArchetypeSrc = nullptr;
1709 Chunk* m_pChunkSrc = nullptr;
1711 uint32_t m_rowSrc = 0;
1713 Archetype* m_pArchetype = nullptr;
1721 using RelationMutationPath = detail::RelationMutationPath;
1722
1723#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
1724 static constexpr uint32_t MAX_TERMS = 32;
1725 static_assert(MAX_TERMS <= ChunkHeader::MAX_COMPONENTS);
1726
1729#endif
1730
1731#if GAIA_OBSERVERS_ENABLED
1732 cnt::sarray_ext<Entity, MAX_TERMS> tl_del_nonfragmenting_relations;
1733#endif
1734
1739 EntityBuilder(World& world, Entity entity, EntityContainer& ec):
1740 m_world(world), m_pArchetypeSrc(ec.pArchetype), m_pChunkSrc(ec.pChunk), m_rowSrc(ec.row),
1741 m_pArchetype(ec.pArchetype), m_entity(entity) {
1742 // Make sure entity matches the provided entity container record
1743 GAIA_ASSERT(ec.pChunk->entity_view()[ec.row] == entity);
1744 }
1745
1749 EntityBuilder(World& world, Entity entity): m_world(world), m_entity(entity) {
1750 const auto& ec = world.fetch(entity);
1751 m_pArchetypeSrc = ec.pArchetype;
1752 m_pChunkSrc = ec.pChunk;
1753 m_rowSrc = ec.row;
1754
1755 m_pArchetype = ec.pArchetype;
1756 }
1757
1758 EntityBuilder(const EntityBuilder&) = default;
1759
1760 EntityBuilder& operator=(const EntityBuilder&) = delete;
1761 EntityBuilder& operator=(EntityBuilder&&) = delete;
1762
1763 ~EntityBuilder() {
1764 commit();
1765 }
1766
1769 void commit() {
1770 // No requests to change the archetype were made
1771 if (m_pArchetype == nullptr) {
1772 return;
1773 }
1774
1775 // Change in archetype detected
1776 if (m_pArchetypeSrc != m_pArchetype) {
1777 auto& ec = m_world.fetch(m_entity);
1778 GAIA_ASSERT(ec.pArchetype == m_pArchetypeSrc);
1779#if GAIA_OBSERVERS_ENABLED
1780 const bool hasOnDelObservers = !tl_del_comps.empty() && m_world.m_observers.has_on_del_observers();
1781 const bool hasOnAddObservers = !tl_new_comps.empty() && m_world.m_observers.has_on_add_observers();
1782 auto delDiffCtx = !hasOnDelObservers ? ObserverRegistry::DiffDispatchCtx{}
1783 : m_world.m_observers.prepare_diff(
1784 m_world, ObserverEvent::OnDel, EntitySpan{tl_del_comps},
1785 EntitySpan{&m_entity, 1});
1786 auto addDiffCtx = !hasOnAddObservers ? ObserverRegistry::DiffDispatchCtx{}
1787 : m_world.m_observers.prepare_diff(
1788 m_world, ObserverEvent::OnAdd, EntitySpan{tl_new_comps},
1789 EntitySpan{&m_entity, 1});
1790#endif
1791
1792#if GAIA_OBSERVERS_ENABLED
1793 if (hasOnDelObservers)
1794 flush_del_nonfragmenting_relations();
1795#endif
1796
1797 // Trigger remove hooks if there are any
1798 trigger_del_hooks(*m_pArchetype);
1799
1800 // Now that we have the final archetype move the entity to it
1801 m_world.move_entity_raw(m_entity, ec, *m_pArchetype);
1802
1803 if (m_targetNameKey.str() != nullptr || m_targetAliasKey.str() != nullptr) {
1804 const auto compIdx = ec.pChunk->comp_idx(GAIA_ID(EntityDesc));
1805 // No need to update version, entity move did it already.
1806 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut_gen<false>(compIdx, ec.row));
1807 GAIA_ASSERT(core::check_alignment(pDesc));
1808
1809 // Update the entity name string pointers if necessary
1810 if (m_targetNameKey.str() != nullptr) {
1811 pDesc->name = m_targetNameKey.str();
1812 pDesc->name_len = m_targetNameKey.len();
1813 }
1814
1815 // Update the entity alias string pointers if necessary
1816 if (m_targetAliasKey.str() != nullptr) {
1817 pDesc->alias = m_targetAliasKey.str();
1818 pDesc->alias_len = m_targetAliasKey.len();
1819 }
1820 }
1821
1822 // Trigger add hooks if there are any
1823 trigger_add_hooks(*m_pArchetype);
1824#if GAIA_OBSERVERS_ENABLED
1825 if (hasOnDelObservers)
1826 m_world.m_observers.finish_diff(m_world, GAIA_MOV(delDiffCtx));
1827 if (hasOnAddObservers)
1828 m_world.m_observers.finish_diff(m_world, GAIA_MOV(addDiffCtx));
1829#endif
1830 cleanup_deleted_sparse_components();
1831
1832 m_pArchetypeSrc = ec.pArchetype;
1833 m_pChunkSrc = ec.pChunk;
1834 m_rowSrc = ec.row;
1835 }
1836 // Archetype is still the same. Make sure no chunk movement has happened.
1837 else {
1838#if GAIA_ASSERT_ENABLED
1839 auto& ec = m_world.fetch(m_entity);
1840 GAIA_ASSERT(ec.pChunk == m_pChunkSrc);
1841#endif
1842
1843#if GAIA_OBSERVERS_ENABLED
1844 const bool hasOnDelObservers = !tl_del_comps.empty() && m_world.m_observers.has_on_del_observers();
1845 const bool hasOnAddObservers = !tl_new_comps.empty() && m_world.m_observers.has_on_add_observers();
1846 auto delDiffCtx = !hasOnDelObservers ? ObserverRegistry::DiffDispatchCtx{}
1847 : m_world.m_observers.prepare_diff(
1848 m_world, ObserverEvent::OnDel, EntitySpan{tl_del_comps},
1849 EntitySpan{&m_entity, 1});
1850 auto addDiffCtx = !hasOnAddObservers ? ObserverRegistry::DiffDispatchCtx{}
1851 : m_world.m_observers.prepare_diff(
1852 m_world, ObserverEvent::OnAdd, EntitySpan{tl_new_comps},
1853 EntitySpan{&m_entity, 1});
1854#endif
1855
1856#if GAIA_OBSERVERS_ENABLED
1857 if (hasOnDelObservers)
1858 flush_del_nonfragmenting_relations();
1859#endif
1860
1861 if (m_targetNameKey.str() != nullptr || m_targetAliasKey.str() != nullptr) {
1862 const auto compIdx = m_pChunkSrc->comp_idx(GAIA_ID(EntityDesc));
1863 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<true>(compIdx, m_rowSrc));
1864 GAIA_ASSERT(core::check_alignment(pDesc));
1865
1866 // Update the entity name string pointers if necessary
1867 if (m_targetNameKey.str() != nullptr) {
1868 pDesc->name = m_targetNameKey.str();
1869 pDesc->name_len = m_targetNameKey.len();
1870 }
1871
1872 // Update the entity alias string pointers if necessary
1873 if (m_targetAliasKey.str() != nullptr) {
1874 pDesc->alias = m_targetAliasKey.str();
1875 pDesc->alias_len = m_targetAliasKey.len();
1876 }
1877 }
1878
1879#if GAIA_OBSERVERS_ENABLED
1880 if (hasOnDelObservers)
1881 m_world.m_observers.on_del(m_world, *m_pArchetypeSrc, EntitySpan{tl_del_comps}, EntitySpan{&m_entity, 1});
1882 if (hasOnAddObservers)
1883 m_world.m_observers.on_add(m_world, *m_pArchetypeSrc, EntitySpan{tl_new_comps}, EntitySpan{&m_entity, 1});
1884 if (hasOnDelObservers)
1885 m_world.m_observers.finish_diff(m_world, GAIA_MOV(delDiffCtx));
1886 if (hasOnAddObservers)
1887 m_world.m_observers.finish_diff(m_world, GAIA_MOV(addDiffCtx));
1888#endif
1889 cleanup_deleted_sparse_components();
1890 }
1891
1892 // Finalize the builder by reseting the archetype pointer
1893 m_pArchetype = nullptr;
1894 m_targetNameKey = {};
1895 m_targetAliasKey = {};
1896 }
1897
1905 void name(const char* name, uint32_t len = 0) {
1906 name_inter<true>(name, len);
1907 }
1908
1920 void name_raw(const char* name, uint32_t len = 0) {
1921 name_inter<false>(name, len);
1922 }
1923
1928 void alias(const char* alias, uint32_t len = 0) {
1929 alias_inter<true>(alias, len);
1930 }
1931
1936 void alias_raw(const char* alias, uint32_t len = 0) {
1937 alias_inter<false>(alias, len);
1938 }
1939
1941 void del_name() {
1942 if (m_entity.pair())
1943 return;
1944
1945 // The following block is essentially the same as this but without the archetype pointer access:
1946 // const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
1947 const auto compIdx = core::get_index(m_pChunkSrc->ids_view(), GAIA_ID(EntityDesc));
1948 if (compIdx == BadIndex)
1949 return;
1950
1951 {
1952 const auto* pDesc = reinterpret_cast<const EntityDesc*>(m_pChunkSrc->comp_ptr(compIdx, m_rowSrc));
1953 GAIA_ASSERT(core::check_alignment(pDesc));
1954 if (pDesc->name == nullptr)
1955 return;
1956 }
1957
1958 // TODO: Trigger the hooks/observers manually here? I do not like essentially calling comp_ptr twice here.
1959 // The second one could be replaced with const cast + emit.
1960 // No need to update version, commit() will do it
1961 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<false>(compIdx, m_rowSrc));
1962 del_name_inter(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0));
1963 m_world.invalidate_scope_path_cache();
1964
1965 pDesc->name = nullptr;
1966 pDesc->name_len = 0;
1967 if (pDesc->alias == nullptr)
1968 del_inter(GAIA_ID(EntityDesc));
1969
1970 m_targetNameKey = {};
1971 }
1972
1974 void del_alias() {
1975 if (m_entity.pair())
1976 return;
1977
1978 // The following block is essentially the same as this but without the archetype pointer access:
1979 // const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
1980 const auto compIdx = core::get_index(m_pChunkSrc->ids_view(), GAIA_ID(EntityDesc));
1981 if (compIdx == BadIndex)
1982 return;
1983
1984 {
1985 const auto* pDesc = reinterpret_cast<const EntityDesc*>(m_pChunkSrc->comp_ptr(compIdx, m_rowSrc));
1986 GAIA_ASSERT(core::check_alignment(pDesc));
1987 if (pDesc->alias == nullptr)
1988 return;
1989 }
1990
1991 // TODO: Trigger the hooks/observers manually here? I do not like essentially calling comp_ptr twice here.
1992 // The second one could be replaced with const cast + emit.
1993 // No need to update version, commit() will do it
1994 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<false>(compIdx, m_rowSrc));
1995 del_alias_inter(EntityNameLookupKey(pDesc->alias, pDesc->alias_len, 0));
1996
1997 pDesc->alias = nullptr;
1998 pDesc->alias_len = 0;
1999 if (pDesc->name == nullptr)
2000 del_inter(GAIA_ID(EntityDesc));
2001
2002 m_targetAliasKey = {};
2003 }
2004
2009 GAIA_PROF_SCOPE(EntityBuilder::add);
2010 GAIA_ASSERT(m_world.valid(m_entity));
2011 GAIA_ASSERT(m_world.valid(entity));
2012
2013 add_inter(entity);
2014 return *this;
2015 }
2016
2021 GAIA_PROF_SCOPE(EntityBuilder::add);
2022 GAIA_ASSERT(m_world.valid(m_entity));
2023 GAIA_ASSERT(m_world.valid(pair.first()));
2024 GAIA_ASSERT(m_world.valid(pair.second()));
2025
2026 add_inter(pair);
2027 return *this;
2028 }
2029
2034 EntityBuilder& as(Entity entityBase) {
2035 return add(Pair(Is, entityBase));
2036 }
2037
2041 return add(Prefab);
2042 }
2043
2048 GAIA_NODISCARD bool as(Entity entity, Entity entityBase) const {
2049 return static_cast<const World&>(m_world).is(entity, entityBase);
2050 }
2051
2056 return add(Pair(ChildOf, parent));
2057 }
2058
2062 template <typename T>
2064 if constexpr (is_pair<T>::value) {
2065 const auto rel = m_world.template reg_comp<typename T::rel>().entity;
2066 const auto tgt = m_world.template reg_comp<typename T::tgt>().entity;
2067 const Entity ent = Pair(rel, tgt);
2068 add_inter(ent);
2069 return ent;
2070 } else {
2071 return m_world.template reg_comp<T>().entity;
2072 }
2073 }
2074
2078 template <typename T>
2080 verify_comp<T>();
2081 add(register_component<T>());
2082 return *this;
2083 }
2084
2089 GAIA_PROF_SCOPE(EntityBuilder::del);
2090 GAIA_ASSERT(m_world.valid(m_entity));
2091 GAIA_ASSERT(m_world.valid(entity));
2092 del_inter(entity);
2093 return *this;
2094 }
2095
2100 GAIA_PROF_SCOPE(EntityBuilder::add);
2101 GAIA_ASSERT(m_world.valid(m_entity));
2102 GAIA_ASSERT(m_world.valid(pair.first()));
2103 GAIA_ASSERT(m_world.valid(pair.second()));
2104 del_inter(pair);
2105 return *this;
2106 }
2107
2111 template <typename T>
2113 verify_comp<T>();
2114 del(register_component<T>());
2115 return *this;
2116 }
2117
2118 private:
2121 void trigger_add_hooks(const Archetype& newArchetype) {
2122#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2123 if GAIA_UNLIKELY (m_world.tearing_down()) {
2124 tl_new_comps.clear();
2125 (void)newArchetype;
2126 return;
2127 }
2128
2129 m_world.lock();
2130
2131 #if GAIA_ENABLE_ADD_DEL_HOOKS
2132 // if (hookCnt > 0)
2133 {
2134 // Trigger component hooks first
2135 for (auto entity: tl_new_comps) {
2136 const auto* pItem = m_world.component_item(m_entity, entity);
2137 if (pItem == nullptr)
2138 continue;
2139 const auto& hooks = ComponentCache::hooks(*pItem);
2140 if (hooks.func_add != nullptr)
2141 hooks.func_add(m_world, *pItem, m_entity);
2142 }
2143 }
2144 #endif
2145
2146 #if GAIA_OBSERVERS_ENABLED
2147 // Trigger observers second
2148 if (m_world.m_observers.has_on_add_observers())
2149 m_world.m_observers.on_add(m_world, newArchetype, std::span<Entity>{tl_new_comps}, {&m_entity, 1});
2150 #else
2151 (void)newArchetype;
2152 #endif
2153
2154 tl_new_comps.clear();
2155
2156 m_world.unlock();
2157#endif
2158 }
2159
2162 void trigger_del_hooks(const Archetype& newArchetype) {
2163#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2164 if GAIA_UNLIKELY (m_world.tearing_down()) {
2165 tl_del_comps.clear();
2166 (void)newArchetype;
2167 return;
2168 }
2169
2170 m_world.notify_inherited_del_dependents(m_entity, std::span<Entity>{tl_del_comps});
2171 m_world.lock();
2172
2173 #if GAIA_OBSERVERS_ENABLED
2174 // Trigger observers first
2175 if (m_world.m_observers.has_on_del_observers())
2176 m_world.m_observers.on_del(m_world, newArchetype, std::span<Entity>{tl_del_comps}, {&m_entity, 1});
2177 #else
2178 (void)newArchetype;
2179 #endif
2180
2181 #if GAIA_ENABLE_ADD_DEL_HOOKS
2182 // if (hookCnt > 0)
2183 {
2184 // Trigger component hooks second
2185 for (auto entity: tl_del_comps) {
2186 const auto* pItem = m_world.component_item(m_entity, entity);
2187 if (pItem == nullptr)
2188 continue;
2189 const auto& hooks = ComponentCache::hooks(*pItem);
2190 if (hooks.func_del != nullptr)
2191 hooks.func_del(m_world, *pItem, m_entity);
2192 }
2193 }
2194 #endif
2195
2196 m_world.unlock();
2197#endif
2198 }
2199
2201 void cleanup_deleted_sparse_components() {
2202 for (auto entity: tl_del_comps) {
2203 if (entity.pair() || !m_world.component_uses_sparse_storage(entity) ||
2204 m_world.component_is_non_fragmenting(entity))
2205 continue;
2206
2207 const auto it = m_world.m_sparseComponentsByComp.find(EntityLookupKey(entity));
2208 if (it != m_world.m_sparseComponentsByComp.end())
2209 it->second.func_del(it->second.pStore, m_entity);
2210 }
2211
2212 tl_del_comps.clear();
2213 }
2214
2215#if GAIA_OBSERVERS_ENABLED
2217 void flush_del_nonfragmenting_relations() {
2218 for (auto entity: tl_del_nonfragmenting_relations)
2219 del_nonfragmenting_relation_id(entity);
2220 tl_del_nonfragmenting_relations.clear();
2221 }
2222#endif
2223
2228 bool handle_add_entity(Entity entity, RelationMutationPath relationPath) {
2229 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> targets;
2230 const bool isPair = entity.pair();
2231 GAIA_ASSERT(isPair || relationPath == RelationMutationPath::Fragmenting);
2232
2233 if (isPair)
2234 m_world.invalidate_scope_path_cache();
2235
2236 // Handle entity combinations that can't be together
2237 if (m_world.m_hasCantCombinePolicy) {
2238 const auto& ecMain = m_world.fetch(entity);
2239 if ((ecMain.flags & EntityContainerFlags::HasCantCombine) != 0) {
2240 m_world.targets(entity, CantCombine, [&targets](Entity target) {
2241 targets.push_back(target);
2242 });
2243 for (auto e: targets) {
2244 if (m_pArchetype->has(e)) {
2245#if GAIA_ASSERT_ENABLED
2246 GAIA_ASSERT2(false, "Trying to add an entity which can't be combined with the source");
2247 print_archetype_entities(m_world, *m_pArchetype, entity, true);
2248#endif
2249 return false;
2250 }
2251 }
2252 }
2253 }
2254
2255 // Handle archetype-backed exclusive pairs. Exclusive non-fragmenting relations replace their target in
2256 // non-fragmenting relation storage and never need archetype-pair replacement checks.
2257 if (isPair && relationPath != RelationMutationPath::NonFragmentingExclusive) {
2258 // Check if (rel, tgt)'s rel part is exclusive
2259 const auto& ecRel = m_world.m_recs.entities[entity.id()];
2261 if ((ecRel.flags & EntityContainerFlags::IsExclusive) != 0 && m_pArchetype->pairs() != 0) {
2262 auto rel = Entity(
2263 entity.id(), ecRel.data.gen, (bool)ecRel.data.ent, (bool)ecRel.data.pair,
2264 (EntityKind)ecRel.data.kind);
2265 auto tgt = m_world.try_get(entity.gen());
2266 if (tgt == EntityBad)
2267 return false;
2268
2269 const auto targetsCntMax = m_pArchetype->pair_matches(Pair(rel, All));
2270 if (targetsCntMax > 1) {
2271#if GAIA_ASSERT_ENABLED
2272 GAIA_ASSERT2(
2273 false, "Trying to add a pair with exclusive relationship but there are multiple targets present. "
2274 "Make sure to add the Exclusive property before any relationships with it are created.");
2275 print_archetype_entities(m_world, *m_pArchetype, entity, true);
2276#endif
2277 return false;
2278 }
2279
2280 if (targetsCntMax == 1) {
2281 // Make sure to remove the (rel, tgt0) so only the new (rel, tgt1) remains.
2282 targets.clear();
2283 m_world.targets_if(m_entity, rel, [&targets](Entity target) {
2284 targets.push_back(target);
2285 return false;
2286 });
2287
2288 GAIA_ASSERT(targets.size() <= 1);
2289 if (!targets.empty() && tgt != targets[0]) {
2290 // Exclusive relationship replaces the previous one.
2291 // We need to check if the old one can be removed.
2292 // This is what del_inter does on the inside.
2293 // It first checks if entity can be deleted and calls handle_del afterwards.
2294 if (!can_del(entity)) {
2295#if GAIA_ASSERT_ENABLED
2296 GAIA_ASSERT2(
2297 false,
2298 "Trying to replace an exclusive relationship but the entity which is getting removed has "
2299 "dependencies.");
2300 print_archetype_entities(m_world, *m_pArchetype, entity, true);
2301#endif
2302 return false;
2303 }
2304
2305 handle_del(ecs::Pair(rel, targets[0]));
2306 }
2307 }
2308 }
2309 }
2310
2311 // Handle requirements
2312 if (m_world.m_hasRequiresPolicy) {
2313 targets.clear();
2314 m_world.targets(entity, Requires, [&targets](Entity target) {
2315 targets.push_back(target);
2316 });
2317
2318 for (auto e: targets) {
2319 auto* pArchetype = m_pArchetype;
2320 handle_add<false>(e);
2321 if (m_pArchetype != pArchetype) {
2322 const auto requiredPath = e.pair() ? relation_mutation_path_add(e) : RelationMutationPath::Fragmenting;
2323 handle_add_entity(e, requiredPath);
2324 }
2325 }
2326 }
2327
2328 return true;
2329 }
2330
2334 GAIA_NODISCARD bool has_Requires_tgt(Entity entity) const {
2335 if (!m_world.m_hasRequiresPolicy)
2336 return false;
2337
2338 // Don't allow to delete entity if something in the archetype requires it
2339 auto ids = m_pArchetype->ids_view();
2340 for (auto e: ids) {
2341 if (m_world.has(e, Pair(Requires, entity)))
2342 return true;
2343 }
2344
2345 return false;
2346 }
2347
2352 static void set_flag(EntityContainerFlagsType& flags, EntityContainerFlags flag, bool enable) {
2353 if (enable)
2354 flags |= flag;
2355 else
2356 flags &= ~flag;
2357 }
2358
2363 void set_flag(Entity entity, EntityContainerFlags flag, bool enable) {
2364 auto& ec = m_world.fetch(entity);
2365 set_flag(ec.flags, flag, enable);
2366 }
2367
2371 void try_set_flags(Entity entity, bool enable) {
2372 if (entity.pair()) {
2373 if (entity.id() == Is.id()) {
2374 auto& ec = m_world.fetch(entity);
2375 try_set_Is(ec, entity, enable);
2376 }
2377
2378 if (entity.id() == CantCombine.id() || entity.id() == OnDelete.id() || entity.id() == OnDeleteTarget.id()) {
2379 auto& ecMain = m_world.fetch(m_entity);
2380 try_set_CantCombine(ecMain, entity, enable);
2381 try_set_OnDelete(ecMain, entity, enable);
2382 try_set_OnDeleteTargetPolicy(ecMain, entity, enable);
2383 }
2384 if (enable && entity.id() == Requires.id())
2385 m_world.m_hasRequiresPolicy = true;
2386
2387 if (m_world.m_hasOnDeleteTargetPolicy)
2388 try_set_OnDeleteTarget(entity, enable);
2389 return;
2390 }
2391
2392 auto& ecMain = m_world.fetch(m_entity);
2393 try_set_CantCombine(ecMain, entity, enable);
2394
2395 auto& ec = m_world.fetch(entity);
2396 try_set_Is(ec, entity, enable);
2397 try_set_IsExclusive(ecMain, entity, enable);
2398 if (enable)
2399 try_set_sticky_component_traits(ecMain, entity);
2400 try_set_IsSingleton(ecMain, entity, enable);
2401 try_set_OnDelete(ecMain, entity, enable);
2402 try_set_OnDeleteTargetPolicy(ecMain, entity, enable);
2403 }
2404
2409 void try_set_Is(EntityContainer& ec, Entity entity, bool enable) {
2410 if (!entity.pair() || entity.id() != Is.id())
2411 return;
2412
2413 set_flag(ec.flags, EntityContainerFlags::HasAliasOf, enable);
2414 }
2415
2420 void try_set_CantCombine(EntityContainer& ec, Entity entity, bool enable) {
2421 if (!entity.pair() || entity.id() != CantCombine.id())
2422 return;
2423
2424 GAIA_ASSERT(entity != m_entity);
2425
2426 // Setting the flag can be done right away.
2427 // One bit can only contain information about one pair but there
2428 // can be any amount of CanCombine pairs formed with an entity.
2429 // Therefore, when resetting the flag, we first need to check if there
2430 // are any other targets with this flag set and only reset the flag
2431 // if there is only one present.
2432 if (enable) {
2433 m_world.m_hasCantCombinePolicy = true;
2434 set_flag(ec.flags, EntityContainerFlags::HasCantCombine, true);
2435 } else if ((ec.flags & EntityContainerFlags::HasCantCombine) != 0) {
2436 uint32_t targets = 0;
2437 m_world.targets(m_entity, CantCombine, [&targets]([[maybe_unused]] Entity entity) {
2438 ++targets;
2439 });
2440 if (targets == 1)
2441 set_flag(ec.flags, EntityContainerFlags::HasCantCombine, false);
2442 }
2443 }
2444
2449 void try_set_IsExclusive(EntityContainer& ec, Entity entity, bool enable) {
2450 if (entity.pair() || entity.id() != Exclusive.id())
2451 return;
2452
2453 set_flag(ec.flags, EntityContainerFlags::IsExclusive, enable);
2454 }
2455
2459 void try_set_sticky_component_traits(EntityContainer& ecMain, Entity entity) {
2460 if (entity.pair())
2461 return;
2462
2463 if (entity.id() == DontFragment.id()) {
2464 m_world.set_component_dont_fragment(m_entity, ecMain);
2465 return;
2466 }
2467
2468 if (entity.id() == Sparse.id())
2469 m_world.set_component_sparse_storage(m_entity);
2470 }
2471
2475 GAIA_NODISCARD RelationMutationPath relation_mutation_path(Entity entity) const noexcept {
2476 GAIA_ASSERT(entity.pair());
2477
2478 return relation_mutation_path_from_flags(m_world.fetch(m_world.get(entity.id())).flags);
2479 }
2480
2484 GAIA_NODISCARD RelationMutationPath relation_mutation_path_add(Entity entity) const noexcept {
2485 GAIA_ASSERT(entity.pair());
2486
2487 const auto& ecRel = m_world.m_recs.entities[entity.id()];
2488 return relation_mutation_path_from_flags(ecRel.flags);
2489 }
2490
2494 GAIA_NODISCARD static RelationMutationPath
2495 relation_mutation_path_from_flags(EntityContainerFlagsType flags) noexcept {
2496 const auto nonFragmentingExclusiveFlags =
2497 EntityContainerFlags::IsExclusive | EntityContainerFlags::IsDontFragment;
2498 if ((flags & nonFragmentingExclusiveFlags) == nonFragmentingExclusiveFlags)
2499 return RelationMutationPath::NonFragmentingExclusive;
2500
2501 if ((flags & EntityContainerFlags::IsDontFragment) == 0)
2502 return RelationMutationPath::Fragmenting;
2503
2504 return RelationMutationPath::NonFragmentingArchetypePair;
2505 }
2506
2510 GAIA_NODISCARD bool has_archetype_id(Entity entity) const {
2511 return m_pArchetype->has(entity);
2512 }
2513
2517 GAIA_NODISCARD bool has_nonfragmenting_relation_id(Entity entity) const {
2518 GAIA_ASSERT(entity.pair());
2519 return m_world.has_nonfragmenting_relation_pair(m_entity, entity);
2520 }
2521
2525 GAIA_NODISCARD bool has_nonfragmenting_archetype_pair_id(Entity entity) const {
2526 GAIA_ASSERT(entity.pair());
2527 return has_archetype_id(entity);
2528 }
2529
2532 void add_archetype_id(Entity entity) {
2533 m_pArchetype = m_world.foc_archetype_add_no_graph(m_pArchetype, entity);
2534 }
2535
2539 GAIA_NODISCARD bool add_id(Entity entity) {
2540 add_archetype_id(entity);
2541 return true;
2542 }
2543
2546 void del_nonfragmenting_relation_id(Entity entity) {
2547 GAIA_ASSERT(entity.pair());
2548
2549 const auto relation = m_world.try_get(entity.id());
2550 const auto target = m_world.try_get(entity.gen());
2551 if (relation != EntityBad && target != EntityBad)
2552 (void)m_world.nonfragmenting_relation_del(m_entity, relation, target);
2553 }
2554
2557 void del_archetype_id(Entity entity) {
2558 m_pArchetype = m_world.foc_archetype_del_no_graph(m_pArchetype, entity);
2559 }
2560
2563 void del_id(Entity entity) {
2564 del_archetype_id(entity);
2565 }
2566
2571 void try_set_OnDeleteTargetPolicy(EntityContainer& ec, Entity entity, bool enable) {
2572 if (entity == Pair(OnDeleteTarget, Delete))
2573 set_flag(ec.flags, EntityContainerFlags::OnDeleteTarget_Delete, enable);
2574 else if (entity == Pair(OnDeleteTarget, Remove))
2575 set_flag(ec.flags, EntityContainerFlags::OnDeleteTarget_Remove, enable);
2576 else if (entity == Pair(OnDeleteTarget, Error))
2577 set_flag(ec.flags, EntityContainerFlags::OnDeleteTarget_Error, enable);
2578 else
2579 return;
2580
2581 if (enable)
2582 m_world.m_hasOnDeleteTargetPolicy = true;
2583 }
2584
2588 void try_set_OnDeleteTarget(Entity entity, bool enable) {
2589 if (!entity.pair())
2590 return;
2591
2592 if (enable) {
2593 GAIA_ASSERT(m_world.valid(m_world.get(entity.id())));
2594 GAIA_ASSERT(m_world.valid(m_world.get(entity.gen())));
2595
2596 const auto& ecRel = m_world.m_recs.entities[entity.id()];
2597 const auto policyFlags = ecRel.flags & (EntityContainerFlags::OnDeleteTarget_Delete |
2598 EntityContainerFlags::OnDeleteTarget_Remove |
2599 EntityContainerFlags::OnDeleteTarget_Error);
2600 if (policyFlags == 0)
2601 return;
2602
2603 auto& ecTgt = m_world.m_recs.entities[entity.gen()];
2604 if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Delete) != 0)
2605 set_flag(ecTgt.flags, EntityContainerFlags::OnDeleteTarget_Delete, true);
2606 else if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Remove) != 0)
2607 set_flag(ecTgt.flags, EntityContainerFlags::OnDeleteTarget_Remove, true);
2608 else if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Error) != 0)
2609 set_flag(ecTgt.flags, EntityContainerFlags::OnDeleteTarget_Error, true);
2610 return;
2611 }
2612
2613 const auto rel = m_world.try_get(entity.id());
2614 if (rel == EntityBad)
2615 return;
2616
2617 const auto& ecRel = m_world.fetch(rel);
2618 const auto policyFlags =
2619 ecRel.flags & (EntityContainerFlags::OnDeleteTarget_Delete | EntityContainerFlags::OnDeleteTarget_Remove |
2620 EntityContainerFlags::OnDeleteTarget_Error);
2621 if (policyFlags == 0)
2622 return;
2623
2624 const auto tgt = m_world.try_get(entity.gen());
2625 if (tgt == EntityBad)
2626 return;
2627
2628 // Adding a pair to an entity with OnDeleteTarget relationship.
2629 // We need to update the target entity's flags.
2630 if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Delete) != 0)
2631 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Delete, enable);
2632 else if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Remove) != 0)
2633 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Remove, enable);
2634 else if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Error) != 0)
2635 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Error, enable);
2636 }
2637
2642 void try_set_OnDelete(EntityContainer& ec, Entity entity, bool enable) {
2643 if (entity == Pair(OnDelete, Delete))
2644 set_flag(ec.flags, EntityContainerFlags::OnDelete_Delete, enable);
2645 else if (entity == Pair(OnDelete, Remove))
2646 set_flag(ec.flags, EntityContainerFlags::OnDelete_Remove, enable);
2647 else if (entity == Pair(OnDelete, Error))
2648 set_flag(ec.flags, EntityContainerFlags::OnDelete_Error, enable);
2649 }
2650
2655 void try_set_IsSingleton(EntityContainer& ec, Entity entity, bool enable) {
2656 const bool isSingleton = enable && m_entity == entity;
2657 set_flag(ec.flags, EntityContainerFlags::IsSingleton, isSingleton);
2658 }
2659
2663 void handle_DependsOn(Entity entity, bool enable) {
2664 (void)entity;
2665 (void)enable;
2666 // auto& ec = m_world.fetch(entity);
2667 // if (enable) {
2668 // // Calculate the depth in the dependency tree
2669 // uint32_t depth = 1;
2670
2671 // auto e = entity;
2672 // if (m_world.valid(e)) {
2673 // while (true) {
2674 // auto tgt = m_world.target(e, DependsOn);
2675 // if (tgt == EntityBad)
2676 // break;
2677
2678 // ++depth;
2679 // e = tgt;
2680 // }
2681 // }
2682 // ec.depthDependsOn = (uint8_t)depth;
2683
2684 // // Update depth for all entities depending on this one
2685 // auto q = m_world.uquery();
2686 // q.all(ecs::Pair(DependsOn, m_entity)) //
2687 // .each([&](Entity dependingEntity) {
2688 // auto& ecDependingEntity = m_world.fetch(dependingEntity);
2689 // ecDependingEntity.depthDependsOn += (uint8_t)depth;
2690 // });
2691 // } else {
2692 // // Update depth for all entities depending on this one
2693 // auto q = m_world.uquery();
2694 // q.all(ecs::Pair(DependsOn, m_entity)) //
2695 // .each([&](Entity dependingEntity) {
2696 // auto& ecDependingEntity = m_world.fetch(dependingEntity);
2697 // ecDependingEntity.depthDependsOn -= ec.depthDependsOn;
2698 // });
2699
2700 // // Reset the depth
2701 // ec.depthDependsOn = 0;
2702 // }
2703 }
2704
2707 void invalidate_relation_change(Entity entity) {
2708 GAIA_ASSERT(entity.pair());
2709
2710 auto relation = m_world.m_recs.entities.handle(entity.id());
2711 m_world.invalidate_relation_caches(relation);
2712 }
2713
2717 GAIA_NODISCARD bool link_is_relation(Entity entity) {
2718 GAIA_ASSERT(entity.pair());
2719
2720 if (entity.id() != Is.id())
2721 return true;
2722
2723 auto e = m_world.try_get(entity.gen());
2724 if (e == EntityBad)
2725 return false;
2726
2727 EntityLookupKey entityKey(m_entity);
2728 EntityLookupKey eKey(e);
2729
2730 // m_entity -> {..., e}
2731 auto& entity_to_e = m_world.m_entityToAsTargets[entityKey];
2732 entity_to_e.insert(eKey);
2733 m_world.m_entityToAsTargetsTravCache = {};
2734 // e -> {..., m_entity}
2735 auto& e_to_entity = m_world.m_entityToAsRelations[eKey];
2736 e_to_entity.insert(entityKey);
2737 m_world.m_entityToAsRelationsTravCache = {};
2738
2739 // Make sure the relation entity is registered as archetype so queries can find it
2740 // auto& ec = m_world.fetch(tgt);
2741 // m_world.add_entity_archetype_pair(m_entity, ec.pArchetype);
2742
2743 // Cached queries might need to be invalidated.
2744 m_world.invalidate_queries_for_entity({Is, e});
2745 return true;
2746 }
2747
2750 void unlink_is_relation(Entity entity) {
2751 GAIA_ASSERT(entity.pair());
2752
2753 if (entity.id() != Is.id())
2754 return;
2755
2756 auto e = m_world.try_get(entity.gen());
2757 if (e != EntityBad)
2758 m_world.unlink_live_is_relation(m_entity, e);
2759 }
2760
2764 template <bool IsBootstrap>
2765 void finish_add_id(Entity entity) {
2766 if constexpr (!IsBootstrap) {
2767 handle_DependsOn(entity, true);
2768
2769#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2770 tl_new_comps.push_back(entity);
2771#endif
2772 }
2773 }
2774
2777 void finish_del_id(Entity entity) {
2778#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2779 tl_del_comps.push_back(entity);
2780#endif
2781 }
2782
2787 template <bool IsBootstrap>
2788 bool handle_add(Entity entity) {
2789 if (entity.pair()) {
2790 const auto relationPath = relation_mutation_path_add(entity);
2791 if (relationPath == RelationMutationPath::NonFragmentingExclusive)
2792 return handle_add_nonfragmenting_relation<IsBootstrap>(entity);
2793
2794 return handle_add_archetype_relation<IsBootstrap>(entity);
2795 }
2796
2797#if GAIA_ASSERT_ENABLED
2798 World::verify_add(m_world, *m_pArchetype, m_entity, entity);
2799#endif
2800
2801 // Don't add the same entity twice
2802 if (has_archetype_id(entity))
2803 return false;
2804
2805 if (is_component_storage_trait(entity) && !m_world.can_add_component_storage_trait(m_entity))
2806 return false;
2807
2808 try_set_flags(entity, true);
2809
2810 if (!add_id(entity))
2811 return false;
2812
2813 finish_add_id<IsBootstrap>(entity);
2814
2815 return true;
2816 }
2817
2822 template <bool IsBootstrap>
2823 bool handle_add_archetype_relation(Entity entity) {
2824 GAIA_ASSERT(entity.pair());
2825
2826#if GAIA_ASSERT_ENABLED
2827 World::verify_add(m_world, *m_pArchetype, m_entity, entity);
2828#endif
2829
2830 // Don't add the same pair twice.
2831 if (has_archetype_id(entity))
2832 return false;
2833
2834 invalidate_relation_change(entity);
2835
2836 try_set_flags(entity, true);
2837 if (!link_is_relation(entity))
2838 return false;
2839
2840 add_archetype_id(entity);
2841 finish_add_id<IsBootstrap>(entity);
2842 return true;
2843 }
2844
2849 template <bool IsBootstrap>
2850 bool handle_add_nonfragmenting_relation(Entity entity) {
2851 GAIA_ASSERT(entity.pair());
2852
2853 // Don't add the same pair twice.
2854 if (has_nonfragmenting_relation_id(entity))
2855 return false;
2856
2857 const auto relation = m_world.try_get(entity.id());
2858 const auto target = m_world.try_get(entity.gen());
2859 if (relation == EntityBad || target == EntityBad)
2860 return false;
2861
2862 const auto* pStore = m_world.nonfragmenting_relation_store(relation);
2863 const auto oldTarget = pStore != nullptr ? pStore->target(m_entity) : EntityBad;
2864 if (oldTarget != EntityBad && oldTarget != target) {
2865 const auto oldPair = Pair(relation, oldTarget);
2866 invalidate_relation_change(oldPair);
2867 try_set_flags(oldPair, false);
2868 handle_DependsOn(oldPair, false);
2869 unlink_is_relation(oldPair);
2870 finish_del_id(oldPair);
2871 }
2872
2873 invalidate_relation_change(entity);
2874
2875 try_set_flags(entity, true);
2876 if (!link_is_relation(entity))
2877 return false;
2878
2879 m_world.nonfragmenting_relation_set(m_entity, relation, target);
2880
2881 finish_add_id<IsBootstrap>(entity);
2882
2883 return true;
2884 }
2885
2888 void handle_del(Entity entity) {
2889 if (entity.pair() && !m_world.valid(entity)) {
2890 if (m_pArchetype->has(entity)) {
2891 const auto relation = m_world.try_get(entity.id());
2892 if (relation != EntityBad) {
2893 m_world.invalidate_relation_caches(relation);
2894 }
2895
2896 if (entity.id() == Is.id())
2897 m_world.unlink_stale_is_relations_by_target_id(m_entity, entity.gen());
2898
2899 m_pArchetype = m_world.foc_archetype_del_no_graph(m_pArchetype, entity);
2900 }
2901 return;
2902 }
2903
2904 if (entity.pair()) {
2905 m_world.invalidate_scope_path_cache();
2906
2907 const auto relationPath = relation_mutation_path(entity);
2908 if (relationPath == RelationMutationPath::NonFragmentingExclusive)
2909 handle_del_nonfragmenting_relation(entity);
2910 else
2911 handle_del_archetype_relation(entity);
2912 return;
2913 }
2914
2915#if GAIA_ASSERT_ENABLED
2916 World::verify_del(m_world, *m_pArchetype, m_entity, entity);
2917#endif
2918
2919 // Don't delete what has not beed added
2920 if (!has_archetype_id(entity))
2921 return;
2922
2923 try_set_flags(entity, false);
2924 handle_DependsOn(entity, false);
2925
2926 del_id(entity);
2927 finish_del_id(entity);
2928 }
2929
2932 void handle_del_archetype_relation(Entity entity) {
2933 GAIA_ASSERT(entity.pair());
2934
2935#if GAIA_ASSERT_ENABLED
2936 World::verify_del(m_world, *m_pArchetype, m_entity, entity);
2937#endif
2938
2939 // Don't delete what has not been added.
2940 if (!has_archetype_id(entity))
2941 return;
2942
2943 invalidate_relation_change(entity);
2944
2945 try_set_flags(entity, false);
2946 handle_DependsOn(entity, false);
2947 unlink_is_relation(entity);
2948
2949 del_archetype_id(entity);
2950 finish_del_id(entity);
2951 }
2952
2955 void handle_del_nonfragmenting_relation(Entity entity) {
2956 GAIA_ASSERT(entity.pair());
2957
2958 // Don't delete what has not been added.
2959 if (!has_nonfragmenting_relation_id(entity))
2960 return;
2961
2962 invalidate_relation_change(entity);
2963
2964 try_set_flags(entity, false);
2965 handle_DependsOn(entity, false);
2966 unlink_is_relation(entity);
2967
2968#if GAIA_OBSERVERS_ENABLED
2969 if (m_world.m_observers.has_on_del_observers())
2970 tl_del_nonfragmenting_relations.push_back(entity);
2971 else
2972#endif
2973 del_nonfragmenting_relation_id(entity);
2974 finish_del_id(entity);
2975 }
2976
2981 GAIA_NODISCARD bool prepare_pair_add(Entity entity, RelationMutationPath relationPath) {
2982 GAIA_ASSERT(entity.pair());
2983
2984 if (relationPath == RelationMutationPath::NonFragmentingExclusive) {
2985 if (has_nonfragmenting_relation_id(entity))
2986 return false;
2987 } else if (relationPath == RelationMutationPath::NonFragmentingArchetypePair) {
2988 if (has_nonfragmenting_archetype_pair_id(entity))
2989 return false;
2990 }
2991
2992 // Make sure the entity container record exists if it is a pair.
2993 m_world.assign_pair(entity, *m_world.m_pEntityArchetype);
2994 return true;
2995 }
2996
2999 void add_inter(Entity entity) {
3000 GAIA_ASSERT(!is_wildcard(entity));
3001 const bool isPair = entity.pair();
3002 const auto relationPath = isPair ? relation_mutation_path_add(entity) : RelationMutationPath::Fragmenting;
3003
3004 if (isPair && !prepare_pair_add(entity, relationPath))
3005 return;
3006
3007 if (!handle_add_entity(entity, relationPath))
3008 return;
3009
3010 if (isPair) {
3011 if (relationPath == RelationMutationPath::NonFragmentingExclusive)
3012 handle_add_nonfragmenting_relation<false>(entity);
3013 else
3014 handle_add_archetype_relation<false>(entity);
3015 return;
3016 }
3017
3018 handle_add<false>(entity);
3019 }
3020
3026 static void rebuild_graph_edge(Archetype* pArchetypeLeft, Archetype* pArchetypeRight, Entity entity) {
3027 pArchetypeLeft->del_graph_edge_right_local(entity);
3028 pArchetypeRight->del_graph_edge_left_local(entity);
3029 pArchetypeLeft->build_graph_edges(pArchetypeRight, entity);
3030 }
3031
3038 static void ensure_graph_edge(Archetype* pArchetypeLeft, Archetype* pArchetypeRight, Entity entity) {
3039 const auto right = pArchetypeLeft->find_edge_right(entity);
3040 const auto left = pArchetypeRight->find_edge_left(entity);
3041 if (right.id == pArchetypeRight->id() && left.id == pArchetypeLeft->id())
3042 return;
3043
3044 rebuild_graph_edge(pArchetypeLeft, pArchetypeRight, entity);
3045 }
3046
3049 void add_inter_init(Entity entity) {
3050 GAIA_ASSERT(!is_wildcard(entity));
3051 const bool isPair = entity.pair();
3052 const auto relationPath = isPair ? relation_mutation_path_add(entity) : RelationMutationPath::Fragmenting;
3053
3054 if (isPair && !prepare_pair_add(entity, relationPath))
3055 return;
3056
3057 if (!handle_add_entity(entity, relationPath))
3058 return;
3059
3060 if (isPair) {
3061 if (relationPath == RelationMutationPath::NonFragmentingExclusive)
3062 handle_add_nonfragmenting_relation<true>(entity);
3063 else
3064 handle_add_archetype_relation<true>(entity);
3065 return;
3066 }
3067
3068 handle_add<true>(entity);
3069 }
3070
3074 GAIA_NODISCARD static bool is_component_storage_trait(Entity entity) noexcept {
3075 return !entity.pair() && (entity.id() == DontFragment.id() || entity.id() == Sparse.id());
3076 }
3077
3083 GAIA_NODISCARD bool can_del(Entity entity) const noexcept {
3084 if (!entity.pair() && m_world.has_direct(entity, Requires))
3085 return false;
3086 if (has_Requires_tgt(entity))
3087 return false;
3088
3089 return true;
3090 }
3091
3095 bool del_inter(Entity entity) {
3096 if (!can_del(entity))
3097 return false;
3098
3099 handle_del(entity);
3100 return true;
3101 }
3102
3105 void del_name_inter(EntityNameLookupKey key) {
3106 const auto it = m_world.m_nameToEntity.find(key);
3107 // If the assert is hit it means the pointer to the name string was invalidated or became dangling.
3108 // That should not be possible for strings managed internally so the only other option is user-managed
3109 // strings are broken.
3110 GAIA_ASSERT(it != m_world.m_nameToEntity.end());
3111 if (it != m_world.m_nameToEntity.end()) {
3112 // Release memory allocated for the string if we own it
3113 if (it->first.owned())
3114 mem::mem_free((void*)key.str());
3115
3116 m_world.m_nameToEntity.erase(it);
3117 }
3118 }
3119
3122 void del_alias_inter(EntityNameLookupKey key) {
3123 const auto it = m_world.m_aliasToEntity.find(key);
3124 // If the assert is hit it means the pointer to the name string was invalidated or became dangling.
3125 // That should not be possible for strings managed internally so the only other option is user-managed
3126 // strings are broken.
3127 GAIA_ASSERT(it != m_world.m_aliasToEntity.end());
3128 if (it != m_world.m_aliasToEntity.end()) {
3129 // Release memory allocated for the string if we own it
3130 if (it->first.owned())
3131 mem::mem_free((void*)key.str());
3132
3133 m_world.m_aliasToEntity.erase(it);
3134 }
3135 }
3136
3141 template <bool IsOwned>
3142 void name_inter(const char* name, uint32_t len) {
3144 GAIA_ASSERT(!m_entity.pair());
3145 if (m_entity.pair())
3146 return;
3147
3148 // When nullptr is passed for the name it means the user wants to delete the current one
3149 if (name == nullptr) {
3150 GAIA_ASSERT(len == 0);
3151 del_name();
3152 return;
3153 }
3154
3155 GAIA_ASSERT(len < ComponentCacheItem::MaxNameLength);
3156
3157 // Make sure the name does not contain a dot because this character is reserved for
3158 // hierarchical lookups, e.g. "parent.child.subchild".
3159 GAIA_FOR(len) {
3160 const bool hasInvalidCharacter = name[i] == '.';
3161 GAIA_ASSERT(!hasInvalidCharacter && "Character '.' can't be used in entity names");
3162 if (hasInvalidCharacter)
3163 return;
3164 }
3165
3166 EntityNameLookupKey key(
3167 name, len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len, IsOwned);
3168
3169 // Make sure the name is unique. Ignore setting the same name twice on the same entity.
3170 // If it is not, there is nothing to do.
3171 auto it = m_world.m_nameToEntity.find(key);
3172 if (it == m_world.m_nameToEntity.end()) {
3173 // If we already had some name, remove the pair from the map first.
3174 if (m_targetNameKey.str() != nullptr) {
3175 del_name_inter(m_targetNameKey);
3176 } else {
3177 const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
3178 if (compIdx != BadIndex) {
3179 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut(compIdx, m_rowSrc));
3180 GAIA_ASSERT(core::check_alignment(pDesc));
3181 if (pDesc->name != nullptr) {
3182 del_name_inter(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0));
3183 pDesc->name = nullptr;
3184 }
3185 } else {
3186 // Make sure EntityDesc is added to the entity.
3187 add_inter(GAIA_ID(EntityDesc));
3188 }
3189 }
3190
3191 // Insert the new pair
3192 it = m_world.m_nameToEntity.emplace(key, m_entity).first;
3193 } else {
3194#if GAIA_ASSERT_ENABLED
3195 if (it->second != m_entity && World::s_enableUniqueNameDuplicateAssert)
3196 GAIA_ASSERT(false && "Trying to set non-unique name for an entity");
3197#endif
3198
3199 // Attempts to set the same name again, or not a unique name, will be dropped.
3200 return;
3201 }
3202
3203 if constexpr (IsOwned) {
3204 // Allocate enough storage for the name
3205 char* entityStr = (char*)mem::mem_alloc(key.len() + 1);
3206 memcpy((void*)entityStr, (const void*)name, key.len());
3207 entityStr[key.len()] = 0;
3208
3209 m_targetNameKey = EntityNameLookupKey(entityStr, key.len(), 1, {key.hash()});
3210
3211 // Update the map so it points to the newly allocated string.
3212 // We replace the pointer we provided in try_emplace with an internally allocated string.
3213 auto p = robin_hood::pair(std::make_pair(m_targetNameKey, m_entity));
3214 it->swap(p);
3215 } else {
3216 m_targetNameKey = key;
3217
3218 // We tell the map the string is non-owned.
3219 auto p = robin_hood::pair(std::make_pair(key, m_entity));
3220 it->swap(p);
3221 }
3222
3223 m_world.invalidate_scope_path_cache();
3224 }
3225
3230 template <bool IsOwned>
3231 void alias_inter(const char* alias, uint32_t len) {
3233 GAIA_ASSERT(!m_entity.pair());
3234 if (m_entity.pair())
3235 return;
3236
3237 // When nullptr is passed for the alias it means the user wants to delete the current one
3238 if (alias == nullptr) {
3239 GAIA_ASSERT(len == 0);
3240 del_alias();
3241 return;
3242 }
3243
3244 GAIA_ASSERT(len < ComponentCacheItem::MaxNameLength);
3245
3246 // Make sure the name does not contain a dot because this character is reserved for
3247 // hierarchical lookups, e.g. "parent.child.subchild".
3248 GAIA_FOR(len) {
3249 const bool hasInvalidCharacter = alias[i] == '.';
3250 GAIA_ASSERT(!hasInvalidCharacter && "Character '.' can't be used in entity aliases");
3251 if (hasInvalidCharacter)
3252 return;
3253 }
3254
3255 EntityNameLookupKey key(
3256 alias, len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len, IsOwned);
3257
3258 auto it = m_world.m_aliasToEntity.find(key);
3259 if (it == m_world.m_aliasToEntity.end()) {
3260 // If we already had some alias, remove the pair from the map first.
3261 if (m_targetAliasKey.str() != nullptr) {
3262 del_alias_inter(m_targetAliasKey);
3263 } else {
3264 const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
3265 if (compIdx != BadIndex) {
3266 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut(compIdx, m_rowSrc));
3267 GAIA_ASSERT(core::check_alignment(pDesc));
3268 if (pDesc->alias != nullptr) {
3269 del_alias_inter(EntityNameLookupKey(pDesc->alias, pDesc->alias_len, 0));
3270 pDesc->alias = nullptr;
3271 }
3272 } else {
3273 // Make sure EntityDesc is added to the entity.
3274 add_inter(GAIA_ID(EntityDesc));
3275 }
3276 }
3277
3278 it = m_world.m_aliasToEntity.emplace(key, m_entity).first;
3279 } else {
3280#if GAIA_ASSERT_ENABLED
3281 if (it->second != m_entity && World::s_enableUniqueNameDuplicateAssert)
3282 GAIA_ASSERT(false && "Trying to set non-unique alias for an entity");
3283#endif
3284
3285 // Attempts to set the same alias again, or not a unique alias, will be dropped.
3286 return;
3287 }
3288
3289 if constexpr (IsOwned) {
3290 // Allocate enough storage for the alias
3291 char* aliasStr = (char*)mem::mem_alloc(key.len() + 1);
3292 memcpy((void*)aliasStr, (const void*)alias, key.len());
3293 aliasStr[key.len()] = 0;
3294
3295 m_targetAliasKey = EntityNameLookupKey(aliasStr, key.len(), 1, {key.hash()});
3296
3297 // Update the map so it points to the newly allocated string.
3298 // We replace the pointer we provided in try_emplace with an internally allocated string.
3299 auto p = robin_hood::pair(std::make_pair(m_targetAliasKey, m_entity));
3300 it->swap(p);
3301 } else {
3302 m_targetAliasKey = key;
3303
3304 // We tell the map the string is non-owned.
3305 auto p = robin_hood::pair(std::make_pair(key, m_entity));
3306 it->swap(p);
3307 }
3308 }
3309 };
3310
3311 //----------------------------------------------------------------------
3312
3315 GAIA_NODISCARD ComponentCache& comp_cache_mut() {
3316 return m_compCache;
3317 }
3318
3321 GAIA_NODISCARD const ComponentCache& comp_cache() const {
3322 return m_compCache;
3323 }
3324
3325 //----------------------------------------------------------------------
3326
3331 GAIA_NODISCARD Entity symbol(const char* symbol, uint32_t len = 0) const {
3332 if (symbol == nullptr || symbol[0] == 0)
3333 return EntityBad;
3334
3335 const auto* pItem = comp_cache().symbol(symbol, len);
3336 return pItem != nullptr ? pItem->entity : EntityBad;
3337 }
3338
3342 GAIA_NODISCARD util::str_view symbol(Entity component) const {
3343 const auto* pItem = comp_cache().find(component);
3344 return pItem != nullptr ? comp_cache().symbol_name(*pItem) : util::str_view{};
3345 }
3346
3351 GAIA_NODISCARD Entity path(const char* path, uint32_t len = 0) const {
3352 if (path == nullptr || path[0] == 0)
3353 return EntityBad;
3354
3355 const auto* pItem = comp_cache().path(path, len);
3356 return pItem != nullptr ? pItem->entity : EntityBad;
3357 }
3358
3362 GAIA_NODISCARD util::str_view path(Entity component) const {
3363 const auto* pItem = comp_cache().find(component);
3364 return pItem != nullptr ? comp_cache().path_name(*pItem) : util::str_view{};
3365 }
3366
3372 bool path(Entity component, const char* path, uint32_t len = 0) {
3373 auto* pItem = comp_cache_mut().find(component);
3374 return pItem != nullptr ? comp_cache_mut().path(*pItem, path, len) : false;
3375 }
3376
3381 GAIA_NODISCARD Entity alias(const char* alias, uint32_t len = 0) const {
3382 if (alias == nullptr || alias[0] == 0)
3383 return EntityBad;
3384
3385 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
3386 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
3387 const auto it = m_aliasToEntity.find(EntityNameLookupKey(alias, l, 0));
3388 return it != m_aliasToEntity.end() ? it->second : EntityBad;
3389 }
3390
3394 GAIA_NODISCARD util::str_view alias(Entity entity) const {
3395 if (entity.pair())
3396 return {};
3397
3398 const auto& ec = m_recs.entities[entity.id()];
3399 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
3400 if (compIdx == BadIndex)
3401 return {};
3402
3403 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
3404 GAIA_ASSERT(core::check_alignment(pDesc));
3405 return {pDesc->alias, pDesc->alias_len};
3406 }
3407
3413 bool alias(Entity entity, const char* alias, uint32_t len = 0) {
3414 if (!valid(entity) || entity.pair())
3415 return false;
3416
3417 const auto before = this->alias(entity);
3418 EntityBuilder(*this, entity).alias(alias, len);
3419 const auto after = this->alias(entity);
3420 if (alias == nullptr)
3421 return !before.empty() && after.empty();
3422
3423 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
3424 return after == util::str_view(alias, l);
3425 }
3426
3432 bool alias_raw(Entity entity, const char* alias, uint32_t len = 0) {
3433 if (!valid(entity) || entity.pair())
3434 return false;
3435
3436 const auto before = this->alias(entity);
3437 EntityBuilder(*this, entity).alias_raw(alias, len);
3438 const auto after = this->alias(entity);
3439 if (alias == nullptr)
3440 return !before.empty() && after.empty();
3441
3442 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
3443 return after == util::str_view(alias, l);
3444 }
3445
3450 GAIA_NODISCARD util::str_view display_name(Entity entity) const {
3451 const auto* pItem = comp_cache().find(entity);
3452 if (pItem == nullptr)
3453 return {};
3454
3455 const auto aliasValue = alias(entity);
3456 if (!aliasValue.empty())
3457 return aliasValue;
3458
3459 const auto pathValue = path(entity);
3460 if (!pathValue.empty()) {
3461 const auto symbolEntity = symbol(pathValue.data(), pathValue.size());
3462 if (symbolEntity == EntityBad || symbolEntity == entity)
3463 return pathValue;
3464 }
3465
3466 return symbol(entity);
3467 }
3468
3469 //----------------------------------------------------------------------
3470
3471 private:
3473 void invalidate_scope_path_cache() const {
3474 m_componentScopePathCache.clear();
3475 m_componentScopePathCacheEntity = EntityBad;
3476 m_componentScopePathCacheValid = false;
3477 }
3478
3483 GAIA_NODISCARD bool build_scope_path(Entity scope, util::str& out) const {
3484 out.clear();
3485 if (!valid(scope) || scope.pair())
3486 return false;
3487
3489 auto curr = scope;
3490 while (curr != EntityBad) {
3491 const auto currName = name(curr);
3492 if (currName.empty()) {
3493 out.clear();
3494 return false;
3495 }
3496
3497 segments.push_back(currName);
3498 curr = target(curr, ChildOf);
3499 }
3500
3501 if (segments.empty())
3502 return false;
3503
3504 uint32_t totalLen = 0;
3505 for (auto segment: segments)
3506 totalLen += segment.size();
3507 totalLen += (uint32_t)segments.size() - 1;
3508
3509 out.reserve(totalLen);
3510 for (uint32_t i = (uint32_t)segments.size(); i > 0; --i) {
3511 if (!out.empty())
3512 out.append('.');
3513 out.append(segments[i - 1]);
3514 }
3515
3516 return true;
3517 }
3518
3522 GAIA_NODISCARD bool current_scope_path(util::str& out) const {
3523 if (m_componentScope == EntityBad) {
3524 invalidate_scope_path_cache();
3525 out.clear();
3526 return false;
3527 }
3528
3529 if (m_componentScopePathCacheValid && m_componentScopePathCacheEntity == m_componentScope) {
3530 out.assign(m_componentScopePathCache.view());
3531 return true;
3532 }
3533
3534 if (!build_scope_path(m_componentScope, out)) {
3535 invalidate_scope_path_cache();
3536 return false;
3537 }
3538
3539 m_componentScopePathCache.assign(out.view());
3540 m_componentScopePathCacheEntity = m_componentScope;
3541 m_componentScopePathCacheValid = true;
3542 return true;
3543 }
3544
3550 GAIA_NODISCARD const ComponentCacheItem*
3551 find_comp_scope_chain_inter(Entity scopeEntity, const char* name, uint32_t len) const {
3552 if (scopeEntity == EntityBad)
3553 return nullptr;
3554
3555 util::str scopePath;
3556 if (!build_scope_path(scopeEntity, scopePath))
3557 return nullptr;
3558
3559 util::str scopedName;
3560 scopedName.reserve(scopePath.size() + 1 + len);
3561
3562 while (!scopePath.empty()) {
3563 scopedName.clear();
3564 scopedName.append(scopePath.view());
3565 scopedName.append('.');
3566 scopedName.append(name, len);
3567
3568 if (const auto* pItem = m_compCache.path(scopedName.data(), (uint32_t)scopedName.size()); pItem != nullptr)
3569 return pItem;
3570
3571 const auto parentSepIdx = scopePath.view().find_last_of('.');
3572 if (parentSepIdx == BadIndex)
3573 break;
3574
3575 scopePath.assign(util::str_view(scopePath.data(), parentSepIdx));
3576 }
3577
3578 return nullptr;
3579 }
3580
3586 void add_comp_scope_chain_hits_inter(
3587 cnt::darray<Entity>& out, Entity scopeEntity, const char* name, uint32_t len) const {
3588 if (scopeEntity == EntityBad)
3589 return;
3590
3591 util::str scopePath;
3592 if (!build_scope_path(scopeEntity, scopePath))
3593 return;
3594
3595 util::str scopedName;
3596 scopedName.reserve(scopePath.size() + 1 + len);
3597
3598 while (!scopePath.empty()) {
3599 scopedName.clear();
3600 scopedName.append(scopePath.view());
3601 scopedName.append('.');
3602 scopedName.append(name, len);
3603
3604 if (const auto* pItem = m_compCache.path(scopedName.data(), (uint32_t)scopedName.size()); pItem != nullptr)
3605 ComponentCache::push_unique_entity(out, pItem->entity);
3606
3607 const auto parentSepIdx = scopePath.view().find_last_of('.');
3608 if (parentSepIdx == BadIndex)
3609 break;
3610
3611 scopePath.assign(util::str_view(scopePath.data(), parentSepIdx));
3612 }
3613 }
3614
3619 GAIA_NODISCARD const ComponentCacheItem* find_comp_lookup_inter(const char* name, uint32_t len) const {
3620 if (const auto* pItem = find_comp_scope_chain_inter(m_componentScope, name, len); pItem != nullptr)
3621 return pItem;
3622
3623 for (const auto scopeEntity: m_componentLookupPath) {
3624 if (scopeEntity == m_componentScope)
3625 continue;
3626
3627 if (const auto* pItem = find_comp_scope_chain_inter(scopeEntity, name, len); pItem != nullptr)
3628 return pItem;
3629 }
3630
3631 return nullptr;
3632 }
3633
3638 void add_comp_lookup_hits_inter(cnt::darray<Entity>& out, const char* name, uint32_t len) const {
3639 add_comp_scope_chain_hits_inter(out, m_componentScope, name, len);
3640 for (const auto scopeEntity: m_componentLookupPath) {
3641 if (scopeEntity == m_componentScope)
3642 continue;
3643
3644 add_comp_scope_chain_hits_inter(out, scopeEntity, name, len);
3645 }
3646 }
3647
3654 GAIA_NODISCARD const ComponentCacheItem*
3655 find_comp_exact_inter(const char* name, uint32_t len, bool isPath, bool isSymbol) const {
3656 if (const auto* pItem = m_compCache.symbol(name, len); pItem != nullptr)
3657 return pItem;
3658
3659 if (!isPath) {
3660 if (const auto* pItem = m_compCache.path(name, len); pItem != nullptr)
3661 return pItem;
3662 if (!isSymbol) {
3663 if (const auto* pItem = m_compCache.short_symbol(name, len); pItem != nullptr)
3664 return pItem;
3665 }
3666 }
3667
3668 return nullptr;
3669 }
3670
3677 void add_comp_exact_hits_inter(
3678 cnt::darray<Entity>& out, const char* name, uint32_t len, bool isPath, bool isSymbol) const {
3679 if (const auto* pItem = m_compCache.symbol(name, len); pItem != nullptr)
3680 ComponentCache::push_unique_entity(out, pItem->entity);
3681
3682 m_compCache.add_path_matches(out, util::str_view(name, len));
3683
3684 if (out.empty() && !isPath && !isSymbol) {
3685 if (const auto* pItem = m_compCache.short_symbol(name, len); pItem != nullptr)
3686 ComponentCache::push_unique_entity(out, pItem->entity);
3687 }
3688 }
3689
3692 GAIA_NODISCARD bool has_comp_lookup_ctx_inter() const noexcept {
3693 return m_componentScope != EntityBad || !m_componentLookupPath.empty();
3694 }
3695
3700 GAIA_NODISCARD static bool is_unqualified_comp_name_inter(const char* name, uint32_t len) noexcept {
3701 return memchr(name, '.', len) == nullptr && memchr(name, ':', len) == nullptr;
3702 }
3703
3708 GAIA_NODISCARD Entity pick_name_or_comp_inter(Entity namedEntity, const ComponentCacheItem* pCompItem) const {
3709 if (pCompItem == nullptr)
3710 return namedEntity;
3711
3712 if (namedEntity == EntityBad)
3713 return pCompItem->entity;
3714
3715 return m_compCache.find(namedEntity) != nullptr ? pCompItem->entity : namedEntity;
3716 }
3717
3725 GAIA_NODISCARD const ComponentCacheItem* resolve_component_name_inter(const char* name, uint32_t len = 0) const {
3726 GAIA_ASSERT(name != nullptr);
3727
3728 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
3729 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
3730 const bool isPath = memchr(name, '.', l) != nullptr;
3731 const bool isSymbol = memchr(name, ':', l) != nullptr;
3732
3733 if (isPath) {
3734 if (const auto* pItem = m_compCache.path(name, l); pItem != nullptr)
3735 return pItem;
3736 }
3737
3738 if (!isPath && !isSymbol) {
3739 if (const auto* pItem = find_comp_lookup_inter(name, l); pItem != nullptr)
3740 return pItem;
3741 }
3742
3743 if (const auto* pItem = find_comp_exact_inter(name, l, isPath, isSymbol); pItem != nullptr)
3744 return pItem;
3745
3746 const auto aliasEntity = alias(name, l);
3747 return aliasEntity != EntityBad ? m_compCache.find(aliasEntity) : nullptr;
3748 }
3749
3750 public:
3751 //----------------------------------------------------------------------
3752
3756 GAIA_NODISCARD bool valid(Entity entity) const {
3757 return entity.pair() //
3758 ? valid_pair(entity)
3759 : valid_entity(entity);
3760 }
3761
3762 //----------------------------------------------------------------------
3763
3767 GAIA_NODISCARD Entity get(EntityId id) const {
3768 // Cleanup, observer propagation, and wildcard expansion can briefly encounter stale ids.
3769 // Treat those as absent instead of crashing the world.
3770 if (!valid_entity_id(id))
3771 return EntityBad;
3772
3773 const auto& ec = m_recs.entities[id];
3774 return Entity(id, ec.data.gen, (bool)ec.data.ent, (bool)ec.data.pair, (EntityKind)ec.data.kind);
3775 }
3776
3780 GAIA_NODISCARD Entity try_get(EntityId id) const {
3781 return valid_entity_id(id) ? get(id) : EntityBad;
3782 }
3783
3787 template <typename T>
3788 GAIA_NODISCARD Entity get() const {
3789 return comp_cache().get<T>().entity;
3790 }
3791
3795 template <typename T>
3796 GAIA_NODISCARD const ComponentCacheItem& reg_comp() {
3797#if GAIA_ECS_AUTO_COMPONENT_REGISTRATION
3798 return add<T>();
3799#else
3800 return comp_cache().template get<T>();
3801#endif
3802 }
3803
3804 //----------------------------------------------------------------------
3805
3811 return EntityBuilder(*this, entity);
3812 }
3813
3817 GAIA_NODISCARD Entity add(EntityKind kind = EntityKind::EK_Gen) {
3818 return add(*m_pEntityArchetype, true, false, kind);
3819 }
3820
3824 GAIA_NODISCARD Entity prefab(EntityKind kind = EntityKind::EK_Gen) {
3825 const auto entity = add(kind);
3826 add(entity, Prefab);
3827 return entity;
3828 }
3829
3833 template <typename Func = TFunc_Void_With_Entity>
3834 void add_n(uint32_t count, Func func = func_void_with_entity) {
3835 add_entity_n(*m_pEntityArchetype, count, func);
3836 }
3837
3843 template <typename Func = TFunc_Void_With_Entity>
3844 void add_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
3845 auto& ec = m_recs.entities[entity.id()];
3846
3847 GAIA_ASSERT(ec.pArchetype != nullptr);
3848 GAIA_ASSERT(ec.pChunk != nullptr);
3849
3850 add_entity_n(*ec.pArchetype, count, func);
3851 }
3852
3856 template <typename T>
3857 GAIA_NODISCARD const ComponentCacheItem& add() {
3858 static_assert(!is_pair<T>::value, "Pairs can't be registered as components");
3859
3860 using CT = component_type_t<T>;
3861 using FT = typename CT::TypeFull;
3862 constexpr auto kind = CT::Kind;
3863
3864 const auto* pItem = comp_cache().find<FT>();
3865 if (pItem != nullptr)
3866 return *pItem;
3867
3868 const auto entity = add(*m_pCompArchetype, false, false, kind);
3869 util::str scopePath;
3870 (void)current_scope_path(scopePath);
3871
3872 const auto& item = comp_cache_mut().add<FT>(entity, scopePath.view());
3873 item.func_create_sparse_store = [](World& world, Entity component) {
3874 (void)world.sparse_component_store_mut<FT>(component);
3875 };
3876 finalize_component_registration(item, item.comp.storage_type() == DataStorageType::Sparse);
3877 if constexpr (supports_sparse_component_storage<FT>()) {
3878 if (item.comp.storage_type() == DataStorageType::Sparse)
3879 (void)sparse_component_store_mut<FT>(item.entity);
3880 }
3881
3882 return item;
3883 }
3884
3891 template <typename T>
3892 GAIA_NODISCARD const ComponentCacheItem& add(const RuntimeTypeDesc& runtimeType) {
3893 static_assert(!is_pair<T>::value, "Pairs can't be registered as components");
3894
3895 using CT = component_type_t<T>;
3896 using FT = typename CT::TypeFull;
3897 constexpr auto kind = CT::Kind;
3898
3899 const auto* pItem = comp_cache().find<FT>();
3900 if (pItem != nullptr)
3901 return *pItem;
3902
3903 validate_runtime_semantics(runtimeType);
3904 const auto entity = add(*m_pCompArchetype, false, false, kind);
3905 util::str scopePath;
3906 (void)current_scope_path(scopePath);
3907
3908 const auto& item = comp_cache_mut().add<FT>(entity, runtimeType, scopePath.view());
3909 item.func_create_sparse_store = [](World& world, Entity component) {
3910 (void)world.sparse_component_store_mut<FT>(component);
3911 };
3912 finalize_component_registration(item, item.comp.storage_type() == DataStorageType::Sparse);
3913 if constexpr (supports_sparse_component_storage<FT>()) {
3914 if (item.comp.storage_type() == DataStorageType::Sparse)
3915 (void)sparse_component_store_mut<FT>(item.entity);
3916 }
3917
3918 return item;
3919 }
3920
3925 GAIA_NODISCARD ComponentCacheItem& add(const ComponentDesc& desc, EntityKind kind = EntityKind::EK_Gen) {
3926 GAIA_ASSERT(!desc.name.empty());
3927 GAIA_ASSERT(desc.name.size() < ComponentCacheItem::MaxNameLength);
3928
3929 if (const auto* pItem = comp_cache().symbol(desc.name); pItem != nullptr)
3930 return *comp_cache_mut().find(pItem->entity);
3931
3932 validate_runtime_semantics(desc.runtimeType);
3933 const auto entity = add(*m_pCompArchetype, false, false, kind);
3934 util::str scopePath;
3935 (void)current_scope_path(scopePath);
3936 auto& itemInfo = comp_cache_mut().add(entity, desc, scopePath.view());
3937 finalize_component_registration(itemInfo, true);
3938 return itemInfo;
3939 }
3940
3945 void add(Entity entity, Entity object) {
3946#if GAIA_ASSERT_ENABLED
3947 if (!object.pair()) {
3948 const auto* pItem = comp_cache().find(object);
3949 if (pItem != nullptr && pItem->entity == object && component_uses_sparse_storage(object))
3950 GAIA_ASSERT2(
3951 false, "Out-of-line runtime components require an explicit typed value when added by entity id");
3952 }
3953#endif
3954 EntityBuilder(*this, entity).add(object);
3955 }
3956
3962 void add(Entity entity, Pair pair) {
3963 auto& ec = m_recs.entities[entity.id()];
3964 EntityBuilder builder(*this, entity, ec);
3965 builder.add(pair);
3966 builder.commit();
3967 }
3968
3974 template <typename T>
3975 void add(Entity entity) {
3976 using FT = typename component_type_t<T>::TypeFull;
3977 const auto& item = add<FT>();
3978 if constexpr (uses_compile_time_sparse_storage<FT>()) {
3979 (void)sparse_component_store_mut<FT>(item.entity).add(entity);
3980 finish_sparse_component_add_inter(entity, item.entity, compile_time_sparse_storage_mode(item.entity));
3981 return;
3982 }
3983
3984 EntityBuilder(*this, entity).add<T>();
3985 }
3986
3994 template <typename T>
3995 void add(Entity entity, Entity object, T&& value) {
3996 static_assert(core::is_raw_v<T>);
3997
3998 if constexpr (supports_sparse_component_storage<typename component_type_t<T>::TypeFull>()) {
3999 using FT = typename component_type_t<T>::TypeFull;
4000 if (can_use_sparse_component_storage<FT>(object)) {
4001 const auto mode = sparse_storage_mode(object);
4002 if (mode != SparseStorageMode::None) {
4003 auto& data = sparse_component_add_value<FT>(object, entity);
4004 data = GAIA_FWD(value);
4005 finish_sparse_component_add_inter(entity, object, mode);
4006 return;
4007 }
4008 }
4009 }
4010
4011 EntityBuilder eb(*this, entity);
4012#if GAIA_OBSERVERS_ENABLED
4013 auto addDiffCtx =
4014 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
4015#endif
4016 eb.add_inter_init(object);
4017 eb.commit();
4018
4019 const auto& ec = fetch(entity);
4020 // Make sure the idx is 0 for unique entities
4021 const auto idx = uint16_t(ec.row * (1U - (uint32_t)object.kind()));
4022 ComponentSetter{*this, ec.pChunk, entity, idx}.sset(object, GAIA_FWD(value));
4023 notify_add_single(entity, object);
4024#if GAIA_OBSERVERS_ENABLED
4025 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4026#endif
4027 }
4028
4035 template <typename T, typename U = typename actual_type_t<T>::Type>
4036 void add(Entity entity, U&& value) {
4037 using FT = typename component_type_t<T>::TypeFull;
4038 if constexpr (uses_compile_time_sparse_storage<FT>()) {
4039 const auto& item = add<FT>();
4040 auto& data = sparse_component_store_mut<FT>(item.entity).add(entity);
4041 data = GAIA_FWD(value);
4042 finish_sparse_component_add_inter(entity, item.entity, compile_time_sparse_storage_mode(item.entity));
4043 return;
4044 }
4045
4046 EntityBuilder builder(*this, entity);
4047 auto object = builder.register_component<T>();
4048#if GAIA_OBSERVERS_ENABLED
4049 auto addDiffCtx =
4050 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
4051#endif
4052 // Materialize the component first, write the initial value, and only then dispatch OnAdd.
4053 // This keeps observer-visible state aligned with the final stored payload.
4054 builder.add_inter_init(object);
4055 builder.commit();
4056
4057 const auto& ec = m_recs.entities[entity.id()];
4058 // Make sure the idx is 0 for unique payload storage.
4059 const auto idx = uint16_t(ec.row * (actual_type_t<T>::Kind == EntityKind::EK_Gen));
4060 ComponentSetter{*this, ec.pChunk, entity, idx}.sset<T>(GAIA_FWD(value));
4061 notify_add_single(entity, object);
4062#if GAIA_OBSERVERS_ENABLED
4063 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4064#endif
4065 }
4066
4071 GAIA_NODISCARD bool override(Entity entity, Entity object) {
4072 return override_inter(entity, object);
4073 }
4074
4079 GAIA_NODISCARD bool override(Entity entity, Pair pair) {
4080 return override_inter(entity, (Entity)pair);
4081 }
4082
4087 template <typename T>
4088 GAIA_NODISCARD bool override(Entity entity) {
4089 static_assert(!is_pair<T>::value);
4090 using FT = typename component_type_t<T>::TypeFull;
4091 const auto& item = add<FT>();
4092
4093 if constexpr (uses_compile_time_sparse_storage<FT>())
4094 return override_sparse_component_inter(entity, item.entity);
4095
4096 return override_inter(entity, item.entity);
4097 }
4098
4104 template <typename T>
4105 GAIA_NODISCARD bool override(Entity entity, Entity object) {
4106 static_assert(!is_pair<T>::value);
4107 using FT = typename component_type_t<T>::TypeFull;
4108
4109 if constexpr (supports_sparse_component_storage<FT>()) {
4110 if (can_use_sparse_component_storage<FT>(object))
4111 return override_sparse_component_inter(entity, object);
4112 }
4113
4114 return override_inter(entity, object);
4115 }
4116
4117 //----------------------------------------------------------------------
4118
4123 void clear(Entity entity) {
4124 GAIA_ASSERT(!entity.pair());
4125 GAIA_ASSERT(valid(entity));
4126
4127 EntityBuilder eb(*this, entity);
4128
4129 // Remove back to front because it's better for the archetype graph
4130 auto ids = eb.m_pArchetype->ids_view();
4131 for (uint32_t i = (uint32_t)ids.size() - 1; i != (uint32_t)-1; --i)
4132 eb.del(ids[i]);
4133
4134 eb.commit();
4135 }
4136
4137 //----------------------------------------------------------------------
4138
4146 GAIA_NODISCARD Entity copy(Entity srcEntity) {
4147 GAIA_ASSERT(!srcEntity.pair());
4148 GAIA_ASSERT(valid(srcEntity));
4149
4150 auto& ec = m_recs.entities[srcEntity.id()];
4151 GAIA_ASSERT(ec.pArchetype != nullptr);
4152 GAIA_ASSERT(ec.pChunk != nullptr);
4153
4154 auto* pDstArchetype = ec.pArchetype;
4155 Entity dstEntity;
4156
4157 // Names have to be unique so if we see that EntityDesc is present during copy
4158 // we navigate towards a version of the archetype without the EntityDesc.
4159 if (pDstArchetype->has<EntityDesc>()) {
4160 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4161
4162 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
4163 auto& ecDst = m_recs.entities[dstEntity.id()];
4164 Chunk::copy_foreign_entity_data(ec.pChunk, ec.row, ecDst.pChunk, ecDst.row);
4165 } else {
4166 // No description associated with the entity, direct copy is possible
4167 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
4168 Chunk::copy_entity_data(srcEntity, dstEntity, m_recs);
4169 }
4170
4171 copy_all_sparse_entity_data(srcEntity, dstEntity);
4172
4173 return dstEntity;
4174 }
4175
4185 template <typename Func = TFunc_Void_With_Entity>
4186 void copy_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
4187 copy_n_inter(entity, count, func, EntitySpan{});
4188 }
4189
4190#if GAIA_OBSERVERS_ENABLED
4198 GAIA_NODISCARD Entity copy_ext(Entity srcEntity) {
4199 GAIA_ASSERT(!srcEntity.pair());
4200 GAIA_ASSERT(valid(srcEntity));
4201
4202 auto& ec = m_recs.entities[srcEntity.id()];
4203 GAIA_ASSERT(ec.pArchetype != nullptr);
4204 GAIA_ASSERT(ec.pChunk != nullptr);
4205
4206 auto* pDstArchetype = ec.pArchetype;
4207 // Names have to be unique so if we see that EntityDesc is present during copy
4208 // we navigate towards a version of the archetype without the EntityDesc.
4209 const bool hasEntityDesc = pDstArchetype->has<EntityDesc>();
4210 if (hasEntityDesc)
4211 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4212
4213 const auto archetypeIdCount = (uint32_t)pDstArchetype->ids_view().size();
4214 const auto sparseIdCount = copied_non_frag_sparse_id_count(srcEntity);
4215 const auto addedIdCount = archetypeIdCount + sparseIdCount;
4216 auto* pAddedIds = addedIdCount != 0U ? (Entity*)alloca(sizeof(Entity) * addedIdCount) : nullptr;
4217 write_archetype_ids(*pDstArchetype, pAddedIds);
4218 write_copied_non_frag_sparse_ids(srcEntity, pAddedIds + archetypeIdCount);
4219 #if GAIA_OBSERVERS_ENABLED
4220 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{pAddedIds, addedIdCount});
4221 #endif
4222
4223 Entity dstEntity;
4224 if (hasEntityDesc) {
4225 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
4226 auto& ecDst = m_recs.entities[dstEntity.id()];
4227 Chunk::copy_foreign_entity_data(ec.pChunk, ec.row, ecDst.pChunk, ecDst.row);
4228 } else {
4229 // No description associated with the entity, direct copy is possible
4230 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
4231 Chunk::copy_entity_data(srcEntity, dstEntity, m_recs);
4232 }
4233
4234 (void)copy_all_sparse_entity_data(srcEntity, dstEntity);
4235 m_observers.add_diff_targets(*this, addDiffCtx, EntitySpan{&dstEntity, 1});
4236
4237 m_observers.on_add(*this, *pDstArchetype, EntitySpan{pAddedIds, addedIdCount}, EntitySpan{&dstEntity, 1});
4238 #if GAIA_OBSERVERS_ENABLED
4239 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4240 #endif
4241
4242 return dstEntity;
4243 }
4244
4254 template <typename Func = TFunc_Void_With_Entity>
4255 void copy_ext_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
4256 auto& ec = m_recs.entities[entity.id()];
4257 auto* pDstArchetype = ec.pArchetype;
4258 if (pDstArchetype->has<EntityDesc>())
4259 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4260
4261 const auto archetypeIdCount = (uint32_t)pDstArchetype->ids_view().size();
4262 const auto sparseIdCount = copied_non_frag_sparse_id_count(entity);
4263 const auto addedIdCount = archetypeIdCount + sparseIdCount;
4264 auto* pAddedIds = addedIdCount != 0U ? (Entity*)alloca(sizeof(Entity) * addedIdCount) : nullptr;
4265 write_archetype_ids(*pDstArchetype, pAddedIds);
4266 write_copied_non_frag_sparse_ids(entity, pAddedIds + archetypeIdCount);
4267 #if GAIA_OBSERVERS_ENABLED
4268 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{pAddedIds, addedIdCount});
4269 #endif
4270 copy_n_inter(
4271 entity, count, func, EntitySpan{pAddedIds, addedIdCount}, EntityBad
4272 #if GAIA_OBSERVERS_ENABLED
4273 ,
4274 &addDiffCtx
4275 #endif
4276 );
4277 #if GAIA_OBSERVERS_ENABLED
4278 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4279 #endif
4280 }
4281#endif
4282
4283 private:
4291 template <typename Func>
4292 void invoke_copy_batch_callback(
4293 Func& func, Archetype* pDstArchetype, Chunk* pDstChunk, uint32_t originalChunkSize, uint32_t toCreate) {
4294 if constexpr (std::is_invocable_v<Func, CopyIter&>) {
4295 CopyIter it;
4296 it.set_world(this);
4297 it.set_archetype(pDstArchetype);
4298 it.set_chunk(pDstChunk);
4299 it.set_range((uint16_t)originalChunkSize, (uint16_t)toCreate);
4300 func(it);
4301 } else {
4302 auto entities = pDstChunk->entity_view();
4303 GAIA_FOR2(originalChunkSize, pDstChunk->size()) func(entities[i]);
4304 }
4305 }
4306
4311 template <typename Func>
4312 void flush_copy_iter_group(Func& func, CopyIterGroupState& group) {
4313 if (group.count == 0)
4314 return;
4315
4316 CopyIter it;
4317 it.set_world(this);
4318 it.set_archetype(group.pArchetype);
4319 it.set_chunk(group.pChunk);
4320 it.set_range(group.startRow, group.count);
4321 func(it);
4322 group.count = 0;
4323 }
4324
4330 template <typename Func>
4331 void push_copy_iter_group(Func& func, CopyIterGroupState& group, Entity instance) {
4332 const auto& ec = fetch(instance);
4333
4334 if (group.count != 0 && ec.pArchetype == group.pArchetype && ec.pChunk == group.pChunk &&
4335 ec.row == uint16_t(group.startRow + group.count)) {
4336 ++group.count;
4337 return;
4338 }
4339
4340 flush_copy_iter_group(func, group);
4341 group.pArchetype = ec.pArchetype;
4342 group.pChunk = ec.pChunk;
4343 group.startRow = ec.row;
4344 group.count = 1;
4345 }
4346
4350 void prepare_parent_batch(Entity parentEntity, const NonFragmentingRelationStore& parentStore) {
4351 GAIA_ASSERT(valid(parentEntity));
4352 if (parentStore.sources(parentEntity) != nullptr)
4353 return;
4354
4355 const auto parentPair = Pair(Parent, parentEntity);
4356 assign_pair(parentPair, *m_pEntityArchetype);
4357
4358 auto& ecParent = fetch(parentEntity);
4359 EntityBuilder::set_flag(ecParent.flags, EntityContainerFlags::OnDeleteTarget_Delete, true);
4360 }
4361
4364 void prepare_parent_batch(Entity parentEntity) {
4365 const auto* pStore = nonfragmenting_relation_store(Parent);
4366 if (pStore != nullptr) {
4367 prepare_parent_batch(parentEntity, *pStore);
4368 return;
4369 }
4370
4371 GAIA_ASSERT(valid(parentEntity));
4372 const auto parentPair = Pair(Parent, parentEntity);
4373 assign_pair(parentPair, *m_pEntityArchetype);
4374
4375 auto& ecParent = fetch(parentEntity);
4376 EntityBuilder::set_flag(ecParent.flags, EntityContainerFlags::OnDeleteTarget_Delete, true);
4377 }
4378
4385 void parent_batch(
4386 Entity parentEntity, Archetype& archetype, Chunk& chunk, uint32_t originalChunkSize, uint32_t toCreate) {
4387 GAIA_ASSERT(valid(parentEntity));
4388
4389 if (toCreate == 0)
4390 return;
4391
4392 auto& parentStore = nonfragmenting_relation_store_mut(Parent);
4393 prepare_parent_batch(parentEntity, parentStore);
4394
4395 auto entities = chunk.entity_view();
4396#if GAIA_OBSERVERS_ENABLED
4397 if (!m_observers.has_on_add_observers()) {
4398 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
4399 nonfragmenting_relation_set(parentStore, entities[rowIdx], Parent, parentEntity);
4400 }
4401 return;
4402 }
4403
4404 const Entity parentPair = Pair(Parent, parentEntity);
4405 auto addDiffCtx = m_observers.prepare_diff(
4406 *this, ObserverEvent::OnAdd, EntitySpan{&parentPair, 1},
4407 EntitySpan{entities.data() + originalChunkSize, toCreate});
4408#endif
4409 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
4410 nonfragmenting_relation_set(parentStore, entities[rowIdx], Parent, parentEntity);
4411 }
4412
4413#if GAIA_OBSERVERS_ENABLED
4414 m_observers.on_add(
4415 *this, archetype, EntitySpan{&parentPair, 1}, EntitySpan{entities.data() + originalChunkSize, toCreate});
4416 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4417#endif
4418 }
4419
4423 void parent_direct(Entity entity, Entity parentEntity) {
4424 GAIA_ASSERT(valid(entity));
4425 GAIA_ASSERT(valid(parentEntity));
4426 auto& parentStore = nonfragmenting_relation_store_mut(Parent);
4427 const auto oldParentEntity = parentStore.target(entity);
4428
4429 if (oldParentEntity == parentEntity)
4430 return;
4431
4432#if GAIA_OBSERVERS_ENABLED
4433 const bool hasOnDelObservers = oldParentEntity != EntityBad && m_observers.has_on_del_observers();
4434 const bool hasOnAddObservers = m_observers.has_on_add_observers();
4435 if (!hasOnDelObservers && !hasOnAddObservers) {
4436 prepare_parent_batch(parentEntity, parentStore);
4437 nonfragmenting_relation_set(parentStore, entity, Parent, parentEntity);
4438 return;
4439 }
4440
4441 const Entity oldParentPair = hasOnDelObservers ? Pair(Parent, oldParentEntity) : EntityBad;
4442 const Entity parentPair = Pair(Parent, parentEntity);
4443 auto delDiffCtx = !hasOnDelObservers
4444 ? ObserverRegistry::DiffDispatchCtx{}
4445 : m_observers.prepare_diff(
4446 *this, ObserverEvent::OnDel, EntitySpan{&oldParentPair, 1}, EntitySpan{&entity, 1});
4447 auto addDiffCtx = !hasOnAddObservers
4448 ? ObserverRegistry::DiffDispatchCtx{}
4449 : m_observers.prepare_diff(
4450 *this, ObserverEvent::OnAdd, EntitySpan{&parentPair, 1}, EntitySpan{&entity, 1});
4451 prepare_parent_batch(parentEntity, parentStore);
4452#else
4453 prepare_parent_batch(parentEntity, parentStore);
4454#endif
4455 nonfragmenting_relation_set(parentStore, entity, Parent, parentEntity);
4456
4457#if GAIA_OBSERVERS_ENABLED
4458 const auto& ec = fetch(entity);
4459 if (hasOnDelObservers)
4460 m_observers.on_del(*this, *ec.pArchetype, EntitySpan{&oldParentPair, 1}, EntitySpan{&entity, 1});
4461 if (hasOnAddObservers)
4462 m_observers.on_add(*this, *ec.pArchetype, EntitySpan{&parentPair, 1}, EntitySpan{&entity, 1});
4463 if (hasOnDelObservers)
4464 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
4465 if (hasOnAddObservers)
4466 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4467#endif
4468 }
4469
4473 void notify_add_single(Entity entity, Entity object) {
4474#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
4475 if GAIA_UNLIKELY (tearing_down())
4476 return;
4477
4478 const auto& ec = fetch(entity);
4479
4480 lock();
4481
4482 #if GAIA_ENABLE_ADD_DEL_HOOKS
4483 const auto* pItem = component_item(entity, object);
4484 if (pItem != nullptr) {
4485 const auto& hooks = ComponentCache::hooks(*pItem);
4486 if (hooks.func_add != nullptr)
4487 hooks.func_add(*this, *pItem, entity);
4488 }
4489 #endif
4490
4491 #if GAIA_OBSERVERS_ENABLED
4492 m_observers.on_add(*this, *ec.pArchetype, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
4493 #endif
4494
4495 unlock();
4496#else
4497 (void)entity;
4498 (void)object;
4499#endif
4500 }
4501
4505 void notify_del_single(Entity entity, Entity object) {
4506#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
4507 if GAIA_UNLIKELY (tearing_down())
4508 return;
4509
4510 const auto& ec = fetch(entity);
4511
4512 lock();
4513
4514 #if GAIA_OBSERVERS_ENABLED
4515 m_observers.on_del(*this, *ec.pArchetype, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
4516 #endif
4517
4518 #if GAIA_ENABLE_ADD_DEL_HOOKS
4519 const auto* pItem = component_item(entity, object);
4520 if (pItem != nullptr) {
4521 const auto& hooks = ComponentCache::hooks(*pItem);
4522 if (hooks.func_del != nullptr)
4523 hooks.func_del(*this, *pItem, entity);
4524 }
4525 #endif
4526
4527 unlock();
4528#else
4529 (void)entity;
4530 (void)object;
4531#endif
4532 }
4533
4540 GAIA_NODISCARD bool has_semantic_match_without_source(
4541 Entity entity, Entity object, Entity excludedSource, cnt::set<EntityLookupKey>& visited) const {
4542 const auto inserted = visited.insert(EntityLookupKey(entity));
4543 if (!inserted.second)
4544 return false;
4545
4546 if (entity != excludedSource && has_direct(entity, object))
4547 return true;
4548
4549 const auto it = m_entityToAsTargets.find(EntityLookupKey(entity));
4550 if (it == m_entityToAsTargets.end())
4551 return false;
4552
4553 for (const auto baseKey: it->second) {
4554 if (has_semantic_match_without_source(baseKey.entity(), object, excludedSource, visited))
4555 return true;
4556 }
4557
4558 return false;
4559 }
4560
4564 void notify_inherited_del_dependents(Entity source, Entity object) {
4565#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
4566 const auto& descendants = as_relations_trav_cache(source);
4567 for (const auto descendant: descendants) {
4568 if (descendant == source)
4569 continue;
4570 if (has_direct(descendant, object) || !has(descendant, object))
4571 continue;
4572
4573 cnt::set<EntityLookupKey> visited;
4574 if (has_semantic_match_without_source(descendant, object, source, visited))
4575 continue;
4576
4577 notify_del_single(descendant, object);
4578 }
4579#else
4580 (void)source;
4581 (void)object;
4582#endif
4583 }
4584
4588 void notify_inherited_del_dependents(Entity source, EntitySpan removedObjects) {
4589 for (const auto object: removedObjects)
4590 notify_inherited_del_dependents(source, object);
4591 }
4592
4600#if GAIA_OBSERVERS_ENABLED
4602#endif
4603 template <typename Func>
4604 void copy_n_inter(
4605 Entity entity, uint32_t count, Func& func, EntitySpan addedIds, Entity parentInstance = EntityBad
4606#if GAIA_OBSERVERS_ENABLED
4607 ,
4608 ObserverRegistry::DiffDispatchCtx* pAddDiffCtx = nullptr
4609#endif
4610 ) {
4611 GAIA_ASSERT(!entity.pair());
4612 GAIA_ASSERT(valid(entity));
4613 GAIA_ASSERT(parentInstance == EntityBad || valid(parentInstance));
4614
4615 if (count == 0U)
4616 return;
4617
4618#if GAIA_OBSERVERS_ENABLED
4619 const bool useLocalAddDiff = !addedIds.empty() && pAddDiffCtx == nullptr;
4620 ObserverRegistry::DiffDispatchCtx addDiffCtx{};
4621 if (useLocalAddDiff)
4622 addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{addedIds});
4623#endif
4624
4625 auto& ec = m_recs.entities[entity.id()];
4626
4627 GAIA_ASSERT(ec.pChunk != nullptr);
4628 GAIA_ASSERT(ec.pArchetype != nullptr);
4629
4630 auto* pSrcChunk = ec.pChunk;
4631 auto* pDstArchetype = ec.pArchetype;
4632 const auto hasEntityDesc = pDstArchetype->has<EntityDesc>();
4633 if (hasEntityDesc)
4634 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4635
4636 if (parentInstance != EntityBad)
4637 prepare_parent_batch(parentInstance);
4638
4639 // Entities array might get reallocated after m_recs.entities.alloc
4640 // so instead of fetching the container again we simply cache the row
4641 // of our source entity.
4642 const auto srcRow = ec.row;
4643
4644 EntityContainerCtx ctx{true, false, EntityKind::EK_Gen};
4645
4646 uint32_t left = count;
4647 do {
4648 auto* pDstChunk = pDstArchetype->foc_free_chunk();
4649 const uint32_t originalChunkSize = pDstChunk->size();
4650 const uint32_t freeSlotsInChunk = pDstChunk->capacity() - originalChunkSize;
4651 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
4652
4653 GAIA_FOR(toCreate) {
4654 const auto entityNew = m_recs.entities.alloc(&ctx);
4655 auto& ecNew = m_recs.entities[entityNew.id()];
4656 store_entity(ecNew, entityNew, pDstArchetype, pDstChunk);
4657
4658#if GAIA_ASSERT_ENABLED
4659 GAIA_ASSERT(ecNew.pChunk == pDstChunk);
4660 auto entityExpected = pDstChunk->entity_view()[ecNew.row];
4661 GAIA_ASSERT(entityExpected == entityNew);
4662#endif
4663
4664 if (hasEntityDesc) {
4665 Chunk::copy_foreign_entity_data(pSrcChunk, srcRow, pDstChunk, ecNew.row);
4666 }
4667
4668 copy_all_sparse_entity_data(entity, entityNew);
4669 }
4670
4671 pDstArchetype->try_update_free_chunk_idx();
4672
4673 if (!hasEntityDesc) {
4674 pDstChunk->call_gen_ctors(originalChunkSize, toCreate);
4675
4676 {
4677 GAIA_PROF_SCOPE(World::copy_n_entity_data);
4678 Chunk::copy_entity_data_n_same_chunk(pSrcChunk, srcRow, pDstChunk, originalChunkSize, toCreate);
4679 }
4680 }
4681
4682 pDstChunk->update_versions();
4683
4684#if GAIA_OBSERVERS_ENABLED
4685 if (!addedIds.empty()) {
4686 auto entities = pDstChunk->entity_view();
4687 if (pAddDiffCtx != nullptr)
4688 m_observers.add_diff_targets(
4689 *this, *pAddDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
4690 else if (useLocalAddDiff)
4691 m_observers.add_diff_targets(
4692 *this, addDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
4693 m_observers.on_add(
4694 *this, *pDstArchetype, addedIds, EntitySpan{entities.data() + originalChunkSize, toCreate});
4695 }
4696#endif
4697
4698 if (parentInstance != EntityBad)
4699 parent_batch(parentInstance, *pDstArchetype, *pDstChunk, originalChunkSize, toCreate);
4700
4701 invoke_copy_batch_callback(func, pDstArchetype, pDstChunk, originalChunkSize, toCreate);
4702
4703 left -= toCreate;
4704 } while (left > 0);
4705#if GAIA_OBSERVERS_ENABLED
4706 if (useLocalAddDiff)
4707 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4708#endif
4709 }
4710
4714 GAIA_NODISCARD bool id_uses_inherit_policy(Entity id) const {
4715 return !is_wildcard(id) && valid(id) && target(id, OnInstantiate) == Inherit;
4716 }
4717
4722 GAIA_NODISCARD Entity inherited_id_owner(Entity entity, Entity id) const {
4723 if (!id_uses_inherit_policy(id))
4724 return EntityBad;
4725
4726 const auto& targets = as_targets_trav_cache(entity);
4727 for (const auto target: targets) {
4728 if (has_inter(target, id, false))
4729 return target;
4730 }
4731
4732 return EntityBad;
4733 }
4734
4739 GAIA_NODISCARD bool has_direct_sparse_component_inter(Entity entity, Entity object) const {
4740 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
4741 return itSparseStore != m_sparseComponentsByComp.end() &&
4742 itSparseStore->second.func_has(itSparseStore->second.pStore, entity);
4743 }
4744
4751 GAIA_NODISCARD Entity id_owner_inter(Entity entity, Entity object) const {
4752 GAIA_ASSERT(valid(entity));
4753 GAIA_ASSERT(object != EntityBad);
4754 GAIA_ASSERT(!is_wildcard(object));
4755
4756 const auto& ec = fetch(entity);
4757 if (is_req_del(ec))
4758 return EntityBad;
4759
4760 if (object.pair()) {
4761 if (has_nonfragmenting_relation_pair(entity, object) || ec.pArchetype->has(object))
4762 return entity;
4763 } else {
4764 if (has_direct_sparse_component_inter(entity, object) || ec.pArchetype->has(object))
4765 return entity;
4766 }
4767
4768 return inherited_id_owner(entity, object);
4769 }
4770
4774 GAIA_NODISCARD bool instantiate_copies_id(Entity id) const {
4775 const auto policy = target(id, OnInstantiate);
4776 if (policy == EntityBad || policy == Override)
4777 return true;
4778 if (policy == DontInherit || policy == Inherit)
4779 return false;
4780 return true;
4781 }
4782
4788 template <typename T>
4789 GAIA_NODISCARD bool prefab_child_edge_exists(const T& children, Entity childPrefab) const {
4790 for (const auto& child: children) {
4791 if (child.prefab == childPrefab)
4792 return true;
4793 }
4794
4795 return false;
4796 }
4797
4804 template <typename T>
4805 void gather_prefab_children_for_relation(Entity prefabEntity, Entity relation, T& outChildren) {
4806 sources(relation, prefabEntity, [&](Entity childPrefab) {
4807 if (!has_direct(childPrefab, Prefab) || prefab_child_edge_exists(outChildren, childPrefab))
4808 return;
4809
4810 PrefabChildEdge edge{};
4811 edge.prefab = childPrefab;
4812 edge.relation = relation;
4813 outChildren.push_back(edge);
4814 });
4815 }
4816
4822 template <typename T>
4823 void gather_sorted_prefab_children(Entity prefabEntity, T& outChildren) {
4824 gather_prefab_children_for_relation(prefabEntity, Parent, outChildren);
4825 gather_prefab_children_for_relation(prefabEntity, ChildOf, outChildren);
4826
4827 core::sort(outChildren, [](const PrefabChildEdge& left, const PrefabChildEdge& right) {
4828 if (left.prefab.id() != right.prefab.id())
4829 return left.prefab.id() < right.prefab.id();
4830
4831 return left.relation.id() < right.relation.id();
4832 });
4833 }
4834
4838 GAIA_NODISCARD bool prefab_hierarchy_parent_id(Entity id) const {
4839 if (!id.pair() || id.id() != ChildOf.id())
4840 return false;
4841
4842 const auto parentPrefab = pair_tgt(*this, id);
4843 return valid(parentPrefab) && has_direct(parentPrefab, Prefab);
4844 }
4845
4850 void attach_prefab_child_instance(Entity instance, Entity relation, Entity parentInstance) {
4851 GAIA_ASSERT(valid(instance));
4852 GAIA_ASSERT(valid(relation));
4853 GAIA_ASSERT(valid(parentInstance));
4854
4855 if (relation == Parent) {
4856 parent_direct(instance, parentInstance);
4857 return;
4858 }
4859
4860 add(instance, Pair(relation, parentInstance));
4861 }
4862
4869 GAIA_NODISCARD bool copy_sparse_store_inter(
4870 Entity srcEntity, Entity dstEntity, Entity comp, const SparseComponentStoreErased& store) {
4871 if (!copies_sparse_payload_inter(comp, srcEntity, store))
4872 return false;
4873
4874 GAIA_ASSERT(store.func_copy_entity != nullptr);
4875 return store.func_copy_entity(store.pStore, dstEntity, srcEntity);
4876 }
4877
4883 uint32_t copy_all_sparse_entity_data(Entity srcEntity, Entity dstEntity, Entity* pCopiedIds = nullptr) {
4884 uint32_t copiedCnt = 0;
4885 for (auto& [compKey, store]: m_sparseComponentsByComp) {
4886 const auto comp = compKey.entity();
4887 if (!copy_sparse_store_inter(srcEntity, dstEntity, comp, store))
4888 continue;
4889
4890 if (pCopiedIds != nullptr)
4891 pCopiedIds[copiedCnt] = comp;
4892 ++copiedCnt;
4893 }
4894
4895 return copiedCnt;
4896 }
4897
4904 uint32_t copy_sparse_entity_data(
4905 Entity srcEntity, Entity dstEntity, EntitySpan copiedSparseIds, Entity* pCopiedIds = nullptr) {
4906 uint32_t copiedCnt = 0;
4907 for (const auto comp: copiedSparseIds) {
4908 auto it = m_sparseComponentsByComp.find(EntityLookupKey(comp));
4909 GAIA_ASSERT(it != m_sparseComponentsByComp.end());
4910
4911 auto& store = it->second;
4912 if (!copy_sparse_store_inter(srcEntity, dstEntity, comp, store))
4913 continue;
4914
4915 if (pCopiedIds != nullptr)
4916 pCopiedIds[copiedCnt] = comp;
4917 ++copiedCnt;
4918 }
4919
4920 return copiedCnt;
4921 }
4922
4926 void write_archetype_ids(const Archetype& dstArchetype, Entity* pDst) const {
4927 for (const auto id: dstArchetype.ids_view())
4928 *pDst++ = id;
4929 }
4930
4934 GAIA_NODISCARD uint32_t copied_non_frag_sparse_id_count(Entity srcEntity) const {
4935 uint32_t count = 0;
4936 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
4937 const auto comp = compKey.entity();
4938 if (!copies_non_frag_sparse_payload_inter(comp, srcEntity, store))
4939 continue;
4940 ++count;
4941 }
4942
4943 return count;
4944 }
4945
4949 void write_copied_non_frag_sparse_ids(Entity srcEntity, Entity* pDst) const {
4950 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
4951 const auto comp = compKey.entity();
4952 if (!copies_non_frag_sparse_payload_inter(comp, srcEntity, store))
4953 continue;
4954 *pDst++ = comp;
4955 }
4956 }
4957
4963 GAIA_NODISCARD bool copy_sparse_payload_inter(Entity dstEntity, Entity srcEntity, Entity object) {
4964 const auto mode = sparse_storage_mode(object);
4965 if (mode == SparseStorageMode::None)
4966 return false;
4967
4968 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
4969 if (itSparseStore == m_sparseComponentsByComp.end())
4970 return false;
4971
4972 return copy_sparse_store_inter(srcEntity, dstEntity, object, itSparseStore->second);
4973 }
4974
4979 GAIA_NODISCARD bool override_sparse_component_inter(Entity entity, Entity object) {
4980 GAIA_ASSERT(valid(entity));
4981 GAIA_ASSERT(valid(object));
4982 GAIA_ASSERT(sparse_storage_mode(object) != SparseStorageMode::None);
4983
4984 if (has_direct(entity, object))
4985 return false;
4986
4987 const auto inheritedOwner = inherited_id_owner(entity, object);
4988 if (inheritedOwner == EntityBad)
4989 return false;
4990
4991 if (!copy_sparse_payload_inter(entity, inheritedOwner, object))
4992 return false;
4993
4994 if (sparse_storage_mode(object) == SparseStorageMode::Fragmenting)
4995 make_sparse_copy_direct_inter(entity, object);
4996 return true;
4997 }
4998
5005 GAIA_NODISCARD bool copy_owned_sparse_component_inter(Entity srcEntity, Entity dstEntity, Entity object) {
5006 GAIA_ASSERT(valid(srcEntity));
5007 GAIA_ASSERT(valid(dstEntity));
5008 GAIA_ASSERT(valid(object));
5009 GAIA_ASSERT(sparse_storage_mode(object) != SparseStorageMode::None);
5010
5011 const auto mode = sparse_storage_mode(object);
5012 if (mode == SparseStorageMode::Fragmenting) {
5013 if (!copy_sparse_payload_inter(dstEntity, srcEntity, object))
5014 return false;
5015
5016 make_sparse_copy_direct_inter(dstEntity, object);
5017 notify_add_single(dstEntity, object);
5018 return true;
5019 }
5020#if GAIA_OBSERVERS_ENABLED
5021 auto addDiffCtx =
5022 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&dstEntity, 1});
5023#endif
5024 if (!copy_sparse_payload_inter(dstEntity, srcEntity, object))
5025 return false;
5026 notify_add_single(dstEntity, object);
5027#if GAIA_OBSERVERS_ENABLED
5028 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5029#endif
5030 return true;
5031 }
5032
5036 void make_sparse_copy_direct_inter(Entity entity, Entity object) {
5037 GAIA_ASSERT(sparse_storage_mode(object) == SparseStorageMode::Fragmenting);
5038 EntityBuilder eb(*this, entity);
5039 eb.add_inter_init(object);
5040 eb.commit();
5041 }
5042
5048 void copy_direct_component_data_inter(
5049 Entity srcEntity, Entity dstEntity, Entity object, const ComponentCacheItem& item) {
5050 GAIA_ASSERT(valid(srcEntity));
5051 GAIA_ASSERT(valid(dstEntity));
5052 GAIA_ASSERT(valid(object));
5053 GAIA_ASSERT(item.entity == object);
5054 GAIA_ASSERT(item.comp.size() != 0U);
5055
5056 const auto& ecDst = fetch(dstEntity);
5057 const auto& ecSrc = fetch(srcEntity);
5058 const auto compIdxDst = ecDst.pChunk->comp_idx(object);
5059 const auto compIdxSrc = ecSrc.pChunk->comp_idx(object);
5060 GAIA_ASSERT(compIdxDst != BadIndex && compIdxSrc != BadIndex);
5061
5062 const auto idxDst = uint16_t(ecDst.row * (1U - (uint32_t)object.kind()));
5063 const auto idxSrc = uint16_t(ecSrc.row * (1U - (uint32_t)object.kind()));
5064 void* pDst = ecDst.pChunk->comp_ptr_mut(compIdxDst);
5065 const void* pSrc = ecSrc.pChunk->comp_ptr(compIdxSrc);
5066 item.copy(pDst, pSrc, idxDst, idxSrc, ecDst.pChunk->capacity(), ecSrc.pChunk->capacity());
5067 }
5068
5073 GAIA_NODISCARD bool override_inter(Entity entity, Entity object) {
5074 GAIA_ASSERT(valid(entity));
5075 GAIA_ASSERT(object.pair() || valid(object));
5076
5077 if (has_direct(entity, object))
5078 return false;
5079
5080 const auto inheritedOwner = inherited_id_owner(entity, object);
5081 if (inheritedOwner == EntityBad)
5082 return false;
5083
5084 if (!object.pair()) {
5085 const auto* pItem = comp_cache().find(object);
5086 if (pItem != nullptr && pItem->entity == object) {
5087 const auto mode = sparse_storage_mode(object);
5088 if (mode != SparseStorageMode::None)
5089 return override_sparse_component_inter(entity, object);
5090
5091 if (pItem->comp.size() != 0U) {
5092 add(entity, object);
5093 copy_direct_component_data_inter(inheritedOwner, entity, object, *pItem);
5094 return true;
5095 }
5096 }
5097 }
5098
5099 add(entity, object);
5100 return true;
5101 }
5102
5108 GAIA_NODISCARD bool copy_owned_id_from_entity(Entity srcEntity, Entity dstEntity, Entity object) {
5109 GAIA_ASSERT(valid(srcEntity));
5110 GAIA_ASSERT(valid(dstEntity));
5111 GAIA_ASSERT(object.pair() || valid(object));
5112
5113 if (has_direct(dstEntity, object))
5114 return false;
5115
5116 if (!object.pair()) {
5117 const auto* pItem = comp_cache().find(object);
5118 if (pItem != nullptr && pItem->entity == object) {
5119 if (sparse_storage_mode(object) != SparseStorageMode::None)
5120 return copy_owned_sparse_component_inter(srcEntity, dstEntity, object);
5121
5122 if (pItem->comp.size() != 0U) {
5123 EntityBuilder eb(*this, dstEntity);
5124 eb.add_inter_init(object);
5125 eb.commit();
5126 copy_direct_component_data_inter(srcEntity, dstEntity, object, *pItem);
5127 notify_add_single(dstEntity, object);
5128 return true;
5129 }
5130 }
5131 }
5132
5133 add(dstEntity, object);
5134 return true;
5135 }
5136
5140 GAIA_NODISCARD Archetype* instantiate_prefab_dst_archetype(Entity prefabEntity) {
5141 GAIA_ASSERT(!prefabEntity.pair());
5142 GAIA_ASSERT(valid(prefabEntity));
5143 GAIA_ASSERT(has_direct(prefabEntity, Prefab));
5144
5145 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab))
5146 return fetch(prefabEntity).pArchetype;
5147
5148 auto& ecSrc = m_recs.entities[prefabEntity.id()];
5149 GAIA_ASSERT(ecSrc.pArchetype != nullptr);
5150
5151 auto* pDstArchetype = ecSrc.pArchetype;
5152 if (pDstArchetype->has<EntityDesc>())
5153 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
5154 if (pDstArchetype->has(Prefab))
5155 pDstArchetype = foc_archetype_del(pDstArchetype, Prefab);
5156
5157 for (const auto id: ecSrc.pArchetype->ids_view()) {
5158 if (id.pair() && id.id() == Is.id()) {
5159 pDstArchetype = foc_archetype_del(pDstArchetype, id);
5160 continue;
5161 }
5162 if (prefab_hierarchy_parent_id(id)) {
5163 pDstArchetype = foc_archetype_del(pDstArchetype, id);
5164 continue;
5165 }
5166
5167 if (!instantiate_copies_id(id))
5168 pDstArchetype = foc_archetype_del(pDstArchetype, id);
5169 }
5170
5171 const auto isPair = Pair(Is, prefabEntity);
5172 assign_pair(isPair, *m_pEntityArchetype);
5173 pDstArchetype = foc_archetype_add(pDstArchetype, isPair);
5174
5175 return pDstArchetype;
5176 }
5177
5182 template <typename T>
5183 void collect_prefab_copied_sparse_ids(Entity prefabEntity, T& outCopiedSparseIds) {
5184 outCopiedSparseIds.clear();
5185 if (m_sparseComponentsByComp.empty())
5186 return;
5187
5188 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
5189 const auto comp = compKey.entity();
5190 if (!instantiate_copies_id(comp) || !copies_sparse_payload_inter(comp, prefabEntity, store))
5191 continue;
5192 outCopiedSparseIds.push_back(comp);
5193 }
5194 }
5195
5201 template <typename T>
5202 void collect_prefab_added_ids(Archetype* pDstArchetype, EntitySpan copiedSparseIds, T& outAddedIds) {
5203 outAddedIds.clear();
5204 for (const auto id: pDstArchetype->ids_view())
5205 outAddedIds.push_back(id);
5206
5207 for (const auto comp: copiedSparseIds) {
5208 if (sparse_copy_adds_id_inter(comp))
5209 outAddedIds.push_back(comp);
5210 }
5211 }
5212
5217 template <typename T>
5218 void collect_prefab_add_hook_ids(EntitySpan addedIds, T& outHookIds) {
5219 outHookIds.clear();
5220 for (const auto id: addedIds) {
5221 if (!id.comp())
5222 continue;
5223
5224 const auto& item = comp_cache().get(id);
5225 if (ComponentCache::hooks(item).func_add != nullptr)
5226 outHookIds.push_back(id);
5227 }
5228 }
5229
5238 GAIA_NODISCARD Entity instantiate_prefab_node_inter(
5239 Entity prefabEntity, Archetype* pDstArchetype, Entity parentInstance, EntitySpan copiedSparseIds,
5240 EntitySpan addedIds, EntitySpan addHookIds) {
5241 GAIA_ASSERT(!prefabEntity.pair());
5242 GAIA_ASSERT(valid(prefabEntity));
5243 GAIA_ASSERT(has_direct(prefabEntity, Prefab));
5244 GAIA_ASSERT(pDstArchetype != nullptr);
5245#if GAIA_OBSERVERS_ENABLED
5246 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{addedIds});
5247#endif
5248
5249 auto& ecSrc = m_recs.entities[prefabEntity.id()];
5250 GAIA_ASSERT(ecSrc.pArchetype != nullptr);
5251 GAIA_ASSERT(ecSrc.pChunk != nullptr);
5252
5253 EntityContainerCtx ctx{true, false, prefabEntity.kind()};
5254 const auto instance = m_recs.entities.alloc(&ctx);
5255 auto& ecDst = m_recs.entities[instance.id()];
5256 auto* pDstChunk = pDstArchetype->foc_free_chunk();
5257 store_entity(ecDst, instance, pDstArchetype, pDstChunk);
5258 pDstArchetype->try_update_free_chunk_idx();
5259 Chunk::copy_foreign_entity_data(ecSrc.pChunk, ecSrc.row, pDstChunk, ecDst.row);
5260 pDstChunk->update_versions();
5261
5262 ecDst.flags |= EntityContainerFlags::HasAliasOf;
5263
5264 // Keep payload copy and observer/add-id reporting separate:
5265 // fragmenting sparse payloads must still be copied here even though their id is
5266 // already present in the destination archetype and therefore absent from addedIds tail.
5267 (void)copy_sparse_entity_data(prefabEntity, instance, copiedSparseIds);
5268#if GAIA_OBSERVERS_ENABLED
5269 m_observers.add_diff_targets(*this, addDiffCtx, EntitySpan{&instance, 1});
5270#endif
5271
5272 invalidate_relation_caches(Is);
5273
5274 const auto instanceKey = EntityLookupKey(instance);
5275 const auto prefabKey = EntityLookupKey(prefabEntity);
5276 m_entityToAsTargets[instanceKey].insert(prefabKey);
5277 m_entityToAsTargetsTravCache = {};
5278 m_entityToAsRelations[prefabKey].insert(instanceKey);
5279 m_entityToAsRelationsTravCache = {};
5280 invalidate_queries_for_entity({Is, prefabEntity});
5281
5282#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
5283 if GAIA_UNLIKELY (tearing_down()) {
5284 (void)pDstArchetype;
5285 (void)addedIds;
5286 } else {
5287 lock();
5288
5289 #if GAIA_ENABLE_ADD_DEL_HOOKS
5290 for (const auto id: addHookIds) {
5291 const auto& item = comp_cache().get(id);
5292 const auto& hooks = ComponentCache::hooks(item);
5293 GAIA_ASSERT(hooks.func_add != nullptr);
5294 hooks.func_add(*this, item, instance);
5295 }
5296 #endif
5297
5298 #if GAIA_OBSERVERS_ENABLED
5299 m_observers.on_add(*this, *pDstArchetype, addedIds, EntitySpan{&instance, 1});
5300 #endif
5301
5302 unlock();
5303 }
5304#endif
5305
5306#if GAIA_OBSERVERS_ENABLED
5307 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5308#endif
5309
5310 if (parentInstance != EntityBad)
5311 parent_direct(instance, parentInstance);
5312
5313 return instance;
5314 }
5315
5320 GAIA_NODISCARD Entity instantiate_prefab_node_inter(Entity prefabEntity, Entity parentInstance) {
5321 auto* pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
5322 cnt::darray_ext<Entity, 16> copiedSparseIds;
5323 cnt::darray_ext<Entity, 16> addedIds;
5324 cnt::darray_ext<Entity, 16> addHookIds;
5325 collect_prefab_copied_sparse_ids(prefabEntity, copiedSparseIds);
5326 collect_prefab_added_ids(pDstArchetype, EntitySpan{copiedSparseIds}, addedIds);
5327 collect_prefab_add_hook_ids(EntitySpan{addedIds}, addHookIds);
5328 return instantiate_prefab_node_inter(
5329 prefabEntity, pDstArchetype, parentInstance, EntitySpan{copiedSparseIds}, EntitySpan{addedIds},
5330 EntitySpan{addHookIds});
5331 }
5332
5339 template <typename Func>
5340 void instantiate_prefab_n_inter(
5341 const PrefabInstantiatePlanNode& node, Entity parentInstance, uint32_t count, Func& func) {
5342 GAIA_ASSERT(node.prefab != EntityBad);
5343 GAIA_ASSERT(node.pDstArchetype != nullptr);
5344
5345 if (count == 0U)
5346 return;
5347#if GAIA_OBSERVERS_ENABLED
5348 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{node.addedIds});
5349#endif
5350
5351 auto& ecSrc = m_recs.entities[node.prefab.id()];
5352 GAIA_ASSERT(ecSrc.pChunk != nullptr);
5353
5354 if (parentInstance != EntityBad)
5355 prepare_parent_batch(parentInstance);
5356
5357 const auto srcRow = ecSrc.row;
5358 auto* pSrcChunk = ecSrc.pChunk;
5359 auto* pDstArchetype = node.pDstArchetype;
5360 const auto prefabKey = EntityLookupKey(node.prefab);
5361 EntityContainerCtx ctx{true, false, node.prefab.kind()};
5362 auto& asRelations = m_entityToAsRelations[prefabKey];
5363 m_entityToAsTargets.reserve(m_entityToAsTargets.size() + count);
5364 asRelations.reserve(asRelations.size() + count);
5365
5366 uint32_t left = count;
5367 do {
5368 auto* pDstChunk = pDstArchetype->foc_free_chunk();
5369 const uint32_t originalChunkSize = pDstChunk->size();
5370 const uint32_t freeSlotsInChunk = pDstChunk->capacity() - originalChunkSize;
5371 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
5372
5373 GAIA_FOR_(toCreate, rowOffset) {
5374 const auto instance = m_recs.entities.alloc(&ctx);
5375 auto& ecDst = m_recs.entities[instance.id()];
5376 store_entity(ecDst, instance, pDstArchetype, pDstChunk);
5377 ecDst.flags |= EntityContainerFlags::HasAliasOf;
5378
5379 (void)copy_sparse_entity_data(node.prefab, instance, EntitySpan{node.copiedSparseIds});
5380 }
5381
5382 pDstArchetype->try_update_free_chunk_idx();
5383 Chunk::copy_foreign_entity_data_n(pSrcChunk, srcRow, pDstChunk, originalChunkSize, toCreate);
5384 pDstChunk->update_versions();
5385
5386 invalidate_relation_caches(Is);
5387
5388 auto entities = pDstChunk->entity_view();
5389 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
5390 const auto instance = entities[rowIdx];
5391 m_entityToAsTargets[EntityLookupKey(instance)].insert(prefabKey);
5392 asRelations.insert(EntityLookupKey(instance));
5393 }
5394 m_entityToAsTargetsTravCache = {};
5395 m_entityToAsRelationsTravCache = {};
5396 invalidate_queries_for_entity({Is, node.prefab});
5397
5398#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
5399 if GAIA_UNLIKELY (tearing_down()) {
5400 (void)entities;
5401 (void)originalChunkSize;
5402 (void)toCreate;
5403 } else {
5404 lock();
5405
5406 #if GAIA_ENABLE_ADD_DEL_HOOKS
5407 for (const auto id: node.addHookIds) {
5408 const auto& item = comp_cache().get(id);
5409 const auto& hooks = ComponentCache::hooks(item);
5410 GAIA_ASSERT(hooks.func_add != nullptr);
5411
5412 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
5413 hooks.func_add(*this, item, entities[rowIdx]);
5414 }
5415 }
5416 #endif
5417
5418 #if GAIA_OBSERVERS_ENABLED
5419 m_observers.add_diff_targets(*this, addDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
5420 m_observers.on_add(
5421 *this, *pDstArchetype, EntitySpan{node.addedIds},
5422 EntitySpan{entities.data() + originalChunkSize, toCreate});
5423 #endif
5424
5425 unlock();
5426 }
5427#endif
5428
5429 if (parentInstance != EntityBad)
5430 parent_batch(parentInstance, *pDstArchetype, *pDstChunk, originalChunkSize, toCreate);
5431
5432 invoke_copy_batch_callback(func, pDstArchetype, pDstChunk, originalChunkSize, toCreate);
5433
5434 left -= toCreate;
5435 } while (left > 0);
5436#if GAIA_OBSERVERS_ENABLED
5437 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5438#endif
5439 }
5440
5447 template <typename T>
5448 void build_prefab_instantiate_plan(Entity prefabEntity, uint32_t parentIdx, Entity parentRelation, T& plan) {
5449 PrefabInstantiatePlanNode node{};
5450 node.prefab = prefabEntity;
5451 node.parentIdx = parentIdx;
5452 node.parentRelation = parentRelation;
5453 node.pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
5454 collect_prefab_copied_sparse_ids(prefabEntity, node.copiedSparseIds);
5455 collect_prefab_added_ids(node.pDstArchetype, EntitySpan{node.copiedSparseIds}, node.addedIds);
5456 collect_prefab_add_hook_ids(EntitySpan{node.addedIds}, node.addHookIds);
5457
5458 const auto nodeIdx = (uint32_t)plan.size();
5459 plan.push_back(GAIA_MOV(node));
5460
5461 cnt::darray_ext<PrefabChildEdge, 16> prefabChildren;
5462 gather_sorted_prefab_children(prefabEntity, prefabChildren);
5463
5464 for (const auto& child: prefabChildren)
5465 build_prefab_instantiate_plan(child.prefab, nodeIdx, child.relation, plan);
5466 }
5467
5473 GAIA_NODISCARD bool instance_has_prefab_child(Entity parentInstance, Entity relation, Entity childPrefab) const {
5474 bool found = false;
5475 sources(relation, parentInstance, [&](Entity child) {
5476 if (found)
5477 return;
5478 if (has_direct(child, Pair(Is, childPrefab)))
5479 found = true;
5480 });
5481 return found;
5482 }
5483
5491 template <typename T>
5492 uint32_t sync_prefab_instance(
5493 Entity prefabEntity, Entity instance, const PrefabInstantiatePlanNode& node, const T& prefabChildren) {
5494 uint32_t changes = 0;
5495
5496 const auto isPair = Pair(Is, prefabEntity);
5497 for (const auto id: node.pDstArchetype->ids_view()) {
5498 if (id == isPair || has_direct(instance, id))
5499 continue;
5500 if (copy_owned_id_from_entity(prefabEntity, instance, id))
5501 ++changes;
5502 }
5503
5504 for (const auto comp: node.copiedSparseIds) {
5505 if (has_direct(instance, comp))
5506 continue;
5507 if (copy_owned_id_from_entity(prefabEntity, instance, comp))
5508 ++changes;
5509 }
5510
5511 for (const auto& child: prefabChildren) {
5512 if (instance_has_prefab_child(instance, child.relation, child.prefab))
5513 continue;
5514 (void)instantiate_inter(child.prefab, instance, child.relation);
5515 ++changes;
5516 }
5517
5518 return changes;
5519 }
5520
5525 uint32_t sync_prefab_inter(Entity prefabEntity, cnt::set<EntityLookupKey>& visited) {
5526 GAIA_ASSERT(!prefabEntity.pair());
5527 GAIA_ASSERT(valid(prefabEntity));
5528
5529 if (!has_direct(prefabEntity, Prefab))
5530 return 0;
5531
5532 const auto ins = visited.insert(EntityLookupKey(prefabEntity));
5533 if (!ins.second)
5534 return 0;
5535
5536 PrefabInstantiatePlanNode node{};
5537 node.prefab = prefabEntity;
5538 node.pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
5539 collect_prefab_copied_sparse_ids(prefabEntity, node.copiedSparseIds);
5540 collect_prefab_added_ids(node.pDstArchetype, EntitySpan{node.copiedSparseIds}, node.addedIds);
5541 collect_prefab_add_hook_ids(EntitySpan{node.addedIds}, node.addHookIds);
5542
5543 cnt::darray_ext<PrefabChildEdge, 16> prefabChildren;
5544 gather_sorted_prefab_children(prefabEntity, prefabChildren);
5545
5546 uint32_t changes = 0;
5547 const auto& descendants = as_relations_trav_cache(prefabEntity);
5548 for (const auto entity: descendants) {
5549 if (has_direct(entity, Prefab))
5550 continue;
5551 changes += sync_prefab_instance(prefabEntity, entity, node, prefabChildren);
5552 }
5553
5554 for (const auto& child: prefabChildren)
5555 changes += sync_prefab_inter(child.prefab, visited);
5556
5557 return changes;
5558 }
5559
5565 GAIA_NODISCARD Entity
5566 instantiate_inter(Entity prefabEntity, Entity parentInstance, Entity parentRelation = Parent) {
5567 const auto directParentInstance = parentRelation == Parent ? parentInstance : EntityBad;
5568 const auto instance = instantiate_prefab_node_inter(prefabEntity, directParentInstance);
5569 if (parentInstance != EntityBad && parentRelation != Parent)
5570 attach_prefab_child_instance(instance, parentRelation, parentInstance);
5571
5572 cnt::darray_ext<PrefabChildEdge, 16> prefabChildren;
5573 gather_sorted_prefab_children(prefabEntity, prefabChildren);
5574
5575 for (const auto& child: prefabChildren)
5576 (void)instantiate_inter(child.prefab, instance, child.relation);
5577
5578 return instance;
5579 }
5580
5581 public:
5588 GAIA_NODISCARD Entity instantiate(Entity prefabEntity) {
5589 GAIA_ASSERT(!prefabEntity.pair());
5590 GAIA_ASSERT(valid(prefabEntity));
5591
5592 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab))
5593 return copy(prefabEntity);
5594
5595 return instantiate_inter(prefabEntity, EntityBad);
5596 }
5597
5606 GAIA_NODISCARD Entity instantiate(Entity prefabEntity, Entity parentInstance) {
5607 GAIA_ASSERT(!prefabEntity.pair());
5608 GAIA_ASSERT(valid(prefabEntity));
5609 GAIA_ASSERT(valid(parentInstance));
5610
5611 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab)) {
5612 const auto instance = copy(prefabEntity);
5613 parent_direct(instance, parentInstance);
5614 return instance;
5615 }
5616
5617 return instantiate_inter(prefabEntity, parentInstance);
5618 }
5619
5633 template <typename Func = TFunc_Void_With_Entity>
5634 void instantiate_n(Entity prefabEntity, uint32_t count, Func func = func_void_with_entity) {
5635 instantiate_n(prefabEntity, EntityBad, count, func);
5636 }
5637
5644 void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count) {
5645 instantiate_n(prefabEntity, parentInstance, count, func_void_with_entity);
5646 }
5647
5657 template <typename Func>
5658 void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count, Func func) {
5659 GAIA_ASSERT(!prefabEntity.pair());
5660 GAIA_ASSERT(valid(prefabEntity));
5661 GAIA_ASSERT(parentInstance == EntityBad || valid(parentInstance));
5662
5663 if (count == 0U)
5664 return;
5665
5666 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab)) {
5667 if (parentInstance == EntityBad) {
5668 copy_n(prefabEntity, count, func);
5669 return;
5670 }
5671
5672 copy_n_inter(prefabEntity, count, func, EntitySpan{}, parentInstance);
5673 return;
5674 }
5675
5677 build_prefab_instantiate_plan(prefabEntity, BadIndex, EntityBad, plan);
5678 if (plan.size() == 1) {
5679 instantiate_prefab_n_inter(plan[0], parentInstance, count, func);
5680 return;
5681 }
5682
5683 const auto planSize = (uint32_t)plan.size();
5684 GAIA_ASSERT(planSize <= uint32_t(-1) / count);
5685 cnt::darray<Entity> spawned;
5686 spawned.resize(planSize * count);
5687
5688 uint32_t rootIdx = 0;
5689 auto collectRoot = [&](Entity instance) {
5690 spawned[rootIdx++] = instance;
5691 };
5692 instantiate_prefab_n_inter(plan[0], parentInstance, count, collectRoot);
5693
5694 GAIA_FOR2_(1, planSize, planIdx) {
5695 uint32_t nodeIdx = 0;
5696 const auto nodeOffset = planIdx * count;
5697 auto collectNode = [&](Entity instance) {
5698 spawned[nodeOffset + nodeIdx++] = instance;
5699 };
5700 instantiate_prefab_n_inter(plan[planIdx], EntityBad, count, collectNode);
5701
5702 const auto parentOffset = plan[planIdx].parentIdx * count;
5703 GAIA_FOR_(count, instanceIdx) {
5704 attach_prefab_child_instance(
5705 spawned[nodeOffset + instanceIdx], plan[planIdx].parentRelation, spawned[parentOffset + instanceIdx]);
5706 }
5707 }
5708
5709 if constexpr (std::is_invocable_v<Func, CopyIter&>) {
5710 CopyIterGroupState group;
5711 GAIA_FOR_(count, idx) {
5712 push_copy_iter_group(func, group, spawned[idx]);
5713 }
5714 flush_copy_iter_group(func, group);
5715 } else {
5716 GAIA_FOR_(count, idx) {
5717 func(spawned[idx]);
5718 }
5719 }
5720 }
5721
5728 GAIA_NODISCARD uint32_t sync(Entity prefabEntity) {
5729 GAIA_ASSERT(!prefabEntity.pair());
5730 GAIA_ASSERT(valid(prefabEntity));
5731
5733 return sync_prefab_inter(prefabEntity, visited);
5734 }
5735
5742 GAIA_NODISCARD Entity find_prefab_instance(Entity instanceRoot, Entity prefabEntity) const {
5743 if (!valid(instanceRoot) || !valid(prefabEntity))
5744 return EntityBad;
5745
5746 const auto isPair = Pair(Is, prefabEntity);
5747 if (has_direct(instanceRoot, isPair))
5748 return instanceRoot;
5749
5750 Entity found = EntityBad;
5754 queue.push_back(instanceRoot);
5755 visited.insert(EntityLookupKey(instanceRoot));
5756
5757 for (uint32_t i = 0; i < queue.size() && found == EntityBad; ++i) {
5758 children.clear();
5759 auto collectChild = [&](Entity child) {
5760 const auto key = EntityLookupKey(child);
5761 const auto ins = visited.insert(key);
5762 if (!ins.second)
5763 return;
5764
5765 children.push_back(child);
5766 };
5767 sources(Parent, queue[i], collectChild);
5768 sources(ChildOf, queue[i], collectChild);
5769
5770 core::sort(children, [](Entity left, Entity right) {
5771 return left.id() < right.id();
5772 });
5773
5774 for (const auto child: children) {
5775 if (has_direct(child, isPair)) {
5776 found = child;
5777 break;
5778 }
5779
5780 queue.push_back(child);
5781 }
5782 }
5783
5784 return found;
5785 }
5786
5787 //----------------------------------------------------------------------
5788
5791 void del(Entity entity) {
5792#if GAIA_OBSERVERS_ENABLED
5793 if (entity_deletion_active(entity))
5794 return;
5795#endif
5796
5797 if (!entity.pair()) {
5798 if (relation_uses_non_fragmenting_storage(entity)) {
5799 cnt::darray_ext<Entity, 64> pairEntities;
5800 if (const auto* pTargets = targets(entity)) {
5801 for (auto targetKey: *pTargets)
5802 pairEntities.push_back(Pair(entity, targetKey.entity()));
5803 }
5804 if (const auto* pRelations = relations(entity)) {
5805 for (auto relationKey: *pRelations) {
5806 const auto relation = relationKey.entity();
5807 if (relation != entity)
5808 pairEntities.push_back(Pair(relation, entity));
5809 }
5810 }
5811
5812 auto& ec = fetch(entity);
5813 handle_del_entity(ec, entity, EntitySpan{pairEntities.data(), pairEntities.size()});
5814 return;
5815 }
5816
5817 // Delete all relationships associated with this entity (if any)
5818 del_inter(Pair(entity, All));
5819 del_inter(Pair(All, entity));
5820 }
5821
5822 del_inter(entity);
5823 }
5824
5829 void del(Entity entity, Entity object) {
5830 if (!object.pair()) {
5831 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
5832 if (itSparseStore != m_sparseComponentsByComp.end()) {
5833 if (!component_is_non_fragmenting(object)) {
5834 {
5835 EntityBuilder eb(*this, entity);
5836 eb.del(object);
5837 }
5838 itSparseStore->second.func_del(itSparseStore->second.pStore, entity);
5839 return;
5840 }
5841#if GAIA_OBSERVERS_ENABLED
5842 auto delDiffCtx =
5843 m_observers.prepare_diff(*this, ObserverEvent::OnDel, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
5844#endif
5845 notify_inherited_del_dependents(entity, object);
5846 notify_del_single(entity, object);
5847 itSparseStore->second.func_del(itSparseStore->second.pStore, entity);
5848#if GAIA_OBSERVERS_ENABLED
5849 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
5850#endif
5851 return;
5852 }
5853 }
5854 EntityBuilder(*this, entity).del(object);
5855 }
5856
5862 void del(Entity entity, Pair pair) {
5863 EntityBuilder(*this, entity).del(pair);
5864 }
5865
5871 template <typename T>
5872 void del(Entity entity) {
5873 using CT = component_type_t<T>;
5874 using FT = typename CT::TypeFull;
5875
5876 if constexpr (uses_compile_time_sparse_storage<FT>()) {
5877 const auto* pItem = comp_cache().template find<FT>();
5878 if (pItem != nullptr)
5879 del(entity, pItem->entity);
5880 return;
5881 }
5882
5883 EntityBuilder(*this, entity).del<FT>();
5884 }
5885
5886 //----------------------------------------------------------------------
5887
5891 void as(Entity entity, Entity entityBase) {
5892 // Form the relationship
5893 add(entity, Pair(Is, entityBase));
5894 }
5895
5900 GAIA_NODISCARD bool is(Entity entity, Entity entityBase) const {
5901 return is_inter<false>(entity, entityBase);
5902 }
5903
5910 GAIA_NODISCARD bool in(Entity entity, Entity entityBase) const {
5911 return is_inter<true>(entity, entityBase);
5912 }
5913
5917 GAIA_NODISCARD bool is_base(Entity target) const {
5918 GAIA_ASSERT(valid_entity(target));
5919
5920 // Pairs are not supported
5921 if (target.pair())
5922 return false;
5923
5924 const auto it = m_entityToAsRelations.find(EntityLookupKey(target));
5925 return it != m_entityToAsRelations.end();
5926 }
5927
5928 //----------------------------------------------------------------------
5929
5933 void child(Entity entity, Entity parent) {
5934 add(entity, Pair(ChildOf, parent));
5935 }
5936
5941 GAIA_NODISCARD bool child(Entity entity, Entity parent) const {
5942 return has(entity, Pair(ChildOf, parent));
5943 }
5944
5949 void parent(Entity entity, Entity parentEntity) {
5950 parent_direct(entity, parentEntity);
5951 }
5952
5957 GAIA_NODISCARD bool parent(Entity entity, Entity parentEntity) const {
5958 return has_direct(entity, Pair(Parent, parentEntity));
5959 }
5960
5961 //----------------------------------------------------------------------
5962
5970 template <
5971 typename T
5972#if GAIA_ENABLE_HOOKS
5973 ,
5974 bool TriggerSetEffects
5975#endif
5976 >
5977 void modify(Entity entity) {
5978 GAIA_ASSERT(valid(entity));
5979
5980 if constexpr (uses_compile_time_sparse_storage<T>()) {
5981 const auto* pItem = comp_cache().template find<T>();
5982 if (pItem != nullptr) {
5983#if GAIA_ASSERT_ENABLED
5984 auto* pStore = sparse_component_store_erased(pItem->entity);
5985 GAIA_ASSERT(pStore != nullptr);
5986 GAIA_ASSERT(has_direct_sparse_component_inter(entity, pItem->entity));
5987#endif
5988
5989 ::gaia::ecs::update_version(m_worldVersion);
5990
5991#if GAIA_OBSERVERS_ENABLED
5992 if constexpr (TriggerSetEffects)
5993 world_notify_on_set_entity(*this, pItem->entity, entity);
5994#endif
5995 return;
5996 }
5997 }
5998
5999 auto& ec = m_recs.entities[entity.id()];
6000 ec.pChunk->template modify<
6001 T
6002#if GAIA_ENABLE_HOOKS
6003 ,
6004 TriggerSetEffects
6005#endif
6006 >();
6007
6008#if GAIA_OBSERVERS_ENABLED
6009 if constexpr (TriggerSetEffects) {
6010 Entity term = EntityBad;
6011 if constexpr (is_pair<T>::value) {
6012 const auto rel = comp_cache().template get<typename T::rel>().entity;
6013 const auto tgt = comp_cache().template get<typename T::tgt>().entity;
6014 term = (Entity)Pair(rel, tgt);
6015 } else
6016 term = comp_cache().template get<T>().entity;
6017
6018 world_notify_on_set(*this, term, *ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
6019 }
6020#endif
6021 }
6022
6032 template <
6033 typename T
6034#if GAIA_ENABLE_HOOKS
6035 ,
6036 bool TriggerSetEffects
6037#endif
6038 >
6039 void modify(Entity entity, Entity object) {
6040 GAIA_ASSERT(valid(entity));
6041 GAIA_ASSERT(valid(object));
6042
6043 using FT = typename component_type_t<T>::TypeFull;
6044 if constexpr (supports_sparse_component_storage<FT>()) {
6045 if (can_use_sparse_component_storage<FT>(object)) {
6046#if GAIA_ASSERT_ENABLED
6047 auto* pStore = sparse_component_store_erased(object);
6048 GAIA_ASSERT(pStore != nullptr);
6049 GAIA_ASSERT(has_direct_sparse_component_inter(entity, object));
6050#endif
6051
6052 ::gaia::ecs::update_version(m_worldVersion);
6053
6054#if GAIA_OBSERVERS_ENABLED
6055 if constexpr (TriggerSetEffects)
6056 world_notify_on_set_entity(*this, object, entity);
6057#endif
6058 return;
6059 }
6060 }
6061
6062 auto& ec = m_recs.entities[entity.id()];
6063 const auto compIdx = ec.pChunk->comp_idx(object);
6064 GAIA_ASSERT(compIdx != ComponentIndexBad);
6065
6066 if constexpr (TriggerSetEffects)
6067 ec.pChunk->finish_write(compIdx, ec.row, (uint16_t)(ec.row + 1));
6068 else
6069 ec.pChunk->update_world_version(compIdx);
6070 }
6071
6072 //----------------------------------------------------------------------
6073
6079 GAIA_NODISCARD ComponentSetter acc_mut(Entity entity) {
6080 GAIA_ASSERT(valid(entity));
6081
6082 const auto& ec = m_recs.entities[entity.id()];
6083 return ComponentSetter{*this, ec.pChunk, entity, ec.row};
6084 }
6085
6096 template <typename T>
6097 GAIA_NODISCARD auto set(Entity entity) {
6098 static_assert(!is_pair<T>::value);
6099 using FT = typename component_type_t<T>::TypeFull;
6100 using ValueType = typename actual_type_t<T>::Type;
6101 const auto& item = add<FT>();
6102 return SetWriteProxyTyped<T, ValueType>{*this, entity, item.entity, get<T>(entity)};
6103 }
6104
6116 template <typename T>
6117 GAIA_NODISCARD auto set(Entity entity, Entity object) {
6118 static_assert(!is_pair<T>::value);
6119 return SetWriteProxyObject<typename actual_type_t<T>::Type>{*this, entity, object, get<T>(entity, object)};
6120 }
6121
6130 template <typename T>
6131 GAIA_NODISCARD decltype(auto) sset(Entity entity) {
6132 static_assert(!is_pair<T>::value);
6133 using FT = typename component_type_t<T>::TypeFull;
6134 const auto& item = add<FT>();
6135 if constexpr (uses_compile_time_sparse_storage<FT>())
6136 return sparse_component_store_mut<FT>(item.entity).mut(entity);
6137 return acc_mut(entity).smut<T>();
6138 }
6139
6146 template <typename T>
6147 GAIA_NODISCARD decltype(auto) sset(Entity entity, Entity object) {
6148 static_assert(!is_pair<T>::value);
6149 using FT = typename component_type_t<T>::TypeFull;
6150 if constexpr (supports_sparse_component_storage<FT>()) {
6151 if (can_use_sparse_component_storage<FT>(object))
6152 return sparse_component_mut_value<FT>(object, entity);
6153 }
6154 return acc_mut(entity).smut<T>(object);
6155 }
6156
6157 //----------------------------------------------------------------------
6158
6167 template <typename T>
6168 GAIA_NODISCARD decltype(auto) mut(Entity entity) {
6169 static_assert(!is_pair<T>::value);
6170 return sset<T>(entity);
6171 }
6172
6179 template <typename T>
6180 GAIA_NODISCARD decltype(auto) mut(Entity entity, Entity object) {
6181 static_assert(!is_pair<T>::value);
6182 return sset<T>(entity, object);
6183 }
6184
6190 GAIA_NODISCARD ComponentRawView get_raw(Entity entity, Entity component) const {
6191 if (component == EntityBad || !valid(entity))
6192 return {};
6193
6194 const auto owner = id_owner_inter(entity, component);
6195 if (owner == EntityBad)
6196 return {};
6197
6198 const auto* pItem = component_item(owner, component);
6199 if (pItem == nullptr || !raw_component_supported(*pItem))
6200 return {};
6201 if (pItem->comp.storage_type() == DataStorageType::Sparse) {
6202 if (component.pair())
6203 return {};
6204 const auto* pStore = sparse_component_store_erased(component);
6205 if (pStore == nullptr || !pStore->func_has(pStore->pStore, owner))
6206 return {};
6207 return {pStore->func_get(pStore->pStore, owner), pItem->comp.size(), ComponentRawViewFlag_Valid};
6208 }
6209
6210 const auto& ec = fetch(owner);
6211 const auto compIdx = ec.pChunk->comp_idx(component);
6212 if (compIdx == ComponentIndexBad)
6213 return {};
6214
6215 const auto size = pItem->comp.size();
6216 if (size == 0)
6217 return {nullptr, 0, ComponentRawViewFlag_Valid};
6218
6219 const auto row = uint32_t(ec.row * (1U - (uint32_t)component.kind()));
6220 return {ec.pChunk->comp_ptr(compIdx, row), size, ComponentRawViewFlag_Valid};
6221 }
6222
6228 GAIA_NODISCARD ComponentRawMutView mut_raw(Entity entity, Entity component) {
6229 if (component == EntityBad || !valid(entity))
6230 return {};
6231
6232 const auto& ec = fetch(entity);
6233 if (is_req_del(ec))
6234 return {};
6235
6236 const auto* pItem = component_item(entity, component);
6237 if (pItem == nullptr || !raw_component_supported(*pItem))
6238 return {};
6239 if (pItem->comp.storage_type() == DataStorageType::Sparse) {
6240 if (component.pair())
6241 return {};
6242 const auto* pStore = sparse_component_store_erased(component);
6243 if (pStore == nullptr || !pStore->func_has(pStore->pStore, entity))
6244 return {};
6245 return {pStore->func_mut(pStore->pStore, entity), pItem->comp.size(), ComponentRawViewFlag_Valid};
6246 }
6247
6248 const auto compIdx = core::get_index(ec.pChunk->ids_view(), component);
6249 if (compIdx == BadIndex)
6250 return {};
6251
6252 const auto size = pItem->comp.size();
6253 if (size == 0)
6254 return {nullptr, 0, ComponentRawViewFlag_Valid};
6255
6256 const auto row = uint32_t(ec.row * (1U - (uint32_t)component.kind()));
6257 return {ec.pChunk->comp_ptr_mut(compIdx, row), size, ComponentRawViewFlag_Valid};
6258 }
6259
6266 GAIA_NODISCARD ComponentRawView get_raw_field(Entity entity, Entity component, uint32_t fieldIdx) const {
6267 if (component == EntityBad || !valid(entity))
6268 return {};
6269
6270 const auto owner = id_owner_inter(entity, component);
6271 if (owner == EntityBad)
6272 return {};
6273
6274 const auto* pItem = component_item(owner, component);
6275 if (pItem == nullptr || !soa_field_supported(*pItem) || fieldIdx >= pItem->comp.soa() ||
6276 pItem->soaSizes[fieldIdx] == 0)
6277 return {};
6278
6279 const auto& ec = fetch(owner);
6280 const auto compIdx = ec.pChunk->comp_idx(component);
6281 if (compIdx == ComponentIndexBad)
6282 return {};
6283
6284 const auto row = uint32_t(ec.row * (1U - (uint32_t)component.kind()));
6285 const auto capacity = pItem->entity.kind() == EntityKind::EK_Uni ? 1U : ec.pChunk->capacity();
6286 const std::span<const uint8_t> fieldSizes{pItem->soaSizes, pItem->comp.soa()};
6287 const auto* pData = mem::data_view_policy_soa_erased::get(
6288 ec.pChunk->comp_ptr(compIdx), pItem->comp.alig(), fieldSizes, fieldIdx, row, capacity);
6289 return {pData, pItem->soaSizes[fieldIdx], ComponentRawViewFlag_Valid};
6290 }
6291
6298 GAIA_NODISCARD ComponentRawMutView mut_raw_field(Entity entity, Entity component, uint32_t fieldIdx) {
6299 if (component == EntityBad || !valid(entity))
6300 return {};
6301
6302 const auto& ec = fetch(entity);
6303 if (is_req_del(ec))
6304 return {};
6305
6306 const auto* pItem = component_item(entity, component);
6307 if (pItem == nullptr || !soa_field_supported(*pItem) || fieldIdx >= pItem->comp.soa() ||
6308 pItem->soaSizes[fieldIdx] == 0)
6309 return {};
6310
6311 const auto compIdx = core::get_index(ec.pChunk->ids_view(), component);
6312 if (compIdx == BadIndex)
6313 return {};
6314
6315 const auto row = uint32_t(ec.row * (1U - (uint32_t)component.kind()));
6316 const auto capacity = pItem->entity.kind() == EntityKind::EK_Uni ? 1U : ec.pChunk->capacity();
6317 const std::span<const uint8_t> fieldSizes{pItem->soaSizes, pItem->comp.soa()};
6319 ec.pChunk->comp_ptr_mut(compIdx), pItem->comp.alig(), fieldSizes, fieldIdx, row, capacity);
6320 return {pData, pItem->soaSizes[fieldIdx], ComponentRawViewFlag_Valid};
6321 }
6322
6328 GAIA_NODISCARD ComponentCursor cursor(Entity entity, Entity component) const;
6329
6336 GAIA_NODISCARD ComponentCursor cursor_mut(Entity entity, Entity component);
6337
6344 bool add_raw(Entity entity, Entity component, const void* data, uint32_t size) {
6345 if (component == EntityBad || !valid(entity))
6346 return false;
6347
6348 const auto* pItem = component.pair() ? comp_cache().find_pair_payload(component) : comp_cache().find(component);
6349 if (pItem == nullptr || !raw_component_payload_args_valid(*pItem, data, size))
6350 return false;
6351
6352 if (has_direct(entity, component))
6353 return false;
6354 if (pItem->comp.storage_type() == DataStorageType::Sparse) {
6355 if (component.pair())
6356 return false;
6357 const auto mode = sparse_storage_mode(component);
6358 if (mode == SparseStorageMode::None)
6359 return false;
6360
6361 auto& store = sparse_component_store_erased_mut(component, *pItem);
6362 auto* pPayload = store.func_add(store.pStore, entity);
6363 if (size != 0)
6364 memcpy(pPayload, data, size);
6365 finish_sparse_component_add_inter(entity, component, mode);
6366 return true;
6367 }
6368
6369 EntityBuilder eb(*this, entity);
6370#if GAIA_OBSERVERS_ENABLED
6371 auto addDiffCtx =
6372 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&component, 1}, EntitySpan{&entity, 1});
6373#endif
6374 eb.add_inter_init(component);
6375 eb.commit();
6376
6377 const auto payload = mut_raw(entity, component);
6378 GAIA_ASSERT(payload.valid());
6379 if (payload.valid() && size != 0)
6380 memcpy(payload.data, data, size);
6381
6382 notify_add_single(entity, component);
6383#if GAIA_OBSERVERS_ENABLED
6384 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
6385#endif
6386 return payload.valid();
6387 }
6388
6395 bool set_raw(Entity entity, Entity component, const void* data, uint32_t size) {
6396 if (component == EntityBad)
6397 return false;
6398
6399 const auto payload = mut_raw(entity, component);
6400 if (!payload.valid() || payload.size != size || (size != 0 && data == nullptr))
6401 return false;
6402 if (size != 0)
6403 memcpy(payload.data, data, size);
6404
6405 finish_write(entity, component);
6406 return true;
6407 }
6408
6412 void modify_raw(Entity entity, Entity component) {
6413 if (!mut_raw(entity, component).valid()) {
6414 if (component == EntityBad || !valid(entity))
6415 return;
6416
6417 const auto& ec = fetch(entity);
6418 const auto* pItem = !is_req_del(ec) ? component_item(entity, component) : nullptr;
6419 if (pItem == nullptr || !soa_field_supported(*pItem) ||
6420 core::get_index(ec.pChunk->ids_view(), component) == BadIndex)
6421 return;
6422 }
6423 finish_write(entity, component);
6424 }
6425
6426 //----------------------------------------------------------------------
6427
6434 GAIA_ASSERT(valid(entity));
6435
6436 const auto& ec = m_recs.entities[entity.id()];
6437 return ComponentGetter{*this, ec.pChunk, entity, ec.row};
6438 }
6439
6447 template <typename T>
6448 GAIA_NODISCARD decltype(auto) get(Entity entity) const {
6449 using FT = typename component_type_t<T>::TypeFull;
6450 const auto compEntity = [&]() {
6451 if constexpr (is_pair<FT>::value) {
6452 const auto rel = comp_cache().template get<typename FT::rel>().entity;
6453 const auto tgt = comp_cache().template get<typename FT::tgt>().entity;
6454 return (Entity)Pair(rel, tgt);
6455 } else {
6456 return comp_cache().template get<FT>().entity;
6457 }
6458 }();
6459 if constexpr (uses_compile_time_sparse_storage<FT>()) {
6460 const auto owner = id_owner_inter(entity, compEntity);
6461 GAIA_ASSERT(owner != EntityBad);
6462 const auto* pStore = sparse_component_store<FT>(compEntity);
6463 GAIA_ASSERT(pStore != nullptr);
6464 return pStore->get(owner);
6465 }
6466
6467 const auto owner = id_owner_inter(entity, compEntity);
6468 GAIA_ASSERT(owner != EntityBad);
6469 return acc(owner).template get<T>();
6470 }
6471
6477 template <typename T>
6478 GAIA_NODISCARD decltype(auto) get(Entity entity, Entity object) const {
6479 using FT = typename component_type_t<T>::TypeFull;
6480 if constexpr (supports_sparse_component_storage<FT>()) {
6481 if (can_use_sparse_component_storage<FT>(object)) {
6482 const auto owner = id_owner_inter(entity, object);
6483 GAIA_ASSERT(owner != EntityBad);
6484 return sparse_component_get_value<FT>(object, owner);
6485 }
6486 }
6487
6488 const auto owner = id_owner_inter(entity, object);
6489 GAIA_ASSERT(owner != EntityBad);
6490 return acc(owner).template get<T>(object);
6491 }
6492
6493 //----------------------------------------------------------------------
6494
6498 GAIA_NODISCARD bool has(Entity entity) const {
6499 // Pair
6500 if (entity.pair()) {
6501 if (entity == Pair(All, All))
6502 return true;
6503
6504 if (is_wildcard(entity)) {
6505 if (!m_entityToArchetypeMap.contains(EntityLookupKey(entity)))
6506 return false;
6507
6508 // If the pair is found, both entities forming it need to be found as well
6509 GAIA_ASSERT(has(get(entity.id())) && has(get(entity.gen())));
6510
6511 return true;
6512 }
6513
6514 const auto* pPair = m_recs.pair_record_find(entity);
6515 if (pPair == nullptr)
6516 return false;
6517
6518 const auto& ec = *pPair;
6519 if (is_req_del(ec))
6520 return false;
6521
6522#if GAIA_ASSERT_ENABLED
6523 // If the pair is found, both entities forming it need to be found as well
6524 GAIA_ASSERT(has(get(entity.id())) && has(get(entity.gen())));
6525
6526 // Index of the entity must fit inside the chunk
6527 auto* pChunk = ec.pChunk;
6528 GAIA_ASSERT(pChunk != nullptr && ec.row < pChunk->size());
6529#endif
6530
6531 return true;
6532 }
6533
6534 // Regular entity
6535 {
6536 // Entity ID has to fit inside the entity array
6537 if (entity.id() >= m_recs.entities.size() || !m_recs.entities.has(entity.id()))
6538 return false;
6539
6540 // Index of the entity must fit inside the chunk
6541 const auto& ec = m_recs.entities[entity.id()];
6542 if (is_req_del(ec))
6543 return false;
6544
6545 auto* pChunk = ec.pChunk;
6546 return pChunk != nullptr && ec.row < pChunk->size();
6547 }
6548 }
6549
6553 GAIA_NODISCARD bool has(Pair pair) const {
6554 return has((Entity)pair);
6555 }
6556
6563 GAIA_NODISCARD bool has(Entity entity, Entity object) const {
6564 return has_inter(entity, object, true);
6565 }
6566
6571 GAIA_NODISCARD bool has_direct(Entity entity, Entity object) const {
6572 return has_inter(entity, object, false);
6573 }
6574
6579 GAIA_NODISCARD bool has_direct(Entity entity, Pair pair) const {
6580 return has_inter(entity, (Entity)pair, false);
6581 }
6582
6583 private:
6589 GAIA_NODISCARD bool has_inter(Entity entity, Entity object, bool allowSemanticIs) const {
6590 const auto& ec = fetch(entity);
6591 if (is_req_del(ec))
6592 return false;
6593
6594 if (object.pair() && has_nonfragmenting_relation_pair(entity, object))
6595 return true;
6596 if (!object.pair()) {
6597 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
6598 if (itSparseStore != m_sparseComponentsByComp.end())
6599 return has_direct_sparse_component_inter(entity, object) ||
6600 (allowSemanticIs && inherited_id_owner(entity, object) != EntityBad);
6601 }
6602
6603 const auto* pArchetype = ec.pArchetype;
6604
6605 if (object.pair()) {
6606 if (allowSemanticIs && object.id() == Is.id() && !is_wildcard(object.gen())) {
6607 const auto target = get(object.gen());
6608 return valid(target) && is(entity, target);
6609 }
6610
6611 // Early exit if there are no pairs on the archetype
6612 if (pArchetype->pairs() == 0)
6613 return false;
6614
6615 EntityId rel = object.id();
6616 EntityId tgt = object.gen();
6617
6618 // (*,*)
6619 if (rel == All.id() && tgt == All.id())
6620 return true;
6621
6622 // (X,*)
6623 if (rel != All.id() && tgt == All.id()) {
6624 auto ids = pArchetype->ids_view();
6625 for (auto id: ids) {
6626 if (!id.pair())
6627 continue;
6628 if (id.id() == rel)
6629 return true;
6630 }
6631
6632 return false;
6633 }
6634
6635 // (*,X)
6636 if (rel == All.id() && tgt != All.id()) {
6637 auto ids = pArchetype->ids_view();
6638 for (auto id: ids) {
6639 if (!id.pair())
6640 continue;
6641 if (id.gen() == tgt)
6642 return true;
6643 }
6644
6645 return false;
6646 }
6647 }
6648
6649 if (pArchetype->has(object))
6650 return true;
6651
6652 return allowSemanticIs && inherited_id_owner(entity, object) != EntityBad;
6653 }
6654
6655 public:
6659 GAIA_NODISCARD std::span<const Entity> lookup_path() const {
6660 return {m_componentLookupPath.data(), m_componentLookupPath.size()};
6661 }
6662
6666 void lookup_path(std::span<const Entity> scopes) {
6667 m_componentLookupPath.clear();
6668 m_componentLookupPath.reserve((uint32_t)scopes.size());
6669 for (const auto scopeEntity: scopes) {
6670 GAIA_ASSERT(scopeEntity != EntityBad && valid(scopeEntity) && !scopeEntity.pair());
6671 if (scopeEntity == EntityBad || !valid(scopeEntity) || scopeEntity.pair())
6672 continue;
6673
6674 m_componentLookupPath.push_back(scopeEntity);
6675 }
6676 }
6677
6680 GAIA_NODISCARD Entity scope() const {
6681 return m_componentScope;
6682 }
6683
6689 GAIA_ASSERT(scope == EntityBad || (valid(scope) && !scope.pair()));
6690 const auto prev = m_componentScope;
6691 if (scope == EntityBad || (valid(scope) && !scope.pair())) {
6692 m_componentScope = scope;
6693 invalidate_scope_path_cache();
6694 }
6695 return prev;
6696 }
6697
6703 template <typename Func>
6704 void scope(Entity scopeEntity, Func&& func) {
6705 struct ComponentScopeRestore final {
6706 World& world;
6707 Entity prevScope;
6708 ~ComponentScopeRestore() {
6709 world.scope(prevScope);
6710 }
6711 };
6712
6713 ComponentScopeRestore restore{*this, scope(scopeEntity)};
6714 func();
6715 }
6716
6723 Entity module(const char* path, uint32_t len = 0) {
6724 if (path == nullptr || path[0] == 0)
6725 return EntityBad;
6726
6727 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(path, ComponentCacheItem::MaxNameLength) : len;
6728 if (l == 0 || l >= ComponentCacheItem::MaxNameLength)
6729 return EntityBad;
6730 if (path[l - 1] == '.')
6731 return EntityBad;
6732
6733 Entity parent = EntityBad;
6734 uint32_t partBeg = 0;
6735 while (partBeg < l) {
6736 uint32_t partEnd = partBeg;
6737 while (partEnd < l && path[partEnd] != '.')
6738 ++partEnd;
6739 if (partEnd == partBeg)
6740 return EntityBad;
6741
6742 const auto partLen = partEnd - partBeg;
6743 const auto key = EntityNameLookupKey(path + partBeg, partLen, 0);
6744 const auto it = m_nameToEntity.find(key);
6745 Entity curr = EntityBad;
6746
6747 if (it != m_nameToEntity.end()) {
6748 curr = it->second;
6749 if (parent != EntityBad && !static_cast<const World&>(*this).child(curr, parent)) {
6750 GAIA_ASSERT2(false, "Module path collides with an existing entity name outside the requested scope");
6751 return EntityBad;
6752 }
6753 } else {
6754 curr = add();
6755 name(curr, path + partBeg, partLen);
6756 if (parent != EntityBad)
6757 child(curr, parent);
6758 }
6759
6760 parent = curr;
6761 partBeg = partEnd + 1;
6762 }
6763
6764 return parent;
6765 }
6766
6773 GAIA_NODISCARD bool has(Entity entity, Pair pair) const {
6774 return has(entity, (Entity)pair);
6775 }
6776
6783 template <typename T>
6784 GAIA_NODISCARD bool has(Entity entity) const {
6785 GAIA_ASSERT(valid(entity));
6786
6787 using FT = typename component_type_t<T>::TypeFull;
6788 const auto compEntity = [&]() {
6789 if constexpr (is_pair<FT>::value) {
6790 const auto* pRel = comp_cache().template find<typename FT::rel>();
6791 const auto* pTgt = comp_cache().template find<typename FT::tgt>();
6792 if (pRel == nullptr || pTgt == nullptr)
6793 return EntityBad;
6794
6795 const auto rel = pRel->entity;
6796 const auto tgt = pTgt->entity;
6797 return (Entity)Pair(rel, tgt);
6798 } else {
6799 const auto* pItem = comp_cache().template find<FT>();
6800 return pItem != nullptr ? pItem->entity : EntityBad;
6801 }
6802 }();
6803 if (compEntity == EntityBad)
6804 return false;
6805
6806 return id_owner_inter(entity, compEntity) != EntityBad;
6807 }
6808
6809 //----------------------------------------------------------------------
6810
6820 void name(Entity entity, const char* name, uint32_t len = 0) {
6821 EntityBuilder(*this, entity).name(name, len);
6822 }
6823
6837 void name_raw(Entity entity, const char* name, uint32_t len = 0) {
6838 EntityBuilder(*this, entity).name_raw(name, len);
6839 }
6840
6845 GAIA_NODISCARD util::str_view name(Entity entity) const {
6846 if (entity.pair())
6847 return {};
6848
6849 const auto& ec = m_recs.entities[entity.id()];
6850 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
6851 if (compIdx == BadIndex)
6852 return {};
6853
6854 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
6855 GAIA_ASSERT(core::check_alignment(pDesc));
6856 return {pDesc->name, pDesc->name_len};
6857 }
6858
6863 GAIA_NODISCARD util::str_view name(EntityId entityId) const {
6864 auto entity = get(entityId);
6865 return name(entity);
6866 }
6867
6868 //----------------------------------------------------------------------
6869
6877 GAIA_NODISCARD Entity resolve(const char* name, uint32_t len = 0) const {
6878 if (name == nullptr || name[0] == 0)
6879 return EntityBad;
6880
6881 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
6882 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
6883
6884 if (memchr(name, '.', l) != nullptr) {
6885 const auto entity = get_entity_inter(name, l);
6886 if (entity != EntityBad)
6887 return entity;
6888 }
6889
6890 return get_inter(name, l);
6891 }
6892
6898 void resolve(cnt::darray<Entity>& out, const char* name, uint32_t len = 0) const {
6899 out.clear();
6900 if (name == nullptr || name[0] == 0)
6901 return;
6902
6903 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
6904 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
6905
6906 auto push_unique = [&](Entity entity) {
6907 if (entity == EntityBad)
6908 return;
6909 for (const auto existing: out) {
6910 if (existing == entity)
6911 return;
6912 }
6913 out.push_back(entity);
6914 };
6915
6916 push_unique(get_entity_inter(name, l));
6917
6918 if (memchr(name, '.', l) == nullptr && memchr(name, ':', l) == nullptr)
6919 add_comp_lookup_hits_inter(out, name, l);
6920
6921 const bool isPath = memchr(name, '.', l) != nullptr;
6922 const bool isSymbol = memchr(name, ':', l) != nullptr;
6923 add_comp_exact_hits_inter(out, name, l, isPath, isSymbol);
6924
6925 push_unique(alias(name, l));
6926 }
6927
6933 GAIA_NODISCARD Entity get(const char* name, uint32_t len = 0) const {
6934 return resolve(name, len);
6935 }
6936
6937 private:
6942 GAIA_NODISCARD Entity find_named_entity_inter(const char* name, uint32_t len = 0) const {
6943 if (name == nullptr || name[0] == 0)
6944 return EntityBad;
6945
6946 const auto key = EntityNameLookupKey(name, len, 0);
6947 const auto it = m_nameToEntity.find(key);
6948 return it != m_nameToEntity.end() ? it->second : EntityBad;
6949 }
6950
6955 GAIA_NODISCARD bool hierarchy_child_matches_parent(Entity child, Entity parent) const {
6956 return this->child(child, parent) || this->parent(child, parent);
6957 }
6958
6963 GAIA_NODISCARD Entity get_entity_inter(const char* name, uint32_t len = 0) const {
6964 if (name == nullptr || name[0] == 0)
6965 return EntityBad;
6966
6967 if (len == 0) {
6968 while (name[len] != '\0')
6969 ++len;
6970 }
6971
6972 Entity parent = EntityBad;
6973 Entity child = EntityBad;
6974 uint32_t posDot = 0;
6975 std::span<const char> str(name, len);
6976
6977 posDot = core::get_index(str, '.');
6978 if (posDot == BadIndex)
6979 return find_named_entity_inter(str.data(), (uint32_t)str.size());
6980
6981 if (posDot == 0)
6982 return EntityBad;
6983
6984 parent = find_named_entity_inter(str.data(), posDot);
6985 if (parent == EntityBad)
6986 return EntityBad;
6987
6988 str = str.subspan(posDot + 1);
6989 while (!str.empty()) {
6990 posDot = core::get_index(str, '.');
6991
6992 if (posDot == BadIndex) {
6993 child = find_named_entity_inter(str.data(), (uint32_t)str.size());
6994 if (child == EntityBad || !hierarchy_child_matches_parent(child, parent))
6995 return EntityBad;
6996
6997 return child;
6998 }
6999
7000 if (posDot == 0)
7001 return EntityBad;
7002
7003 child = find_named_entity_inter(str.data(), posDot);
7004 if (child == EntityBad || !hierarchy_child_matches_parent(child, parent))
7005 return EntityBad;
7006
7007 parent = child;
7008 str = str.subspan(posDot + 1);
7009 }
7010
7011 return parent;
7012 }
7013
7018 GAIA_NODISCARD Entity get_inter(const char* name, uint32_t len = 0) const {
7019 if (name == nullptr || name[0] == 0)
7020 return EntityBad;
7021
7022 auto key = EntityNameLookupKey(name, len, 0);
7023 const auto l = key.len();
7024 const bool isUnqualifiedCompName = is_unqualified_comp_name_inter(name, l);
7025 const auto namedEntity = find_named_entity_inter(name, l);
7026
7027 if (has_comp_lookup_ctx_inter() && isUnqualifiedCompName) {
7028 if (const auto* pScopedItem = resolve_component_name_inter(name, l); pScopedItem != nullptr)
7029 return pick_name_or_comp_inter(namedEntity, pScopedItem);
7030 }
7031
7032 if (namedEntity != EntityBad)
7033 return namedEntity;
7034
7035 if (const auto* pItem = resolve_component_name_inter(name, l); pItem != nullptr)
7036 return pItem->entity;
7037
7038 const auto aliasEntity = alias(name, l);
7039 if (aliasEntity != EntityBad)
7040 return aliasEntity;
7041
7042 // No entity with the given name exists. Return a bad entity
7043 return EntityBad;
7044 }
7045
7046 public:
7047 //----------------------------------------------------------------------
7048
7053 GAIA_NODISCARD const cnt::set<EntityLookupKey>* relations(Entity target) const {
7054 return m_pairLookup.relations(target);
7055 }
7056
7062 GAIA_NODISCARD Entity relation(Entity entity, Entity target) const {
7063 GAIA_ASSERT(valid(entity));
7064 if (!valid(target))
7065 return EntityBad;
7066
7067 for (const auto& it: m_nonFragmentingRelationsByRel) {
7068 if (it.second.target(entity) == target)
7069 return it.first.entity();
7070 }
7071
7072 const auto& ec = fetch(entity);
7073 const auto* pArchetype = ec.pArchetype;
7074
7075 // Early exit if there are no pairs on the archetype
7076 if (pArchetype->pairs() == 0)
7077 return EntityBad;
7078
7079 const auto indices = pArchetype->pair_tgt_indices(target);
7080 if (indices.empty())
7081 return EntityBad;
7082
7083 const auto ids = pArchetype->ids_view();
7084 const auto e = ids[indices[0]];
7085 const auto& ecRel = m_recs.entities[e.id()];
7086 return *ecRel.pEntity;
7087 }
7088
7094 template <typename Func>
7095 void relations(Entity entity, Entity target, Func func) const {
7096 GAIA_ASSERT(valid(entity));
7097 if (!valid(target))
7098 return;
7099
7100 for (const auto& it: m_nonFragmentingRelationsByRel) {
7101 if (it.second.target(entity) == target)
7102 func(it.first.entity());
7103 }
7104
7105 const auto& ec = fetch(entity);
7106 const auto* pArchetype = ec.pArchetype;
7107
7108 // Early exit if there are no pairs on the archetype
7109 if (pArchetype->pairs() == 0)
7110 return;
7111
7112 const auto ids = pArchetype->ids_view();
7113 for (auto idsIdx: pArchetype->pair_tgt_indices(target)) {
7114 const auto e = ids[idsIdx];
7115
7116 const auto& ecRel = m_recs.entities[e.id()];
7117 auto relation = *ecRel.pEntity;
7118 func(relation);
7119 }
7120 }
7121
7128 template <typename Func>
7129 void relations_if(Entity entity, Entity target, Func func) const {
7130 GAIA_ASSERT(valid(entity));
7131 if (!valid(target))
7132 return;
7133
7134 for (const auto& it: m_nonFragmentingRelationsByRel) {
7135 if (it.second.target(entity) == target && !func(it.first.entity()))
7136 return;
7137 }
7138
7139 const auto& ec = fetch(entity);
7140 const auto* pArchetype = ec.pArchetype;
7141
7142 // Early exit if there are no pairs on the archetype
7143 if (pArchetype->pairs() == 0)
7144 return;
7145
7146 const auto ids = pArchetype->ids_view();
7147 for (auto idsIdx: pArchetype->pair_tgt_indices(target)) {
7148 const auto e = ids[idsIdx];
7149
7150 const auto& ecRel = m_recs.entities[e.id()];
7151 auto relation = *ecRel.pEntity;
7152 if (!func(relation))
7153 return;
7154 }
7155 }
7156
7161 GAIA_NODISCARD const cnt::darray<Entity>& as_relations_trav_cache(Entity target) const {
7162 const auto key = EntityLookupKey(target);
7163 const auto itCache = m_entityToAsRelationsTravCache.find(key);
7164 if (itCache != m_entityToAsRelationsTravCache.end())
7165 return itCache->second;
7166
7167 auto& cache = m_entityToAsRelationsTravCache[key];
7168 const auto it = m_entityToAsRelations.find(key);
7169 if (it == m_entityToAsRelations.end())
7170 return cache;
7171
7174 stack.reserve((uint32_t)it->second.size());
7175 for (auto relation: it->second)
7176 stack.push_back(relation);
7177
7178 while (!stack.empty()) {
7179 const auto relationKey = stack.back();
7180 stack.pop_back();
7181
7182 const auto relation = relationKey.entity();
7183 cache.push_back(relation);
7184
7185 const auto itChild = m_entityToAsRelations.find(relationKey);
7186 if (itChild == m_entityToAsRelations.end())
7187 continue;
7188
7189 for (auto childRelation: itChild->second)
7190 stack.push_back(childRelation);
7191 }
7192
7193 return cache;
7194 }
7195
7200 GAIA_NODISCARD const cnt::darray<Entity>& as_targets_trav_cache(Entity relation) const {
7201 const auto key = EntityLookupKey(relation);
7202 const auto itCache = m_entityToAsTargetsTravCache.find(key);
7203 if (itCache != m_entityToAsTargetsTravCache.end())
7204 return itCache->second;
7205
7206 auto& cache = m_entityToAsTargetsTravCache[key];
7207 const auto it = m_entityToAsTargets.find(key);
7208 if (it == m_entityToAsTargets.end())
7209 return cache;
7210
7213 stack.reserve((uint32_t)it->second.size());
7214 for (auto target: it->second)
7215 stack.push_back(target);
7216
7217 while (!stack.empty()) {
7218 const auto targetKey = stack.back();
7219 stack.pop_back();
7220
7221 const auto target = targetKey.entity();
7222 cache.push_back(target);
7223
7224 const auto itChild = m_entityToAsTargets.find(targetKey);
7225 if (itChild == m_entityToAsTargets.end())
7226 continue;
7227
7228 for (auto childTarget: itChild->second)
7229 stack.push_back(childTarget);
7230 }
7231
7232 return cache;
7233 }
7234
7240 GAIA_NODISCARD const cnt::darray<Entity>& targets_trav_cache(Entity relation, Entity source) const {
7241 const auto key = EntityLookupKey(Pair(relation, source));
7242 const auto itCache = m_targetsTravCache.find(key);
7243 if (itCache != m_targetsTravCache.end())
7244 return itCache->second;
7245
7246 auto& cache = m_targetsTravCache[key];
7247 m_relationCachesPopulated = true;
7248 if (!valid(relation) || !valid(source))
7249 return cache;
7250
7251 auto curr = source;
7252 GAIA_FOR(MAX_TRAV_DEPTH) {
7253 const auto next = target(curr, relation);
7254 if (next == EntityBad || next == curr)
7255 break;
7256
7257 cache.push_back(next);
7258 curr = next;
7259 }
7260
7261 return cache;
7262 }
7263
7268 GAIA_NODISCARD const cnt::darray<Entity>& targets_all_cache(Entity source) const {
7269 const auto key = EntityLookupKey(source);
7270 const auto itCache = m_targetsAllCache.find(key);
7271 if (itCache != m_targetsAllCache.end())
7272 return itCache->second;
7273
7274 auto& cache = m_targetsAllCache[key];
7275 m_relationCachesPopulated = true;
7276 if (!valid(source))
7277 return cache;
7278
7279 const auto visitStamp = next_entity_visit_stamp();
7280
7281 for (const auto& it: m_nonFragmentingRelationsByRel) {
7282 const auto target = it.second.target(source);
7283 if (target != EntityBad && try_mark_entity_visited(target, visitStamp))
7284 cache.push_back(target);
7285 }
7286
7287 const auto& ec = fetch(source);
7288 const auto* pArchetype = ec.pArchetype;
7289 if (pArchetype->pairs() == 0)
7290 return cache;
7291
7292 const auto ids = pArchetype->ids_view();
7293 for (auto idsIdx: pArchetype->pair_indices()) {
7294 const auto id = ids[idsIdx];
7295 const auto target = pair_target_if_alive(id);
7296 if (target == EntityBad)
7297 continue;
7298 if (try_mark_entity_visited(target, visitStamp))
7299 cache.push_back(target);
7300 }
7301
7302 return cache;
7303 }
7304
7309 GAIA_NODISCARD const cnt::darray<Entity>& sources_all_cache(Entity target) const {
7310 const auto key = EntityLookupKey(target);
7311 const auto itCache = m_sourcesAllCache.find(key);
7312 if (itCache != m_sourcesAllCache.end())
7313 return itCache->second;
7314
7315 auto& cache = m_sourcesAllCache[key];
7316 m_relationCachesPopulated = true;
7317 if (!valid(target))
7318 return cache;
7319
7320 const auto visitStamp = next_entity_visit_stamp();
7321
7322 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
7323 (void)relKey;
7324 const auto* pSources = store.sources(target);
7325 if (pSources == nullptr)
7326 continue;
7327
7328 for (auto source: *pSources) {
7329 if (valid(source) && try_mark_entity_visited(source, visitStamp))
7330 cache.push_back(source);
7331 }
7332 }
7333
7334 const auto pair = Pair(All, target);
7335 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
7336 if (it == m_entityToArchetypeMap.end())
7337 return cache;
7338
7339 for (const auto& record: it->second) {
7340 const auto* pArchetype = record.pArchetype;
7341 if (pArchetype->is_req_del())
7342 continue;
7343
7344 for (const auto* pChunk: pArchetype->chunks()) {
7345 const auto entities = pChunk->entity_view();
7346 GAIA_EACH(entities) {
7347 const auto source = entities[i];
7348 if (!valid(source))
7349 continue;
7350 if (try_mark_entity_visited(source, visitStamp))
7351 cache.push_back(source);
7352 }
7353 }
7354 }
7355
7356 return cache;
7357 }
7358
7365 template <typename Func>
7366 void targets_trav(Entity relation, Entity source, Func func) const {
7367 if (!valid(relation) || !valid(source))
7368 return;
7369
7370 auto curr = source;
7371 GAIA_FOR(MAX_TRAV_DEPTH) {
7372 const auto next = target(curr, relation);
7373 if (next == EntityBad || next == curr)
7374 break;
7375 if (!enabled(next))
7376 break;
7377
7378 func(next);
7379 curr = next;
7380 }
7381 }
7382
7390 template <typename Func>
7391 GAIA_NODISCARD bool targets_trav_if(Entity relation, Entity source, Func func) const {
7392 if (!valid(relation) || !valid(source))
7393 return false;
7394
7395 auto curr = source;
7396 GAIA_FOR(MAX_TRAV_DEPTH) {
7397 const auto next = target(curr, relation);
7398 if (next == EntityBad || next == curr)
7399 break;
7400 if (!enabled(next))
7401 break;
7402
7403 if (!func(next))
7404 return true;
7405 curr = next;
7406 }
7407
7408 return false;
7409 }
7410
7416 GAIA_NODISCARD const cnt::darray<Entity>& sources_bfs_trav_cache(Entity relation, Entity rootTarget) const {
7417 const auto key = EntityLookupKey(Pair(relation, rootTarget));
7418 const auto itCache = m_srcBfsTravCache.find(key);
7419 if (itCache != m_srcBfsTravCache.end())
7420 return itCache->second;
7421
7422 auto& cache = m_srcBfsTravCache[key];
7423 m_relationCachesPopulated = true;
7424 if (!valid(relation) || !valid(rootTarget))
7425 return cache;
7426
7429 queue.push_back(rootTarget);
7430
7432 visited.insert(EntityLookupKey(rootTarget));
7433
7434 for (uint32_t i = 0; i < queue.size(); ++i) {
7435 const auto currTarget = queue[i];
7436
7437 children.clear();
7438 sources(relation, currTarget, [&](Entity source) {
7439 const auto keySource = EntityLookupKey(source);
7440 const auto& ins = visited.insert(keySource);
7441 if (!ins.second)
7442 return;
7443
7444 children.push_back(source);
7445 });
7446
7447 core::sort(children, [](Entity left, Entity right) {
7448 return left.id() < right.id();
7449 });
7450
7451 for (auto child: children) {
7452 cache.push_back(child);
7453 queue.push_back(child);
7454 }
7455 }
7456
7457 return cache;
7458 }
7459
7466 GAIA_NODISCARD uint32_t depth_order_cache(Entity relation, Entity sourceTarget) const {
7467 const auto key = EntityLookupKey(Pair(relation, sourceTarget));
7468 const auto itCache = m_depthOrderCache.find(key);
7469 if (itCache != m_depthOrderCache.end()) {
7470 GAIA_ASSERT(itCache->second != GroupIdMax && "depth_order requires an acyclic relation graph");
7471 return itCache->second;
7472 }
7473
7474 if (!valid(relation) || !valid(sourceTarget))
7475 return 0;
7476
7477 // Mark this node as in-flight so cycles trip a debug assert instead of recursing forever.
7478 m_relationCachesPopulated = true;
7479 m_depthOrderCache[key] = GroupIdMax;
7480
7481 uint32_t depth = 1;
7482 targets(sourceTarget, relation, [&](Entity next) {
7483 const auto nextDepth = depth_order_cache(relation, next);
7484 if (nextDepth == 0)
7485 return;
7486 const auto candidate = nextDepth + 1;
7487 if (candidate > depth)
7488 depth = candidate;
7489 });
7490
7491 m_depthOrderCache[key] = depth;
7492 return depth;
7493 }
7494
7499 template <typename Func>
7500 void as_relations_trav(Entity target, Func func) const {
7501 if (!valid(target))
7502 return;
7503
7504 const auto& relations = as_relations_trav_cache(target);
7505 for (auto relation: relations)
7506 func(relation);
7507 }
7508
7514 template <typename Func>
7515 GAIA_NODISCARD bool as_relations_trav_if(Entity target, Func func) const {
7516 if (!valid(target))
7517 return false;
7518
7519 const auto& relations = as_relations_trav_cache(target);
7520 for (auto relation: relations) {
7521 if (func(relation))
7522 return true;
7523 }
7524
7525 return false;
7526 }
7527
7528 //----------------------------------------------------------------------
7529
7534 GAIA_NODISCARD const cnt::set<EntityLookupKey>* targets(Entity relation) const {
7535 return m_pairLookup.targets(relation);
7536 }
7537
7543 GAIA_NODISCARD Entity target(Entity entity, Entity relation) const {
7544 if (!valid(entity))
7545 return EntityBad;
7546 if (relation != All && !valid(relation))
7547 return EntityBad;
7548
7549 if (relation == All) {
7550 const auto& targets = targets_all_cache(entity);
7551 return targets.empty() ? EntityBad : targets[0];
7552 }
7553
7554 if (relation_uses_non_fragmenting_storage(relation)) {
7555 const auto* pStore = nonfragmenting_relation_store(relation);
7556 if (pStore == nullptr)
7557 return EntityBad;
7558
7559 return pStore->target(entity);
7560 }
7561
7562 const auto& ec = fetch(entity);
7563 const auto* pArchetype = ec.pArchetype;
7564
7565 // Early exit if there are no pairs on the archetype
7566 if (pArchetype->pairs() == 0)
7567 return EntityBad;
7568
7569 const auto ids = pArchetype->ids_view();
7570 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
7571 const auto e = ids[idsIdx];
7572 const auto target = pair_target_if_alive(e);
7573 if (target == EntityBad)
7574 continue;
7575 return target;
7576 }
7577
7578 return EntityBad;
7579 }
7580
7586 template <typename Func>
7587 void targets(Entity entity, Entity relation, Func func) const {
7588 if (!valid(entity))
7589 return;
7590 if (relation != All && !valid(relation))
7591 return;
7592
7593 if (relation == All) {
7594 for (auto target: targets_all_cache(entity))
7595 func(target);
7596 return;
7597 }
7598
7599 if (relation_uses_non_fragmenting_storage(relation)) {
7600 const auto target = this->target(entity, relation);
7601 if (target != EntityBad)
7602 func(target);
7603 return;
7604 }
7605
7606 const auto& ec = fetch(entity);
7607 const auto* pArchetype = ec.pArchetype;
7608
7609 // Early exit if there are no pairs on the archetype
7610 if (pArchetype->pairs() == 0)
7611 return;
7612
7613 const auto ids = pArchetype->ids_view();
7614 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
7615 const auto e = ids[idsIdx];
7616 const auto target = pair_target_if_alive(e);
7617 if (target == EntityBad)
7618 continue;
7619 func(target);
7620 }
7621 }
7622
7629 template <typename Func>
7630 void targets_if(Entity entity, Entity relation, Func func) const {
7631 GAIA_ASSERT(valid(entity));
7632 if (relation != All && !valid(relation))
7633 return;
7634
7635 if (relation == All) {
7636 for (auto target: targets_all_cache(entity)) {
7637 if (!func(target))
7638 return;
7639 }
7640 return;
7641 }
7642
7643 if (relation_uses_non_fragmenting_storage(relation)) {
7644 const auto target = this->target(entity, relation);
7645 if (target != EntityBad)
7646 (void)func(target);
7647 return;
7648 }
7649
7650 const auto& ec = fetch(entity);
7651 const auto* pArchetype = ec.pArchetype;
7652
7653 // Early exit if there are no pairs on the archetype
7654 if (pArchetype->pairs() == 0)
7655 return;
7656
7657 const auto ids = pArchetype->ids_view();
7658 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
7659 const auto e = ids[idsIdx];
7660 const auto target = pair_target_if_alive(e);
7661 if (target == EntityBad)
7662 continue;
7663 if (!func(target))
7664 return;
7665 }
7666 }
7667
7671 GAIA_NODISCARD Entity pair_target_if_alive(Entity pair) const {
7672 GAIA_ASSERT(pair.pair());
7673 if (!valid_entity_id((EntityId)pair.gen()))
7674 return EntityBad;
7675
7676 const auto& ecTarget = m_recs.entities[pair.gen()];
7677 if (ecTarget.pEntity == nullptr)
7678 return EntityBad;
7679
7680 const auto target = *ecTarget.pEntity;
7681 return valid(target) ? target : EntityBad;
7682 }
7683
7689 template <typename Func>
7690 void sources(Entity relation, Entity target, Func func) const {
7691 if ((relation != All && !valid(relation)) || !valid(target))
7692 return;
7693
7694 if (relation == All) {
7695 for (auto source: sources_all_cache(target))
7696 func(source);
7697 return;
7698 }
7699
7700 if (relation_uses_non_fragmenting_storage(relation)) {
7701 const auto* pStore = nonfragmenting_relation_store(relation);
7702 if (pStore == nullptr)
7703 return;
7704
7705 const auto* pSources = pStore->sources(target);
7706 if (pSources == nullptr)
7707 return;
7708
7709 for (auto source: *pSources) {
7710 if (!valid(source))
7711 continue;
7712
7713 func(source);
7714 }
7715 return;
7716 }
7717
7718 const auto pair = Pair(relation, target);
7719 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
7720 if (it == m_entityToArchetypeMap.end())
7721 return;
7722
7723 for (const auto& record: it->second) {
7724 const auto* pArchetype = record.pArchetype;
7725 if (pArchetype->is_req_del())
7726 continue;
7727
7728 for (const auto* pChunk: pArchetype->chunks()) {
7729 auto entities = pChunk->entity_view();
7730 GAIA_EACH(entities) {
7731 const auto source = entities[i];
7732 if (!valid(source))
7733 continue;
7734 func(source);
7735 }
7736 }
7737 }
7738 }
7739
7746 template <typename Func>
7747 void sources_if(Entity relation, Entity target, Func func) const {
7748 if ((relation != All && !valid(relation)) || !valid(target))
7749 return;
7750
7751 if (relation == All) {
7752 for (auto source: sources_all_cache(target)) {
7753 if (!func(source))
7754 return;
7755 }
7756 return;
7757 }
7758
7759 if (relation_uses_non_fragmenting_storage(relation)) {
7760 const auto* pStore = nonfragmenting_relation_store(relation);
7761 if (pStore == nullptr)
7762 return;
7763
7764 const auto* pSources = pStore->sources(target);
7765 if (pSources == nullptr)
7766 return;
7767
7768 for (auto source: *pSources) {
7769 if (!valid(source))
7770 continue;
7771
7772 if (!func(source))
7773 return;
7774 }
7775 return;
7776 }
7777
7778 const auto pair = Pair(relation, target);
7779 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
7780 if (it == m_entityToArchetypeMap.end())
7781 return;
7782
7783 for (const auto& record: it->second) {
7784 const auto* pArchetype = record.pArchetype;
7785 if (pArchetype->is_req_del())
7786 continue;
7787
7788 for (const auto* pChunk: pArchetype->chunks()) {
7789 auto entities = pChunk->entity_view();
7790 GAIA_EACH(entities) {
7791 const auto source = entities[i];
7792 if (!valid(source))
7793 continue;
7794 if (!func(source))
7795 return;
7796 }
7797 }
7798 }
7799 }
7800
7801 private:
7804 GAIA_NODISCARD uint64_t next_entity_visit_stamp() const {
7805 ++m_entityVisitStamp;
7806 if (m_entityVisitStamp != 0)
7807 return m_entityVisitStamp;
7808
7809 const auto cnt = (uint32_t)m_entityVisitStamps.size();
7810 GAIA_FOR(cnt) {
7811 m_entityVisitStamps[i] = 0;
7812 }
7813
7814 m_entityVisitStamp = 1;
7815 return m_entityVisitStamp;
7816 }
7817
7822 GAIA_NODISCARD bool try_mark_entity_visited(Entity entity, uint64_t stamp) const {
7823 GAIA_ASSERT(!entity.pair());
7824 if (entity.id() >= m_entityVisitStamps.size())
7825 m_entityVisitStamps.resize(m_recs.entities.size(), 0);
7826
7827 auto& slot = m_entityVisitStamps[entity.id()];
7828 if (slot == stamp)
7829 return false;
7830
7831 slot = stamp;
7832 return true;
7833 }
7834
7840 template <typename Func>
7841 GAIA_NODISCARD bool for_each_inherited_term_entity(Entity term, Func&& func) const {
7842 cnt::set<EntityLookupKey> seen;
7843 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
7844 if (it == m_entityToArchetypeMap.end())
7845 return true;
7846
7847 for (const auto& record: it->second) {
7848 const auto* pArchetype = record.pArchetype;
7849 if (pArchetype->is_req_del())
7850 continue;
7851
7852 for (const auto* pChunk: pArchetype->chunks()) {
7853 const auto entities = pChunk->entity_view();
7854 GAIA_EACH(entities) {
7855 const auto entity = entities[i];
7856 GAIA_ASSERT(valid(entity));
7857 const auto entityKey = EntityLookupKey(entity);
7858 if (seen.contains(entityKey))
7859 continue;
7860 seen.insert(entityKey);
7861
7862 if (!func(entity))
7863 return false;
7864
7865 const auto& descendants = as_relations_trav_cache(entity);
7866 for (const auto descendant: descendants) {
7867 GAIA_ASSERT(valid(descendant));
7868 const auto descendantKey = EntityLookupKey(descendant);
7869 if (seen.contains(descendantKey))
7870 continue;
7871 seen.insert(descendantKey);
7872
7873 if (!func(descendant))
7874 return false;
7875 }
7876 }
7877 }
7878 }
7879
7880 return true;
7881 }
7882
7888 GAIA_NODISCARD uint32_t count_direct_term_entities_inter(Entity term, bool allowSemanticIs) const {
7889 if (term == EntityBad)
7890 return 0;
7891
7892 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
7893 const auto target = get(term.gen());
7894 if (!valid(target))
7895 return 0;
7896
7897 return (uint32_t)as_relations_trav_cache(target).size() + 1;
7898 }
7899
7900 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
7901 uint32_t cnt = 0;
7902 (void)for_each_inherited_term_entity(term, [&](Entity) {
7903 ++cnt;
7904 return true;
7905 });
7906 return cnt;
7907 }
7908
7909 if (term.pair() && relation_uses_non_fragmenting_storage(pair_rel(*this, term))) {
7910 const auto relation = pair_rel(*this, term);
7911 const auto* pStore = nonfragmenting_relation_store(relation);
7912 if (pStore == nullptr)
7913 return 0;
7914
7915 if (is_wildcard(term.gen()))
7916 return pStore->source_count();
7917
7918 const auto* pSources = pStore->sources(pair_tgt(*this, term));
7919 return pSources != nullptr ? (uint32_t)pSources->size() : 0;
7920 }
7921
7922 if (!term.pair() && component_is_non_fragmenting(term)) {
7923 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
7924 return it != m_sparseComponentsByComp.end() ? it->second.func_count(it->second.pStore) : 0;
7925 }
7926
7927 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
7928 if (it == m_entityToArchetypeMap.end())
7929 return 0;
7930
7931 uint32_t cnt = 0;
7932 for (const auto& record: it->second) {
7933 const auto* pArchetype = record.pArchetype;
7934 if (pArchetype->is_req_del())
7935 continue;
7936 for (const auto* pChunk: pArchetype->chunks())
7937 cnt += pChunk->size();
7938 }
7939
7940 return cnt;
7941 }
7942
7947 void collect_direct_term_entities_inter(Entity term, cnt::darray<Entity>& out, bool allowSemanticIs) const {
7948 if (term == EntityBad)
7949 return;
7950
7951 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
7952 const auto target = get(term.gen());
7953 if (!valid(target))
7954 return;
7955
7956 out.push_back(target);
7957 const auto& relations = as_relations_trav_cache(target);
7958 out.reserve(out.size() + (uint32_t)relations.size());
7959 for (auto relation: relations)
7960 out.push_back(relation);
7961 return;
7962 }
7963
7964 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
7965 (void)for_each_inherited_term_entity(term, [&](Entity entity) {
7966 out.push_back(entity);
7967 return true;
7968 });
7969 return;
7970 }
7971
7972 if (term.pair() && relation_uses_non_fragmenting_storage(pair_rel(*this, term))) {
7973 const auto relation = pair_rel(*this, term);
7974 const auto* pStore = nonfragmenting_relation_store(relation);
7975 if (pStore == nullptr)
7976 return;
7977
7978 if (is_wildcard(term.gen())) {
7979 cnt::darray<EntityId> sourceIds;
7980 pStore->collect_source_ids(sourceIds);
7981 out.reserve(out.size() + pStore->source_count());
7982 for (auto sourceId: sourceIds) {
7983 if (!m_recs.entities.has(sourceId))
7984 continue;
7985 out.push_back(EntityContainer::handle(m_recs.entities[sourceId]));
7986 }
7987 return;
7988 }
7989
7990 const auto* pSources = pStore->sources(pair_tgt(*this, term));
7991 if (pSources == nullptr)
7992 return;
7993
7994 out.reserve(out.size() + (uint32_t)pSources->size());
7995 for (auto source: *pSources)
7996 out.push_back(source);
7997 return;
7998 }
7999
8000 if (!term.pair() && component_is_non_fragmenting(term)) {
8001 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
8002 if (it != m_sparseComponentsByComp.end())
8003 it->second.func_collect_entities(it->second.pStore, out);
8004 return;
8005 }
8006
8007 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
8008 if (it == m_entityToArchetypeMap.end())
8009 return;
8010
8011 for (const auto& record: it->second) {
8012 const auto* pArchetype = record.pArchetype;
8013 if (pArchetype->is_req_del())
8014 continue;
8015
8016 for (const auto* pChunk: pArchetype->chunks()) {
8017 const auto entities = pChunk->entity_view();
8018 out.reserve(out.size() + (uint32_t)entities.size());
8019 GAIA_EACH(entities)
8020 out.push_back(entities[i]);
8021 }
8022 }
8023 }
8024
8031 GAIA_NODISCARD bool for_each_direct_term_entity_inter(
8032 Entity term, void* ctx, bool (*func)(void*, Entity), bool allowSemanticIs) const {
8033 if (term == EntityBad)
8034 return true;
8035
8036 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
8037 const auto target = get(term.gen());
8038 if (!valid(target))
8039 return true;
8040
8041 if (!func(ctx, target))
8042 return false;
8043
8044 const auto& relations = as_relations_trav_cache(target);
8045 for (auto relation: relations) {
8046 if (!func(ctx, relation))
8047 return false;
8048 }
8049 return true;
8050 }
8051
8052 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
8053 return for_each_inherited_term_entity(term, [&](Entity entity) {
8054 return func(ctx, entity);
8055 });
8056 }
8057
8058 if (term.pair() && relation_uses_non_fragmenting_storage(pair_rel(*this, term))) {
8059 const auto relation = pair_rel(*this, term);
8060 const auto* pStore = nonfragmenting_relation_store(relation);
8061 if (pStore == nullptr)
8062 return true;
8063
8064 if (is_wildcard(term.gen())) {
8065 cnt::darray<EntityId> sourceIds;
8066 pStore->collect_source_ids(sourceIds);
8067 for (auto sourceId: sourceIds) {
8068 if (!m_recs.entities.has(sourceId))
8069 continue;
8070 if (!func(ctx, EntityContainer::handle(m_recs.entities[sourceId])))
8071 return false;
8072 }
8073 return true;
8074 }
8075
8076 const auto* pSources = pStore->sources(pair_tgt(*this, term));
8077 if (pSources == nullptr)
8078 return true;
8079
8080 for (auto source: *pSources) {
8081 if (!func(ctx, source))
8082 return false;
8083 }
8084 return true;
8085 }
8086
8087 if (!term.pair() && component_is_non_fragmenting(term)) {
8088 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
8089 if (it == m_sparseComponentsByComp.end())
8090 return true;
8091 return it->second.func_for_each_entity(it->second.pStore, ctx, func);
8092 }
8093
8094 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
8095 if (it == m_entityToArchetypeMap.end())
8096 return true;
8097
8098 for (const auto& record: it->second) {
8099 const auto* pArchetype = record.pArchetype;
8100 if (pArchetype->is_req_del())
8101 continue;
8102
8103 for (const auto* pChunk: pArchetype->chunks()) {
8104 const auto entities = pChunk->entity_view();
8105 GAIA_EACH(entities) {
8106 if (!func(ctx, entities[i]))
8107 return false;
8108 }
8109 }
8110 }
8111
8112 return true;
8113 }
8114
8115 public:
8119 GAIA_NODISCARD uint32_t count_direct_term_entities(Entity term) const {
8120 return count_direct_term_entities_inter(term, true);
8121 }
8122
8126 GAIA_NODISCARD uint32_t count_direct_term_entities_direct(Entity term) const {
8127 return count_direct_term_entities_inter(term, false);
8128 }
8129
8134 collect_direct_term_entities_inter(term, out, true);
8135 }
8136
8141 collect_direct_term_entities_inter(term, out, false);
8142 }
8143
8149 GAIA_NODISCARD bool for_each_direct_term_entity(Entity term, void* ctx, bool (*func)(void*, Entity)) const {
8150 return for_each_direct_term_entity_inter(term, ctx, func, true);
8151 }
8152
8158 GAIA_NODISCARD bool
8159 for_each_direct_term_entity_direct(Entity term, void* ctx, bool (*func)(void*, Entity)) const {
8160 return for_each_direct_term_entity_inter(term, ctx, func, false);
8161 }
8162
8168 template <typename Func>
8169 void sources_bfs(Entity relation, Entity rootTarget, Func func) const {
8170 if (!valid(relation) || !valid(rootTarget))
8171 return;
8172
8173 if (m_enabledHierarchyVersion == 0) {
8174 const auto& cachedSources = sources_bfs_trav_cache(relation, rootTarget);
8175 for (auto source: cachedSources)
8176 func(source);
8177 return;
8178 }
8179
8182 queue.push_back(rootTarget);
8183
8185 visited.insert(EntityLookupKey(rootTarget));
8186
8187 for (uint32_t i = 0; i < queue.size(); ++i) {
8188 const auto currTarget = queue[i];
8189
8190 children.clear();
8191 sources(relation, currTarget, [&](Entity source) {
8192 const auto key = EntityLookupKey(source);
8193 const auto ins = visited.insert(key);
8194 if (!ins.second)
8195 return;
8196
8197 children.push_back(source);
8198 });
8199
8200 core::sort(children, [](Entity left, Entity right) {
8201 return left.id() < right.id();
8202 });
8203
8204 for (auto child: children) {
8205 if (!enabled(child))
8206 continue;
8207 func(child);
8208 queue.push_back(child);
8209 }
8210 }
8211 }
8212
8220 template <typename Func>
8221 GAIA_NODISCARD bool sources_bfs_if(Entity relation, Entity rootTarget, Func func) const {
8222 if (!valid(relation) || !valid(rootTarget))
8223 return false;
8224
8225 if (m_enabledHierarchyVersion == 0) {
8226 const auto& cachedSources = sources_bfs_trav_cache(relation, rootTarget);
8227 for (auto source: cachedSources) {
8228 if (func(source))
8229 return true;
8230 }
8231
8232 return false;
8233 }
8234
8237 queue.push_back(rootTarget);
8238
8240 visited.insert(EntityLookupKey(rootTarget));
8241
8242 for (uint32_t i = 0; i < queue.size(); ++i) {
8243 const auto currTarget = queue[i];
8244
8245 children.clear();
8246 sources(relation, currTarget, [&](Entity source) {
8247 const auto key = EntityLookupKey(source);
8248 const auto ins = visited.insert(key);
8249 if (!ins.second)
8250 return;
8251
8252 children.push_back(source);
8253 });
8254
8255 core::sort(children, [](Entity left, Entity right) {
8256 return left.id() < right.id();
8257 });
8258
8259 for (auto child: children) {
8260 if (!enabled(child))
8261 continue;
8262 if (func(child))
8263 return true;
8264
8265 queue.push_back(child);
8266 }
8267 }
8268
8269 return false;
8270 }
8271
8276 template <typename Func>
8277 void as_targets_trav(Entity relation, Func func) const {
8278 GAIA_ASSERT(valid(relation));
8279 if (!valid(relation))
8280 return;
8281
8282 const auto& targets = as_targets_trav_cache(relation);
8283 for (auto target: targets)
8284 func(target);
8285 }
8286
8292 template <typename Func>
8293 bool as_targets_trav_if(Entity relation, Func func) const {
8294 GAIA_ASSERT(valid(relation));
8295 if (!valid(relation))
8296 return false;
8297
8298 const auto& targets = as_targets_trav_cache(relation);
8299 for (auto target: targets)
8300 if (func(target))
8301 return true;
8302
8303 return false;
8304 }
8305
8306 //----------------------------------------------------------------------
8307
8311 return *m_pCmdBufferST;
8312 }
8313
8317 return *m_pCmdBufferMT;
8318 }
8319
8320 //----------------------------------------------------------------------
8321
8322#if GAIA_SYSTEMS_ENABLED
8323
8325 void systems_init();
8326
8344 void systems_run();
8345
8348 SystemBuilder system();
8349
8351 SystemRegistry& systems() {
8352 return m_systems;
8353 }
8354
8356 const SystemRegistry& systems() const {
8357 return m_systems;
8358 }
8359
8360#endif
8361
8362#if GAIA_OBSERVERS_ENABLED
8363
8366 ObserverBuilder observer();
8367
8369 ObserverRegistry& observers() {
8370 return m_observers;
8371 }
8372
8374 const ObserverRegistry& observers() const {
8375 return m_observers;
8376 }
8377
8381 void defer_on_set_begin(uint32_t slotCount) {
8382 GAIA_ASSERT(m_deferOnSetDepth != (uint32_t)-1);
8383 if (m_deferOnSetDepth++ != 0)
8384 return;
8385
8386 // One queue per work item. Items are distributed to threads in disjoint ranges, so a
8387 // queue is only ever touched by the single thread processing that item and no
8388 // synchronization is needed while the region is open.
8389 m_deferredOnSet.resize(slotCount);
8390 for (auto& queue: m_deferredOnSet)
8391 queue.clear();
8392 }
8393
8397 void defer_on_set_end() {
8398 GAIA_ASSERT(m_deferOnSetDepth > 0);
8399 if (--m_deferOnSetDepth != 0)
8400 return;
8401
8402 // Observer callbacks may trigger writes of their own. Those are dispatched directly
8403 // because the recording region already ended, so move the queues aside before
8404 // walking them.
8405 auto pending = GAIA_MOV(m_deferredOnSet);
8406 m_deferredOnSet = {};
8407 for (const auto& queue: pending) {
8408 for (const auto& item: queue)
8409 world_notify_on_set_entity(*this, item.term, item.entity);
8410 }
8411 }
8412
8414 GAIA_NODISCARD bool defer_on_set_active() const {
8415 return m_deferOnSetDepth != 0;
8416 }
8417
8422 void defer_on_set_record(uint32_t slot, Entity term, Entity entity) {
8423 GAIA_ASSERT(slot < m_deferredOnSet.size());
8424 m_deferredOnSet[slot].push_back({term, entity});
8425 }
8426
8427#endif
8428
8432 void defer_sort_inv_begin(uint32_t slotCount) {
8433 GAIA_ASSERT(m_deferSortInvDepth != (uint32_t)-1);
8434 if (m_deferSortInvDepth++ != 0)
8435 return;
8436
8437 // One queue per work item. Items are distributed to threads in disjoint ranges, so a
8438 // queue is only ever touched by the single thread processing that item and no
8439 // synchronization is needed while the region is open.
8440 m_deferredSortInv.resize(slotCount);
8441 for (auto& queue: m_deferredSortInv)
8442 queue.clear();
8443 }
8444
8449 GAIA_ASSERT(m_deferSortInvDepth > 0);
8450 if (--m_deferSortInvDepth != 0)
8451 return;
8452
8453 // The coordinator may itself write during apply (e.g. through nested queries), so move
8454 // the queue aside before walking it, matching the observer path.
8455 auto pending = GAIA_MOV(m_deferredSortInv);
8456 m_deferredSortInv = {};
8457 for (const auto& queue: pending) {
8458 for (const auto& item: queue)
8459 invalidate_sorted_queries_for_entity(item.entity);
8460 }
8461 }
8462
8465 GAIA_NODISCARD bool defer_sort_inv_active() const {
8466 return m_deferSortInvDepth != 0;
8467 }
8468
8472 void defer_sort_inv_record(uint32_t slot, Entity entity) {
8473 GAIA_ASSERT(slot < m_deferredSortInv.size());
8474 m_deferredSortInv[slot].push_back({entity});
8475 }
8476
8477 //----------------------------------------------------------------------
8478
8483 void enable(Entity entity, bool enable) {
8484 GAIA_ASSERT(valid(entity));
8485
8486 auto& ec = m_recs.entities[entity.id()];
8487 auto& archetype = *ec.pArchetype;
8488 auto* pChunk = ec.pChunk;
8489 const bool wasEnabled = !ec.data.dis;
8490#if GAIA_ASSERT_ENABLED
8491 verify_enable(*this, archetype, entity);
8492#endif
8493 archetype.enable_entity(ec.pChunk, ec.row, enable, m_recs);
8494
8495 if (wasEnabled != enable) {
8496 pChunk->update_world_version();
8497 pChunk->update_entity_order_version();
8498 update_version(m_enabledHierarchyVersion);
8499 update_version(m_worldVersion);
8500 }
8501 }
8502
8506 GAIA_NODISCARD bool enabled(const EntityContainer& ec) const {
8507 const bool entityStateInContainer = !ec.data.dis;
8508#if GAIA_ASSERT_ENABLED
8509 const bool entityStateInChunk = ec.pChunk->enabled(ec.row);
8510 GAIA_ASSERT(entityStateInChunk == entityStateInContainer);
8511#endif
8512 return entityStateInContainer;
8513 }
8514
8519 GAIA_NODISCARD bool enabled(Entity entity) const {
8520 GAIA_ASSERT(valid(entity));
8521
8522 const auto& ec = m_recs.entities[entity.id()];
8523 return enabled(ec);
8524 }
8525
8531 GAIA_NODISCARD bool enabled_hierarchy(Entity entity, Entity relation) const {
8532 GAIA_ASSERT(valid(entity));
8533 GAIA_ASSERT(valid(relation));
8534 if (!valid(entity) || !valid(relation))
8535 return false;
8536 if (!enabled(entity))
8537 return false;
8538
8539 auto curr = entity;
8540 GAIA_FOR(MAX_TRAV_DEPTH) {
8541 const auto next = target(curr, relation);
8542 if (next == EntityBad || next == curr)
8543 break;
8544 if (!enabled(next))
8545 return false;
8546 curr = next;
8547 }
8548
8549 return true;
8550 }
8551
8552 //----------------------------------------------------------------------
8553
8557 GAIA_NODISCARD Chunk* get_chunk(Entity entity) const {
8558 GAIA_ASSERT(entity.id() < m_recs.entities.size());
8559 const auto& ec = m_recs.entities[entity.id()];
8560 return ec.pChunk;
8561 }
8562
8568 GAIA_NODISCARD Chunk* get_chunk(Entity entity, uint32_t& row) const {
8569 GAIA_ASSERT(entity.id() < m_recs.entities.size());
8570 const auto& ec = m_recs.entities[entity.id()];
8571 row = ec.row;
8572 return ec.pChunk;
8573 }
8574
8577 GAIA_NODISCARD uint32_t size() const {
8578 return m_recs.entities.item_count();
8579 }
8580
8587 uint32_t& outArchetypes, uint32_t& outChunks, uint32_t& outEntitiesTotal, uint32_t& outEntitiesActive) const {
8588 outArchetypes = (uint32_t)m_archetypes.size();
8589 outChunks = 0;
8590 outEntitiesTotal = 0;
8591 outEntitiesActive = 0;
8592
8593 for (const auto* pArchetype: m_archetypes) {
8594 if (pArchetype == nullptr)
8595 continue;
8596 const auto& chunks = pArchetype->chunks();
8597 outChunks += (uint32_t)chunks.size();
8598 for (const auto* pChunk: chunks) {
8599 if (pChunk == nullptr)
8600 continue;
8601 outEntitiesTotal += pChunk->size();
8602 outEntitiesActive += pChunk->size_enabled();
8603 }
8604 }
8605 }
8606
8609 GAIA_NODISCARD uint32_t& world_version() {
8610 return m_worldVersion;
8611 }
8612
8617 GAIA_NODISCARD uint32_t rel_version(Entity relation) const {
8618 const auto it = m_relationVersions.find(EntityLookupKey(relation));
8619 return it != m_relationVersions.end() ? it->second : 0;
8620 }
8621
8625 GAIA_NODISCARD uint32_t enabled_hierarchy_version() const {
8626 return m_enabledHierarchyVersion;
8627 }
8628
8631 GAIA_NODISCARD uint32_t archetype_delete_version() const {
8632 return m_archetypeDeleteVersion;
8633 }
8634
8635 friend uint32_t world_rel_version(const World& world, Entity relation);
8636 friend uint32_t world_version(const World& world);
8637 friend uint32_t world_archetype_delete_version(const World& world);
8638 friend uint32_t world_entity_archetype_version(const World& world, Entity entity);
8639
8643 const auto key = EntityLookupKey(entity);
8644 const auto it = m_srcEntityVersions.find(key);
8645 if (it == m_srcEntityVersions.end())
8646 return;
8647
8648 update_version(it->second);
8649 }
8650
8654 m_srcEntityVersions.erase(EntityLookupKey(entity));
8655 }
8656
8662 if (!valid(entity))
8663 return;
8664
8665 auto& ec = fetch(entity);
8666 const auto prevLifespan = ec.pArchetype->max_lifespan();
8667 ec.pArchetype->set_max_lifespan(lifespan);
8668
8669 if (prevLifespan == 0) {
8670 // The archetype used to be immortal but not anymore
8671 try_enqueue_archetype_for_deletion(*ec.pArchetype);
8672 }
8673 }
8674
8675 //----------------------------------------------------------------------
8676
8688#if GAIA_OBSERVERS_ENABLED && GAIA_ASSERT_ENABLED
8689 GAIA_ASSERT(!observer_callback_active());
8690#endif
8691
8692 // Finish deleting entities
8693 del_finalize();
8694
8695 // Run garbage collector
8696 gc();
8697 }
8698
8708 void frame_end() {
8709 util::log_flush();
8710
8711 // Signal the end of the frame
8712 GAIA_PROF_FRAME();
8713 }
8714
8724 void update() {
8725#if GAIA_OBSERVERS_ENABLED && GAIA_ASSERT_ENABLED
8726 GAIA_ASSERT(!observer_callback_active());
8727#endif
8728
8729#if GAIA_SYSTEMS_ENABLED
8730 systems_run();
8731#endif
8732 frame_cleanup();
8733 frame_end();
8734 }
8735
8739 void teardown() {
8740 if GAIA_UNLIKELY (m_teardownActive)
8741 return;
8742 m_teardownActive = true;
8743
8744 GAIA_PROF_SCOPE(World::teardown);
8745
8746#if GAIA_SYSTEMS_ENABLED
8747 systems_done();
8748 m_systems.teardown();
8749#endif
8750
8751#if GAIA_OBSERVERS_ENABLED
8752 m_observers.teardown();
8753#endif
8754
8755 for (;;) {
8756 const auto prevReqArchetypes = m_reqArchetypesToDel.size();
8757 const auto prevReqEntities = m_reqEntitiesToDel.size();
8758 const auto prevChunks = m_chunksToDel.size();
8759 const auto prevArchetypes = m_archetypesToDel.size();
8760
8761 del_finalize();
8762 gc();
8763
8764 if (m_reqArchetypesToDel.empty() && m_reqEntitiesToDel.empty() && m_chunksToDel.empty() &&
8765 m_archetypesToDel.empty())
8766 break;
8767
8768 const bool madeProgress = m_reqArchetypesToDel.size() != prevReqArchetypes ||
8769 m_reqEntitiesToDel.size() != prevReqEntities ||
8770 m_chunksToDel.size() != prevChunks || m_archetypesToDel.size() != prevArchetypes;
8771 if (!madeProgress)
8772 break;
8773 }
8774
8775 util::log_flush();
8776 }
8777
8779 void cleanup() {
8780 cleanup_inter();
8781
8782 // Reinit
8783 m_pRootArchetype = nullptr;
8784 m_pEntityArchetype = nullptr;
8785 m_pCompArchetype = nullptr;
8786 m_nextArchetypeId = 0;
8787 m_defragLastArchetypeIdx = 0;
8788 m_worldVersion = 0;
8789 m_enabledHierarchyVersion = 0;
8790 m_archetypeDeleteVersion = 0;
8791 init();
8792 }
8793
8796 void defrag_entities_per_tick(uint32_t value) {
8797 m_defragEntitiesPerTick = value;
8798 }
8799
8800 //--------------------------------------------------------------------------------
8801
8803 void diag_archetypes() const {
8804 GAIA_LOG_N("Archetypes:%u", (uint32_t)m_archetypes.size());
8805 for (auto* pArchetype: m_archetypes)
8806 Archetype::diag(*this, *pArchetype);
8807 }
8808
8811 void diag_components() const {
8812 comp_cache().diag();
8813 }
8814
8817 void diag_entities() const {
8818 validate_entities();
8819
8820 GAIA_LOG_N("Deleted entities: %u", (uint32_t)m_recs.entities.get_free_items());
8821 if (m_recs.entities.get_free_items() != 0U) {
8822 GAIA_LOG_N(" --> %u", (uint32_t)m_recs.entities.get_next_free_item());
8823
8824 uint32_t iters = 0;
8825 auto fe = m_recs.entities.next_free(m_recs.entities.get_next_free_item());
8826 while (fe != IdentifierIdBad) {
8827 GAIA_LOG_N(" --> %u", m_recs.entities.next_free(fe));
8828 fe = m_recs.entities.next_free(fe);
8829 ++iters;
8830 if (iters > m_recs.entities.get_free_items())
8831 break;
8832 }
8833
8834 if ((iters == 0U) || iters > m_recs.entities.get_free_items())
8835 GAIA_LOG_E(" Entities recycle list contains inconsistent data!");
8836 }
8837 }
8838
8840 void diag() const {
8841 diag_archetypes();
8842 diag_components();
8843 diag_entities();
8844 }
8845
8846 private:
8848 void cleanup_inter() {
8849 GAIA_PROF_SCOPE(World::cleanup_inter);
8850
8851 // Shutdown bypasses the regular GC path, so clear raw-pointer tracking first.
8852 // Chunk/component dtors that run while archetypes are freed can still drop cached queries,
8853 // but after this point they must not touch stale archetype/chunk reverse indices.
8854 {
8855 m_queryCache.clear_archetype_tracking();
8856 m_reqArchetypesToDel = {};
8857 m_reqEntitiesToDel = {};
8858#if GAIA_OBSERVERS_ENABLED
8859 m_entitiesDeleting = {};
8860#endif
8861 m_chunksToDel = {};
8862 m_archetypesToDel = {};
8863 }
8864
8865 // Clear entities
8866 m_recs.entities = {};
8867 m_recs.pair_records_clear();
8868
8869 // Clear archetypes
8870 {
8871 // Delete all allocated chunks and their parent archetypes
8872 for (auto* pArchetype: m_archetypes)
8873 Archetype::destroy(pArchetype);
8874
8875 m_entityToAsRelations = {};
8876 m_entityToAsRelationsTravCache = {};
8877 m_entityToAsTargets = {};
8878 m_entityToAsTargetsTravCache = {};
8879 m_targetsTravCache = {};
8880 m_srcBfsTravCache = {};
8881 m_depthOrderCache = {};
8882 m_sourcesAllCache = {};
8883 m_targetsAllCache = {};
8884 m_relationCachesPopulated = false;
8885 m_hasOnDeleteTargetPolicy = false;
8886 m_hasCantCombinePolicy = false;
8887 m_pairLookup.clear();
8888 m_nonFragmentingRelationsByRel = {};
8889 for (auto& [compKey, store]: m_sparseComponentsByComp) {
8890 (void)compKey;
8891 store.func_clear_store(store.pStore);
8892 store.func_del_store(store.pStore);
8893 }
8894 m_sparseComponentsByComp = {};
8895 m_relationVersions = {};
8896 m_lastRelationVersionRelation = EntityBad;
8897 m_pLastRelationVersion = nullptr;
8898 m_srcEntityVersions = {};
8899
8900 m_archetypes = {};
8901 m_archetypesById = {};
8902 m_archetypesByHash = {};
8903 }
8904
8905 // Clear caches
8906 {
8907 m_entityToArchetypeMap = {};
8908 m_entityToArchetypeMapVersions = {};
8909 m_queryCache.clear();
8910 for (auto* pScratch: m_queryMatchScratchStack)
8911 delete pScratch;
8912 m_queryMatchScratchStack = {};
8913 m_queryMatchScratchDepth = 0;
8914 m_querySerMap = {};
8915 m_nextQuerySerId = 0;
8916 }
8917
8918 // Clear entity aliases
8919 {
8920 for (auto& pair: m_aliasToEntity) {
8921 if (!pair.first.owned())
8922 continue;
8923 // Release any memory allocated for owned names
8924 mem::mem_free((void*)pair.first.str());
8925 }
8926 m_aliasToEntity = {};
8927 }
8928
8929 // Clear entity names
8930 {
8931 for (auto& pair: m_nameToEntity) {
8932 if (!pair.first.owned())
8933 continue;
8934 // Release any memory allocated for owned names
8935 mem::mem_free((void*)pair.first.str());
8936 }
8937 m_nameToEntity = {};
8938 }
8939
8940 // Clear component cache
8941 m_compCache.clear();
8942 }
8943
8948 GAIA_NODISCARD static bool valid(const EntityContainer& ec, [[maybe_unused]] Entity entityExpected) {
8949 if ((ec.flags & EntityContainerFlags::Load) != 0) {
8950 return entityExpected.id() == ec.idx && entityExpected.gen() == ec.data.gen &&
8951 entityExpected.entity() == (bool)ec.data.ent && entityExpected.pair() == (bool)ec.data.pair &&
8952 entityExpected.kind() == (EntityKind)ec.data.kind;
8953 }
8954
8955 if (is_req_del(ec))
8956 return false;
8957
8958 // The entity in the chunk must match the index in the entity container
8959 const auto* pChunk = ec.pChunk;
8960 if (pChunk == nullptr || ec.row >= pChunk->size())
8961 return false;
8962
8963 const auto entityPresent = pChunk->entity_view()[ec.row];
8964 // Public validity checks can legitimately observe a recycled slot with a different generation.
8965 // Treat that as stale instead of aborting.
8966 return entityExpected == entityPresent;
8967 }
8968
8972 GAIA_NODISCARD bool valid_pair(Entity entity) const {
8973 if (entity == EntityBad)
8974 return false;
8975
8976 GAIA_ASSERT(entity.pair());
8977 if (!entity.pair())
8978 return false;
8979
8980 // Ignore wildcards because they can't be attached to entities
8981 if (is_wildcard(entity))
8982 return true;
8983
8984 const auto* pPair = m_recs.pair_record_find(entity);
8985 if (pPair == nullptr)
8986 return false;
8987
8988 const auto& ec = *pPair;
8989 return valid(ec, entity);
8990 }
8991
8995 GAIA_NODISCARD bool valid_entity(Entity entity) const {
8996 if (entity == EntityBad)
8997 return false;
8998
8999 GAIA_ASSERT(!entity.pair());
9000 if (entity.pair())
9001 return false;
9002
9003 // Entity ID has to fit inside the entity array
9004 if (entity.id() >= m_recs.entities.size())
9005 return false;
9006
9007 const auto* pEc = m_recs.entities.try_get(entity.id());
9008 if (pEc == nullptr)
9009 return false;
9010
9011 return valid(*pEc, entity);
9012 }
9013
9018 GAIA_NODISCARD bool valid_entity_id(EntityId entityId) const {
9019 if (entityId == EntityBad.id())
9020 return false;
9021
9022 // Entity ID has to fit inside the entity array
9023 if (entityId >= m_recs.entities.size())
9024 return false;
9025
9026 const auto* pEc = m_recs.entities.try_get(entityId);
9027 if (pEc == nullptr)
9028 return false;
9029
9030 const auto& ec = *pEc;
9031 if (ec.data.pair != 0)
9032 return false;
9033
9034 return valid(
9035 ec, Entity(entityId, ec.data.gen, (bool)ec.data.ent, (bool)ec.data.pair, (EntityKind)ec.data.kind));
9036 }
9037
9041 void lock() {
9042 GAIA_ASSERT(m_structuralChangesLocked != (uint32_t)-1);
9043 ++m_structuralChangesLocked;
9044 }
9045
9049 void unlock() {
9050 GAIA_ASSERT(m_structuralChangesLocked > 0);
9051 --m_structuralChangesLocked;
9052 }
9053
9054#if GAIA_SYSTEMS_ENABLED
9056 void systems_done();
9057#endif
9058
9059 public:
9062 GAIA_NODISCARD bool locked() const {
9063 return m_structuralChangesLocked != 0;
9064 }
9065
9069 GAIA_NODISCARD bool tearing_down() const {
9070 return m_teardownActive;
9071 }
9072
9073 private:
9074 static constexpr uint32_t WorldSerializerVersion = 4;
9075#if GAIA_JSON_ENABLED
9076 static constexpr uint32_t WorldSerializerJSONVersion = 1;
9077#endif
9078
9081 void save_to(ser::serializer s) const {
9082 GAIA_ASSERT(s.valid());
9083
9084 // Version number, currently unused
9085 s.save((uint32_t)WorldSerializerVersion);
9086
9087 // Store the index of the last core component.
9088 // TODO: As this changes, we will have to modify entity ids accordingly.
9089 const auto lastCoreComponentId = GAIA_ID(LastCoreComponent).id();
9090 s.save(lastCoreComponentId);
9091
9092 // Entities
9093 {
9094 auto saveEntityContainer = [&](const EntityContainer& ec) {
9095 s.save(ec.idx);
9096 s.save(ec.dataRaw);
9097 s.save(ec.row);
9098 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) == 0);
9099 s.save(ec.flags); // ignore Load
9100
9101#if GAIA_USE_SAFE_ENTITY
9102 s.save(ec.refCnt);
9103#else
9104 s.save((uint32_t)0);
9105#endif
9106
9107 uint32_t archetypeIdx = ec.pArchetype->list_idx();
9108 s.save(archetypeIdx);
9109 uint32_t chunkIdx = ec.pChunk->idx();
9110 s.save(chunkIdx);
9111 };
9112
9113 const auto recEntities = (uint32_t)m_recs.entities.size();
9114 const auto newEntities = recEntities - lastCoreComponentId;
9115 s.save(newEntities);
9116 GAIA_FOR2(lastCoreComponentId, recEntities) {
9117 const bool isAlive = m_recs.entities.has(i);
9118 s.save(isAlive);
9119 if (isAlive)
9120 saveEntityContainer(m_recs.entities[i]);
9121 else {
9122 s.save(m_recs.entities.handle(i).val);
9123 s.save(m_recs.entities.next_free(i));
9124 }
9125 }
9126
9127 {
9128 uint32_t pairsCnt = 0;
9129 for (auto it = m_recs.pair_record_begin(); it != m_recs.pair_record_end(); ++it) {
9130 const auto& pair = *it;
9131 // Skip core pairs
9132 if (pair.first.entity().id() < lastCoreComponentId && pair.first.entity().gen() < lastCoreComponentId)
9133 continue;
9134
9135 ++pairsCnt;
9136 }
9137 s.save(pairsCnt);
9138 }
9139 {
9140 for (auto it = m_recs.pair_record_begin(); it != m_recs.pair_record_end(); ++it) {
9141 const auto& pair = *it;
9142 // Skip core pairs
9143 if (pair.first.entity().id() < lastCoreComponentId && pair.first.entity().gen() < lastCoreComponentId)
9144 continue;
9145
9146 saveEntityContainer(pair.second);
9147 }
9148 }
9149
9150 s.save(m_recs.entities.m_nextFreeIdx);
9151 s.save(m_recs.entities.m_freeItems);
9152 }
9153
9154 // World
9155 {
9156 s.save((uint32_t)m_archetypes.size());
9157 for (auto* pArchetype: m_archetypes) {
9158 s.save((uint32_t)pArchetype->ids_view().size());
9159 for (auto e: pArchetype->ids_view())
9160 s.save(e);
9161
9162 pArchetype->save(s);
9163 }
9164
9165 s.save(m_worldVersion);
9166 }
9167
9168 // Non-fragmenting exclusive relation edges.
9169 {
9170 uint32_t edgeCnt = 0;
9171 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
9172 (void)relKey;
9173 edgeCnt += store.source_count();
9174 }
9175 s.save(edgeCnt);
9176
9177 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
9178 const auto relation = relKey.entity();
9179 cnt::darray<EntityId> sourceIds;
9180 store.collect_source_ids(sourceIds);
9181 for (auto sourceId: sourceIds) {
9182 const auto source = get(sourceId);
9183 GAIA_ASSERT(valid(source));
9184 const auto target = store.target(source);
9185 GAIA_ASSERT(target != EntityBad);
9186 s.save(source);
9187 s.save(relation);
9188 s.save(target);
9189 }
9190 }
9191 }
9192
9193 // Entity names
9194 {
9195 s.save((uint32_t)m_nameToEntity.size());
9196 for (const auto& pair: m_nameToEntity) {
9197 s.save(pair.second);
9198 const bool isOwnedStr = pair.first.owned();
9199 s.save(isOwnedStr);
9200
9201 // For owner string we copy the entire string into the buffer
9202 if (isOwnedStr) {
9203 const auto* str = pair.first.str();
9204 const uint32_t len = pair.first.len();
9205 s.save(len);
9206 s.save_raw(str, len, ser::serialization_type_id::c8);
9207 }
9208 // Non-owned strings will only store the pointer.
9209 // However, if it is a component, we do not store anything at all because we can reconstruct
9210 // the name from our component cache.
9211 else if (!pair.second.comp()) {
9212 const auto* str = pair.first.str();
9213 const uint32_t len = pair.first.len();
9214 s.save(len);
9215 const auto ptr_val = (uint64_t)str;
9216 s.save_raw(&ptr_val, sizeof(ptr_val), ser::serialization_type_id::u64);
9217 }
9218 }
9219 }
9220
9221 // Entity aliases
9222 {
9223 uint32_t aliasCnt = 0;
9224 GAIA_FOR((uint32_t)m_recs.entities.size()) {
9225 const auto entity = get((EntityId)i);
9226 if (!valid(entity) || entity.pair())
9227 continue;
9228
9229 const auto& ec = m_recs.entities[i];
9230 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
9231 if (compIdx == BadIndex)
9232 continue;
9233
9234 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
9235 if (pDesc->alias != nullptr)
9236 ++aliasCnt;
9237 }
9238
9239 s.save(aliasCnt);
9240 GAIA_FOR((uint32_t)m_recs.entities.size()) {
9241 const auto entity = get((EntityId)i);
9242 if (!valid(entity) || entity.pair())
9243 continue;
9244
9245 const auto& ec = m_recs.entities[i];
9246 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
9247 if (compIdx == BadIndex)
9248 continue;
9249
9250 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
9251 if (pDesc->alias == nullptr)
9252 continue;
9253
9254 s.save(entity);
9255 s.save(pDesc->alias_len);
9256 s.save_raw(pDesc->alias, pDesc->alias_len, ser::serialization_type_id::c8);
9257 }
9258 }
9259 }
9260
9261 public:
9268 void save() {
9269 auto s = m_serializer;
9270 GAIA_ASSERT(s.valid());
9271
9272 s.reset();
9273 save_to(s);
9274 }
9275
9276#if GAIA_JSON_ENABLED
9284 bool save_json(
9285 ser::ser_json& writer, ser::JsonSaveFlags flags = ser::JsonSaveFlags::Default,
9286 const ser::RuntimeJsonPolicy& policy = {}) const;
9287
9292 ser::json_str save_json(bool& ok, ser::JsonSaveFlags flags = ser::JsonSaveFlags::Default) const;
9293
9296 uint64_t runtime_schema_hash() const;
9297
9301 bool save_runtime_schema_json(ser::ser_json& writer) const;
9302
9305 ser::json_str save_runtime_schema_json() const;
9306
9316 bool patch_comp_json(
9317 Entity entity, Entity component, ser::json_str_view pointer, ser::json_str_view value,
9318 ser::JsonDiagnostics& diagnostics, const ser::RuntimeJsonPolicy& policy = {},
9319 uint64_t expectedRuntimeSchemaHash = 0);
9320
9327 bool load_json(
9328 const char* json, uint32_t len, ser::JsonDiagnostics& diagnostics, const ser::RuntimeJsonPolicy& policy = {});
9333 bool load_json(const char* json, uint32_t len);
9334
9340 bool
9341 load_json(ser::json_str_view json, ser::JsonDiagnostics& diagnostics, const ser::RuntimeJsonPolicy& policy = {});
9342
9346 bool load_json(ser::json_str_view json);
9347#endif
9348
9357 bool load(ser::serializer inputSerializer = {}) {
9358 auto s = inputSerializer.valid() ? inputSerializer : m_serializer;
9359 GAIA_ASSERT(s.valid());
9360
9361 // Move back to the beginning of the stream
9362 s.seek(0);
9363
9364 // Version number, currently unused
9365 uint32_t version = 0;
9366 s.load(version);
9367 if (version < 2 || version > WorldSerializerVersion) {
9368 GAIA_LOG_E("Unsupported world version %u. Expected 2..%u.", version, WorldSerializerVersion);
9369 return false;
9370 }
9371
9372 // Store the index of the last core component. As they change, we will have to modify entity ids accordingly.
9373 uint32_t lastCoreComponentId = 0;
9374 s.load(lastCoreComponentId);
9375
9376 // Append-only core ids are handled via load-time entity remapping.
9377 // Snapshots from a runtime with a larger core-id boundary are not supported.
9378 const auto currLastCoreComponentId = GAIA_ID(LastCoreComponent).id();
9379 if (lastCoreComponentId > currLastCoreComponentId) {
9380 GAIA_LOG_E(
9381 "Unsupported world core boundary %u. Current runtime supports up to %u.", lastCoreComponentId,
9382 currLastCoreComponentId);
9383 return false;
9384 }
9385 // Install the append-only core-id remap for nested Entity::load() calls.
9386 // This keeps the serializer API unchanged, at the cost of relying on
9387 // scoped thread-local state instead of explicit serializer-local context.
9388 const detail::EntityLoadRemapGuard entityLoadRemapGuard(
9389 lastCoreComponentId, currLastCoreComponentId, version >= WorldSerializerVersion);
9390 auto remapLoadedEntityId = [&](uint32_t id) {
9391 return detail::remap_loaded_entity_id(id, lastCoreComponentId, currLastCoreComponentId);
9392 };
9393
9394 // Entities
9395 {
9396 auto loadEntityContainer = [&](EntityContainer& ec) {
9397 s.load(ec.idx);
9398 s.load(ec.dataRaw);
9399 s.load(ec.row);
9400 s.load(ec.flags);
9401 if ((ec.flags & EntityContainerFlags::HasCantCombine) != 0)
9402 m_hasCantCombinePolicy = true;
9403 if ((ec.flags & (EntityContainerFlags::OnDeleteTarget_Delete | EntityContainerFlags::OnDeleteTarget_Remove |
9404 EntityContainerFlags::OnDeleteTarget_Error)) != 0) {
9405 m_hasOnDeleteTargetPolicy = true;
9406 }
9407 ec.flags |= EntityContainerFlags::Load;
9408
9409 ec.idx = remapLoadedEntityId(ec.idx);
9410 if (ec.data.pair != 0)
9411 ec.data.gen = remapLoadedEntityId(ec.data.gen);
9412
9413#if GAIA_USE_SAFE_ENTITY
9414 s.load(ec.refCnt);
9415#else
9416 s.load(ec.unused);
9417 // if this value is different from zero, it means we are trying to load data
9418 // that was previously saved with GAIA_USE_SAFE_ENTITY. It's probably not a good idea
9419 // because if your program used reference counting it probably won't work correctly.
9420 GAIA_ASSERT(ec.unused == 0);
9421#endif
9422 // Store the archetype idx inside the pointer. We will decode this once archetypes are created.
9423 uint32_t archetypeIdx = 0;
9424 s.load(archetypeIdx);
9425 ec.pArchetype = (Archetype*)((uintptr_t)archetypeIdx);
9426 // Store the chunk idx inside the pointer. We will decode this once chunks are created.
9427 uint32_t chunkIdx = 0;
9428 s.load(chunkIdx);
9429 ec.pChunk = (Chunk*)((uintptr_t)chunkIdx);
9430 };
9431
9432 uint32_t newEntities = 0;
9433 s.load(newEntities);
9434 GAIA_FOR(newEntities) {
9435 bool isAlive = false;
9436 s.load(isAlive);
9437 if (isAlive) {
9438 EntityContainer ec{};
9439 loadEntityContainer(ec);
9440 m_recs.entities.add_live(GAIA_MOV(ec));
9441 } else {
9442 Identifier id = IdentifierBad;
9443 uint32_t nextFreeIdx = Entity::IdMask;
9444 s.load(id);
9445 s.load(nextFreeIdx);
9446 auto entity = Entity(id);
9447 entity = detail::remap_loaded_entity(entity, lastCoreComponentId, currLastCoreComponentId);
9448 nextFreeIdx = remapLoadedEntityId(nextFreeIdx);
9449 GAIA_ASSERT(entity.id() == remapLoadedEntityId(lastCoreComponentId + i));
9450 m_recs.entities.add_free(entity, nextFreeIdx);
9451 }
9452 }
9453
9454 uint32_t pairsCnt = 0;
9455 s.load(pairsCnt);
9456 GAIA_FOR(pairsCnt) {
9457 EntityContainer ec{};
9458 loadEntityContainer(ec);
9459 const auto pair = EntityContainer::handle(ec);
9460 const bool added = m_recs.pair_record_try_add(pair, GAIA_MOV(ec));
9461 GAIA_ASSERT(added);
9462 }
9463
9464 s.load(m_recs.entities.m_nextFreeIdx);
9465 m_recs.entities.m_nextFreeIdx = remapLoadedEntityId(m_recs.entities.m_nextFreeIdx);
9466 s.load(m_recs.entities.m_freeItems);
9467 }
9468
9469 // World
9470 {
9471 uint32_t archetypesSize = 0;
9472 s.load(archetypesSize);
9473 m_archetypes.reserve(archetypesSize);
9474 GAIA_FOR(archetypesSize) {
9475 uint32_t idsSize = 0;
9476 s.load(idsSize);
9477 Entity ids[ChunkHeader::MAX_COMPONENTS];
9478 GAIA_FOR_(idsSize, j) {
9479 s.load(ids[j]);
9480 }
9481
9482 // Calculate the lookup hash
9483 const auto hashLookup = calc_lookup_hash({&ids[0], idsSize}).hash;
9484
9485 auto* pArchetype = find_archetype({hashLookup}, {&ids[0], idsSize});
9486 if (pArchetype == nullptr) {
9487 // Create the archetype
9488 pArchetype = create_archetype({&ids[0], idsSize});
9489 pArchetype->set_hashes({hashLookup});
9490
9491 // No need to do anything with the archetype graph. It will build itself naturally.
9492 // pArchetype->build_graph_edges(pArchetypeRight, entity);
9493
9494 // Register the archetype in the world
9495 reg_archetype(pArchetype);
9496 }
9497
9498 // Load archetype data
9499 pArchetype->load(s);
9500 }
9501
9502 s.load(m_worldVersion);
9503 }
9504
9505 // Update entity records.
9506 // We previously encoded the archetype id into refCnt.
9507 // Now we need to convert it back to the pointer.
9508 {
9509 for (auto& ec: m_recs.entities) {
9510 if ((ec.flags & EntityContainerFlags::Load) == 0)
9511 continue;
9512 ec.flags &= ~EntityContainerFlags::Load; // Clear the load flag
9513
9514 const auto archetypeIdx = (ArchetypeId)((uintptr_t)ec.pArchetype); // Decode the archetype idx
9515 ec.pArchetype = m_archetypes[archetypeIdx];
9516 const uint32_t chunkIdx = (uint32_t)((uintptr_t)ec.pChunk); // Decode the chunk idx
9517 ec.pChunk = ec.pArchetype->chunks()[chunkIdx];
9518 ec.pEntity = &ec.pChunk->entity_view()[ec.row];
9519 }
9520
9521 for (auto it = m_recs.pair_record_begin(); it != m_recs.pair_record_end(); ++it) {
9522 auto& pair = *it;
9523 auto& ec = pair.second;
9524
9525 // Core pairs remain in-world during load and were not serialized into the stream.
9526 if ((ec.flags & EntityContainerFlags::Load) == 0)
9527 continue;
9528
9529 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) != 0);
9530 ec.flags &= ~EntityContainerFlags::Load; // Clear the load flag
9531
9532 const auto archetypeIdx = (ArchetypeId)((uintptr_t)ec.pArchetype); // Decode the archetype idx
9533 ec.pArchetype = m_archetypes[archetypeIdx];
9534 const uint32_t chunkIdx = (uint32_t)((uintptr_t)ec.pChunk); // Decode the chunk idx
9535 ec.pChunk = ec.pArchetype->chunks()[chunkIdx];
9536 ec.pEntity = &ec.pChunk->entity_view()[ec.row];
9537 }
9538 }
9539
9540 if (version < WorldSerializerVersion) {
9541 for (const auto& [entityId, pItem]: m_compCache.m_compByEntityId) {
9542 (void)entityId;
9543 GAIA_ASSERT(pItem != nullptr);
9544 auto comp = pItem->comp;
9545 comp.data.id = pItem->entity.id();
9546 sync_component_record(pItem->entity, comp);
9547 }
9548 }
9549
9550 if (version >= 4) {
9551 uint32_t edgeCnt = 0;
9552 s.load(edgeCnt);
9553 GAIA_FOR(edgeCnt) {
9554 Entity source;
9555 Entity relation;
9556 Entity target;
9557 s.load(source);
9558 s.load(relation);
9559 s.load(target);
9560
9561 if (!valid(source) || !valid(relation) || !valid(target) ||
9562 !relation_uses_non_fragmenting_storage(relation))
9563 continue;
9564
9565 assign_pair(Pair(relation, target), *m_pEntityArchetype);
9566 nonfragmenting_relation_set(source, relation, target);
9567 }
9568 }
9569
9570#if GAIA_ASSERT_ENABLED
9571 for (const auto& ec: m_recs.entities) {
9572 GAIA_ASSERT(ec.idx < m_recs.entities.size());
9573 GAIA_ASSERT(m_recs.entities.handle(ec.idx) == EntityContainer::handle(ec));
9574 GAIA_ASSERT(ec.pArchetype != nullptr);
9575 GAIA_ASSERT(ec.pChunk != nullptr);
9576 GAIA_ASSERT(ec.pEntity != nullptr);
9577 }
9578#endif
9579 // Entity names
9580 {
9581 m_nameToEntity = {};
9582 uint32_t cnt = 0;
9583 s.load(cnt);
9584 GAIA_FOR(cnt) {
9585 Entity entity;
9586 s.load(entity);
9587 // entity.data.gen = 0; // Reset generation to zero
9588
9589 const auto& ec = fetch(entity);
9590 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
9591 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
9592 GAIA_ASSERT(core::check_alignment(pDesc));
9593
9594 bool isOwned = false;
9595 s.load(isOwned);
9596 if (!isOwned) {
9597 if (entity.comp()) {
9598 // Make components point back to their component cache record because if we save the world and load
9599 // it back in runtime, EntityDesc would still point to the old pointers to component names.
9600 const auto& ci = comp_cache().get(entity);
9601 const auto symbol = ci.symbol_name();
9602 pDesc->name = symbol.data();
9603 // Length should still be the same. Only the pointer has changed.
9604 GAIA_ASSERT(pDesc->name_len == symbol.size());
9605 m_nameToEntity.try_emplace(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0), entity);
9606 } else {
9607 uint32_t len = 0;
9608 s.load(len);
9609 uint64_t ptr_val = 0;
9610 s.load_raw(&ptr_val, sizeof(ptr_val), ser::serialization_type_id::u64);
9611
9612 // Simply point to whereever the original pointer pointed to
9613 pDesc->name = (const char*)ptr_val;
9614 pDesc->name_len = len;
9615 m_nameToEntity.try_emplace(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0), entity);
9616 }
9617
9618 continue;
9619 }
9620
9621 uint32_t len = 0;
9622 s.load(len);
9623
9624 // Get a pointer to where the string begins and seek to the end of the string
9625 const char* entityStr = (const char*)(s.data() + s.tell());
9626 s.seek(s.tell() + len);
9627
9628 // Make sure EntityDesc does not point anywhere right now.
9629 {
9630 pDesc->name = nullptr;
9631 pDesc->name_len = 0;
9632 }
9633
9634 // Name the entity using an owned string
9635 name(entity, entityStr, len);
9636 }
9637 }
9638
9639 // Entity aliases
9640 {
9641 m_aliasToEntity = {};
9642 for (auto& ec: m_recs.entities) {
9643 const auto entity = EntityContainer::handle(ec);
9644 if (entity.pair())
9645 continue;
9646
9647 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
9648 if (compIdx == BadIndex)
9649 continue;
9650
9651 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
9652 GAIA_ASSERT(core::check_alignment(pDesc));
9653 pDesc->alias = nullptr;
9654 pDesc->alias_len = 0;
9655 }
9656
9657 uint32_t cnt = 0;
9658 s.load(cnt);
9659 GAIA_FOR(cnt) {
9660 Entity entity;
9661 s.load(entity);
9662
9663 const auto& ec = fetch(entity);
9664 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
9665 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
9666 GAIA_ASSERT(core::check_alignment(pDesc));
9667
9668 uint32_t len = 0;
9669 s.load(len);
9670
9671 // Get a pointer to where the string begins and seek to the end of the string
9672 const char* aliasStr = (const char*)(s.data() + s.tell());
9673 s.seek(s.tell() + len);
9674
9675 pDesc->alias = nullptr;
9676 pDesc->alias_len = 0;
9677 alias(entity, aliasStr, len);
9678 }
9679 }
9680
9681 return true;
9682 }
9683
9687 template <typename TSerializer>
9688 bool load(TSerializer& inputSerializer) {
9689 return load(ser::make_serializer(inputSerializer));
9690 }
9691
9692 private:
9694 void sort_archetypes() {
9695 struct sort_cond {
9696 bool operator()(const Archetype* a, const Archetype* b) const {
9697 return a->id() < b->id();
9698 }
9699 };
9700
9701 core::sort(m_archetypes, sort_cond{}, [&](uint32_t left, uint32_t right) {
9702 Archetype* tmp = m_archetypes[left];
9703
9704 m_archetypes[right]->list_idx(left);
9705 m_archetypes[left]->list_idx(right);
9706
9707 m_archetypes.data()[left] = (Archetype*)m_archetypes[right];
9708 m_archetypes.data()[right] = tmp;
9709 });
9710 }
9711
9715 void remove_chunk(Archetype& archetype, Chunk& chunk) {
9716 archetype.del(&chunk);
9717 try_enqueue_archetype_for_deletion(archetype);
9718 }
9719
9722 void remove_chunk_from_delete_queue(uint32_t idx) {
9723 GAIA_ASSERT(idx < m_chunksToDel.size());
9724
9725 auto* pChunk = m_chunksToDel[idx].pChunk;
9726 pChunk->clear_delete_queue_index();
9727
9728 const auto lastIdx = (uint32_t)m_chunksToDel.size() - 1;
9729 if (idx != lastIdx) {
9730 auto* pMovedChunk = m_chunksToDel[lastIdx].pChunk;
9731 pMovedChunk->delete_queue_index(idx);
9732 }
9733
9734 core::swap_erase(m_chunksToDel, idx);
9735 }
9736
9741 void remove_entity(Archetype& archetype, Chunk& chunk, uint16_t row) {
9742 archetype.remove_entity(chunk, row, m_recs);
9743 try_enqueue_chunk_for_deletion(archetype, chunk);
9744 }
9745
9747 void del_empty_chunks() {
9748 GAIA_PROF_SCOPE(World::del_empty_chunks);
9749
9750 for (uint32_t i = 0; i < m_chunksToDel.size();) {
9751 auto* pArchetype = m_chunksToDel[i].pArchetype;
9752 auto* pChunk = m_chunksToDel[i].pChunk;
9753
9754 // Revive reclaimed chunks
9755 if (!pChunk->empty()) {
9756 pChunk->revive();
9757 revive_archetype(*pArchetype);
9758 remove_chunk_from_delete_queue(i);
9759 continue;
9760 }
9761
9762 // Skip chunks which still have some lifespan left
9763 if (pChunk->progress_death()) {
9764 ++i;
9765 continue;
9766 }
9767
9768 // Delete unused chunks that are past their lifespan
9769 remove_chunk_from_delete_queue(i);
9770 remove_chunk(*pArchetype, *pChunk);
9771 }
9772 }
9773
9776 void del_empty_archetype(Archetype* pArchetype) {
9777 GAIA_PROF_SCOPE(World::del_empty_archetype);
9778
9779 GAIA_ASSERT(pArchetype != nullptr);
9780 GAIA_ASSERT(pArchetype->empty() || pArchetype->is_req_del());
9781 GAIA_ASSERT(!pArchetype->dying() || pArchetype->is_req_del());
9782
9783 unreg_archetype(pArchetype);
9784 for (auto& ec: m_recs.entities) {
9785 if (ec.pArchetype != pArchetype)
9786 continue;
9787
9788 ec.pArchetype = nullptr;
9789 ec.pChunk = nullptr;
9790 ec.pEntity = nullptr;
9791 }
9792 for (auto it = m_recs.pair_record_begin(); it != m_recs.pair_record_end(); ++it) {
9793 auto& ec = it->second;
9794 if (ec.pArchetype != pArchetype)
9795 continue;
9796
9797 ec.pArchetype = nullptr;
9798 ec.pChunk = nullptr;
9799 ec.pEntity = nullptr;
9800 }
9801 Archetype::destroy(pArchetype);
9802 }
9803
9805 void del_empty_archetypes() {
9806 GAIA_PROF_SCOPE(World::del_empty_archetypes);
9807
9808 cnt::sarray_ext<Archetype*, 512> tmp;
9809
9810 // Remove all dead archetypes from query caches.
9811 // Because the number of cached queries is way higher than the number of archetypes
9812 // we want to remove, we flip the logic around and iterate over all query caches
9813 // and match against our lists.
9814 // Note, all archetype pointers in the tmp array are invalid at this point and can
9815 // be used only for comparison. They can't be dereferenced.
9816 auto remove_from_queries = [&]() {
9817 if (tmp.empty())
9818 return;
9819
9820 for (auto* pArchetype: tmp) {
9821 m_queryCache.remove_archetype_from_queries(pArchetype);
9822 del_empty_archetype(pArchetype);
9823 }
9824 tmp.clear();
9825 };
9826
9827 for (uint32_t i = 0; i < m_archetypesToDel.size();) {
9828 auto* pArchetype = m_archetypesToDel[i];
9829
9830 // Skip reclaimed archetypes or archetypes that became immortal
9831 if (!pArchetype->empty() || pArchetype->max_lifespan() == 0) {
9832 revive_archetype(*pArchetype);
9833 core::swap_erase(m_archetypesToDel, i);
9834 continue;
9835 }
9836
9837 // Skip archetypes which still have some lifespan left unless
9838 // they are force-deleted.
9839 if (!pArchetype->is_req_del() && pArchetype->progress_death()) {
9840 ++i;
9841 continue;
9842 }
9843
9844 tmp.push_back(pArchetype);
9845
9846 // Remove the unused archetypes
9847 core::swap_erase(m_archetypesToDel, i);
9848
9849 // Clear what we have once the capacity is reached
9850 if (tmp.size() == tmp.max_size())
9851 remove_from_queries();
9852 }
9853
9854 remove_from_queries();
9855 }
9856
9859 void revive_archetype(Archetype& archetype) {
9860 const bool wasReqDel = archetype.is_req_del();
9861 archetype.revive();
9862 if (wasReqDel)
9863 update_version(m_archetypeDeleteVersion);
9864 m_reqArchetypesToDel.erase(ArchetypeLookupKey(archetype.lookup_hash(), &archetype));
9865 }
9866
9870 void try_enqueue_chunk_for_deletion(Archetype& archetype, Chunk& chunk) {
9871 if (chunk.dying() || !chunk.empty())
9872 return;
9873
9874 // When the chunk is emptied we want it to be removed. We can't do it
9875 // rowB away and need to wait for world::gc() to be called.
9876 //
9877 // However, we need to prevent the following:
9878 // 1) chunk is emptied, add it to some removal list
9879 // 2) chunk is reclaimed
9880 // 3) chunk is emptied, add it to some removal list again
9881 //
9882 // Therefore, we have a flag telling us the chunk is already waiting to
9883 // be removed. The chunk might be reclaimed before garbage collection happens
9884 // but it simply ignores such requests. This way we always have at most one
9885 // record for removal for any given chunk.
9886 chunk.start_dying();
9887
9888 m_chunksToDel.push_back({&archetype, &chunk});
9889 chunk.delete_queue_index((uint32_t)m_chunksToDel.size() - 1);
9890 }
9891
9894 void try_enqueue_archetype_for_deletion(Archetype& archetype) {
9895 if (!archetype.ready_to_die())
9896 return;
9897
9898 // When the chunk is emptied we want it to be removed. We can't do it
9899 // rowB away and need to wait for world::gc() to be called.
9900 //
9901 // However, we need to prevent the following:
9902 // 1) archetype is emptied, add it to some removal list
9903 // 2) archetype is reclaimed
9904 // 3) archetype is emptied, add it to some removal list again
9905 //
9906 // Therefore, we have a flag telling us the chunk is already waiting to
9907 // be removed. The archetype might be reclaimed before garbage collection happens
9908 // but it simply ignores such requests. This way we always have at most one
9909 // record for removal for any given chunk.
9910 archetype.start_dying();
9911
9912 m_archetypesToDel.push_back(&archetype);
9913 }
9914
9917 void defrag_chunks(uint32_t maxEntities) {
9918 GAIA_PROF_SCOPE(World::defrag_chunks);
9919
9920 const auto maxIters = m_archetypes.size();
9921 // There has to be at least the root archetype present
9922 GAIA_ASSERT(maxIters > 0);
9923
9924 GAIA_FOR(maxIters) {
9925 const auto idx = (m_defragLastArchetypeIdx + 1) % maxIters;
9926 auto* pArchetype = m_archetypes[idx];
9927 defrag_archetype(*pArchetype, maxEntities);
9928 if (maxEntities == 0)
9929 return;
9930
9931 m_defragLastArchetypeIdx = idx;
9932 }
9933 }
9934
9938 void defrag_archetype(Archetype& archetype, uint32_t& maxEntities) {
9939 // Assuming the following chunk layout:
9940 // Chunk_1: 10/10
9941 // Chunk_2: 1/10
9942 // Chunk_3: 7/10
9943 // Chunk_4: 10/10
9944 // Chunk_5: 9/10
9945 // After full defragmentation we end up with:
9946 // Chunk_1: 10/10
9947 // Chunk_2: 10/10 (7 entities from Chunk_3 + 2 entities from Chunk_5)
9948 // Chunk_3: 0/10 (empty, ready for removal)
9949 // Chunk_4: 10/10
9950 // Chunk_5: 7/10
9951 // TODO: Implement mask of semi-full chunks so we can pick one easily when searching
9952 // for a chunk to fill with a new entity and when defragmenting.
9953 // NOTE 1:
9954 // Even though entity movement might be present during defragmentation, we do
9955 // not update the world version here because no real structural changes happen.
9956 // All entities and components remain intact, they just move to a different place.
9957 // NOTE 2:
9958 // Entities belonging to chunks with uni components are locked to their chunk.
9959 // Therefore, we won't defragment them unless their uni components contain matching
9960 // values.
9961
9962 if (maxEntities == 0)
9963 return;
9964
9965 const auto& chunks = archetype.chunks();
9966 if (chunks.size() < 2)
9967 return;
9968
9969 uint32_t front = 0;
9970 uint32_t back = chunks.size() - 1;
9971
9972 auto* pDstChunk = chunks[front];
9973 auto* pSrcChunk = chunks[back];
9974
9975 // Find the first semi-full chunk in the front
9976 while (front < back && (pDstChunk->full() || !pDstChunk->is_semi()))
9977 pDstChunk = chunks[++front];
9978 // Find the last semi-full chunk in the back
9979 while (front < back && (pSrcChunk->empty() || !pSrcChunk->is_semi()))
9980 pSrcChunk = chunks[--back];
9981
9982 const auto& props = archetype.props();
9983 const bool hasUniEnts =
9984 props.cntEntities > 0 && archetype.ids_view()[props.cntEntities - 1].kind() == EntityKind::EK_Uni;
9985
9986 // Find the first semi-empty chunk in the back
9987 while (front < back) {
9988 pDstChunk = chunks[front];
9989 pSrcChunk = chunks[back];
9990
9991 const uint32_t entitiesInSrcChunk = pSrcChunk->size();
9992 const uint32_t spaceInDstChunk = pDstChunk->capacity() - pDstChunk->size();
9993 const uint32_t entitiesToMoveSrc = core::get_min(entitiesInSrcChunk, maxEntities);
9994 const uint32_t entitiesToMove = core::get_min(entitiesToMoveSrc, spaceInDstChunk);
9995
9996 // Make sure uni components have matching values
9997 if (hasUniEnts) {
9998 auto rec = pSrcChunk->comp_rec_view();
9999 bool res = true;
10000 GAIA_FOR2(props.genEntities, props.cntEntities) {
10001 const auto* pSrcVal = (const void*)pSrcChunk->comp_ptr(i, 0);
10002 const auto* pDstVal = (const void*)pDstChunk->comp_ptr(i, 0);
10003 if (rec[i].pItem->cmp(pSrcVal, pDstVal)) {
10004 res = false;
10005 break;
10006 }
10007 }
10008
10009 // When there is not a match we move to the next chunk
10010 if (!res) {
10011 pDstChunk = chunks[++front];
10012 goto next_iteration;
10013 }
10014 }
10015
10016 GAIA_FOR(entitiesToMove) {
10017 const auto lastSrcEntityIdx = entitiesInSrcChunk - i - 1;
10018 const auto entity = pSrcChunk->entity_view()[lastSrcEntityIdx];
10019
10020 auto& ec = m_recs[entity];
10021
10022 const auto srcRow = ec.row;
10023 const auto dstRow = pDstChunk->add_entity(entity);
10024 const bool wasEnabled = !ec.data.dis;
10025
10026 // Make sure the old entity becomes enabled now
10027 archetype.enable_entity(pSrcChunk, srcRow, true, m_recs);
10028 // We go back-to-front in the chunk so enabling the entity is not expected to change its row
10029 GAIA_ASSERT(srcRow == ec.row);
10030
10031 // Move data from the old chunk to the new one
10032 pDstChunk->move_entity_data(entity, dstRow, m_recs);
10033
10034 // Remove the entity record from the old chunk.
10035 // Normally we'd call remove_entity but we don't want to trigger world
10036 // version updated all the time. It's enough to do it just once at the
10037 // end of defragmentation.
10038 // remove_entity(archetype, *pSrcChunk, srcRow);
10039 archetype.remove_entity_raw(*pSrcChunk, srcRow, m_recs);
10040 try_enqueue_chunk_for_deletion(archetype, *pSrcChunk);
10041
10042 // Bring the entity container record up-to-date
10043 ec.pChunk = pDstChunk;
10044 ec.row = (uint16_t)dstRow;
10045 ec.pEntity = &pDstChunk->entity_view()[dstRow];
10046
10047 // Transfer the original enabled state to the new chunk
10048 archetype.enable_entity(pDstChunk, dstRow, wasEnabled, m_recs);
10049 }
10050
10051 // Update world versions
10052 if (entitiesToMove > 0) {
10053 pSrcChunk->update_world_version();
10054 pDstChunk->update_world_version();
10055 pSrcChunk->update_entity_order_version();
10056 pDstChunk->update_entity_order_version();
10057 update_version(m_worldVersion);
10058 }
10059
10060 maxEntities -= entitiesToMove;
10061 if (maxEntities == 0)
10062 return;
10063
10064 // The source is empty, find another semi-empty source
10065 if (pSrcChunk->empty()) {
10066 while (front < back) {
10067 if (chunks[--back]->is_semi())
10068 break;
10069 }
10070 }
10071
10072 next_iteration:
10073 // The destination chunk is full, we need to move to the next one.
10074 // The idea is to fill the destination as much as possible.
10075 while (front < back && pDstChunk->full())
10076 pDstChunk = chunks[++front];
10077 }
10078 }
10079
10084 GAIA_NODISCARD Archetype* find_archetype(Archetype::LookupHash hashLookup, EntitySpan ids) {
10085 auto tmpArchetype = ArchetypeLookupChecker(ids);
10086 ArchetypeLookupKey key(hashLookup, &tmpArchetype);
10087
10088 // Search for the archetype in the map
10089 const auto it = m_archetypesByHash.find(key);
10090 if (it == m_archetypesByHash.end())
10091 return nullptr;
10092
10093 auto* pArchetype = it->second;
10094 return pArchetype;
10095 }
10096
10097 GAIA_NODISCARD static auto
10102 find_component_index_record(ComponentIndexEntryArray& records, const Archetype* pArchetype) {
10103 return core::get_index_if(records, [&](const auto& record) {
10104 return record.matches(pArchetype);
10105 });
10106 }
10107
10108 GAIA_NODISCARD static auto
10113 find_component_index_record(const ComponentIndexEntryArray& records, const Archetype* pArchetype) {
10114 return core::get_index_if(records, [&](const auto& record) {
10115 return record.matches(pArchetype);
10116 });
10117 }
10118
10122 void update_entity_archetype_lookup_revision(EntityLookupKey entityKey) {
10123 auto [it, _] = m_entityToArchetypeMapVersions.try_emplace(entityKey, 0);
10124 (void)_;
10125 ++it->second;
10126 if (it->second == 0)
10127 it->second = 1;
10128 }
10129
10136 void add_entity_archetype_pair(
10137 Entity entity, Archetype* pArchetype, uint16_t compIdx = ComponentIndexBad, uint16_t matchCount = 1) {
10138 GAIA_ASSERT(pArchetype != nullptr);
10139 GAIA_ASSERT(matchCount > 0);
10140
10141 EntityLookupKey entityKey(entity);
10142 auto prepared = m_entityToArchetypeMap.prepare_insert(entityKey);
10143 if (!prepared.found()) {
10144 ComponentIndexEntryArray records;
10145 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
10146 m_entityToArchetypeMap.emplace_prepared(prepared, entityKey, GAIA_MOV(records));
10147 return;
10148 }
10149
10150 auto it = m_entityToArchetypeMap.prepared_iterator(prepared);
10151 auto& records = it->second;
10152 const auto idx = find_component_index_record(records, pArchetype);
10153 if (idx == BadIndex) {
10154 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
10155 return;
10156 }
10157
10158 auto& record = records[idx];
10159 record.matchCount = (uint16_t)(record.matchCount + matchCount);
10160 if (compIdx != ComponentIndexBad)
10161 record.compIdx = compIdx;
10162 }
10163
10171 void add_new_entity_archetype_pair(
10172 Entity entity, Archetype* pArchetype, uint16_t compIdx = ComponentIndexBad, uint16_t matchCount = 1) {
10173 GAIA_ASSERT(pArchetype != nullptr);
10174 GAIA_ASSERT(matchCount > 0);
10175
10176 EntityLookupKey entityKey(entity);
10177 auto prepared = m_entityToArchetypeMap.prepare_insert(entityKey);
10178 if (!prepared.found()) {
10179 ComponentIndexEntryArray records;
10180 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
10181 m_entityToArchetypeMap.emplace_prepared(prepared, entityKey, GAIA_MOV(records));
10182 return;
10183 }
10184
10185 auto it = m_entityToArchetypeMap.prepared_iterator(prepared);
10186 auto& records = it->second;
10187 if (!records.empty()) {
10188 auto& record = records.back();
10189 if (record.matches(pArchetype)) {
10190 record.matchCount = (uint16_t)(record.matchCount + matchCount);
10191 if (compIdx != ComponentIndexBad)
10192 record.compIdx = compIdx;
10193 return;
10194 }
10195 }
10196
10197 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
10198 }
10199
10204 void add_pair_archetype_query_pairs(Entity pair, Archetype* pArchetype, uint16_t matchCount = 1) {
10205 GAIA_ASSERT(pair.pair());
10206 GAIA_ASSERT(pArchetype != nullptr);
10207 GAIA_ASSERT(matchCount > 0);
10208
10209 const auto first = get(pair.id());
10210 const auto second = get(pair.gen());
10211
10212 add_entity_archetype_pair(Pair(All, second), pArchetype, ComponentIndexBad, matchCount);
10213 add_entity_archetype_pair(Pair(first, All), pArchetype, ComponentIndexBad, matchCount);
10214 add_entity_archetype_pair(Pair(All, All), pArchetype, ComponentIndexBad, matchCount);
10215 }
10216
10221 void add_new_pair_archetype_query_pairs(Entity pair, Archetype* pArchetype, uint16_t matchCount = 1) {
10222 GAIA_ASSERT(pair.pair());
10223 GAIA_ASSERT(pArchetype != nullptr);
10224 GAIA_ASSERT(matchCount > 0);
10225
10226 const auto first = get(pair.id());
10227 const auto second = get(pair.gen());
10228
10229 add_new_entity_archetype_pair(Pair(All, second), pArchetype, ComponentIndexBad, matchCount);
10230 add_new_entity_archetype_pair(Pair(first, All), pArchetype, ComponentIndexBad, matchCount);
10231 add_new_entity_archetype_pair(Pair(All, All), pArchetype, ComponentIndexBad, matchCount);
10232 }
10233
10237 void del_entity_query_pair(Pair pair, Entity entityToRemove) {
10238 const auto entityKey = EntityLookupKey(pair);
10239 auto it = m_entityToArchetypeMap.find(entityKey);
10240 if (it == m_entityToArchetypeMap.end())
10241 return;
10242 auto& records = it->second;
10243 bool changed = false;
10244
10245 // Remove any reference to the found archetype from the array.
10246 // We don't know the archetype so we remove/decrement any archetype record that contains our entity.
10247 for (uint32_t i = records.size() - 1; i != (uint32_t)-1; --i) {
10248 auto& record = records[i];
10249 const auto* pArchetype = record.pArchetype;
10250 if (!pArchetype->has(entityToRemove))
10251 continue;
10252
10253 if ((!is_wildcard(pair.first()) && !is_wildcard(pair.second())) || record.matchCount <= 1)
10254 core::swap_erase_unsafe(records, i);
10255 else
10256 --record.matchCount;
10257 changed = true;
10258 }
10259
10260 if (changed)
10261 update_entity_archetype_lookup_revision(entityKey);
10262
10263 if (records.empty())
10264 m_entityToArchetypeMap.erase(it);
10265 }
10266
10271 void del_entity_query_pair(Pair pair, Archetype* pArchetypeToRemove) {
10272 GAIA_ASSERT(pArchetypeToRemove != nullptr);
10273
10274 const auto entityKey = EntityLookupKey(pair);
10275 auto it = m_entityToArchetypeMap.find(entityKey);
10276 if (it == m_entityToArchetypeMap.end())
10277 return;
10278
10279 auto& records = it->second;
10280 const auto idx = find_component_index_record(records, pArchetypeToRemove);
10281 if (idx != BadIndex) {
10282 core::swap_erase_unsafe(records, idx);
10283 update_entity_archetype_lookup_revision(entityKey);
10284 }
10285
10286 if (records.empty())
10287 m_entityToArchetypeMap.erase(it);
10288 }
10289
10293 void del_pair_archetype_query_pairs(Entity pair, Archetype* pArchetypeToRemove) {
10294 GAIA_ASSERT(pair.pair());
10295 GAIA_ASSERT(pArchetypeToRemove != nullptr);
10296
10297 GAIA_ASSERT(pair.id() < m_recs.entities.size());
10298 GAIA_ASSERT(pair.gen() < m_recs.entities.size());
10299 const auto first = m_recs.entities.handle(pair.id());
10300 const auto second = m_recs.entities.handle(pair.gen());
10301
10302 del_entity_query_pair(Pair(All, second), pArchetypeToRemove);
10303 del_entity_query_pair(Pair(first, All), pArchetypeToRemove);
10304 del_entity_query_pair(Pair(All, All), pArchetypeToRemove);
10305 }
10306
10310 void del_pair_archetype_query_pairs(Entity pair, Entity entityToRemove) {
10311 GAIA_ASSERT(pair.pair());
10312
10313 GAIA_ASSERT(pair.id() < m_recs.entities.size());
10314 GAIA_ASSERT(pair.gen() < m_recs.entities.size());
10315 const auto first = m_recs.entities.handle(pair.id());
10316 const auto second = m_recs.entities.handle(pair.gen());
10317
10318 del_entity_query_pair(Pair(All, second), entityToRemove);
10319 del_entity_query_pair(Pair(first, All), entityToRemove);
10320 del_entity_query_pair(Pair(All, All), entityToRemove);
10321 }
10322
10327 void del_entity_archetype_pair(Entity entity, Archetype* pArchetypeToRemove) {
10328 GAIA_ASSERT(entity != Pair(All, All));
10329 GAIA_ASSERT(pArchetypeToRemove != nullptr);
10330
10331 const auto entityKey = EntityLookupKey(entity);
10332 auto it = m_entityToArchetypeMap.find(entityKey);
10333 if (it == m_entityToArchetypeMap.end())
10334 return;
10335
10336 auto& records = it->second;
10337 const auto idx = find_component_index_record(records, pArchetypeToRemove);
10338 if (idx != BadIndex) {
10339 core::swap_erase_unsafe(records, idx);
10340 update_entity_archetype_lookup_revision(entityKey);
10341 }
10342
10343 if (records.empty())
10344 m_entityToArchetypeMap.erase(it);
10345 }
10346
10349 void del_archetype_entity_pairs(Archetype* pArchetype) {
10350 GAIA_ASSERT(pArchetype != nullptr);
10351
10352 for (const auto entity: pArchetype->ids_view()) {
10353 del_entity_archetype_pair(entity, pArchetype);
10354
10355 if (!entity.pair())
10356 continue;
10357
10358 // Archetype unregistration can run while the pair's relation or target entity is already
10359 // invalid. Rebuild wildcard pair lookup keys from the stored entity records instead of
10360 // calling get(), which asserts on invalidated ids.
10361 GAIA_ASSERT(entity.id() < m_recs.entities.size());
10362 GAIA_ASSERT(entity.gen() < m_recs.entities.size());
10363 del_pair_archetype_query_pairs(entity, pArchetype);
10364 }
10365 }
10366
10370 void del_entity_archetype_pairs(Entity entity, Archetype* pArchetype) {
10371 GAIA_ASSERT(entity != Pair(All, All));
10372
10373 const auto entityKey = EntityLookupKey(entity);
10374 if (m_entityToArchetypeMap.erase(entityKey) != 0)
10375 update_entity_archetype_lookup_revision(entityKey);
10376
10377 if (entity.pair()) {
10378 if (pArchetype != nullptr) {
10379 del_pair_archetype_query_pairs(entity, pArchetype);
10380 } else {
10381 del_pair_archetype_query_pairs(entity, entity);
10382 }
10383 }
10384 }
10385
10389 GAIA_NODISCARD Archetype* create_archetype(EntitySpan entities) {
10390 GAIA_ASSERT(m_nextArchetypeId < (decltype(m_nextArchetypeId))-1);
10391 auto* pArchetype = Archetype::create(*this, m_nextArchetypeId++, m_worldVersion, entities);
10392
10393 const auto entityCnt = (uint32_t)entities.size();
10394 GAIA_FOR(entityCnt) {
10395 auto entity = entities[i];
10396 add_new_entity_archetype_pair(entity, pArchetype, (uint16_t)i);
10397
10398#if GAIA_OBSERVERS_ENABLED
10399 auto& ec = fetch(entity);
10400 if ((ec.flags & EntityContainerFlags::IsObserved) != 0 || m_observers.has_observers(entity)) {
10401 ec.flags |= EntityContainerFlags::IsObserved;
10402 pArchetype->observed_terms_inc();
10403 }
10404#endif
10405
10406 // If the entity is a pair, make sure to create special wildcard records for it
10407 // as well so wildcard queries can find the archetype.
10408 if (entity.pair()) {
10409 add_new_pair_archetype_query_pairs(entity, pArchetype);
10410 }
10411 }
10412
10413 return pArchetype;
10414 }
10415
10418 void reg_archetype(Archetype* pArchetype) {
10419 GAIA_ASSERT(pArchetype != nullptr);
10420
10421 // // Make sure hashes were set already
10422 // GAIA_ASSERT(
10423 // (m_archetypesById.empty() || pArchetype == m_pRootArchetype) || (pArchetype->lookup_hash().hash != 0));
10424
10425 // Make sure the archetype is not registered yet
10426 GAIA_ASSERT(pArchetype->list_idx() == BadIndex);
10427
10428 // Register the archetype
10429 [[maybe_unused]] const auto it0 =
10430 m_archetypesById.emplace(ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash()), pArchetype);
10431 [[maybe_unused]] const auto it1 =
10432 m_archetypesByHash.emplace(ArchetypeLookupKey(pArchetype->lookup_hash(), pArchetype), pArchetype);
10433
10434 GAIA_ASSERT(it0.second);
10435 GAIA_ASSERT(it1.second);
10436
10437 pArchetype->list_idx(m_archetypes.size());
10438 m_archetypes.emplace_back(pArchetype);
10439
10440 m_queryCache.register_archetype_with_queries(pArchetype);
10441 }
10442
10445 void unreg_archetype(Archetype* pArchetype) {
10446 GAIA_ASSERT(pArchetype != nullptr);
10447
10448 // Make sure hashes were set already
10449 GAIA_ASSERT(
10450 (m_archetypesById.empty() || pArchetype == m_pRootArchetype) || (pArchetype->lookup_hash().hash != 0));
10451
10452 // Make sure the archetype was registered already
10453 GAIA_ASSERT(pArchetype->list_idx() != BadIndex);
10454
10455 // Query rematching uses the entity -> archetype lookup map as an input. Remove this
10456 // archetype from all of its lookup buckets before destroying it so dead archetype
10457 // pointers cannot be reintroduced into cached query state during the next rematch.
10458 del_archetype_entity_pairs(pArchetype);
10459
10460 // Break graph connections
10461 {
10462 auto& edgeLefts = pArchetype->left_edges();
10463 for (auto& itLeft: edgeLefts)
10464 remove_edge_from_archetype(pArchetype, itLeft.second, itLeft.first.entity());
10465 }
10466
10467 auto tmpArchetype = ArchetypeLookupChecker(pArchetype->ids_view());
10468 [[maybe_unused]] const auto res0 =
10469 m_archetypesById.erase(ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash()));
10470 [[maybe_unused]] const auto res1 =
10471 m_archetypesByHash.erase(ArchetypeLookupKey(pArchetype->lookup_hash(), &tmpArchetype));
10472 GAIA_ASSERT(res0 != 0);
10473 GAIA_ASSERT(res1 != 0);
10474
10475 const auto idx = pArchetype->list_idx();
10476 GAIA_ASSERT(idx == core::get_index(m_archetypes, pArchetype));
10477 core::swap_erase(m_archetypes, idx);
10478 update_entity_archetype_lookup_revision(EntityBadLookupKey);
10479 if (!m_archetypes.empty() && idx != m_archetypes.size())
10480 m_archetypes[idx]->list_idx(idx);
10481 }
10482
10483#if GAIA_ASSERT_ENABLED
10489 static void print_archetype_entities(const World& world, const Archetype& archetype, Entity entity, bool adding) {
10490 auto ids = archetype.ids_view();
10491
10492 GAIA_LOG_W("Currently present:");
10493 GAIA_EACH(ids) {
10494 const auto name = entity_name(world, ids[i]);
10495 GAIA_LOG_W(
10496 "> [%u] %.*s [%s]", i, (int)name.size(), name.empty() ? "" : name.data(),
10497 EntityKindString[(uint32_t)ids[i].kind()]);
10498 }
10499
10500 GAIA_LOG_W("Trying to %s:", adding ? "add" : "del");
10501 const auto name = entity_name(world, entity);
10502 GAIA_LOG_W(
10503 "> %.*s [%s]", (int)name.size(), name.empty() ? "" : name.data(),
10504 EntityKindString[(uint32_t)entity.kind()]);
10505 }
10506
10512 static void verify_add(const World& world, Archetype& archetype, Entity entity, Entity addEntity) {
10513 // Make sure the world is not locked
10514 if (world.locked()) {
10515 GAIA_ASSERT2(false, "Trying to add an entity while the world is locked");
10516 GAIA_LOG_W("Trying to add an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
10517 print_archetype_entities(world, archetype, entity, false);
10518 return;
10519 }
10520
10521 // Makes sure no wildcard entities are added
10522 if (is_wildcard(addEntity)) {
10523 GAIA_ASSERT2(false, "Adding wildcard pairs is not supported");
10524 print_archetype_entities(world, archetype, addEntity, true);
10525 return;
10526 }
10527
10528 // Make sure not to add too many entities/components
10529 auto ids = archetype.ids_view();
10530 if GAIA_UNLIKELY (ids.size() + 1 >= ChunkHeader::MAX_COMPONENTS) {
10531 GAIA_ASSERT2(false, "Trying to add too many entities to entity!");
10532 GAIA_LOG_W("Trying to add an entity to entity [%u:%u] but there's no space left!", entity.id(), entity.gen());
10533 print_archetype_entities(world, archetype, addEntity, true);
10534 return;
10535 }
10536 }
10537
10543 static void verify_del(const World& world, Archetype& archetype, Entity entity, Entity func_del) {
10544 // Make sure the world is not locked
10545 if (world.locked()) {
10546 GAIA_ASSERT2(false, "Trying to delete an entity while the world is locked");
10547 GAIA_LOG_W("Trying to delete an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
10548 print_archetype_entities(world, archetype, entity, false);
10549 return;
10550 }
10551
10552 // Make sure the entity is present on the archetype
10553 if GAIA_UNLIKELY (!archetype.has(func_del)) {
10554 GAIA_ASSERT2(false, "Trying to remove an entity which wasn't added");
10555 GAIA_LOG_W("Trying to del an entity from entity [%u:%u] but it was never added", entity.id(), entity.gen());
10556 print_archetype_entities(world, archetype, func_del, false);
10557 return;
10558 }
10559 }
10560
10565 static void verify_enable(const World& world, Archetype& archetype, Entity entity) {
10566 if (world.locked()) {
10567 GAIA_ASSERT2(false, "Trying to enable/disable an entity while the world is locked");
10568 GAIA_LOG_W("Trying to enable/disable an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
10569 print_archetype_entities(world, archetype, entity, false);
10570 }
10571 }
10572
10577 static void verify_move(const World& world, Archetype& archetype, Entity entity) {
10578 if (world.locked()) {
10579 GAIA_ASSERT2(false, "Trying to move an entity while the world is locked");
10580 GAIA_LOG_W("Trying to move an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
10581 print_archetype_entities(world, archetype, entity, false);
10582 }
10583 }
10584#endif
10585
10591 GAIA_NODISCARD Archetype* foc_archetype_add(Archetype* pArchetypeLeft, Entity entity) {
10592 // Check if the component is found when following the "add" edges
10593 bool edgeNeedsRebuild = false;
10594 {
10595 const auto edge = pArchetypeLeft->find_edge_right(entity);
10596 if (edge != ArchetypeIdHashPairBad) {
10597 auto it = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
10598 if (it != m_archetypesById.end() && it->second != nullptr)
10599 return it->second;
10600
10601 // Drop stale local cache edge and rebuild it below.
10602 pArchetypeLeft->del_graph_edge_right_local(entity);
10603 edgeNeedsRebuild = true;
10604 }
10605 }
10606
10607 // Prepare a joint array of components of old + the newly added component
10608 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
10609 {
10610 auto entsOld = pArchetypeLeft->ids_view();
10611 const auto entsOldCnt = entsOld.size();
10612 entsNew.resize((uint32_t)entsOld.size() + 1);
10613 GAIA_FOR(entsOldCnt) entsNew[i] = entsOld[i];
10614 entsNew[(uint32_t)entsOld.size()] = entity;
10615 }
10616
10617 // Make sure to sort the components so we receive the same hash no matter the order in which components
10618 // are provided Bubble sort is okay. We're dealing with at most ChunkHeader::MAX_COMPONENTS items.
10619 sort(entsNew, SortComponentCond{});
10620
10621 // Once sorted we can calculate the hashes
10622 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
10623 auto* pArchetypeRight = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
10624 if (pArchetypeRight == nullptr) {
10625 pArchetypeRight = create_archetype({entsNew.data(), entsNew.size()});
10626 pArchetypeRight->set_hashes({hashLookup});
10627 reg_archetype(pArchetypeRight);
10628 edgeNeedsRebuild = true;
10629 }
10630
10631 if (edgeNeedsRebuild)
10632 pArchetypeLeft->build_graph_edges(pArchetypeRight, entity);
10633
10634 return pArchetypeRight;
10635 }
10636
10642 GAIA_NODISCARD Archetype* foc_archetype_add_no_graph(Archetype* pArchetypeLeft, Entity entity) {
10643 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
10644 {
10645 auto entsOld = pArchetypeLeft->ids_view();
10646 const auto entsOldCnt = entsOld.size();
10647 entsNew.resize((uint32_t)entsOld.size() + 1);
10648 GAIA_FOR(entsOldCnt) entsNew[i] = entsOld[i];
10649 entsNew[(uint32_t)entsOld.size()] = entity;
10650 }
10651
10652 sort(entsNew, SortComponentCond{});
10653
10654 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
10655 auto* pArchetypeRight = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
10656 if (pArchetypeRight != nullptr)
10657 return pArchetypeRight;
10658
10659 pArchetypeRight = create_archetype({entsNew.data(), entsNew.size()});
10660 pArchetypeRight->set_hashes({hashLookup});
10661 reg_archetype(pArchetypeRight);
10662 return pArchetypeRight;
10663 }
10664
10670 GAIA_NODISCARD Archetype* foc_archetype_del(Archetype* pArchetypeRight, Entity entity) {
10671 // Check if the component is found when following the "del" edges
10672 bool edgeNeedsRebuild = false;
10673 {
10674 const auto edge = pArchetypeRight->find_edge_left(entity);
10675 if (edge != ArchetypeIdHashPairBad) {
10676 const auto it = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
10677 if (it != m_archetypesById.end()) {
10678 auto* pArchetypeLeft = it->second;
10679 if (pArchetypeLeft != nullptr)
10680 return pArchetypeLeft;
10681 }
10682
10683 // Drop stale local cache edge and rebuild it below.
10684 pArchetypeRight->del_graph_edge_left_local(entity);
10685 edgeNeedsRebuild = true;
10686 }
10687 }
10688
10689 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
10690 auto entsOld = pArchetypeRight->ids_view();
10691
10692 // Find the intersection
10693 for (const auto e: entsOld) {
10694 if (e == entity)
10695 continue;
10696
10697 entsNew.push_back(e);
10698 }
10699
10700 // Verify there was a change
10701 GAIA_ASSERT(entsNew.size() != entsOld.size());
10702
10703 // Calculate the hashes
10704 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
10705 auto* pArchetype = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
10706 if (pArchetype == nullptr) {
10707 pArchetype = create_archetype({entsNew.data(), entsNew.size()});
10708 pArchetype->set_hashes({hashLookup});
10709 reg_archetype(pArchetype);
10710 edgeNeedsRebuild = true;
10711 }
10712
10713 if (edgeNeedsRebuild)
10714 pArchetype->build_graph_edges(pArchetypeRight, entity);
10715
10716 return pArchetype;
10717 }
10718
10724 GAIA_NODISCARD Archetype* foc_archetype_del_no_graph(Archetype* pArchetypeRight, Entity entity) {
10725 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
10726 auto entsOld = pArchetypeRight->ids_view();
10727
10728 for (const auto e: entsOld) {
10729 if (e == entity)
10730 continue;
10731
10732 entsNew.push_back(e);
10733 }
10734
10735 GAIA_ASSERT(entsNew.size() != entsOld.size());
10736
10737 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
10738 auto* pArchetype = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
10739 if (pArchetype != nullptr)
10740 return pArchetype;
10741
10742 pArchetype = create_archetype({entsNew.data(), entsNew.size()});
10743 pArchetype->set_hashes({hashLookup});
10744 reg_archetype(pArchetype);
10745 return pArchetype;
10746 }
10747
10750 GAIA_NODISCARD const auto& archetypes() const {
10751 return m_archetypes;
10752 }
10753
10757 GAIA_NODISCARD Archetype& archetype(Entity entity) {
10758 const auto& ec = fetch(entity);
10759 return *ec.pArchetype;
10760 }
10761
10765 void del_name(EntityContainer& ec, Entity entity) {
10766 EntityBuilder(*this, entity, ec).del_name();
10767 }
10768
10771 void del_name(Entity entity) {
10772 EntityBuilder(*this, entity).del_name();
10773 }
10774
10779 void del_entity(Entity entity, bool invalidate) {
10780 if (entity.pair() || entity == EntityBad)
10781 return;
10782
10783 auto& ec = fetch(entity);
10784 del_entity_inter(ec, entity, invalidate);
10785 }
10786
10792 void del_entity(EntityContainer& ec, Entity entity, bool invalidate) {
10793 if (entity.pair() || entity == EntityBad)
10794 return;
10795
10796 del_entity_inter(ec, entity, invalidate);
10797 }
10798
10803 void del_entity_inter(EntityContainer& ec, Entity entity, bool invalidate) {
10804 GAIA_ASSERT(entity.id() > GAIA_ID(LastCoreComponent).id());
10805
10806 // if (!is_req_del(ec))
10807 {
10808 if (m_recs.entities.item_count() == 0)
10809 return;
10810
10811#if GAIA_ASSERT_ENABLED
10812 auto* pChunk = ec.pChunk;
10813 GAIA_ASSERT(pChunk != nullptr);
10814#endif
10815
10816 // Remove the entity from its chunk.
10817 // We call del_name first because remove_entity calls component destructors.
10818 // If the call was made inside invalidate_entity we would access a memory location
10819 // which has already been destructed which is not nice.
10820 del_name(ec, entity);
10821 remove_entity(*ec.pArchetype, *ec.pChunk, ec.row);
10822 remove_src_entity_version(entity);
10823 }
10824
10825 // Invalidate on-demand.
10826 // We delete as a separate step in the delayed deletion.
10827 if (invalidate)
10828 invalidate_entity(entity);
10829 }
10830
10836 void del_entities(Archetype& archetype) {
10837 for (auto* pChunk: archetype.chunks()) {
10838 auto ids = pChunk->entity_view();
10839 for (auto e: ids) {
10840 if (!valid(e))
10841 continue;
10842
10843#if GAIA_ASSERT_ENABLED
10844 const auto& ec = fetch(e);
10845
10846 // We should never end up trying to delete a forbidden-to-delete entity
10847 GAIA_ASSERT((ec.flags & EntityContainerFlags::OnDeleteTarget_Error) == 0);
10848#endif
10849
10850 del_entity(e, true);
10851 }
10852
10853 validate_chunk(pChunk);
10854
10855 // If the chunk was already dying we need to remove it from the delete list
10856 // because we can delete it right away.
10857 if (pChunk->queued_for_deletion())
10858 remove_chunk_from_delete_queue(pChunk->delete_queue_index());
10859
10860 remove_chunk(archetype, *pChunk);
10861 }
10862
10863 validate_entities();
10864 }
10865
10868 void del_inter(Entity entity) {
10869 auto on_delete = [this](Entity entityToDel) {
10870 auto& ec = fetch(entityToDel);
10871 handle_del_entity(ec, entityToDel, EntitySpan{});
10872 };
10873
10874 if (is_wildcard(entity)) {
10875 const auto rel = get(entity.id());
10876 const auto tgt = get(entity.gen());
10877
10878 // (*,*)
10879 if (rel == All && tgt == All) {
10880 GAIA_ASSERT2(false, "Not supported yet");
10881 }
10882 // (*,X)
10883 else if (rel == All) {
10884 if (const auto* pTargets = relations(tgt)) {
10885 // handle_del might invalidate the targets map so we need to make a copy
10886 // TODO: this is suboptimal at best, needs to be optimized
10887 cnt::darray_ext<Entity, 64> tmp;
10888 for (auto key: *pTargets)
10889 tmp.push_back(key.entity());
10890 for (auto e: tmp)
10891 on_delete(Pair(e, tgt));
10892 }
10893 }
10894 // (X,*)
10895 else if (tgt == All) {
10896 if (const auto* pRelations = targets(rel)) {
10897 // handle_del might invalidate the targets map so we need to make a copy
10898 // TODO: this is suboptimal at best, needs to be optimized
10899 cnt::darray_ext<Entity, 64> tmp;
10900 for (auto key: *pRelations)
10901 tmp.push_back(key.entity());
10902 for (auto e: tmp)
10903 on_delete(Pair(rel, e));
10904 }
10905 }
10906 } else {
10907 on_delete(entity);
10908 }
10909 }
10910
10912 void del_finalize_archetypes() {
10913 GAIA_PROF_SCOPE(World::del_finalize_archetypes);
10914
10915 for (auto& key: m_reqArchetypesToDel) {
10916 auto* pArchetype = key.archetype();
10917 if (pArchetype == nullptr)
10918 continue;
10919
10920 del_entities(*pArchetype);
10921
10922 // Now that all entities are deleted, all their chunks are requested to get deleted
10923 // and in turn the archetype itself as well. Therefore, it is added to the archetype
10924 // delete list and picked up by del_empty_archetypes. No need to call deletion from here.
10925 // > del_empty_archetype(pArchetype);
10926 }
10927 m_reqArchetypesToDel.clear();
10928 }
10929
10931 void del_finalize_entities() {
10932 GAIA_PROF_SCOPE(World::del_finalize_entities);
10933
10934 for (auto it = m_reqEntitiesToDel.begin(); it != m_reqEntitiesToDel.end();) {
10935 const auto e = it->entity();
10936
10937 // Entities that form archetypes need to stay until the archetype itself is gone
10938 if (m_entityToArchetypeMap.contains(*it)) {
10939 ++it;
10940 continue;
10941 }
10942
10943 // Requested entities are partially deleted. We only need to invalidate them.
10944 invalidate_entity(e);
10945
10946 it = m_reqEntitiesToDel.erase(it);
10947 }
10948 }
10949
10951 void del_finalize() {
10952 GAIA_PROF_SCOPE(World::del_finalize);
10953
10954 del_finalize_archetypes();
10955 del_finalize_entities();
10956 }
10957
10963 GAIA_NODISCARD bool archetype_cond_match(Archetype& archetype, Pair cond, Entity target) const {
10964 // E.g.:
10965 // target = (All, entity)
10966 // cond = (OnDeleteTarget, delete)
10967 // Delete the entity if it matches the cond
10968 auto ids = archetype.ids_view();
10969
10970 if (target.pair()) {
10971 for (auto e: ids) {
10972 // Find the pair which matches (All, entity)
10973 if (!e.pair())
10974 continue;
10975 if (e.gen() != target.gen())
10976 continue;
10977
10978 const auto& ec = m_recs.entities[e.id()];
10979 const auto entity = ec.pChunk->entity_view()[ec.row];
10980 if (!has(entity, cond))
10981 continue;
10982
10983 return true;
10984 }
10985 } else {
10986 for (auto e: ids) {
10987 if (e.pair())
10988 continue;
10989 if (!has(e, cond))
10990 continue;
10991
10992 return true;
10993 }
10994 }
10995
10996 return false;
10997 }
10998
11002 void move_to_archetype(Archetype& srcArchetype, Archetype& dstArchetype) {
11003 GAIA_ASSERT(&srcArchetype != &dstArchetype);
11004
11005 bool updated = false;
11006
11007 for (auto* pSrcChunk: srcArchetype.chunks()) {
11008 auto srcEnts = pSrcChunk->entity_view();
11009 if (srcEnts.empty())
11010 continue;
11011
11012 // Copy entities back-to-front to avoid unnecessary data movements.
11013 // TODO: Handle disabled entities efficiently.
11014 // If there are disabled entities, we still do data movements if there already
11015 // are enabled entities in the chunk.
11016 // TODO: If the header was of some fixed size, e.g. if we always acted as if we had
11017 // ChunkHeader::MAX_COMPONENTS, certain data movements could be done pretty much instantly.
11018 // E.g. when removing tags or pairs, we would simply replace the chunk pointer
11019 // with a pointer to another one. The same goes for archetypes. Component data
11020 // would not have to move at all internal chunk header pointers would remain unchanged.
11021
11022 uint32_t i = (uint32_t)srcEnts.size();
11023 while (i != 0) {
11024 auto* pDstChunk = dstArchetype.foc_free_chunk();
11025 const uint32_t dstSpaceLeft = pDstChunk->capacity() - pDstChunk->size();
11026 const uint32_t cnt = core::get_min(dstSpaceLeft, i);
11027 for (uint32_t j = 0; j < cnt; ++j) {
11028 auto e = srcEnts[i - j - 1];
11029 move_entity(e, fetch(e), dstArchetype, *pDstChunk);
11030 }
11031
11032 pDstChunk->update_world_version();
11033 pDstChunk->update_entity_order_version();
11034
11035 GAIA_ASSERT(cnt <= i);
11036 i -= cnt;
11037 }
11038
11039 pSrcChunk->update_world_version();
11040 pSrcChunk->update_entity_order_version();
11041 updated = true;
11042 }
11043
11044 if (updated)
11045 update_version(m_worldVersion);
11046 }
11047
11052 GAIA_NODISCARD Archetype* calc_dst_archetype_ent(Archetype* pArchetype, Entity entity) {
11053 GAIA_ASSERT(!is_wildcard(entity));
11054
11055 auto ids = pArchetype->ids_view();
11056 for (auto id: ids) {
11057 if (id != entity)
11058 continue;
11059
11060 return foc_archetype_del(pArchetype, id);
11061 }
11062
11063 return nullptr;
11064 }
11065
11070 GAIA_NODISCARD Archetype* calc_dst_archetype_all_ent(Archetype* pArchetype, Entity entity) {
11071 GAIA_ASSERT(is_wildcard(entity));
11072
11073 Archetype* pDstArchetype = pArchetype;
11074
11075 auto ids = pArchetype->ids_view();
11076 for (auto id: ids) {
11077 if (!id.pair() || id.gen() != entity.gen())
11078 continue;
11079
11080 pDstArchetype = foc_archetype_del(pDstArchetype, id);
11081 }
11082
11083 return pArchetype != pDstArchetype ? pDstArchetype : nullptr;
11084 }
11085
11090 GAIA_NODISCARD Archetype* calc_dst_archetype_ent_all(Archetype* pArchetype, Entity entity) {
11091 GAIA_ASSERT(is_wildcard(entity));
11092
11093 Archetype* pDstArchetype = pArchetype;
11094
11095 auto ids = pArchetype->ids_view();
11096 for (auto id: ids) {
11097 if (!id.pair() || id.id() != entity.id())
11098 continue;
11099
11100 pDstArchetype = foc_archetype_del(pDstArchetype, id);
11101 }
11102
11103 return pArchetype != pDstArchetype ? pDstArchetype : nullptr;
11104 }
11105
11110 GAIA_NODISCARD Archetype* calc_dst_archetype_all_all(Archetype* pArchetype, [[maybe_unused]] Entity entity) {
11111 GAIA_ASSERT(is_wildcard(entity));
11112
11113 Archetype* pDstArchetype = pArchetype;
11114 bool found = false;
11115
11116 auto ids = pArchetype->ids_view();
11117 for (auto id: ids) {
11118 if (!id.pair())
11119 continue;
11120
11121 pDstArchetype = foc_archetype_del(pDstArchetype, id);
11122 found = true;
11123 }
11124
11125 return found ? pDstArchetype : nullptr;
11126 }
11127
11133 GAIA_NODISCARD Archetype* calc_dst_archetype(Archetype* pArchetype, Entity entity) {
11134 if (entity.pair()) {
11135 auto rel = entity.id();
11136 auto tgt = entity.gen();
11137
11138 // Removing a wildcard pair. We need to find all pairs matching it.
11139 if (rel == All.id() || tgt == All.id()) {
11140 // (first, All) means we need to match (first, A), (first, B), ...
11141 if (rel != All.id() && tgt == All.id())
11142 return calc_dst_archetype_ent_all(pArchetype, entity);
11143
11144 // (All, second) means we need to match (A, second), (B, second), ...
11145 if (rel == All.id() && tgt != All.id())
11146 return calc_dst_archetype_all_ent(pArchetype, entity);
11147
11148 // (All, All) means we need to match all relationships
11149 return calc_dst_archetype_all_all(pArchetype, EntityBad);
11150 }
11151 }
11152
11153 // Non-wildcard pair or entity
11154 return calc_dst_archetype_ent(pArchetype, entity);
11155 }
11156
11159 void req_del(Archetype& archetype) {
11160 if (archetype.is_req_del())
11161 return;
11162
11163 archetype.req_del();
11164 update_version(m_archetypeDeleteVersion);
11165 m_reqArchetypesToDel.insert(ArchetypeLookupKey(archetype.lookup_hash(), &archetype));
11166 }
11167
11172 void req_del_inter(EntityContainer& ec, Entity entity) {
11173 if (is_req_del(ec))
11174 return;
11175
11176 unlink_live_is_relations(entity);
11177#if GAIA_OBSERVERS_ENABLED
11178 del_nonfragmenting_relation_source_observed(entity);
11179 del_nonfragmenting_relation_source(entity);
11180 auto delDiffCtx =
11181 m_observers.prepare_diff(*this, ObserverEvent::OnDel, EntitySpan{&entity, 1}, EntitySpan{&entity, 1});
11182#else
11183 del_nonfragmenting_relation_source(entity);
11184#endif
11185 del_entity(ec, entity, false);
11186#if GAIA_OBSERVERS_ENABLED
11187 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
11188#endif
11189
11190 ec.req_del();
11191 m_reqEntitiesToDel.insert(EntityLookupKey(entity));
11192 }
11193
11197 void req_del(EntityContainer& ec, Entity entity) {
11198#if GAIA_OBSERVERS_ENABLED
11199 if (entity_deletion_active(entity))
11200 return;
11201
11202 entity_deletion_enter(entity);
11203 req_del_inter(ec, entity);
11204 entity_deletion_leave(entity);
11205#else
11206 req_del_inter(ec, entity);
11207#endif
11208 }
11209
11212 void invalidate_pair_removal_caches(Entity entity) {
11213 if (!entity.pair())
11214 return;
11215
11216 auto invalidate_relation = [this](Entity relation) {
11217 if (relation == EntityBad)
11218 return;
11219 touch_rel_version(relation);
11220 invalidate_queries_for_rel(relation);
11221 };
11222
11223 if (entity.id() != All.id()) {
11224 invalidate_relation(try_get(entity.id()));
11225 } else if (entity.gen() != All.id()) {
11226 const auto target = try_get(entity.gen());
11227 if (target != EntityBad) {
11228 if (const auto* pRelations = relations(target)) {
11229 for (auto relationKey: *pRelations)
11230 invalidate_relation(relationKey.entity());
11231 }
11232 }
11233 } else {
11234 for (auto it = m_pairLookup.relation_begin(); it != m_pairLookup.relation_end(); ++it)
11235 invalidate_relation(it->first.entity());
11236 }
11237
11238 clear_relation_caches();
11239 }
11240
11244 void unlink_live_is_relation(Entity source, Entity target) {
11245 const auto sourceKey = EntityLookupKey(source);
11246 const auto targetKey = EntityLookupKey(target);
11247
11248 invalidate_queries_for_entity({Is, target});
11249
11250 if (const auto itTargets = m_entityToAsTargets.find(sourceKey); itTargets != m_entityToAsTargets.end()) {
11251 itTargets->second.erase(targetKey);
11252 if (itTargets->second.empty())
11253 m_entityToAsTargets.erase(itTargets);
11254 }
11255 m_entityToAsTargetsTravCache = {};
11256
11257 if (const auto itRelations = m_entityToAsRelations.find(targetKey);
11258 itRelations != m_entityToAsRelations.end()) {
11259 itRelations->second.erase(sourceKey);
11260 if (itRelations->second.empty())
11261 m_entityToAsRelations.erase(itRelations);
11262 }
11263 m_entityToAsRelationsTravCache = {};
11264 }
11265
11268 void unlink_live_is_relations(Entity source) {
11269 const auto itTargets = m_entityToAsTargets.find(EntityLookupKey(source));
11270 if (itTargets == m_entityToAsTargets.end())
11271 return;
11272
11273 cnt::darray_ext<Entity, 4> targets;
11274 for (auto targetKey: itTargets->second)
11275 targets.push_back(targetKey.entity());
11276
11277 for (auto target: targets)
11278 unlink_live_is_relation(source, target);
11279 }
11280
11284 void unlink_stale_is_relations_by_target_id(Entity source, EntityId targetId) {
11285 const auto sourceKey = EntityLookupKey(source);
11286 const auto itTargets = m_entityToAsTargets.find(sourceKey);
11287 if (itTargets == m_entityToAsTargets.end())
11288 return;
11289
11290 cnt::darray_ext<EntityLookupKey, 4> removedTargets;
11291 for (auto targetKey: itTargets->second) {
11292 if (targetKey.entity().id() == targetId)
11293 removedTargets.push_back(targetKey);
11294 }
11295
11296 for (auto targetKey: removedTargets) {
11297 invalidate_queries_for_structural_entity(EntityLookupKey(Pair{Is, targetKey.entity()}));
11298 itTargets->second.erase(targetKey);
11299
11300 const auto itRelations = m_entityToAsRelations.find(targetKey);
11301 if (itRelations != m_entityToAsRelations.end()) {
11302 itRelations->second.erase(sourceKey);
11303 if (itRelations->second.empty())
11304 m_entityToAsRelations.erase(itRelations);
11305 }
11306 }
11307
11308 if (itTargets->second.empty())
11309 m_entityToAsTargets.erase(itTargets);
11310
11311 m_entityToAsTargetsTravCache = {};
11312 m_entityToAsRelationsTravCache = {};
11313 }
11314
11320 template <typename Func>
11321 void each_delete_cascade_direct_source(Entity target, Pair cond, Func&& func) {
11322 GAIA_ASSERT(!target.pair());
11323
11324 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
11325 const auto relation = relKey.entity();
11326 if (!has(relation, cond))
11327 continue;
11328
11329 const auto* pSources = store.sources(target);
11330 if (pSources == nullptr)
11331 continue;
11332
11333 for (auto source: *pSources)
11334 func(source);
11335 }
11336
11337 const auto pairEntity = Pair(All, target);
11338 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pairEntity));
11339 if (it != m_entityToArchetypeMap.end()) {
11340 for (const auto& record: it->second) {
11341 auto* pArchetype = record.pArchetype;
11342 if (pArchetype == nullptr || pArchetype->is_req_del())
11343 continue;
11344 if (!archetype_cond_match(*pArchetype, cond, pairEntity))
11345 continue;
11346
11347 for (const auto* pChunk: pArchetype->chunks()) {
11348 const auto entities = pChunk->entity_view();
11349 GAIA_EACH(entities)
11350 func(entities[i]);
11351 }
11352 }
11353 }
11354 }
11355
11360 GAIA_NODISCARD bool has_delete_cascade_direct_sources(Entity target, Pair cond) const {
11361 GAIA_ASSERT(!target.pair());
11362
11363 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
11364 if (store.sources(target) != nullptr && has(relKey.entity(), cond))
11365 return true;
11366 }
11367
11368 const auto pairEntity = Pair(All, target);
11369 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pairEntity));
11370 if (it == m_entityToArchetypeMap.end())
11371 return false;
11372
11373 for (const auto& record: it->second) {
11374 auto* pArchetype = record.pArchetype;
11375 if (pArchetype == nullptr || pArchetype->is_req_del())
11376 continue;
11377 if (!archetype_cond_match(*pArchetype, cond, pairEntity))
11378 continue;
11379
11380 for (const auto* pChunk: pArchetype->chunks()) {
11381 if (!pChunk->empty())
11382 return true;
11383 }
11384 }
11385
11386 return false;
11387 }
11388
11393 void collect_delete_cascade_direct_sources(Entity target, Pair cond, cnt::darray<Entity>& out) {
11394 GAIA_ASSERT(!target.pair());
11395 const auto visitStamp = next_entity_visit_stamp();
11396 each_delete_cascade_direct_source(target, cond, [&](Entity source) {
11397 if (!valid(source))
11398 return;
11399 if (!try_mark_entity_visited(source, visitStamp))
11400 return;
11401 out.push_back(source);
11402 });
11403 }
11404
11409 void collect_delete_cascade_sources(Entity target, Pair cond, cnt::darray<Entity>& out) {
11410 GAIA_ASSERT(!target.pair());
11411 const auto visitStamp = next_entity_visit_stamp();
11412 cnt::darray_ext<Entity, 32> targetsToVisit;
11413 (void)try_mark_entity_visited(target, visitStamp);
11414 targetsToVisit.push_back(target);
11415
11416 for (uint32_t i = 0; i < targetsToVisit.size(); ++i) {
11417 const auto currTarget = targetsToVisit[i];
11418 each_delete_cascade_direct_source(currTarget, cond, [&](Entity source) {
11419 if (!valid(source))
11420 return;
11421 if (!try_mark_entity_visited(source, visitStamp))
11422 return;
11423
11424 out.push_back(source);
11425 targetsToVisit.push_back(source);
11426 });
11427 }
11428 }
11429
11432 void req_del_entities_with(Entity entity) {
11433 GAIA_PROF_SCOPE(World::req_del_entities_with);
11434
11435 GAIA_ASSERT(entity != Pair(All, All));
11436
11437 auto req_del_nonfragmenting_pair = [&](Entity relation, Entity target) {
11438 cnt::darray<Entity> sourcesToDel;
11439 sources(relation, target, [&](Entity source) {
11440 sourcesToDel.push_back(source);
11441 });
11442
11443 for (auto source: sourcesToDel)
11444 req_del(fetch(source), source);
11445 };
11446
11447 if (entity.pair()) {
11448 if (entity.id() != All.id()) {
11449 const auto relation = try_get(entity.id());
11450 const auto target = try_get(entity.gen());
11451 if (relation != EntityBad && target != EntityBad && relation_uses_non_fragmenting_storage(relation))
11452 req_del_nonfragmenting_pair(relation, target);
11453 } else {
11454 const auto target = try_get(entity.gen());
11455 if (target == EntityBad)
11456 goto skip_req_del_all_target;
11457 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
11458 if (store.sources(target) == nullptr)
11459 continue;
11460
11461 req_del_nonfragmenting_pair(relKey.entity(), target);
11462 }
11463 skip_req_del_all_target:;
11464 }
11465 } else if (relation_uses_non_fragmenting_storage(entity)) {
11466 if (const auto* pTargets = targets(entity)) {
11467 for (auto targetKey: *pTargets)
11468 req_del_nonfragmenting_pair(entity, targetKey.entity());
11469 }
11470 }
11471
11472 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
11473 if (it == m_entityToArchetypeMap.end())
11474 return;
11475
11476 for (const auto& record: it->second)
11477 req_del(*record.pArchetype);
11478 }
11479
11484 void req_del_entities_with(Entity entity, Pair cond) {
11485 cnt::set<EntityLookupKey> visited;
11486 req_del_entities_with(entity, cond, visited);
11487 }
11488
11493 void req_del_entities_with(Entity entity, Pair cond, cnt::set<EntityLookupKey>& visited) {
11494 GAIA_PROF_SCOPE(World::req_del_entities_with);
11495
11496 GAIA_ASSERT(entity != Pair(All, All));
11497 if (!visited.insert(EntityLookupKey(entity)).second)
11498 return;
11499
11500 cnt::darray<Entity> cascadeTargets;
11501 if (entity.pair() && entity.id() == All.id()) {
11502 const auto target = try_get(entity.gen());
11503 if (target != EntityBad)
11504 collect_delete_cascade_direct_sources(target, cond, cascadeTargets);
11505 }
11506
11507 auto req_del_nonfragmenting_pair = [&](Entity relation, Entity target) {
11508 if (!has(relation, cond))
11509 return;
11510
11511 cnt::darray<Entity> sourcesToDel;
11512 sources(relation, target, [&](Entity source) {
11513 sourcesToDel.push_back(source);
11514 });
11515
11516 for (auto source: sourcesToDel)
11517 req_del(fetch(source), source);
11518 };
11519
11520 for (auto source: cascadeTargets) {
11521 if (has_delete_cascade_direct_sources(source, cond))
11522 req_del_entities_with(Pair(All, source), cond, visited);
11523 }
11524
11525 if (entity.pair()) {
11526 if (entity.id() != All.id()) {
11527 const auto relation = try_get(entity.id());
11528 const auto target = try_get(entity.gen());
11529 if (relation != EntityBad && target != EntityBad && relation_uses_non_fragmenting_storage(relation))
11530 req_del_nonfragmenting_pair(relation, target);
11531 } else {
11532 const auto target = try_get(entity.gen());
11533 if (target == EntityBad)
11534 goto skip_req_del_all_target_cond;
11535 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
11536 if (store.sources(target) == nullptr)
11537 continue;
11538
11539 req_del_nonfragmenting_pair(relKey.entity(), target);
11540 }
11541 skip_req_del_all_target_cond:;
11542 }
11543 } else if (relation_uses_non_fragmenting_storage(entity)) {
11544 if (const auto* pTargets = targets(entity)) {
11545 for (auto targetKey: *pTargets)
11546 req_del_nonfragmenting_pair(entity, targetKey.entity());
11547 }
11548 }
11549
11550 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
11551 if (it == m_entityToArchetypeMap.end())
11552 return;
11553
11554 for (const auto& record: it->second) {
11555 auto* pArchetype = record.pArchetype;
11556 // Evaluate the condition if a valid pair is given
11557 if (!archetype_cond_match(*pArchetype, cond, entity))
11558 continue;
11559
11560 req_del(*pArchetype);
11561 }
11562 }
11563
11566 void rem_from_entities(Entity entity) {
11567 GAIA_PROF_SCOPE(World::rem_from_entities);
11568
11569 invalidate_pair_removal_caches(entity);
11570
11571 auto rem_nonfragmenting_pair = [&](Entity relation, Entity target) {
11572 cnt::darray<Entity> sourcesToRem;
11573 sources(relation, target, [&](Entity source) {
11574 sourcesToRem.push_back(source);
11575 });
11576
11577 for (auto source: sourcesToRem)
11578 del(source, Pair(relation, target));
11579 };
11580
11581 if (entity.pair()) {
11582 if (entity.id() != All.id()) {
11583 const auto relation = try_get(entity.id());
11584 const auto target = try_get(entity.gen());
11585 if (relation != EntityBad && target != EntityBad && relation_uses_non_fragmenting_storage(relation))
11586 rem_nonfragmenting_pair(relation, target);
11587 } else {
11588 const auto target = try_get(entity.gen());
11589 if (target == EntityBad)
11590 goto skip_rem_all_target;
11591 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
11592 if (store.sources(target) == nullptr)
11593 continue;
11594
11595 rem_nonfragmenting_pair(relKey.entity(), target);
11596 }
11597 skip_rem_all_target:;
11598 }
11599 } else if (relation_uses_non_fragmenting_storage(entity)) {
11600 if (const auto* pTargets = targets(entity)) {
11601 for (auto targetKey: *pTargets)
11602 rem_nonfragmenting_pair(entity, targetKey.entity());
11603 }
11604 }
11605
11606 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
11607 if (it == m_entityToArchetypeMap.end())
11608 return;
11609
11610 // Invalidate the singleton status if necessary
11611 if (!entity.pair()) {
11612 auto& ec = fetch(entity);
11613 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0) {
11614 auto ids = ec.pArchetype->ids_view();
11615 const auto idx = core::get_index(ids, entity);
11616 if (idx != BadIndex)
11617 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::IsSingleton, false);
11618 }
11619 }
11620
11621#if GAIA_OBSERVERS_ENABLED
11622 cnt::set<EntityLookupKey> diffTermSet;
11623 cnt::darray<Entity> diffTerms;
11624 cnt::darray<Entity> diffTargets;
11625 for (const auto& record: it->second) {
11626 auto* pArchetype = record.pArchetype;
11627 if (pArchetype->is_req_del())
11628 continue;
11629
11630 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
11631 if (pDstArchetype == nullptr)
11632 continue;
11633
11634 for (auto id: pArchetype->ids_view()) {
11635 bool matches = false;
11636 if (entity.pair()) {
11637 if (entity.id() == All.id() && entity.gen() == All.id())
11638 matches = id.pair();
11639 else if (entity.id() == All.id())
11640 matches = id.pair() && id.gen() == entity.gen();
11641 else if (entity.gen() == All.id())
11642 matches = id.pair() && id.id() == entity.id();
11643 else
11644 matches = id == entity;
11645 } else
11646 matches = id == entity;
11647
11648 if (!matches)
11649 continue;
11650
11651 if (diffTermSet.insert(EntityLookupKey(id)).second)
11652 diffTerms.push_back(id);
11653 }
11654
11655 for (const auto* pChunk: pArchetype->chunks()) {
11656 const auto entities = pChunk->entity_view();
11657 GAIA_EACH(entities)
11658 diffTargets.push_back(entities[i]);
11659 }
11660 }
11661 auto delDiffCtx = diffTargets.empty() || diffTerms.empty()
11662 ? ObserverRegistry::DiffDispatchCtx{}
11663 : m_observers.prepare_diff(
11664 *this, ObserverEvent::OnDel, EntitySpan{diffTerms.data(), diffTerms.size()},
11665 EntitySpan{diffTargets.data(), diffTargets.size()});
11666#endif
11667
11668 // Update archetypes of all affected entities
11669 for (const auto& record: it->second) {
11670 auto* pArchetype = record.pArchetype;
11671 if (pArchetype->is_req_del())
11672 continue;
11673
11674 if (entity.pair()) {
11675 cnt::darray_ext<Entity, 16> removedIsTargets;
11676 for (auto id: pArchetype->ids_view()) {
11677 bool matches = false;
11678 if (entity.id() == All.id() && entity.gen() == All.id())
11679 matches = id.pair();
11680 else if (entity.id() == All.id())
11681 matches = id.pair() && id.gen() == entity.gen();
11682 else if (entity.gen() == All.id())
11683 matches = id.pair() && id.id() == entity.id();
11684 else
11685 matches = id == entity;
11686
11687 if (!matches || !id.pair() || id.id() != Is.id())
11688 continue;
11689
11690 const auto target = try_get(id.gen());
11691 if (target != EntityBad)
11692 removedIsTargets.push_back(target);
11693 }
11694
11695 if (!removedIsTargets.empty()) {
11696 for (const auto* pChunk: pArchetype->chunks()) {
11697 auto entities = pChunk->entity_view();
11698 GAIA_EACH(entities) {
11699 for (const auto target: removedIsTargets)
11700 unlink_live_is_relation(entities[i], target);
11701 }
11702 }
11703 }
11704 }
11705
11706 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
11707 if (pDstArchetype != nullptr)
11708 move_to_archetype(*pArchetype, *pDstArchetype);
11709 }
11710
11711#if GAIA_OBSERVERS_ENABLED
11712 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
11713#endif
11714 }
11715
11720 void rem_from_entities(Entity entity, Pair cond) {
11721 GAIA_PROF_SCOPE(World::rem_from_entities);
11722
11723 invalidate_pair_removal_caches(entity);
11724
11725 auto rem_nonfragmenting_pair = [&](Entity relation, Entity target) {
11726 if (!has(relation, cond))
11727 return;
11728
11729 cnt::darray<Entity> sourcesToRem;
11730 sources(relation, target, [&](Entity source) {
11731 sourcesToRem.push_back(source);
11732 });
11733
11734 for (auto source: sourcesToRem)
11735 del(source, Pair(relation, target));
11736 };
11737
11738 if (entity.pair()) {
11739 if (entity.id() != All.id()) {
11740 const auto relation = try_get(entity.id());
11741 const auto target = try_get(entity.gen());
11742 if (relation != EntityBad && target != EntityBad && relation_uses_non_fragmenting_storage(relation))
11743 rem_nonfragmenting_pair(relation, target);
11744 } else {
11745 const auto target = try_get(entity.gen());
11746 if (target == EntityBad)
11747 goto skip_rem_all_target_cond;
11748 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
11749 if (store.sources(target) == nullptr)
11750 continue;
11751
11752 rem_nonfragmenting_pair(relKey.entity(), target);
11753 }
11754 skip_rem_all_target_cond:;
11755 }
11756 } else if (relation_uses_non_fragmenting_storage(entity)) {
11757 if (const auto* pTargets = targets(entity)) {
11758 for (auto targetKey: *pTargets)
11759 rem_nonfragmenting_pair(entity, targetKey.entity());
11760 }
11761 }
11762
11763 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
11764 if (it == m_entityToArchetypeMap.end())
11765 return;
11766
11767 // Invalidate the singleton status if necessary
11768 if (!entity.pair()) {
11769 auto& ec = fetch(entity);
11770 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0) {
11771 auto ids = ec.pArchetype->ids_view();
11772 const auto idx = core::get_index(ids, entity);
11773 if (idx != BadIndex)
11774 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::IsSingleton, false);
11775 }
11776 }
11777
11778#if GAIA_OBSERVERS_ENABLED
11779 cnt::set<EntityLookupKey> diffTermSet;
11780 cnt::darray<Entity> diffTerms;
11781 cnt::darray<Entity> diffTargets;
11782 for (const auto& record: it->second) {
11783 auto* pArchetype = record.pArchetype;
11784 if (pArchetype->is_req_del())
11785 continue;
11786
11787 if (!archetype_cond_match(*pArchetype, cond, entity))
11788 continue;
11789
11790 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
11791 if (pDstArchetype == nullptr)
11792 continue;
11793
11794 for (auto id: pArchetype->ids_view()) {
11795 bool matches = false;
11796 if (entity.pair()) {
11797 if (entity.id() == All.id() && entity.gen() == All.id())
11798 matches = id.pair();
11799 else if (entity.id() == All.id())
11800 matches = id.pair() && id.gen() == entity.gen();
11801 else if (entity.gen() == All.id())
11802 matches = id.pair() && id.id() == entity.id();
11803 else
11804 matches = id == entity;
11805 } else
11806 matches = id == entity;
11807
11808 if (!matches)
11809 continue;
11810
11811 if (diffTermSet.insert(EntityLookupKey(id)).second)
11812 diffTerms.push_back(id);
11813 }
11814
11815 for (const auto* pChunk: pArchetype->chunks()) {
11816 const auto entities = pChunk->entity_view();
11817 GAIA_EACH(entities)
11818 diffTargets.push_back(entities[i]);
11819 }
11820 }
11821 auto delDiffCtx = diffTargets.empty() || diffTerms.empty()
11822 ? ObserverRegistry::DiffDispatchCtx{}
11823 : m_observers.prepare_diff(
11824 *this, ObserverEvent::OnDel, EntitySpan{diffTerms.data(), diffTerms.size()},
11825 EntitySpan{diffTargets.data(), diffTargets.size()});
11826#endif
11827
11828 for (const auto& record: it->second) {
11829 auto* pArchetype = record.pArchetype;
11830 if (pArchetype->is_req_del())
11831 continue;
11832
11833 // Evaluate the condition if a valid pair is given
11834 if (!archetype_cond_match(*pArchetype, cond, entity))
11835 continue;
11836
11837 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
11838 if (pDstArchetype != nullptr)
11839 move_to_archetype(*pArchetype, *pDstArchetype);
11840 }
11841
11842#if GAIA_OBSERVERS_ENABLED
11843 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
11844#endif
11845 }
11846
11849 void del_pair_entities(EntitySpan pairEntities) {
11850 for (auto pair: pairEntities) {
11851 if (valid(pair))
11852 del_inter(pair);
11853 }
11854 }
11855
11869 void handle_del_entity(EntityContainer& ec, Entity entity, EntitySpan pairEntities) {
11870 GAIA_PROF_SCOPE(World::handle_del_entity);
11871
11872 GAIA_ASSERT(!is_wildcard(entity));
11873#if GAIA_OBSERVERS_ENABLED
11874 if (entity_deletion_active(entity))
11875 return;
11876#endif
11877
11878 if (entity.pair()) {
11879 if ((ec.flags & EntityContainerFlags::OnDelete_Error) != 0) {
11880 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted");
11881 GAIA_LOG_E(
11882 "Trying to delete a pair [%u.%u] %s [%s] that is forbidden from being deleted", entity.id(),
11883 entity.gen(), name(entity), EntityKindString[entity.kind()]);
11884 return;
11885 }
11886
11887 const auto tgt = try_get(entity.gen());
11888 const bool hasLiveTarget = tgt != EntityBad;
11889 if (hasLiveTarget) {
11890 const auto& ecTgt = fetch(tgt);
11891 if ((ecTgt.flags & EntityContainerFlags::OnDeleteTarget_Error) != 0 ||
11892 has_nonfragmenting_relation_target_cond(tgt, Pair(OnDeleteTarget, Error))) {
11893 GAIA_ASSERT2(
11894 false, "Trying to delete an entity that is forbidden from being deleted (target restriction)");
11895 GAIA_LOG_E(
11896 "Trying to delete a pair [%u.%u] %s [%s] that is forbidden from being deleted (target restriction)",
11897 entity.id(), entity.gen(), name(entity), EntityKindString[entity.kind()]);
11898 return;
11899 }
11900 }
11901
11902#if GAIA_USE_SAFE_ENTITY
11903 // Decrement the ref count at this point.
11904 if ((ec.flags & EntityContainerFlags::RefDecreased) == 0) {
11905 --ec.refCnt;
11906 ec.flags |= EntityContainerFlags::RefDecreased;
11907 }
11908
11909 // Don't delete so long something still references us
11910 if (ec.refCnt != 0)
11911 return;
11912#endif
11913
11914#if GAIA_OBSERVERS_ENABLED
11915 entity_deletion_enter(entity);
11916#endif
11917
11918 if (hasLiveTarget) {
11919 const auto& ecTgt = fetch(tgt);
11920 if ((ecTgt.flags & EntityContainerFlags::OnDeleteTarget_Delete) != 0 ||
11921 has_nonfragmenting_relation_target_cond(tgt, Pair(OnDeleteTarget, Delete))) {
11922#if GAIA_OBSERVERS_ENABLED
11923 cnt::darray<Entity> cascadeTargets;
11924 collect_delete_cascade_sources(tgt, Pair(OnDeleteTarget, Delete), cascadeTargets);
11925 auto cascadeDelDiffCtx =
11926 cascadeTargets.empty()
11927 ? ObserverRegistry::DiffDispatchCtx{}
11928 : m_observers.prepare_diff(
11929 *this, ObserverEvent::OnDel, EntitySpan{cascadeTargets.data(), cascadeTargets.size()},
11930 EntitySpan{cascadeTargets.data(), cascadeTargets.size()});
11931#endif
11932 // Delete all entities referencing this one as a relationship pair's target
11933 req_del_entities_with(Pair(All, tgt), Pair(OnDeleteTarget, Delete));
11934#if GAIA_OBSERVERS_ENABLED
11935 m_observers.finish_diff(*this, GAIA_MOV(cascadeDelDiffCtx));
11936#endif
11937 } else {
11938 // Remove from all entities referencing this one as a relationship pair's target
11939 rem_from_entities(Pair(All, tgt));
11940 }
11941 }
11942
11943 // This entity has been requested to be deleted already. Nothing more for us to do here
11944 if (is_req_del(ec)) {
11945#if GAIA_OBSERVERS_ENABLED
11946 entity_deletion_leave(entity);
11947#endif
11948 return;
11949 }
11950
11951#if GAIA_OBSERVERS_ENABLED
11952 observers().del(*this, entity);
11953#endif
11954#if GAIA_SYSTEMS_ENABLED
11955 systems().del(entity);
11956#endif
11957
11958 if ((ec.flags & EntityContainerFlags::OnDelete_Delete) != 0) {
11959 // Delete all references to the entity
11960 req_del_entities_with(entity);
11961 } else {
11962 // Entities are only removed by default
11963 rem_from_entities(entity);
11964 }
11965 } else {
11966 if ((ec.flags & EntityContainerFlags::OnDelete_Error) != 0) {
11967 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted");
11968 GAIA_LOG_E(
11969 "Trying to delete an entity [%u.%u] %s [%s] that is forbidden from being deleted", entity.id(),
11970 entity.gen(), name(entity), EntityKindString[entity.kind()]);
11971 return;
11972 }
11973
11974 if ((ec.flags & EntityContainerFlags::OnDeleteTarget_Error) != 0 ||
11975 has_nonfragmenting_relation_target_cond(entity, Pair(OnDeleteTarget, Error))) {
11976 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted (a pair's target)");
11977 GAIA_LOG_E(
11978 "Trying to delete an entity [%u.%u] %s [%s] that is forbidden from being deleted (a pair's target)",
11979 entity.id(), entity.gen(), name(entity), EntityKindString[entity.kind()]);
11980 return;
11981 }
11982
11983#if GAIA_USE_SAFE_ENTITY
11984 // Decrement the ref count at this point.
11985 if ((ec.flags & EntityContainerFlags::RefDecreased) == 0) {
11986 --ec.refCnt;
11987 ec.flags |= EntityContainerFlags::RefDecreased;
11988 }
11989
11990 // Don't delete so long something still references us
11991 if (ec.refCnt != 0)
11992 return;
11993#endif
11994
11995#if GAIA_OBSERVERS_ENABLED
11996 entity_deletion_enter(entity);
11997#endif
11998
11999 const bool deleteTargets = (ec.flags & EntityContainerFlags::OnDeleteTarget_Delete) != 0 ||
12000 has_nonfragmenting_relation_target_cond(entity, Pair(OnDeleteTarget, Delete));
12001 cnt::darray<Entity> cascadeTargets;
12002 if (deleteTargets)
12003 collect_delete_cascade_sources(entity, Pair(OnDeleteTarget, Delete), cascadeTargets);
12004#if GAIA_OBSERVERS_ENABLED
12005 auto cascadeDelDiffCtx = ObserverRegistry::DiffDispatchCtx{};
12006 if (!cascadeTargets.empty()) {
12007 cascadeDelDiffCtx = m_observers.prepare_diff(
12008 *this, ObserverEvent::OnDel, EntitySpan{cascadeTargets.data(), cascadeTargets.size()},
12009 EntitySpan{cascadeTargets.data(), cascadeTargets.size()});
12010 }
12011#endif
12012
12013 if (deleteTargets) {
12014 // Delete all entities referencing this one as a relationship pair's target
12015 req_del_entities_with(Pair(All, entity), Pair(OnDeleteTarget, Delete));
12016 } else {
12017 // Remove from all entities referencing this one as a relationship pair's target
12018 rem_from_entities(Pair(All, entity));
12019 }
12020
12021#if GAIA_OBSERVERS_ENABLED
12022 m_observers.finish_diff(*this, GAIA_MOV(cascadeDelDiffCtx));
12023#endif
12024
12025 // This entity is has been requested to be deleted already. Nothing more for us to do here
12026 if (is_req_del(ec)) {
12027 del_pair_entities(pairEntities);
12028#if GAIA_OBSERVERS_ENABLED
12029 entity_deletion_leave(entity);
12030#endif
12031 return;
12032 }
12033
12034#if GAIA_OBSERVERS_ENABLED
12035 observers().del(*this, entity);
12036#endif
12037#if GAIA_SYSTEMS_ENABLED
12038 systems().del(entity);
12039#endif
12040
12041 if ((ec.flags & EntityContainerFlags::OnDelete_Delete) != 0) {
12042 // Delete all references to the entity
12043 req_del_entities_with(entity);
12044 } else {
12045 // Entities are only removed by default
12046 rem_from_entities(entity);
12047 }
12048 }
12049
12050 del_pair_entities(pairEntities);
12051
12052 // Mark the entity with the "delete requested" flag
12053 req_del_inter(ec, entity);
12054
12055#if GAIA_USE_WEAK_ENTITY
12056 // Invalidate WeakEntities
12057 while (ec.pWeakTracker != nullptr) {
12058 auto* pTracker = ec.pWeakTracker;
12059 ec.pWeakTracker = pTracker->next;
12060 if (ec.pWeakTracker != nullptr)
12061 ec.pWeakTracker->prev = nullptr;
12062
12063 auto* pWeakEntity = pTracker->pWeakEntity;
12064 GAIA_ASSERT(pWeakEntity != nullptr);
12065 GAIA_ASSERT(pWeakEntity->m_pTracker == pTracker);
12066 pWeakEntity->m_pTracker = nullptr;
12067 pWeakEntity->m_entity = EntityBad;
12068 delete pTracker;
12069 }
12070#endif
12071#if GAIA_OBSERVERS_ENABLED
12072 entity_deletion_leave(entity);
12073#endif
12074 }
12075
12080 void remove_edge_from_archetype(Archetype* pArchetype, ArchetypeGraphEdge edgeLeft, Entity edgeEntity) {
12081 GAIA_ASSERT(pArchetype != nullptr);
12082
12083 const auto edgeLeftIt = m_archetypesById.find(ArchetypeIdLookupKey(edgeLeft.id, edgeLeft.hash));
12084 if (edgeLeftIt == m_archetypesById.end())
12085 return;
12086
12087 auto* pArchetypeLeft = edgeLeftIt->second;
12088 GAIA_ASSERT(pArchetypeLeft != nullptr);
12089
12090 // Remove the connection with the current archetype
12091 pArchetypeLeft->del_graph_edges(pArchetype, edgeEntity);
12092
12093 // Traverse all archetypes on the right
12094 auto& archetypesRight = pArchetype->right_edges();
12095 for (auto& it: archetypesRight) {
12096 const auto& edgeRight = it.second;
12097 const auto edgeRightIt = m_archetypesById.find(ArchetypeIdLookupKey(edgeRight.id, edgeRight.hash));
12098 if (edgeRightIt == m_archetypesById.end())
12099 continue;
12100
12101 auto* pArchetypeRight = edgeRightIt->second;
12102
12103 // Remove the connection with the current archetype
12104 pArchetype->del_graph_edges(pArchetypeRight, it.first.entity());
12105 }
12106 }
12107
12110 void remove_edges(Entity entityToRemove) {
12111 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entityToRemove));
12112 if (it == m_entityToArchetypeMap.end())
12113 return;
12114
12115 for (const auto& record: it->second) {
12116 auto* pArchetype = record.pArchetype;
12117 remove_edge_from_archetype(pArchetype, pArchetype->find_edge_left(entityToRemove), entityToRemove);
12118 }
12119 }
12120
12123 void remove_edges_from_pairs(Entity entity) {
12124 if (entity.pair())
12125 return;
12126
12127 // Make sure to remove all pairs containing the entity
12128 // (X, something)
12129 const auto* tgts = targets(entity);
12130 if (tgts != nullptr) {
12131 for (auto target: *tgts)
12132 remove_edges(Pair(entity, target.entity()));
12133 }
12134 // (something, X)
12135 const auto* rels = relations(entity);
12136 if (rels != nullptr) {
12137 for (auto relation: *rels)
12138 remove_edges(Pair(relation.entity(), entity));
12139 }
12140 }
12141
12144 void del_graph_edges(Entity entity) {
12145 remove_edges(entity);
12146 remove_edges_from_pairs(entity);
12147 }
12148
12151 void touch_rel_version(Entity relation) {
12152 if (m_pLastRelationVersion != nullptr && m_lastRelationVersionRelation == relation) {
12153 ++*m_pLastRelationVersion;
12154 if (*m_pLastRelationVersion == 0)
12155 *m_pLastRelationVersion = 1;
12156 return;
12157 }
12158
12159 const EntityLookupKey key(relation);
12160 const auto ret = m_relationVersions.try_emplace(key, 1);
12161 auto it = ret.first;
12162 if (!ret.second) {
12163 ++it->second;
12164 if (it->second == 0)
12165 it->second = 1;
12166 }
12167
12168 m_lastRelationVersionRelation = relation;
12169 m_pLastRelationVersion = &it->second;
12170 }
12171
12173 void clear_relation_caches() {
12174 if (!m_relationCachesPopulated)
12175 return;
12176 m_relationCachesPopulated = false;
12177
12178 m_targetsTravCache = {};
12179 m_srcBfsTravCache = {};
12180 m_depthOrderCache = {};
12181 m_sourcesAllCache = {};
12182 m_targetsAllCache = {};
12183 }
12184
12187 void invalidate_relation_caches(Entity relation) {
12188 touch_rel_version(relation);
12189 invalidate_queries_for_rel(relation);
12190 clear_relation_caches();
12191 }
12192
12199 bool del_pair_record(Entity entity, Archetype*& pArchetype, Entity& rel, Entity& tgt) {
12200 GAIA_ASSERT(entity.pair());
12201
12202 EntityContainer ec{};
12203 if (!m_recs.pair_record_remove(entity, ec))
12204 return false;
12205
12206 pArchetype = ec.pArchetype;
12207
12208 // The relation or target entity may already be invalid at this point. Rebuild the
12209 // lookup keys from the stored entity records instead of calling get().
12210 GAIA_ASSERT(entity.id() < m_recs.entities.size());
12211 GAIA_ASSERT(entity.gen() < m_recs.entities.size());
12212 rel = m_recs.entities.handle(entity.id());
12213 tgt = m_recs.entities.handle(entity.gen());
12214 return true;
12215 }
12216
12220 void del_pair_lookup(Entity rel, Entity tgt) {
12221 m_pairLookup.del_pair(rel, tgt);
12222 }
12223
12226 void del_entity_pair_lookup(Entity entity) {
12227 m_pairLookup.del_entity_pairs(entity);
12228 }
12229
12232 void del_pair_data_for_entity(Entity entity) {
12233 Archetype* pArchetype = nullptr;
12234
12235 if (entity.pair()) {
12236 Entity rel;
12237 Entity tgt;
12238 if (del_pair_record(entity, pArchetype, rel, tgt))
12239 del_pair_lookup(rel, tgt);
12240 } else {
12241 // Update the container record
12242 auto ec = m_recs.entities[entity.id()];
12243 m_recs.entities.free(entity);
12244
12245 // Remove all sparse-storage components from this entity.
12246 del_sparse_components(entity);
12247 // Remove all outgoing non-fragmenting exclusive relations from this source entity.
12248 del_nonfragmenting_relation_source(entity);
12249 // If the deleted entity is itself a non-fragmenting exclusive relation, drop its store.
12250 del_nonfragmenting_relation(entity);
12251 // If the deleted entity is itself a sparse-storage component, drop its store.
12252 del_sparse_component_store(entity);
12253
12254 // If this is a singleton entity its archetype needs to be deleted
12255 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0)
12256 req_del(*ec.pArchetype);
12257
12258 ec.pArchetype = nullptr;
12259 ec.pChunk = nullptr;
12260 ec.pEntity = nullptr;
12261 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::DeleteRequested, false);
12262
12263 // Update pair lookup entries.
12264 del_entity_pair_lookup(entity);
12265 }
12266
12267 del_entity_archetype_pairs(entity, pArchetype);
12268 }
12269
12272 void invalidate_entity(Entity entity) {
12273 del_graph_edges(entity);
12274 del_pair_data_for_entity(entity);
12275 }
12276
12282 void store_entity(EntityContainer& ec, Entity entity, Archetype* pArchetype, Chunk* pChunk) {
12283 GAIA_ASSERT(pArchetype != nullptr);
12284 GAIA_ASSERT(pChunk != nullptr);
12285 GAIA_ASSERT(
12286 !locked() && "Entities can't be stored while the world is locked "
12287 "(structural changes are forbidden during this time!)");
12288
12289 ec.pArchetype = pArchetype;
12290 ec.pChunk = pChunk;
12291 ec.row = pChunk->add_entity(entity);
12292 ec.pEntity = &pChunk->entity_view()[ec.row];
12293 GAIA_ASSERT(entity.pair() || ec.data.gen == entity.gen());
12294 ec.data.dis = 0;
12295 }
12296
12302 void move_entity(Entity entity, EntityContainer& ec, Archetype& dstArchetype, Chunk& dstChunk) {
12303 GAIA_PROF_SCOPE(World::move_entity);
12304
12305 auto* pDstChunk = &dstChunk;
12306 auto* pSrcChunk = ec.pChunk;
12307
12308 GAIA_ASSERT(pDstChunk != pSrcChunk);
12309
12310 const auto srcRow0 = ec.row;
12311 const auto dstRow = pDstChunk->add_entity(entity);
12312 const bool wasEnabled = !ec.data.dis;
12313
12314 auto& srcArchetype = *ec.pArchetype;
12315 const bool archetypeChanged = srcArchetype.id() != dstArchetype.id();
12316#if GAIA_ASSERT_ENABLED
12317 verify_move(*this, srcArchetype, entity);
12318#endif
12319
12320 // Make sure the old entity becomes enabled now
12321 srcArchetype.enable_entity(pSrcChunk, srcRow0, true, m_recs);
12322 // Enabling the entity might have changed its chunk index so fetch it again
12323 const auto srcRow = ec.row;
12324
12325 // Move data from the old chunk to the new one
12326 if (dstArchetype.id() == srcArchetype.id()) {
12327 pDstChunk->move_entity_data(entity, dstRow, m_recs);
12328 } else {
12329 pDstChunk->move_foreign_entity_data(pSrcChunk, srcRow, pDstChunk, dstRow);
12330 }
12331
12332 // Remove the entity record from the old chunk
12333 remove_entity(srcArchetype, *pSrcChunk, srcRow);
12334
12335 // An entity might have moved, try updating the free chunk index
12336 dstArchetype.try_update_free_chunk_idx();
12337
12338 // Bring the entity container record up-to-date
12339 ec.pArchetype = &dstArchetype;
12340 ec.pChunk = pDstChunk;
12341 ec.row = (uint16_t)dstRow;
12342 ec.pEntity = &pDstChunk->entity_view()[dstRow];
12343 if (archetypeChanged)
12344 update_src_entity_version(entity);
12345
12346 // Make the enabled state in the new chunk match the original state
12347 dstArchetype.enable_entity(pDstChunk, dstRow, wasEnabled, m_recs);
12348
12349 // End-state validation
12350 GAIA_ASSERT(valid(entity));
12351 validate_chunk(pSrcChunk);
12352 validate_chunk(pDstChunk);
12353 validate_entities();
12354 }
12355
12360 void move_entity_raw(Entity entity, EntityContainer& ec, Archetype& dstArchetype) {
12361 // Update the old chunk's world version first
12362 ec.pChunk->update_world_version();
12363 ec.pChunk->update_entity_order_version();
12364
12365 auto* pDstChunk = dstArchetype.foc_free_chunk();
12366 move_entity(entity, ec, dstArchetype, *pDstChunk);
12367
12368 // Update world versions
12369 pDstChunk->update_world_version();
12370 pDstChunk->update_entity_order_version();
12371 update_version(m_worldVersion);
12372 }
12373
12378 Chunk* move_entity(Entity entity, Archetype& dstArchetype) {
12379 // Archetypes need to be different
12380 auto& ec = fetch(entity);
12381 if (ec.pArchetype == &dstArchetype)
12382 return nullptr;
12383
12384 // Update the old chunk's world version first
12385 ec.pChunk->update_world_version();
12386 ec.pChunk->update_entity_order_version();
12387
12388 auto* pDstChunk = dstArchetype.foc_free_chunk();
12389 move_entity(entity, ec, dstArchetype, *pDstChunk);
12390
12391 // Update world versions
12392 pDstChunk->update_world_version();
12393 pDstChunk->update_entity_order_version();
12394 update_version(m_worldVersion);
12395
12396 return pDstChunk;
12397 }
12398
12401 void validate_archetype_edges([[maybe_unused]] const Archetype* pArchetype) const {
12402#if GAIA_ECS_VALIDATE_ARCHETYPE_GRAPH && GAIA_ASSERT_ENABLED
12403 GAIA_ASSERT(pArchetype != nullptr);
12404
12405 // Validate left edges
12406 const auto& archetypesLeft = pArchetype->left_edges();
12407 for (const auto& it: archetypesLeft) {
12408 const auto& edge = it.second;
12409 const auto edgeIt = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
12410 if (edgeIt == m_archetypesById.end())
12411 continue;
12412
12413 const auto entity = it.first.entity();
12414 const auto* pArchetypeRight = edgeIt->second;
12415
12416 // Edge must be found
12417 const auto edgeRight = pArchetypeRight->find_edge_right(entity);
12418 GAIA_ASSERT(edgeRight != ArchetypeIdHashPairBad);
12419
12420 // The edge must point to pArchetype
12421 const auto it2 = m_archetypesById.find(ArchetypeIdLookupKey(edgeRight.id, edgeRight.hash));
12422 GAIA_ASSERT(it2 != m_archetypesById.end());
12423 const auto* pArchetype2 = it2->second;
12424 GAIA_ASSERT(pArchetype2 == pArchetype);
12425 }
12426
12427 // Validate right edges
12428 const auto& archetypesRight = pArchetype->right_edges();
12429 for (const auto& it: archetypesRight) {
12430 const auto& edge = it.second;
12431 const auto edgeIt = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
12432 if (edgeIt == m_archetypesById.end())
12433 continue;
12434
12435 const auto entity = it.first.entity();
12436 const auto* pArchetypeRight = edgeIt->second;
12437
12438 // Edge must be found
12439 const auto edgeLeft = pArchetypeRight->find_edge_left(entity);
12440 GAIA_ASSERT(edgeLeft != ArchetypeIdHashPairBad);
12441
12442 // The edge must point to pArchetype
12443 const auto it2 = m_archetypesById.find(ArchetypeIdLookupKey(edgeLeft.id, edgeLeft.hash));
12444 GAIA_ASSERT(it2 != m_archetypesById.end());
12445 const auto* pArchetype2 = it2->second;
12446 GAIA_ASSERT(pArchetype2 == pArchetype);
12447 }
12448#endif
12449 }
12450
12452 void validate_entities() const {
12453#if GAIA_ECS_VALIDATE_ENTITY_LIST
12454 m_recs.entities.validate();
12455#endif
12456 }
12457
12460 void validate_chunk([[maybe_unused]] Chunk* pChunk) const {
12461#if GAIA_ECS_VALIDATE_CHUNKS && GAIA_ASSERT_ENABLED
12462 GAIA_ASSERT(pChunk != nullptr);
12463
12464 const auto entities = pChunk->entity_view();
12465 for (uint16_t row = 0; row < entities.size(); ++row) {
12466 const auto entity = entities[row];
12467 const EntityContainer* pEc = nullptr;
12468 if (entity.pair()) {
12469 pEc = m_recs.pair_record_find(entity);
12470 } else if (entity.id() < m_recs.entities.size()) {
12471 pEc = &m_recs.entities[entity.id()];
12472 }
12473
12474 if (pEc == nullptr) {
12475 GAIA_ASSERT(!valid(entity));
12476 continue;
12477 }
12478 if (!valid(entity)) {
12479 GAIA_ASSERT(is_req_del(*pEc));
12480 continue;
12481 }
12482 GAIA_ASSERT(pEc->pChunk == pChunk);
12483 GAIA_ASSERT(pEc->row == row);
12484 GAIA_ASSERT(pEc->pEntity == &entities[row]);
12485 }
12486
12487 for (const auto& ec: m_recs.entities) {
12488 if (ec.pChunk != pChunk)
12489 continue;
12490 const Entity entity(ec.idx, ec.data.gen, ec.data.ent != 0, false, (EntityKind)ec.data.kind);
12491 if (!valid(entity)) {
12492 GAIA_ASSERT(is_req_del(ec));
12493 continue;
12494 }
12495 GAIA_ASSERT(ec.row < entities.size());
12496 GAIA_ASSERT(entities[ec.row] == entity);
12497 GAIA_ASSERT(ec.pEntity == &entities[ec.row]);
12498 }
12499
12500 for (auto it = m_recs.pair_record_begin(); it != m_recs.pair_record_end(); ++it) {
12501 const auto& pair = *it;
12502 const auto& ec = pair.second;
12503 if (ec.pChunk != pChunk)
12504 continue;
12505 const auto entity = pair.first.entity();
12506 if (!valid(entity)) {
12507 GAIA_ASSERT(is_req_del(ec));
12508 continue;
12509 }
12510 GAIA_ASSERT(ec.row < entities.size());
12511 GAIA_ASSERT(entities[ec.row] == entity);
12512 GAIA_ASSERT(ec.pEntity == &entities[ec.row]);
12513 }
12514#endif
12515 }
12516
12523 template <bool CheckIn>
12524 GAIA_NODISCARD bool is_inter(Entity entity, Entity entityBase) const {
12525 GAIA_ASSERT(valid_entity(entity));
12526 GAIA_ASSERT(valid_entity(entityBase));
12527
12528 // Pairs are not supported
12529 if (entity.pair() || entityBase.pair())
12530 return false;
12531
12532 if constexpr (!CheckIn) {
12533 if (entity == entityBase)
12534 return true;
12535 }
12536
12537 const auto& targets = as_targets_trav_cache(entity);
12538 for (auto target: targets) {
12539 if (target == entityBase)
12540 return true;
12541 }
12542
12543 return false;
12544 }
12545
12551 template <bool CheckIn, typename Func>
12552 void as_up_trav(Entity entity, Func func) {
12553 GAIA_ASSERT(valid_entity(entity));
12554
12555 // Pairs are not supported
12556 if (entity.pair())
12557 return;
12558
12559 if constexpr (!CheckIn) {
12560 func(entity);
12561 }
12562
12563 const auto& ec = m_recs.entities[entity.id()];
12564 const auto* pArchetype = ec.pArchetype;
12565
12566 // Early exit if there are no Is relationship pairs on the archetype
12567 if (pArchetype->pairs_is() == 0)
12568 return;
12569
12570 for (uint32_t i = 0; i < pArchetype->pairs_is(); ++i) {
12571 auto e = pArchetype->entity_from_pairs_as_idx(i);
12572 const auto& ecTarget = m_recs.entities[e.gen()];
12573 auto target = *ecTarget.pEntity;
12574 func(target);
12575
12576 as_up_trav<CheckIn>(target, func);
12577 }
12578 }
12579
12585 template <typename T>
12586 const ComponentCacheItem& reg_core_entity(Entity id, Archetype* pArchetype) {
12587 auto comp = add(*pArchetype, id.entity(), id.pair(), id.kind());
12588 const auto& ci = comp_cache_mut().add<T>(id);
12589 GAIA_ASSERT(ci.entity == id);
12590 GAIA_ASSERT(comp == id);
12591 (void)comp;
12592 return ci;
12593 }
12594
12599 template <typename T>
12600 const ComponentCacheItem& reg_core_entity(Entity id) {
12601 return reg_core_entity<T>(id, m_pRootArchetype);
12602 }
12603
12609 static ComponentDesc primitive_type_desc(const char* name, uint32_t nameLen, uint32_t size) {
12610 ComponentDesc desc{};
12611 desc.name = util::str_view(name, nameLen);
12612 desc.size = size;
12613 desc.alig = size;
12614 desc.storageType = DataStorageType::Table;
12615 desc.runtimeType.typeKind = RuntimeTypeKind::Primitive;
12616 return desc;
12617 }
12618
12625 const ComponentCacheItem& reg_core_primitive_type(Entity id, const char* name, uint32_t nameLen, uint32_t size) {
12626 auto comp = add(*m_pCompArchetype, id.entity(), id.pair(), id.kind());
12627 const auto desc = primitive_type_desc(name, nameLen, size);
12628 const auto& ci = comp_cache_mut().add(id, desc);
12629 GAIA_ASSERT(ci.entity == id);
12630 GAIA_ASSERT(comp == id);
12631 (void)comp;
12632 finalize_component_registration(ci, false);
12633 return ci;
12634 }
12635
12637 void init();
12638
12640 void done() {
12641 cleanup_inter();
12642
12643#if GAIA_ECS_CHUNK_ALLOCATOR
12644 ChunkAllocator::get().flush();
12645#endif
12646 }
12647
12651 void assign_entity(Entity entity, Archetype& archetype) {
12652 GAIA_ASSERT(!entity.pair());
12653
12654 auto* pChunk = archetype.foc_free_chunk();
12655 store_entity(m_recs.entities[entity.id()], entity, &archetype, pChunk);
12656 pChunk->update_versions();
12657 archetype.try_update_free_chunk_idx();
12658
12659 // Call constructors for the generic components on the newly added entity if necessary
12660 pChunk->call_gen_ctors(pChunk->size() - 1, 1);
12661
12662#if GAIA_ASSERT_ENABLED
12663 const auto& ec = m_recs.entities[entity.id()];
12664 GAIA_ASSERT(ec.pChunk == pChunk);
12665 auto entityExpected = pChunk->entity_view()[ec.row];
12666 GAIA_ASSERT(entityExpected == entity);
12667#endif
12668 }
12669
12674 bool assign_pair_record(Entity entity, Archetype& archetype) {
12675 GAIA_ASSERT(entity.pair());
12676
12677 // Pairs are always added to m_pEntityArchetype initially and this can't change.
12678 GAIA_ASSERT(&archetype == m_pEntityArchetype);
12679
12680 if (m_recs.pair_record_contains(entity))
12681 return false;
12682
12683 // Update the container record
12684 EntityContainer ec{};
12685 ec.idx = entity.id();
12686 ec.data.gen = entity.gen();
12687 ec.data.pair = 1;
12688 ec.data.ent = 1;
12689 ec.data.kind = EntityKind::EK_Gen;
12690
12691 auto* pChunk = archetype.foc_free_chunk();
12692 store_entity(ec, entity, &archetype, pChunk);
12693 pChunk->update_versions();
12694 archetype.try_update_free_chunk_idx();
12695
12696 m_recs.pair_record_add(entity, GAIA_MOV(ec));
12697 return true;
12698 }
12699
12702 void add_pair_lookup(Entity entity) {
12703 GAIA_ASSERT(entity.pair());
12704
12705 const auto rel = get(entity.id());
12706 const auto tgt = get(entity.gen());
12707 m_pairLookup.add_pair(rel, tgt);
12708 }
12709
12713 void assign_pair(Entity entity, Archetype& archetype) {
12714 if (!assign_pair_record(entity, archetype))
12715 return;
12716
12717 add_pair_lookup(entity);
12718
12719#if GAIA_OBSERVERS_ENABLED
12720 m_observers.try_mark_term_observed(*this, entity);
12721#endif
12722 }
12723
12730 GAIA_NODISCARD Entity add(Archetype& archetype, bool isEntity, bool isPair, EntityKind kind) {
12731 EntityContainerCtx ctx{isEntity, isPair, kind};
12732 const auto entity = m_recs.entities.alloc(&ctx);
12733 assign_entity(entity, archetype);
12734 return entity;
12735 }
12736
12742 template <typename Func>
12743 void add_entity_n(Archetype& archetype, uint32_t count, Func func) {
12744 EntityContainerCtx ctx{true, false, EntityKind::EK_Gen};
12745#if GAIA_OBSERVERS_ENABLED
12746 const auto addedIds = EntitySpan{archetype.ids_view()};
12747 ObserverRegistry::DiffDispatchCtx addDiffCtx{};
12748 if (!addedIds.empty())
12749 addDiffCtx = m_observers.prepare_diff_add_new(*this, addedIds);
12750#endif
12751
12752 uint32_t left = count;
12753 do {
12754 auto* pChunk = archetype.foc_free_chunk();
12755 const uint32_t originalChunkSize = pChunk->size();
12756 const uint32_t freeSlotsInChunk = pChunk->capacity() - originalChunkSize;
12757 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
12758
12759 GAIA_FOR(toCreate) {
12760 const auto entityNew = m_recs.entities.alloc(&ctx);
12761 auto& ecNew = m_recs.entities[entityNew.id()];
12762 store_entity(ecNew, entityNew, &archetype, pChunk);
12763
12764#if GAIA_ASSERT_ENABLED
12765 GAIA_ASSERT(ecNew.pChunk == pChunk);
12766 auto entityExpected = pChunk->entity_view()[ecNew.row];
12767 GAIA_ASSERT(entityExpected == entityNew);
12768#endif
12769 }
12770
12771 // New entities were added, try updating the free chunk index
12772 archetype.try_update_free_chunk_idx();
12773
12774 // Call constructors for the generic components on the newly added entity if necessary
12775 pChunk->call_gen_ctors(originalChunkSize, toCreate);
12776
12777 // Call functors
12778 {
12779 auto entities = pChunk->entity_view();
12780 GAIA_FOR2(originalChunkSize, pChunk->size()) func(entities[i]);
12781 }
12782
12783 pChunk->update_versions();
12784
12785#if GAIA_OBSERVERS_ENABLED
12786 if (!addedIds.empty()) {
12787 auto entities = pChunk->entity_view();
12788 const auto targets = EntitySpan{entities.data() + originalChunkSize, toCreate};
12789 m_observers.add_diff_targets(*this, addDiffCtx, targets);
12790 m_observers.on_add(*this, archetype, addedIds, targets);
12791 }
12792#endif
12793
12794 left -= toCreate;
12795 } while (left > 0);
12796
12797#if GAIA_OBSERVERS_ENABLED
12798 if (!addedIds.empty())
12799 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
12800#endif
12801 }
12802
12804 void gc() {
12805 GAIA_PROF_SCOPE(World::gc);
12806
12807 del_empty_chunks();
12808 defrag_chunks(m_defragEntitiesPerTick);
12809 del_empty_archetypes();
12810 }
12811
12812 public:
12817 QuerySerBuffer& query_buffer(QueryId& serId) {
12818 // No serialization id set on the query, try creating a new record
12819 if GAIA_UNLIKELY (serId == QueryIdBad) {
12820#if GAIA_ASSERT_ENABLED
12821 uint32_t safetyCounter = 0;
12822#endif
12823
12824 while (true) {
12825#if GAIA_ASSERT_ENABLED
12826 // Make sure we don't cross some safety threshold
12827 ++safetyCounter;
12828 GAIA_ASSERT(safetyCounter < 100000);
12829#endif
12830
12831 serId = ++m_nextQuerySerId;
12832 // Make sure we do not overflow
12833 GAIA_ASSERT(serId != 0);
12834
12835 // If the id is already found, try again.
12836 // Note, this is essentially never going to repeat. We would have to prepare millions if
12837 // not billions of queries for which we only added inputs but never queried them.
12838 auto ret = m_querySerMap.try_emplace(serId);
12839 if (!ret.second)
12840 continue;
12841
12842 return ret.first->second;
12843 };
12844 }
12845
12846 return m_querySerMap[serId];
12847 }
12848
12851 void query_buffer_reset(QueryId& serId) {
12852 auto it = m_querySerMap.find(serId);
12853 if (it == m_querySerMap.end())
12854 return;
12855
12856 m_querySerMap.erase(it);
12857 serId = QueryIdBad;
12858 }
12859
12863 m_queryCache.invalidate_queries_for_entity(entityKey, QueryCache::ChangeKind::Structural);
12864 }
12865
12869 if (!m_queryCache.has_relation_query_dependencies())
12870 return;
12871
12872 m_queryCache.invalidate_queries_for_rel(relation, QueryCache::ChangeKind::DynamicResult);
12873 }
12874
12877 GAIA_NODISCARD bool has_sorted_queries() const {
12878 return m_queryCache.has_sorted_queries();
12879 }
12880
12884 GAIA_NODISCARD bool has_sorted_queries_for_entity(Entity entity) const {
12885 return m_queryCache.has_sorted_queries_for_entity(entity);
12886 }
12887
12891 m_queryCache.invalidate_sorted_queries_for_entity(entity);
12892 }
12893
12896 m_queryCache.invalidate_sorted_queries();
12897 }
12898
12902 GAIA_ASSERT(is_pair.first() == Is);
12903
12904 // We still need to handle invalidation "down-the-tree".
12905 // E.g. following setup:
12906 // q = w.query().all({Is,animal});
12907 // w.as(wolf, carnivore);
12908 // w.as(carnivore, animal);
12909 // q.each() ...; // animal, carnivore, wolf
12910 // w.del(wolf, {Is,carnivore}) // wolf is no longer a carnivore and thus no longer an animal
12911 // After this deletion, we need to invalidate "q" because wolf is no longer an animal
12912 // and we don't want q to include it.
12913 // q.each() ...; // animal
12914
12915 auto e = is_pair.second();
12916 as_up_trav<false>(e, [&](Entity target) {
12917 // Invalidate all queries that contain everything in our path.
12918 invalidate_queries_for_structural_entity(EntityLookupKey(Pair{Is, target}));
12919 });
12920 }
12921
12926 Entity name_to_entity(std::span<const char> exprRaw) const {
12927 auto expr = util::trim(exprRaw);
12928 if (expr.empty())
12929 return EntityBad;
12930
12931 if (expr[0] == '(') {
12932 if (expr.size() < 5 || expr.back() != ')')
12933 return EntityBad;
12934
12935 const auto idStr = expr.subspan(1, expr.size() - 2);
12936 const auto commaIdx = core::get_index(idStr, ',');
12937 if (commaIdx == BadIndex)
12938 return EntityBad;
12939
12940 const auto first = name_to_entity(idStr.subspan(0, commaIdx));
12941 if (first == EntityBad || first.pair())
12942 return EntityBad;
12943 const auto second = name_to_entity(idStr.subspan(commaIdx + 1));
12944 if (second == EntityBad || second.pair())
12945 return EntityBad;
12946
12947 return ecs::Pair(first, second);
12948 }
12949
12950 // Wildcard character
12951 if (expr.size() == 1 && expr[0] == '*')
12952 return All;
12953
12954 return get_inter(expr.data(), (uint32_t)expr.size());
12955 }
12956
12962 Entity expr_to_entity(va_list& args, std::span<const char> exprRaw) const {
12963 auto expr = util::trim(exprRaw);
12964
12965 if (expr[0] == '%') {
12966 if (expr[1] != 'e') {
12967 GAIA_ASSERT2(false, "Expression '%' not terminated");
12968 return EntityBad;
12969 }
12970
12971 auto id = (Identifier)va_arg(args, unsigned long long);
12972 return Entity(id);
12973 }
12974
12975 if (expr[0] == '(') {
12976 if (expr.back() != ')') {
12977 GAIA_ASSERT2(false, "Expression '(' not terminated");
12978 return EntityBad;
12979 }
12980
12981 const auto idStr = expr.subspan(1, expr.size() - 2);
12982 const auto commaIdx = core::get_index(idStr, ',');
12983
12984 const auto first = expr_to_entity(args, idStr.subspan(0, commaIdx));
12985 if (first == EntityBad)
12986 return EntityBad;
12987 const auto second = expr_to_entity(args, idStr.subspan(commaIdx + 1));
12988 if (second == EntityBad)
12989 return EntityBad;
12990
12991 return ecs::Pair(first, second);
12992 }
12993
12994 {
12995 auto idStr = util::trim(expr);
12996
12997 // Wildcard character
12998 if (idStr.size() == 1 && idStr[0] == '*')
12999 return All;
13000
13001 // Anything else is a component name
13002 const auto* pItem = resolve_component_name_inter(idStr.data(), (uint32_t)idStr.size());
13003 if (pItem == nullptr) {
13004 GAIA_ASSERT2(false, "Component not found");
13005 GAIA_LOG_W("Component '%.*s' not found", (uint32_t)idStr.size(), idStr.data());
13006 return EntityBad;
13007 }
13008
13009 return pItem->entity;
13010 }
13011 }
13012 };
13013
13015 inline ComponentRawView world_get_raw(const World& world, Entity entity, Entity component) {
13016 return world.get_raw(entity, component);
13017 }
13018
13024 inline bool world_bind_raw_sparse_store(const World& world, Entity component, RawSparseStoreOps& ops) {
13025 const auto* pStore = world.sparse_component_store_erased(component);
13026 if (pStore == nullptr)
13027 return false;
13028
13029 const auto* pItem = world.comp_cache().find(component);
13030 GAIA_ASSERT(pItem != nullptr);
13031 ops = {pStore->pStore, pStore->func_get, pStore->func_mut, pStore->func_has, pItem->comp.size()};
13032 return true;
13033 }
13034
13036 inline ComponentRawMutView world_mut_raw(World& world, Entity entity, Entity component) {
13037 return world.mut_raw(entity, component);
13038 }
13039
13041 inline ComponentRawView
13042 world_get_raw_field(const World& world, Entity entity, Entity component, uint32_t fieldIdx) {
13043 return world.get_raw_field(entity, component, fieldIdx);
13044 }
13045
13047 inline ComponentRawMutView world_mut_raw_field(World& world, Entity entity, Entity component, uint32_t fieldIdx) {
13048 return world.mut_raw_field(entity, component, fieldIdx);
13049 }
13050
13051 inline ComponentCursor World::cursor(Entity entity, Entity component) const {
13052 if (component != EntityBad && valid(entity)) {
13053 const auto owner = id_owner_inter(entity, component);
13054 if (owner != EntityBad) {
13055 const auto* pItem = component_item(owner, component);
13056 if (pItem != nullptr && soa_field_supported(*pItem)) {
13057 const auto& ec = fetch(owner);
13058 if (ec.pChunk->comp_idx(component) != ComponentIndexBad)
13059 return ComponentCursor::from_soa(*this, comp_cache(), owner, component, pItem->comp.size());
13060 }
13061 }
13062 }
13063
13064 return ComponentCursor::from_raw(comp_cache(), component, get_raw(entity, component));
13065 }
13066
13068 if (component != EntityBad && valid(entity)) {
13069 const auto& ec = fetch(entity);
13070 const auto* pItem = !is_req_del(ec) ? component_item(entity, component) : nullptr;
13071 if (pItem != nullptr && soa_field_supported(*pItem) &&
13072 core::get_index(ec.pChunk->ids_view(), component) != BadIndex)
13073 return ComponentCursor::from_soa(*this, comp_cache(), entity, component, pItem->comp.size());
13074 }
13075
13076 return ComponentCursor::from_raw(*this, comp_cache(), entity, component, mut_raw(entity, component));
13077 }
13078
13081 } // namespace ecs
13082} // namespace gaia
13083
13084#include "api.inl"
13085#if GAIA_OBSERVERS_ENABLED
13086 #include "observer_registry.inl"
13087#endif
13088
13089#include "observer.inl"
13090#include "system.inl"
13091
13092namespace gaia {
13093 namespace ecs {
13094 inline void World::init() {
13095 // Use the default serializer
13096 set_serializer(nullptr);
13097
13098 // Register the root archetype
13099 {
13100 m_pRootArchetype = create_archetype({});
13101 m_pRootArchetype->set_hashes({calc_lookup_hash({})});
13102 reg_archetype(m_pRootArchetype);
13103 }
13104
13105 (void)reg_core_entity<Core_>(Core);
13106
13107 // Entity archetype matches the root archetype for now
13108 m_pEntityArchetype = m_pRootArchetype;
13109
13110 // Register the component archetype (entity + EntityDesc + Component)
13111 {
13112 Archetype* pCompArchetype{};
13113 {
13114 const auto id = GAIA_ID(EntityDesc);
13115 const auto& ci = reg_core_entity<EntityDesc>(id);
13116 EntityBuilder(*this, id).add_inter_init(ci.entity);
13117 const auto symbol = ci.symbol_name();
13118 sset<EntityDesc>(id) = {symbol.data(), symbol.size(), nullptr, 0};
13119 pCompArchetype = m_recs.entities[id.id()].pArchetype;
13120 }
13121 {
13122 const auto id = GAIA_ID(Component);
13123 const auto& ci = reg_core_entity<Component>(id, pCompArchetype);
13124 EntityBuilder(*this, id).add_inter_init(ci.entity);
13125 const auto symbol = ci.symbol_name();
13126 acc_mut(id)
13127 // Entity descriptor
13128 .sset<EntityDesc>({symbol.data(), symbol.size(), nullptr, 0})
13129 // Component
13130 .sset<Component>(ci.comp);
13131 m_pCompArchetype = m_recs.entities[id.id()].pArchetype;
13132 }
13133 }
13134
13135 // Core components.
13136 // Their order must correspond to the value sequence in id.h.
13137 {
13138 (void)reg_core_entity<OnDelete_>(OnDelete);
13139 (void)reg_core_entity<OnDeleteTarget_>(OnDeleteTarget);
13140 (void)reg_core_entity<Remove_>(Remove);
13141 (void)reg_core_entity<Delete_>(Delete);
13142 (void)reg_core_entity<Error_>(Error);
13143 (void)reg_core_entity<Requires_>(Requires);
13144 (void)reg_core_entity<CantCombine_>(CantCombine);
13145 (void)reg_core_entity<Exclusive_>(Exclusive);
13146 (void)reg_core_entity<DontFragment_>(DontFragment);
13147 (void)reg_core_entity<Sparse_>(Sparse);
13148 (void)reg_core_entity<Acyclic_>(Acyclic);
13149 (void)reg_core_entity<Traversable_>(Traversable);
13150 (void)reg_core_entity<All_>(All);
13151 (void)reg_core_entity<ChildOf_>(ChildOf);
13152 (void)reg_core_entity<Parent_>(Parent);
13153 (void)reg_core_entity<Is_>(Is);
13154 (void)reg_core_entity<Prefab_>(Prefab);
13155 (void)reg_core_entity<OnInstantiate_>(OnInstantiate);
13156 (void)reg_core_entity<Override_>(Override);
13157 (void)reg_core_entity<Inherit_>(Inherit);
13158 (void)reg_core_entity<DontInherit_>(DontInherit);
13159 (void)reg_core_entity<System_>(System);
13160 (void)reg_core_entity<DependsOn_>(DependsOn);
13161 (void)reg_core_entity<Observer_>(Observer);
13162
13163 (void)reg_core_entity<_Var0>(Var0);
13164 (void)reg_core_entity<_Var1>(Var1);
13165 (void)reg_core_entity<_Var2>(Var2);
13166 (void)reg_core_entity<_Var3>(Var3);
13167 (void)reg_core_entity<_Var4>(Var4);
13168 (void)reg_core_entity<_Var5>(Var5);
13169 (void)reg_core_entity<_Var6>(Var6);
13170 (void)reg_core_entity<_Var7>(Var7);
13171
13172 (void)reg_core_primitive_type(S8, "gaia::ecs::S8", 13, 1);
13173 (void)reg_core_primitive_type(U8, "gaia::ecs::U8", 13, 1);
13174 (void)reg_core_primitive_type(S16, "gaia::ecs::S16", 14, 2);
13175 (void)reg_core_primitive_type(U16, "gaia::ecs::U16", 14, 2);
13176 (void)reg_core_primitive_type(S32, "gaia::ecs::S32", 14, 4);
13177 (void)reg_core_primitive_type(U32, "gaia::ecs::U32", 14, 4);
13178 (void)reg_core_primitive_type(S64, "gaia::ecs::S64", 14, 8);
13179 (void)reg_core_primitive_type(U64, "gaia::ecs::U64", 14, 8);
13180 (void)reg_core_primitive_type(Bool, "gaia::ecs::Bool", 15, 1);
13181 (void)reg_core_primitive_type(Char8, "gaia::ecs::Char8", 16, 1);
13182 (void)reg_core_primitive_type(Char16, "gaia::ecs::Char16", 17, 2);
13183 (void)reg_core_primitive_type(Char32, "gaia::ecs::Char32", 17, 4);
13184 (void)reg_core_primitive_type(F8, "gaia::ecs::F8", 13, 1);
13185 (void)reg_core_primitive_type(F16, "gaia::ecs::F16", 14, 2);
13186 (void)reg_core_primitive_type(F32, "gaia::ecs::F32", 14, 4);
13187 (void)reg_core_primitive_type(F64, "gaia::ecs::F64", 14, 8);
13188 }
13189
13190 // Add special properties for core components.
13191 // Their order must correspond to the value sequence in id.h.
13192 {
13193 EntityBuilder(*this, Core) //
13194 .add(Core)
13195 .add(Pair(OnDelete, Error));
13196 EntityBuilder(*this, GAIA_ID(EntityDesc)) //
13197 .add(Core)
13198 .add(Pair(OnDelete, Error));
13199 EntityBuilder(*this, GAIA_ID(Component)) //
13200 .add(Core)
13201 .add(Pair(OnDelete, Error));
13202 EntityBuilder(*this, OnDelete) //
13203 .add(Core)
13204 .add(Exclusive)
13205 .add(Pair(OnDelete, Error));
13206 EntityBuilder(*this, OnDeleteTarget) //
13207 .add(Core)
13208 .add(Exclusive)
13209 .add(Pair(OnDelete, Error));
13210 EntityBuilder(*this, Remove) //
13211 .add(Core)
13212 .add(Pair(OnDelete, Error));
13213 EntityBuilder(*this, Delete) //
13214 .add(Core)
13215 .add(Pair(OnDelete, Error));
13216 EntityBuilder(*this, Error) //
13217 .add(Core)
13218 .add(Pair(OnDelete, Error));
13219 EntityBuilder(*this, All) //
13220 .add(Core)
13221 .add(Pair(OnDelete, Error));
13222 EntityBuilder(*this, Requires) //
13223 .add(Core)
13224 .add(Acyclic)
13225 .add(Pair(OnDelete, Error));
13226 EntityBuilder(*this, CantCombine) //
13227 .add(Core)
13228 .add(Acyclic)
13229 .add(Pair(OnDelete, Error));
13230 EntityBuilder(*this, Exclusive) //
13231 .add(Core)
13232 .add(Pair(OnDelete, Error))
13233 .add(Acyclic);
13234 EntityBuilder(*this, DontFragment) //
13235 .add(Core)
13236 .add(Requires)
13237 .add(Pair(OnDelete, Error))
13238 .add(Acyclic);
13239 EntityBuilder(*this, Sparse) //
13240 .add(Core)
13241 .add(Requires)
13242 .add(Pair(OnDelete, Error))
13243 .add(Acyclic);
13244 EntityBuilder(*this, Acyclic) //
13245 .add(Core)
13246 .add(Pair(OnDelete, Error));
13247 EntityBuilder(*this, Traversable) //
13248 .add(Core)
13249 .add(Pair(OnDelete, Error));
13250
13251 EntityBuilder(*this, ChildOf) //
13252 .add(Core)
13253 .add(Acyclic)
13254 .add(Exclusive)
13255 .add(Traversable)
13256 .add(Pair(OnDelete, Error))
13257 .add(Pair(OnDeleteTarget, Delete));
13258 EntityBuilder(*this, Parent) //
13259 .add(Core)
13260 .add(Acyclic)
13261 .add(Exclusive)
13262 .add(DontFragment)
13263 .add(Traversable)
13264 .add(Pair(OnDelete, Error))
13265 .add(Pair(OnDeleteTarget, Delete));
13266 EntityBuilder(*this, Is) //
13267 .add(Core)
13268 .add(Acyclic)
13269 .add(Pair(OnDelete, Error));
13270 EntityBuilder(*this, Prefab) //
13271 .add(Core)
13272 .add(Pair(OnDelete, Error));
13273 EntityBuilder(*this, OnInstantiate) //
13274 .add(Core)
13275 .add(Acyclic)
13276 .add(Exclusive)
13277 .add(DontFragment)
13278 .add(Pair(OnDelete, Error));
13279 EntityBuilder(*this, Override) //
13280 .add(Core)
13281 .add(Pair(OnDelete, Error));
13282 EntityBuilder(*this, Inherit) //
13283 .add(Core)
13284 .add(Pair(OnDelete, Error));
13285 EntityBuilder(*this, DontInherit) //
13286 .add(Core)
13287 .add(Pair(OnDelete, Error));
13288
13289 EntityBuilder(*this, System) //
13290 .add(Core)
13291 .add(Acyclic)
13292 .add(Pair(OnDelete, Error));
13293 EntityBuilder(*this, DependsOn) //
13294 .add(Core)
13295 .add(Acyclic)
13296 .add(Pair(OnDelete, Error));
13297 EntityBuilder(*this, Observer) //
13298 .add(Core)
13299 .add(Acyclic)
13300 .add(Pair(OnDelete, Error));
13301
13302 EntityBuilder(*this, Var0) //
13303 .add(Core)
13304 .add(Pair(OnDelete, Error));
13305 EntityBuilder(*this, Var1) //
13306 .add(Core)
13307 .add(Pair(OnDelete, Error));
13308 EntityBuilder(*this, Var2) //
13309 .add(Core)
13310 .add(Pair(OnDelete, Error));
13311 EntityBuilder(*this, Var3) //
13312 .add(Core)
13313 .add(Pair(OnDelete, Error));
13314 EntityBuilder(*this, Var4) //
13315 .add(Core)
13316 .add(Pair(OnDelete, Error));
13317 EntityBuilder(*this, Var5) //
13318 .add(Core)
13319 .add(Pair(OnDelete, Error));
13320 EntityBuilder(*this, Var6) //
13321 .add(Core)
13322 .add(Pair(OnDelete, Error));
13323 EntityBuilder(*this, Var7) //
13324 .add(Core)
13325 .add(Pair(OnDelete, Error));
13326
13327 EntityBuilder(*this, S8) //
13328 .add(Core)
13329 .add(Pair(OnDelete, Error));
13330 EntityBuilder(*this, U8) //
13331 .add(Core)
13332 .add(Pair(OnDelete, Error));
13333 EntityBuilder(*this, S16) //
13334 .add(Core)
13335 .add(Pair(OnDelete, Error));
13336 EntityBuilder(*this, U16) //
13337 .add(Core)
13338 .add(Pair(OnDelete, Error));
13339 EntityBuilder(*this, S32) //
13340 .add(Core)
13341 .add(Pair(OnDelete, Error));
13342 EntityBuilder(*this, U32) //
13343 .add(Core)
13344 .add(Pair(OnDelete, Error));
13345 EntityBuilder(*this, S64) //
13346 .add(Core)
13347 .add(Pair(OnDelete, Error));
13348 EntityBuilder(*this, U64) //
13349 .add(Core)
13350 .add(Pair(OnDelete, Error));
13351 EntityBuilder(*this, Bool) //
13352 .add(Core)
13353 .add(Pair(OnDelete, Error));
13354 EntityBuilder(*this, Char8) //
13355 .add(Core)
13356 .add(Pair(OnDelete, Error));
13357 EntityBuilder(*this, Char16) //
13358 .add(Core)
13359 .add(Pair(OnDelete, Error));
13360 EntityBuilder(*this, Char32) //
13361 .add(Core)
13362 .add(Pair(OnDelete, Error));
13363 EntityBuilder(*this, F8) //
13364 .add(Core)
13365 .add(Pair(OnDelete, Error));
13366 EntityBuilder(*this, F16) //
13367 .add(Core)
13368 .add(Pair(OnDelete, Error));
13369 EntityBuilder(*this, F32) //
13370 .add(Core)
13371 .add(Pair(OnDelete, Error));
13372 EntityBuilder(*this, F64) //
13373 .add(Core)
13374 .add(Pair(OnDelete, Error));
13375 }
13376
13377 // Remove all archetypes with no chunks. We don't want any leftovers after
13378 // archetype movements.
13379 {
13380 for (uint32_t i = 1; i < m_archetypes.size(); ++i) {
13381 auto* pArchetype = m_archetypes[i];
13382 if (!pArchetype->chunks().empty())
13383 continue;
13384
13385 // Request deletion the standard way.
13386 // We could simply add archetypes into m_archetypesToDel but this way
13387 // we can actually replicate what the system really does on the inside
13388 // and it will require more work at the cost of easier maintenance.
13389 // The amount of archetypes cleanup is very small after init and the code
13390 // only runs after the world is created so this is not a big deal.
13391 req_del(*pArchetype);
13392 }
13393
13394 // Cleanup
13395 {
13396 del_finalize();
13397 while (!m_chunksToDel.empty() || !m_archetypesToDel.empty())
13398 gc();
13399
13400 // Make sure everything has been cleared
13401 GAIA_ASSERT(m_reqArchetypesToDel.empty());
13402 GAIA_ASSERT(m_chunksToDel.empty());
13403 GAIA_ASSERT(m_archetypesToDel.empty());
13404 }
13405
13406 sort_archetypes();
13407
13408 // Make sure archetypes have valid graphs after the cleanup
13409 for (const auto* pArchetype: m_archetypes)
13410 validate_archetype_edges(pArchetype);
13411 }
13412
13413 // Make sure archetype pointers are up-to-date
13414 m_pCompArchetype = m_recs.entities[GAIA_ID(Component).id()].pArchetype;
13415
13416#if GAIA_SYSTEMS_ENABLED
13417 // Initialize the systems query
13418 systems_init();
13419#endif
13420 }
13421
13427 inline GroupId
13428 group_by_func_default([[maybe_unused]] const World& world, const Archetype& archetype, Entity groupBy) {
13429 if (archetype.pairs() > 0) {
13430 auto ids = archetype.ids_view();
13431 for (auto id: ids) {
13432 if (!id.pair() || id.id() != groupBy.id())
13433 continue;
13434
13435 // Consider the pair's target the groupId
13436 return id.gen();
13437 }
13438 }
13439
13440 // No group
13441 return 0;
13442 }
13443
13445 inline GroupId group_by_func_depth_order(const World& world, const Archetype& archetype, Entity relation) {
13446 GAIA_ASSERT(!relation.pair());
13447
13448 // Depth ordering only makes sense for fragmenting relations whose target participates in archetype identity.
13449 // Non-fragmenting relations such as Parent must stay on per-entity traversal, because their targets vary per
13450 // entity and cannot be represented by one cached archetype depth. The level is derived from the cached upward
13451 // traversal chain so normal query iteration can stay cheap.
13452 if (!world.relation_supports_depth_order(relation) || archetype.pairs() == 0)
13453 return 0;
13454
13455 auto ids = archetype.ids_view();
13456 GroupId maxDepth = 0;
13457 bool found = false;
13458
13459 for (auto idsIdx: archetype.pair_rel_indices(relation)) {
13460 const auto pair = ids[idsIdx];
13461 const auto target = world.pair_target_if_alive(pair);
13462 if (target == EntityBad)
13463 continue;
13464
13465 const GroupId depth = GroupId(world.depth_order_cache(relation, target));
13466
13467 if (!found || depth > maxDepth) {
13468 maxDepth = depth;
13469 found = true;
13470 }
13471 }
13472
13473 return found ? maxDepth : 0;
13474 }
13475 } // namespace ecs
13476} // namespace gaia
13477
13478#if GAIA_JSON_ENABLED
13479 #include "gaia/ecs/impl/world_json.h"
13480 #include "gaia/ecs/impl/world_json_patch.h"
13481 #include "gaia/ecs/impl/world_schema_json.h"
13482#endif
13483
13484#if GAIA_SYSTEMS_ENABLED
13485namespace gaia {
13486 namespace ecs {
13487 namespace detail {
13492 GAIA_NODISCARD inline bool entity_schedule_less(Entity lhs, Entity rhs) {
13493 if (lhs.id() != rhs.id())
13494 return lhs.id() < rhs.id();
13495 return lhs.gen() < rhs.gen();
13496 }
13497
13502 GAIA_NODISCARD inline bool system_schedule_stack_contains(const cnt::darray<Entity>& stack, Entity entity) {
13503 for (auto item: stack) {
13504 if (item == entity)
13505 return true;
13506 }
13507 return false;
13508 }
13509
13516 GAIA_NODISCARD inline uint32_t
13517 system_schedule_dep_depth(World& world, Entity entity, Entity skipTarget, cnt::darray<Entity>& stack) {
13518 if (entity == EntityBad || system_schedule_stack_contains(stack, entity))
13519 return 0;
13520
13521 stack.push_back(entity);
13522 uint32_t depth = 0;
13523 world.targets(entity, DependsOn, [&](Entity target) {
13524 if (target == skipTarget)
13525 return;
13526 const auto targetDepth = system_schedule_dep_depth(world, target, EntityBad, stack) + 1;
13527 if (targetDepth > depth)
13528 depth = targetDepth;
13529 });
13530 stack.pop_back();
13531 return depth;
13532 }
13533
13538 GAIA_NODISCARD inline Entity system_phase(World& world, Entity systemEntity) {
13539 const auto phase = world.target(systemEntity, ChildOf);
13540 if (phase == EntityBad)
13541 return EntityBad;
13542 if (!world.has(systemEntity, Pair(DependsOn, phase)))
13543 return EntityBad;
13544 return phase;
13545 }
13546
13551 GAIA_NODISCARD inline SystemScheduleItem system_schedule_item(World& world, Entity systemEntity) {
13552 SystemScheduleItem item{};
13553 item.entity = systemEntity;
13554 item.phase = system_phase(world, systemEntity);
13555 item.hasPhase = item.phase != EntityBad;
13556 return item;
13557 }
13558
13561 inline void submit_pending_system_jobs(cnt::darray<PendingSystemJob>& pending) {
13562 for (auto& item: pending)
13563 item.job.submit();
13564 }
13565
13568 inline void finish_pending_system_jobs(cnt::darray<PendingSystemJob>& pending) {
13569 for (auto& item: pending)
13570 item.job.wait();
13571 for (auto& item: pending)
13572 item.job.del();
13573 pending.clear();
13574 }
13575
13578 inline void flush_pending_system_jobs(cnt::darray<PendingSystemJob>& pending) {
13579 submit_pending_system_jobs(pending);
13580 finish_pending_system_jobs(pending);
13581 }
13582
13586 GAIA_NODISCARD inline bool sched_supports_deferred_system_jobs(const Sched& sched) {
13587 const auto& resolved = sched_resolve(sched);
13588 return resolved.add != nullptr && resolved.add_par != nullptr && resolved.submit != nullptr &&
13589 resolved.dep != nullptr && resolved.wait != nullptr && resolved.del != nullptr;
13590 }
13591
13595 inline void
13596 system_schedule_entity_indices(const cnt::darray<SystemScheduleItem>& items, cnt::darray<uint32_t>& outIndices) {
13597 outIndices.clear();
13598 outIndices.reserve(items.size());
13599 for (uint32_t i = 0; i < items.size(); ++i)
13600 outIndices.push_back(i);
13601 core::sort(outIndices, [&](uint32_t lhs, uint32_t rhs) {
13602 return entity_schedule_less(items[lhs].entity, items[rhs].entity);
13603 });
13604 }
13605
13611 GAIA_NODISCARD inline uint32_t system_schedule_find_item_by_entity(
13612 const cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices, Entity entity) {
13613 uint32_t lo = 0;
13614 uint32_t hi = entityIndices.size();
13615 while (lo < hi) {
13616 const uint32_t mid = lo + (hi - lo) / 2;
13617 const auto midEntity = items[entityIndices[mid]].entity;
13618 if (midEntity == entity)
13619 return entityIndices[mid];
13620 if (entity_schedule_less(midEntity, entity))
13621 lo = mid + 1;
13622 else
13623 hi = mid;
13624 }
13625 return UINT32_MAX;
13626 }
13627
13632 GAIA_NODISCARD inline bool
13633 system_schedule_same_group(const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
13634 if (lhs.hasPhase != rhs.hasPhase)
13635 return false;
13636 if (!lhs.hasPhase)
13637 return true;
13638 return lhs.phase == rhs.phase;
13639 }
13640
13647 GAIA_NODISCARD inline uint32_t system_schedule_primary_target_idx(
13648 World& world, const cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices,
13649 uint32_t itemIdx) {
13650 uint32_t bestIdx = UINT32_MAX;
13651 uint32_t bestDepth = 0;
13652 const auto& item = items[itemIdx];
13653 world.targets(item.entity, DependsOn, [&](Entity target) {
13654 if (target == item.phase)
13655 return;
13656
13657 const auto targetIdx = system_schedule_find_item_by_entity(items, entityIndices, target);
13658 if (targetIdx == UINT32_MAX)
13659 return;
13660 if (!system_schedule_same_group(item, items[targetIdx]))
13661 return;
13662
13663 const auto targetDepth = items[targetIdx].systemDepth;
13664 if (bestIdx == UINT32_MAX || targetDepth > bestDepth ||
13665 (targetDepth == bestDepth && entity_schedule_less(items[targetIdx].entity, items[bestIdx].entity))) {
13666 bestIdx = targetIdx;
13667 bestDepth = targetDepth;
13668 }
13669 });
13670 return bestIdx;
13671 }
13672
13680 inline void system_schedule_visit_primary_children(
13681 cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& firstChildren,
13682 const cnt::darray<uint32_t>& nextSiblings, cnt::darray<uint8_t>& states, uint32_t itemIdx, uint32_t& order) {
13683 if (states[itemIdx] != 0)
13684 return;
13685
13686 states[itemIdx] = 1;
13687 for (uint32_t childIdx = firstChildren[itemIdx]; childIdx != UINT32_MAX; childIdx = nextSiblings[childIdx])
13688 system_schedule_visit_primary_children(items, firstChildren, nextSiblings, states, childIdx, order);
13689 items[itemIdx].systemOrder = order++;
13690 states[itemIdx] = 2;
13691 }
13692
13698 inline void system_schedule_assign_group_orders(
13699 World& world, cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& groupIndices,
13700 const cnt::darray<uint32_t>& entityIndices, SystemScheduleScratch& scratch) {
13701 auto& sortedGroupIndices = scratch.sortedGroupIndices;
13702 auto& primaryTargets = scratch.primaryTargets;
13703 auto& firstChildren = scratch.firstChildren;
13704 auto& nextSiblings = scratch.nextSiblings;
13705 auto& states = scratch.states;
13706
13707 sortedGroupIndices = groupIndices;
13708 core::sort(sortedGroupIndices, [&](uint32_t lhs, uint32_t rhs) {
13709 return entity_schedule_less(items[lhs].entity, items[rhs].entity);
13710 });
13711
13712 primaryTargets.clear();
13713 primaryTargets.resize(items.size(), UINT32_MAX);
13714 for (auto itemIdx: groupIndices)
13715 primaryTargets[itemIdx] = system_schedule_primary_target_idx(world, items, entityIndices, itemIdx);
13716
13717 firstChildren.clear();
13718 nextSiblings.clear();
13719 firstChildren.resize(items.size(), UINT32_MAX);
13720 nextSiblings.resize(items.size(), UINT32_MAX);
13721 for (uint32_t i = sortedGroupIndices.size(); i > 0; --i) {
13722 const auto itemIdx = sortedGroupIndices[i - 1];
13723 const auto targetIdx = primaryTargets[itemIdx];
13724 if (targetIdx == UINT32_MAX)
13725 continue;
13726 nextSiblings[itemIdx] = firstChildren[targetIdx];
13727 firstChildren[targetIdx] = itemIdx;
13728 }
13729
13730 states.clear();
13731 states.resize(items.size(), 0);
13732
13733 uint32_t order = 0;
13734 for (auto itemIdx: sortedGroupIndices) {
13735 if (states[itemIdx] == 0 && primaryTargets[itemIdx] == UINT32_MAX) {
13736 system_schedule_visit_primary_children(items, firstChildren, nextSiblings, states, itemIdx, order);
13737 }
13738 }
13739
13740 // Remaining items are in a DependsOn cycle. The cycle is invalid, but the update stays deterministic.
13741 for (auto itemIdx: sortedGroupIndices) {
13742 if (states[itemIdx] == 0) {
13743 items[itemIdx].systemOrder = order++;
13744 states[itemIdx] = 2;
13745 }
13746 }
13747 }
13748
13753 GAIA_NODISCARD inline uint32_t
13754 system_schedule_find_phase(const cnt::darray<SystemPhaseScheduleItem>& phases, Entity phase) {
13755 for (uint32_t i = 0; i < phases.size(); ++i) {
13756 if (phases[i].phase == phase)
13757 return i;
13758 }
13759 return UINT32_MAX;
13760 }
13761
13767 GAIA_NODISCARD inline uint32_t system_schedule_primary_phase_idx(
13768 World& world, const cnt::darray<SystemPhaseScheduleItem>& phases, uint32_t phaseIdx) {
13769 uint32_t bestIdx = UINT32_MAX;
13770 uint32_t bestDepth = 0;
13771 world.targets(phases[phaseIdx].phase, DependsOn, [&](Entity target) {
13772 const auto targetIdx = system_schedule_find_phase(phases, target);
13773 if (targetIdx == UINT32_MAX)
13774 return;
13775
13776 const auto targetDepth = phases[targetIdx].depth;
13777 if (bestIdx == UINT32_MAX || targetDepth > bestDepth ||
13778 (targetDepth == bestDepth && entity_schedule_less(phases[targetIdx].phase, phases[bestIdx].phase))) {
13779 bestIdx = targetIdx;
13780 bestDepth = targetDepth;
13781 }
13782 });
13783 return bestIdx;
13784 }
13785
13793 inline void system_schedule_visit_phase_children(
13794 cnt::darray<SystemPhaseScheduleItem>& phases, const cnt::darray<uint32_t>& firstChildren,
13795 const cnt::darray<uint32_t>& nextSiblings, cnt::darray<uint8_t>& states, uint32_t phaseIdx, uint32_t& order) {
13796 if (states[phaseIdx] != 0)
13797 return;
13798
13799 states[phaseIdx] = 1;
13800 for (uint32_t childIdx = firstChildren[phaseIdx]; childIdx != UINT32_MAX; childIdx = nextSiblings[childIdx])
13801 system_schedule_visit_phase_children(phases, firstChildren, nextSiblings, states, childIdx, order);
13802 phases[phaseIdx].order = order++;
13803 states[phaseIdx] = 2;
13804 }
13805
13810 inline void system_schedule_assign_phase_orders(
13811 World& world, cnt::darray<SystemPhaseScheduleItem>& phases, SystemScheduleScratch& scratch) {
13812 // Phase ordering has two layers:
13813 // 1. A cheap DFS postorder over each phase's primary DependsOn target. This gives stable depth/path
13814 // keys and a deterministic fallback for cycles.
13815 // 2. A direct DependsOn topological pass. This preserves all explicit phase targets, including
13816 // multi-target dependencies that the primary-target path cannot represent by itself.
13817 auto& sortedPhases = scratch.sortedPhases;
13818 auto& primaryPhases = scratch.primaryPhases;
13819 auto& firstChildren = scratch.firstChildren;
13820 auto& nextSiblings = scratch.nextSiblings;
13821 auto& states = scratch.states;
13822
13823 // Work in entity-id order whenever the graph does not force a different order. That keeps
13824 // independent phases deterministic and makes cycle fallback stable.
13825 sortedPhases.clear();
13826 sortedPhases.reserve(phases.size());
13827 for (uint32_t i = 0; i < phases.size(); ++i)
13828 sortedPhases.push_back(i);
13829 core::sort(sortedPhases, [&](uint32_t lhs, uint32_t rhs) {
13830 return entity_schedule_less(phases[lhs].phase, phases[rhs].phase);
13831 });
13832
13833 // Collapse each phase's dependency path to one primary target for the DFS ordering key. Direct
13834 // multi-target dependencies are handled later by the topological pass.
13835 primaryPhases.clear();
13836 primaryPhases.resize(phases.size(), UINT32_MAX);
13837 for (uint32_t i = 0; i < phases.size(); ++i)
13838 primaryPhases[i] = system_schedule_primary_phase_idx(world, phases, i);
13839
13840 // Build primary-target adjacency once, then walk it instead of repeatedly scanning all phases for
13841 // children. Reverse insertion preserves sorted child traversal.
13842 firstChildren.clear();
13843 nextSiblings.clear();
13844 firstChildren.resize(phases.size(), UINT32_MAX);
13845 nextSiblings.resize(phases.size(), UINT32_MAX);
13846 for (uint32_t i = sortedPhases.size(); i > 0; --i) {
13847 const auto phaseIdx = sortedPhases[i - 1];
13848 const auto targetIdx = primaryPhases[phaseIdx];
13849 if (targetIdx == UINT32_MAX)
13850 continue;
13851 nextSiblings[phaseIdx] = firstChildren[targetIdx];
13852 firstChildren[targetIdx] = phaseIdx;
13853 }
13854
13855 states.clear();
13856 states.resize(phases.size(), 0);
13857
13858 // Assign the primary DFS order from roots first. Any phase left unvisited participates in a
13859 // primary-target cycle, so it receives a deterministic entity-order fallback below.
13860 uint32_t order = 0;
13861 for (auto phaseIdx: sortedPhases) {
13862 if (states[phaseIdx] == 0 && primaryPhases[phaseIdx] == UINT32_MAX) {
13863 system_schedule_visit_phase_children(phases, firstChildren, nextSiblings, states, phaseIdx, order);
13864 }
13865 }
13866
13867 // Remaining phases are in a DependsOn cycle. The cycle is invalid, but the update stays deterministic.
13868 for (auto phaseIdx: sortedPhases) {
13869 if (states[phaseIdx] == 0) {
13870 phases[phaseIdx].order = order++;
13871 states[phaseIdx] = 2;
13872 }
13873 }
13874
13875 auto& edges = scratch.edges;
13876 auto& childCounts = scratch.childCounts;
13877 auto& firstEdges = scratch.firstEdges;
13878 auto& readyNext = scratch.readyNext;
13879 auto& ordered = scratch.sortedIndices;
13880 auto& visited = scratch.visited;
13881
13882 edges.clear();
13883 childCounts.clear();
13884 childCounts.resize(phases.size(), 0);
13885
13886 // Build explicit phase edges from the actual DependsOn pairs. We count incoming children per
13887 // target so a phase becomes ready only after all phases depending on it have been emitted.
13888 for (uint32_t phaseIdx = 0; phaseIdx < phases.size(); ++phaseIdx) {
13889 world.targets(phases[phaseIdx].phase, DependsOn, [&](Entity target) {
13890 const auto targetIdx = system_schedule_find_phase(phases, target);
13891 if (targetIdx == UINT32_MAX || targetIdx == phaseIdx)
13892 return;
13893
13894 SystemScheduleEdge edge{};
13895 edge.child = phaseIdx;
13896 edge.target = targetIdx;
13897 edges.push_back(edge);
13898 ++childCounts[targetIdx];
13899 });
13900 }
13901 if (edges.empty())
13902 return;
13903
13904 // Link outgoing edges per child phase. This lets the ready-list pass touch only edges affected by
13905 // the phase it just emitted.
13906 firstEdges.clear();
13907 firstEdges.resize(phases.size(), UINT32_MAX);
13908 for (uint32_t edgeIdx = edges.size(); edgeIdx > 0; --edgeIdx) {
13909 auto& edge = edges[edgeIdx - 1];
13910 edge.next = firstEdges[edge.child];
13911 firstEdges[edge.child] = edgeIdx - 1;
13912 }
13913
13914 auto phase_less = [&](uint32_t lhs, uint32_t rhs) {
13915 const auto& lhsPhase = phases[lhs];
13916 const auto& rhsPhase = phases[rhs];
13917 if (lhsPhase.order != rhsPhase.order)
13918 return lhsPhase.order < rhsPhase.order;
13919 if (lhsPhase.depth != rhsPhase.depth)
13920 return lhsPhase.depth > rhsPhase.depth;
13921 return entity_schedule_less(lhsPhase.phase, rhsPhase.phase);
13922 };
13923 auto ready_insert = [&](uint32_t& readyHead, uint32_t phaseIdx) {
13924 uint32_t* ppCurr = &readyHead;
13925 while (*ppCurr != UINT32_MAX && phase_less(*ppCurr, phaseIdx))
13926 ppCurr = &readyNext[*ppCurr];
13927 readyNext[phaseIdx] = *ppCurr;
13928 *ppCurr = phaseIdx;
13929 };
13930
13931 readyNext.clear();
13932 readyNext.resize(phases.size(), UINT32_MAX);
13933
13934 // Kahn pass in Gaia's scheduler direction: children run before their DependsOn targets. The
13935 // ordered ready list preserves the primary DFS order whenever several phases are unblocked.
13936 uint32_t readyHead = UINT32_MAX;
13937 for (auto phaseIdx: sortedPhases) {
13938 if (childCounts[phaseIdx] == 0)
13939 ready_insert(readyHead, phaseIdx);
13940 }
13941
13942 ordered.clear();
13943 ordered.reserve(phases.size());
13944 while (readyHead != UINT32_MAX) {
13945 const auto phaseIdx = readyHead;
13946 readyHead = readyNext[readyHead];
13947 ordered.push_back(phaseIdx);
13948
13949 for (uint32_t edgeIdx = firstEdges[phaseIdx]; edgeIdx != UINT32_MAX; edgeIdx = edges[edgeIdx].next) {
13950 const auto targetIdx = edges[edgeIdx].target;
13951 GAIA_ASSERT(childCounts[targetIdx] > 0);
13952 --childCounts[targetIdx];
13953 if (childCounts[targetIdx] == 0)
13954 ready_insert(readyHead, targetIdx);
13955 }
13956 }
13957
13958 if (ordered.size() != phases.size()) {
13959 // A direct phase dependency cycle cannot be topologically sorted. Keep the acyclic prefix and
13960 // append the remaining phases in entity order so execution is still deterministic.
13961 visited.clear();
13962 visited.resize(phases.size(), 0);
13963 for (auto phaseIdx: ordered)
13964 visited[phaseIdx] = 1;
13965 for (auto phaseIdx: sortedPhases) {
13966 if (visited[phaseIdx] == 0)
13967 ordered.push_back(phaseIdx);
13968 }
13969 }
13970
13971 for (uint32_t i = 0; i < ordered.size(); ++i)
13972 phases[ordered[i]].order = i;
13973 }
13974
13979 inline void system_schedule_assign_order_keys(
13980 World& world, cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices,
13981 SystemScheduleScratch& scratch) {
13982 auto& stack = scratch.entityStack;
13983 auto& phases = scratch.phases;
13984 auto& groupIndices = scratch.groupIndices;
13985
13986 for (auto& item: items) {
13987 stack.clear();
13988 item.systemDepth = system_schedule_dep_depth(world, item.entity, item.phase, stack);
13989 }
13990
13991 phases.clear();
13992 for (auto& item: items) {
13993 if (!item.hasPhase || system_schedule_find_phase(phases, item.phase) != UINT32_MAX)
13994 continue;
13995 SystemPhaseScheduleItem phaseItem{};
13996 phaseItem.phase = item.phase;
13997 stack.clear();
13998 phaseItem.depth = system_schedule_dep_depth(world, phaseItem.phase, EntityBad, stack);
13999 phases.push_back(phaseItem);
14000 }
14001
14002 system_schedule_assign_phase_orders(world, phases, scratch);
14003 for (auto& item: items) {
14004 if (!item.hasPhase)
14005 continue;
14006 const auto phaseIdx = system_schedule_find_phase(phases, item.phase);
14007 if (phaseIdx == UINT32_MAX)
14008 continue;
14009 item.phaseOrder = phases[phaseIdx].order;
14010 item.phaseDepth = phases[phaseIdx].depth;
14011 }
14012
14013 groupIndices.clear();
14014 groupIndices.reserve(items.size());
14015 for (uint32_t i = 0; i < items.size(); ++i) {
14016 if (!items[i].hasPhase)
14017 groupIndices.push_back(i);
14018 }
14019 if (!groupIndices.empty())
14020 system_schedule_assign_group_orders(world, items, groupIndices, entityIndices, scratch);
14021
14022 for (auto& phase: phases) {
14023 groupIndices.clear();
14024 for (uint32_t i = 0; i < items.size(); ++i) {
14025 if (items[i].phase == phase.phase)
14026 groupIndices.push_back(i);
14027 }
14028 if (!groupIndices.empty())
14029 system_schedule_assign_group_orders(world, items, groupIndices, entityIndices, scratch);
14030 }
14031 }
14032
14042 GAIA_NODISCARD inline bool system_schedule_less(const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
14043 if (lhs.hasPhase != rhs.hasPhase)
14044 return lhs.hasPhase;
14045
14046 if (lhs.hasPhase && lhs.phase != rhs.phase) {
14047 if (lhs.phaseOrder != rhs.phaseOrder)
14048 return lhs.phaseOrder < rhs.phaseOrder;
14049 if (lhs.phaseDepth != rhs.phaseDepth)
14050 return lhs.phaseDepth > rhs.phaseDepth;
14051 return entity_schedule_less(lhs.phase, rhs.phase);
14052 }
14053
14054 if (lhs.systemOrder != rhs.systemOrder)
14055 return lhs.systemOrder < rhs.systemOrder;
14056 if (lhs.systemDepth != rhs.systemDepth)
14057 return lhs.systemDepth > rhs.systemDepth;
14058 return entity_schedule_less(lhs.entity, rhs.entity);
14059 }
14060
14066 inline void system_schedule_add_edge(
14067 cnt::darray<SystemScheduleEdge>& edges, cnt::darray<uint32_t>& childCounts, uint32_t childIdx,
14068 uint32_t targetIdx) {
14069 SystemScheduleEdge edge{};
14070 edge.child = childIdx;
14071 edge.target = targetIdx;
14072 edges.push_back(edge);
14073 ++childCounts[targetIdx];
14074 }
14075
14082 inline void system_schedule_build_edges(
14083 World& world, const cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices,
14084 cnt::darray<SystemScheduleEdge>& edges, cnt::darray<uint32_t>& childCounts) {
14085 childCounts.resize(items.size(), 0);
14086 for (uint32_t childIdx = 0; childIdx < items.size(); ++childIdx) {
14087 const auto& child = items[childIdx];
14088 world.targets(child.entity, DependsOn, [&](Entity target) {
14089 if (target == child.phase)
14090 return;
14091 const auto targetIdx = system_schedule_find_item_by_entity(items, entityIndices, target);
14092 if (targetIdx == UINT32_MAX || targetIdx == childIdx)
14093 return;
14094 if (!system_schedule_same_group(child, items[targetIdx]))
14095 return;
14096
14097 system_schedule_add_edge(edges, childCounts, childIdx, targetIdx);
14098 });
14099 }
14100 }
14101
14107 inline void system_schedule_ready_insert(
14108 const cnt::darray<SystemScheduleItem>& items, cnt::darray<uint32_t>& readyNext, uint32_t& readyHead,
14109 uint32_t itemIdx) {
14110 uint32_t* ppCurr = &readyHead;
14111 while (*ppCurr != UINT32_MAX && system_schedule_less(items[*ppCurr], items[itemIdx]))
14112 ppCurr = &readyNext[*ppCurr];
14113 readyNext[itemIdx] = *ppCurr;
14114 *ppCurr = itemIdx;
14115 }
14116
14124 inline void order_system_schedule_items(
14125 World& world, cnt::darray<SystemScheduleItem>& items, SystemScheduleScratch& scratch) {
14126 auto& entityIndices = scratch.entityIndices;
14127 auto& edges = scratch.edges;
14128 auto& childCounts = scratch.childCounts;
14129 auto& sortedIndices = scratch.sortedIndices;
14130 auto& firstEdges = scratch.firstEdges;
14131 auto& ordered = scratch.ordered;
14132 auto& visited = scratch.visited;
14133 auto& readyNext = scratch.readyNext;
14134
14135 system_schedule_entity_indices(items, entityIndices);
14136 system_schedule_assign_order_keys(world, items, entityIndices, scratch);
14137
14138 edges.clear();
14139 childCounts.clear();
14140 edges.reserve(items.size());
14141 system_schedule_build_edges(world, items, entityIndices, edges, childCounts);
14142 if (edges.empty()) {
14143 core::sort(items, [](const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
14144 return system_schedule_less(lhs, rhs);
14145 });
14146 return;
14147 }
14148
14149 sortedIndices.clear();
14150 sortedIndices.reserve(items.size());
14151 for (uint32_t i = 0; i < items.size(); ++i)
14152 sortedIndices.push_back(i);
14153 core::sort(sortedIndices, [&](uint32_t lhs, uint32_t rhs) {
14154 return system_schedule_less(items[lhs], items[rhs]);
14155 });
14156
14157 firstEdges.clear();
14158 firstEdges.resize(items.size(), UINT32_MAX);
14159 for (uint32_t i = edges.size(); i > 0; --i) {
14160 auto& edge = edges[i - 1];
14161 edge.next = firstEdges[edge.child];
14162 firstEdges[edge.child] = i - 1;
14163 }
14164
14165 ordered.clear();
14166 ordered.reserve(items.size());
14167
14168 visited.clear();
14169 visited.resize(items.size(), 0);
14170 readyNext.clear();
14171 readyNext.resize(items.size(), UINT32_MAX);
14172
14173 uint32_t readyHead = UINT32_MAX;
14174 for (uint32_t i = sortedIndices.size(); i > 0; --i) {
14175 const auto itemIdx = sortedIndices[i - 1];
14176 if (childCounts[itemIdx] == 0) {
14177 readyNext[itemIdx] = readyHead;
14178 readyHead = itemIdx;
14179 }
14180 }
14181
14182 while (ordered.size() < items.size()) {
14183 uint32_t bestIdx = readyHead;
14184 if (bestIdx != UINT32_MAX)
14185 readyHead = readyNext[bestIdx];
14186 if (bestIdx == UINT32_MAX) {
14187 for (auto itemIdx: sortedIndices) {
14188 if (visited[itemIdx] == 0) {
14189 bestIdx = itemIdx;
14190 break;
14191 }
14192 }
14193 }
14194
14195 GAIA_ASSERT(bestIdx != UINT32_MAX);
14196 if (bestIdx == UINT32_MAX)
14197 break;
14198
14199 visited[bestIdx] = 1;
14200 ordered.push_back(items[bestIdx]);
14201
14202 for (uint32_t edgeIdx = firstEdges[bestIdx]; edgeIdx != UINT32_MAX; edgeIdx = edges[edgeIdx].next) {
14203 const auto targetIdx = edges[edgeIdx].target;
14204 if (visited[targetIdx] != 0)
14205 continue;
14206 GAIA_ASSERT(childCounts[targetIdx] > 0);
14207 if (childCounts[targetIdx] > 0) {
14208 --childCounts[targetIdx];
14209 if (childCounts[targetIdx] == 0)
14210 system_schedule_ready_insert(items, readyNext, readyHead, targetIdx);
14211 }
14212 }
14213 }
14214
14215 items = ordered;
14216 }
14217
14222 GAIA_NODISCARD inline bool
14223 system_schedule_batch_changed(const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
14224 if (lhs.hasPhase != rhs.hasPhase)
14225 return true;
14226 if (lhs.hasPhase)
14227 return lhs.phase != rhs.phase || lhs.systemDepth != rhs.systemDepth;
14228 return lhs.systemDepth != rhs.systemDepth;
14229 }
14230
14234 GAIA_NODISCARD inline bool system_exec_uses_scheduler(QueryExecType type) {
14235 return type == QueryExecType::Parallel || type == QueryExecType::ParallelPerf ||
14236 type == QueryExecType::ParallelEff;
14237 }
14238
14242 inline void run_system_entity_erased(void* pCtx, const SystemScheduleItem& item) {
14243 auto& ctx = *static_cast<SystemRunCtx*>(pCtx);
14244 GAIA_ASSERT(ctx.pWorld != nullptr);
14245 GAIA_ASSERT(ctx.pPending != nullptr);
14246 if (ctx.pWorld == nullptr || ctx.pPending == nullptr)
14247 return;
14248
14249 auto& world = *ctx.pWorld;
14250 auto& pending = *ctx.pPending;
14251 const auto systemEntity = item.entity;
14252 if (!world.valid(systemEntity) || !world.has(systemEntity, System))
14253 return;
14254 if (!world.enabled_hierarchy(systemEntity, ChildOf))
14255 return;
14256
14257 if (!ctx.hasCurrent) {
14258 ctx.current = item;
14259 ctx.hasCurrent = true;
14260 } else if (system_schedule_batch_changed(ctx.current, item)) {
14261 flush_pending_system_jobs(pending);
14262 ctx.current = item;
14263 }
14264
14265 auto ss = world.acc_mut(systemEntity);
14266 auto& sys = ss.smut<ecs::System_>();
14267 if (!ctx.canScheduleSystems || !system_exec_uses_scheduler(sys.execType) || sys.query.main_thread_required()) {
14268 flush_pending_system_jobs(pending);
14269 sys.exec(world);
14270 return;
14271 }
14272
14273 auto job = sys.job(world);
14274 if (!job.valid())
14275 return;
14276
14277 for (auto& pendingJob: pending) {
14278 if (!world.valid(pendingJob.entity) || !world.has(pendingJob.entity, System))
14279 continue;
14280
14281 auto prevSs = world.acc_mut(pendingJob.entity);
14282 auto& prevSys = prevSs.smut<ecs::System_>();
14283 if (!prevSys.query.can_run_parallel(sys.query))
14284 job.dep(pendingJob.job);
14285 }
14286
14287 pending.emplace_back(systemEntity, GAIA_MOV(job));
14288 }
14289
14293 inline void collect_system_schedule_item_erased(void* pCtx, Entity systemEntity) {
14294 auto& ctx = *static_cast<SystemCollectCtx*>(pCtx);
14295 GAIA_ASSERT(ctx.pWorld != nullptr);
14296 GAIA_ASSERT(ctx.pItems != nullptr);
14297 if (ctx.pWorld == nullptr || ctx.pItems == nullptr)
14298 return;
14299 ctx.pItems->push_back(system_schedule_item(*ctx.pWorld, systemEntity));
14300 }
14301
14303 inline void collect_system_entity_erased(void* pCtx, Entity systemEntity) {
14304 auto& out = *static_cast<cnt::darray<Entity>*>(pCtx);
14305 out.push_back(systemEntity);
14306 }
14307 } // namespace detail
14308
14309 inline void World::systems_init() {
14310 m_systemsQuery = query().all(System);
14311 }
14312
14313 inline void World::systems_run() {
14314 if GAIA_UNLIKELY (tearing_down())
14315 return;
14316
14317 auto& items = m_systemScheduleScratch.items;
14318 items.clear();
14319
14320 detail::SystemCollectCtx collectCtx{};
14321 collectCtx.pWorld = this;
14322 collectCtx.pItems = &items;
14323 m_systemsQuery.each_entity_enabled(&collectCtx, detail::collect_system_schedule_item_erased);
14324 detail::order_system_schedule_items(*this, items, m_systemScheduleScratch);
14325
14326 cnt::darray<detail::PendingSystemJob> pending;
14327 detail::SystemRunCtx ctx{};
14328 ctx.pWorld = this;
14329 ctx.pPending = &pending;
14330 ctx.canScheduleSystems = detail::sched_supports_deferred_system_jobs(world_sched(*this));
14331
14332 for (auto& item: items)
14333 detail::run_system_entity_erased(&ctx, item);
14334 detail::flush_pending_system_jobs(pending);
14335 }
14336
14337 inline void World::systems_done() {
14338 cnt::darray<Entity> tmpEntities;
14339 m_systemsQuery.each_entity_enabled(&tmpEntities, detail::collect_system_entity_erased);
14340
14341 // Wait for every outstanding system job before mutating any system runtime state.
14342 // This keeps dependency chains intact while jobs are still live.
14343 for (auto entity: tmpEntities) {
14344 if (!valid(entity) || !has(entity, System))
14345 continue;
14346
14347 auto ss = acc_mut(entity);
14348 auto& sys = ss.smut<ecs::System_>();
14349 if (sys.jobHandle != (mt::JobHandle)mt::JobNull_t{}) {
14350 auto& tp = mt::ThreadPool::get();
14351 tp.wait(sys.jobHandle);
14352 }
14353 }
14354
14355 // With all system jobs complete we can release their runtime state safely.
14356 for (auto entity: tmpEntities) {
14357 if (!valid(entity) || !has(entity, System))
14358 continue;
14359
14360 auto ss = acc_mut(entity);
14361 auto& sys = ss.smut<ecs::System_>();
14362 if (sys.jobHandle != (mt::JobHandle)mt::JobNull_t{}) {
14363 auto& tp = mt::ThreadPool::get();
14364 tp.del(sys.jobHandle);
14365 sys.jobHandle = mt::JobNull;
14366 }
14367 sys.query = {};
14368 }
14369
14370 m_systemsQuery = {};
14371 tmpEntities.clear();
14372 }
14373
14374 inline SystemBuilder World::system() {
14375 // Create the system
14376 auto e = add();
14377 EntityBuilder(*this, e) //
14378 .add<System_>();
14379
14380 auto ss = acc_mut(e);
14381 auto& sys = ss.smut<System_>();
14382 auto& sysRuntime = systems().data_add(e);
14383 {
14384 sys.entity = e;
14385 sys.query = query();
14386 sysRuntime.on_each_func = {};
14387 }
14388 return SystemBuilder(*this, e);
14389 }
14390 } // namespace ecs
14391} // namespace gaia
14392#endif
14393
14394namespace gaia {
14395 namespace ecs {
14397 inline uint32_t world_version(const World& world) {
14398 return world.m_worldVersion;
14399 }
14400
14402 inline uint32_t world_archetype_delete_version(const World& world) {
14403 return world.m_archetypeDeleteVersion;
14404 }
14405
14409 inline const Sched& world_sched(const World& world) {
14410 return world.sched();
14411 }
14412
14419 inline void
14420 world_for_each_target(const World& world, Entity entity, Entity relation, void* ctx, void (*func)(void*, Entity)) {
14421 world.targets(entity, relation, [ctx, func](Entity target) {
14422 func(ctx, target);
14423 });
14424 }
14425
14429 inline QueryMatchScratch& query_match_scratch_acquire(World& world) {
14430 if (world.m_queryMatchScratchDepth == world.m_queryMatchScratchStack.size())
14431 world.m_queryMatchScratchStack.push_back(new QueryMatchScratch());
14432
14433 auto& scratch = *world.m_queryMatchScratchStack[world.m_queryMatchScratchDepth++];
14434 scratch.clear_temporary_matches();
14435 return scratch;
14436 }
14437
14441 inline void query_match_scratch_release(World& world, bool keepStamps) {
14442 GAIA_ASSERT(world.m_queryMatchScratchDepth > 0);
14443 auto& scratch = *world.m_queryMatchScratchStack[--world.m_queryMatchScratchDepth];
14444 if (keepStamps)
14445 scratch.clear_temporary_matches_keep_stamps();
14446 else
14447 scratch.clear_temporary_matches();
14448 }
14449
14453 GAIA_FORCEINLINE void world_invalidate_sorted_queries_for_entity(World& world, Entity entity) {
14454 // Sorting is rarely used; one predictable branch keeps the hot write path free of the
14455 // invalidation machinery for both serial and parallel writes.
14456 if GAIA_LIKELY (!world.has_sorted_queries())
14457 return;
14458
14459 if (world.defer_sort_inv_active()) {
14460 // Only record entities a sorted query actually depends on, so the defer queue stays
14461 // empty for the common case where no component carried a sorted query.
14462 if (!world.has_sorted_queries_for_entity(entity))
14463 return;
14464 const auto slot = defer_slot();
14465 GAIA_ASSERT(slot != BadDeferSlot);
14466 world.defer_sort_inv_record(slot, entity);
14467 return;
14468 }
14470 }
14471
14474 inline void world_invalidate_sorted_queries(World& world) {
14475 world.invalidate_sorted_queries();
14476 }
14477
14481 inline void world_defer_sort_inv_begin(World& world, uint32_t slotCount) {
14482 world.defer_sort_inv_begin(slotCount);
14483 }
14484
14488 inline void world_defer_sort_inv_end(World& world) {
14489 world.defer_sort_inv_end();
14490 }
14491
14497 inline void world_defer_parallel_begin(World& world, uint32_t itemCount) {
14498#if GAIA_OBSERVERS_ENABLED
14499 world_defer_on_set_begin(world, itemCount);
14500#endif
14501 world_defer_sort_inv_begin(world, itemCount);
14502 }
14503
14507 inline void world_defer_parallel_end(World& world) {
14508 world_defer_sort_inv_end(world);
14509#if GAIA_OBSERVERS_ENABLED
14510 world_defer_on_set_end(world);
14511#endif
14512 }
14513
14514#if GAIA_OBSERVERS_ENABLED
14518 inline void world_defer_on_set_begin(World& world, uint32_t slotCount) {
14519 world.defer_on_set_begin(slotCount);
14520 }
14521
14525 inline void world_defer_on_set_end(World& world) {
14526 world.defer_on_set_end();
14527 }
14528#endif
14529
14535 inline bool world_has_entity_term(const World& world, Entity entity, Entity term) {
14536 if (term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
14537 const auto target = world.get(term.gen());
14538 return world.valid(target) && world.is(entity, target);
14539 }
14540
14541 return world.has(entity, term);
14542 }
14543
14549 inline bool world_has_entity_term_in(const World& world, Entity entity, Entity term) {
14550 if (term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
14551 const auto target = world.get(term.gen());
14552 return world.valid(target) && world.in(entity, target);
14553 }
14554
14555 return false;
14556 }
14557
14562 inline bool world_term_uses_inherit_policy(const World& world, Entity term) {
14563 return !is_wildcard(term) && world.valid(term) && world.target(term, OnInstantiate) == Inherit;
14564 }
14565
14571 inline bool world_has_entity_term_direct(const World& world, Entity entity, Entity term) {
14572 return world.has_direct(entity, term);
14573 }
14574
14579 inline bool world_relation_uses_non_fragmenting_storage(const World& world, Entity relation) {
14580 return world.relation_uses_non_fragmenting_storage(relation);
14581 }
14582
14587 inline bool world_relation_is_non_fragmenting(const World& world, Entity relation) {
14588 return world.relation_is_non_fragmenting(relation);
14589 }
14590
14595 inline bool world_component_uses_sparse_storage(const World& world, Entity component) {
14596 return world.component_uses_sparse_storage(component);
14597 }
14598
14603 inline bool world_component_is_non_fragmenting(const World& world, Entity component) {
14604 return world.component_is_non_fragmenting(component);
14605 }
14606
14611 inline uint32_t world_count_direct_term_entities(const World& world, Entity term) {
14612 return world.count_direct_term_entities(term);
14613 }
14614
14619 inline uint32_t world_count_in_term_entities(const World& world, Entity term) {
14620 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
14621 return 0;
14622
14623 const auto target = world.get(term.gen());
14624 return world.valid(target) ? (uint32_t)world.as_relations_trav_cache(target).size() : 0U;
14625 }
14626
14631 inline uint32_t world_count_direct_term_entities_direct(const World& world, Entity term) {
14632 return world.count_direct_term_entities_direct(term);
14633 }
14634
14639 inline void world_collect_direct_term_entities(const World& world, Entity term, cnt::darray<Entity>& out) {
14640 world.collect_direct_term_entities(term, out);
14641 }
14642
14647 inline void world_collect_in_term_entities(const World& world, Entity term, cnt::darray<Entity>& out) {
14648 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
14649 return;
14650
14651 const auto target = world.get(term.gen());
14652 if (!world.valid(target))
14653 return;
14654
14655 const auto& relations = world.as_relations_trav_cache(target);
14656 out.reserve(out.size() + (uint32_t)relations.size());
14657 for (auto relation: relations)
14658 out.push_back(relation);
14659 }
14660
14665 inline void world_collect_direct_term_entities_direct(const World& world, Entity term, cnt::darray<Entity>& out) {
14666 world.collect_direct_term_entities_direct(term, out);
14667 }
14668
14675 inline bool
14676 world_for_each_direct_term_entity(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
14677 return world.for_each_direct_term_entity(term, ctx, func);
14678 }
14679
14686 inline bool world_for_each_in_term_entity(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
14687 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
14688 return true;
14689
14690 const auto target = world.get(term.gen());
14691 if (!world.valid(target))
14692 return true;
14693
14694 for (auto relation: world.as_relations_trav_cache(target)) {
14695 if (!func(ctx, relation))
14696 return false;
14697 }
14698
14699 return true;
14700 }
14701
14708 inline bool
14709 world_for_each_direct_term_entity_direct(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
14710 return world.for_each_direct_term_entity_direct(term, ctx, func);
14711 }
14712
14717 inline bool world_entity_enabled(const World& world, Entity entity) {
14718 return world.enabled(entity);
14719 }
14720
14725 inline Entity world_pair_target_if_alive(const World& world, Entity pair) {
14726 return world.pair_target_if_alive(pair);
14727 }
14728
14734 inline bool world_entity_enabled_hierarchy(const World& world, Entity entity, Entity relation) {
14735 return world.enabled_hierarchy(entity, relation);
14736 }
14737
14741 inline uint32_t world_enabled_hierarchy_version(const World& world) {
14742 return world.enabled_hierarchy_version();
14743 }
14744
14749 inline bool world_relation_is_hierarchy(const World& world, Entity relation) {
14750 return world.relation_is_hierarchy(relation);
14751 }
14752
14757 inline bool world_relation_is_fragmenting(const World& world, Entity relation) {
14758 return world.relation_is_fragmenting(relation);
14759 }
14760
14765 inline bool world_relation_is_fragmenting_hierarchy(const World& world, Entity relation) {
14766 return world.relation_is_fragmenting_hierarchy(relation);
14767 }
14768
14773 inline bool world_relation_supports_depth_order(const World& world, Entity relation) {
14774 return world.relation_supports_depth_order(relation);
14775 }
14776
14781 inline bool world_relation_depth_order_prunes_disabled_subtrees(const World& world, Entity relation) {
14782 return world.relation_depth_order_prunes_disabled_subtrees(relation);
14783 }
14784
14789 inline bool world_entity_prefab(const World& world, Entity entity) {
14790 const auto& ec = world.fetch(entity);
14791 return ec.pArchetype != nullptr && ec.pArchetype->has(Prefab);
14792 }
14793
14799 inline Entity world_query_first_inherited_owner(const World& world, const Archetype& archetype, Entity term) {
14800 const auto& chunks = archetype.chunks();
14801 const Chunk* pFirstChunk = nullptr;
14802 for (const auto* pChunk: chunks) {
14803 if (pChunk == nullptr || pChunk->size() == 0)
14804 continue;
14805 pFirstChunk = pChunk;
14806 break;
14807 }
14808
14809 if (pFirstChunk == nullptr)
14810 return EntityBad;
14811
14812 const auto firstEntity = pFirstChunk->entity_view()[0];
14813 for (const auto target: world.as_targets_trav_cache(firstEntity)) {
14814 if (!world.has_direct(target, term))
14815 continue;
14816 return target;
14817 }
14818
14819 return EntityBad;
14820 }
14821
14826 inline const Archetype* world_entity_archetype(const World& world, Entity entity) {
14827 return world.fetch(entity).pArchetype;
14828 }
14829
14834 inline uint32_t world_component_index_bucket_size(const World& world, Entity term) {
14835 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
14836 if (it == world.m_entityToArchetypeMap.end())
14837 return 0;
14838
14839 return (uint32_t)it->second.size();
14840 }
14841
14847 inline uint32_t world_component_index_comp_idx(const World& world, const Archetype& archetype, Entity term) {
14848 if (is_wildcard(term))
14849 return BadIndex;
14850
14851 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
14852 if (it == world.m_entityToArchetypeMap.end())
14853 return BadIndex;
14854
14855 const auto idx = core::get_index_if(it->second, [&](const auto& entry) {
14856 return entry.matches(&archetype);
14857 });
14858 if (idx == BadIndex)
14859 return BadIndex;
14860
14861 return it->second[idx].compIdx;
14862 }
14863
14869 inline uint32_t world_component_index_match_count(const World& world, const Archetype& archetype, Entity term) {
14870 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
14871 if (it == world.m_entityToArchetypeMap.end())
14872 return 0;
14873
14874 const auto idx = core::get_index_if(it->second, [&](const auto& entry) {
14875 return entry.matches(&archetype);
14876 });
14877 if (idx == BadIndex)
14878 return 0;
14879
14880 return it->second[idx].matchCount;
14881 }
14882
14889 template <typename T>
14890 inline const std::remove_cv_t<std::remove_reference_t<T>>*
14891 world_query_inherited_arg_data_const(World& world, Entity owner, Entity id) {
14892 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
14893 return &world.template get<Arg>(owner, id);
14894 }
14895
14901 inline const void* world_query_inherited_arg_data_const_ptr(const World& world, Entity owner, Entity id) {
14902 const auto& ec = world.fetch(owner);
14903 const auto row = id.kind() == EntityKind::EK_Gen ? ec.row : 0;
14904 return ec.pChunk->comp_ptr(ec.pChunk->comp_idx(id), row);
14905 }
14906
14912 template <typename T>
14913 inline decltype(auto) world_direct_entity_arg(World& world, Entity entity) {
14914 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
14915 if constexpr (std::is_same_v<Arg, Entity>)
14916 return entity;
14917 else if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>)
14918 return world.template mut_im<Arg>(entity);
14919 else
14920 return world.template get<Arg>(entity);
14921 }
14922
14928 template <typename T>
14929 inline decltype(auto) world_direct_entity_arg_raw(World& world, Entity entity) {
14930 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
14931 if constexpr (std::is_same_v<Arg, Entity>)
14932 return entity;
14933 else if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>)
14934 return world.template mut<Arg>(entity);
14935 else
14936 return world.template get<Arg>(entity);
14937 }
14938
14944 template <typename T>
14945 inline void* world_typed_sparse_store_ptr(World& world, Entity component) {
14946 return &world.template sparse_component_store_mut<T>(component);
14947 }
14948
14954 template <typename T>
14955 inline const T& world_typed_sparse_store_get(const void* pStore, Entity entity) {
14956 return static_cast<const detail::SparseComponentStore<T>*>(pStore)->get(entity);
14957 }
14958
14964 template <typename T>
14965 inline T& world_typed_sparse_store_mut(void* pStore, Entity entity) {
14966 return static_cast<detail::SparseComponentStore<T>*>(pStore)->mut(entity);
14967 }
14968
14974 template <typename T>
14975 inline bool world_typed_sparse_store_has(const void* pStore, Entity entity) {
14976 return static_cast<const detail::SparseComponentStore<T>*>(pStore)->has(entity);
14977 }
14978
14983 template <typename T>
14984 inline Entity world_query_arg_id(World& world) {
14985 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
14986 using FT = typename component_type_t<Arg>::TypeFull;
14987 if constexpr (is_pair<FT>::value) {
14988 const auto rel = comp_cache(world).template get<typename FT::rel>().entity;
14989 const auto tgt = comp_cache(world).template get<typename FT::tgt>().entity;
14990 return (Entity)Pair(rel, tgt);
14991 } else
14992 return comp_cache(world).template get<FT>().entity;
14993 }
14994
15000 template <typename T>
15001 inline decltype(auto) world_query_entity_arg(World& world, Entity entity) {
15002 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
15003 if constexpr (std::is_same_v<Arg, Entity>)
15004 return entity;
15005 else {
15006 const auto id = world_query_arg_id<Arg>(world);
15007 return world_query_entity_arg_by_id<T>(world, entity, id);
15008 }
15009 }
15010
15018 template <typename T>
15019 inline decltype(auto) world_query_entity_arg_by_id(World& world, Entity entity, Entity id) {
15020 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
15021 if constexpr (std::is_same_v<Arg, Entity>)
15022 return entity;
15023 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
15024 if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>) {
15025 if (!world.has_direct(entity, termId)) {
15026 if constexpr (is_pair<Arg>::value)
15027 (void)world.override(entity, termId);
15028 else
15029 (void)world.template override<Arg>(entity, termId);
15030 }
15031
15032 return world.template mut_im<Arg>(entity, termId);
15033 } else
15034 return world.template get<Arg>(entity, termId);
15035 }
15036
15044 template <typename T>
15045 inline decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id) {
15046 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
15047 if constexpr (std::is_same_v<Arg, Entity>)
15048 return entity;
15049
15050 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
15051 if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>) {
15052 if (!world.has_direct(entity, termId)) {
15053 if constexpr (is_pair<Arg>::value)
15054 (void)world.override(entity, termId);
15055 else
15056 (void)world.template override<Arg>(entity, termId);
15057 }
15058
15059 return world.template mut<Arg>(entity, termId);
15060 } else
15061 return world.template get<Arg>(entity, termId);
15062 }
15063
15073 template <typename T>
15074 inline void world_init_query_entity_arg_by_id_chunk_stable_const(
15075 World& world, const Chunk& chunk, const Entity* pEntities, Entity id, bool& direct, uint32_t& compIdx,
15076 const std::remove_cv_t<std::remove_reference_t<T>>*& pDataInherited) {
15077 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
15078 if constexpr (std::is_same_v<Arg, Entity>) {
15079 direct = false;
15080 compIdx = BadIndex;
15081 pDataInherited = nullptr;
15082 return;
15083 }
15084
15085 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
15086 direct = chunk.has(termId);
15087 compIdx = BadIndex;
15088 pDataInherited = nullptr;
15089
15090 if (direct) {
15091 compIdx = chunk.comp_idx(termId);
15092 GAIA_ASSERT(compIdx != BadIndex);
15093 return;
15094 }
15095
15096 auto owner = EntityBad;
15097 const auto firstEntity = pEntities[0];
15098 for (const auto target: world.as_targets_trav_cache(firstEntity)) {
15099 if (!world.has_direct(target, termId))
15100 continue;
15101
15102 owner = target;
15103 break;
15104 }
15105
15106 GAIA_ASSERT(owner != EntityBad);
15107 pDataInherited = &world.template get<Arg>(owner, termId);
15108 }
15109
15119 template <typename T>
15120 inline decltype(auto) world_query_entity_arg_by_id_cached_const(
15121 World& world, Entity entity, Entity id, const Archetype*& pLastArchetype, Entity& cachedOwner,
15122 bool& cachedDirect) {
15123 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
15124 if constexpr (std::is_same_v<Arg, Entity>)
15125 return entity;
15126
15127 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
15128 const auto& ec = world.fetch(entity);
15129 if (ec.pArchetype != pLastArchetype) {
15130 pLastArchetype = ec.pArchetype;
15131 cachedDirect = ec.pArchetype->has(termId);
15132 cachedOwner = EntityBad;
15133
15134 if (!cachedDirect) {
15135 for (const auto target: world.as_targets_trav_cache(entity)) {
15136 if (!world.has_direct(target, termId))
15137 continue;
15138
15139 cachedOwner = target;
15140 break;
15141 }
15142
15143 GAIA_ASSERT(cachedOwner != EntityBad);
15144 }
15145 }
15146
15147 if (cachedDirect)
15148 return ComponentGetter{world, ec.pChunk, entity, ec.row}.template get<Arg>(termId);
15149
15150 return world.template get<Arg>(cachedOwner, termId);
15151 }
15152
15159 inline void world_notify_on_set(World& world, Entity term, Chunk& chunk, uint16_t from, uint16_t to) {
15160#if GAIA_OBSERVERS_ENABLED
15161 if (world.tearing_down())
15162 return;
15163 if (!world.observers().has_on_set_observers(term))
15164 return;
15165
15166 auto entities = chunk.entity_view();
15167 if (from >= entities.size())
15168 return;
15169 if (to > entities.size())
15170 to = (uint16_t)entities.size();
15171 if (from >= to)
15172 return;
15173
15174 // Observer dispatch mutates world-owned state and runs user code. Neither is safe from a
15175 // worker thread, so inside a deferring region the notification is only recorded here and
15176 // the coordinator dispatches it after the region joins.
15177 if (world.defer_on_set_active()) {
15178 const auto slot = defer_slot();
15179 GAIA_ASSERT(slot != BadDeferSlot);
15180 for (uint32_t row = from; row < to; ++row)
15181 world.defer_on_set_record(slot, term, entities[row]);
15182 return;
15183 }
15184
15185 world.observers().on_set(world, term, EntitySpan{entities.data() + from, uint32_t(to - from)});
15186#else
15187 (void)world;
15188 (void)term;
15189 (void)chunk;
15190 (void)from;
15191 (void)to;
15192#endif
15193 }
15194
15195 //----------------------------------------------------------------------
15196
15198 GAIA_ASSERT(m_pWorld != nullptr);
15199 GAIA_ASSERT(m_entity != EntityBad);
15200 return m_pWorld->get_raw(m_entity, component);
15201 }
15202
15203 inline ComponentRawView ComponentGetter::get_raw_field(Entity component, uint32_t fieldIdx) const {
15204 GAIA_ASSERT(m_pWorld != nullptr);
15205 GAIA_ASSERT(m_entity != EntityBad);
15206 return m_pWorld->get_raw_field(m_entity, component, fieldIdx);
15207 }
15208
15210 GAIA_ASSERT(m_pWorld != nullptr);
15211 GAIA_ASSERT(m_entity != EntityBad);
15212 return const_cast<World*>(m_pWorld)->mut_raw(m_entity, component);
15213 }
15214
15215 inline ComponentRawMutView ComponentSetter::mut_raw_field(Entity component, uint32_t fieldIdx) {
15216 GAIA_ASSERT(m_pWorld != nullptr);
15217 GAIA_ASSERT(m_entity != EntityBad);
15218 return const_cast<World*>(m_pWorld)->mut_raw_field(m_entity, component, fieldIdx);
15219 }
15220
15221 inline ComponentSetter& ComponentSetter::set_raw(Entity component, const void* data, uint32_t size) {
15222 GAIA_ASSERT(m_pWorld != nullptr);
15223 GAIA_ASSERT(m_entity != EntityBad);
15224 const auto ok = const_cast<World*>(m_pWorld)->set_raw(m_entity, component, data, size);
15225 GAIA_ASSERT(ok);
15226 (void)ok;
15227 return *this;
15228 }
15229
15231 GAIA_ASSERT(m_pWorld != nullptr);
15232 GAIA_ASSERT(m_entity != EntityBad);
15233 const_cast<World*>(m_pWorld)->modify_raw(m_entity, component);
15234 return *this;
15235 }
15236
15241 template <typename T>
15242 GAIA_NODISCARD decltype(auto) ComponentGetter::get(Entity type) const {
15243 GAIA_ASSERT(m_pWorld != nullptr);
15244 GAIA_ASSERT(m_entity != EntityBad);
15245
15246 using FT = typename component_type_t<T>::TypeFull;
15247 if constexpr (World::template supports_sparse_component_storage<FT>()) {
15248 if (m_pWorld->template can_use_sparse_component_storage<FT>(type)) {
15249 return m_pWorld->template sparse_component_get_value<FT>(type, m_entity);
15250 }
15251 }
15252
15253 const auto row = (uint16_t)(m_row * (actual_type_t<T>::Kind == EntityKind::EK_Gen));
15254 return m_pChunk->template get<T>(row, type);
15255 }
15256
15257 template <typename T>
15258 decltype(auto) ComponentSetter::mut(Entity type) {
15259 return smut<T>(type);
15260 }
15261
15262 template <typename T>
15263 decltype(auto) ComponentSetter::smut(Entity type) {
15264 GAIA_ASSERT(m_pWorld != nullptr);
15265 GAIA_ASSERT(m_entity != EntityBad);
15266
15267 using FT = typename component_type_t<T>::TypeFull;
15268 if constexpr (World::template supports_sparse_component_storage<FT>()) {
15269 auto& world = *const_cast<World*>(m_pWorld);
15270 if (world.template can_use_sparse_component_storage<FT>(type))
15271 return world.template sparse_component_mut_value<FT>(type, m_entity);
15272 }
15273
15274 const auto row = (uint16_t)(m_row * (actual_type_t<T>::Kind == EntityKind::EK_Gen));
15275 return const_cast<Chunk*>(m_pChunk)->template sset<T>(row, type);
15276 }
15277
15278 template <typename T>
15280 smut<T>(type) = GAIA_FWD(value);
15281 return *this;
15282 }
15283
15284 template <typename T>
15286 GAIA_ASSERT(m_pWorld != nullptr);
15287 GAIA_ASSERT(m_entity != EntityBad);
15288
15289 smut<T>(type) = GAIA_FWD(value);
15290 using FT = typename component_type_t<T>::TypeFull;
15291 auto& world = *const_cast<World*>(m_pWorld);
15292
15293 if constexpr (World::template supports_sparse_component_storage<FT>()) {
15294 if (world.template can_use_sparse_component_storage<FT>(type))
15295 ::gaia::ecs::update_version(world.m_worldVersion);
15296 }
15297
15298 world.finish_write(m_entity, type);
15299 return *this;
15300 }
15301
15302 //----------------------------------------------------------------------
15303
15308 inline void world_notify_on_set_entity(World& world, Entity term, Entity entity) {
15309#if GAIA_OBSERVERS_ENABLED
15310 if (world.tearing_down())
15311 return;
15312 if (!world.valid(entity))
15313 return;
15314 if (!world.observers().has_on_set_observers(term))
15315 return;
15316
15317 // See world_notify_on_set(): inside a deferring region the notification is recorded and
15318 // dispatched by the coordinator once the region joins.
15319 if (world.defer_on_set_active()) {
15320 const auto slot = defer_slot();
15321 GAIA_ASSERT(slot != BadDeferSlot);
15322 world.defer_on_set_record(slot, term, entity);
15323 return;
15324 }
15325
15326 world.observers().on_set(world, term, EntitySpan{&entity, 1});
15327#else
15328 (void)world;
15329 (void)term;
15330 (void)entity;
15331#endif
15332 }
15333
15335 inline void world_finish_write(World& world, Entity term, Entity entity) {
15336 world.finish_write(entity, term);
15337 }
15338
15339 } // namespace ecs
15340} // namespace gaia
15341
15342namespace gaia {
15343 namespace ecs {
15345 inline uint32_t world_rel_version(const World& world, Entity relation) {
15346 return world.rel_version(relation);
15347 }
15348
15355 inline uint32_t world_entity_archetype_version(const World& world, Entity entity) {
15356 if (!world.valid(entity))
15357 return 0;
15358
15359 const auto key = EntityLookupKey(entity);
15360 auto it = world.m_srcEntityVersions.find(key);
15361 if (it != world.m_srcEntityVersions.end())
15362 return it->second;
15363
15364 it = world.m_srcEntityVersions.try_emplace(key, 1).first;
15365 return it->second;
15366 }
15367 } // namespace ecs
15368} // namespace gaia
15369
15370#if GAIA_OBSERVERS_ENABLED
15371namespace gaia {
15372 namespace ecs {
15373 inline ObserverBuilder World::observer() {
15374 // Create the observer
15375 auto e = add();
15376 EntityBuilder(*this, e) //
15377 .add<Observer_>();
15378
15379 auto ss = acc_mut(e);
15380 auto& hdr = ss.smut<Observer_>();
15381 auto& obs = observers().data_add(e);
15382 {
15383 hdr.entity = e;
15384 obs.entity = e;
15385 obs.query = query();
15386 }
15387 return ObserverBuilder(*this, e);
15388 }
15389 } // namespace ecs
15390} // namespace gaia
15391#endif
Array with variable size of elements of type.
Definition darray_impl.h:27
iterator insert(iterator pos, const T &arg)
Insert the element to the position given by iterator pos.
Definition darray_impl.h:356
void reserve(size_type cap)
Ensures storage for at least the requested number of elements.
Definition darray_impl.h:223
GAIA_NODISCARD size_type size() const noexcept
Returns the number of elements.
Definition darray_impl.h:504
GAIA_NODISCARD decltype(auto) back() noexcept
Accesses the last element.
Definition darray_impl.h:542
void clear() noexcept
Removes all elements.
Definition darray_impl.h:449
void resize(size_type count)
Changes the number of elements.
Definition darray_impl.h:240
iterator erase(iterator pos) noexcept
Removes the element at pos.
Definition darray_impl.h:399
GAIA_NODISCARD bool empty() const noexcept
Checks whether the container has no elements.
Definition darray_impl.h:510
void pop_back() noexcept
Removes the last element.
Definition darray_impl.h:342
GAIA_NODISCARD pointer data() noexcept
Returns a pointer to the element storage.
Definition darray_impl.h:193
void push_back(const T &arg)
Appends an element.
Definition darray_impl.h:309
GAIA_NODISCARD auto end() noexcept
Returns an iterator one past the last element.
Definition darray_impl.h:592
GAIA_NODISCARD ArchetypeId id() const
Archetype id used to address the archetype in the world list.
Definition archetype.h:72
Fixed-shape group of chunks storing entities that share the same component layout....
Definition archetype.h:97
static GAIA_NODISCARD Archetype * create(const World &world, ArchetypeId archetypeId, uint32_t &worldVersion, EntitySpan ids)
Creates a new archetype from a component term span.
Definition archetype.h:540
GAIA_NODISCARD EntitySpan ids_view() const
Span over the component and entity identifiers defining the archetype shape.
Definition archetype.h:831
void set_hashes(LookupHash hashLookup)
Sets hashes for each component type and lookup.
Definition archetype.h:691
static void destroy(Archetype *pArchetype)
Destroys the archetype and frees its memory.
Definition archetype.h:671
core::direct_hash_key< uint64_t > LookupHash
Direct hash type used for archetype lookups.
Definition archetype.h:100
GAIA_NODISCARD uint32_t pairs() const
Returns the number of pairs registered in the archetype.
Definition archetype.h:843
static void diag(const World &world, const Archetype &archetype)
Performs diagnostics on a specific archetype. Prints basic info about it and the chunks it contains.
Definition archetype.h:1413
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:892
static constexpr uint16_t MAX_ARCHETYPE_LIFESPAN
Archetype lifespan must be at least as long as chunk lifespan.
Definition archetype.h:107
GAIA_NODISCARD uint32_t pair_matches(Entity pair) const
Returns how many pair ids in this archetype match the provided wildcard-capable pair query....
Definition archetype.h:857
Fixed-capacity archetype storage unit holding entities and their component columns.
Definition chunk.h:36
static void copy_entity_data_n_same_chunk(Chunk *pSrcChunk, uint32_t srcRow, Chunk *pDstChunk, uint32_t dstRow, uint32_t dstCount)
Copies all data associated with srcRow into dstCount consecutive rows in the same-archetype chunk.
Definition chunk.h:963
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:343
GAIA_NODISCARD EntitySpan ids_view() const
Span over the component and entity identifiers held by this chunk.
Definition chunk.h:868
static void copy_entity_data(Entity srcEntity, Entity dstEntity, EntityContainers &recs)
Copies all data associated with srcEntity into dstEntity.
Definition chunk.h:930
GAIA_NODISCARD uint8_t * comp_ptr_mut(uint32_t compIdx)
Mutable pointer to the start of a component column.
Definition chunk.h:881
GAIA_NODISCARD uint32_t comp_idx(Entity entity) const
Returns the internal index of a component based on the provided entity.
Definition chunk.h:1793
GAIA_NODISCARD const uint8_t * comp_ptr(uint32_t compIdx) const
Const pointer to the start of a component column.
Definition chunk.h:898
GAIA_NODISCARD bool has(Entity entity) const
Checks if a component/entity entity is present in the chunk.
Definition chunk.h:1527
static void copy_foreign_entity_data(Chunk *pSrcChunk, uint32_t srcRow, Chunk *pDstChunk, uint32_t dstRow)
Copies all data associated with entity into the chunk so that it is stored at the row row.
Definition chunk.h:1079
static void copy_foreign_entity_data_n(Chunk *pSrcChunk, uint32_t srcRow, Chunk *pDstChunk, uint32_t dstRow, uint32_t dstCount)
Copies all data associated with srcRow into dstCount consecutive rows in a foreign chunk.
Definition chunk.h:996
Owns entities, components, archetypes, queries, observers, and systems.
Definition world.h:80
GAIA_NODISCARD bool relation_depth_order_prunes_disabled_subtrees(Entity relation) const
Returns true when depth-ordered iteration may safely prune disabled subtrees at archetype level....
Definition world.h:1075
GAIA_NODISCARD uint32_t & world_version()
Returns the current version of the world.
Definition world.h:8609
void defer_sort_inv_end()
Stops recording sorted-query invalidations and applies everything recorded since the matching defer_s...
Definition world.h:8448
void as(Entity entity, Entity entityBase)
Shortcut for add(entity, Pair(Is, entityBase)).
Definition world.h:5891
GAIA_NODISCARD const EntityContainer & fetch(Entity entity) const
Returns the internal record for entity.
Definition world.h:921
void invalidate_queries_for_structural_entity(EntityLookupKey entityKey)
Invalidates cached queries structurally affected by entityKey.
Definition world.h:12862
void sources_bfs(Entity relation, Entity rootTarget, Func func) const
Traverses relationship sources in breadth-first order. Starting at rootTarget, this visits all direct...
Definition world.h:8169
GAIA_NODISCARD uint32_t count_direct_term_entities_direct(Entity term) const
Counts entities directly matching term without semantic Is expansion.
Definition world.h:8126
GAIA_NODISCARD const NonFragmentingRelationStore * nonfragmenting_relation_store(Entity relation) const
Returns the non-fragmenting relation store for relation, or nullptr when absent.
Definition world.h:1481
GAIA_NODISCARD Entity pair_target_if_alive(Entity pair) const
Resolves the target of an archetype-stored exact pair id, skipping stale cleanup-time targets.
Definition world.h:7671
void scope(Entity scopeEntity, Func &&func)
Executes func with a temporary component scope and restores the previous scope afterwards....
Definition world.h:6704
void sync_component_record(Entity component, Component comp)
Updates the cached component metadata in both the component cache and the core Component storage.
Definition world.h:1156
void defrag_entities_per_tick(uint32_t value)
Sets the maximum number of entities defragmented per world tick.
Definition world.h:8796
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:8661
void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count, Func func)
Instantiates count copies of a prefab as normal root entities, assigning a direct Pair(Parent,...
Definition world.h:5658
GAIA_NODISCARD ComponentRawMutView mut_raw_field(Entity entity, Entity component, uint32_t fieldIdx)
Returns one mutable field value from a directly owned SoA field array. This is a silent write path....
Definition world.h:6298
GAIA_NODISCARD const SparseComponentStoreErased * sparse_component_store_erased(Entity component) const
Returns the erased sparse store for component, or nullptr when absent.
Definition world.h:1408
GAIA_NODISCARD bool has_sorted_queries() const
Checks whether any cached sorted query is registered in this world.
Definition world.h:12877
GAIA_NODISCARD const cnt::darray< Entity > & sources_all_cache(Entity target) const
Returns the cached deduped direct sources for wildcard source traversal on target....
Definition world.h:7309
GAIA_NODISCARD bool targets_trav_if(Entity relation, Entity source, Func func) const
Traverses relationship targets upwards starting from source. Disabled entities act as traversal barri...
Definition world.h:7391
GAIA_NODISCARD const cnt::set< EntityLookupKey > * targets(Entity relation) const
Returns targets for relation.
Definition world.h:7534
void update_src_entity_version(Entity entity)
Updates a tracked source-entity version after the entity changes archetype membership.
Definition world.h:8642
GAIA_NODISCARD bool has_nonfragmenting_relation_pair(Entity source, Entity object) const
Checks whether a source matches a possibly wildcarded non-fragmenting relation pair.
Definition world.h:1543
bool alias(Entity entity, const char *alias, uint32_t len=0)
Assigns an alias name to an entity.
Definition world.h:3413
void set_component_sparse_storage(Entity component)
Latches Sparse storage on a component entity before any instances exist. This moves the payload out o...
Definition world.h:1232
GAIA_NODISCARD const cnt::darray< Entity > & targets_trav_cache(Entity relation, Entity source) const
Returns the cached unlimited upward traversal chain for (relation, source). The cache excludes the so...
Definition world.h:7240
ser::serializer get_serializer() const
Returns the currently bound runtime serializer handle.
Definition world.h:1694
GAIA_NODISCARD uint32_t depth_order_cache(Entity relation, Entity sourceTarget) const
Returns the cached fragmenting relation depth used by depth-ordered iteration for (relation,...
Definition world.h:7466
EntityBuilder build(Entity entity)
Starts a bulk add/remove operation on entity.
Definition world.h:3810
GAIA_NODISCARD Entity prefab(EntityKind kind=EntityKind::EK_Gen)
Creates a new prefab entity.
Definition world.h:3824
GAIA_NODISCARD util::str_view symbol(Entity component) const
Returns the registered symbol name for a component entity.
Definition world.h:3342
GAIA_NODISCARD bool has_nonfragmenting_relation_target_cond(Entity target, Pair cond) const
Checks whether any non-fragmenting exclusive relation targeting target uses an OnDeleteTarget rule.
Definition world.h:1658
bool add_raw(Entity entity, Entity component, const void *data, uint32_t size)
Adds an AoS component and initializes its raw payload before OnAdd observers run.
Definition world.h:6344
void lookup_path(std::span< const Entity > scopes)
Replaces the ordered component lookup path used for unqualified component lookup. Each scope is searc...
Definition world.h:6666
GAIA_NODISCARD decltype(auto) mut(Entity entity, Entity object)
Returns a mutable reference or proxy to the component associated with object on entity....
Definition world.h:6180
Query uquery()
Provides an uncached query set up to work with the parent world. Uncached queries keep only a local i...
Definition world.h:849
void enable(Entity entity, bool enable)
Enables or disables an entire entity.
Definition world.h:8483
GAIA_NODISCARD const ComponentCache & comp_cache() const
Returns read-only access to the world component cache.
Definition world.h:3321
void nonfragmenting_relation_set(Entity source, Entity relation, Entity target)
Sets an exclusive non-fragmenting relation target, creating its store when necessary.
Definition world.h:1512
Entity expr_to_entity(va_list &args, std::span< const char > exprRaw) const
Resolves a textual id expression with e placeholders to an entity. Supports the same pair and wildcar...
Definition world.h:12962
void set_sched(const Sched &sched)
Installs a custom scheduler used by ECS parallel execution paths.
Definition world.h:872
void child(Entity entity, Entity parent)
Shortcut for add(entity, Pair(ChildOf, parent)).
Definition world.h:5933
GAIA_NODISCARD Entity symbol(const char *symbol, uint32_t len=0) const
Finds a component entity by its exact registered symbol.
Definition world.h:3331
GAIA_NODISCARD bool copies_non_frag_sparse_payload_inter(Entity comp, Entity srcEntity, const SparseComponentStoreErased &store) const
Checks whether an inter-world copy includes a non-fragmenting sparse payload.
Definition world.h:1138
GAIA_NODISCARD uint32_t sync(Entity prefabEntity)
Propagates additive prefab changes to existing non-prefab instances. Missing copied ids are added to ...
Definition world.h:5728
void set_serializer(ser::serializer serializer)
Binds a pre-built runtime serializer handle.
Definition world.h:1680
void clear(Entity entity)
Removes any component or entity attached to entity.
Definition world.h:4123
GAIA_NODISCARD bool override(Entity entity, Entity object)
Materializes an inherited id as directly owned storage on entity.
Definition world.h:4071
GAIA_NODISCARD const ComponentCacheItem & add()
Creates a new component if not found already.
Definition world.h:3857
bool alias_raw(Entity entity, const char *alias, uint32_t len=0)
Assigns an alias name to an entity without copying the string.
Definition world.h:3432
void add(Entity entity, Entity object)
Attaches entity object to entity entity.
Definition world.h:3945
void finalize_component_registration(const ComponentCacheItem &item, bool addSparseTrait)
Finalizes a newly registered component entity after the cache record has been created....
Definition world.h:1206
GAIA_NODISCARD util::str_view name(EntityId entityId) const
Returns the entity name assigned to entityId.
Definition world.h:6863
GAIA_NODISCARD const cnt::set< EntityLookupKey > * relations(Entity target) const
Returns relations for target.
Definition world.h:7053
void del_nonfragmenting_relation(Entity relation)
Removes every stored source-target mapping for a non-fragmenting relation.
Definition world.h:1639
Entity scope(Entity scope)
Sets the current component scope used for component registration and relative component lookup....
Definition world.h:6688
GAIA_NODISCARD decltype(auto) get(Entity entity, Entity object) const
Returns the value stored in the component associated with object on entity.
Definition world.h:6478
GAIA_NODISCARD ComponentCursor cursor(Entity entity, Entity component) const
Creates a read-only cursor over a runtime component on entity. Inherited ids resolve like get_raw()....
Definition world.h:13051
void validate_runtime_semantics(const RuntimeTypeDesc &runtimeType) const
Validates runtime-reflection state against the live scope graph in debug builds. Verifies that every ...
Definition world.h:1176
GAIA_NODISCARD Entity get() const
Returns the entity registered for component type T.
Definition world.h:3788
GAIA_NODISCARD decltype(auto) sset(Entity entity)
Returns silent mutable access to component type T on entity. This is a silent write and does not trig...
Definition world.h:6131
GAIA_NODISCARD decltype(auto) sparse_component_add_value(Entity component, Entity entity)
Adds or returns a sparse value through the store's erased payload interface.
Definition world.h:1367
GAIA_NODISCARD bool enabled(const EntityContainer &ec) const
Checks if an entity is enabled.
Definition world.h:8506
void add(Entity entity, U &&value)
Attaches a new component T to entity. Also sets its value.
Definition world.h:4036
GAIA_NODISCARD SparseStorageMode compile_time_sparse_storage_mode(Entity component) const
Returns the fragmentation mode for a component known at compile time to use sparse storage.
Definition world.h:1293
void invalidate_sorted_queries_for_entity(Entity entity)
Invalidates cached sorted queries whose row ordering depends on entity.
Definition world.h:12890
GAIA_NODISCARD Entity find_prefab_instance(Entity instanceRoot, Entity prefabEntity) const
Finds the entity inside instanceRoot that was instantiated from prefabEntity. The lookup checks insta...
Definition world.h:5742
GAIA_NODISCARD bool is(Entity entity, Entity entityBase) const
Checks if entity inherits from entityBase.
Definition world.h:5900
void sources(Entity relation, Entity target, Func func) const
Returns relationship sources for the relation and target.
Definition world.h:7690
void del_sparse_component_store(Entity component)
Deletes the sparse component store associated with component.
Definition world.h:1468
GAIA_NODISCARD bool for_each_direct_term_entity_direct(Entity term, void *ctx, bool(*func)(void *, Entity)) const
Visits entities directly matching term without semantic Is expansion.
Definition world.h:8159
void modify(Entity entity)
Marks the component type T as modified. Best used with acc_mut().sset() or set() to manually trigger ...
Definition world.h:5977
GAIA_NODISCARD bool defer_sort_inv_active() const
Returns whether sorted-query invalidations are currently being recorded.
Definition world.h:8465
void add(Entity entity)
Attaches a new component T to entity.
Definition world.h:3975
GAIA_NODISCARD EntityContainer & fetch(Entity entity)
Returns the internal record for entity.
Definition world.h:892
void del(Entity entity)
Removes an entity along with all data associated with it.
Definition world.h:5791
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:6837
void del(Entity entity)
Removes a component T from entity.
Definition world.h:5872
GAIA_NODISCARD SparseStorageMode sparse_storage_mode(Entity component) const
Returns the sparse storage mode used by a component.
Definition world.h:1108
void remove_src_entity_version(Entity entity)
Removes sparse source-version state for an entity that is being destroyed.
Definition world.h:8653
GAIA_NODISCARD const ComponentCacheItem & add(const RuntimeTypeDesc &runtimeType)
Registers a compile-time component with explicit runtime type metadata. Metadata is applied only duri...
Definition world.h:3892
GAIA_NODISCARD bool locked() const
Checks if the chunk is locked for structural changes.
Definition world.h:9062
GAIA_NODISCARD const cnt::darray< Entity > & as_relations_trav_cache(Entity target) const
Returns the cached transitive Is descendants for a target entity. The cache is rebuilt lazily and cle...
Definition world.h:7161
GAIA_NODISCARD const cnt::darray< Entity > & targets_all_cache(Entity source) const
Returns the cached deduped direct targets for wildcard target traversal on source....
Definition world.h:7268
void defer_sort_inv_record(uint32_t slot, Entity entity)
Records a sorted-query invalidation for later application.
Definition world.h:8472
void nonfragmenting_relation_set(NonFragmentingRelationStore &store, Entity source, Entity relation, Entity target)
Sets an exclusive non-fragmenting relation target in an existing store.
Definition world.h:1502
GAIA_NODISCARD ComponentCursor cursor_mut(Entity entity, Entity component)
Creates a mutable cursor over a directly owned runtime component on entity. Direct writes through mut...
Definition world.h:13067
static GAIA_NODISCARD constexpr bool uses_compile_time_sparse_storage()
Returns whether T has compile-time sparse payload storage.
Definition world.h:1285
void invalidate_queries_for_entity(Pair is_pair)
Invalidates semantic Is queries affected by removing or changing is_pair.
Definition world.h:12901
void defer_sort_inv_begin(uint32_t slotCount)
Starts recording sorted-query invalidations instead of applying them right away. Called by the coordi...
Definition world.h:8432
GAIA_NODISCARD bool has_sorted_queries_for_entity(Entity entity) const
Checks whether any cached sorted query depends on entity being written.
Definition world.h:12884
GAIA_NODISCARD const ComponentCacheItem & reg_comp()
Returns the registered component cache item for T, auto-registering it when enabled.
Definition world.h:3796
GAIA_NODISCARD ComponentRawView get_raw(Entity entity, Entity component) const
Returns raw read-only bytes for an AoS component or exact pair payload on entity. Inherited ids resol...
Definition world.h:6190
void invalidate_queries_for_rel(Entity relation)
Invalidates cached queries whose dynamic result depends on relation.
Definition world.h:12868
GAIA_NODISCARD Entity instantiate(Entity prefabEntity, Entity parentInstance)
Instantiates a prefab as a normal entity parented under parentInstance. The instance copies the prefa...
Definition world.h:5606
GAIA_NODISCARD Entity get(EntityId id) const
Returns the entity located at the index id.
Definition world.h:3767
GAIA_NODISCARD const Sched & sched() const
Returns the resolved scheduler used by this world.
Definition world.h:883
void update()
Runs systems and then finishes the current frame.
Definition world.h:8724
void resolve(cnt::darray< Entity > &out, const char *name, uint32_t len=0) const
Collects every entity and component entity that matches name. This is useful for diagnostics when a s...
Definition world.h:6898
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:6820
void add(Entity entity, Entity object, T &&value)
Attaches object to entity. Also sets its value.
Definition world.h:3995
ComponentGetter acc(Entity entity) const
Starts a bulk get operation on an entity.
Definition world.h:6433
GAIA_NODISCARD const cnt::darray< Entity > & sources_bfs_trav_cache(Entity relation, Entity rootTarget) const
Returns the cached unlimited breadth-first descendant traversal for (relation, rootTarget)....
Definition world.h:7416
GAIA_NODISCARD bool enabled(Entity entity) const
Checks if an entity is enabled.
Definition world.h:8519
GAIA_NODISCARD const cnt::darray< Entity > & as_targets_trav_cache(Entity relation) const
Returns the cached transitive Is targets for a relation entity. The cache is rebuilt lazily and clear...
Definition world.h:7200
GAIA_NODISCARD decltype(auto) sparse_component_get_value(Entity component, Entity entity) const
Returns a read-only sparse value through the store's erased payload interface.
Definition world.h:1398
GAIA_NODISCARD bool sparse_copy_adds_id_inter(Entity comp) const
Checks whether copying a sparse payload must also add its id to the destination entity.
Definition world.h:1147
void parent(Entity entity, Entity parentEntity)
Adds a direct non-fragmenting Parent relationship to parentEntity. The relationship is non-fragmentin...
Definition world.h:5949
void diag_components() const
Performs diagnostics on registered components. Prints basic info about them and reports and detected ...
Definition world.h:8811
void collect_direct_term_entities(Entity term, cnt::darray< Entity > &out) const
Appends entities directly matching term to out, including semantic Is expansion.
Definition world.h:8133
void targets_if(Entity entity, Entity relation, Func func) const
Returns the relationship targets for the relation entity on entity.
Definition world.h:7630
GAIA_NODISCARD bool valid(Entity entity) const
Checks if entity is valid.
Definition world.h:3756
CommandBufferST & cmd_buffer_st() const
Returns the single-threaded deferred command buffer owned by the world.
Definition world.h:8310
void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count)
Instantiates count copies of a prefab as normal root entities parented under parentInstance....
Definition world.h:5644
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity is currently used by the world.
Definition world.h:6498
bool path(Entity component, const char *path, uint32_t len=0)
Assigns a scoped path name to a component entity.
Definition world.h:3372
void targets_trav(Entity relation, Entity source, Func func) const
Traverses relationship targets upwards starting from source. Disabled entities act as traversal barri...
Definition world.h:7366
void modify(Entity entity, Entity object)
Marks the component associated with object as modified on entity. Best used with mut<T>(entity,...
Definition world.h:6039
GAIA_NODISCARD Entity copy(Entity srcEntity)
Creates a new entity by cloning an already existing one. Does not trigger observers.
Definition world.h:4146
void diag() const
Performs all diagnostics.
Definition world.h:8840
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:8568
GAIA_NODISCARD bool has(Entity entity, Pair pair) const
Checks if entity contains pair.
Definition world.h:6773
Entity module(const char *path, uint32_t len=0)
Finds or builds a named module hierarchy and returns the deepest scope entity. Each path segment is m...
Definition world.h:6723
GAIA_NODISCARD bool relation_is_hierarchy(Entity relation) const
Returns true for hierarchy-like relations whose targets form an exclusive traversable parent chain....
Definition world.h:1037
GAIA_NODISCARD SparseComponentStore< T > * sparse_component_store(Entity component)
Returns the sparse component store for component, or nullptr if it does not exist.
Definition world.h:1324
GAIA_NODISCARD Entity add(EntityKind kind=EntityKind::EK_Gen)
Creates a new empty entity.
Definition world.h:3817
GAIA_NODISCARD bool has(Pair pair) const
Checks if pair is currently used by the world.
Definition world.h:6553
void save()
Saves contents of the world to a buffer. The buffer is reset, not appended. NOTE: In order for custom...
Definition world.h:9268
GAIA_NODISCARD std::span< const Entity > lookup_path() const
Returns the ordered component lookup path used for unqualified component lookup. Each scope is search...
Definition world.h:6659
GAIA_NODISCARD bool can_use_sparse_component_storage(Entity object) const
Returns whether object is a usable sparse storage target for component type T.
Definition world.h:1302
bool nonfragmenting_relation_del(Entity source, Entity relation, Entity target)
Removes an exclusive non-fragmenting relation from a source entity.
Definition world.h:1522
GAIA_NODISCARD bool relation_uses_non_fragmenting_storage(Entity relation) const
Returns whether relation uses non-fragmenting relation storage. Only exclusive non-fragmenting relati...
Definition world.h:1024
GAIA_NODISCARD bool relation_supports_depth_order(Entity relation) const
Returns true when the relation can drive cached depth-ordered iteration. This requires a fragmenting ...
Definition world.h:1066
GAIA_NODISCARD decltype(auto) get(Entity entity) const
Returns the value stored in the component T on entity.
Definition world.h:6448
GAIA_NODISCARD bool relation_is_fragmenting(Entity relation) const
Returns true when the relation still participates in archetype identity. Non-fragmenting relations su...
Definition world.h:1048
void add(Entity entity, Pair pair)
Attaches a relationship pair to entity.
Definition world.h:3962
GAIA_NODISCARD uint32_t count_direct_term_entities(Entity term) const
Counts entities directly matching term, including semantic Is inheritance expansion.
Definition world.h:8119
void add_n(uint32_t count, Func func=func_void_with_entity)
Creates count new empty entities.
Definition world.h:3834
GAIA_NODISCARD uint32_t enabled_hierarchy_version() const
Returns the version that changes when entity enabled state changes. Hierarchy-aware cached traversals...
Definition world.h:8625
GAIA_NODISCARD bool can_add_component_storage_trait(Entity component) const
Returns whether a runtime storage trait can be attached to component. Compile-time component storage ...
Definition world.h:1263
void runtime_counters(uint32_t &outArchetypes, uint32_t &outChunks, uint32_t &outEntitiesTotal, uint32_t &outEntitiesActive) const
Returns high-level runtime counters useful for diagnostics/telemetry.
Definition world.h:8586
void set_component_dont_fragment(Entity component, EntityContainer &ec)
Latches DontFragment on a component entity record. This first moves the payload out of chunks,...
Definition world.h:1218
GAIA_NODISCARD bool for_each_direct_term_entity(Entity term, void *ctx, bool(*func)(void *, Entity)) const
Visits entities directly matching term, including semantic Is expansion.
Definition world.h:8149
GAIA_NODISCARD ComponentCacheItem & add(const ComponentDesc &desc, EntityKind kind=EntityKind::EK_Gen)
Creates a new runtime component from a plain component descriptor if not found already.
Definition world.h:3925
void del(Entity entity, Entity object)
Removes an object from entity if possible.
Definition world.h:5829
GAIA_NODISCARD bool has_direct(Entity entity, Entity object) const
Checks if entity directly contains the entity object, without semantic inheritance expansion.
Definition world.h:6571
void diag_entities() const
Performs diagnostics on entities of the world. Also performs validation of internal structures which ...
Definition world.h:8817
GAIA_NODISCARD bool is_dont_fragment(Entity entity) const
Returns whether entity is marked DontFragment.
Definition world.h:1009
GAIA_NODISCARD util::str_view path(Entity component) const
Returns the scoped path name for a component entity.
Definition world.h:3362
GAIA_NODISCARD decltype(auto) mut(Entity entity)
Returns a mutable reference or proxy to the component on entity without triggering a world version up...
Definition world.h:6168
GAIA_NODISCARD bool copies_sparse_payload_inter(Entity comp, Entity srcEntity, const SparseComponentStoreErased &store) const
Checks whether an inter-world copy includes a sparse component payload.
Definition world.h:1129
void reset_sched()
Resets the world back to the default Gaia scheduler.
Definition world.h:877
void instantiate_n(Entity prefabEntity, uint32_t count, Func func=func_void_with_entity)
Instantiates count copies of a prefab as normal root entities. Each instance copies the prefab's dire...
Definition world.h:5634
GAIA_NODISCARD Entity resolve(const char *name, uint32_t len=0) const
Resolves name in the world naming system. Entity names and hierarchical entity paths are attempted fi...
Definition world.h:6877
GAIA_NODISCARD Entity path(const char *path, uint32_t len=0) const
Finds a component entity by its exact scoped path.
Definition world.h:3351
void frame_cleanup()
Performs deferred cleanup for the current frame.
Definition world.h:8687
void diag_archetypes() const
Performs diagnostics on archetypes. Prints basic info about them and the chunks they contain.
Definition world.h:8803
GAIA_NODISCARD bool child(Entity entity, Entity parent) const
Checks whether entity has a ChildOf relationship to parent.
Definition world.h:5941
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:5910
GAIA_NODISCARD SparseComponentStoreErased & sparse_component_store_erased_mut(Entity component, const ComponentCacheItem &item)
Returns the erased sparse store for component, creating runtime-sized storage when absent.
Definition world.h:1418
GAIA_NODISCARD bool has_direct(Entity entity, Pair pair) const
Checks if entity directly contains pair, without semantic inheritance expansion.
Definition world.h:6579
void relations_if(Entity entity, Entity target, Func func) const
Returns the relationship relations for the target entity on entity.
Definition world.h:7129
void del_sparse_components(Entity entity)
Removes all sparse component instances owned by entity.
Definition world.h:1459
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:4186
GAIA_NODISCARD util::str_view name(Entity entity) const
Returns the name assigned to entity.
Definition world.h:6845
void sources_if(Entity relation, Entity target, Func func) const
Returns relationship sources for the relation and target.
Definition world.h:7747
GAIA_NODISCARD auto set(Entity entity)
Returns a write-back proxy for the component T on entity. The proxy copies the current value,...
Definition world.h:6097
bool as_targets_trav_if(Entity relation, Func func) const
Traverses transitive Is targets of relation until func returns true.
Definition world.h:8293
void as_targets_trav(Entity relation, Func func) const
Traverses transitive Is targets of relation. The traversal uses the cached closure built by as_target...
Definition world.h:8277
GAIA_NODISCARD uint32_t rel_version(Entity relation) const
Returns structural version for a given relation. Increments whenever any Pair(relation,...
Definition world.h:8617
GAIA_NODISCARD bool relation_is_fragmenting_hierarchy(Entity relation) const
Returns true for hierarchy relations that still fragment archetypes. ChildOf satisfies this today,...
Definition world.h:1056
Query query()
Provides a cached query set up to work with the parent world. Cached queries use local scope by defau...
Definition world.h:839
bool load(ser::serializer inputSerializer={})
Loads a world state from a buffer. The buffer is sought to 0 before any loading happens....
Definition world.h:9357
void as_relations_trav(Entity target, Func func) const
Traverses transitive Is descendants of target. The traversal uses the cached closure built by as_rela...
Definition world.h:7500
void teardown()
Performs world shutdown maintenance without running systems or observers. Runtime callbacks are shut ...
Definition world.h:8739
void del(Entity entity, Pair pair)
Removes an existing entity relationship pair.
Definition world.h:5862
static GAIA_NODISCARD bool is_req_del(const EntityContainer &ec)
Returns whether the record is already in delete-requested state. Covers both explicit per-entity dele...
Definition world.h:953
Entity name_to_entity(std::span< const char > exprRaw) const
Resolves a textual id expression to an entity. Supports names, aliases, wildcard *,...
Definition world.h:12926
GAIA_NODISCARD Entity scope() const
Returns the current component scope used for component registration and relative component lookup.
Definition world.h:6680
GAIA_NODISCARD auto set(Entity entity, Entity object)
Returns a write-back proxy for the component associated with object on entity. The proxy copies the c...
Definition world.h:6117
GAIA_NODISCARD decltype(auto) sset(Entity entity, Entity object)
Sets the value of the component associated with object on entity without updating world version....
Definition world.h:6147
GAIA_NODISCARD bool component_uses_sparse_storage(Entity component) const
Returns whether component stores instance data in sparse storage instead of archetype chunks....
Definition world.h:1083
GAIA_NODISCARD Chunk * get_chunk(Entity entity) const
Returns a chunk containing the entity.
Definition world.h:8557
GAIA_NODISCARD SparseComponentStore< T > & sparse_component_store_mut(Entity component)
Returns the sparse component store for component, creating it if needed.
Definition world.h:1350
GAIA_NODISCARD bool sources_bfs_if(Entity relation, Entity rootTarget, Func func) const
Traverses relationship sources in breadth-first order. Starting at rootTarget, this visits all direct...
Definition world.h:8221
GAIA_NODISCARD decltype(auto) sparse_component_mut_value(Entity component, Entity entity)
Returns a mutable sparse value through the store's erased payload interface.
Definition world.h:1385
void del_nonfragmenting_relation_source(Entity source)
Removes all outgoing non-fragmenting relations from a source entity.
Definition world.h:1585
GAIA_NODISCARD bool parent(Entity entity, Entity parentEntity) const
Checks whether entity has a direct non-fragmenting Parent relationship to parentEntity.
Definition world.h:5957
GAIA_NODISCARD ComponentRawMutView mut_raw(Entity entity, Entity component)
Returns raw mutable bytes for a directly owned AoS component or exact pair payload....
Definition world.h:6228
GAIA_NODISCARD bool is_base(Entity target) const
Checks whether an entity is the target of at least one direct Is relation.
Definition world.h:5917
GAIA_NODISCARD Entity get(const char *name, uint32_t len=0) const
Returns the entity assigned a name name. This is a convenience alias for resolve(name).
Definition world.h:6933
GAIA_NODISCARD util::str_view alias(Entity entity) const
Returns the alias assigned to an entity.
Definition world.h:3394
static GAIA_NODISCARD constexpr bool supports_sparse_component_storage()
Sparse storage currently supports only plain generic components. Pairs, unique components and SoA lay...
Definition world.h:1276
void targets(Entity entity, Entity relation, Func func) const
Returns the relationship targets for the relation entity on entity.
Definition world.h:7587
GAIA_NODISCARD ComponentRawView get_raw_field(Entity entity, Entity component, uint32_t fieldIdx) const
Returns one read-only field value from a directly addressed SoA field array. The field index follows ...
Definition world.h:6266
GAIA_NODISCARD bool has(Entity entity, Entity object) const
Checks if entity contains the entity object.
Definition world.h:6563
GAIA_NODISCARD NonFragmentingRelationStore & nonfragmenting_relation_store_mut(Entity relation)
Returns the non-fragmenting relation store for relation, creating it if needed.
Definition world.h:1492
GAIA_NODISCARD uint32_t archetype_delete_version() const
Returns the version that changes when archetype deletion visibility changes.
Definition world.h:8631
void set_serializer(TSerializer &serializer)
Binds a concrete serializer object through ser::make_serializer().
Definition world.h:1688
GAIA_NODISCARD uint32_t size() const
Returns the number of active entities.
Definition world.h:8577
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity contains the component T.
Definition world.h:6784
void set_serializer(std::nullptr_t)
Resets runtime serializer binding to the default internal bin_stream backend.
Definition world.h:1673
void collect_direct_term_entities_direct(Entity term, cnt::darray< Entity > &out) const
Appends entities directly matching term to out without semantic Is expansion.
Definition world.h:8140
GAIA_NODISCARD Entity instantiate(Entity prefabEntity)
Instantiates a prefab as a normal entity. The instance copies the prefab's direct data,...
Definition world.h:5588
GAIA_NODISCARD util::str_view display_name(Entity entity) const
Returns the preferred display name for a entity. This is intended for diagnostics and other pretty ou...
Definition world.h:3450
bool set_raw(Entity entity, Entity component, const void *data, uint32_t size)
Replaces raw bytes for a directly owned AoS component and finishes the write.
Definition world.h:6395
GAIA_NODISCARD ComponentSetter acc_mut(Entity entity)
Starts a bulk set operation on entity.
Definition world.h:6079
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:3844
void modify_raw(Entity entity, Entity component)
Marks a raw payload or SoA field returned by a mutable raw view as modified and emits normal set side...
Definition world.h:6412
void frame_end()
Marks the end of the current frame.
Definition world.h:8708
GAIA_NODISCARD Entity relation(Entity entity, Entity target) const
Returns the first relationship relation for the target entity on entity.
Definition world.h:7062
void finish_sparse_component_add_inter(Entity entity, Entity object, SparseStorageMode mode)
Finishes adding a sparse component after its payload has been created. Fragmenting components also ad...
Definition world.h:1437
GAIA_NODISCARD Entity alias(const char *alias, uint32_t len=0) const
Finds an entity by its exact alias.
Definition world.h:3381
QuerySerBuffer & query_buffer(QueryId &serId)
Returns the temporary serialization buffer used while building a query. A fresh query id is allocated...
Definition world.h:12817
void cleanup()
Clears the world so that all its entities and components are released.
Definition world.h:8779
GAIA_NODISCARD Entity target(Entity entity, Entity relation) const
Returns the first relationship target for the relation entity on entity.
Definition world.h:7543
GAIA_NODISCARD const SparseComponentStore< T > * sparse_component_store(Entity component) const
Returns the sparse component store for component, or nullptr if it does not exist.
Definition world.h:1337
GAIA_NODISCARD ComponentCache & comp_cache_mut()
Returns mutable access to the world component cache.
Definition world.h:3315
void relations(Entity entity, Entity target, Func func) const
Returns the relationship relations for the target entity on entity.
Definition world.h:7095
void query_buffer_reset(QueryId &serId)
Releases the temporary serialization buffer associated with serId.
Definition world.h:12851
GAIA_NODISCARD bool relation_is_non_fragmenting(Entity relation) const
Returns whether relation is non-fragmenting.
Definition world.h:1016
GAIA_NODISCARD bool component_is_non_fragmenting(Entity component) const
Returns whether component is non-fragmenting. Non-fragmenting components do not participate in archet...
Definition world.h:1098
void invalidate_sorted_queries()
Invalidates all cached sorted queries after row-order changes.
Definition world.h:12895
GAIA_NODISCARD bool enabled_hierarchy(Entity entity, Entity relation) const
Checks whether an entity is enabled together with all of its ancestors reachable through relation....
Definition world.h:8531
bool load(TSerializer &inputSerializer)
Loads a world state from a serializer-compatible stream wrapper.
Definition world.h:9688
GAIA_NODISCARD bool tearing_down() const
Returns true while the world is draining teardown work and normal runtime callbacks must not execute.
Definition world.h:9069
GAIA_NODISCARD bool as_relations_trav_if(Entity target, Func func) const
Traverses transitive Is descendants of target until func returns true.
Definition world.h:7515
GAIA_NODISCARD Entity try_get(EntityId id) const
Returns the entity for id when it is still live, or EntityBad for stale cleanup-time ids.
Definition world.h:3780
CommandBufferMT & cmd_buffer_mt() const
Returns the multi-thread-safe deferred command buffer owned by the world.
Definition world.h:8316
Buffer for deferred execution of some operations on entities.
Definition command_buffer.h:52
Builds, caches, and executes a Gaia-ECS query.
Definition query.h:507
Wrapper for two Entities forming a relationship pair.
Definition id.h:614
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:262
static ThreadPool & get()
Returns the process-wide thread-pool instance.
Definition threadpool.h:161
Default in-memory binary backend used by ECS world/runtime serialization. Provides aligned raw read/w...
Definition ser_binary.h:12
Same API as ser_buffer_binary, but backed by fully dynamic storage.
Definition ser_buffer_binary.h:161
void save(Writer &writer, const T &data)
Write data using Writer at compile-time.
Definition ser_ct.h:101
Fixed-limit string lookup key carrying a precomputed 32-bit hash.
Definition hashing_string.h:14
const char * str() const
Returns the referenced string.
Definition hashing_string.h:67
Stack-only cursor over raw component bytes and runtime field metadata.
Definition component_cursor.h:186
static GAIA_NODISCARD ComponentCursor from_raw(const ComponentCache &components, Entity component, ComponentRawView view)
Creates a read-only cursor from a raw component view.
Definition component_cursor.h:198
static GAIA_NODISCARD ComponentCursor from_soa(const World &world, const ComponentCache &components, Entity entity, Entity component, uint32_t size)
Creates a read-only cursor over a non-contiguous SoA component value.
Definition component_cursor.h:249
Plain component registration descriptor shared by typed and runtime component paths....
Definition component_desc.h:263
RuntimeTypeDesc runtimeType
Runtime reflection metadata copied during registration.
Definition component_desc.h:299
util::str_view name
Registered component symbol.
Definition component_desc.h:284
Entity-scoped component accessor bound to a specific world, chunk and row. It is not a standalone chu...
Definition component_getter.h:16
GAIA_NODISCARD decltype(auto) get() const
Returns the value stored in the component T on entity.
Definition component_getter.h:38
Entity m_entity
Entity whose components are accessed.
Definition component_getter.h:22
const World * m_pWorld
World used to resolve inherited and sparse component data.
Definition component_getter.h:18
GAIA_NODISCARD ComponentRawView get_raw(Entity component) const
Returns a raw byte view for a runtime component on this entity.
Definition world.h:15197
GAIA_NODISCARD ComponentRawView get_raw_field(Entity component, uint32_t fieldIdx) const
Returns one read-only SoA field value for a runtime component on this entity.
Definition world.h:15203
Non-owning mutable view over raw component bytes on an entity.
Definition component_cursor.h:64
Non-owning read-only view over raw component bytes on an entity.
Definition component_cursor.h:38
Entity-scoped mutable component accessor bound to a specific world, chunk and row....
Definition component_setter.h:16
decltype(auto) mut()
Returns a mutable reference to component without triggering hooks, observers or world-version updates...
Definition component_setter.h:29
GAIA_NODISCARD ComponentRawMutView mut_raw(Entity component)
Returns a mutable raw byte view for a runtime component without finishing the write....
Definition world.h:15209
GAIA_NODISCARD ComponentRawMutView mut_raw_field(Entity component, uint32_t fieldIdx)
Returns one mutable SoA field value without finishing the component write. Pair with modify_raw(compo...
Definition world.h:15215
ComponentSetter & set(U &&value)
Sets the value of the component.
Definition component_setter.h:42
decltype(auto) smut()
Returns a mutable reference to component without triggering a world version update.
Definition component_setter.h:73
ComponentSetter & modify_raw(Entity component)
Marks a raw payload or SoA field returned by a mutable raw view as modified.
Definition world.h:15230
ComponentSetter & sset(U &&value)
Sets the value of the component without triggering a world version update.
Definition component_setter.h:86
ComponentSetter & set_raw(Entity component, const void *data, uint32_t size)
Replaces a runtime component payload and emits normal post-write set notifications.
Definition world.h:15221
Identifier of a registered component type. Packs the component id, size, alignment,...
Definition id.h:42
Component used to describe the entity name.
Definition id.h:597
Hashmap lookup structure used for Entity.
Definition id.h:543
Identifier of an entity or component instance in the world. Packs the entity index,...
Definition id.h:296
GAIA_NODISCARD constexpr auto gen() const noexcept
Generation index of the entity.
Definition id.h:365
GAIA_NODISCARD constexpr bool comp() const noexcept
Whether this id refers to a component.
Definition id.h:383
InternalData data
Structured view of the packed value.
Definition id.h:328
static constexpr uint32_t IdMask
Bit mask covering all valid entity indices.
Definition id.h:298
GAIA_NODISCARD constexpr bool pair() const noexcept
Whether this id refers to a relationship pair.
Definition id.h:377
GAIA_NODISCARD constexpr auto kind() const noexcept
Entity kind of this id.
Definition id.h:389
GAIA_NODISCARD constexpr auto id() const noexcept
Entity index in the entity array.
Definition id.h:359
GAIA_NODISCARD constexpr bool entity() const noexcept
Whether this id refers to an entity.
Definition id.h:371
Temporary VM matching buffer meant to be owned by an ECS World. QueryInfo only acquires a frame while...
Definition query_info.h:67
Entity semantic
Optional named entity identifying the authored semantic.
Definition component_desc.h:111
Runtime reflection metadata embedded in a component descriptor or supplied to typed registration....
Definition component_desc.h:229
Entity semantic
Optional named entity identifying the authored semantic.
Definition component_desc.h:233
const RuntimeFieldInit * fields
Runtime field initializers copied during registration.
Definition component_desc.h:241
uint32_t fieldCount
Number of field initializers.
Definition component_desc.h:243
Scheduler descriptor used by ECS runtime code. All callbacks may be null when the descriptor is only ...
Definition sched.h:71
Applies structural and value changes to one entity.
Definition world.h:1701
EntityBuilder & as(Entity entityBase)
Shortcut for add(Pair(Is, entityBase)). Effectively makes an entity inherit from entityBase.
Definition world.h:2034
void commit()
Commits all gathered changes and performs an archetype movement.
Definition world.h:1769
void del_name()
Removes any name associated with the entity.
Definition world.h:1941
EntityBuilder & child(Entity parent)
Shortcut for add(Pair(ChildOf, parent))
Definition world.h:2055
Archetype * m_pArchetype
Target archetype we want to move to.
Definition world.h:1713
EntityBuilder & del()
Removes the registered component or pair type from the pending entity state.
Definition world.h:2112
GAIA_NODISCARD bool as(Entity entity, Entity entityBase) const
Check if entity inherits from entityBase.
Definition world.h:2048
EntityBuilder & add()
Adds the registered component or pair type to the pending entity state.
Definition world.h:2079
EntityBuilder & add(Pair pair)
Prepares an archetype movement by following the "add" edge of the current archetype.
Definition world.h:2020
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:1920
EntityBuilder & add(Entity entity)
Prepares an archetype movement by following the "add" edge of the current archetype.
Definition world.h:2008
EntityBuilder(World &world, Entity entity)
Creates a builder and fetches the entity's current storage record.
Definition world.h:1749
EntityBuilder & del(Pair pair)
Prepares an archetype movement by following the "del" edge of the current archetype.
Definition world.h:2099
EntityBuilder(World &world, Entity entity, EntityContainer &ec)
Creates a builder from an already fetched entity record.
Definition world.h:1739
Entity register_component()
Takes care of registering the component type used by T.
Definition world.h:2063
EntityBuilder & del(Entity entity)
Prepares an archetype movement by following the "del" edge of the current archetype.
Definition world.h:2088
Entity m_entity
Source entity.
Definition world.h:1719
void alias(const char *alias, uint32_t len=0)
Assigns an alias to entity. Ignored if used with pair. The string is copied and kept internally.
Definition world.h:1928
EntityNameLookupKey m_targetAliasKey
Target alias string pointer.
Definition world.h:1717
EntityBuilder & prefab()
Marks the entity as a prefab.
Definition world.h:2040
EntityNameLookupKey m_targetNameKey
Target name.
Definition world.h:1715
void del_alias()
Removes any alias associated with the entity.
Definition world.h:1974
World & m_world
World receiving the accumulated entity changes.
Definition world.h:1705
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:1905
void alias_raw(const char *alias, uint32_t len=0)
Assigns an alias to entity. Ignored if used with pair. The string is NOT copied. You are responsible ...
Definition world.h:1936
Contiguous destination rows emitted as one CopyIter callback range.
Definition copy_scratch.h:16
Storage for exclusive relation pairs that do not fragment archetypes.
Definition nonfragmenting_relation_store.h:14
GAIA_NODISCARD Entity target(Entity source) const
Returns the target currently bound to source.
Definition nonfragmenting_relation_store.h:60
Detects whether a type is a relationship pair.
Definition id.h:285
static constexpr bool value
True when the type derives from the pair base.
Definition id.h:287
static GAIA_NODISCARD const uint8_t * get(const void *pData, uint32_t alignment, std::span< const uint8_t > fieldSizes, uint32_t fieldIdx, uint32_t row, uint32_t capacity) noexcept
Returns one read-only field value from type-erased SoA storage.
Definition data_layout_policy.h:362
static GAIA_NODISCARD uint8_t * set(void *pData, uint32_t alignment, std::span< const uint8_t > fieldSizes, uint32_t fieldIdx, uint32_t row, uint32_t capacity) noexcept
Returns one mutable field value from type-erased SoA storage.
Definition data_layout_policy.h:384
Runtime serializer type-erased handle. Traversal logic is shared with compile-time serialization,...
Definition ser_rt.h:94
void save_raw(const T &value)
Writes the object representation of a typed value.
Definition ser_rt.h:157
void load(T &arg)
Deserializes a value through generic traversal.
Definition ser_rt.h:126
GAIA_NODISCARD uint32_t tell() const
Returns the current backend cursor.
Definition ser_rt.h:204
void reset()
Resets the backend when supported.
Definition ser_rt.h:196
void save(const T &arg)
Serializes a value through generic traversal.
Definition ser_rt.h:115
void seek(uint32_t pos)
Moves the backend cursor.
Definition ser_rt.h:219
GAIA_NODISCARD bool valid() const
Checks whether mandatory backend callbacks are bound.
Definition ser_rt.h:106
GAIA_NODISCARD const char * data() const
Returns the backend data pointer when exposed.
Definition ser_rt.h:189
void load_raw(T &value)
Reads an object representation into a typed value.
Definition ser_rt.h:165
Lightweight non-owning string view over a character sequence.
Definition str.h:13
GAIA_NODISCARD constexpr uint32_t size() const
Returns the number of characters in the view.
Definition str.h:42
GAIA_NODISCARD constexpr bool empty() const
Checks whether the view contains no characters.
Definition str.h:48
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:332
void append(const char *data, uint32_t size)
Appends size characters from data.
Definition str.h:390
void clear()
Removes all characters from the string.
Definition str.h:353
GAIA_NODISCARD bool empty() const
Checks whether the string contains no characters.
Definition str.h:438
GAIA_NODISCARD str_view view() const
Returns a non-owning view over the current contents.
Definition str.h:444
void reserve(uint32_t len)
Reserves capacity for at least len characters.
Definition str.h:359