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;
90#if GAIA_OBSERVERS_ENABLED
91 friend class ObserverRegistry;
92 friend struct ObserverRuntimeData;
93 friend struct ObserverRegistry::DiffDispatcher;
94 friend struct ObserverRegistry::DirectDispatcher;
95 friend struct ObserverRegistry::SharedDispatch;
96#endif
97 friend struct ComponentGetter;
98 friend struct ComponentSetter;
99 friend void lock(World&);
100 friend void unlock(World&);
101 friend EntityContainer* try_fetch_mut(World&, Entity);
102#if GAIA_USE_SAFE_ENTITY
103 friend void safe_entity_acquire(World&, Entity);
104 friend void safe_entity_release(World&, Entity);
105#endif
106 friend QueryMatchScratch& query_match_scratch_acquire(World&);
107 friend void query_match_scratch_release(World&, bool);
108 friend uint32_t world_component_index_bucket_size(const World&, Entity);
109 friend uint32_t world_component_index_comp_idx(const World&, const Archetype&, Entity);
110 friend uint32_t world_component_index_match_count(const World&, const Archetype&, Entity);
111 template <typename T>
112 friend decltype(auto) world_direct_entity_arg(World& world, Entity entity);
113 template <typename T>
114 friend decltype(auto) world_direct_entity_arg_raw(World& world, Entity entity);
115 template <typename T>
116 friend decltype(auto) world_query_entity_arg_by_id(World& world, Entity entity, Entity id);
117 template <typename T>
118 friend decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id);
119 friend void world_finish_write(World& world, Entity term, Entity entity);
120
121 ser::bin_stream m_stream;
122 ser::serializer m_serializer{};
123
124 using TFunc_Void_With_Entity = void(Entity);
127 static void func_void_with_entity([[maybe_unused]] Entity entity) {}
128
129 using EntityNameLookupKey = core::StringLookupKey<256>;
131 using EntityArrayMap = cnt::map<EntityLookupKey, cnt::darray<Entity>>;
132
133 using NonFragmentingRelationStore = detail::NonFragmentingRelationStore;
134 using SparseComponentStoreErased = detail::SparseComponentStoreErased;
135 using RuntimeSparseComponentStore = detail::RuntimeSparseComponentStore;
136 template <typename T>
137 using SparseComponentStore = detail::SparseComponentStore<T>;
138 using CopyIterGroupState = detail::CopyIterGroupState;
139 using PrefabChildEdge = detail::PrefabChildEdge;
140 using PrefabInstantiatePlanNode = detail::PrefabInstantiatePlanNode;
141
142 template <
143 typename TApi, typename TValue, bool DeriveFromValue = std::is_class_v<TValue> && !std::is_final_v<TValue>>
144 class SetWriteProxyTyped;
145
146 template <typename TApi, typename TValue>
147 class SetWriteProxyTyped<TApi, TValue, true>: public TValue {
148 World* m_pWorld = nullptr;
149 Entity m_entity = EntityBad;
150 Entity m_term = EntityBad;
151
153 void commit() {
154 if (m_pWorld == nullptr)
155 return;
156
157 m_pWorld->template write_back_set_typed<TApi, TValue>(m_entity, m_term, static_cast<const TValue&>(*this));
158 m_pWorld = nullptr;
159 }
160
161 public:
167 SetWriteProxyTyped(World& world, Entity entity, Entity term, const TValue& value):
168 TValue(value), m_pWorld(&world), m_entity(entity), m_term(term) {}
169
175 SetWriteProxyTyped(World& world, Entity entity, Entity term, TValue&& value):
176 TValue(GAIA_MOV(value)), m_pWorld(&world), m_entity(entity), m_term(term) {}
177
178 SetWriteProxyTyped(const SetWriteProxyTyped&) = delete;
179 SetWriteProxyTyped& operator=(const SetWriteProxyTyped&) = delete;
180
181 SetWriteProxyTyped(SetWriteProxyTyped&& other) noexcept:
182 TValue(static_cast<TValue&&>(other)), m_pWorld(other.m_pWorld), m_entity(other.m_entity),
183 m_term(other.m_term) {
184 other.m_pWorld = nullptr;
185 }
186
187 ~SetWriteProxyTyped() {
188 commit();
189 }
190
191 SetWriteProxyTyped& operator=(const TValue& value) {
192 static_cast<TValue&>(*this) = value;
193 return *this;
194 }
195
196 SetWriteProxyTyped& operator=(TValue&& value) {
197 static_cast<TValue&>(*this) = GAIA_MOV(value);
198 return *this;
199 }
200
203 GAIA_NODISCARD operator TValue&() {
204 return *this;
205 }
206
209 GAIA_NODISCARD operator const TValue&() const {
210 return *this;
211 }
212 };
213
214 template <typename TApi, typename TValue>
215 class SetWriteProxyTyped<TApi, TValue, false> {
216 World* m_pWorld = nullptr;
217 Entity m_entity = EntityBad;
218 Entity m_term = EntityBad;
219 TValue m_value{};
220
222 void commit() {
223 if (m_pWorld == nullptr)
224 return;
225
226 m_pWorld->template write_back_set_typed<TApi, TValue>(m_entity, m_term, m_value);
227 m_pWorld = nullptr;
228 }
229
230 public:
236 SetWriteProxyTyped(World& world, Entity entity, Entity term, const TValue& value):
237 m_pWorld(&world), m_entity(entity), m_term(term), m_value(value) {}
238
244 SetWriteProxyTyped(World& world, Entity entity, Entity term, TValue&& value):
245 m_pWorld(&world), m_entity(entity), m_term(term), m_value(GAIA_MOV(value)) {}
246
247 SetWriteProxyTyped(const SetWriteProxyTyped&) = delete;
248 SetWriteProxyTyped& operator=(const SetWriteProxyTyped&) = delete;
249
250 SetWriteProxyTyped(SetWriteProxyTyped&& other) noexcept:
251 m_pWorld(other.m_pWorld), m_entity(other.m_entity), m_term(other.m_term), m_value(GAIA_MOV(other.m_value)) {
252 other.m_pWorld = nullptr;
253 }
254
255 ~SetWriteProxyTyped() {
256 commit();
257 }
258
259 SetWriteProxyTyped& operator=(const TValue& value) {
260 m_value = value;
261 return *this;
262 }
263
264 SetWriteProxyTyped& operator=(TValue&& value) {
265 m_value = GAIA_MOV(value);
266 return *this;
267 }
268
271 GAIA_NODISCARD operator TValue&() {
272 return m_value;
273 }
274
277 GAIA_NODISCARD operator const TValue&() const {
278 return m_value;
279 }
280
283 GAIA_NODISCARD TValue* operator->() {
284 return &m_value;
285 }
286
289 GAIA_NODISCARD const TValue* operator->() const {
290 return &m_value;
291 }
292 };
293
294 template <typename TValue, bool DeriveFromValue = std::is_class_v<TValue> && !std::is_final_v<TValue>>
295 class SetWriteProxyObject;
296
297 template <typename TValue>
298 class SetWriteProxyObject<TValue, true>: public TValue {
299 World* m_pWorld = nullptr;
300 Entity m_entity = EntityBad;
301 Entity m_term = EntityBad;
302
304 void commit() {
305 if (m_pWorld == nullptr)
306 return;
307
308 m_pWorld->template write_back_set_object<TValue>(m_entity, m_term, static_cast<const TValue&>(*this));
309 m_pWorld = nullptr;
310 }
311
312 public:
318 SetWriteProxyObject(World& world, Entity entity, Entity term, const TValue& value):
319 TValue(value), m_pWorld(&world), m_entity(entity), m_term(term) {}
320
326 SetWriteProxyObject(World& world, Entity entity, Entity term, TValue&& value):
327 TValue(GAIA_MOV(value)), m_pWorld(&world), m_entity(entity), m_term(term) {}
328
329 SetWriteProxyObject(const SetWriteProxyObject&) = delete;
330 SetWriteProxyObject& operator=(const SetWriteProxyObject&) = delete;
331
332 SetWriteProxyObject(SetWriteProxyObject&& other) noexcept:
333 TValue(static_cast<TValue&&>(other)), m_pWorld(other.m_pWorld), m_entity(other.m_entity),
334 m_term(other.m_term) {
335 other.m_pWorld = nullptr;
336 }
337
338 ~SetWriteProxyObject() {
339 commit();
340 }
341
342 SetWriteProxyObject& operator=(const TValue& value) {
343 static_cast<TValue&>(*this) = value;
344 return *this;
345 }
346
347 SetWriteProxyObject& operator=(TValue&& value) {
348 static_cast<TValue&>(*this) = GAIA_MOV(value);
349 return *this;
350 }
351
354 GAIA_NODISCARD operator TValue&() {
355 return *this;
356 }
357
360 GAIA_NODISCARD operator const TValue&() const {
361 return *this;
362 }
363 };
364
365 template <typename TValue>
366 class SetWriteProxyObject<TValue, false> {
367 World* m_pWorld = nullptr;
368 Entity m_entity = EntityBad;
369 Entity m_term = EntityBad;
370 TValue m_value{};
371
373 void commit() {
374 if (m_pWorld == nullptr)
375 return;
376
377 m_pWorld->template write_back_set_object<TValue>(m_entity, m_term, m_value);
378 m_pWorld = nullptr;
379 }
380
381 public:
387 SetWriteProxyObject(World& world, Entity entity, Entity term, const TValue& value):
388 m_pWorld(&world), m_entity(entity), m_term(term), m_value(value) {}
389
395 SetWriteProxyObject(World& world, Entity entity, Entity term, TValue&& value):
396 m_pWorld(&world), m_entity(entity), m_term(term), m_value(GAIA_MOV(value)) {}
397
398 SetWriteProxyObject(const SetWriteProxyObject&) = delete;
399 SetWriteProxyObject& operator=(const SetWriteProxyObject&) = delete;
400
401 SetWriteProxyObject(SetWriteProxyObject&& other) noexcept:
402 m_pWorld(other.m_pWorld), m_entity(other.m_entity), m_term(other.m_term), m_value(GAIA_MOV(other.m_value)) {
403 other.m_pWorld = nullptr;
404 }
405
406 ~SetWriteProxyObject() {
407 commit();
408 }
409
410 SetWriteProxyObject& operator=(const TValue& value) {
411 m_value = value;
412 return *this;
413 }
414
415 SetWriteProxyObject& operator=(TValue&& value) {
416 m_value = GAIA_MOV(value);
417 return *this;
418 }
419
422 GAIA_NODISCARD operator TValue&() {
423 return m_value;
424 }
425
428 GAIA_NODISCARD operator const TValue&() const {
429 return m_value;
430 }
431
434 GAIA_NODISCARD TValue* operator->() {
435 return &m_value;
436 }
437
440 GAIA_NODISCARD const TValue* operator->() const {
441 return &m_value;
442 }
443 };
444
449 GAIA_NODISCARD EntityContainer& cont(Entity entity) {
450 if (entity.pair())
451 return fetch(entity);
452 return m_recs.entities[entity.id()];
453 }
454
459 GAIA_NODISCARD const EntityContainer& cont(Entity entity) const {
460 if (entity.pair())
461 return fetch(entity);
462 return m_recs.entities[entity.id()];
463 }
464
469 template <typename T>
470 GAIA_NODISCARD decltype(auto) mut_im(Entity entity) {
471 static_assert(!is_pair<T>::value);
472 using FT = typename component_type_t<T>::TypeFull;
473 const auto& item = add<FT>();
474 if constexpr (uses_ct_sparse_storage<FT>())
475 return sparse_component_store_mut<FT>(item.entity).mut(entity);
476
477 const auto& ec = cont(entity);
478 return ec.pChunk->template set<T>(ec.row);
479 }
480
486 GAIA_NODISCARD const ComponentCacheItem* component_item(Entity entity, Entity component) const {
487 if (!component.pair())
488 return comp_cache().find(component);
489
490 const auto& ec = fetch(entity);
491 const auto compIdx = core::get_index(ec.pChunk->ids_view(), component);
492 return compIdx != BadIndex ? ec.pChunk->comp_rec_view()[compIdx].pItem
493 : comp_cache().find_pair_payload(component);
494 }
495
499 GAIA_NODISCARD static bool raw_component_supported(const ComponentCacheItem& item) noexcept {
500 return item.comp.soa() == 0;
501 }
502
506 GAIA_NODISCARD static bool soa_field_supported(const ComponentCacheItem& item) noexcept {
507 return item.comp.soa() != 0 && item.comp.storage_type() == DataStorageType::Table;
508 }
509
515 GAIA_NODISCARD static bool
516 raw_component_payload_args_valid(const ComponentCacheItem& item, const void* data, uint32_t size) noexcept {
517 if (!raw_component_supported(item))
518 return false;
519 if (size != item.comp.size())
520 return false;
521 return size == 0 || data != nullptr;
522 }
523
529 template <typename T>
530 GAIA_NODISCARD decltype(auto) mut_im(Entity entity, Entity object) {
531 static_assert(!is_pair<T>::value);
532 using FT = typename component_type_t<T>::TypeFull;
533 if constexpr (supports_sparse_component_storage<FT>()) {
534 if (can_use_sparse_component_storage<FT>(object))
535 return sparse_component_mut_value<FT>(object, entity);
536 }
537
538 const auto& ec = cont(entity);
539 return ec.pChunk->template set<T>(ec.row, object);
540 }
541
547 template <typename T, bool TriggerSetEffects>
548 void modify_table_inter(Entity entity, EntityContainer& ec) {
549 (void)entity;
550
551 ec.pChunk->template modify<T, TriggerSetEffects>();
552
553#if GAIA_OBSERVERS_ENABLED
554 if constexpr (TriggerSetEffects) {
555 Entity term = EntityBad;
556 if constexpr (is_pair<T>::value) {
557 const auto rel = comp_cache().template get<typename T::rel>().entity;
558 const auto tgt = comp_cache().template get<typename T::tgt>().entity;
559 term = (Entity)Pair(rel, tgt);
560 } else
561 term = comp_cache().template get<T>().entity;
562
563 world_notify_on_set(*this, term, *ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
564 }
565#else
566 (void)entity;
567#endif
568 }
569
574 template <bool TriggerSetEffects>
575 void modify_table_inter(Entity object, EntityContainer& ec) {
576 const auto compIdx = ec.pChunk->comp_idx(object);
577 GAIA_ASSERT(compIdx != ComponentIndexBad);
578
579 if constexpr (TriggerSetEffects)
580 ec.pChunk->finish_write(compIdx, ec.row, (uint16_t)(ec.row + 1));
581 else
582 ec.pChunk->update_world_version(compIdx);
583 }
584
591 void mark_sparse_component_write(Entity entity, Entity term) {
592 const auto& ec = fetch(entity);
593 const auto compIdx = core::get_index(ec.pChunk->ids_view(), term);
594 if (compIdx != BadIndex)
595 ec.pChunk->update_world_version(compIdx);
596 else
597 ec.pChunk->update_entity_order_version();
598 }
599
605 void mark_non_fragmenting_membership_changed(Entity entity, Entity term) {
606 if (tearing_down())
607 return;
608
609 ::gaia::ecs::update_version(m_worldVersion);
610 mark_sparse_component_write(entity, term);
611 }
612
618 void finish_write(Entity entity, Entity term) {
619 if (tearing_down() || !valid(entity))
620 return;
621
622 ::gaia::ecs::update_version(m_worldVersion);
623
624 if (sparse_storage_mode(term) != SparseStorageMode::None) {
625 mark_sparse_component_write(entity, term);
626 world_notify_on_set_entity(*this, term, entity);
627 return;
628 }
629
630 auto compIdx = uint32_t(BadIndex);
631 {
632 const auto& ec = fetch(entity);
633 compIdx = world_component_index_comp_idx(*this, *ec.pArchetype, term);
634 }
635
636 if (compIdx == BadIndex) {
637 (void) override(entity, term);
638 const auto& ec = fetch(entity);
639 compIdx = world_component_index_comp_idx(*this, *ec.pArchetype, term);
640 if (compIdx == BadIndex)
641 return;
642 }
643
644 const auto& ec = fetch(entity);
645 const auto row = ec.row;
646 (void)ec.pChunk->comp_ptr_mut_gen<true>(compIdx, row);
647 world_notify_on_set_entity(*this, term, entity);
648 }
649
656 template <typename TApi, typename TValue>
657 void write_back_set_typed(Entity entity, Entity term, const TValue& value) {
658 using FT = typename component_type_t<TApi>::TypeFull;
659
660 if constexpr (uses_ct_sparse_storage<FT>()) {
661 sparse_component_store_mut<FT>(term).add(entity) = value;
662 finish_write(entity, term);
663 } else {
664 const auto& ec = fetch(entity);
665 const auto row = ec.row;
666 ComponentSetter{*this, ec.pChunk, entity, row}.sset<TApi>(value);
667 finish_write(entity, term);
668 }
669 }
670
676 template <typename TValue>
677 void write_back_set_object(Entity entity, Entity term, const TValue& value) {
678 using FT = typename component_type_t<TValue>::TypeFull;
679 if constexpr (supports_sparse_component_storage<FT>()) {
680 if (can_use_sparse_component_storage<FT>(term)) {
681 sparse_component_add_value<FT>(term, entity) = value;
682 finish_write(entity, term);
683 return;
684 }
685 }
686
687 const auto& ec = fetch(entity);
688 const auto row = ec.row;
689 ComponentSetter{*this, ec.pChunk, entity, row}.template smut<TValue>(term) = value;
690 finish_write(entity, term);
691 }
692
693 //----------------------------------------------------------------------
694
695 //----------------------------------------------------------------------
696
698 ComponentCache m_compCache;
700 QueryCache m_queryCache;
703 cnt::darray<QueryMatchScratch*> m_queryMatchScratchStack;
705 uint32_t m_queryMatchScratchDepth = 0;
711 QuerySerMap m_querySerMap;
712 uint32_t m_nextQuerySerId = 0;
713
714#if GAIA_OBSERVERS_ENABLED && GAIA_ASSERT_ENABLED
716 uint32_t m_observerCallbackDepth = 0;
717#endif
718
720 EntityToArchetypeMap m_entityToArchetypeMap;
722 EntityToArchetypeVersionMap m_entityToArchetypeMapVersions;
731 PairMap m_entityToAsTargets;
734 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_entityToAsTargetsTravCache;
742 PairMap m_entityToAsRelations;
745 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_entityToAsRelationsTravCache;
748 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_targetsTravCache;
751 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_srcBfsTravCache;
754 mutable cnt::map<EntityLookupKey, uint32_t> m_depthOrderCache;
757 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_sourcesAllCache;
760 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_targetsAllCache;
762 mutable bool m_relationCachesPopulated = false;
764 bool m_hasOnDeleteTargetPolicy = false;
766 bool m_hasCantCombinePolicy = false;
768 bool m_hasRequiresPolicy = false;
770 PairLookup m_pairLookup;
772 cnt::map<EntityLookupKey, NonFragmentingRelationStore> m_nonFragmentingRelationsByRel;
774 cnt::map<EntityLookupKey, SparseComponentStoreErased> m_sparseComponentsByComp;
776 cnt::map<EntityLookupKey, uint32_t> m_relationVersions;
778 Entity m_lastRelationVersionRelation = EntityBad;
780 uint32_t* m_pLastRelationVersion = nullptr;
783 mutable cnt::map<EntityLookupKey, uint32_t> m_srcEntityVersions;
784
785 enum class SparseStorageMode : uint8_t { None, Fragmenting, NonFragmenting };
786
788 ArchetypeDArray m_archetypes;
790 ArchetypeMapByHash m_archetypesByHash;
792 ArchetypeMapById m_archetypesById;
793
795 Archetype* m_pRootArchetype = nullptr;
797 Archetype* m_pEntityArchetype = nullptr;
799 Archetype* m_pCompArchetype = nullptr;
801 ArchetypeId m_nextArchetypeId = 0;
802
804 uint32_t m_defaultMaxLifespan = Archetype::MAX_ARCHETYPE_LIFESPAN;
805
807 EntityContainers m_recs;
809 cnt::map<EntityNameLookupKey, Entity> m_nameToEntity;
811 cnt::map<EntityNameLookupKey, Entity> m_aliasToEntity;
813 Entity m_componentScope = EntityBad;
815 cnt::darray<Entity> m_componentLookupPath;
817 mutable util::str m_componentScopePathCache;
819 mutable Entity m_componentScopePathCacheEntity = EntityBad;
821 mutable bool m_componentScopePathCacheValid = false;
822
824 cnt::set<ArchetypeLookupKey> m_reqArchetypesToDel;
826 cnt::set<EntityLookupKey> m_reqEntitiesToDel;
828 cnt::darray<Entity> m_archetypeRowsToDel;
830 uint32_t m_archetypeRowDelDepth = 0;
832 const EntityContainer* m_pArchetypeRowInRules = nullptr;
833
834#if GAIA_OBSERVERS_ENABLED
836 ObserverRegistry m_observers;
837
839 struct DeferredOnSet {
841 Entity term;
843 Entity entity;
844 };
845
848 cnt::darray<cnt::darray<DeferredOnSet>> m_deferredOnSet;
850 uint32_t m_deferOnSetDepth = 0;
851#endif
852
860 struct DeferredSortInv {
862 Entity entity;
863 };
864
867 cnt::darray<cnt::darray<DeferredSortInv>> m_deferredSortInv;
869 uint32_t m_deferSortInvDepth = 0;
870
871#if GAIA_SYSTEMS_ENABLED
873 SystemRegistry m_systems;
874#endif
875
877 CommandBufferST* m_pCmdBufferST;
879 CommandBufferMT* m_pCmdBufferMT;
881 bool m_teardownActive = false;
883 Query m_systemsQuery;
885 detail::SystemScheduleScratch m_systemScheduleScratch;
887 Sched m_sched{};
889 mutable cnt::darray<uint64_t> m_entityVisitStamps;
891 mutable uint64_t m_entityVisitStamp = 0;
892
893#if GAIA_USE_SAFE_ENTITY
895 uint32_t m_compDtorDepth = 0;
898 cnt::darray<Entity> m_safeEntityDels;
899#endif
901 static constexpr uint32_t EntitiesDeletingScanMax = 16;
903 cnt::darray_ext<Entity, EntitiesDeletingScanMax> m_entitiesDeleting;
907 cnt::set<EntityLookupKey> m_entitiesDeletingSet;
909 cnt::darray<ArchetypeChunkPair> m_chunksToDel;
911 ArchetypeDArray m_archetypesToDel;
913 uint32_t m_defragLastArchetypeIdx = 0;
915 uint32_t m_defragEntitiesPerTick = 100;
916
918 uint32_t m_worldVersion = 0;
920 uint32_t m_enabledHierarchyVersion = 0;
922 uint32_t m_archetypeDeleteVersion = 0;
923
924 uint32_t m_structuralChangesLocked = 0;
925
930 GAIA_NODISCARD const EntityContainer* try_entity_record(Entity entity) const {
931 if (entity.pair())
932 return nullptr;
933
934 const auto* pEc = m_recs.entities.try_get(entity.id());
935 if (pEc == nullptr || pEc->data.gen != entity.gen())
936 return nullptr;
937
938 return pEc;
939 }
940
945 GAIA_NODISCARD const EntityContainer* try_fetch_record(Entity entity) const {
946 if (entity == EntityBad)
947 return nullptr;
948
949 if (entity.pair()) {
950 if (is_wildcard(entity))
951 return nullptr;
952 return m_recs.pair_record_find(entity);
953 }
954
955 return try_entity_record(entity);
956 }
957
961 GAIA_NODISCARD EntityContainer* try_fetch_record(Entity entity) {
962 return const_cast<EntityContainer*>(static_cast<const World*>(this)->try_fetch_record(entity));
963 }
964
968 GAIA_NODISCARD static bool record_uses_non_fragmenting_relation_storage(const EntityContainer& ec) {
969 return (ec.flags & EntityContainerFlags::IsExclusive) != 0 &&
970 (ec.flags & EntityContainerFlags::IsDontFragment) != 0;
971 }
972
973 public:
974 World():
975 // Command buffer for the main thread
976 m_pCmdBufferST(cmd_buffer_st_create(*this)),
977 // Command buffer safe for concurrent access
978 m_pCmdBufferMT(cmd_buffer_mt_create(*this)) {
979 init();
980 }
981
982 ~World() {
983 teardown();
984 done();
985 cmd_buffer_destroy(*m_pCmdBufferST);
986 cmd_buffer_destroy(*m_pCmdBufferMT);
987 }
988
989 World(World&&) = delete;
990 World(const World&) = delete;
991 World& operator=(World&&) = delete;
992 World& operator=(const World&) = delete;
993
994 //----------------------------------------------------------------------
995
1000 return Query(
1001 *const_cast<World*>(this), m_queryCache,
1002 //
1003 m_nextArchetypeId, m_worldVersion, m_entityToArchetypeMap, m_entityToArchetypeMapVersions, m_archetypes);
1004 }
1005
1010 auto q = query();
1011 q.kind(QueryCacheKind::None);
1012 return q;
1013 }
1014
1015#if GAIA_ECS_TEST_HOOKS
1017 GAIA_NODISCARD bool verify_query_cache() const {
1018 return m_queryCache.verify_archetype_tracking();
1019 }
1020
1023 GAIA_NODISCARD uint32_t test_query_cache_count() const {
1024 return m_queryCache.test_query_count();
1025 }
1026#endif
1027
1028 //----------------------------------------------------------------------
1029
1032 void set_sched(const Sched& sched) {
1033 m_sched = sched;
1034 }
1035
1038 m_sched = {};
1039 }
1040
1043 GAIA_NODISCARD const Sched& sched() const {
1044 return sched_resolve(m_sched);
1045 }
1046
1047 //----------------------------------------------------------------------
1048
1052 GAIA_NODISCARD EntityContainer& fetch(Entity entity) {
1053#if GAIA_ASSERT_ENABLED
1054 // Wildcard pairs are not a real entity so we can't accept them
1055 GAIA_ASSERT(!entity.pair() || !is_wildcard(entity));
1056 if (!valid(entity)) {
1057 // Delete-time cleanup can still reference an exact pair record after one endpoint
1058 // has already become invalid. Delete-time observer diffs can also still reference
1059 // a delete-requested, queued-for-delete, or stale removed entity record before it
1060 // is invalidated. Allow these forms so cleanup and observer dispatch can finish.
1061 const bool allowStaleExactPair = entity.pair() && m_recs.pair_record_contains(entity);
1062 const bool allowDeleteRequestedEntity =
1063 !entity.pair() && entity.id() < m_recs.entities.size() &&
1064 m_recs.entities[entity.id()].data.gen == entity.gen() &&
1065 (m_recs.entities[entity.id()].flags & EntityContainerFlags::DeleteRequested) != 0;
1066 const bool allowQueuedDeleteEntity = !entity.pair() && m_reqEntitiesToDel.contains(EntityLookupKey(entity));
1067 const bool allowStaleEntityRecord = !entity.pair() && entity.id() < m_recs.entities.size() &&
1068 m_recs.entities[entity.id()].data.gen == entity.gen() &&
1069 m_recs.entities[entity.id()].pArchetype != nullptr &&
1070 m_recs.entities[entity.id()].pChunk != nullptr;
1071 GAIA_ASSERT(
1072 allowStaleExactPair || allowDeleteRequestedEntity || allowQueuedDeleteEntity || allowStaleEntityRecord);
1073 }
1074#endif
1075 return m_recs[entity];
1076 }
1077
1081 GAIA_NODISCARD const EntityContainer& fetch(Entity entity) const {
1082#if GAIA_ASSERT_ENABLED
1083 // Wildcard pairs are not a real entity so we can't accept them
1084 GAIA_ASSERT(!entity.pair() || !is_wildcard(entity));
1085 if (!valid(entity)) {
1086 // Delete-time cleanup can still reference an exact pair record after one endpoint
1087 // has already become invalid. Delete-time observer diffs can also still reference
1088 // a delete-requested, queued-for-delete, or stale removed entity record before it
1089 // is invalidated. Allow these forms so cleanup and observer dispatch can finish.
1090 const bool allowStaleExactPair = entity.pair() && m_recs.pair_record_contains(entity);
1091 const bool allowDeleteRequestedEntity =
1092 !entity.pair() && entity.id() < m_recs.entities.size() &&
1093 m_recs.entities[entity.id()].data.gen == entity.gen() &&
1094 (m_recs.entities[entity.id()].flags & EntityContainerFlags::DeleteRequested) != 0;
1095 const bool allowQueuedDeleteEntity = !entity.pair() && m_reqEntitiesToDel.contains(EntityLookupKey(entity));
1096 const bool allowStaleEntityRecord = !entity.pair() && entity.id() < m_recs.entities.size() &&
1097 m_recs.entities[entity.id()].data.gen == entity.gen() &&
1098 m_recs.entities[entity.id()].pArchetype != nullptr &&
1099 m_recs.entities[entity.id()].pChunk != nullptr;
1100 GAIA_ASSERT(
1101 allowStaleExactPair || allowDeleteRequestedEntity || allowQueuedDeleteEntity || allowStaleEntityRecord);
1102 }
1103#endif
1104 return m_recs[entity];
1105 }
1106
1107 //----------------------------------------------------------------------
1108
1115 GAIA_NODISCARD bool is_req_del(const EntityContainer& ec) const {
1116 if ((ec.flags & EntityContainerFlags::DeleteRequested) != 0)
1117 return true;
1118 return is_req_del_or_queued(ec) && &ec != m_pArchetypeRowInRules;
1119 }
1120
1125 GAIA_NODISCARD static bool is_req_del_or_queued(const EntityContainer& ec) {
1126 if ((ec.flags & EntityContainerFlags::DeleteRequested) != 0)
1127 return true;
1128 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) == 0);
1129 return ec.pArchetype != nullptr && ec.pArchetype->is_req_del();
1130 }
1131
1136 GAIA_NODISCARD const EntityContainer* try_live_record(Entity entity) const {
1137 const auto* pEc = try_fetch_record(entity);
1138 if (pEc == nullptr || !valid(*pEc, entity))
1139 return nullptr;
1140 return pEc;
1141 }
1142
1146 GAIA_NODISCARD EntityContainer* try_live_record(Entity entity) {
1147 return const_cast<EntityContainer*>(static_cast<const World*>(this)->try_live_record(entity));
1148 }
1149
1150#if GAIA_OBSERVERS_ENABLED
1151 #if GAIA_ASSERT_ENABLED
1153 void observer_callback_enter() {
1154 ++m_observerCallbackDepth;
1155 }
1156
1158 void observer_callback_leave() {
1159 GAIA_ASSERT(m_observerCallbackDepth != 0);
1160 --m_observerCallbackDepth;
1161 }
1162
1164 GAIA_NODISCARD bool observer_callback_active() const {
1165 return m_observerCallbackDepth != 0;
1166 }
1167 #endif
1168#endif
1169
1173 GAIA_NODISCARD bool entity_deletion_active(Entity entity) const {
1174 if GAIA_UNLIKELY (m_entitiesDeleting.size() > EntitiesDeletingScanMax)
1175 return m_entitiesDeletingSet.contains(EntityLookupKey(entity));
1176
1177 for (auto deleting: m_entitiesDeleting) {
1178 if (deleting == entity)
1179 return true;
1180 }
1181 return false;
1182 }
1183
1187 GAIA_ASSERT(!entity_deletion_active(entity));
1188 m_entitiesDeleting.push_back(entity);
1189 if GAIA_UNLIKELY (m_entitiesDeleting.size() > EntitiesDeletingScanMax)
1190 entity_deletion_set_enter();
1191 }
1192
1195 void entity_deletion_leave([[maybe_unused]] Entity entity) {
1196 GAIA_ASSERT(!m_entitiesDeleting.empty());
1197 GAIA_ASSERT(m_entitiesDeleting.back() == entity);
1198 if GAIA_UNLIKELY (m_entitiesDeleting.size() > EntitiesDeletingScanMax)
1199 entity_deletion_set_leave();
1200 m_entitiesDeleting.pop_back();
1201 }
1202
1205 GAIA_NOINLINE void entity_deletion_set_enter() {
1206 if (m_entitiesDeleting.size() == EntitiesDeletingScanMax + 1) {
1207 for (auto deleting: m_entitiesDeleting)
1208 m_entitiesDeletingSet.insert(EntityLookupKey(deleting));
1209 } else
1210 m_entitiesDeletingSet.insert(EntityLookupKey(m_entitiesDeleting.back()));
1211 }
1212
1216 GAIA_NOINLINE void entity_deletion_set_leave() {
1217 if (m_entitiesDeleting.size() == EntitiesDeletingScanMax + 1) {
1218 for (auto deleting: m_entitiesDeleting)
1219 m_entitiesDeletingSet.erase(EntityLookupKey(deleting));
1220 } else
1221 m_entitiesDeletingSet.erase(EntityLookupKey(m_entitiesDeleting.back()));
1222 }
1223
1227 GAIA_NODISCARD bool is_dont_fragment(Entity entity) const {
1228 const auto* pEc = try_entity_record(entity);
1229 return pEc != nullptr && (pEc->flags & EntityContainerFlags::IsDontFragment) != 0;
1230 }
1231
1235 GAIA_NODISCARD bool relation_is_non_fragmenting(Entity relation) const {
1236 return is_dont_fragment(relation);
1237 }
1238
1243 GAIA_NODISCARD bool relation_uses_non_fragmenting_storage(Entity relation) const {
1244 const auto* pEc = try_entity_record(relation);
1245 return pEc != nullptr && record_uses_non_fragmenting_relation_storage(*pEc);
1246 }
1247
1252 GAIA_NODISCARD bool relation_is_hierarchy(Entity relation) const {
1253 const auto* pEc = try_entity_record(relation);
1254 if (pEc == nullptr || (pEc->flags & EntityContainerFlags::IsExclusive) == 0 || pEc->pArchetype == nullptr)
1255 return false;
1256
1257 return pEc->pArchetype->has(Traversable);
1258 }
1259
1264 GAIA_NODISCARD bool relation_is_fragmenting(Entity relation) const {
1265 const auto* pEc = try_entity_record(relation);
1266 if (pEc == nullptr)
1267 return false;
1268
1269 return (pEc->flags & EntityContainerFlags::IsDontFragment) == 0;
1270 }
1271
1276 GAIA_NODISCARD bool relation_is_fragmenting_hierarchy(Entity relation) const {
1277 return relation_is_hierarchy(relation) && relation_is_fragmenting(relation);
1278 }
1279
1286 GAIA_NODISCARD bool relation_supports_depth_order(Entity relation) const {
1287 return relation_is_fragmenting(relation);
1288 }
1289
1295 GAIA_NODISCARD bool relation_depth_order_prunes_disabled_subtrees(Entity relation) const {
1296 return relation_is_fragmenting_hierarchy(relation);
1297 }
1298
1303 GAIA_NODISCARD bool component_uses_sparse_storage(Entity component) const {
1304 if (!component.pair()) {
1305 const auto* pEc = try_entity_record(component);
1306 if (pEc == nullptr || pEc->data.ent != 0)
1307 return false;
1308
1309 const auto* pItem = comp_cache().find(component);
1310 if (pItem == nullptr || pItem->comp.soa() != 0)
1311 return false;
1312
1313 return gaia::ecs::component_uses_sparse_storage(pItem->comp);
1314 }
1315
1316 const auto* pItem = comp_cache().find_pair_payload(component);
1317 return pItem != nullptr && pItem->comp.soa() == 0 && gaia::ecs::component_uses_sparse_storage(pItem->comp);
1318 }
1319
1325 GAIA_NODISCARD bool component_is_non_fragmenting(Entity component) const {
1326 if (component.pair())
1327 return sparse_storage_mode(component) == SparseStorageMode::NonFragmenting;
1328
1329 const auto* pEc = try_entity_record(component);
1330 if (pEc == nullptr || pEc->data.ent != 0)
1331 return false;
1332
1333 const auto flags = pEc->flags;
1334 return (flags & EntityContainerFlags::IsDontFragment) != 0 && (flags & EntityContainerFlags::IsExclusive) == 0;
1335 }
1336
1340 GAIA_NODISCARD SparseStorageMode sparse_storage_mode(Entity component) const {
1341 if (!component.pair()) {
1342 const auto* pEc = try_entity_record(component);
1343 if (pEc == nullptr || pEc->data.ent != 0)
1344 return SparseStorageMode::None;
1345
1346 const auto* pItem = comp_cache().find(component);
1347 if (pItem == nullptr || pItem->comp.soa() != 0 || !gaia::ecs::component_uses_sparse_storage(pItem->comp))
1348 return SparseStorageMode::None;
1349
1350 return (pEc->flags & EntityContainerFlags::IsDontFragment) != 0 ? SparseStorageMode::NonFragmenting
1351 : SparseStorageMode::Fragmenting;
1352 }
1353
1354 const auto* pItem = comp_cache().find_pair_payload(component);
1355 if (pItem == nullptr || pItem->comp.soa() != 0 || !gaia::ecs::component_uses_sparse_storage(pItem->comp))
1356 return SparseStorageMode::None;
1357
1358 return is_dont_fragment(pair_rel(*this, component)) ? SparseStorageMode::NonFragmenting
1359 : SparseStorageMode::Fragmenting;
1360 }
1361
1367 GAIA_NODISCARD bool
1368 copies_sparse_payload_inter(Entity comp, Entity srcEntity, const SparseComponentStoreErased& store) const {
1369 return store.func_has(store.pStore, srcEntity) &&
1370 (component_is_non_fragmenting(comp) || sparse_storage_mode(comp) != SparseStorageMode::None);
1371 }
1372
1379 Entity comp, Entity srcEntity, const SparseComponentStoreErased& store) const {
1380 return store.func_has(store.pStore, srcEntity) && component_is_non_fragmenting(comp);
1381 }
1382
1386 GAIA_NODISCARD bool sparse_copy_adds_id_inter(Entity comp) const {
1387 return component_is_non_fragmenting(comp);
1388 }
1389
1390 //----------------------------------------------------------------------
1391
1396 auto& item = comp_cache_mut().get(component);
1397 item.comp = comp;
1398
1399 auto& ec = m_recs.entities[component.id()];
1400 if (ec.pArchetype == nullptr || ec.pChunk == nullptr)
1401 return;
1402
1403 const auto compIdx = core::get_index(ec.pArchetype->ids_view(), GAIA_ID(Component));
1404 if (compIdx == BadIndex)
1405 return;
1406
1407 auto* pComp = reinterpret_cast<Component*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
1408 *pComp = comp;
1409 }
1410
1415 void validate_runtime_semantics(const RuntimeTypeDesc& runtimeType) const {
1416#if GAIA_ASSERT_ENABLED
1417 auto validate = [&](Entity semantic) {
1418 if (semantic == EntityBad)
1419 return;
1420 util::str path;
1421 GAIA_ASSERT(build_scope_path(semantic, path));
1422 };
1423 validate(runtimeType.semantic);
1424 GAIA_FOR(runtimeType.fieldCount)
1425 validate(runtimeType.fields[i].semantic);
1426#else
1427 (void)runtimeType;
1428#endif
1429 }
1430
1431#if GAIA_JSON_ENABLED
1437 bool write_runtime_schema_json(ser::ser_json& writer, const char* schemaHash, bool includeRuntimeEntities) const;
1438#endif
1439
1445 void finalize_component_registration(const ComponentCacheItem& item, bool addSparseTrait) {
1446 sync_component_record(item.entity, item.comp);
1447 const auto symbol = item.symbol_name();
1448 name_raw(item.entity, symbol.data(), symbol.size());
1449 if (addSparseTrait && item.comp.storage_type() == DataStorageType::Sparse)
1450 add(item.entity, Sparse);
1451 }
1452
1457 void set_component_dont_fragment(Entity component, EntityContainer& ec) {
1458 if (component.comp()) {
1459 const auto& item = comp_cache().get(component);
1460 if (item.comp.size() != 0)
1461 set_component_sparse_storage(component);
1462 }
1463
1464 if ((ec.flags & EntityContainerFlags::IsDontFragment) != 0)
1465 return;
1466
1467 ec.flags |= EntityContainerFlags::IsDontFragment;
1468 invalidate_queries_for_structural_entity(EntityLookupKey(component));
1469 }
1470
1476 GAIA_ASSERT(valid(component));
1477 GAIA_ASSERT(component.comp());
1478 GAIA_ASSERT(!component.pair());
1479 GAIA_ASSERT(!component.entity());
1480
1481 const auto& item = comp_cache().get(component);
1482 GAIA_ASSERT(item.entity == component);
1483
1484 if (item.comp.storage_type() == DataStorageType::Sparse)
1485 return;
1486
1487 GAIA_ASSERT(item.comp.size() != 0);
1488 if (item.comp.size() == 0)
1489 return;
1490
1491 GAIA_ASSERT(item.comp.soa() == 0);
1492 if (item.comp.soa() != 0)
1493 return;
1494
1495 const auto directTermEntityCnt = count_direct_term_entities_direct(component);
1496 GAIA_ASSERT(directTermEntityCnt == 0);
1497 if (directTermEntityCnt != 0)
1498 return;
1499
1500 auto comp = item.comp;
1501 comp.data.storage = (IdentifierData)DataStorageType::Sparse;
1502 sync_component_record(component, comp);
1503 }
1504
1511 GAIA_NODISCARD bool can_add_component_storage_trait(Entity component, Entity trait) const {
1512 if (!component.comp())
1513 return true;
1514
1515 const auto& item = comp_cache().get(component);
1516 if (item.func_create_sparse_store == nullptr || item.comp.storage_type() == DataStorageType::Sparse)
1517 return true;
1518
1519 if (trait.id() != DontFragment.id() || item.comp.size() != 0)
1520 return false;
1521
1522 return count_direct_term_entities_direct(component) == 0;
1523 }
1524
1529 template <typename T>
1530 GAIA_NODISCARD static constexpr bool supports_sparse_component_storage() {
1531 using U = typename actual_type_t<T>::Type;
1532 return !mem::is_soa_layout_v<U>;
1533 }
1534
1538 template <typename T>
1539 GAIA_NODISCARD static constexpr bool uses_ct_sparse_storage() {
1540 return uses_ct_sparse_storage_v<T>;
1541 }
1542
1546 template <typename T>
1548 if constexpr (is_pair<T>::value) {
1549 const auto relation = reg_comp<typename T::rel>().entity;
1550 const auto target = reg_comp<typename T::tgt>().entity;
1551 return Pair(relation, target);
1552 } else
1553 return reg_comp<T>().entity;
1554 }
1555
1561 template <typename T>
1562 GAIA_NODISCARD GAIA_FORCEINLINE Entity register_sparse_component_id(SparseStorageMode& mode) {
1563 const auto object = register_component_id<T>();
1564 auto storageTraitOwner = object;
1565 if constexpr (is_pair<T>::value)
1566 storageTraitOwner = m_recs.entities.handle(object.id());
1567
1568 mode = is_dont_fragment(storageTraitOwner) ? SparseStorageMode::NonFragmenting : SparseStorageMode::Fragmenting;
1569 return object;
1570 }
1571
1576 template <typename T>
1577 GAIA_NODISCARD bool can_use_sparse_component_storage(Entity object) const {
1578 if constexpr (!supports_sparse_component_storage<T>())
1579 return false;
1580 else {
1581 if (try_live_record(object) == nullptr)
1582 return false;
1583
1584 const auto* pItem = object.pair() ? comp_cache().find_pair_payload(object) : comp_cache().find(object);
1585 if (pItem == nullptr || (!object.pair() && pItem->entity != object) ||
1586 !gaia::ecs::component_uses_sparse_storage(pItem->comp))
1587 return false;
1588
1589 using U = typename actual_type_t<T>::Type;
1590 return pItem->comp.size() == (uint32_t)sizeof(U) && pItem->comp.alig() == (uint32_t)alignof(U) &&
1591 pItem->comp.soa() == 0;
1592 }
1593 }
1594
1599 template <typename T>
1600 GAIA_NODISCARD auto* sparse_component_store(Entity component) {
1601 using U = typename actual_type_t<T>::Type;
1602 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1603 if (it == m_sparseComponentsByComp.end())
1604 return (SparseComponentStore<U>*)nullptr;
1605
1606 return static_cast<SparseComponentStore<U>*>(it->second.pStore);
1607 }
1608
1613 template <typename T>
1614 GAIA_NODISCARD const auto* sparse_component_store(Entity component) const {
1615 using U = typename actual_type_t<T>::Type;
1616 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1617 if (it == m_sparseComponentsByComp.end())
1618 return (const SparseComponentStore<U>*)nullptr;
1619
1620 return static_cast<const SparseComponentStore<U>*>(it->second.pStore);
1621 }
1622
1627 template <typename T>
1628 GAIA_NODISCARD auto& sparse_component_store_mut(Entity component) {
1629 using U = typename actual_type_t<T>::Type;
1630 const auto key = EntityLookupKey(component);
1631 const auto it = m_sparseComponentsByComp.find(key);
1632 if (it != m_sparseComponentsByComp.end())
1633 return *static_cast<SparseComponentStore<U>*>(it->second.pStore);
1634
1635 auto* pStore = new SparseComponentStore<U>();
1636 m_sparseComponentsByComp.emplace(key, make_sparse_component_store_erased(pStore));
1637 return *pStore;
1638 }
1639
1645 template <typename T>
1646 GAIA_NODISCARD decltype(auto) sparse_component_add_value(Entity component, Entity entity) {
1647 using U = typename actual_type_t<T>::Type;
1648 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1649 if (it == m_sparseComponentsByComp.end()) {
1650 const auto* pItem =
1651 component.pair() ? comp_cache().find_pair_payload(component) : comp_cache().find(component);
1652 GAIA_ASSERT(pItem != nullptr);
1653 auto& store = sparse_component_store_erased_mut(component, *pItem);
1654 return *(U*)store.func_add(store.pStore, entity);
1655 }
1656 return *(U*)it->second.func_add(it->second.pStore, entity);
1657 }
1658
1664 template <typename T>
1665 GAIA_NODISCARD decltype(auto) sparse_component_mut_value(Entity component, Entity entity) {
1666 using U = typename actual_type_t<T>::Type;
1667 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1668 GAIA_ASSERT(it != m_sparseComponentsByComp.end());
1669 return *(U*)it->second.func_mut(it->second.pStore, entity);
1670 }
1671
1677 template <typename T>
1678 GAIA_NODISCARD decltype(auto) sparse_component_get_value(Entity component, Entity entity) const {
1679 using U = typename actual_type_t<T>::Type;
1680 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1681 GAIA_ASSERT(it != m_sparseComponentsByComp.end());
1682 return *(const U*)it->second.func_get(it->second.pStore, entity);
1683 }
1684
1690 template <typename T>
1691 GAIA_NODISCARD decltype(auto) sparse_component_get_entity_value(Entity component, Entity entity) const {
1692 using U = typename actual_type_t<T>::Type;
1693 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1694 GAIA_ASSERT(it != m_sparseComponentsByComp.end());
1695 return *(const U*)it->second.func_get_entity(it->second.pStore, entity);
1696 }
1697
1701 GAIA_NODISCARD const SparseComponentStoreErased* sparse_component_store_erased(Entity component) const {
1702 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1703 return it != m_sparseComponentsByComp.end() ? &it->second : nullptr;
1704 }
1705
1710 GAIA_NODISCARD SparseComponentStoreErased&
1711 sparse_component_store_erased_mut(Entity component, const ComponentCacheItem& item) {
1712 const auto key = EntityLookupKey(component);
1713 const auto it = m_sparseComponentsByComp.find(key);
1714 if (it != m_sparseComponentsByComp.end())
1715 return it->second;
1716 if (item.func_create_sparse_store != nullptr) {
1717 item.func_create_sparse_store(*this, component);
1718 return m_sparseComponentsByComp.find(key)->second;
1719 }
1720
1721 auto* pStore = new RuntimeSparseComponentStore(item);
1722 return m_sparseComponentsByComp.emplace(key, make_sparse_component_store_erased(pStore)).first->second;
1723 }
1724
1730 void finish_sparse_component_add_inter(Entity entity, Entity object, SparseStorageMode mode) {
1731 GAIA_ASSERT(mode != SparseStorageMode::None);
1732
1733#if GAIA_OBSERVERS_ENABLED
1734 auto ctx =
1735 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
1736#endif
1737 if (object.pair() || mode == SparseStorageMode::Fragmenting) {
1738 GAIA_ASSERT(!locked());
1739 EntityBuilder eb(*this, entity);
1740 eb.add_inter_init(object, false);
1741 eb.commit();
1742 }
1743
1744 notify_add_single(entity, object);
1745#if GAIA_OBSERVERS_ENABLED
1746 m_observers.finish_diff(*this, GAIA_MOV(ctx));
1747#endif
1748 }
1749
1753 for (auto& [compKey, store]: m_sparseComponentsByComp) {
1754 (void)compKey;
1755 store.func_del(store.pStore, entity);
1756 }
1757 }
1758
1762 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1763 if (it == m_sparseComponentsByComp.end())
1764 return;
1765
1766 it->second.func_clear_store(it->second.pStore);
1767 it->second.func_del_store(it->second.pStore);
1768 m_sparseComponentsByComp.erase(it);
1769 }
1770
1775 const auto it = m_nonFragmentingRelationsByRel.find(EntityLookupKey(relation));
1776 if (it == m_nonFragmentingRelationsByRel.end())
1777 return nullptr;
1778
1779 return &it->second;
1780 }
1781
1786 return m_nonFragmentingRelationsByRel[EntityLookupKey(relation)];
1787 }
1788
1794 void
1796 GAIA_ASSERT(relation_uses_non_fragmenting_storage(relation));
1797 if (store.set(source, target))
1798 invalidate_relation_caches(relation);
1799 }
1800
1805 void nonfragmenting_relation_set(Entity source, Entity relation, Entity target) {
1806 auto& store = nonfragmenting_relation_store_mut(relation);
1807 nonfragmenting_relation_set(store, source, relation, target);
1808 }
1809
1815 bool nonfragmenting_relation_del(Entity source, Entity relation, Entity target) {
1816 const auto itStore = m_nonFragmentingRelationsByRel.find(EntityLookupKey(relation));
1817 if (itStore == m_nonFragmentingRelationsByRel.end())
1818 return false;
1819
1820 auto& store = itStore->second;
1821 if (!store.remove(source, target))
1822 return false;
1823
1824 if (store.empty())
1825 m_nonFragmentingRelationsByRel.erase(itStore);
1826
1827 invalidate_relation_caches(relation);
1828
1829 return true;
1830 }
1831
1836 GAIA_NODISCARD bool has_nonfragmenting_relation_pair(Entity source, Entity object) const {
1837 if (!object.pair())
1838 return false;
1839
1840 const auto relId = object.id();
1841 const auto tgtId = object.gen();
1842
1843 if (relId != All.id()) {
1844 const auto relation = get(relId);
1845 if (!relation_uses_non_fragmenting_storage(relation))
1846 return false;
1847
1848 const auto* pStore = nonfragmenting_relation_store(relation);
1849 if (pStore == nullptr)
1850 return false;
1851
1852 const auto target = pStore->target(source);
1853 if (target == EntityBad)
1854 return false;
1855
1856 return tgtId == All.id() || target.id() == tgtId;
1857 }
1858
1859 if (tgtId == All.id()) {
1860 for (const auto& it: m_nonFragmentingRelationsByRel) {
1861 if (it.second.target(source) != EntityBad)
1862 return true;
1863 }
1864 return false;
1865 }
1866
1867 const auto target = get(tgtId);
1868 for (const auto& it: m_nonFragmentingRelationsByRel) {
1869 if (it.second.target(source) == target)
1870 return true;
1871 }
1872
1873 return false;
1874 }
1875
1879 cnt::darray<Entity> relations;
1880 for (const auto& it: m_nonFragmentingRelationsByRel) {
1881 const auto relation = it.first.entity();
1882 if (it.second.target(source) != EntityBad)
1883 relations.push_back(relation);
1884 }
1885 if (relations.empty())
1886 return;
1887
1888 for (auto relation: relations) {
1889 touch_rel_version(relation);
1890 invalidate_queries_for_rel(relation);
1891 (void)nonfragmenting_relation_del(source, relation, EntityBad);
1892 }
1893 clear_relation_caches();
1894 }
1895
1896#if GAIA_OBSERVERS_ENABLED
1899 void del_nonfragmenting_relation_source_observed(Entity source) {
1900 if (!m_observers.has_on_del_observers())
1901 return;
1902
1903 cnt::darray<Entity> pairs;
1904 for (const auto& it: m_nonFragmentingRelationsByRel) {
1905 const auto relation = it.first.entity();
1906 const auto target = it.second.target(source);
1907 if (target != EntityBad)
1908 pairs.push_back(Pair(relation, target));
1909 }
1910 if (pairs.empty())
1911 return;
1912
1913 const auto& ec = fetch(source);
1914 for (auto pair: pairs) {
1915 const auto relation = try_get(pair.id());
1916 const auto target = try_get(pair.gen());
1917 if (relation == EntityBad || target == EntityBad)
1918 continue;
1919
1920 const Entity object = Pair(relation, target);
1921 auto delDiffCtx =
1922 m_observers.prepare_diff(*this, ObserverEvent::OnDel, EntitySpan{&object, 1}, EntitySpan{&source, 1});
1923 if (nonfragmenting_relation_del(source, relation, target))
1924 m_observers.on_del(*this, *ec.pArchetype, EntitySpan{&object, 1}, EntitySpan{&source, 1});
1925 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
1926 }
1927 }
1928#endif
1929
1933 const auto itStore = m_nonFragmentingRelationsByRel.find(EntityLookupKey(relation));
1934 if (itStore == m_nonFragmentingRelationsByRel.end())
1935 return;
1936
1937 cnt::darray<EntityId> sourceIds;
1938 cnt::darray<Entity> pairSources;
1939 itStore->second.collect_source_ids(sourceIds);
1940 itStore->second.collect_pair_sources(pairSources);
1941
1942 touch_rel_version(relation);
1943 invalidate_queries_for_rel(relation);
1944 for (auto sourceId: sourceIds)
1945 (void)nonfragmenting_relation_del(get(sourceId), relation, EntityBad);
1946 for (auto source: pairSources)
1947 (void)nonfragmenting_relation_del(source, relation, EntityBad);
1948 clear_relation_caches();
1949 }
1950
1955 GAIA_NODISCARD bool has_nonfragmenting_relation_target_cond(Entity target, Pair cond) const {
1956 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
1957 if (store.sources(target) == nullptr)
1958 continue;
1959
1960 if (has(relKey.entity(), cond))
1961 return true;
1962 }
1963
1964 return false;
1965 }
1966
1967 //----------------------------------------------------------------------
1968
1970 void set_serializer(std::nullptr_t) {
1971 // Always use the binary serializer as the default.
1972 m_serializer = ser::make_serializer(m_stream);
1973 }
1974
1978 GAIA_ASSERT(serializer.valid());
1979 m_serializer = serializer;
1980 }
1981
1984 template <typename TSerializer>
1985 void set_serializer(TSerializer& serializer) {
1986 set_serializer(ser::make_serializer(serializer));
1987 }
1988
1992 return m_serializer;
1993 }
1994
1995 //----------------------------------------------------------------------
1996
1998 struct EntityBuilder final {
1999 friend class World;
2000 friend CommandBufferST;
2001 friend CommandBufferMT;
2002 friend CommandBufferPlainST;
2003 friend CommandBufferPlainMT;
2004
2008 Archetype* m_pArchetypeSrc = nullptr;
2010 Chunk* m_pChunkSrc = nullptr;
2012 uint32_t m_rowSrc = 0;
2014 Archetype* m_pArchetype = nullptr;
2022 using RelationMutationPath = detail::RelationMutationPath;
2023
2024#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2025 static constexpr uint32_t MAX_TERMS = 32;
2026 static_assert(MAX_TERMS <= ChunkHeader::MAX_COMPONENTS);
2027
2032#endif
2033
2034#if GAIA_OBSERVERS_ENABLED
2036 cnt::darray_ext<Entity, MAX_TERMS> tl_del_nonfragmenting_relations;
2038 cnt::darray_ext<ObserverRegistry::DiffDispatchCtx, 4> tl_add_relation_diff_contexts;
2040 cnt::darray_ext<ObserverRegistry::DiffDispatchCtx, 4> tl_del_relation_diff_contexts;
2041#endif
2042
2047 EntityBuilder(World& world, Entity entity, EntityContainer& ec):
2048 m_world(world), m_pArchetypeSrc(ec.pArchetype), m_pChunkSrc(ec.pChunk), m_rowSrc(ec.row),
2049 m_pArchetype(ec.pArchetype), m_entity(entity) {
2050 // Make sure entity matches the provided entity container record
2051 GAIA_ASSERT(ec.pChunk->entity_view()[ec.row] == entity);
2052 }
2053
2057 EntityBuilder(World& world, Entity entity): m_world(world), m_entity(entity) {
2058 const auto& ec = world.fetch(entity);
2059 m_pArchetypeSrc = ec.pArchetype;
2060 m_pChunkSrc = ec.pChunk;
2061 m_rowSrc = ec.row;
2062
2063 m_pArchetype = ec.pArchetype;
2064 }
2065
2068
2071 EntityBuilder(EntityBuilder&& other) noexcept:
2072 m_world(other.m_world), m_pArchetypeSrc(other.m_pArchetypeSrc), m_pChunkSrc(other.m_pChunkSrc),
2073 m_rowSrc(other.m_rowSrc), m_pArchetype(other.m_pArchetype), m_targetNameKey(other.m_targetNameKey),
2074 m_targetAliasKey(other.m_targetAliasKey), m_entity(other.m_entity)
2075#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2076 ,
2077 tl_new_comps(GAIA_MOV(other.tl_new_comps)), tl_del_comps(GAIA_MOV(other.tl_del_comps))
2078#endif
2079#if GAIA_OBSERVERS_ENABLED
2080 ,
2081 tl_del_nonfragmenting_relations(GAIA_MOV(other.tl_del_nonfragmenting_relations)),
2082 tl_add_relation_diff_contexts(GAIA_MOV(other.tl_add_relation_diff_contexts)),
2083 tl_del_relation_diff_contexts(GAIA_MOV(other.tl_del_relation_diff_contexts))
2084#endif
2085 {
2086 other.m_pArchetype = nullptr;
2087 }
2088
2089 EntityBuilder& operator=(const EntityBuilder&) = delete;
2090 EntityBuilder& operator=(EntityBuilder&&) = delete;
2091
2092 ~EntityBuilder() {
2093 commit();
2094 }
2095
2098 void commit() {
2099 // No requests to change the archetype were made
2100 if (m_pArchetype == nullptr) {
2101 return;
2102 }
2103
2104 // Change in archetype detected
2105 if (m_pArchetypeSrc != m_pArchetype) {
2106 auto& ec = m_world.fetch(m_entity);
2107 GAIA_ASSERT(ec.pArchetype == m_pArchetypeSrc);
2108#if GAIA_OBSERVERS_ENABLED
2109 const bool hasOnDelObservers = !tl_del_comps.empty() && m_world.m_observers.has_on_del_observers();
2110 const bool hasOnAddObservers = !tl_new_comps.empty() && m_world.m_observers.has_on_add_observers();
2111 const bool hasNonfragmentingAdds = hasOnAddObservers && has_nonfragmenting_adds();
2112 auto delDiffCtx = !hasOnDelObservers ? ObserverRegistry::DiffDispatchCtx{}
2113 : m_world.m_observers.prepare_diff(
2114 m_world, ObserverEvent::OnDel, EntitySpan{tl_del_comps},
2115 EntitySpan{&m_entity, 1});
2116 auto addDiffCtx =
2117 !hasOnAddObservers ? ObserverRegistry::DiffDispatchCtx{}
2118 : hasNonfragmentingAdds
2119 ? m_world.m_observers.prepare_diff_add_new(m_world, EntitySpan{tl_new_comps})
2120 : m_world.m_observers.prepare_diff(
2121 m_world, ObserverEvent::OnAdd, EntitySpan{tl_new_comps}, EntitySpan{&m_entity, 1});
2122 if (hasNonfragmentingAdds)
2123 m_world.m_observers.add_diff_targets(m_world, addDiffCtx, EntitySpan{&m_entity, 1});
2124#endif
2125
2126#if GAIA_OBSERVERS_ENABLED
2127 if (hasOnDelObservers)
2128 flush_del_nonfragmenting_relations();
2129#endif
2130
2131 // Trigger remove hooks if there are any
2132 trigger_del_hooks(*m_pArchetype);
2133
2134 // Now that we have the final archetype move the entity to it
2135 m_world.move_entity_raw(m_entity, ec, *m_pArchetype);
2136
2137 if (m_targetNameKey.str() != nullptr || m_targetAliasKey.str() != nullptr) {
2138 const auto compIdx = ec.pChunk->comp_idx(GAIA_ID(EntityDesc));
2139 // No need to update version, entity move did it already.
2140 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut_gen<false>(compIdx, ec.row));
2141 GAIA_ASSERT(core::check_alignment(pDesc));
2142
2143 // Update the entity name string pointers if necessary
2144 if (m_targetNameKey.str() != nullptr) {
2145 pDesc->name = m_targetNameKey.str();
2146 pDesc->name_len = m_targetNameKey.len();
2147 }
2148
2149 // Update the entity alias string pointers if necessary
2150 if (m_targetAliasKey.str() != nullptr) {
2151 pDesc->alias = m_targetAliasKey.str();
2152 pDesc->alias_len = m_targetAliasKey.len();
2153 }
2154 }
2155
2156 // Trigger add hooks if there are any
2157 trigger_add_hooks(*m_pArchetype);
2158#if GAIA_OBSERVERS_ENABLED
2159 if (hasOnDelObservers)
2160 m_world.m_observers.finish_diff(m_world, GAIA_MOV(delDiffCtx));
2161 if (hasOnAddObservers)
2162 m_world.m_observers.finish_diff(m_world, GAIA_MOV(addDiffCtx));
2163#endif
2164 cleanup_deleted_sparse_components();
2165
2166 m_pArchetypeSrc = ec.pArchetype;
2167 m_pChunkSrc = ec.pChunk;
2168 m_rowSrc = ec.row;
2169 }
2170 // Archetype is still the same. Make sure no chunk movement has happened.
2171 else {
2172#if GAIA_ASSERT_ENABLED
2173 auto& ec = m_world.fetch(m_entity);
2174 GAIA_ASSERT(ec.pChunk == m_pChunkSrc);
2175#endif
2176
2177#if GAIA_OBSERVERS_ENABLED
2178 const bool hasOnDelObservers = !tl_del_comps.empty() && m_world.m_observers.has_on_del_observers();
2179 const bool hasOnAddObservers = !tl_new_comps.empty() && m_world.m_observers.has_on_add_observers();
2180 const bool hasNonfragmentingAdds = hasOnAddObservers && has_nonfragmenting_adds();
2181 auto delDiffCtx = !hasOnDelObservers ? ObserverRegistry::DiffDispatchCtx{}
2182 : m_world.m_observers.prepare_diff(
2183 m_world, ObserverEvent::OnDel, EntitySpan{tl_del_comps},
2184 EntitySpan{&m_entity, 1});
2185 auto addDiffCtx =
2186 !hasOnAddObservers ? ObserverRegistry::DiffDispatchCtx{}
2187 : hasNonfragmentingAdds
2188 ? m_world.m_observers.prepare_diff_add_new(m_world, EntitySpan{tl_new_comps})
2189 : m_world.m_observers.prepare_diff(
2190 m_world, ObserverEvent::OnAdd, EntitySpan{tl_new_comps}, EntitySpan{&m_entity, 1});
2191 if (hasNonfragmentingAdds)
2192 m_world.m_observers.add_diff_targets(m_world, addDiffCtx, EntitySpan{&m_entity, 1});
2193#endif
2194
2195#if GAIA_OBSERVERS_ENABLED
2196 if (hasOnDelObservers)
2197 flush_del_nonfragmenting_relations();
2198#endif
2199
2200 if (m_targetNameKey.str() != nullptr || m_targetAliasKey.str() != nullptr) {
2201 const auto compIdx = m_pChunkSrc->comp_idx(GAIA_ID(EntityDesc));
2202 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<true>(compIdx, m_rowSrc));
2203 GAIA_ASSERT(core::check_alignment(pDesc));
2204
2205 // Update the entity name string pointers if necessary
2206 if (m_targetNameKey.str() != nullptr) {
2207 pDesc->name = m_targetNameKey.str();
2208 pDesc->name_len = m_targetNameKey.len();
2209 }
2210
2211 // Update the entity alias string pointers if necessary
2212 if (m_targetAliasKey.str() != nullptr) {
2213 pDesc->alias = m_targetAliasKey.str();
2214 pDesc->alias_len = m_targetAliasKey.len();
2215 }
2216 }
2217
2218#if GAIA_OBSERVERS_ENABLED
2219 if (hasOnDelObservers)
2220 m_world.m_observers.on_del(m_world, *m_pArchetypeSrc, EntitySpan{tl_del_comps}, EntitySpan{&m_entity, 1});
2221 if (hasOnAddObservers)
2222 m_world.m_observers.on_add(m_world, *m_pArchetypeSrc, EntitySpan{tl_new_comps}, EntitySpan{&m_entity, 1});
2223 if (hasOnDelObservers)
2224 m_world.m_observers.finish_diff(m_world, GAIA_MOV(delDiffCtx));
2225 if (hasOnAddObservers)
2226 m_world.m_observers.finish_diff(m_world, GAIA_MOV(addDiffCtx));
2227#endif
2228 cleanup_deleted_sparse_components();
2229 }
2230
2231#if GAIA_OBSERVERS_ENABLED
2232 finish_relation_diff_contexts();
2233#endif
2234
2235 // Finalize the builder by reseting the archetype pointer
2236 m_pArchetype = nullptr;
2237 m_targetNameKey = {};
2238 m_targetAliasKey = {};
2239 }
2240
2248 void name(const char* name, uint32_t len = 0) {
2249 name_inter<true>(name, len);
2250 }
2251
2263 void name_raw(const char* name, uint32_t len = 0) {
2264 name_inter<false>(name, len);
2265 }
2266
2271 void alias(const char* alias, uint32_t len = 0) {
2272 alias_inter<true>(alias, len);
2273 }
2274
2279 void alias_raw(const char* alias, uint32_t len = 0) {
2280 alias_inter<false>(alias, len);
2281 }
2282
2284 void del_name() {
2285 if (m_entity.pair())
2286 return;
2287
2288 // The following block is essentially the same as this but without the archetype pointer access:
2289 // const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
2290 const auto compIdx = core::get_index(m_pChunkSrc->ids_view(), GAIA_ID(EntityDesc));
2291 if (compIdx == BadIndex)
2292 return;
2293
2294 {
2295 const auto* pDesc = reinterpret_cast<const EntityDesc*>(m_pChunkSrc->comp_ptr(compIdx, m_rowSrc));
2296 GAIA_ASSERT(core::check_alignment(pDesc));
2297 if (pDesc->name == nullptr)
2298 return;
2299 }
2300
2301 // TODO: Trigger the hooks/observers manually here? I do not like essentially calling comp_ptr twice here.
2302 // The second one could be replaced with const cast + emit.
2303 // No need to update version, commit() will do it
2304 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<false>(compIdx, m_rowSrc));
2305 del_name_inter(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0));
2306 m_world.invalidate_scope_path_cache();
2307
2308 pDesc->name = nullptr;
2309 pDesc->name_len = 0;
2310 if (pDesc->alias == nullptr)
2311 del_inter(GAIA_ID(EntityDesc));
2312
2313 m_targetNameKey = {};
2314 }
2315
2317 void del_alias() {
2318 if (m_entity.pair())
2319 return;
2320
2321 // The following block is essentially the same as this but without the archetype pointer access:
2322 // const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
2323 const auto compIdx = core::get_index(m_pChunkSrc->ids_view(), GAIA_ID(EntityDesc));
2324 if (compIdx == BadIndex)
2325 return;
2326
2327 {
2328 const auto* pDesc = reinterpret_cast<const EntityDesc*>(m_pChunkSrc->comp_ptr(compIdx, m_rowSrc));
2329 GAIA_ASSERT(core::check_alignment(pDesc));
2330 if (pDesc->alias == nullptr)
2331 return;
2332 }
2333
2334 // TODO: Trigger the hooks/observers manually here? I do not like essentially calling comp_ptr twice here.
2335 // The second one could be replaced with const cast + emit.
2336 // No need to update version, commit() will do it
2337 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<false>(compIdx, m_rowSrc));
2338 del_alias_inter(EntityNameLookupKey(pDesc->alias, pDesc->alias_len, 0));
2339
2340 pDesc->alias = nullptr;
2341 pDesc->alias_len = 0;
2342 if (pDesc->name == nullptr)
2343 del_inter(GAIA_ID(EntityDesc));
2344
2345 m_targetAliasKey = {};
2346 }
2347
2352 GAIA_PROF_SCOPE(EntityBuilder::add);
2353 GAIA_ASSERT(m_world.valid(m_entity));
2354 GAIA_ASSERT(m_world.valid(entity));
2355
2356 add_inter(entity);
2357 return *this;
2358 }
2359
2365 GAIA_PROF_SCOPE(EntityBuilder::add);
2366 GAIA_ASSERT(m_world.valid(m_entity));
2367 GAIA_ASSERT(m_world.valid(pair.first()));
2368 GAIA_ASSERT(m_world.valid(pair.second()));
2369 GAIA_ASSERT(!pair.first().pair());
2370 GAIA_ASSERT(!pair.second().pair());
2371 GAIA_ASSERT(pair.first() != Is || !m_entity.pair());
2372
2373 add_inter(pair);
2374 return *this;
2375 }
2376
2381 EntityBuilder& as(Entity entityBase) {
2382 GAIA_ASSERT(!m_entity.pair());
2383 GAIA_ASSERT(!entityBase.pair());
2384 return add(Pair(Is, entityBase));
2385 }
2386
2390 return add(Prefab);
2391 }
2392
2397 GAIA_NODISCARD bool as(Entity entity, Entity entityBase) const {
2398 return static_cast<const World&>(m_world).is(entity, entityBase);
2399 }
2400
2405 return add(Pair(ChildOf, parent));
2406 }
2407
2411 template <typename T>
2413 return m_world.template register_component_id<T>();
2414 }
2415
2419 template <typename T>
2421 verify_comp<T>();
2422 add(register_component<T>());
2423 return *this;
2424 }
2425
2430 GAIA_PROF_SCOPE(EntityBuilder::del);
2431 GAIA_ASSERT(m_world.valid(m_entity));
2432 GAIA_ASSERT(m_world.valid(entity));
2433 del_inter(entity);
2434 return *this;
2435 }
2436
2441 GAIA_PROF_SCOPE(EntityBuilder::add);
2442 GAIA_ASSERT(m_world.valid(m_entity));
2443 GAIA_ASSERT(m_world.valid(pair.first()));
2444 GAIA_ASSERT(m_world.valid(pair.second()));
2445 del_inter(pair);
2446 return *this;
2447 }
2448
2452 template <typename T>
2454 verify_comp<T>();
2455 del(register_component<T>());
2456 return *this;
2457 }
2458
2459 private:
2462 void trigger_add_hooks(const Archetype& newArchetype) {
2463#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2464 if GAIA_UNLIKELY (m_world.tearing_down()) {
2465 tl_new_comps.clear();
2466 (void)newArchetype;
2467 return;
2468 }
2469
2470 m_world.lock();
2471
2472 #if GAIA_ENABLE_ADD_DEL_HOOKS
2473 // if (hookCnt > 0)
2474 {
2475 // Trigger component hooks first
2476 for (auto entity: tl_new_comps) {
2477 const auto* pItem = m_world.component_item(m_entity, entity);
2478 if (pItem == nullptr)
2479 continue;
2480 const auto& hooks = pItem->hooks();
2481 if (hooks.func_add != nullptr)
2482 hooks.func_add(m_world, *pItem, m_entity);
2483 }
2484 }
2485 #endif
2486
2487 #if GAIA_OBSERVERS_ENABLED
2488 // Trigger observers second
2489 if (m_world.m_observers.has_on_add_observers())
2490 m_world.m_observers.on_add(m_world, newArchetype, std::span<Entity>{tl_new_comps}, {&m_entity, 1});
2491 #else
2492 (void)newArchetype;
2493 #endif
2494
2495 tl_new_comps.clear();
2496
2497 m_world.unlock();
2498#endif
2499 }
2500
2503 void trigger_del_hooks(const Archetype& newArchetype) {
2504#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2505 if GAIA_UNLIKELY (m_world.tearing_down()) {
2506 tl_del_comps.clear();
2507 (void)newArchetype;
2508 return;
2509 }
2510
2511 m_world.notify_inherited_del_dependents(m_entity, std::span<Entity>{tl_del_comps});
2512 m_world.lock();
2513
2514 #if GAIA_OBSERVERS_ENABLED
2515 // Trigger observers first
2516 if (m_world.m_observers.has_on_del_observers())
2517 m_world.m_observers.on_del(m_world, newArchetype, std::span<Entity>{tl_del_comps}, {&m_entity, 1});
2518 #else
2519 (void)newArchetype;
2520 #endif
2521
2522 #if GAIA_ENABLE_ADD_DEL_HOOKS
2523 // if (hookCnt > 0)
2524 {
2525 // Trigger component hooks second
2526 for (auto entity: tl_del_comps) {
2527 const auto* pItem = m_world.component_item(m_entity, entity);
2528 if (pItem == nullptr)
2529 continue;
2530 const auto& hooks = pItem->hooks();
2531 if (hooks.func_del != nullptr)
2532 hooks.func_del(m_world, *pItem, m_entity);
2533 }
2534 }
2535 #endif
2536
2537 m_world.unlock();
2538#endif
2539 }
2540
2542 void cleanup_deleted_sparse_components() {
2543 for (auto entity: tl_del_comps) {
2544 if (entity.pair() || m_world.sparse_storage_mode(entity) != SparseStorageMode::Fragmenting)
2545 continue;
2546
2547 const auto it = m_world.m_sparseComponentsByComp.find(EntityLookupKey(entity));
2548 if (it != m_world.m_sparseComponentsByComp.end())
2549 it->second.func_del(it->second.pStore, m_entity);
2550 }
2551
2552 tl_del_comps.clear();
2553 }
2554
2555#if GAIA_OBSERVERS_ENABLED
2557 void finish_relation_diff_contexts() {
2558 for (auto& ctx: tl_del_relation_diff_contexts)
2559 m_world.m_observers.finish_diff(m_world, GAIA_MOV(ctx));
2560 tl_del_relation_diff_contexts.clear();
2561 for (auto& ctx: tl_add_relation_diff_contexts)
2562 m_world.m_observers.finish_diff(m_world, GAIA_MOV(ctx));
2563 tl_add_relation_diff_contexts.clear();
2564 }
2565
2568 GAIA_NODISCARD bool has_nonfragmenting_adds() const {
2569 for (auto entity: tl_new_comps) {
2570 if (entity.pair() && relation_mutation_path(entity) == RelationMutationPath::NonFragmentingExclusive)
2571 return true;
2572 }
2573 return false;
2574 }
2575
2577 void flush_del_nonfragmenting_relations() {
2578 for (auto entity: tl_del_nonfragmenting_relations)
2579 del_nonfragmenting_relation_id(entity);
2580 tl_del_nonfragmenting_relations.clear();
2581 }
2582#endif
2583
2588 bool handle_add_entity(Entity entity, RelationMutationPath relationPath) {
2589 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> targets;
2590 const bool isPair = entity.pair();
2591 GAIA_ASSERT(isPair || relationPath == RelationMutationPath::Fragmenting);
2592
2593 if (isPair)
2594 m_world.invalidate_scope_path_cache();
2595
2596 // Handle entity combinations that can't be together
2597 if (m_world.m_hasCantCombinePolicy) {
2598 const auto& ecMain = m_world.fetch(entity);
2599 if ((ecMain.flags & EntityContainerFlags::HasCantCombine) != 0) {
2600 m_world.targets(entity, CantCombine, [&targets](Entity target) {
2601 targets.push_back(target);
2602 });
2603 for (auto e: targets) {
2604 if (m_pArchetype->has(e)) {
2605#if GAIA_ASSERT_ENABLED
2606 GAIA_ASSERT2(false, "Trying to add an entity which can't be combined with the source");
2607 print_archetype_entities(m_world, *m_pArchetype, entity, true);
2608#endif
2609 return false;
2610 }
2611 }
2612 }
2613 }
2614
2615 // Handle archetype-backed exclusive pairs. Exclusive non-fragmenting relations replace their target in
2616 // non-fragmenting relation storage and never need archetype-pair replacement checks.
2617 if (isPair && relationPath != RelationMutationPath::NonFragmentingExclusive) {
2618 // Check if (rel, tgt)'s rel part is exclusive
2619 const auto& ecRel = m_world.m_recs.entities[entity.id()];
2621 if ((ecRel.flags & EntityContainerFlags::IsExclusive) != 0 && m_pArchetype->pairs() != 0) {
2622 auto rel = Entity(entity.id(), ecRel.data.gen, (bool)ecRel.data.ent, (bool)ecRel.data.pair);
2623 auto tgt = m_world.try_get(entity.gen());
2624 if (tgt == EntityBad)
2625 return false;
2626
2627 const auto targetsCntMax = m_pArchetype->pair_matches(Pair(rel, All));
2628 if (targetsCntMax > 1) {
2629#if GAIA_ASSERT_ENABLED
2630 GAIA_ASSERT2(
2631 false, "Trying to add a pair with exclusive relationship but there are multiple targets present. "
2632 "Make sure to add the Exclusive property before any relationships with it are created.");
2633 print_archetype_entities(m_world, *m_pArchetype, entity, true);
2634#endif
2635 return false;
2636 }
2637
2638 if (targetsCntMax == 1) {
2639 // Make sure to remove the (rel, tgt0) so only the new (rel, tgt1) remains.
2640 targets.clear();
2641 m_world.targets_if(m_entity, rel, [&targets](Entity target) {
2642 targets.push_back(target);
2643 return false;
2644 });
2645
2646 GAIA_ASSERT(targets.size() <= 1);
2647 if (!targets.empty() && tgt != targets[0]) {
2648 // Exclusive relationship replaces the previous one.
2649 // We need to check if the old one can be removed.
2650 // This is what del_inter does on the inside.
2651 // It first checks if entity can be deleted and calls handle_del afterwards.
2652 if (!can_del(entity)) {
2653#if GAIA_ASSERT_ENABLED
2654 GAIA_ASSERT2(
2655 false,
2656 "Trying to replace an exclusive relationship but the entity which is getting removed has "
2657 "dependencies.");
2658 print_archetype_entities(m_world, *m_pArchetype, entity, true);
2659#endif
2660 return false;
2661 }
2662
2663 handle_del(ecs::Pair(rel, targets[0]));
2664 }
2665 }
2666 }
2667 }
2668
2669 // Handle requirements
2670 if (m_world.m_hasRequiresPolicy) {
2671 targets.clear();
2672 m_world.targets(entity, Requires, [&targets](Entity target) {
2673 targets.push_back(target);
2674 });
2675
2676 for (auto e: targets) {
2677 auto* pArchetype = m_pArchetype;
2678 handle_add<false>(e);
2679 if (m_pArchetype != pArchetype) {
2680 const auto requiredPath = e.pair() ? relation_mutation_path_add(e) : RelationMutationPath::Fragmenting;
2681 handle_add_entity(e, requiredPath);
2682 }
2683 }
2684 }
2685
2686 return true;
2687 }
2688
2692 GAIA_NODISCARD bool has_Requires_tgt(Entity entity) const {
2693 if (!m_world.m_hasRequiresPolicy)
2694 return false;
2695
2696 // Don't allow to delete entity if something in the archetype requires it
2697 auto ids = m_pArchetype->ids_view();
2698 for (auto e: ids) {
2699 if (m_world.has(e, Pair(Requires, entity)))
2700 return true;
2701 }
2702
2703 return false;
2704 }
2705
2710 static void set_flag(EntityContainerFlagsType& flags, EntityContainerFlags flag, bool enable) {
2711 if (enable)
2712 flags |= flag;
2713 else
2714 flags &= ~flag;
2715 }
2716
2721 void set_flag(Entity entity, EntityContainerFlags flag, bool enable) {
2722 auto& ec = m_world.fetch(entity);
2723 set_flag(ec.flags, flag, enable);
2724 }
2725
2729 void try_set_flags(Entity entity, bool enable) {
2730 if (entity.pair()) {
2731 if (entity.id() == Is.id()) {
2732 auto& ec = m_world.fetch(entity);
2733 try_set_Is(ec, entity, enable);
2734 }
2735
2736 if (entity.id() == CantCombine.id() || entity.id() == OnDelete.id() || entity.id() == OnDeleteTarget.id()) {
2737 auto& ecMain = m_world.fetch(m_entity);
2738 try_set_CantCombine(ecMain, entity, enable);
2739 try_set_OnDelete(ecMain, entity, enable);
2740 try_set_OnDeleteTargetPolicy(ecMain, entity, enable);
2741 }
2742 if (enable && entity.id() == Requires.id())
2743 m_world.m_hasRequiresPolicy = true;
2744
2745 if (m_world.m_hasOnDeleteTargetPolicy)
2746 try_set_OnDeleteTarget(entity, enable);
2747 return;
2748 }
2749
2750 auto& ecMain = m_world.fetch(m_entity);
2751 try_set_CantCombine(ecMain, entity, enable);
2752
2753 auto& ec = m_world.fetch(entity);
2754 try_set_Is(ec, entity, enable);
2755 try_set_IsExclusive(ecMain, entity, enable);
2756 if (enable)
2757 try_set_sticky_component_traits(ecMain, entity);
2758 try_set_IsSingleton(ecMain, entity, enable);
2759 try_set_OnDelete(ecMain, entity, enable);
2760 try_set_OnDeleteTargetPolicy(ecMain, entity, enable);
2761 }
2762
2767 void try_set_Is(EntityContainer& ec, Entity entity, bool enable) {
2768 if (!entity.pair() || entity.id() != Is.id())
2769 return;
2770
2771 set_flag(ec.flags, EntityContainerFlags::HasAliasOf, enable);
2772 }
2773
2778 void try_set_CantCombine(EntityContainer& ec, Entity entity, bool enable) {
2779 if (!entity.pair() || entity.id() != CantCombine.id())
2780 return;
2781
2782 GAIA_ASSERT(entity != m_entity);
2783
2784 // Setting the flag can be done right away.
2785 // One bit can only contain information about one pair but there
2786 // can be any amount of CanCombine pairs formed with an entity.
2787 // Therefore, when resetting the flag, we first need to check if there
2788 // are any other targets with this flag set and only reset the flag
2789 // if there is only one present.
2790 if (enable) {
2791 m_world.m_hasCantCombinePolicy = true;
2792 set_flag(ec.flags, EntityContainerFlags::HasCantCombine, true);
2793 } else if ((ec.flags & EntityContainerFlags::HasCantCombine) != 0) {
2794 uint32_t targets = 0;
2795 m_world.targets(m_entity, CantCombine, [&targets]([[maybe_unused]] Entity entity) {
2796 ++targets;
2797 });
2798 if (targets == 1)
2799 set_flag(ec.flags, EntityContainerFlags::HasCantCombine, false);
2800 }
2801 }
2802
2807 void try_set_IsExclusive(EntityContainer& ec, Entity entity, bool enable) {
2808 if (entity.pair() || entity.id() != Exclusive.id())
2809 return;
2810
2811 set_flag(ec.flags, EntityContainerFlags::IsExclusive, enable);
2812 }
2813
2817 void try_set_sticky_component_traits(EntityContainer& ecMain, Entity entity) {
2818 if (entity.pair())
2819 return;
2820
2821 if (entity.id() == DontFragment.id()) {
2822 m_world.set_component_dont_fragment(m_entity, ecMain);
2823 return;
2824 }
2825
2826 if (entity.id() == Sparse.id())
2827 m_world.set_component_sparse_storage(m_entity);
2828 }
2829
2833 GAIA_NODISCARD RelationMutationPath relation_mutation_path(Entity entity) const noexcept {
2834 GAIA_ASSERT(entity.pair());
2835
2836 return relation_mutation_path_from_flags(m_world.fetch(m_world.get(entity.id())).flags);
2837 }
2838
2842 GAIA_NODISCARD RelationMutationPath relation_mutation_path_add(Entity entity) const noexcept {
2843 GAIA_ASSERT(entity.pair());
2844
2845 const auto& ecRel = m_world.m_recs.entities[entity.id()];
2846 return relation_mutation_path_from_flags(ecRel.flags);
2847 }
2848
2852 GAIA_NODISCARD static RelationMutationPath
2853 relation_mutation_path_from_flags(EntityContainerFlagsType flags) noexcept {
2854 const auto nonFragmentingExclusiveFlags =
2855 EntityContainerFlags::IsExclusive | EntityContainerFlags::IsDontFragment;
2856 if ((flags & nonFragmentingExclusiveFlags) == nonFragmentingExclusiveFlags)
2857 return RelationMutationPath::NonFragmentingExclusive;
2858
2859 if ((flags & EntityContainerFlags::IsDontFragment) == 0)
2860 return RelationMutationPath::Fragmenting;
2861
2862 return RelationMutationPath::NonFragmentingArchetypePair;
2863 }
2864
2868 GAIA_NODISCARD bool has_archetype_id(Entity entity) const {
2869 return m_pArchetype->has(entity);
2870 }
2871
2875 GAIA_NODISCARD bool has_nonfragmenting_relation_id(Entity entity) const {
2876 GAIA_ASSERT(entity.pair());
2877 return m_world.has_nonfragmenting_relation_pair(m_entity, entity);
2878 }
2879
2883 GAIA_NODISCARD bool has_nonfragmenting_archetype_pair_id(Entity entity) const {
2884 GAIA_ASSERT(entity.pair());
2885 return has_archetype_id(entity);
2886 }
2887
2890 void add_archetype_id(Entity entity) {
2891 m_pArchetype = m_world.foc_archetype_add_no_graph(m_pArchetype, entity);
2892 }
2893
2897 GAIA_NODISCARD bool add_id(Entity entity) {
2898 add_archetype_id(entity);
2899 return true;
2900 }
2901
2904 void del_nonfragmenting_relation_id(Entity entity) {
2905 GAIA_ASSERT(entity.pair());
2906
2907 const auto relation = m_world.try_get(entity.id());
2908 const auto target = m_world.try_get(entity.gen());
2909 if (relation != EntityBad && target != EntityBad)
2910 (void)m_world.nonfragmenting_relation_del(m_entity, relation, target);
2911 }
2912
2915 void del_archetype_id(Entity entity) {
2916 m_pArchetype = m_world.foc_archetype_del_no_graph(m_pArchetype, entity);
2917 }
2918
2921 void del_id(Entity entity) {
2922 del_archetype_id(entity);
2923 }
2924
2929 void try_set_OnDeleteTargetPolicy(EntityContainer& ec, Entity entity, bool enable) {
2930 if (entity == Pair(OnDeleteTarget, Delete))
2931 set_flag(ec.flags, EntityContainerFlags::OnDeleteTarget_Delete, enable);
2932 else if (entity == Pair(OnDeleteTarget, Remove))
2933 set_flag(ec.flags, EntityContainerFlags::OnDeleteTarget_Remove, enable);
2934 else if (entity == Pair(OnDeleteTarget, Error))
2935 set_flag(ec.flags, EntityContainerFlags::OnDeleteTarget_Error, enable);
2936 else
2937 return;
2938
2939 if (enable)
2940 m_world.m_hasOnDeleteTargetPolicy = true;
2941 }
2942
2946 void try_set_OnDeleteTarget(Entity entity, bool enable) {
2947 if (!entity.pair())
2948 return;
2949
2950 if (enable) {
2951 GAIA_ASSERT(m_world.valid(m_world.get(entity.id())));
2952 GAIA_ASSERT(m_world.valid(m_world.get(entity.gen())));
2953
2954 const auto& ecRel = m_world.m_recs.entities[entity.id()];
2955 const auto policyFlags = ecRel.flags & (EntityContainerFlags::OnDeleteTarget_Delete |
2956 EntityContainerFlags::OnDeleteTarget_Remove |
2957 EntityContainerFlags::OnDeleteTarget_Error);
2958 if (policyFlags == 0)
2959 return;
2960
2961 auto& ecTgt = m_world.m_recs.entities[entity.gen()];
2962 if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Delete) != 0)
2963 set_flag(ecTgt.flags, EntityContainerFlags::OnDeleteTarget_Delete, true);
2964 else if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Remove) != 0)
2965 set_flag(ecTgt.flags, EntityContainerFlags::OnDeleteTarget_Remove, true);
2966 else if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Error) != 0)
2967 set_flag(ecTgt.flags, EntityContainerFlags::OnDeleteTarget_Error, true);
2968 return;
2969 }
2970
2971 const auto rel = m_world.try_get(entity.id());
2972 if (rel == EntityBad)
2973 return;
2974
2975 const auto& ecRel = m_world.fetch(rel);
2976 const auto policyFlags =
2977 ecRel.flags & (EntityContainerFlags::OnDeleteTarget_Delete | EntityContainerFlags::OnDeleteTarget_Remove |
2978 EntityContainerFlags::OnDeleteTarget_Error);
2979 if (policyFlags == 0)
2980 return;
2981
2982 const auto tgt = m_world.try_get(entity.gen());
2983 if (tgt == EntityBad)
2984 return;
2985
2986 // Adding a pair to an entity with OnDeleteTarget relationship.
2987 // We need to update the target entity's flags.
2988 if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Delete) != 0)
2989 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Delete, enable);
2990 else if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Remove) != 0)
2991 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Remove, enable);
2992 else if ((policyFlags & EntityContainerFlags::OnDeleteTarget_Error) != 0)
2993 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Error, enable);
2994 }
2995
3000 void try_set_OnDelete(EntityContainer& ec, Entity entity, bool enable) {
3001 if (entity == Pair(OnDelete, Delete))
3002 set_flag(ec.flags, EntityContainerFlags::OnDelete_Delete, enable);
3003 else if (entity == Pair(OnDelete, Remove))
3004 set_flag(ec.flags, EntityContainerFlags::OnDelete_Remove, enable);
3005 else if (entity == Pair(OnDelete, Error))
3006 set_flag(ec.flags, EntityContainerFlags::OnDelete_Error, enable);
3007 }
3008
3013 void try_set_IsSingleton(EntityContainer& ec, Entity entity, bool enable) {
3014 const bool isSingleton = enable && m_entity == entity;
3015 set_flag(ec.flags, EntityContainerFlags::IsSingleton, isSingleton);
3016 }
3017
3021 void handle_DependsOn(Entity entity, bool enable) {
3022 (void)entity;
3023 (void)enable;
3024 // auto& ec = m_world.fetch(entity);
3025 // if (enable) {
3026 // // Calculate the depth in the dependency tree
3027 // uint32_t depth = 1;
3028
3029 // auto e = entity;
3030 // if (m_world.valid(e)) {
3031 // while (true) {
3032 // auto tgt = m_world.target(e, DependsOn);
3033 // if (tgt == EntityBad)
3034 // break;
3035
3036 // ++depth;
3037 // e = tgt;
3038 // }
3039 // }
3040 // ec.depthDependsOn = (uint8_t)depth;
3041
3042 // // Update depth for all entities depending on this one
3043 // auto q = m_world.uquery();
3044 // q.all(ecs::Pair(DependsOn, m_entity)) //
3045 // .each([&](Entity dependingEntity) {
3046 // auto& ecDependingEntity = m_world.fetch(dependingEntity);
3047 // ecDependingEntity.depthDependsOn += (uint8_t)depth;
3048 // });
3049 // } else {
3050 // // Update depth for all entities depending on this one
3051 // auto q = m_world.uquery();
3052 // q.all(ecs::Pair(DependsOn, m_entity)) //
3053 // .each([&](Entity dependingEntity) {
3054 // auto& ecDependingEntity = m_world.fetch(dependingEntity);
3055 // ecDependingEntity.depthDependsOn -= ec.depthDependsOn;
3056 // });
3057
3058 // // Reset the depth
3059 // ec.depthDependsOn = 0;
3060 // }
3061 }
3062
3065 void invalidate_relation_change(Entity entity) {
3066 GAIA_ASSERT(entity.pair());
3067
3068 auto relation = m_world.m_recs.entities.handle(entity.id());
3069 m_world.invalidate_relation_caches(relation);
3070 }
3071
3075 GAIA_NODISCARD bool link_is_relation(Entity entity) {
3076 GAIA_ASSERT(entity.pair());
3077
3078 if (entity.id() != Is.id())
3079 return true;
3080
3081 GAIA_ASSERT(!m_entity.pair());
3082
3083 auto e = m_world.try_get(entity.gen());
3084 if (e == EntityBad)
3085 return false;
3086
3087 EntityLookupKey entityKey(m_entity);
3088 EntityLookupKey eKey(e);
3089
3090 // m_entity -> {..., e}
3091 auto& entity_to_e = m_world.m_entityToAsTargets[entityKey];
3092 entity_to_e.insert(eKey);
3093 m_world.m_entityToAsTargetsTravCache = {};
3094 // e -> {..., m_entity}
3095 auto& e_to_entity = m_world.m_entityToAsRelations[eKey];
3096 e_to_entity.insert(entityKey);
3097 m_world.m_entityToAsRelationsTravCache = {};
3098
3099 // Make sure the relation entity is registered as archetype so queries can find it
3100 // auto& ec = m_world.fetch(tgt);
3101 // m_world.add_entity_archetype_pair(m_entity, ec.pArchetype);
3102
3103 // Cached queries might need to be invalidated.
3104 m_world.invalidate_queries_for_entity({Is, e});
3105 return true;
3106 }
3107
3110 void unlink_is_relation(Entity entity) {
3111 GAIA_ASSERT(entity.pair());
3112
3113 if (entity.id() != Is.id())
3114 return;
3115
3116 auto e = m_world.try_get(entity.gen());
3117 if (e != EntityBad)
3118 m_world.unlink_live_is_relation(m_entity, e);
3119 }
3120
3124 template <bool IsBootstrap>
3125 void finish_add_id(Entity entity) {
3126 if constexpr (!IsBootstrap) {
3127 handle_DependsOn(entity, true);
3128
3129#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
3130 tl_new_comps.push_back(entity);
3131#endif
3132 }
3133 }
3134
3137 void finish_del_id(Entity entity) {
3138#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
3139 tl_del_comps.push_back(entity);
3140#endif
3141 }
3142
3145 void add_sparse_pair_payload(Entity pair) {
3146 if (!m_world.component_uses_sparse_storage(pair))
3147 return;
3148
3149 const auto* pItem = m_world.comp_cache().find_pair_payload(pair);
3150 GAIA_ASSERT(pItem != nullptr);
3151 auto& store = m_world.sparse_component_store_erased_mut(pair, *pItem);
3152 (void)store.func_add(store.pStore, m_entity);
3153 }
3154
3157 void add_nonfragmenting_component_id(Entity component) {
3158 const auto* pItem = m_world.comp_cache().find(component);
3159 GAIA_ASSERT(pItem != nullptr);
3160 if (pItem == nullptr)
3161 return;
3162
3163 auto& store = m_world.sparse_component_store_erased_mut(component, *pItem);
3164 (void)store.func_add(store.pStore, m_entity);
3165 }
3166
3169 void del_nonfragmenting_component_id(Entity component) {
3170 const auto it = m_world.m_sparseComponentsByComp.find(EntityLookupKey(component));
3171 if (it != m_world.m_sparseComponentsByComp.end())
3172 it->second.func_del(it->second.pStore, m_entity);
3173 }
3174
3177 void del_sparse_pair_payload(Entity pair) {
3178 const auto it = m_world.m_sparseComponentsByComp.find(EntityLookupKey(pair));
3179 if (it != m_world.m_sparseComponentsByComp.end())
3180 it->second.func_del(it->second.pStore, m_entity);
3181 }
3182
3187 template <bool IsBootstrap>
3188 bool handle_add(Entity entity) {
3189 if (entity.pair()) {
3190 const auto relationPath = relation_mutation_path_add(entity);
3191 if (relationPath == RelationMutationPath::NonFragmentingExclusive)
3192 return handle_add_nonfragmenting_relation<IsBootstrap>(entity);
3193
3194 return handle_add_archetype_relation<IsBootstrap>(entity);
3195 }
3196
3197#if GAIA_ASSERT_ENABLED
3198 World::verify_add(m_world, *m_pArchetype, m_entity, entity);
3199#endif
3200
3201 // Don't add the same entity twice
3202 if (has_archetype_id(entity))
3203 return false;
3204
3205 if (m_world.component_is_non_fragmenting(entity)) {
3206 if (m_world.has_direct_sparse_component_inter(m_entity, entity))
3207 return false;
3208
3209 try_set_flags(entity, true);
3210 if constexpr (!IsBootstrap) {
3211#if GAIA_OBSERVERS_ENABLED
3212 auto addDiffCtx = m_world.m_observers.prepare_diff(
3213 m_world, ObserverEvent::OnAdd, EntitySpan{&entity, 1}, EntitySpan{&m_entity, 1});
3214#endif
3215 add_nonfragmenting_component_id(entity);
3216 handle_DependsOn(entity, true);
3217 m_world.mark_non_fragmenting_membership_changed(m_entity, entity);
3218 m_world.notify_add_single(m_entity, entity);
3219#if GAIA_OBSERVERS_ENABLED
3220 m_world.m_observers.finish_diff(m_world, GAIA_MOV(addDiffCtx));
3221#endif
3222 } else {
3223 add_nonfragmenting_component_id(entity);
3224 }
3225 return true;
3226 }
3227
3228 if (is_component_storage_trait(entity) && !m_world.can_add_component_storage_trait(m_entity, entity))
3229 return false;
3230
3231 try_set_flags(entity, true);
3232
3233 if (!add_id(entity))
3234 return false;
3235
3236 finish_add_id<IsBootstrap>(entity);
3237
3238 return true;
3239 }
3240
3246 template <bool IsBootstrap>
3247 bool handle_add_archetype_relation(Entity entity, bool addSparsePayload = true) {
3248 GAIA_ASSERT(entity.pair());
3249
3250#if GAIA_ASSERT_ENABLED
3251 World::verify_add(m_world, *m_pArchetype, m_entity, entity);
3252#endif
3253
3254 // Don't add the same pair twice.
3255 if (has_archetype_id(entity))
3256 return false;
3257
3258#if GAIA_OBSERVERS_ENABLED
3259 ObserverRegistry::DiffDispatchCtx relationDiffCtx{};
3260 if constexpr (!IsBootstrap) {
3261 if (entity.id() == Is.id())
3262 relationDiffCtx = m_world.m_observers.prepare_diff(
3263 m_world, ObserverEvent::OnAdd, EntitySpan{&entity, 1}, EntitySpan{&m_entity, 1});
3264 }
3265#endif
3266
3267 invalidate_relation_change(entity);
3268
3269 try_set_flags(entity, true);
3270 if (!link_is_relation(entity))
3271 return false;
3272
3273#if GAIA_OBSERVERS_ENABLED
3274 if (relationDiffCtx.active)
3275 tl_add_relation_diff_contexts.push_back(GAIA_MOV(relationDiffCtx));
3276#endif
3277
3278 add_archetype_id(entity);
3279 if (addSparsePayload)
3280 add_sparse_pair_payload(entity);
3281 finish_add_id<IsBootstrap>(entity);
3282 return true;
3283 }
3284
3291 template <bool IsBootstrap>
3292 bool handle_add_nonfragmenting_relation(
3293 Entity entity, bool addSparsePayload = true, bool pairAlreadyPrepared = false) {
3294 GAIA_ASSERT(entity.pair());
3295
3296 // Don't add the same pair twice.
3297 if (!pairAlreadyPrepared && has_nonfragmenting_relation_id(entity))
3298 return false;
3299
3300 const auto relation = m_world.try_get(entity.id());
3301 const auto target = m_world.try_get(entity.gen());
3302 if (relation == EntityBad || target == EntityBad)
3303 return false;
3304
3305 auto& relationStore = m_world.nonfragmenting_relation_store_mut(relation);
3306 const auto oldTarget = relationStore.target(m_entity);
3307 if (oldTarget != EntityBad && oldTarget != target) {
3308 const Entity oldPair = Pair(relation, oldTarget);
3309#if GAIA_OBSERVERS_ENABLED
3310 if constexpr (!IsBootstrap) {
3311 auto delDiffCtx = m_world.m_observers.prepare_diff(
3312 m_world, ObserverEvent::OnDel, EntitySpan{&oldPair, 1}, EntitySpan{&m_entity, 1});
3313 if (delDiffCtx.active) {
3314 delDiffCtx.targetsRemovedAfterPrepare = true;
3315 tl_del_relation_diff_contexts.push_back(GAIA_MOV(delDiffCtx));
3316 }
3317 }
3318#endif
3319 try_set_flags(oldPair, false);
3320 handle_DependsOn(oldPair, false);
3321 unlink_is_relation(oldPair);
3322 del_sparse_pair_payload(oldPair);
3323 finish_del_id(oldPair);
3324 }
3325
3326 try_set_flags(entity, true);
3327 if (!link_is_relation(entity))
3328 return false;
3329
3330#if GAIA_OBSERVERS_ENABLED
3331 if constexpr (!IsBootstrap) {
3332 auto addDiffCtx = m_world.m_observers.prepare_diff_add_new(m_world, EntitySpan{&entity, 1});
3333 m_world.m_observers.add_diff_targets(m_world, addDiffCtx, EntitySpan{&m_entity, 1});
3334 if (addDiffCtx.active && !addDiffCtx.targetsAddedAfterPrepare)
3335 tl_add_relation_diff_contexts.push_back(GAIA_MOV(addDiffCtx));
3336 }
3337#endif
3338
3339 m_world.nonfragmenting_relation_set(relationStore, m_entity, relation, target);
3340 if (addSparsePayload)
3341 add_sparse_pair_payload(entity);
3342
3343 finish_add_id<IsBootstrap>(entity);
3344
3345 return true;
3346 }
3347
3350 void handle_del(Entity entity) {
3351 if (entity.pair() && !m_world.valid(entity)) {
3352 del_sparse_pair_payload(entity);
3353
3354 if (m_pArchetype->has(entity)) {
3355 const auto relation = m_world.try_get(entity.id());
3356 if (relation != EntityBad) {
3357 m_world.invalidate_relation_caches(relation);
3358 }
3359
3360 if (entity.id() == Is.id())
3361 m_world.unlink_stale_is_relations_by_target_id(m_entity, entity.gen());
3362
3363 m_pArchetype = m_world.foc_archetype_del_no_graph(m_pArchetype, entity);
3364 }
3365 return;
3366 }
3367
3368 if (entity.pair()) {
3369 m_world.invalidate_scope_path_cache();
3370 if (is_wildcard(entity)) {
3371 handle_del_nonfragmenting_wildcard(entity);
3372 handle_del_archetype_wildcard(entity);
3373 return;
3374 }
3375
3376 const auto relationPath = relation_mutation_path(entity);
3377 if (relationPath == RelationMutationPath::NonFragmentingExclusive)
3378 handle_del_nonfragmenting_relation(entity);
3379 else
3380 handle_del_archetype_relation(entity);
3381 return;
3382 }
3383
3384#if GAIA_ASSERT_ENABLED
3385 World::verify_del(m_world, *m_pArchetype, m_entity, entity);
3386#endif
3387
3388 // Don't delete what has not beed added
3389 if (!has_archetype_id(entity)) {
3390 if (m_world.component_is_non_fragmenting(entity) &&
3391 m_world.has_direct_sparse_component_inter(m_entity, entity)) {
3392 try_set_flags(entity, false);
3393 handle_DependsOn(entity, false);
3394#if GAIA_OBSERVERS_ENABLED
3395 auto delDiffCtx = m_world.m_observers.prepare_diff(
3396 m_world, ObserverEvent::OnDel, EntitySpan{&entity, 1}, EntitySpan{&m_entity, 1});
3397#endif
3398 del_nonfragmenting_component_id(entity);
3399 m_world.mark_non_fragmenting_membership_changed(m_entity, entity);
3400 m_world.notify_del_single(m_entity, entity);
3401#if GAIA_OBSERVERS_ENABLED
3402 m_world.m_observers.finish_diff(m_world, GAIA_MOV(delDiffCtx));
3403#endif
3404 }
3405 return;
3406 }
3407
3408 try_set_flags(entity, false);
3409 handle_DependsOn(entity, false);
3410
3411 del_id(entity);
3412 finish_del_id(entity);
3413 }
3414
3417 void handle_del_archetype_relation(Entity entity) {
3418 GAIA_ASSERT(entity.pair());
3419
3420#if GAIA_ASSERT_ENABLED
3421 World::verify_del(m_world, *m_pArchetype, m_entity, entity);
3422#endif
3423
3424 // Don't delete what has not been added.
3425 if (!has_archetype_id(entity))
3426 return;
3427
3428 invalidate_relation_change(entity);
3429
3430 try_set_flags(entity, false);
3431 handle_DependsOn(entity, false);
3432 unlink_is_relation(entity);
3433
3434 del_sparse_pair_payload(entity);
3435
3436 del_archetype_id(entity);
3437 finish_del_id(entity);
3438 }
3439
3442 void handle_del_archetype_wildcard(Entity entity) {
3443 GAIA_ASSERT(entity.pair());
3444 GAIA_ASSERT(is_wildcard(entity));
3445
3446 const auto relationFilter = entity.id();
3447 const auto targetFilter = entity.gen();
3448 cnt::darray_ext<Entity, 16> pairs;
3449 for (auto pair: m_pArchetype->ids_view()) {
3450 if (!pair.pair())
3451 continue;
3452 if (relationFilter != All.id() && pair.id() != relationFilter)
3453 continue;
3454 if (targetFilter != All.id() && pair.gen() != targetFilter)
3455 continue;
3456
3457 pairs.push_back(pair);
3458 }
3459
3460 for (auto pair: pairs) {
3461 if (can_del(pair))
3462 handle_del_archetype_relation(pair);
3463 }
3464 }
3465
3468 void handle_del_nonfragmenting_relation(Entity entity) {
3469 GAIA_ASSERT(entity.pair());
3470
3471 // Don't delete what has not been added.
3472 if (!has_nonfragmenting_relation_id(entity))
3473 return;
3474
3475 try_set_flags(entity, false);
3476 handle_DependsOn(entity, false);
3477 unlink_is_relation(entity);
3478 del_sparse_pair_payload(entity);
3479
3480#if GAIA_OBSERVERS_ENABLED
3481 if (m_world.m_observers.has_on_del_observers())
3482 tl_del_nonfragmenting_relations.push_back(entity);
3483 else
3484#endif
3485 del_nonfragmenting_relation_id(entity);
3486 finish_del_id(entity);
3487 }
3488
3491 void handle_del_nonfragmenting_wildcard(Entity entity) {
3492 GAIA_ASSERT(entity.pair());
3493 GAIA_ASSERT(is_wildcard(entity));
3494
3495 const auto relationFilter = entity.id();
3496 const auto targetFilter = entity.gen();
3497 cnt::darray_ext<Entity, 16> pairs;
3498 for (const auto& item: m_world.m_nonFragmentingRelationsByRel) {
3499 const auto relation = item.first.entity();
3500 if (relationFilter != All.id() && relation.id() != relationFilter)
3501 continue;
3502
3503 const auto target = item.second.target(m_entity);
3504 if (target == EntityBad || (targetFilter != All.id() && target.id() != targetFilter))
3505 continue;
3506
3507 pairs.push_back(Pair(relation, target));
3508 }
3509
3510 core::sort(pairs.begin(), pairs.end(), core::is_smaller<Entity>{});
3511 for (auto pair: pairs) {
3512 if (can_del(pair))
3513 handle_del_nonfragmenting_relation(pair);
3514 }
3515 }
3516
3521 GAIA_NODISCARD bool prepare_pair_add(Entity entity, RelationMutationPath relationPath) {
3522 GAIA_ASSERT(entity.pair());
3523 GAIA_ASSERT(entity.id() != Is.id() || !m_entity.pair());
3524
3525 if (relationPath == RelationMutationPath::NonFragmentingExclusive) {
3526 if (has_nonfragmenting_relation_id(entity))
3527 return false;
3528 } else if (relationPath == RelationMutationPath::NonFragmentingArchetypePair) {
3529 if (has_nonfragmenting_archetype_pair_id(entity))
3530 return false;
3531 }
3532
3533 // Make sure the entity container record exists if it is a pair.
3534 m_world.assign_pair(entity, *m_world.m_pEntityArchetype);
3535 return true;
3536 }
3537
3540 void add_inter(Entity entity) {
3541 GAIA_ASSERT(!is_wildcard(entity));
3542 const bool isPair = entity.pair();
3543 const auto relationPath = isPair ? relation_mutation_path_add(entity) : RelationMutationPath::Fragmenting;
3544
3545 if (isPair && !prepare_pair_add(entity, relationPath))
3546 return;
3547
3548 if (!handle_add_entity(entity, relationPath))
3549 return;
3550
3551 if (isPair) {
3552 if (relationPath == RelationMutationPath::NonFragmentingExclusive)
3553 handle_add_nonfragmenting_relation<false>(entity, true, true);
3554 else
3555 handle_add_archetype_relation<false>(entity);
3556 return;
3557 }
3558
3559 handle_add<false>(entity);
3560 }
3561
3567 static void rebuild_graph_edge(Archetype* pArchetypeLeft, Archetype* pArchetypeRight, Entity entity) {
3568 pArchetypeLeft->del_graph_edge_right_local(entity);
3569 pArchetypeRight->del_graph_edge_left_local(entity);
3570 pArchetypeLeft->build_graph_edges(pArchetypeRight, entity);
3571 }
3572
3579 static void ensure_graph_edge(Archetype* pArchetypeLeft, Archetype* pArchetypeRight, Entity entity) {
3580 const auto right = pArchetypeLeft->find_edge_right(entity);
3581 const auto left = pArchetypeRight->find_edge_left(entity);
3582 if (right.id == pArchetypeRight->id() && left.id == pArchetypeLeft->id())
3583 return;
3584
3585 rebuild_graph_edge(pArchetypeLeft, pArchetypeRight, entity);
3586 }
3587
3591 void add_inter_init(Entity entity, bool addSparsePayload = true) {
3592 GAIA_ASSERT(!is_wildcard(entity));
3593 const bool isPair = entity.pair();
3594 const auto relationPath = isPair ? relation_mutation_path_add(entity) : RelationMutationPath::Fragmenting;
3595
3596 if (isPair && !prepare_pair_add(entity, relationPath))
3597 return;
3598
3599 if (!handle_add_entity(entity, relationPath))
3600 return;
3601
3602 if (isPair) {
3603 if (relationPath == RelationMutationPath::NonFragmentingExclusive)
3604 handle_add_nonfragmenting_relation<true>(entity, addSparsePayload, true);
3605 else
3606 handle_add_archetype_relation<true>(entity, addSparsePayload);
3607 return;
3608 }
3609
3610 handle_add<true>(entity);
3611 }
3612
3616 GAIA_NODISCARD static bool is_component_storage_trait(Entity entity) noexcept {
3617 return !entity.pair() && (entity.id() == DontFragment.id() || entity.id() == Sparse.id());
3618 }
3619
3625 GAIA_NODISCARD bool can_del(Entity entity) const noexcept {
3626 if (!entity.pair() && m_world.has_direct(entity, Requires))
3627 return false;
3628 if (has_Requires_tgt(entity))
3629 return false;
3630
3631 return true;
3632 }
3633
3637 bool del_inter(Entity entity) {
3638 if (!can_del(entity))
3639 return false;
3640
3641 handle_del(entity);
3642 return true;
3643 }
3644
3647 void del_name_inter(EntityNameLookupKey key) {
3648 World::del_name_lookup(m_world.m_nameToEntity, key);
3649 }
3650
3653 void del_alias_inter(EntityNameLookupKey key) {
3654 World::del_name_lookup(m_world.m_aliasToEntity, key);
3655 }
3656
3661 template <bool IsOwned>
3662 void name_inter(const char* name, uint32_t len) {
3664 GAIA_ASSERT(!m_entity.pair());
3665 if (m_entity.pair())
3666 return;
3667
3668 // When nullptr is passed for the name it means the user wants to delete the current one
3669 if (name == nullptr) {
3670 GAIA_ASSERT(len == 0);
3671 del_name();
3672 return;
3673 }
3674
3675 GAIA_ASSERT(len < ComponentCacheItem::MaxNameLength);
3676
3677 // Make sure the name does not contain a dot because this character is reserved for
3678 // hierarchical lookups, e.g. "parent.child.subchild".
3679 GAIA_FOR(len) {
3680 const bool hasInvalidCharacter = name[i] == '.';
3681 GAIA_ASSERT(!hasInvalidCharacter && "Character '.' can't be used in entity names");
3682 if (hasInvalidCharacter)
3683 return;
3684 }
3685
3686 EntityNameLookupKey key(
3687 name, len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len, IsOwned);
3688
3689 // Make sure the name is unique. Ignore setting the same name twice on the same entity.
3690 // If it is not, there is nothing to do.
3691 auto it = m_world.m_nameToEntity.find(key);
3692 if (it == m_world.m_nameToEntity.end()) {
3693 // If we already had some name, remove the pair from the map first.
3694 if (m_targetNameKey.str() != nullptr) {
3695 del_name_inter(m_targetNameKey);
3696 } else {
3697 const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
3698 if (compIdx != BadIndex) {
3699 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut(compIdx, m_rowSrc));
3700 GAIA_ASSERT(core::check_alignment(pDesc));
3701 if (pDesc->name != nullptr) {
3702 del_name_inter(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0));
3703 pDesc->name = nullptr;
3704 }
3705 } else {
3706 // Make sure EntityDesc is added to the entity.
3707 add_inter(GAIA_ID(EntityDesc));
3708 }
3709 }
3710
3711 // Insert the new pair
3712 it = m_world.m_nameToEntity.emplace(key, m_entity).first;
3713 } else {
3714#if GAIA_ASSERT_ENABLED
3715 if (it->second != m_entity && World::s_enableUniqueNameDuplicateAssert)
3716 GAIA_ASSERT(false && "Trying to set non-unique name for an entity");
3717#endif
3718
3719 // Attempts to set the same name again, or not a unique name, will be dropped.
3720 return;
3721 }
3722
3723 if constexpr (IsOwned) {
3724 // Allocate enough storage for the name
3725 char* entityStr = (char*)mem::mem_alloc(key.len() + 1);
3726 memcpy((void*)entityStr, (const void*)name, key.len());
3727 entityStr[key.len()] = 0;
3728
3729 m_targetNameKey = EntityNameLookupKey(entityStr, key.len(), 1, {key.hash()});
3730
3731 // Update the map so it points to the newly allocated string.
3732 // We replace the pointer we provided in try_emplace with an internally allocated string.
3733 auto p = robin_hood::pair(std::make_pair(m_targetNameKey, m_entity));
3734 it->swap(p);
3735 } else {
3736 m_targetNameKey = key;
3737
3738 // We tell the map the string is non-owned.
3739 auto p = robin_hood::pair(std::make_pair(key, m_entity));
3740 it->swap(p);
3741 }
3742
3743 m_world.invalidate_scope_path_cache();
3744 }
3745
3750 template <bool IsOwned>
3751 void alias_inter(const char* alias, uint32_t len) {
3753 GAIA_ASSERT(!m_entity.pair());
3754 if (m_entity.pair())
3755 return;
3756
3757 // When nullptr is passed for the alias it means the user wants to delete the current one
3758 if (alias == nullptr) {
3759 GAIA_ASSERT(len == 0);
3760 del_alias();
3761 return;
3762 }
3763
3764 GAIA_ASSERT(len < ComponentCacheItem::MaxNameLength);
3765
3766 // Make sure the name does not contain a dot because this character is reserved for
3767 // hierarchical lookups, e.g. "parent.child.subchild".
3768 GAIA_FOR(len) {
3769 const bool hasInvalidCharacter = alias[i] == '.';
3770 GAIA_ASSERT(!hasInvalidCharacter && "Character '.' can't be used in entity aliases");
3771 if (hasInvalidCharacter)
3772 return;
3773 }
3774
3775 EntityNameLookupKey key(
3776 alias, len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len, IsOwned);
3777
3778 auto it = m_world.m_aliasToEntity.find(key);
3779 if (it == m_world.m_aliasToEntity.end()) {
3780 // If we already had some alias, remove the pair from the map first.
3781 if (m_targetAliasKey.str() != nullptr) {
3782 del_alias_inter(m_targetAliasKey);
3783 } else {
3784 const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
3785 if (compIdx != BadIndex) {
3786 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut(compIdx, m_rowSrc));
3787 GAIA_ASSERT(core::check_alignment(pDesc));
3788 if (pDesc->alias != nullptr) {
3789 del_alias_inter(EntityNameLookupKey(pDesc->alias, pDesc->alias_len, 0));
3790 pDesc->alias = nullptr;
3791 }
3792 } else {
3793 // Make sure EntityDesc is added to the entity.
3794 add_inter(GAIA_ID(EntityDesc));
3795 }
3796 }
3797
3798 it = m_world.m_aliasToEntity.emplace(key, m_entity).first;
3799 } else {
3800#if GAIA_ASSERT_ENABLED
3801 if (it->second != m_entity && World::s_enableUniqueNameDuplicateAssert)
3802 GAIA_ASSERT(false && "Trying to set non-unique alias for an entity");
3803#endif
3804
3805 // Attempts to set the same alias again, or not a unique alias, will be dropped.
3806 return;
3807 }
3808
3809 if constexpr (IsOwned) {
3810 // Allocate enough storage for the alias
3811 char* aliasStr = (char*)mem::mem_alloc(key.len() + 1);
3812 memcpy((void*)aliasStr, (const void*)alias, key.len());
3813 aliasStr[key.len()] = 0;
3814
3815 m_targetAliasKey = EntityNameLookupKey(aliasStr, key.len(), 1, {key.hash()});
3816
3817 // Update the map so it points to the newly allocated string.
3818 // We replace the pointer we provided in try_emplace with an internally allocated string.
3819 auto p = robin_hood::pair(std::make_pair(m_targetAliasKey, m_entity));
3820 it->swap(p);
3821 } else {
3822 m_targetAliasKey = key;
3823
3824 // We tell the map the string is non-owned.
3825 auto p = robin_hood::pair(std::make_pair(key, m_entity));
3826 it->swap(p);
3827 }
3828 }
3829 };
3830
3831 //----------------------------------------------------------------------
3832
3835 GAIA_NODISCARD ComponentCache& comp_cache_mut() {
3836 return m_compCache;
3837 }
3838
3841 GAIA_NODISCARD const ComponentCache& comp_cache() const {
3842 return m_compCache;
3843 }
3844
3845 //----------------------------------------------------------------------
3846
3851 GAIA_NODISCARD Entity symbol(const char* symbol, uint32_t len = 0) const {
3852 if (symbol == nullptr || symbol[0] == 0)
3853 return EntityBad;
3854
3855 const auto* pItem = comp_cache().symbol(symbol, len);
3856 return pItem != nullptr ? pItem->entity : EntityBad;
3857 }
3858
3862 GAIA_NODISCARD util::str_view symbol(Entity component) const {
3863 const auto* pItem = comp_cache().find(component);
3864 return pItem != nullptr ? comp_cache().symbol_name(*pItem) : util::str_view{};
3865 }
3866
3871 GAIA_NODISCARD Entity path(const char* path, uint32_t len = 0) const {
3872 if (path == nullptr || path[0] == 0)
3873 return EntityBad;
3874
3875 const auto* pItem = comp_cache().path(path, len);
3876 return pItem != nullptr ? pItem->entity : EntityBad;
3877 }
3878
3882 GAIA_NODISCARD util::str_view path(Entity component) const {
3883 const auto* pItem = comp_cache().find(component);
3884 return pItem != nullptr ? comp_cache().path_name(*pItem) : util::str_view{};
3885 }
3886
3892 bool path(Entity component, const char* path, uint32_t len = 0) {
3893 auto* pItem = comp_cache_mut().find(component);
3894 return pItem != nullptr ? comp_cache_mut().path(*pItem, path, len) : false;
3895 }
3896
3901 GAIA_NODISCARD Entity alias(const char* alias, uint32_t len = 0) const {
3902 if (alias == nullptr || alias[0] == 0)
3903 return EntityBad;
3904
3905 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
3906 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
3907 const auto it = m_aliasToEntity.find(EntityNameLookupKey(alias, l, 0));
3908 return it != m_aliasToEntity.end() ? it->second : EntityBad;
3909 }
3910
3914 GAIA_NODISCARD util::str_view alias(Entity entity) const {
3915 if (entity.pair())
3916 return {};
3917
3918 const auto& ec = m_recs.entities[entity.id()];
3919 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
3920 if (compIdx == BadIndex)
3921 return {};
3922
3923 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
3924 GAIA_ASSERT(core::check_alignment(pDesc));
3925 return {pDesc->alias, pDesc->alias_len};
3926 }
3927
3933 bool alias(Entity entity, const char* alias, uint32_t len = 0) {
3934 if (!valid(entity) || entity.pair())
3935 return false;
3936
3937 const auto before = this->alias(entity);
3938 EntityBuilder(*this, entity).alias(alias, len);
3939 const auto after = this->alias(entity);
3940 if (alias == nullptr)
3941 return !before.empty() && after.empty();
3942
3943 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
3944 return after == util::str_view(alias, l);
3945 }
3946
3952 bool alias_raw(Entity entity, const char* alias, uint32_t len = 0) {
3953 if (!valid(entity) || entity.pair())
3954 return false;
3955
3956 const auto before = this->alias(entity);
3957 EntityBuilder(*this, entity).alias_raw(alias, len);
3958 const auto after = this->alias(entity);
3959 if (alias == nullptr)
3960 return !before.empty() && after.empty();
3961
3962 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
3963 return after == util::str_view(alias, l);
3964 }
3965
3970 GAIA_NODISCARD util::str_view display_name(Entity entity) const {
3971 const auto* pItem = comp_cache().find(entity);
3972 if (pItem == nullptr)
3973 return {};
3974
3975 const auto aliasValue = alias(entity);
3976 if (!aliasValue.empty())
3977 return aliasValue;
3978
3979 const auto pathValue = path(entity);
3980 if (!pathValue.empty()) {
3981 const auto symbolEntity = symbol(pathValue.data(), pathValue.size());
3982 if (symbolEntity == EntityBad || symbolEntity == entity)
3983 return pathValue;
3984 }
3985
3986 return symbol(entity);
3987 }
3988
3989 //----------------------------------------------------------------------
3990
3991 private:
3993 void invalidate_scope_path_cache() const {
3994 m_componentScopePathCache.clear();
3995 m_componentScopePathCacheEntity = EntityBad;
3996 m_componentScopePathCacheValid = false;
3997 }
3998
4003 GAIA_NODISCARD bool build_scope_path(Entity scope, util::str& out) const {
4004 out.clear();
4005 if (!valid(scope) || scope.pair())
4006 return false;
4007
4009 auto curr = scope;
4010 while (curr != EntityBad) {
4011 const auto currName = name(curr);
4012 if (currName.empty()) {
4013 out.clear();
4014 return false;
4015 }
4016
4017 segments.push_back(currName);
4018 curr = target(curr, ChildOf);
4019 }
4020
4021 if (segments.empty())
4022 return false;
4023
4024 uint32_t totalLen = 0;
4025 for (auto segment: segments)
4026 totalLen += segment.size();
4027 totalLen += (uint32_t)segments.size() - 1;
4028
4029 out.reserve(totalLen);
4030 for (uint32_t i = (uint32_t)segments.size(); i > 0; --i) {
4031 if (!out.empty())
4032 out.append('.');
4033 out.append(segments[i - 1]);
4034 }
4035
4036 return true;
4037 }
4038
4042 GAIA_NODISCARD bool current_scope_path(util::str& out) const {
4043 if (m_componentScope == EntityBad) {
4044 invalidate_scope_path_cache();
4045 out.clear();
4046 return false;
4047 }
4048
4049 if (m_componentScopePathCacheValid && m_componentScopePathCacheEntity == m_componentScope) {
4050 out.assign(m_componentScopePathCache.view());
4051 return true;
4052 }
4053
4054 if (!build_scope_path(m_componentScope, out)) {
4055 invalidate_scope_path_cache();
4056 return false;
4057 }
4058
4059 m_componentScopePathCache.assign(out.view());
4060 m_componentScopePathCacheEntity = m_componentScope;
4061 m_componentScopePathCacheValid = true;
4062 return true;
4063 }
4064
4070 GAIA_NODISCARD const ComponentCacheItem*
4071 find_comp_scope_chain_inter(Entity scopeEntity, const char* name, uint32_t len) const {
4072 if (scopeEntity == EntityBad)
4073 return nullptr;
4074
4075 util::str scopePath;
4076 if (!build_scope_path(scopeEntity, scopePath))
4077 return nullptr;
4078
4079 util::str scopedName;
4080 scopedName.reserve(scopePath.size() + 1 + len);
4081
4082 while (!scopePath.empty()) {
4083 scopedName.clear();
4084 scopedName.append(scopePath.view());
4085 scopedName.append('.');
4086 scopedName.append(name, len);
4087
4088 if (const auto* pItem = m_compCache.path(scopedName.data(), (uint32_t)scopedName.size()); pItem != nullptr)
4089 return pItem;
4090
4091 const auto parentSepIdx = scopePath.view().find_last_of('.');
4092 if (parentSepIdx == BadIndex)
4093 break;
4094
4095 scopePath.assign(util::str_view(scopePath.data(), parentSepIdx));
4096 }
4097
4098 return nullptr;
4099 }
4100
4106 void add_comp_scope_chain_hits_inter(
4107 cnt::darray<Entity>& out, Entity scopeEntity, const char* name, uint32_t len) const {
4108 if (scopeEntity == EntityBad)
4109 return;
4110
4111 util::str scopePath;
4112 if (!build_scope_path(scopeEntity, scopePath))
4113 return;
4114
4115 util::str scopedName;
4116 scopedName.reserve(scopePath.size() + 1 + len);
4117
4118 while (!scopePath.empty()) {
4119 scopedName.clear();
4120 scopedName.append(scopePath.view());
4121 scopedName.append('.');
4122 scopedName.append(name, len);
4123
4124 if (const auto* pItem = m_compCache.path(scopedName.data(), (uint32_t)scopedName.size()); pItem != nullptr)
4125 ComponentCache::push_unique_entity(out, pItem->entity);
4126
4127 const auto parentSepIdx = scopePath.view().find_last_of('.');
4128 if (parentSepIdx == BadIndex)
4129 break;
4130
4131 scopePath.assign(util::str_view(scopePath.data(), parentSepIdx));
4132 }
4133 }
4134
4139 GAIA_NODISCARD const ComponentCacheItem* find_comp_lookup_inter(const char* name, uint32_t len) const {
4140 if (const auto* pItem = find_comp_scope_chain_inter(m_componentScope, name, len); pItem != nullptr)
4141 return pItem;
4142
4143 for (const auto scopeEntity: m_componentLookupPath) {
4144 if (scopeEntity == m_componentScope)
4145 continue;
4146
4147 if (const auto* pItem = find_comp_scope_chain_inter(scopeEntity, name, len); pItem != nullptr)
4148 return pItem;
4149 }
4150
4151 return nullptr;
4152 }
4153
4158 void add_comp_lookup_hits_inter(cnt::darray<Entity>& out, const char* name, uint32_t len) const {
4159 add_comp_scope_chain_hits_inter(out, m_componentScope, name, len);
4160 for (const auto scopeEntity: m_componentLookupPath) {
4161 if (scopeEntity == m_componentScope)
4162 continue;
4163
4164 add_comp_scope_chain_hits_inter(out, scopeEntity, name, len);
4165 }
4166 }
4167
4174 GAIA_NODISCARD const ComponentCacheItem*
4175 find_comp_exact_inter(const char* name, uint32_t len, bool isPath, bool isSymbol) const {
4176 if (const auto* pItem = m_compCache.symbol(name, len); pItem != nullptr)
4177 return pItem;
4178
4179 if (!isPath) {
4180 if (const auto* pItem = m_compCache.path(name, len); pItem != nullptr)
4181 return pItem;
4182 if (!isSymbol) {
4183 if (const auto* pItem = m_compCache.short_symbol(name, len); pItem != nullptr)
4184 return pItem;
4185 }
4186 }
4187
4188 return nullptr;
4189 }
4190
4197 void add_comp_exact_hits_inter(
4198 cnt::darray<Entity>& out, const char* name, uint32_t len, bool isPath, bool isSymbol) const {
4199 if (const auto* pItem = m_compCache.symbol(name, len); pItem != nullptr)
4200 ComponentCache::push_unique_entity(out, pItem->entity);
4201
4202 m_compCache.add_path_matches(out, util::str_view(name, len));
4203
4204 if (out.empty() && !isPath && !isSymbol) {
4205 if (const auto* pItem = m_compCache.short_symbol(name, len); pItem != nullptr)
4206 ComponentCache::push_unique_entity(out, pItem->entity);
4207 }
4208 }
4209
4212 GAIA_NODISCARD bool has_comp_lookup_ctx_inter() const noexcept {
4213 return m_componentScope != EntityBad || !m_componentLookupPath.empty();
4214 }
4215
4220 GAIA_NODISCARD static bool is_unqualified_comp_name_inter(const char* name, uint32_t len) noexcept {
4221 return memchr(name, '.', len) == nullptr && memchr(name, ':', len) == nullptr;
4222 }
4223
4228 GAIA_NODISCARD Entity pick_name_or_comp_inter(Entity namedEntity, const ComponentCacheItem* pCompItem) const {
4229 if (pCompItem == nullptr)
4230 return namedEntity;
4231
4232 if (namedEntity == EntityBad)
4233 return pCompItem->entity;
4234
4235 return m_compCache.find(namedEntity) != nullptr ? pCompItem->entity : namedEntity;
4236 }
4237
4245 GAIA_NODISCARD const ComponentCacheItem* resolve_component_name_inter(const char* name, uint32_t len = 0) const {
4246 GAIA_ASSERT(name != nullptr);
4247
4248 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
4249 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
4250 const bool isPath = memchr(name, '.', l) != nullptr;
4251 const bool isSymbol = memchr(name, ':', l) != nullptr;
4252
4253 if (isPath) {
4254 if (const auto* pItem = m_compCache.path(name, l); pItem != nullptr)
4255 return pItem;
4256 }
4257
4258 if (!isPath && !isSymbol) {
4259 if (const auto* pItem = find_comp_lookup_inter(name, l); pItem != nullptr)
4260 return pItem;
4261 }
4262
4263 if (const auto* pItem = find_comp_exact_inter(name, l, isPath, isSymbol); pItem != nullptr)
4264 return pItem;
4265
4266 const auto aliasEntity = alias(name, l);
4267 return aliasEntity != EntityBad ? m_compCache.find(aliasEntity) : nullptr;
4268 }
4269
4270 public:
4271 //----------------------------------------------------------------------
4272
4276 GAIA_NODISCARD bool valid(Entity entity) const {
4277 return entity.pair() //
4278 ? valid_pair(entity)
4279 : valid_entity(entity);
4280 }
4281
4282 //----------------------------------------------------------------------
4283
4287 GAIA_NODISCARD Entity get(EntityId id) const {
4288 // Cleanup, observer propagation, and wildcard expansion can briefly encounter stale ids.
4289 // Treat those as absent instead of crashing the world.
4290 if (!valid_entity_id(id))
4291 return EntityBad;
4292
4293 const auto& ec = m_recs.entities[id];
4294 return Entity(id, ec.data.gen, (bool)ec.data.ent, (bool)ec.data.pair);
4295 }
4296
4300 GAIA_NODISCARD Entity try_get(EntityId id) const {
4301 return valid_entity_id(id) ? get(id) : EntityBad;
4302 }
4303
4307 template <typename T>
4308 GAIA_NODISCARD Entity get() const {
4309 return comp_cache().get<T>().entity;
4310 }
4311
4315 template <typename T>
4316 GAIA_NODISCARD const ComponentCacheItem& reg_comp() {
4317#if GAIA_ECS_AUTO_COMPONENT_REGISTRATION
4318 return add<T>();
4319#else
4320 return comp_cache().template get<T>();
4321#endif
4322 }
4323
4324 //----------------------------------------------------------------------
4325
4331 return EntityBuilder(*this, entity);
4332 }
4333
4336 GAIA_NODISCARD Entity add() {
4337 return add(*m_pEntityArchetype, true, false);
4338 }
4339
4342 GAIA_NODISCARD Entity prefab() {
4343 const auto entity = add();
4344 add(entity, Prefab);
4345 return entity;
4346 }
4347
4351 template <typename Func = TFunc_Void_With_Entity>
4352 void add_n(uint32_t count, Func func = func_void_with_entity) {
4353 add_entity_n(*m_pEntityArchetype, count, func);
4354 }
4355
4367 template <typename Func = TFunc_Void_With_Entity>
4368 void add_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
4369 auto& ec = cont(entity);
4370
4371 GAIA_ASSERT(ec.pArchetype != nullptr);
4372 GAIA_ASSERT(ec.pChunk != nullptr);
4373
4374 auto* pDstArchetype = ec.pArchetype;
4375 if (pDstArchetype->has<EntityDesc>())
4376 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4377
4378 cnt::darray_ext<Entity, 8> nonfragmentingPairs;
4379 collect_nonfragmenting_relation_pairs(entity, nonfragmentingPairs);
4380 cnt::darray_ext<Entity, 16> nonFragSparseIds;
4381 cnt::darray_ext<Entity, 16> fragSparseIds;
4382 collect_add_n_sparse_ids(entity, nonFragSparseIds, fragSparseIds);
4383
4384 if (nonfragmentingPairs.empty() && nonFragSparseIds.empty() && fragSparseIds.empty()) {
4385 add_entity_n(*pDstArchetype, count, func);
4386 return;
4387 }
4388
4389 add_entity_n(*pDstArchetype, count, [&](Entity dstEntity) {
4390 apply_add_n_non_table_type(
4391 entity, dstEntity, EntitySpan{nonfragmentingPairs}, EntitySpan{nonFragSparseIds},
4392 EntitySpan{fragSparseIds});
4393 func(dstEntity);
4394 });
4395 }
4396
4404 template <typename Func = TFunc_Void_With_Entity>
4405 void add_n(Pair entity, uint32_t count, Func func = func_void_with_entity) {
4406 add_n((Entity)entity, count, func);
4407 }
4408
4412 template <typename T>
4413 GAIA_NODISCARD const ComponentCacheItem& add() {
4414 static_assert(!is_pair<T>::value, "Pairs can't be registered as components");
4415
4416 using CT = component_type_t<T>;
4417 using FT = typename CT::TypeFull;
4418 const auto* pItem = comp_cache().find<FT>();
4419 if (pItem != nullptr)
4420 return *pItem;
4421
4422 const auto entity = add(*m_pCompArchetype, false, false);
4423 util::str scopePath;
4424 (void)current_scope_path(scopePath);
4425
4426 const auto& item = comp_cache_mut().add<FT>(entity, scopePath.view());
4427 item.func_create_sparse_store = [](World& world, Entity component) {
4428 (void)world.sparse_component_store_mut<FT>(component);
4429 };
4430 finalize_component_registration(item, item.comp.storage_type() == DataStorageType::Sparse);
4431 if constexpr (uses_ct_dont_fragment_v<FT>)
4432 add(item.entity, DontFragment);
4433 if constexpr (supports_sparse_component_storage<FT>()) {
4434 if (item.comp.storage_type() == DataStorageType::Sparse)
4435 (void)sparse_component_store_mut<FT>(item.entity);
4436 }
4437
4438 return item;
4439 }
4440
4447 template <typename T>
4448 GAIA_NODISCARD const ComponentCacheItem& add(const RuntimeTypeDesc& runtimeType) {
4449 static_assert(!is_pair<T>::value, "Pairs can't be registered as components");
4450
4451 using CT = component_type_t<T>;
4452 using FT = typename CT::TypeFull;
4453 const auto* pItem = comp_cache().find<FT>();
4454 if (pItem != nullptr)
4455 return *pItem;
4456
4457 validate_runtime_semantics(runtimeType);
4458 const auto entity = add(*m_pCompArchetype, false, false);
4459 util::str scopePath;
4460 (void)current_scope_path(scopePath);
4461
4462 const auto& item = comp_cache_mut().add<FT>(entity, runtimeType, scopePath.view());
4463 item.func_create_sparse_store = [](World& world, Entity component) {
4464 (void)world.sparse_component_store_mut<FT>(component);
4465 };
4466 finalize_component_registration(item, item.comp.storage_type() == DataStorageType::Sparse);
4467 if constexpr (uses_ct_dont_fragment_v<FT>)
4468 add(item.entity, DontFragment);
4469 if constexpr (supports_sparse_component_storage<FT>()) {
4470 if (item.comp.storage_type() == DataStorageType::Sparse)
4471 (void)sparse_component_store_mut<FT>(item.entity);
4472 }
4473
4474 return item;
4475 }
4476
4480 GAIA_NODISCARD ComponentCacheItem& add(const ComponentDesc& desc) {
4481 GAIA_ASSERT(!desc.name.empty());
4482 GAIA_ASSERT(desc.name.size() < ComponentCacheItem::MaxNameLength);
4483
4484 if (const auto* pItem = comp_cache().symbol(desc.name); pItem != nullptr)
4485 return *comp_cache_mut().find(pItem->entity);
4486
4487 validate_runtime_semantics(desc.runtimeType);
4488 const auto entity = add(*m_pCompArchetype, false, false);
4489 util::str scopePath;
4490 (void)current_scope_path(scopePath);
4491 auto& itemInfo = comp_cache_mut().add(entity, desc, scopePath.view());
4492 finalize_component_registration(itemInfo, true);
4493 return itemInfo;
4494 }
4495
4500 void add(Entity entity, Entity object) {
4501#if GAIA_ASSERT_ENABLED
4502 if (!object.pair()) {
4503 const auto* pItem = comp_cache().find(object);
4504 if (pItem != nullptr && pItem->entity == object && component_uses_sparse_storage(object))
4505 GAIA_ASSERT2(false, "Sparse runtime components require an explicit typed value when added by entity id");
4506 }
4507#endif
4508 EntityBuilder(*this, entity).add(object);
4509 }
4510
4517 void add(Entity entity, Pair pair) {
4518 auto& ec = cont(entity);
4519 EntityBuilder builder(*this, entity, ec);
4520 builder.add(pair);
4521 builder.commit();
4522 }
4523
4530 void add(Pair entity, Pair pair) {
4531 const auto source = (Entity)entity;
4532 auto& ec = fetch(source);
4533 EntityBuilder builder(*this, source, ec);
4534 builder.add(pair);
4535 builder.commit();
4536 }
4537
4543 template <typename T>
4544 void add(Entity entity) {
4545 using FT = typename component_type_t<T>::TypeFull;
4546 if constexpr (uses_ct_sparse_storage<FT>()) {
4547 SparseStorageMode mode;
4548 const auto object = register_sparse_component_id<FT>(mode);
4549 (void)sparse_component_store_mut<FT>(object).add(entity);
4550 finish_sparse_component_add_inter(entity, object, mode);
4551 } else {
4552 EntityBuilder(*this, entity).add<T>();
4553 }
4554 }
4555
4561 template <typename T>
4562 void add(Pair entity) {
4563 add<T>((Entity)entity);
4564 }
4565
4573 template <typename T>
4574 void add(Entity entity, Entity object, T&& value) {
4575 static_assert(core::is_raw_v<T>);
4576
4577 if constexpr (supports_sparse_component_storage<typename component_type_t<T>::TypeFull>()) {
4578 using FT = typename component_type_t<T>::TypeFull;
4579 if (can_use_sparse_component_storage<FT>(object)) {
4580 const auto mode = sparse_storage_mode(object);
4581 if (mode != SparseStorageMode::None) {
4582 auto& data = sparse_component_add_value<FT>(object, entity);
4583 data = GAIA_FWD(value);
4584 finish_sparse_component_add_inter(entity, object, mode);
4585 return;
4586 }
4587 }
4588 }
4589
4590 EntityBuilder eb(*this, entity);
4591#if GAIA_OBSERVERS_ENABLED
4592 auto addDiffCtx =
4593 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
4594#endif
4595 eb.add_inter_init(object);
4596 eb.commit();
4597
4598 const auto& ec = fetch(entity);
4599 const auto idx = ec.row;
4600 ComponentSetter{*this, ec.pChunk, entity, idx}.sset(object, GAIA_FWD(value));
4601 notify_add_single(entity, object);
4602#if GAIA_OBSERVERS_ENABLED
4603 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4604#endif
4605 }
4606
4615 template <typename T>
4616 void add(Entity entity, Pair pair, T&& value) {
4617 add(entity, (Entity)pair, GAIA_FWD(value));
4618 }
4619
4626 template <typename T, typename U = typename actual_type_t<T>::Type>
4627 void add(Entity entity, U&& value) {
4628 using FT = typename component_type_t<T>::TypeFull;
4629 if constexpr (uses_ct_sparse_storage<FT>()) {
4630 SparseStorageMode mode;
4631 const auto object = register_sparse_component_id<FT>(mode);
4632 auto& data = sparse_component_store_mut<FT>(object).add(entity);
4633 data = GAIA_FWD(value);
4634 finish_sparse_component_add_inter(entity, object, mode);
4635 } else {
4636 EntityBuilder builder(*this, entity);
4637 auto object = builder.register_component<T>();
4638#if GAIA_OBSERVERS_ENABLED
4639 auto addDiffCtx =
4640 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
4641#endif
4642 // Materialize the component first, write the initial value, and only then dispatch OnAdd.
4643 // This keeps observer-visible state aligned with the final stored payload.
4644 builder.add_inter_init(object);
4645 builder.commit();
4646
4647 const auto& ec = cont(entity);
4648 const auto idx = ec.row;
4649 ComponentSetter{*this, ec.pChunk, entity, idx}.sset<T>(GAIA_FWD(value));
4650 notify_add_single(entity, object);
4651#if GAIA_OBSERVERS_ENABLED
4652 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4653#endif
4654 }
4655 }
4656
4664 template <typename T, typename U = typename actual_type_t<T>::Type>
4665 void add(Pair entity, U&& value) {
4666 using FT = typename component_type_t<T>::TypeFull;
4667 const auto object = register_component_id<FT>();
4668
4669 using ValueType = typename actual_type_t<T>::Type;
4670 add<ValueType>((Entity)entity, object, GAIA_FWD(value));
4671 }
4672
4677 GAIA_NODISCARD bool override(Entity entity, Entity object) {
4678 return override_inter(entity, object);
4679 }
4680
4685 GAIA_NODISCARD bool override(Entity entity, Pair pair) {
4686 return override_inter(entity, (Entity)pair);
4687 }
4688
4693 template <typename T>
4694 GAIA_NODISCARD bool override(Entity entity) {
4695 static_assert(!is_pair<T>::value);
4696 using FT = typename component_type_t<T>::TypeFull;
4697 const auto& item = add<FT>();
4698
4699 if constexpr (uses_ct_sparse_storage<FT>())
4700 return override_sparse_component_inter(entity, item.entity);
4701 else
4702 return override_inter(entity, item.entity);
4703 }
4704
4710 template <typename T>
4711 GAIA_NODISCARD bool override(Entity entity, Entity object) {
4712 static_assert(!is_pair<T>::value);
4713 using FT = typename component_type_t<T>::TypeFull;
4714
4715 if constexpr (supports_sparse_component_storage<FT>()) {
4716 if (can_use_sparse_component_storage<FT>(object))
4717 return override_sparse_component_inter(entity, object);
4718 }
4719
4720 return override_inter(entity, object);
4721 }
4722
4723 //----------------------------------------------------------------------
4724
4729 void clear(Entity entity) {
4730 GAIA_ASSERT(!entity.pair());
4731 GAIA_ASSERT(valid(entity));
4732
4733 EntityBuilder eb(*this, entity);
4734
4735 // Remove back to front because it's better for the archetype graph
4736 auto ids = eb.m_pArchetype->ids_view();
4737 for (uint32_t i = (uint32_t)ids.size() - 1; i != (uint32_t)-1; --i)
4738 eb.del(ids[i]);
4739
4740 eb.commit();
4741
4743 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
4744 const auto component = compKey.entity();
4745 if (component_is_non_fragmenting(component) && store.func_has(store.pStore, entity))
4746 sparseIds.push_back(component);
4747 }
4748 for (const auto component: sparseIds)
4749 del(entity, component);
4750
4751 cnt::darray_ext<Entity, 8> relationIds;
4752 for (const auto& [relationKey, store]: m_nonFragmentingRelationsByRel) {
4753 if (store.target(entity) != EntityBad)
4754 relationIds.push_back(relationKey.entity());
4755 }
4756 for (const auto relation: relationIds) {
4757 const auto targetEntity = target(entity, relation);
4758 if (targetEntity != EntityBad)
4759 del(entity, Pair(relation, targetEntity));
4760 }
4761 }
4762
4763 //----------------------------------------------------------------------
4764
4773 GAIA_NODISCARD Entity copy(Entity srcEntity) {
4774 GAIA_ASSERT(valid(srcEntity));
4775
4776 auto& ec = cont(srcEntity);
4777 GAIA_ASSERT(ec.pArchetype != nullptr);
4778 GAIA_ASSERT(ec.pChunk != nullptr);
4779
4780 cnt::darray_ext<Entity, 8> nonfragmentingPairs;
4781 collect_nonfragmenting_relation_pairs(srcEntity, nonfragmentingPairs);
4782
4783 auto* pSrcChunk = ec.pChunk;
4784 const auto srcRow = ec.row;
4785 auto* pDstArchetype = ec.pArchetype;
4786 Entity dstEntity;
4787
4788 // Names have to be unique so if we see that EntityDesc is present during copy
4789 // we navigate towards a version of the archetype without the EntityDesc.
4790 const bool hasEntityDesc = pDstArchetype->has<EntityDesc>();
4791 if (hasEntityDesc)
4792 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4793
4794 cnt::darray_ext<Entity, 16> addHookIds;
4795 collect_copy_add_hook_ids(srcEntity, *pDstArchetype, EntitySpan{nonfragmentingPairs}, addHookIds);
4796
4797 dstEntity = add(*pDstArchetype, true, false);
4798 if (hasEntityDesc) {
4799 auto& ecDst = m_recs.entities[dstEntity.id()];
4800 Chunk::copy_foreign_entity_data(pSrcChunk, srcRow, ecDst.pChunk, ecDst.row);
4801 } else
4802 Chunk::copy_entity_data(srcEntity, dstEntity, m_recs);
4803
4804 copy_all_sparse_entity_data(srcEntity, dstEntity);
4805 copy_nonfragmenting_relation_pairs(dstEntity, EntitySpan{nonfragmentingPairs});
4806 trigger_copy_add_hooks(EntitySpan{&dstEntity, 1}, EntitySpan{addHookIds});
4807
4808 return dstEntity;
4809 }
4810
4821 template <typename Func = TFunc_Void_With_Entity>
4822 void copy_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
4823 cnt::darray_ext<Entity, 8> nonfragmentingPairs;
4824 collect_nonfragmenting_relation_pairs(entity, nonfragmentingPairs);
4825 auto* pDstArchetype = cont(entity).pArchetype;
4826 if (pDstArchetype->has<EntityDesc>())
4827 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4828 cnt::darray_ext<Entity, 16> addHookIds;
4829 collect_copy_add_hook_ids(entity, *pDstArchetype, EntitySpan{nonfragmentingPairs}, addHookIds);
4830 copy_n_inter(entity, count, func, EntitySpan{}, EntitySpan{nonfragmentingPairs}, EntitySpan{addHookIds});
4831 }
4832
4833#if GAIA_OBSERVERS_ENABLED
4842 GAIA_NODISCARD Entity copy_ext(Entity srcEntity) {
4843 GAIA_ASSERT(valid(srcEntity));
4844
4845 auto& ec = cont(srcEntity);
4846 GAIA_ASSERT(ec.pArchetype != nullptr);
4847 GAIA_ASSERT(ec.pChunk != nullptr);
4848
4849 cnt::darray_ext<Entity, 8> nonfragmentingPairs;
4850 collect_nonfragmenting_relation_pairs(srcEntity, nonfragmentingPairs);
4851
4852 auto* pSrcChunk = ec.pChunk;
4853 const auto srcRow = ec.row;
4854 auto* pDstArchetype = ec.pArchetype;
4855 // Names have to be unique so if we see that EntityDesc is present during copy
4856 // we navigate towards a version of the archetype without the EntityDesc.
4857 const bool hasEntityDesc = pDstArchetype->has<EntityDesc>();
4858 if (hasEntityDesc)
4859 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4860
4861 const auto archetypeIdCount = (uint32_t)pDstArchetype->ids_view().size();
4862 const auto sparseIdCount = copied_non_frag_sparse_id_count(srcEntity);
4863 const auto nonfragmentingPairCount = (uint32_t)nonfragmentingPairs.size();
4864 const auto addedIdCount = archetypeIdCount + sparseIdCount + nonfragmentingPairCount;
4865 auto* pAddedIds = addedIdCount != 0U ? (Entity*)alloca(sizeof(Entity) * addedIdCount) : nullptr;
4866 write_archetype_ids(*pDstArchetype, pAddedIds);
4867 write_copied_non_frag_sparse_ids(srcEntity, pAddedIds + archetypeIdCount);
4868 GAIA_FOR(nonfragmentingPairCount)
4869 pAddedIds[archetypeIdCount + sparseIdCount + i] = nonfragmentingPairs[i];
4870 cnt::darray_ext<Entity, 16> addHookIds;
4871 collect_add_hook_ids(EntitySpan{pAddedIds, addedIdCount}, addHookIds);
4872 #if GAIA_OBSERVERS_ENABLED
4873 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{pAddedIds, addedIdCount});
4874 #endif
4875
4876 const auto dstEntity = add(*pDstArchetype, true, false);
4877 if (hasEntityDesc) {
4878 auto& ecDst = m_recs.entities[dstEntity.id()];
4879 Chunk::copy_foreign_entity_data(pSrcChunk, srcRow, ecDst.pChunk, ecDst.row);
4880 } else
4881 Chunk::copy_entity_data(srcEntity, dstEntity, m_recs);
4882
4883 (void)copy_all_sparse_entity_data(srcEntity, dstEntity);
4884 copy_nonfragmenting_relation_pairs(dstEntity, EntitySpan{nonfragmentingPairs});
4885 trigger_copy_add_hooks(EntitySpan{&dstEntity, 1}, EntitySpan{addHookIds});
4886 m_observers.add_diff_targets(*this, addDiffCtx, EntitySpan{&dstEntity, 1});
4887
4888 m_observers.on_add(*this, *pDstArchetype, EntitySpan{pAddedIds, addedIdCount}, EntitySpan{&dstEntity, 1});
4889 #if GAIA_OBSERVERS_ENABLED
4890 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4891 #endif
4892
4893 return dstEntity;
4894 }
4895
4906 template <typename Func = TFunc_Void_With_Entity>
4907 void copy_ext_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
4908 cnt::darray_ext<Entity, 8> nonfragmentingPairs;
4909 collect_nonfragmenting_relation_pairs(entity, nonfragmentingPairs);
4910
4911 auto& ec = cont(entity);
4912 auto* pDstArchetype = ec.pArchetype;
4913 if (pDstArchetype->has<EntityDesc>())
4914 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4915
4916 const auto archetypeIdCount = (uint32_t)pDstArchetype->ids_view().size();
4917 const auto sparseIdCount = copied_non_frag_sparse_id_count(entity);
4918 const auto nonfragmentingPairCount = (uint32_t)nonfragmentingPairs.size();
4919 const auto addedIdCount = archetypeIdCount + sparseIdCount + nonfragmentingPairCount;
4920 auto* pAddedIds = addedIdCount != 0U ? (Entity*)alloca(sizeof(Entity) * addedIdCount) : nullptr;
4921 write_archetype_ids(*pDstArchetype, pAddedIds);
4922 write_copied_non_frag_sparse_ids(entity, pAddedIds + archetypeIdCount);
4923 GAIA_FOR(nonfragmentingPairCount)
4924 pAddedIds[archetypeIdCount + sparseIdCount + i] = nonfragmentingPairs[i];
4925 cnt::darray_ext<Entity, 16> addHookIds;
4926 collect_add_hook_ids(EntitySpan{pAddedIds, addedIdCount}, addHookIds);
4927 #if GAIA_OBSERVERS_ENABLED
4928 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{pAddedIds, addedIdCount});
4929 #endif
4930 copy_n_inter(
4931 entity, count, func, EntitySpan{pAddedIds, addedIdCount}, EntitySpan{nonfragmentingPairs},
4932 EntitySpan{addHookIds}, EntityBad
4933 #if GAIA_OBSERVERS_ENABLED
4934 ,
4935 &addDiffCtx
4936 #endif
4937 );
4938 #if GAIA_OBSERVERS_ENABLED
4939 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4940 #endif
4941 }
4942#endif
4943
4944 private:
4952 template <typename Func>
4953 void invoke_copy_batch_callback(
4954 Func& func, Archetype* pDstArchetype, Chunk* pDstChunk, uint32_t originalChunkSize, uint32_t toCreate) {
4955 if constexpr (std::is_invocable_v<Func, CopyIter&>) {
4956 CopyIter it;
4957 it.set_world(this);
4958 it.set_archetype(pDstArchetype);
4959 it.set_chunk(pDstChunk);
4960 it.set_range((uint16_t)originalChunkSize, (uint16_t)toCreate);
4961 func(it);
4962 } else {
4963 auto entities = pDstChunk->entity_view();
4964 GAIA_FOR2(originalChunkSize, pDstChunk->size()) func(entities[i]);
4965 }
4966 }
4967
4972 template <typename Func>
4973 void flush_copy_iter_group(Func& func, CopyIterGroupState& group) {
4974 if (group.count == 0)
4975 return;
4976
4977 CopyIter it;
4978 it.set_world(this);
4979 it.set_archetype(group.pArchetype);
4980 it.set_chunk(group.pChunk);
4981 it.set_range(group.startRow, group.count);
4982 func(it);
4983 group.count = 0;
4984 }
4985
4991 template <typename Func>
4992 void push_copy_iter_group(Func& func, CopyIterGroupState& group, Entity instance) {
4993 const auto& ec = fetch(instance);
4994
4995 if (group.count != 0 && ec.pArchetype == group.pArchetype && ec.pChunk == group.pChunk &&
4996 ec.row == uint16_t(group.startRow + group.count)) {
4997 ++group.count;
4998 return;
4999 }
5000
5001 flush_copy_iter_group(func, group);
5002 group.pArchetype = ec.pArchetype;
5003 group.pChunk = ec.pChunk;
5004 group.startRow = ec.row;
5005 group.count = 1;
5006 }
5007
5011 void prepare_parent_batch(Entity parentEntity, const NonFragmentingRelationStore& parentStore) {
5012 GAIA_ASSERT(valid(parentEntity));
5013 if (parentStore.sources(parentEntity) != nullptr)
5014 return;
5015
5016 const auto parentPair = Pair(Parent, parentEntity);
5017 assign_pair(parentPair, *m_pEntityArchetype);
5018
5019 auto& ecParent = fetch(parentEntity);
5020 EntityBuilder::set_flag(ecParent.flags, EntityContainerFlags::OnDeleteTarget_Delete, true);
5021 }
5022
5025 void prepare_parent_batch(Entity parentEntity) {
5026 const auto* pStore = nonfragmenting_relation_store(Parent);
5027 if (pStore != nullptr) {
5028 prepare_parent_batch(parentEntity, *pStore);
5029 return;
5030 }
5031
5032 GAIA_ASSERT(valid(parentEntity));
5033 const auto parentPair = Pair(Parent, parentEntity);
5034 assign_pair(parentPair, *m_pEntityArchetype);
5035
5036 auto& ecParent = fetch(parentEntity);
5037 EntityBuilder::set_flag(ecParent.flags, EntityContainerFlags::OnDeleteTarget_Delete, true);
5038 }
5039
5046 void parent_batch(
5047 Entity parentEntity, Archetype& archetype, Chunk& chunk, uint32_t originalChunkSize, uint32_t toCreate) {
5048 GAIA_ASSERT(valid(parentEntity));
5049#if !GAIA_OBSERVERS_ENABLED
5050 (void)archetype;
5051#endif
5052
5053 if (toCreate == 0)
5054 return;
5055
5056 auto& parentStore = nonfragmenting_relation_store_mut(Parent);
5057 prepare_parent_batch(parentEntity, parentStore);
5058
5059 auto entities = chunk.entity_view();
5060#if GAIA_OBSERVERS_ENABLED
5061 if (!m_observers.has_on_add_observers()) {
5062 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
5063 nonfragmenting_relation_set(parentStore, entities[rowIdx], Parent, parentEntity);
5064 }
5065 return;
5066 }
5067
5068 const Entity parentPair = Pair(Parent, parentEntity);
5069 auto addDiffCtx = m_observers.prepare_diff(
5070 *this, ObserverEvent::OnAdd, EntitySpan{&parentPair, 1},
5071 EntitySpan{entities.data() + originalChunkSize, toCreate});
5072#endif
5073 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
5074 nonfragmenting_relation_set(parentStore, entities[rowIdx], Parent, parentEntity);
5075 }
5076
5077#if GAIA_OBSERVERS_ENABLED
5078 m_observers.on_add(
5079 *this, archetype, EntitySpan{&parentPair, 1}, EntitySpan{entities.data() + originalChunkSize, toCreate});
5080 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5081#endif
5082 }
5083
5087 void parent_direct(Entity entity, Entity parentEntity) {
5088 GAIA_ASSERT(valid(entity));
5089 GAIA_ASSERT(valid(parentEntity));
5090 auto& parentStore = nonfragmenting_relation_store_mut(Parent);
5091 const auto oldParentEntity = parentStore.target(entity);
5092
5093 if (oldParentEntity == parentEntity)
5094 return;
5095
5096#if GAIA_OBSERVERS_ENABLED
5097 const bool hasOnDelObservers = oldParentEntity != EntityBad && m_observers.has_on_del_observers();
5098 const bool hasOnAddObservers = m_observers.has_on_add_observers();
5099 if (!hasOnDelObservers && !hasOnAddObservers) {
5100 prepare_parent_batch(parentEntity, parentStore);
5101 nonfragmenting_relation_set(parentStore, entity, Parent, parentEntity);
5102 return;
5103 }
5104
5105 const Entity oldParentPair = hasOnDelObservers ? Pair(Parent, oldParentEntity) : EntityBad;
5106 const Entity parentPair = Pair(Parent, parentEntity);
5107 auto delDiffCtx = !hasOnDelObservers
5108 ? ObserverRegistry::DiffDispatchCtx{}
5109 : m_observers.prepare_diff(
5110 *this, ObserverEvent::OnDel, EntitySpan{&oldParentPair, 1}, EntitySpan{&entity, 1});
5111 auto addDiffCtx = !hasOnAddObservers
5112 ? ObserverRegistry::DiffDispatchCtx{}
5113 : m_observers.prepare_diff(
5114 *this, ObserverEvent::OnAdd, EntitySpan{&parentPair, 1}, EntitySpan{&entity, 1});
5115 prepare_parent_batch(parentEntity, parentStore);
5116#else
5117 prepare_parent_batch(parentEntity, parentStore);
5118#endif
5119 nonfragmenting_relation_set(parentStore, entity, Parent, parentEntity);
5120
5121#if GAIA_OBSERVERS_ENABLED
5122 const auto& ec = fetch(entity);
5123 if (hasOnDelObservers)
5124 m_observers.on_del(*this, *ec.pArchetype, EntitySpan{&oldParentPair, 1}, EntitySpan{&entity, 1});
5125 if (hasOnAddObservers)
5126 m_observers.on_add(*this, *ec.pArchetype, EntitySpan{&parentPair, 1}, EntitySpan{&entity, 1});
5127 if (hasOnDelObservers)
5128 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
5129 if (hasOnAddObservers)
5130 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5131#endif
5132 }
5133
5137 void notify_add_single(Entity entity, Entity object) {
5138#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
5139 if GAIA_UNLIKELY (tearing_down())
5140 return;
5141
5142 #if GAIA_OBSERVERS_ENABLED
5143 const auto& ec = fetch(entity);
5144 #endif
5145
5146 lock();
5147
5148 #if GAIA_ENABLE_ADD_DEL_HOOKS
5149 const auto* pItem = component_item(entity, object);
5150 if (pItem != nullptr) {
5151 const auto& hooks = pItem->hooks();
5152 if (hooks.func_add != nullptr)
5153 hooks.func_add(*this, *pItem, entity);
5154 }
5155 #endif
5156
5157 #if GAIA_OBSERVERS_ENABLED
5158 m_observers.on_add(*this, *ec.pArchetype, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
5159 #endif
5160
5161 unlock();
5162#else
5163 (void)entity;
5164 (void)object;
5165#endif
5166 }
5167
5171 void notify_del_single(Entity entity, Entity object) {
5172#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
5173 if GAIA_UNLIKELY (tearing_down())
5174 return;
5175
5176 #if GAIA_OBSERVERS_ENABLED
5177 const auto& ec = fetch(entity);
5178 #endif
5179
5180 lock();
5181
5182 #if GAIA_OBSERVERS_ENABLED
5183 m_observers.on_del(*this, *ec.pArchetype, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
5184 #endif
5185
5186 #if GAIA_ENABLE_ADD_DEL_HOOKS
5187 const auto* pItem = component_item(entity, object);
5188 if (pItem != nullptr) {
5189 const auto& hooks = pItem->hooks();
5190 if (hooks.func_del != nullptr)
5191 hooks.func_del(*this, *pItem, entity);
5192 }
5193 #endif
5194
5195 unlock();
5196#else
5197 (void)entity;
5198 (void)object;
5199#endif
5200 }
5201
5205 GAIA_NODISCARD bool may_notify_del_entity_components(const Archetype& archetype) const {
5206#if GAIA_ENABLE_ADD_DEL_HOOKS
5207 if (m_compCache.hooks_accessed())
5208 return true;
5209#endif
5210#if GAIA_OBSERVERS_ENABLED
5211 return m_observers.has_on_del_observers() &&
5212 (archetype.has_observed_terms() || !m_sparseComponentsByComp.empty());
5213#else
5214 (void)archetype;
5215 return false;
5216#endif
5217 }
5218
5222 void notify_del_entity_components(const EntityContainer& ec, Entity entity) {
5223#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
5224 const auto& archetype = *ec.pArchetype;
5225 if (!may_notify_del_entity_components(archetype))
5226 return;
5227 if GAIA_UNLIKELY (tearing_down())
5228 return;
5229
5230 #if GAIA_OBSERVERS_ENABLED
5231 const bool inspectObserverTerms =
5232 m_observers.has_on_del_observers() && (archetype.has_observed_terms() || !m_sparseComponentsByComp.empty());
5233 #endif
5234 #if GAIA_ENABLE_ADD_DEL_HOOKS
5235 const bool inspectHookTerms = m_compCache.hooks_accessed();
5236 #endif
5237 cnt::darray_ext<Entity, 32> observerTerms;
5238 cnt::darray_ext<Entity, 32> hookTerms;
5239
5240 #if GAIA_OBSERVERS_ENABLED
5241 if (inspectObserverTerms && archetype.has_observed_terms()) {
5242 for (const auto term: archetype.ids_view())
5243 observerTerms.push_back(term);
5244 }
5245 #endif
5246
5247 #if GAIA_ENABLE_ADD_DEL_HOOKS
5248 if (inspectHookTerms) {
5249 for (const auto term: archetype.ids_view()) {
5250 const auto* pItem = component_item(entity, term);
5251 if (pItem != nullptr && pItem->hooks().func_del != nullptr)
5252 hookTerms.push_back(term);
5253 }
5254 }
5255 #endif
5256
5257 for (const auto& [componentKey, store]: m_sparseComponentsByComp) {
5258 const auto component = componentKey.entity();
5259 if (!component_is_non_fragmenting(component) || !store.func_has(store.pStore, entity))
5260 continue;
5261
5262 #if GAIA_OBSERVERS_ENABLED
5263 if ((fetch(component).flags & EntityContainerFlags::IsObserved) != 0)
5264 observerTerms.push_back(component);
5265 #endif
5266 #if GAIA_ENABLE_ADD_DEL_HOOKS
5267 const auto* pItem = component_item(entity, component);
5268 if (inspectHookTerms && pItem != nullptr && pItem->hooks().func_del != nullptr)
5269 hookTerms.push_back(component);
5270 #endif
5271 }
5272
5273 if (observerTerms.empty() && hookTerms.empty())
5274 return;
5275
5276 lock();
5277
5278 #if GAIA_OBSERVERS_ENABLED
5279 if (!observerTerms.empty())
5280 m_observers.on_del(*this, archetype, EntitySpan{observerTerms}, EntitySpan{&entity, 1});
5281 #endif
5282
5283 #if GAIA_ENABLE_ADD_DEL_HOOKS
5284 for (const auto term: hookTerms) {
5285 const auto* pItem = component_item(entity, term);
5286 if (pItem == nullptr)
5287 continue;
5288 const auto& hooks = pItem->hooks();
5289 if (hooks.func_del != nullptr)
5290 hooks.func_del(*this, *pItem, entity);
5291 }
5292 #endif
5293
5294 unlock();
5295#else
5296 (void)ec;
5297 (void)entity;
5298#endif
5299 }
5300
5307 GAIA_NODISCARD bool has_semantic_match_without_source(
5308 Entity entity, Entity object, Entity excludedSource, cnt::set<EntityLookupKey>& visited) const {
5309 const auto inserted = visited.insert(EntityLookupKey(entity));
5310 if (!inserted.second)
5311 return false;
5312
5313 if (entity != excludedSource && has_direct(entity, object))
5314 return true;
5315
5316 const auto it = m_entityToAsTargets.find(EntityLookupKey(entity));
5317 if (it == m_entityToAsTargets.end())
5318 return false;
5319
5320 for (const auto baseKey: it->second) {
5321 if (has_semantic_match_without_source(baseKey.entity(), object, excludedSource, visited))
5322 return true;
5323 }
5324
5325 return false;
5326 }
5327
5331 void notify_inherited_del_dependents(Entity source, Entity object) {
5332#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
5333 const auto& descendants = as_relations_trav_cache(source);
5334 for (const auto descendant: descendants) {
5335 if (descendant == source)
5336 continue;
5337 if (has_direct(descendant, object) || !has(descendant, object))
5338 continue;
5339
5340 cnt::set<EntityLookupKey> visited;
5341 if (has_semantic_match_without_source(descendant, object, source, visited))
5342 continue;
5343
5344 notify_del_single(descendant, object);
5345 }
5346#else
5347 (void)source;
5348 (void)object;
5349#endif
5350 }
5351
5355 void notify_inherited_del_dependents(Entity source, EntitySpan removedObjects) {
5356 for (const auto object: removedObjects)
5357 notify_inherited_del_dependents(source, object);
5358 }
5359
5369#if GAIA_OBSERVERS_ENABLED
5371#endif
5372 template <typename Func>
5373 void copy_n_inter(
5374 Entity entity, uint32_t count, Func& func, EntitySpan addedIds, EntitySpan nonfragmentingPairs,
5375 EntitySpan addHookIds, Entity parentInstance = EntityBad
5376#if GAIA_OBSERVERS_ENABLED
5377 ,
5378 ObserverRegistry::DiffDispatchCtx* pAddDiffCtx = nullptr
5379#endif
5380 ) {
5381 GAIA_ASSERT(valid(entity));
5382 GAIA_ASSERT(parentInstance == EntityBad || valid(parentInstance));
5383#if !GAIA_OBSERVERS_ENABLED
5384 (void)addedIds;
5385#endif
5386
5387 if (count == 0U)
5388 return;
5389
5390#if GAIA_OBSERVERS_ENABLED
5391 const bool useLocalAddDiff = !addedIds.empty() && pAddDiffCtx == nullptr;
5392 ObserverRegistry::DiffDispatchCtx addDiffCtx{};
5393 if (useLocalAddDiff)
5394 addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{addedIds});
5395#endif
5396
5397 auto& ec = cont(entity);
5398
5399 GAIA_ASSERT(ec.pChunk != nullptr);
5400 GAIA_ASSERT(ec.pArchetype != nullptr);
5401
5402 auto* pSrcChunk = ec.pChunk;
5403 auto* pDstArchetype = ec.pArchetype;
5404 const auto hasEntityDesc = pDstArchetype->has<EntityDesc>();
5405 if (hasEntityDesc)
5406 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
5407
5408 if (parentInstance != EntityBad)
5409 prepare_parent_batch(parentInstance);
5410
5411 // Entities array might get reallocated after m_recs.entities.alloc
5412 // so instead of fetching the container again we simply cache the row
5413 // of our source entity.
5414 const auto srcRow = ec.row;
5415
5416 EntityContainerCtx ctx{true, false};
5417
5418 uint32_t left = count;
5419 do {
5420 auto* pDstChunk = pDstArchetype->foc_free_chunk();
5421 const uint32_t originalChunkSize = pDstChunk->size();
5422 const uint32_t freeSlotsInChunk = pDstChunk->capacity() - originalChunkSize;
5423 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
5424
5425 GAIA_FOR(toCreate) {
5426 const auto entityNew = m_recs.entities.alloc(&ctx);
5427 auto& ecNew = m_recs.entities[entityNew.id()];
5428 store_entity(ecNew, entityNew, pDstArchetype, pDstChunk);
5429
5430#if GAIA_ASSERT_ENABLED
5431 GAIA_ASSERT(ecNew.pChunk == pDstChunk);
5432 auto entityExpected = pDstChunk->entity_view()[ecNew.row];
5433 GAIA_ASSERT(entityExpected == entityNew);
5434#endif
5435
5436 if (hasEntityDesc) {
5437 Chunk::copy_foreign_entity_data(pSrcChunk, srcRow, pDstChunk, ecNew.row);
5438 }
5439
5440 copy_all_sparse_entity_data(entity, entityNew);
5441 copy_nonfragmenting_relation_pairs(entityNew, nonfragmentingPairs);
5442 }
5443
5444 pDstArchetype->try_update_free_chunk_idx();
5445
5446 if (!hasEntityDesc) {
5447 pDstChunk->call_ctors(originalChunkSize, toCreate);
5448
5449 {
5450 GAIA_PROF_SCOPE(World::copy_n_entity_data);
5451 Chunk::copy_entity_data_n_same_chunk(pSrcChunk, srcRow, pDstChunk, originalChunkSize, toCreate);
5452 }
5453 }
5454
5455 pDstChunk->update_versions();
5456 auto entities = pDstChunk->entity_view();
5457 trigger_copy_add_hooks(EntitySpan{entities.data() + originalChunkSize, toCreate}, EntitySpan{addHookIds});
5458
5459#if GAIA_OBSERVERS_ENABLED
5460 if (!addedIds.empty()) {
5461 if (pAddDiffCtx != nullptr)
5462 m_observers.add_diff_targets(
5463 *this, *pAddDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
5464 else if (useLocalAddDiff)
5465 m_observers.add_diff_targets(
5466 *this, addDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
5467 m_observers.on_add(
5468 *this, *pDstArchetype, addedIds, EntitySpan{entities.data() + originalChunkSize, toCreate});
5469 }
5470#endif
5471
5472 if (parentInstance != EntityBad)
5473 parent_batch(parentInstance, *pDstArchetype, *pDstChunk, originalChunkSize, toCreate);
5474
5475 invoke_copy_batch_callback(func, pDstArchetype, pDstChunk, originalChunkSize, toCreate);
5476
5477 left -= toCreate;
5478 } while (left > 0);
5479#if GAIA_OBSERVERS_ENABLED
5480 if (useLocalAddDiff)
5481 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5482#endif
5483 }
5484
5488 GAIA_NODISCARD bool id_uses_inherit_policy(Entity id) const {
5489 return !is_wildcard(id) && target(id, OnInstantiate) == Inherit;
5490 }
5491
5496 GAIA_NODISCARD Entity inherited_id_owner(Entity entity, Entity id) const {
5497 if (!id_uses_inherit_policy(id))
5498 return EntityBad;
5499
5500 const auto& targets = as_targets_trav_cache(entity);
5501 for (const auto target: targets) {
5502 if (has_inter(target, id, false))
5503 return target;
5504 }
5505
5506 return EntityBad;
5507 }
5508
5513 GAIA_NODISCARD bool has_direct_sparse_component_inter(Entity entity, Entity object) const {
5514 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
5515 return itSparseStore != m_sparseComponentsByComp.end() &&
5516 itSparseStore->second.func_has(itSparseStore->second.pStore, entity);
5517 }
5518
5525 GAIA_NODISCARD Entity id_owner_inter(Entity entity, Entity object) const {
5526 GAIA_ASSERT(object != EntityBad);
5527 GAIA_ASSERT(!is_wildcard(object));
5528
5529 const auto* pEc = try_live_record(entity);
5530 if (pEc == nullptr)
5531 return EntityBad;
5532
5533 const auto& ec = *pEc;
5534
5535 if (object.pair()) {
5536 if (has_nonfragmenting_relation_pair(entity, object) || ec.pArchetype->has(object))
5537 return entity;
5538 } else {
5539 if (has_direct_sparse_component_inter(entity, object) || ec.pArchetype->has(object))
5540 return entity;
5541 }
5542
5543 return inherited_id_owner(entity, object);
5544 }
5545
5549 GAIA_NODISCARD bool instantiate_copies_id(Entity id) const {
5550 const auto policy = target(id, OnInstantiate);
5551 if (policy == EntityBad || policy == Override)
5552 return true;
5553 if (policy == DontInherit || policy == Inherit)
5554 return false;
5555 return true;
5556 }
5557
5563 template <typename T>
5564 GAIA_NODISCARD bool prefab_child_edge_exists(const T& children, Entity childPrefab) const {
5565 for (const auto& child: children) {
5566 if (child.prefab == childPrefab)
5567 return true;
5568 }
5569
5570 return false;
5571 }
5572
5579 template <typename T>
5580 void gather_prefab_children_for_relation(Entity prefabEntity, Entity relation, T& outChildren) {
5581 sources(relation, prefabEntity, [&](Entity childPrefab) {
5582 if (!has_direct(childPrefab, Prefab) || prefab_child_edge_exists(outChildren, childPrefab))
5583 return;
5584
5585 PrefabChildEdge edge{};
5586 edge.prefab = childPrefab;
5587 edge.relation = relation;
5588 outChildren.push_back(edge);
5589 });
5590 }
5591
5597 template <typename T>
5598 void gather_sorted_prefab_children(Entity prefabEntity, T& outChildren) {
5599 gather_prefab_children_for_relation(prefabEntity, Parent, outChildren);
5600 gather_prefab_children_for_relation(prefabEntity, ChildOf, outChildren);
5601
5602 core::sort(outChildren, [](const PrefabChildEdge& left, const PrefabChildEdge& right) {
5603 if (left.prefab.id() != right.prefab.id())
5604 return left.prefab.id() < right.prefab.id();
5605
5606 return left.relation.id() < right.relation.id();
5607 });
5608 }
5609
5613 GAIA_NODISCARD bool prefab_hierarchy_parent_id(Entity id) const {
5614 if (!id.pair() || id.id() != ChildOf.id())
5615 return false;
5616
5617 const auto parentPrefab = pair_tgt(*this, id);
5618 return valid(parentPrefab) && has_direct(parentPrefab, Prefab);
5619 }
5620
5625 void attach_prefab_child_instance(Entity instance, Entity relation, Entity parentInstance) {
5626 GAIA_ASSERT(valid(instance));
5627 GAIA_ASSERT(valid(relation));
5628 GAIA_ASSERT(valid(parentInstance));
5629
5630 if (relation == Parent) {
5631 parent_direct(instance, parentInstance);
5632 return;
5633 }
5634
5635 add(instance, Pair(relation, parentInstance));
5636 }
5637
5641 void collect_nonfragmenting_relation_pairs(Entity srcEntity, cnt::darray_ext<Entity, 8>& out) const {
5642 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
5643 const auto target = store.target(srcEntity);
5644 if (target != EntityBad)
5645 out.push_back(Pair(relKey.entity(), target));
5646 }
5647
5648 core::sort(out.begin(), out.end(), core::is_smaller<Entity>{});
5649 }
5650
5654 void copy_nonfragmenting_relation_pairs(Entity dstEntity, EntitySpan pairs) {
5655 if (pairs.empty())
5656 return;
5657
5658 EntityBuilder builder(*this, dstEntity);
5659 for (const auto pair: pairs)
5660 builder.add_inter_init(pair);
5661 builder.commit();
5662 }
5663
5671 template <typename TNonFrag, typename TFrag>
5672 void collect_add_n_sparse_ids(Entity srcEntity, TNonFrag& nonFragOut, TFrag& fragOut) const {
5673 nonFragOut.clear();
5674 fragOut.clear();
5675 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
5676 const auto comp = compKey.entity();
5677 if (!store.func_has(store.pStore, srcEntity))
5678 continue;
5679
5680 if (component_is_non_fragmenting(comp)) {
5681 nonFragOut.push_back(comp);
5682 continue;
5683 }
5684
5685 if (sparse_storage_mode(comp) != SparseStorageMode::None)
5686 fragOut.push_back(comp);
5687 }
5688 }
5689
5693 void add_default_sparse_ids(Entity dstEntity, EntitySpan sparseIds) {
5694 for (const auto comp: sparseIds) {
5695 auto it = m_sparseComponentsByComp.find(EntityLookupKey(comp));
5696 GAIA_ASSERT(it != m_sparseComponentsByComp.end());
5697 GAIA_ASSERT(it->second.func_add != nullptr);
5698 (void)it->second.func_add(it->second.pStore, dstEntity);
5699 }
5700 }
5701
5708 void apply_add_n_non_table_type(
5709 Entity srcEntity, Entity dstEntity, EntitySpan nonfragmentingPairs, EntitySpan nonFragSparseIds,
5710 EntitySpan fragSparseIds) {
5711 copy_nonfragmenting_relation_pairs(dstEntity, nonfragmentingPairs);
5712 (void)copy_sparse_entity_data(srcEntity, dstEntity, nonFragSparseIds);
5713 add_default_sparse_ids(dstEntity, fragSparseIds);
5714 }
5715
5722 GAIA_NODISCARD bool copy_sparse_store_inter(
5723 Entity srcEntity, Entity dstEntity, Entity comp, const SparseComponentStoreErased& store) {
5724 if (!copies_sparse_payload_inter(comp, srcEntity, store))
5725 return false;
5726
5727 GAIA_ASSERT(store.func_copy_entity != nullptr);
5728 return store.func_copy_entity(store.pStore, dstEntity, srcEntity);
5729 }
5730
5736 uint32_t copy_all_sparse_entity_data(Entity srcEntity, Entity dstEntity, Entity* pCopiedIds = nullptr) {
5737 uint32_t copiedCnt = 0;
5738 for (auto& [compKey, store]: m_sparseComponentsByComp) {
5739 const auto comp = compKey.entity();
5740 if (!copy_sparse_store_inter(srcEntity, dstEntity, comp, store))
5741 continue;
5742
5743 if (pCopiedIds != nullptr)
5744 pCopiedIds[copiedCnt] = comp;
5745 ++copiedCnt;
5746 }
5747
5748 return copiedCnt;
5749 }
5750
5755 template <typename T>
5756 void collect_add_hook_ids(EntitySpan addedIds, T& outHookIds) const {
5757 outHookIds.clear();
5758#if GAIA_ENABLE_ADD_DEL_HOOKS
5759 if (!m_compCache.hooks_accessed())
5760 return;
5761
5762 for (const auto id: addedIds) {
5763 const auto* pItem = id.pair() ? comp_cache().find_pair_payload(id) : comp_cache().find(id);
5764 if (pItem != nullptr && pItem->hooks().func_add != nullptr)
5765 outHookIds.push_back(id);
5766 }
5767#else
5768 (void)addedIds;
5769#endif
5770 }
5771
5778 template <typename T>
5779 void collect_copy_add_hook_ids(
5780 Entity srcEntity, const Archetype& dstArchetype, EntitySpan nonfragmentingPairs, T& outHookIds) const {
5781 outHookIds.clear();
5782#if GAIA_ENABLE_ADD_DEL_HOOKS
5783 if (!m_compCache.hooks_accessed())
5784 return;
5785
5786 auto appendHook = [&](Entity id) {
5787 const auto* pItem = id.pair() ? comp_cache().find_pair_payload(id) : comp_cache().find(id);
5788 if (pItem != nullptr && pItem->hooks().func_add != nullptr)
5789 outHookIds.push_back(id);
5790 };
5791
5792 for (const auto id: dstArchetype.ids_view())
5793 appendHook(id);
5794 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
5795 const auto component = compKey.entity();
5796 if (copies_non_frag_sparse_payload_inter(component, srcEntity, store))
5797 appendHook(component);
5798 }
5799 for (const auto pair: nonfragmentingPairs)
5800 appendHook(pair);
5801#else
5802 (void)srcEntity;
5803 (void)dstArchetype;
5804 (void)nonfragmentingPairs;
5805#endif
5806 }
5807
5811 void trigger_copy_add_hooks(EntitySpan entities, EntitySpan hookIds) {
5812#if GAIA_ENABLE_ADD_DEL_HOOKS
5813 if (hookIds.empty() || tearing_down())
5814 return;
5815
5816 lock();
5817 for (const auto id: hookIds) {
5818 for (const auto entity: entities) {
5819 const auto* pItem = component_item(entity, id);
5820 if (pItem == nullptr)
5821 continue;
5822 const auto& hooks = pItem->hooks();
5823 if (hooks.func_add != nullptr)
5824 hooks.func_add(*this, *pItem, entity);
5825 }
5826 }
5827 unlock();
5828#else
5829 (void)entities;
5830 (void)hookIds;
5831#endif
5832 }
5833
5840 uint32_t copy_sparse_entity_data(
5841 Entity srcEntity, Entity dstEntity, EntitySpan copiedSparseIds, Entity* pCopiedIds = nullptr) {
5842 uint32_t copiedCnt = 0;
5843 for (const auto comp: copiedSparseIds) {
5844 auto it = m_sparseComponentsByComp.find(EntityLookupKey(comp));
5845 GAIA_ASSERT(it != m_sparseComponentsByComp.end());
5846
5847 auto& store = it->second;
5848 if (!copy_sparse_store_inter(srcEntity, dstEntity, comp, store))
5849 continue;
5850
5851 if (pCopiedIds != nullptr)
5852 pCopiedIds[copiedCnt] = comp;
5853 ++copiedCnt;
5854 }
5855
5856 return copiedCnt;
5857 }
5858
5862 void write_archetype_ids(const Archetype& dstArchetype, Entity* pDst) const {
5863 for (const auto id: dstArchetype.ids_view())
5864 *pDst++ = id;
5865 }
5866
5870 GAIA_NODISCARD uint32_t copied_non_frag_sparse_id_count(Entity srcEntity) const {
5871 uint32_t count = 0;
5872 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
5873 const auto comp = compKey.entity();
5874 if (!copies_non_frag_sparse_payload_inter(comp, srcEntity, store))
5875 continue;
5876 ++count;
5877 }
5878
5879 return count;
5880 }
5881
5885 void write_copied_non_frag_sparse_ids(Entity srcEntity, Entity* pDst) const {
5886 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
5887 const auto comp = compKey.entity();
5888 if (!copies_non_frag_sparse_payload_inter(comp, srcEntity, store))
5889 continue;
5890 *pDst++ = comp;
5891 }
5892 }
5893
5899 GAIA_NODISCARD bool copy_sparse_payload_inter(Entity dstEntity, Entity srcEntity, Entity object) {
5900 if (!component_is_non_fragmenting(object) && sparse_storage_mode(object) == SparseStorageMode::None)
5901 return false;
5902
5903 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
5904 if (itSparseStore == m_sparseComponentsByComp.end())
5905 return false;
5906
5907 return copy_sparse_store_inter(srcEntity, dstEntity, object, itSparseStore->second);
5908 }
5909
5914 GAIA_NODISCARD bool override_sparse_component_inter(Entity entity, Entity object) {
5915 GAIA_ASSERT(valid(entity));
5916 GAIA_ASSERT(valid(object));
5917 GAIA_ASSERT(sparse_storage_mode(object) != SparseStorageMode::None);
5918
5919 if (has_direct(entity, object))
5920 return false;
5921
5922 const auto inheritedOwner = inherited_id_owner(entity, object);
5923 if (inheritedOwner == EntityBad)
5924 return false;
5925
5926 if (!copy_sparse_payload_inter(entity, inheritedOwner, object))
5927 return false;
5928
5929 if (sparse_storage_mode(object) == SparseStorageMode::Fragmenting)
5930 make_sparse_copy_direct_inter(entity, object);
5931 return true;
5932 }
5933
5940 GAIA_NODISCARD bool copy_owned_sparse_component_inter(Entity srcEntity, Entity dstEntity, Entity object) {
5941 GAIA_ASSERT(valid(srcEntity));
5942 GAIA_ASSERT(valid(dstEntity));
5943 GAIA_ASSERT(valid(object));
5944 GAIA_ASSERT(sparse_storage_mode(object) != SparseStorageMode::None);
5945
5946 const auto mode = sparse_storage_mode(object);
5947 if (mode == SparseStorageMode::Fragmenting) {
5948 if (!copy_sparse_payload_inter(dstEntity, srcEntity, object))
5949 return false;
5950
5951 make_sparse_copy_direct_inter(dstEntity, object);
5952 notify_add_single(dstEntity, object);
5953 return true;
5954 }
5955#if GAIA_OBSERVERS_ENABLED
5956 auto addDiffCtx =
5957 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&dstEntity, 1});
5958#endif
5959 if (!copy_sparse_payload_inter(dstEntity, srcEntity, object))
5960 return false;
5961 notify_add_single(dstEntity, object);
5962#if GAIA_OBSERVERS_ENABLED
5963 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5964#endif
5965 return true;
5966 }
5967
5971 void make_sparse_copy_direct_inter(Entity entity, Entity object) {
5972 GAIA_ASSERT(sparse_storage_mode(object) == SparseStorageMode::Fragmenting);
5973 EntityBuilder eb(*this, entity);
5974 eb.add_inter_init(object);
5975 eb.commit();
5976 }
5977
5983 void copy_direct_component_data_inter(
5984 Entity srcEntity, Entity dstEntity, Entity object, const ComponentCacheItem& item) {
5985 GAIA_ASSERT(valid(srcEntity));
5986 GAIA_ASSERT(valid(dstEntity));
5987 GAIA_ASSERT(object.pair() || valid(object));
5988 GAIA_ASSERT(object.pair() || item.entity == object);
5989 GAIA_ASSERT(item.comp.size() != 0U);
5990
5991 const auto& ecDst = fetch(dstEntity);
5992 const auto& ecSrc = fetch(srcEntity);
5993 const auto compIdxDst = ecDst.pChunk->comp_idx(object);
5994 const auto compIdxSrc = ecSrc.pChunk->comp_idx(object);
5995 GAIA_ASSERT(compIdxDst != BadIndex && compIdxSrc != BadIndex);
5996
5997 const auto idxDst = ecDst.row;
5998 const auto idxSrc = ecSrc.row;
5999 void* pDst = ecDst.pChunk->comp_ptr_mut(compIdxDst);
6000 const void* pSrc = ecSrc.pChunk->comp_ptr(compIdxSrc);
6001 item.copy(pDst, pSrc, idxDst, idxSrc, ecDst.pChunk->capacity(), ecSrc.pChunk->capacity());
6002 }
6003
6008 GAIA_NODISCARD bool override_inter(Entity entity, Entity object) {
6009 GAIA_ASSERT(valid(entity));
6010 GAIA_ASSERT(object.pair() || valid(object));
6011
6012 if (has_direct(entity, object))
6013 return false;
6014
6015 const auto inheritedOwner = inherited_id_owner(entity, object);
6016 if (inheritedOwner == EntityBad)
6017 return false;
6018
6019 const auto* pItem = component_item(inheritedOwner, object);
6020 if (pItem != nullptr && sparse_storage_mode(object) != SparseStorageMode::None)
6021 return override_sparse_component_inter(entity, object);
6022
6023 if (pItem != nullptr && pItem->comp.size() != 0U) {
6024 EntityBuilder eb(*this, entity);
6025 eb.add_inter_init(object);
6026 eb.commit();
6027 copy_direct_component_data_inter(inheritedOwner, entity, object, *pItem);
6028 notify_add_single(entity, object);
6029 return true;
6030 }
6031
6032 add(entity, object);
6033 return true;
6034 }
6035
6041 GAIA_NODISCARD bool copy_owned_id_from_entity(Entity srcEntity, Entity dstEntity, Entity object) {
6042 GAIA_ASSERT(valid(srcEntity));
6043 GAIA_ASSERT(valid(dstEntity));
6044 GAIA_ASSERT(object.pair() || valid(object));
6045
6046 if (has_direct(dstEntity, object))
6047 return false;
6048
6049 const auto* pItem = component_item(srcEntity, object);
6050 if (pItem != nullptr && sparse_storage_mode(object) != SparseStorageMode::None)
6051 return copy_owned_sparse_component_inter(srcEntity, dstEntity, object);
6052
6053 if (pItem != nullptr && pItem->comp.size() != 0U) {
6054 EntityBuilder eb(*this, dstEntity);
6055 eb.add_inter_init(object);
6056 eb.commit();
6057 copy_direct_component_data_inter(srcEntity, dstEntity, object, *pItem);
6058 notify_add_single(dstEntity, object);
6059 return true;
6060 }
6061
6062 add(dstEntity, object);
6063 return true;
6064 }
6065
6069 GAIA_NODISCARD Archetype* instantiate_prefab_dst_archetype(Entity prefabEntity) {
6070 GAIA_ASSERT(valid(prefabEntity));
6071 GAIA_ASSERT(has_direct(prefabEntity, Prefab));
6072
6073 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab))
6074 return fetch(prefabEntity).pArchetype;
6075
6076 auto& ecSrc = cont(prefabEntity);
6077 GAIA_ASSERT(ecSrc.pArchetype != nullptr);
6078
6079 auto* pDstArchetype = ecSrc.pArchetype;
6080 if (pDstArchetype->has<EntityDesc>())
6081 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
6082 if (pDstArchetype->has(Prefab))
6083 pDstArchetype = foc_archetype_del(pDstArchetype, Prefab);
6084
6085 for (const auto id: ecSrc.pArchetype->ids_view()) {
6086 if (id.pair() && id.id() == Is.id()) {
6087 pDstArchetype = foc_archetype_del(pDstArchetype, id);
6088 continue;
6089 }
6090 if (prefab_hierarchy_parent_id(id)) {
6091 pDstArchetype = foc_archetype_del(pDstArchetype, id);
6092 continue;
6093 }
6094
6095 if (!instantiate_copies_id(id))
6096 pDstArchetype = foc_archetype_del(pDstArchetype, id);
6097 }
6098
6099 // Nested pairs are not representable, so pair prefabs cannot receive Pair(Is, prefab).
6100 if (!prefabEntity.pair()) {
6101 const auto isPair = Pair(Is, prefabEntity);
6102 assign_pair(isPair, *m_pEntityArchetype);
6103 pDstArchetype = foc_archetype_add(pDstArchetype, isPair);
6104 }
6105
6106 return pDstArchetype;
6107 }
6108
6113 template <typename T>
6114 void collect_prefab_copied_sparse_ids(Entity prefabEntity, T& outCopiedSparseIds) {
6115 outCopiedSparseIds.clear();
6116 if (m_sparseComponentsByComp.empty())
6117 return;
6118
6119 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
6120 const auto comp = compKey.entity();
6121 if (!instantiate_copies_id(comp) || !copies_sparse_payload_inter(comp, prefabEntity, store))
6122 continue;
6123 outCopiedSparseIds.push_back(comp);
6124 }
6125 }
6126
6132 template <typename T>
6133 void collect_prefab_added_ids(Archetype* pDstArchetype, EntitySpan copiedSparseIds, T& outAddedIds) {
6134 outAddedIds.clear();
6135 for (const auto id: pDstArchetype->ids_view())
6136 outAddedIds.push_back(id);
6137
6138 for (const auto comp: copiedSparseIds) {
6139 if (sparse_copy_adds_id_inter(comp))
6140 outAddedIds.push_back(comp);
6141 }
6142 }
6143
6152 GAIA_NODISCARD Entity instantiate_prefab_node_inter(
6153 Entity prefabEntity, Archetype* pDstArchetype, Entity parentInstance, EntitySpan copiedSparseIds,
6154 EntitySpan addedIds, [[maybe_unused]] EntitySpan addHookIds) {
6155 GAIA_ASSERT(valid(prefabEntity));
6156 GAIA_ASSERT(has_direct(prefabEntity, Prefab));
6157 GAIA_ASSERT(pDstArchetype != nullptr);
6158#if GAIA_OBSERVERS_ENABLED
6159 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{addedIds});
6160#endif
6161
6162 auto& ecSrc = cont(prefabEntity);
6163 GAIA_ASSERT(ecSrc.pArchetype != nullptr);
6164 GAIA_ASSERT(ecSrc.pChunk != nullptr);
6165
6166 EntityContainerCtx ctx{true, false};
6167 const auto instance = m_recs.entities.alloc(&ctx);
6168 auto& ecDst = m_recs.entities[instance.id()];
6169 auto* pDstChunk = pDstArchetype->foc_free_chunk();
6170 store_entity(ecDst, instance, pDstArchetype, pDstChunk);
6171 pDstArchetype->try_update_free_chunk_idx();
6172 Chunk::copy_foreign_entity_data(ecSrc.pChunk, ecSrc.row, pDstChunk, ecDst.row);
6173 pDstChunk->update_versions();
6174
6175 if (!prefabEntity.pair())
6176 ecDst.flags |= EntityContainerFlags::HasAliasOf;
6177
6178 // Keep payload copy and observer/add-id reporting separate:
6179 // fragmenting sparse payloads must still be copied here even though their id is
6180 // already present in the destination archetype and therefore absent from addedIds tail.
6181 (void)copy_sparse_entity_data(prefabEntity, instance, copiedSparseIds);
6182#if GAIA_OBSERVERS_ENABLED
6183 m_observers.add_diff_targets(*this, addDiffCtx, EntitySpan{&instance, 1});
6184#endif
6185
6186 if (!prefabEntity.pair()) {
6187 invalidate_relation_caches(Is);
6188
6189 const auto instanceKey = EntityLookupKey(instance);
6190 const auto prefabKey = EntityLookupKey(prefabEntity);
6191 m_entityToAsTargets[instanceKey].insert(prefabKey);
6192 m_entityToAsTargetsTravCache = {};
6193 m_entityToAsRelations[prefabKey].insert(instanceKey);
6194 m_entityToAsRelationsTravCache = {};
6195 invalidate_queries_for_entity({Is, prefabEntity});
6196 }
6197
6198#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
6199 if GAIA_UNLIKELY (tearing_down()) {
6200 (void)pDstArchetype;
6201 (void)addedIds;
6202 } else {
6203 lock();
6204
6205 #if GAIA_ENABLE_ADD_DEL_HOOKS
6206 for (const auto id: addHookIds) {
6207 const auto* pItem = component_item(instance, id);
6208 GAIA_ASSERT(pItem != nullptr);
6209 const auto& hooks = pItem->hooks();
6210 GAIA_ASSERT(hooks.func_add != nullptr);
6211 hooks.func_add(*this, *pItem, instance);
6212 }
6213 #endif
6214
6215 #if GAIA_OBSERVERS_ENABLED
6216 m_observers.on_add(*this, *pDstArchetype, addedIds, EntitySpan{&instance, 1});
6217 #endif
6218
6219 unlock();
6220 }
6221#endif
6222
6223#if GAIA_OBSERVERS_ENABLED
6224 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
6225#endif
6226
6227 if (parentInstance != EntityBad)
6228 parent_direct(instance, parentInstance);
6229
6230 return instance;
6231 }
6232
6237 GAIA_NODISCARD Entity instantiate_prefab_node_inter(Entity prefabEntity, Entity parentInstance) {
6238 auto* pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
6239 cnt::darray_ext<Entity, 16> copiedSparseIds;
6240 cnt::darray_ext<Entity, 16> addedIds;
6241 cnt::darray_ext<Entity, 16> addHookIds;
6242 collect_prefab_copied_sparse_ids(prefabEntity, copiedSparseIds);
6243 collect_prefab_added_ids(pDstArchetype, EntitySpan{copiedSparseIds}, addedIds);
6244 collect_add_hook_ids(EntitySpan{addedIds}, addHookIds);
6245 return instantiate_prefab_node_inter(
6246 prefabEntity, pDstArchetype, parentInstance, EntitySpan{copiedSparseIds}, EntitySpan{addedIds},
6247 EntitySpan{addHookIds});
6248 }
6249
6256 template <typename Func>
6257 void instantiate_prefab_n_inter(
6258 const PrefabInstantiatePlanNode& node, Entity parentInstance, uint32_t count, Func& func) {
6259 GAIA_ASSERT(node.prefab != EntityBad);
6260 GAIA_ASSERT(node.pDstArchetype != nullptr);
6261
6262 if (count == 0U)
6263 return;
6264#if GAIA_OBSERVERS_ENABLED
6265 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{node.addedIds});
6266#endif
6267
6268 auto& ecSrc = cont(node.prefab);
6269 GAIA_ASSERT(ecSrc.pChunk != nullptr);
6270
6271 if (parentInstance != EntityBad)
6272 prepare_parent_batch(parentInstance);
6273
6274 const auto srcRow = ecSrc.row;
6275 auto* pSrcChunk = ecSrc.pChunk;
6276 auto* pDstArchetype = node.pDstArchetype;
6277 const bool bindIs = !node.prefab.pair();
6278 const auto prefabKey = EntityLookupKey(node.prefab);
6279 EntityContainerCtx ctx{true, false};
6280 if (bindIs) {
6281 auto& asRelations = m_entityToAsRelations[prefabKey];
6282 m_entityToAsTargets.reserve(m_entityToAsTargets.size() + count);
6283 asRelations.reserve(asRelations.size() + count);
6284 }
6285
6286 uint32_t left = count;
6287 do {
6288 auto* pDstChunk = pDstArchetype->foc_free_chunk();
6289 const uint32_t originalChunkSize = pDstChunk->size();
6290 const uint32_t freeSlotsInChunk = pDstChunk->capacity() - originalChunkSize;
6291 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
6292
6293 GAIA_FOR_(toCreate, rowOffset) {
6294 const auto instance = m_recs.entities.alloc(&ctx);
6295 auto& ecDst = m_recs.entities[instance.id()];
6296 store_entity(ecDst, instance, pDstArchetype, pDstChunk);
6297 if (bindIs)
6298 ecDst.flags |= EntityContainerFlags::HasAliasOf;
6299
6300 (void)copy_sparse_entity_data(node.prefab, instance, EntitySpan{node.copiedSparseIds});
6301 }
6302
6303 pDstArchetype->try_update_free_chunk_idx();
6304 Chunk::copy_foreign_entity_data_n(pSrcChunk, srcRow, pDstChunk, originalChunkSize, toCreate);
6305 pDstChunk->update_versions();
6306
6307 auto entities = pDstChunk->entity_view();
6308 if (bindIs) {
6309 auto& asRelations = m_entityToAsRelations[prefabKey];
6310 invalidate_relation_caches(Is);
6311
6312 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
6313 const auto instance = entities[rowIdx];
6314 m_entityToAsTargets[EntityLookupKey(instance)].insert(prefabKey);
6315 asRelations.insert(EntityLookupKey(instance));
6316 }
6317 m_entityToAsTargetsTravCache = {};
6318 m_entityToAsRelationsTravCache = {};
6319 invalidate_queries_for_entity({Is, node.prefab});
6320 }
6321
6322#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
6323 if GAIA_UNLIKELY (tearing_down()) {
6324 (void)entities;
6325 (void)originalChunkSize;
6326 (void)toCreate;
6327 } else {
6328 lock();
6329
6330 #if GAIA_ENABLE_ADD_DEL_HOOKS
6331 for (const auto id: node.addHookIds) {
6332 const auto* pItem = component_item(entities[originalChunkSize], id);
6333 GAIA_ASSERT(pItem != nullptr);
6334 const auto& hooks = pItem->hooks();
6335 GAIA_ASSERT(hooks.func_add != nullptr);
6336
6337 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
6338 hooks.func_add(*this, *pItem, entities[rowIdx]);
6339 }
6340 }
6341 #endif
6342
6343 #if GAIA_OBSERVERS_ENABLED
6344 m_observers.add_diff_targets(*this, addDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
6345 m_observers.on_add(
6346 *this, *pDstArchetype, EntitySpan{node.addedIds},
6347 EntitySpan{entities.data() + originalChunkSize, toCreate});
6348 #endif
6349
6350 unlock();
6351 }
6352#endif
6353
6354 if (parentInstance != EntityBad)
6355 parent_batch(parentInstance, *pDstArchetype, *pDstChunk, originalChunkSize, toCreate);
6356
6357 invoke_copy_batch_callback(func, pDstArchetype, pDstChunk, originalChunkSize, toCreate);
6358
6359 left -= toCreate;
6360 } while (left > 0);
6361#if GAIA_OBSERVERS_ENABLED
6362 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
6363#endif
6364 }
6365
6372 template <typename T>
6373 void build_prefab_instantiate_plan(Entity prefabEntity, uint32_t parentIdx, Entity parentRelation, T& plan) {
6374 PrefabInstantiatePlanNode node{};
6375 node.prefab = prefabEntity;
6376 node.parentIdx = parentIdx;
6377 node.parentRelation = parentRelation;
6378 node.pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
6379 collect_prefab_copied_sparse_ids(prefabEntity, node.copiedSparseIds);
6380 collect_prefab_added_ids(node.pDstArchetype, EntitySpan{node.copiedSparseIds}, node.addedIds);
6381 collect_add_hook_ids(EntitySpan{node.addedIds}, node.addHookIds);
6382
6383 const auto nodeIdx = (uint32_t)plan.size();
6384 plan.push_back(GAIA_MOV(node));
6385
6386 cnt::darray_ext<PrefabChildEdge, 16> prefabChildren;
6387 gather_sorted_prefab_children(prefabEntity, prefabChildren);
6388
6389 for (const auto& child: prefabChildren)
6390 build_prefab_instantiate_plan(child.prefab, nodeIdx, child.relation, plan);
6391 }
6392
6398 GAIA_NODISCARD bool instance_has_prefab_child(Entity parentInstance, Entity relation, Entity childPrefab) const {
6399 if (childPrefab.pair())
6400 return false;
6401
6402 bool found = false;
6403 sources(relation, parentInstance, [&](Entity child) {
6404 if (found)
6405 return;
6406 if (has_direct(child, Pair(Is, childPrefab)))
6407 found = true;
6408 });
6409 return found;
6410 }
6411
6419 template <typename T>
6420 uint32_t sync_prefab_instance(
6421 Entity prefabEntity, Entity instance, const PrefabInstantiatePlanNode& node, const T& prefabChildren) {
6422 uint32_t changes = 0;
6423
6424 const auto isPair = Pair(Is, prefabEntity);
6425 for (const auto id: node.pDstArchetype->ids_view()) {
6426 if (id == isPair || has_direct(instance, id))
6427 continue;
6428 if (copy_owned_id_from_entity(prefabEntity, instance, id))
6429 ++changes;
6430 }
6431
6432 for (const auto comp: node.copiedSparseIds) {
6433 if (has_direct(instance, comp))
6434 continue;
6435 if (copy_owned_id_from_entity(prefabEntity, instance, comp))
6436 ++changes;
6437 }
6438
6439 for (const auto& child: prefabChildren) {
6440 if (instance_has_prefab_child(instance, child.relation, child.prefab))
6441 continue;
6442 (void)instantiate_inter(child.prefab, instance, child.relation);
6443 ++changes;
6444 }
6445
6446 return changes;
6447 }
6448
6453 uint32_t sync_prefab_inter(Entity prefabEntity, cnt::set<EntityLookupKey>& visited) {
6454 GAIA_ASSERT(valid(prefabEntity));
6455
6456 if (!has_direct(prefabEntity, Prefab))
6457 return 0;
6458
6459 const auto ins = visited.insert(EntityLookupKey(prefabEntity));
6460 if (!ins.second)
6461 return 0;
6462
6463 PrefabInstantiatePlanNode node{};
6464 node.prefab = prefabEntity;
6465 node.pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
6466 collect_prefab_copied_sparse_ids(prefabEntity, node.copiedSparseIds);
6467 collect_prefab_added_ids(node.pDstArchetype, EntitySpan{node.copiedSparseIds}, node.addedIds);
6468 collect_add_hook_ids(EntitySpan{node.addedIds}, node.addHookIds);
6469
6470 cnt::darray_ext<PrefabChildEdge, 16> prefabChildren;
6471 gather_sorted_prefab_children(prefabEntity, prefabChildren);
6472
6473 uint32_t changes = 0;
6474 const auto& descendants = as_relations_trav_cache(prefabEntity);
6475 for (const auto entity: descendants) {
6476 if (has_direct(entity, Prefab))
6477 continue;
6478 changes += sync_prefab_instance(prefabEntity, entity, node, prefabChildren);
6479 }
6480
6481 for (const auto& child: prefabChildren)
6482 changes += sync_prefab_inter(child.prefab, visited);
6483
6484 return changes;
6485 }
6486
6492 GAIA_NODISCARD Entity
6493 instantiate_inter(Entity prefabEntity, Entity parentInstance, Entity parentRelation = Parent) {
6494 const auto directParentInstance = parentRelation == Parent ? parentInstance : EntityBad;
6495 const auto instance = instantiate_prefab_node_inter(prefabEntity, directParentInstance);
6496 if (parentInstance != EntityBad && parentRelation != Parent)
6497 attach_prefab_child_instance(instance, parentRelation, parentInstance);
6498
6499 cnt::darray_ext<PrefabChildEdge, 16> prefabChildren;
6500 gather_sorted_prefab_children(prefabEntity, prefabChildren);
6501
6502 for (const auto& child: prefabChildren)
6503 (void)instantiate_inter(child.prefab, instance, child.relation);
6504
6505 return instance;
6506 }
6507
6508 public:
6517 GAIA_NODISCARD Entity instantiate(Entity prefabEntity) {
6518 GAIA_ASSERT(valid(prefabEntity));
6519
6520 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab))
6521 return copy(prefabEntity);
6522
6523 return instantiate_inter(prefabEntity, EntityBad);
6524 }
6525
6536 GAIA_NODISCARD Entity instantiate(Entity prefabEntity, Entity parentInstance) {
6537 GAIA_ASSERT(valid(prefabEntity));
6538 GAIA_ASSERT(valid(parentInstance));
6539
6540 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab)) {
6541 const auto instance = copy(prefabEntity);
6542 parent_direct(instance, parentInstance);
6543 return instance;
6544 }
6545
6546 return instantiate_inter(prefabEntity, parentInstance);
6547 }
6548
6564 template <typename Func = TFunc_Void_With_Entity>
6565 void instantiate_n(Entity prefabEntity, uint32_t count, Func func = func_void_with_entity) {
6566 instantiate_n(prefabEntity, EntityBad, count, func);
6567 }
6568
6575 void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count) {
6576 instantiate_n(prefabEntity, parentInstance, count, func_void_with_entity);
6577 }
6578
6588 template <typename Func>
6589 void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count, Func func) {
6590 GAIA_ASSERT(valid(prefabEntity));
6591 GAIA_ASSERT(parentInstance == EntityBad || valid(parentInstance));
6592
6593 if (count == 0U)
6594 return;
6595
6596 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab)) {
6597 if (parentInstance == EntityBad) {
6598 copy_n(prefabEntity, count, func);
6599 return;
6600 }
6601
6602 cnt::darray_ext<Entity, 8> nonfragmentingPairs;
6603 collect_nonfragmenting_relation_pairs(prefabEntity, nonfragmentingPairs);
6604 auto* pDstArchetype = cont(prefabEntity).pArchetype;
6605 if (pDstArchetype->has<EntityDesc>())
6606 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
6607 cnt::darray_ext<Entity, 16> addHookIds;
6608 collect_copy_add_hook_ids(prefabEntity, *pDstArchetype, EntitySpan{nonfragmentingPairs}, addHookIds);
6609 copy_n_inter(
6610 prefabEntity, count, func, EntitySpan{}, EntitySpan{nonfragmentingPairs}, EntitySpan{addHookIds},
6611 parentInstance);
6612 return;
6613 }
6614
6616 build_prefab_instantiate_plan(prefabEntity, BadIndex, EntityBad, plan);
6617 if (plan.size() == 1) {
6618 instantiate_prefab_n_inter(plan[0], parentInstance, count, func);
6619 return;
6620 }
6621
6622 const auto planSize = (uint32_t)plan.size();
6623 GAIA_ASSERT(planSize <= uint32_t(-1) / count);
6624 cnt::darray<Entity> spawned;
6625 spawned.resize(planSize * count);
6626
6627 uint32_t rootIdx = 0;
6628 auto collectRoot = [&](Entity instance) {
6629 spawned[rootIdx++] = instance;
6630 };
6631 instantiate_prefab_n_inter(plan[0], parentInstance, count, collectRoot);
6632
6633 GAIA_FOR2_(1, planSize, planIdx) {
6634 uint32_t nodeIdx = 0;
6635 const auto nodeOffset = planIdx * count;
6636 auto collectNode = [&](Entity instance) {
6637 spawned[nodeOffset + nodeIdx++] = instance;
6638 };
6639 instantiate_prefab_n_inter(plan[planIdx], EntityBad, count, collectNode);
6640
6641 const auto parentOffset = plan[planIdx].parentIdx * count;
6642 GAIA_FOR_(count, instanceIdx) {
6643 attach_prefab_child_instance(
6644 spawned[nodeOffset + instanceIdx], plan[planIdx].parentRelation, spawned[parentOffset + instanceIdx]);
6645 }
6646 }
6647
6648 if constexpr (std::is_invocable_v<Func, CopyIter&>) {
6649 CopyIterGroupState group;
6650 GAIA_FOR_(count, idx) {
6651 push_copy_iter_group(func, group, spawned[idx]);
6652 }
6653 flush_copy_iter_group(func, group);
6654 } else {
6655 GAIA_FOR_(count, idx) {
6656 func(spawned[idx]);
6657 }
6658 }
6659 }
6660
6667 GAIA_NODISCARD uint32_t sync(Entity prefabEntity) {
6668 GAIA_ASSERT(valid(prefabEntity));
6669
6671 return sync_prefab_inter(prefabEntity, visited);
6672 }
6673
6680 GAIA_NODISCARD Entity find_prefab_instance(Entity instanceRoot, Entity prefabEntity) const {
6681 if (!valid(instanceRoot) || !valid(prefabEntity) || prefabEntity.pair())
6682 return EntityBad;
6683
6684 const auto isPair = Pair(Is, prefabEntity);
6685 if (has_direct(instanceRoot, isPair))
6686 return instanceRoot;
6687
6688 Entity found = EntityBad;
6692 queue.push_back(instanceRoot);
6693 visited.insert(EntityLookupKey(instanceRoot));
6694
6695 for (uint32_t i = 0; i < queue.size() && found == EntityBad; ++i) {
6696 children.clear();
6697 auto collectChild = [&](Entity child) {
6698 const auto key = EntityLookupKey(child);
6699 const auto ins = visited.insert(key);
6700 if (!ins.second)
6701 return;
6702
6703 children.push_back(child);
6704 };
6705 sources(Parent, queue[i], collectChild);
6706 sources(ChildOf, queue[i], collectChild);
6707
6708 core::sort(children, SortEntityIdCond{});
6709
6710 for (const auto child: children) {
6711 if (has_direct(child, isPair)) {
6712 found = child;
6713 break;
6714 }
6715
6716 queue.push_back(child);
6717 }
6718 }
6719
6720 return found;
6721 }
6722
6723 //----------------------------------------------------------------------
6724
6727 void del(Entity entity) {
6728 if (entity_deletion_active(entity))
6729 return;
6730
6731 if (!entity.pair()) {
6732 cnt::darray_ext<Entity, 64> pairEntities;
6733 collect_entity_pairs(entity, pairEntities);
6734
6735 auto& ec = fetch(entity);
6736 handle_del_entity(ec, entity, EntitySpan{pairEntities.data(), pairEntities.size()});
6737 } else
6738 del_inter(entity);
6739
6740#if GAIA_USE_SAFE_ENTITY
6741 // Destroying the entity's components may have released the last SafeEntity reference to others.
6742 // Once the outermost deletion is done, nothing is in use anymore and they can go right away.
6743 if (!m_safeEntityDels.empty() && m_entitiesDeleting.empty() && m_archetypeRowDelDepth == 0 &&
6744 m_compDtorDepth == 0 && !locked())
6745 del_released_safe_entities();
6746#endif
6747 }
6748
6753 void del(Entity entity, Entity object) {
6754 if (!object.pair()) {
6755 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
6756 if (itSparseStore != m_sparseComponentsByComp.end()) {
6757 if (!component_is_non_fragmenting(object)) {
6758 {
6759 EntityBuilder eb(*this, entity);
6760 eb.del(object);
6761 }
6762 itSparseStore->second.func_del(itSparseStore->second.pStore, entity);
6763 return;
6764 }
6765#if GAIA_OBSERVERS_ENABLED
6766 auto delDiffCtx =
6767 m_observers.prepare_diff(*this, ObserverEvent::OnDel, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
6768#endif
6769 notify_inherited_del_dependents(entity, object);
6770 notify_del_single(entity, object);
6771 itSparseStore->second.func_del(itSparseStore->second.pStore, entity);
6772#if GAIA_OBSERVERS_ENABLED
6773 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
6774#endif
6775 return;
6776 }
6777 }
6778 EntityBuilder(*this, entity).del(object);
6779 }
6780
6786 void del(Entity entity, Pair pair) {
6787 EntityBuilder(*this, entity).del(pair);
6788 }
6789
6795 template <typename T>
6796 void del(Entity entity) {
6797 using CT = component_type_t<T>;
6798 using FT = typename CT::TypeFull;
6799
6800 if constexpr (uses_ct_sparse_storage<FT>()) {
6801 if constexpr (is_pair<FT>::value)
6802 del(entity, register_component_id<FT>());
6803 else {
6804 const auto* pItem = comp_cache().template find<FT>();
6805 if (pItem != nullptr)
6806 del(entity, pItem->entity);
6807 }
6808 } else {
6809 if constexpr (!is_pair<FT>::value) {
6810 const auto* pItem = comp_cache().template find<FT>();
6811 if (pItem != nullptr && component_is_non_fragmenting(pItem->entity)) {
6812 del(entity, pItem->entity);
6813 return;
6814 }
6815 }
6816
6817 EntityBuilder(*this, entity).del<FT>();
6818 }
6819 }
6820
6821 //----------------------------------------------------------------------
6822
6826 void as(Entity entity, Entity entityBase) {
6827 GAIA_ASSERT(!entity.pair());
6828 GAIA_ASSERT(!entityBase.pair());
6829
6830 // Form the relationship
6831 add(entity, Pair(Is, entityBase));
6832 }
6833
6838 GAIA_NODISCARD bool is(Entity entity, Entity entityBase) const {
6839 return is_inter<false>(entity, entityBase);
6840 }
6841
6848 GAIA_NODISCARD bool in(Entity entity, Entity entityBase) const {
6849 return is_inter<true>(entity, entityBase);
6850 }
6851
6855 GAIA_NODISCARD bool is_base(Entity target) const {
6856 if GAIA_UNLIKELY (target.pair())
6857 return false;
6858
6859 GAIA_ASSERT(valid_entity(target));
6860
6861 const auto it = m_entityToAsRelations.find(EntityLookupKey(target));
6862 return it != m_entityToAsRelations.end();
6863 }
6864
6865 //----------------------------------------------------------------------
6866
6870 void child(Entity entity, Entity parent) {
6871 add(entity, Pair(ChildOf, parent));
6872 }
6873
6877 void child(Pair entity, Entity parent) {
6878 add(entity, Pair(ChildOf, parent));
6879 }
6880
6885 GAIA_NODISCARD bool is_child(Entity entity, Entity parent) const {
6886 return has(entity, Pair(ChildOf, parent));
6887 }
6888
6893 void parent(Entity entity, Entity parentEntity) {
6894 parent_direct(entity, parentEntity);
6895 }
6896
6901 GAIA_NODISCARD bool is_parent(Entity entity, Entity parentEntity) const {
6902 return has_direct(entity, Pair(Parent, parentEntity));
6903 }
6904
6905 //----------------------------------------------------------------------
6906
6914 template <typename T, bool TriggerSetEffects>
6915 void modify(Entity entity) {
6916 GAIA_ASSERT(valid(entity));
6917
6918 if constexpr (uses_ct_sparse_storage<T>()) {
6919 const auto* pItem = comp_cache().template find<T>();
6920 if (pItem != nullptr) {
6921#if GAIA_ASSERT_ENABLED
6922 auto* pStore = sparse_component_store_erased(pItem->entity);
6923 GAIA_ASSERT(pStore != nullptr);
6924 GAIA_ASSERT(has_direct_sparse_component_inter(entity, pItem->entity));
6925#endif
6926
6927 if constexpr (TriggerSetEffects)
6928 finish_write(entity, pItem->entity);
6929 else {
6930 ::gaia::ecs::update_version(m_worldVersion);
6931 mark_sparse_component_write(entity, pItem->entity);
6932 }
6933 return;
6934 }
6935 }
6936
6937 auto& ec = cont(entity);
6938 modify_table_inter<T, TriggerSetEffects>(entity, ec);
6939 }
6940
6946 template <typename T, bool TriggerSetEffects>
6947 void modify(Pair entity) {
6948 const auto source = (Entity)entity;
6949 GAIA_ASSERT(valid(source));
6950
6951 if constexpr (uses_ct_sparse_storage<T>()) {
6952 modify<T, TriggerSetEffects>(source);
6953 } else {
6954 auto& ec = fetch(source);
6955 modify_table_inter<T, TriggerSetEffects>(source, ec);
6956 }
6957 }
6958
6968 template <typename T, bool TriggerSetEffects>
6969 void modify(Entity entity, Entity object) {
6970 GAIA_ASSERT(valid(entity));
6971 GAIA_ASSERT(valid(object));
6972
6973 using FT = typename component_type_t<T>::TypeFull;
6974 if constexpr (supports_sparse_component_storage<FT>()) {
6975 if (can_use_sparse_component_storage<FT>(object)) {
6976#if GAIA_ASSERT_ENABLED
6977 auto* pStore = sparse_component_store_erased(object);
6978 GAIA_ASSERT(pStore != nullptr);
6979 GAIA_ASSERT(has_direct_sparse_component_inter(entity, object));
6980#endif
6981
6982 if constexpr (TriggerSetEffects)
6983 finish_write(entity, object);
6984 else {
6985 ::gaia::ecs::update_version(m_worldVersion);
6986 mark_sparse_component_write(entity, object);
6987 }
6988 return;
6989 }
6990 }
6991
6992 auto& ec = cont(entity);
6993 modify_table_inter<TriggerSetEffects>(object, ec);
6994 }
6995
7002 template <typename T, bool TriggerSetEffects>
7003 void modify(Pair entity, Entity object) {
7004 const auto source = (Entity)entity;
7005 GAIA_ASSERT(valid(source));
7006 GAIA_ASSERT(valid(object));
7007
7008 using FT = typename component_type_t<T>::TypeFull;
7009 if constexpr (supports_sparse_component_storage<FT>()) {
7010 if (can_use_sparse_component_storage<FT>(object)) {
7011 modify<T, TriggerSetEffects>(source, object);
7012 return;
7013 }
7014 }
7015
7016 auto& ec = fetch(source);
7017 modify_table_inter<TriggerSetEffects>(object, ec);
7018 }
7019
7020 //----------------------------------------------------------------------
7021
7027 GAIA_NODISCARD ComponentSetter acc_mut(Entity entity) {
7028 GAIA_ASSERT(valid(entity));
7029
7030 const auto& ec = cont(entity);
7031 return ComponentSetter{*this, ec.pChunk, entity, ec.row};
7032 }
7033
7039 GAIA_NODISCARD ComponentSetter acc_mut(Pair entity) {
7040 const auto source = (Entity)entity;
7041 GAIA_ASSERT(valid(source));
7042
7043 const auto& ec = fetch(source);
7044 return ComponentSetter{*this, ec.pChunk, source, ec.row};
7045 }
7046
7057 template <typename T>
7058 GAIA_NODISCARD auto set(Entity entity) {
7059 static_assert(!is_pair<T>::value);
7060 using FT = typename component_type_t<T>::TypeFull;
7061 using ValueType = typename actual_type_t<T>::Type;
7062 const auto& item = add<FT>();
7063 return SetWriteProxyTyped<T, ValueType>{*this, entity, item.entity, get<T>(entity)};
7064 }
7065
7072 template <typename T>
7073 GAIA_NODISCARD auto set(Pair entity) {
7074 static_assert(!is_pair<T>::value);
7075 using FT = typename component_type_t<T>::TypeFull;
7076 using ValueType = typename actual_type_t<T>::Type;
7077 const auto& item = add<FT>();
7078 const auto source = (Entity)entity;
7079 return SetWriteProxyTyped<T, ValueType>{*this, source, item.entity, get<T>(entity)};
7080 }
7081
7093 template <typename T>
7094 GAIA_NODISCARD auto set(Entity entity, Entity object) {
7095 static_assert(!is_pair<T>::value);
7096 return SetWriteProxyObject<typename actual_type_t<T>::Type>{*this, entity, object, get<T>(entity, object)};
7097 }
7098
7106 template <typename T>
7107 GAIA_NODISCARD auto set(Pair entity, Entity object) {
7108 static_assert(!is_pair<T>::value);
7109 const auto source = (Entity)entity;
7110 return SetWriteProxyObject<typename actual_type_t<T>::Type>{*this, source, object, get<T>(entity, object)};
7111 }
7112
7121 template <typename T>
7122 GAIA_NODISCARD decltype(auto) sset(Entity entity) {
7123 static_assert(!is_pair<T>::value);
7124 using FT = typename component_type_t<T>::TypeFull;
7125 const auto& item = add<FT>();
7126 if constexpr (uses_ct_sparse_storage<FT>())
7127 return sparse_component_store_mut<FT>(item.entity).mut(entity);
7128 else
7129 return acc_mut(entity).smut<T>();
7130 }
7131
7138 template <typename T>
7139 GAIA_NODISCARD decltype(auto) sset(Pair entity) {
7140 static_assert(!is_pair<T>::value);
7141 using FT = typename component_type_t<T>::TypeFull;
7142 const auto& item = add<FT>();
7143 if constexpr (uses_ct_sparse_storage<FT>())
7144 return sparse_component_store_mut<FT>(item.entity).mut((Entity)entity);
7145 else
7146 return acc_mut(entity).smut<T>();
7147 }
7148
7155 template <typename T>
7156 GAIA_NODISCARD decltype(auto) sset(Entity entity, Entity object) {
7157 static_assert(!is_pair<T>::value);
7158 using FT = typename component_type_t<T>::TypeFull;
7159 if constexpr (supports_sparse_component_storage<FT>()) {
7160 if (can_use_sparse_component_storage<FT>(object))
7161 return sparse_component_mut_value<FT>(object, entity);
7162 }
7163 return acc_mut(entity).smut<T>(object);
7164 }
7165
7171 template <typename T>
7172 GAIA_NODISCARD decltype(auto) sset(Pair entity, Entity object) {
7173 static_assert(!is_pair<T>::value);
7174 using FT = typename component_type_t<T>::TypeFull;
7175 const auto source = (Entity)entity;
7176 if constexpr (supports_sparse_component_storage<FT>()) {
7177 if (can_use_sparse_component_storage<FT>(object))
7178 return sparse_component_mut_value<FT>(object, source);
7179 }
7180 return acc_mut(entity).smut<T>(object);
7181 }
7182
7183 //----------------------------------------------------------------------
7184
7193 template <typename T>
7194 GAIA_NODISCARD decltype(auto) mut(Entity entity) {
7195 static_assert(!is_pair<T>::value);
7196 return sset<T>(entity);
7197 }
7198
7203 template <typename T>
7204 GAIA_NODISCARD decltype(auto) mut(Pair entity) {
7205 static_assert(!is_pair<T>::value);
7206 return sset<T>(entity);
7207 }
7208
7215 template <typename T>
7216 GAIA_NODISCARD decltype(auto) mut(Entity entity, Entity object) {
7217 static_assert(!is_pair<T>::value);
7218 return sset<T>(entity, object);
7219 }
7220
7226 template <typename T>
7227 GAIA_NODISCARD decltype(auto) mut(Pair entity, Entity object) {
7228 static_assert(!is_pair<T>::value);
7229 return sset<T>(entity, object);
7230 }
7231
7237 GAIA_NODISCARD ComponentRawView get_raw(Entity entity, Entity component) const {
7238 if (component == EntityBad)
7239 return {};
7240
7241 const auto owner = id_owner_inter(entity, component);
7242 if (owner == EntityBad)
7243 return {};
7244
7245 const auto* pItem = component_item(owner, component);
7246 if (pItem == nullptr || !raw_component_supported(*pItem))
7247 return {};
7248 if (component_uses_sparse_storage(component)) {
7249 const auto* pStore = sparse_component_store_erased(component);
7250 if (pStore == nullptr || !pStore->func_has(pStore->pStore, owner))
7251 return {};
7252 return {pStore->func_get(pStore->pStore, owner), pItem->comp.size(), ComponentRawViewFlag_Valid};
7253 }
7254
7255 const auto& ec = fetch(owner);
7256 const auto compIdx = ec.pChunk->comp_idx(component);
7257 if (compIdx == ComponentIndexBad)
7258 return {};
7259
7260 const auto size = pItem->comp.size();
7261 if (size == 0)
7262 return {nullptr, 0, ComponentRawViewFlag_Valid};
7263
7264 const auto row = uint32_t(ec.row);
7265 return {ec.pChunk->comp_ptr(compIdx, row), size, ComponentRawViewFlag_Valid};
7266 }
7267
7273 GAIA_NODISCARD ComponentRawMutView mut_raw(Entity entity, Entity component) {
7274 if (component == EntityBad)
7275 return {};
7276
7277 const auto* pEc = try_live_record(entity);
7278 if (pEc == nullptr)
7279 return {};
7280
7281 const auto& ec = *pEc;
7282
7283 const auto* pItem = component_item(entity, component);
7284 if (pItem == nullptr || !raw_component_supported(*pItem))
7285 return {};
7286 if (component_uses_sparse_storage(component)) {
7287 const auto* pStore = sparse_component_store_erased(component);
7288 if (pStore == nullptr || !pStore->func_has(pStore->pStore, entity))
7289 return {};
7290 return {pStore->func_mut(pStore->pStore, entity), pItem->comp.size(), ComponentRawViewFlag_Valid};
7291 }
7292
7293 const auto compIdx = core::get_index(ec.pChunk->ids_view(), component);
7294 if (compIdx == BadIndex)
7295 return {};
7296
7297 const auto size = pItem->comp.size();
7298 if (size == 0)
7299 return {nullptr, 0, ComponentRawViewFlag_Valid};
7300
7301 const auto row = uint32_t(ec.row);
7302 return {ec.pChunk->comp_ptr_mut(compIdx, row), size, ComponentRawViewFlag_Valid};
7303 }
7304
7311 GAIA_NODISCARD ComponentRawView get_raw_field(Entity entity, Entity component, uint32_t fieldIdx) const {
7312 if (component == EntityBad)
7313 return {};
7314
7315 const auto owner = id_owner_inter(entity, component);
7316 if (owner == EntityBad)
7317 return {};
7318
7319 const auto* pItem = component_item(owner, component);
7320 if (pItem == nullptr || !soa_field_supported(*pItem) || fieldIdx >= pItem->comp.soa() ||
7321 pItem->soaSizes[fieldIdx] == 0)
7322 return {};
7323
7324 const auto& ec = fetch(owner);
7325 const auto compIdx = ec.pChunk->comp_idx(component);
7326 if (compIdx == ComponentIndexBad)
7327 return {};
7328
7329 const auto row = uint32_t(ec.row);
7330 const auto capacity = ec.pChunk->capacity();
7331 const std::span<const uint8_t> fieldSizes{pItem->soaSizes, pItem->comp.soa()};
7332 const auto* pData = mem::data_view_policy_soa_erased::get(
7333 ec.pChunk->comp_ptr(compIdx), pItem->comp.alig(), fieldSizes, fieldIdx, row, capacity);
7334 return {pData, pItem->soaSizes[fieldIdx], ComponentRawViewFlag_Valid};
7335 }
7336
7343 GAIA_NODISCARD ComponentRawMutView mut_raw_field(Entity entity, Entity component, uint32_t fieldIdx) {
7344 if (component == EntityBad)
7345 return {};
7346
7347 const auto* pEc = try_live_record(entity);
7348 if (pEc == nullptr)
7349 return {};
7350
7351 const auto& ec = *pEc;
7352
7353 const auto* pItem = component_item(entity, component);
7354 if (pItem == nullptr || !soa_field_supported(*pItem) || fieldIdx >= pItem->comp.soa() ||
7355 pItem->soaSizes[fieldIdx] == 0)
7356 return {};
7357
7358 const auto compIdx = core::get_index(ec.pChunk->ids_view(), component);
7359 if (compIdx == BadIndex)
7360 return {};
7361
7362 const auto row = uint32_t(ec.row);
7363 const auto capacity = ec.pChunk->capacity();
7364 const std::span<const uint8_t> fieldSizes{pItem->soaSizes, pItem->comp.soa()};
7366 ec.pChunk->comp_ptr_mut(compIdx), pItem->comp.alig(), fieldSizes, fieldIdx, row, capacity);
7367 return {pData, pItem->soaSizes[fieldIdx], ComponentRawViewFlag_Valid};
7368 }
7369
7375 GAIA_NODISCARD ComponentCursor cursor(Entity entity, Entity component) const;
7376
7383 GAIA_NODISCARD ComponentCursor cursor_mut(Entity entity, Entity component);
7384
7391 bool add_raw(Entity entity, Entity component, const void* data, uint32_t size) {
7392 if (component == EntityBad || try_live_record(entity) == nullptr)
7393 return false;
7394
7395 const auto* pItem = component.pair() ? comp_cache().find_pair_payload(component) : comp_cache().find(component);
7396 if (pItem == nullptr || !raw_component_payload_args_valid(*pItem, data, size))
7397 return false;
7398
7399 if (has_direct(entity, component))
7400 return false;
7401 if (component_uses_sparse_storage(component)) {
7402 const auto mode = sparse_storage_mode(component);
7403 if (mode == SparseStorageMode::None)
7404 return false;
7405
7406 auto& store = sparse_component_store_erased_mut(component, *pItem);
7407 auto* pPayload = store.func_add(store.pStore, entity);
7408 if (size != 0)
7409 memcpy(pPayload, data, size);
7410 finish_sparse_component_add_inter(entity, component, mode);
7411 return true;
7412 }
7413
7414 EntityBuilder eb(*this, entity);
7415#if GAIA_OBSERVERS_ENABLED
7416 auto addDiffCtx =
7417 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&component, 1}, EntitySpan{&entity, 1});
7418#endif
7419 eb.add_inter_init(component);
7420 eb.commit();
7421
7422 const auto payload = mut_raw(entity, component);
7423 GAIA_ASSERT(payload.valid());
7424 if (payload.valid() && size != 0)
7425 memcpy(payload.data, data, size);
7426
7427 notify_add_single(entity, component);
7428#if GAIA_OBSERVERS_ENABLED
7429 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
7430#endif
7431 return payload.valid();
7432 }
7433
7440 bool set_raw(Entity entity, Entity component, const void* data, uint32_t size) {
7441 if (component == EntityBad)
7442 return false;
7443
7444 const auto payload = mut_raw(entity, component);
7445 if (!payload.valid() || payload.size != size || (size != 0 && data == nullptr))
7446 return false;
7447 if (size != 0)
7448 memcpy(payload.data, data, size);
7449
7450 finish_write(entity, component);
7451 return true;
7452 }
7453
7457 void modify_raw(Entity entity, Entity component) {
7458 if (!mut_raw(entity, component).valid()) {
7459 if (component == EntityBad)
7460 return;
7461
7462 const auto* pEc = try_live_record(entity);
7463 if (pEc == nullptr)
7464 return;
7465
7466 const auto& ec = *pEc;
7467 const auto* pItem = component_item(entity, component);
7468 if (pItem == nullptr || !soa_field_supported(*pItem) ||
7469 core::get_index(ec.pChunk->ids_view(), component) == BadIndex)
7470 return;
7471 }
7472 finish_write(entity, component);
7473 }
7474
7475 //----------------------------------------------------------------------
7476
7483 GAIA_ASSERT(valid(entity));
7484
7485 const auto& ec = cont(entity);
7486 return ComponentGetter{*this, ec.pChunk, entity, ec.row};
7487 }
7488
7494 ComponentGetter acc(Pair entity) const {
7495 const auto source = (Entity)entity;
7496 GAIA_ASSERT(valid(source));
7497
7498 const auto& ec = fetch(source);
7499 return ComponentGetter{*this, ec.pChunk, source, ec.row};
7500 }
7501
7509 template <typename T>
7510 GAIA_NODISCARD decltype(auto) get(Entity entity) const {
7511 using FT = typename component_type_t<T>::TypeFull;
7512 const auto compEntity = [&]() {
7513 if constexpr (is_pair<FT>::value) {
7514 const auto rel = comp_cache().template get<typename FT::rel>().entity;
7515 const auto tgt = comp_cache().template get<typename FT::tgt>().entity;
7516 return (Entity)Pair(rel, tgt);
7517 } else {
7518 return comp_cache().template get<FT>().entity;
7519 }
7520 }();
7521 if constexpr (uses_ct_sparse_storage<FT>()) {
7522 const auto owner = id_owner_inter(entity, compEntity);
7523 GAIA_ASSERT(owner != EntityBad);
7524 const auto* pStore = sparse_component_store<FT>(compEntity);
7525 GAIA_ASSERT(pStore != nullptr);
7526 if (owner.pair())
7527 return pStore->get(owner);
7528 return pStore->get_entity(owner);
7529 } else {
7530 const auto owner = id_owner_inter(entity, compEntity);
7531 GAIA_ASSERT(owner != EntityBad);
7532 const auto& ec = cont(owner);
7533 return ComponentGetter{*this, ec.pChunk, owner, ec.row}.template get<T>();
7534 }
7535 }
7536
7543 template <typename T>
7544 GAIA_NODISCARD decltype(auto) get(Pair entity) const {
7545 using FT = typename component_type_t<T>::TypeFull;
7546 const auto compEntity = [&]() {
7547 if constexpr (is_pair<FT>::value) {
7548 const auto rel = comp_cache().template get<typename FT::rel>().entity;
7549 const auto tgt = comp_cache().template get<typename FT::tgt>().entity;
7550 return (Entity)Pair(rel, tgt);
7551 } else {
7552 return comp_cache().template get<FT>().entity;
7553 }
7554 }();
7555 const auto source = (Entity)entity;
7556 if constexpr (uses_ct_sparse_storage<FT>()) {
7557 const auto owner = id_owner_inter(source, compEntity);
7558 GAIA_ASSERT(owner != EntityBad);
7559 const auto* pStore = sparse_component_store<FT>(compEntity);
7560 GAIA_ASSERT(pStore != nullptr);
7561 return pStore->get(owner);
7562 } else {
7563 const auto owner = id_owner_inter(source, compEntity);
7564 GAIA_ASSERT(owner != EntityBad);
7565 const auto& ec = fetch(owner);
7566 return ComponentGetter{*this, ec.pChunk, owner, ec.row}.template get<T>();
7567 }
7568 }
7569
7575 template <typename T>
7576 GAIA_NODISCARD decltype(auto) get(Entity entity, Entity object) const {
7577 using FT = typename component_type_t<T>::TypeFull;
7578 if constexpr (supports_sparse_component_storage<FT>()) {
7579 if (can_use_sparse_component_storage<FT>(object)) {
7580 const auto owner = id_owner_inter(entity, object);
7581 GAIA_ASSERT(owner != EntityBad);
7582 if (owner.pair())
7583 return sparse_component_get_value<FT>(object, owner);
7584 return sparse_component_get_entity_value<FT>(object, owner);
7585 }
7586 }
7587
7588 const auto owner = id_owner_inter(entity, object);
7589 GAIA_ASSERT(owner != EntityBad);
7590 const auto& ec = cont(owner);
7591 return ComponentGetter{*this, ec.pChunk, owner, ec.row}.template get<T>(object);
7592 }
7593
7599 template <typename T>
7600 GAIA_NODISCARD decltype(auto) get(Pair entity, Entity object) const {
7601 using FT = typename component_type_t<T>::TypeFull;
7602 const auto source = (Entity)entity;
7603 if constexpr (supports_sparse_component_storage<FT>()) {
7604 if (can_use_sparse_component_storage<FT>(object)) {
7605 const auto owner = id_owner_inter(source, object);
7606 GAIA_ASSERT(owner != EntityBad);
7607 return sparse_component_get_value<FT>(object, owner);
7608 }
7609 }
7610
7611 const auto owner = id_owner_inter(source, object);
7612 GAIA_ASSERT(owner != EntityBad);
7613 const auto& ec = fetch(owner);
7614 return ComponentGetter{*this, ec.pChunk, owner, ec.row}.template get<T>(object);
7615 }
7616
7617 //----------------------------------------------------------------------
7618
7622 GAIA_NODISCARD bool has(Entity entity) const {
7623 // Pair
7624 if (entity.pair()) {
7625 if (entity == Pair(All, All))
7626 return true;
7627
7628 if (is_wildcard(entity)) {
7629 if (!m_entityToArchetypeMap.contains(EntityLookupKey(entity)))
7630 return false;
7631
7632 // If the pair is found, both entities forming it need to be found as well
7633 GAIA_ASSERT(has(get(entity.id())) && has(get(entity.gen())));
7634
7635 return true;
7636 }
7637
7638 const auto* pPair = m_recs.pair_record_find(entity);
7639 if (pPair == nullptr)
7640 return false;
7641
7642 const auto& ec = *pPair;
7643 if (is_req_del(ec))
7644 return false;
7645
7646#if GAIA_ASSERT_ENABLED
7647 // If the pair is found, both entities forming it need to be found as well
7648 GAIA_ASSERT(has(get(entity.id())) && has(get(entity.gen())));
7649
7650 // Index of the entity must fit inside the chunk
7651 auto* pChunk = ec.pChunk;
7652 GAIA_ASSERT(pChunk != nullptr && ec.row < pChunk->size());
7653#endif
7654
7655 return true;
7656 }
7657
7658 // Regular entity
7659 {
7660 // Entity ID has to fit inside the entity array
7661 if (entity.id() >= m_recs.entities.size() || !m_recs.entities.has(entity.id()))
7662 return false;
7663
7664 // Index of the entity must fit inside the chunk
7665 const auto& ec = m_recs.entities[entity.id()];
7666 if (is_req_del(ec))
7667 return false;
7668
7669 auto* pChunk = ec.pChunk;
7670 return pChunk != nullptr && ec.row < pChunk->size();
7671 }
7672 }
7673
7677 GAIA_NODISCARD bool has(Pair pair) const {
7678 return has((Entity)pair);
7679 }
7680
7687 GAIA_NODISCARD bool has(Entity entity, Entity object) const {
7688 return has_inter(entity, object, true);
7689 }
7690
7695 GAIA_NODISCARD bool has_direct(Entity entity, Entity object) const {
7696 return has_inter(entity, object, false);
7697 }
7698
7703 GAIA_NODISCARD bool has_direct(Entity entity, Pair pair) const {
7704 return has_inter(entity, (Entity)pair, false);
7705 }
7706
7707 private:
7713 GAIA_NODISCARD bool has_inter(Entity entity, Entity object, bool allowSemanticIs) const {
7714 const auto* pEc = try_live_record(entity);
7715 if (pEc == nullptr)
7716 return false;
7717
7718 const auto& ec = *pEc;
7719
7720 if (object.pair() && has_nonfragmenting_relation_pair(entity, object))
7721 return true;
7722 if (!object.pair()) {
7723 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
7724 if (itSparseStore != m_sparseComponentsByComp.end())
7725 return has_direct_sparse_component_inter(entity, object) ||
7726 (allowSemanticIs && inherited_id_owner(entity, object) != EntityBad);
7727 }
7728
7729 const auto* pArchetype = ec.pArchetype;
7730
7731 if (object.pair()) {
7732 if (allowSemanticIs && object.id() == Is.id() && !is_wildcard(object.gen())) {
7733 const auto target = get(object.gen());
7734 return valid(target) && is(entity, target);
7735 }
7736
7737 // Early exit if there are no pairs on the archetype
7738 if (pArchetype->pairs() == 0)
7739 return false;
7740
7741 EntityId rel = object.id();
7742 EntityId tgt = object.gen();
7743
7744 // (*,*)
7745 if (rel == All.id() && tgt == All.id())
7746 return true;
7747
7748 // (X,*)
7749 if (rel != All.id() && tgt == All.id()) {
7750 auto ids = pArchetype->ids_view();
7751 for (auto id: ids) {
7752 if (!id.pair())
7753 continue;
7754 if (id.id() == rel)
7755 return true;
7756 }
7757
7758 return false;
7759 }
7760
7761 // (*,X)
7762 if (rel == All.id() && tgt != All.id()) {
7763 auto ids = pArchetype->ids_view();
7764 for (auto id: ids) {
7765 if (!id.pair())
7766 continue;
7767 if (id.gen() == tgt)
7768 return true;
7769 }
7770
7771 return false;
7772 }
7773 }
7774
7775 if (pArchetype->has(object))
7776 return true;
7777
7778 return allowSemanticIs && inherited_id_owner(entity, object) != EntityBad;
7779 }
7780
7791 template <typename Func>
7792 bool each_id_inter(Entity entity, const EntityIdOptions& options, Func&& func) const {
7793 auto accepts_kind = [&](Entity id) {
7794 return id.pair() ? entity_id_kind_has(options.kind, EntityIdKind::Pair)
7795 : entity_id_kind_has(options.kind, EntityIdKind::Component);
7796 };
7797
7798 auto visit_direct = [&](Entity source, auto&& onId) {
7799 const auto* pEc = try_live_record(source);
7800 if (pEc == nullptr || pEc->pArchetype == nullptr)
7801 return true;
7802
7803 if (entity_id_storage_has(options.storage, EntityIdStorage::Archetype)) {
7804 for (const auto id: pEc->pArchetype->ids_view()) {
7805 if (!accepts_kind(id))
7806 continue;
7807 if (!onId(id))
7808 return false;
7809 }
7810 }
7811
7812 const bool wantDontFragment = entity_id_storage_has(options.storage, EntityIdStorage::DontFragment) &&
7813 entity_id_kind_has(options.kind, EntityIdKind::Component);
7814 const bool wantRelation = entity_id_storage_has(options.storage, EntityIdStorage::Relation) &&
7815 entity_id_kind_has(options.kind, EntityIdKind::Pair);
7816 if (!wantDontFragment && !wantRelation)
7817 return true;
7818
7819 cnt::darray_ext<Entity, 16> extra;
7820 if (wantDontFragment) {
7821 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
7822 const auto component = compKey.entity();
7823 if (component.pair() || !component_is_non_fragmenting(component))
7824 continue;
7825 if (store.func_has(store.pStore, source))
7826 extra.push_back(component);
7827 }
7828 }
7829 if (wantRelation) {
7830 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
7831 const auto target = store.target(source);
7832 if (target != EntityBad)
7833 extra.push_back(Pair(relKey.entity(), target));
7834 }
7835 }
7836 core::sort(extra.begin(), extra.end(), core::is_smaller<Entity>{});
7837 for (const auto id: extra) {
7838 if (!onId(id))
7839 return false;
7840 }
7841
7842 return true;
7843 };
7844
7845 if (!visit_direct(entity, [&](Entity id) {
7846 return func(id);
7847 }))
7848 return false;
7849
7850 if (options.matchKind == QueryMatchKind::Direct)
7851 return true;
7852
7853 cnt::darray_ext<Entity, 16> inheritedIds;
7854 for (const auto base: as_targets_trav_cache(entity)) {
7855 (void)visit_direct(base, [&](Entity id) {
7856 if (!id_uses_inherit_policy(id) || has_direct(entity, id))
7857 return true;
7858 if (core::get_index(inheritedIds, id) != BadIndex)
7859 return true;
7860 inheritedIds.push_back(id);
7861 return true;
7862 });
7863 }
7864 core::sort(inheritedIds.begin(), inheritedIds.end(), core::is_smaller<Entity>{});
7865 for (const auto id: inheritedIds) {
7866 if (!func(id))
7867 return false;
7868 }
7869
7870 return true;
7871 }
7872
7873 public:
7877 GAIA_NODISCARD std::span<const Entity> lookup_path() const {
7878 return {m_componentLookupPath.data(), m_componentLookupPath.size()};
7879 }
7880
7884 void lookup_path(std::span<const Entity> scopes) {
7885 m_componentLookupPath.clear();
7886 m_componentLookupPath.reserve((uint32_t)scopes.size());
7887 for (const auto scopeEntity: scopes) {
7888 GAIA_ASSERT(scopeEntity != EntityBad && valid(scopeEntity) && !scopeEntity.pair());
7889 if (scopeEntity == EntityBad || !valid(scopeEntity) || scopeEntity.pair())
7890 continue;
7891
7892 m_componentLookupPath.push_back(scopeEntity);
7893 }
7894 }
7895
7898 GAIA_NODISCARD Entity scope() const {
7899 return m_componentScope;
7900 }
7901
7907 GAIA_ASSERT(scope == EntityBad || (valid(scope) && !scope.pair()));
7908 const auto prev = m_componentScope;
7909 if (scope == EntityBad || (valid(scope) && !scope.pair())) {
7910 m_componentScope = scope;
7911 invalidate_scope_path_cache();
7912 }
7913 return prev;
7914 }
7915
7921 template <typename Func>
7922 void scope(Entity scopeEntity, Func&& func) {
7923 struct ComponentScopeRestore final {
7924 World& world;
7925 Entity prevScope;
7926 ~ComponentScopeRestore() {
7927 world.scope(prevScope);
7928 }
7929 };
7930
7931 ComponentScopeRestore restore{*this, scope(scopeEntity)};
7932 func();
7933 }
7934
7941 Entity module(const char* path, uint32_t len = 0) {
7942 if (path == nullptr || path[0] == 0)
7943 return EntityBad;
7944
7945 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(path, ComponentCacheItem::MaxNameLength) : len;
7946 if (l == 0 || l >= ComponentCacheItem::MaxNameLength)
7947 return EntityBad;
7948 if (path[l - 1] == '.')
7949 return EntityBad;
7950
7951 Entity parent = EntityBad;
7952 uint32_t partBeg = 0;
7953 while (partBeg < l) {
7954 uint32_t partEnd = partBeg;
7955 while (partEnd < l && path[partEnd] != '.')
7956 ++partEnd;
7957 if (partEnd == partBeg)
7958 return EntityBad;
7959
7960 const auto partLen = partEnd - partBeg;
7961 const auto key = EntityNameLookupKey(path + partBeg, partLen, 0);
7962 const auto it = m_nameToEntity.find(key);
7963 Entity curr = EntityBad;
7964
7965 if (it != m_nameToEntity.end()) {
7966 curr = it->second;
7967 if (parent != EntityBad && !is_child(curr, parent)) {
7968 GAIA_ASSERT2(false, "Module path collides with an existing entity name outside the requested scope");
7969 return EntityBad;
7970 }
7971 } else {
7972 curr = add();
7973 name(curr, path + partBeg, partLen);
7974 if (parent != EntityBad)
7975 child(curr, parent);
7976 }
7977
7978 parent = curr;
7979 partBeg = partEnd + 1;
7980 }
7981
7982 return parent;
7983 }
7984
7991 GAIA_NODISCARD bool has(Entity entity, Pair pair) const {
7992 return has(entity, (Entity)pair);
7993 }
7994
8001 template <typename T>
8002 GAIA_NODISCARD bool has(Entity entity) const {
8003 GAIA_ASSERT(valid(entity));
8004
8005 using FT = typename component_type_t<T>::TypeFull;
8006 const auto compEntity = [&]() {
8007 if constexpr (is_pair<FT>::value) {
8008 const auto* pRel = comp_cache().template find<typename FT::rel>();
8009 const auto* pTgt = comp_cache().template find<typename FT::tgt>();
8010 if (pRel == nullptr || pTgt == nullptr)
8011 return EntityBad;
8012
8013 const auto rel = pRel->entity;
8014 const auto tgt = pTgt->entity;
8015 return (Entity)Pair(rel, tgt);
8016 } else {
8017 const auto* pItem = comp_cache().template find<FT>();
8018 return pItem != nullptr ? pItem->entity : EntityBad;
8019 }
8020 }();
8021 if (compEntity == EntityBad)
8022 return false;
8023
8024 return id_owner_inter(entity, compEntity) != EntityBad;
8025 }
8026
8038 template <typename Func>
8039 void ids(Entity entity, Func&& func, EntityIdOptions options = {}) const {
8040 (void)each_id_inter(entity, options, [&](Entity id) {
8041 func(id);
8042 return true;
8043 });
8044 }
8045
8052 template <typename Func>
8053 void ids_if(Entity entity, Func&& func, EntityIdOptions options = {}) const {
8054 (void)each_id_inter(entity, options, [&](Entity id) {
8055 return func(id);
8056 });
8057 }
8058
8059 //----------------------------------------------------------------------
8060
8070 void name(Entity entity, const char* name, uint32_t len = 0) {
8071 EntityBuilder(*this, entity).name(name, len);
8072 }
8073
8087 void name_raw(Entity entity, const char* name, uint32_t len = 0) {
8088 EntityBuilder(*this, entity).name_raw(name, len);
8089 }
8090
8095 GAIA_NODISCARD util::str_view name(Entity entity) const {
8096 if (entity.pair())
8097 return {};
8098
8099 const auto& ec = m_recs.entities[entity.id()];
8100 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
8101 if (compIdx == BadIndex)
8102 return {};
8103
8104 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
8105 GAIA_ASSERT(core::check_alignment(pDesc));
8106 return {pDesc->name, pDesc->name_len};
8107 }
8108
8113 GAIA_NODISCARD util::str_view name(EntityId entityId) const {
8114 auto entity = get(entityId);
8115 return name(entity);
8116 }
8117
8118 //----------------------------------------------------------------------
8119
8127 GAIA_NODISCARD Entity resolve(const char* name, uint32_t len = 0) const {
8128 if (name == nullptr || name[0] == 0)
8129 return EntityBad;
8130
8131 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
8132 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
8133
8134 if (memchr(name, '.', l) != nullptr) {
8135 const auto entity = get_entity_inter(name, l);
8136 if (entity != EntityBad)
8137 return entity;
8138 }
8139
8140 return get_inter(name, l);
8141 }
8142
8148 void resolve(cnt::darray<Entity>& out, const char* name, uint32_t len = 0) const {
8149 out.clear();
8150 if (name == nullptr || name[0] == 0)
8151 return;
8152
8153 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
8154 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
8155
8156 auto push_unique = [&](Entity entity) {
8157 if (entity == EntityBad)
8158 return;
8159 for (const auto existing: out) {
8160 if (existing == entity)
8161 return;
8162 }
8163 out.push_back(entity);
8164 };
8165
8166 push_unique(get_entity_inter(name, l));
8167
8168 if (memchr(name, '.', l) == nullptr && memchr(name, ':', l) == nullptr)
8169 add_comp_lookup_hits_inter(out, name, l);
8170
8171 const bool isPath = memchr(name, '.', l) != nullptr;
8172 const bool isSymbol = memchr(name, ':', l) != nullptr;
8173 add_comp_exact_hits_inter(out, name, l, isPath, isSymbol);
8174
8175 push_unique(alias(name, l));
8176 }
8177
8183 GAIA_NODISCARD Entity get(const char* name, uint32_t len = 0) const {
8184 return resolve(name, len);
8185 }
8186
8187 private:
8192 GAIA_NODISCARD Entity find_named_entity_inter(const char* name, uint32_t len = 0) const {
8193 if (name == nullptr || name[0] == 0)
8194 return EntityBad;
8195
8196 const auto key = EntityNameLookupKey(name, len, 0);
8197 const auto it = m_nameToEntity.find(key);
8198 return it != m_nameToEntity.end() ? it->second : EntityBad;
8199 }
8200
8205 GAIA_NODISCARD bool hierarchy_child_matches_parent(Entity child, Entity parent) const {
8206 return is_child(child, parent) || is_parent(child, parent);
8207 }
8208
8213 GAIA_NODISCARD Entity get_entity_inter(const char* name, uint32_t len = 0) const {
8214 if (name == nullptr || name[0] == 0)
8215 return EntityBad;
8216
8217 if (len == 0) {
8218 while (name[len] != '\0')
8219 ++len;
8220 }
8221
8222 Entity parent = EntityBad;
8223 Entity child = EntityBad;
8224 uint32_t posDot = 0;
8225 std::span<const char> str(name, len);
8226
8227 posDot = core::get_index(str, '.');
8228 if (posDot == BadIndex)
8229 return find_named_entity_inter(str.data(), (uint32_t)str.size());
8230
8231 if (posDot == 0)
8232 return EntityBad;
8233
8234 parent = find_named_entity_inter(str.data(), posDot);
8235 if (parent == EntityBad)
8236 return EntityBad;
8237
8238 str = str.subspan(posDot + 1);
8239 while (!str.empty()) {
8240 posDot = core::get_index(str, '.');
8241
8242 if (posDot == BadIndex) {
8243 child = find_named_entity_inter(str.data(), (uint32_t)str.size());
8244 if (child == EntityBad || !hierarchy_child_matches_parent(child, parent))
8245 return EntityBad;
8246
8247 return child;
8248 }
8249
8250 if (posDot == 0)
8251 return EntityBad;
8252
8253 child = find_named_entity_inter(str.data(), posDot);
8254 if (child == EntityBad || !hierarchy_child_matches_parent(child, parent))
8255 return EntityBad;
8256
8257 parent = child;
8258 str = str.subspan(posDot + 1);
8259 }
8260
8261 return parent;
8262 }
8263
8268 GAIA_NODISCARD Entity get_inter(const char* name, uint32_t len = 0) const {
8269 if (name == nullptr || name[0] == 0)
8270 return EntityBad;
8271
8272 auto key = EntityNameLookupKey(name, len, 0);
8273 const auto l = key.len();
8274 const bool isUnqualifiedCompName = is_unqualified_comp_name_inter(name, l);
8275 const auto namedEntity = find_named_entity_inter(name, l);
8276
8277 if (has_comp_lookup_ctx_inter() && isUnqualifiedCompName) {
8278 if (const auto* pScopedItem = resolve_component_name_inter(name, l); pScopedItem != nullptr)
8279 return pick_name_or_comp_inter(namedEntity, pScopedItem);
8280 }
8281
8282 if (namedEntity != EntityBad)
8283 return namedEntity;
8284
8285 if (const auto* pItem = resolve_component_name_inter(name, l); pItem != nullptr)
8286 return pItem->entity;
8287
8288 const auto aliasEntity = alias(name, l);
8289 if (aliasEntity != EntityBad)
8290 return aliasEntity;
8291
8292 // No entity with the given name exists. Return a bad entity
8293 return EntityBad;
8294 }
8295
8296 public:
8297 //----------------------------------------------------------------------
8298
8303 GAIA_NODISCARD const cnt::set<EntityLookupKey>* relations(Entity target) const {
8304 return m_pairLookup.relations(target);
8305 }
8306
8312 GAIA_NODISCARD Entity relation(Entity entity, Entity target) const {
8313 const auto* pEc = try_live_record(entity);
8314 if (pEc == nullptr || try_live_record(target) == nullptr)
8315 return EntityBad;
8316
8317 for (const auto& it: m_nonFragmentingRelationsByRel) {
8318 if (it.second.target(entity) == target)
8319 return it.first.entity();
8320 }
8321
8322 const auto* pArchetype = pEc->pArchetype;
8323
8324 // Early exit if there are no pairs on the archetype
8325 if (pArchetype->pairs() == 0)
8326 return EntityBad;
8327
8328 const auto indices = pArchetype->pair_tgt_indices(target);
8329 if (indices.empty())
8330 return EntityBad;
8331
8332 const auto ids = pArchetype->ids_view();
8333 const auto e = ids[indices[0]];
8334 const auto& ecRel = m_recs.entities[e.id()];
8335 return *ecRel.pEntity;
8336 }
8337
8343 template <typename Func>
8344 void relations(Entity entity, Entity target, Func func) const {
8345 const auto* pEc = try_live_record(entity);
8346 if (pEc == nullptr || try_live_record(target) == nullptr)
8347 return;
8348
8349 for (const auto& it: m_nonFragmentingRelationsByRel) {
8350 if (it.second.target(entity) == target)
8351 func(it.first.entity());
8352 }
8353
8354 const auto* pArchetype = pEc->pArchetype;
8355
8356 // Early exit if there are no pairs on the archetype
8357 if (pArchetype->pairs() == 0)
8358 return;
8359
8360 const auto ids = pArchetype->ids_view();
8361 for (auto idsIdx: pArchetype->pair_tgt_indices(target)) {
8362 const auto e = ids[idsIdx];
8363
8364 const auto& ecRel = m_recs.entities[e.id()];
8365 auto relation = *ecRel.pEntity;
8366 func(relation);
8367 }
8368 }
8369
8376 template <typename Func>
8377 void relations_if(Entity entity, Entity target, Func func) const {
8378 const auto* pEc = try_live_record(entity);
8379 if (pEc == nullptr || try_live_record(target) == nullptr)
8380 return;
8381
8382 for (const auto& it: m_nonFragmentingRelationsByRel) {
8383 if (it.second.target(entity) == target && !func(it.first.entity()))
8384 return;
8385 }
8386
8387 const auto* pArchetype = pEc->pArchetype;
8388
8389 // Early exit if there are no pairs on the archetype
8390 if (pArchetype->pairs() == 0)
8391 return;
8392
8393 const auto ids = pArchetype->ids_view();
8394 for (auto idsIdx: pArchetype->pair_tgt_indices(target)) {
8395 const auto e = ids[idsIdx];
8396
8397 const auto& ecRel = m_recs.entities[e.id()];
8398 auto relation = *ecRel.pEntity;
8399 if (!func(relation))
8400 return;
8401 }
8402 }
8403
8408 GAIA_NODISCARD const cnt::darray<Entity>& as_relations_trav_cache(Entity target) const {
8409 const auto key = EntityLookupKey(target);
8410 const auto itCache = m_entityToAsRelationsTravCache.find(key);
8411 if (itCache != m_entityToAsRelationsTravCache.end())
8412 return itCache->second;
8413
8414 auto& cache = m_entityToAsRelationsTravCache[key];
8415 const auto it = m_entityToAsRelations.find(key);
8416 if (it == m_entityToAsRelations.end())
8417 return cache;
8418
8421 stack.reserve((uint32_t)it->second.size());
8422 for (auto relation: it->second)
8423 stack.push_back(relation);
8424
8425 while (!stack.empty()) {
8426 const auto relationKey = stack.back();
8427 stack.pop_back();
8428
8429 const auto relation = relationKey.entity();
8430 cache.push_back(relation);
8431
8432 const auto itChild = m_entityToAsRelations.find(relationKey);
8433 if (itChild == m_entityToAsRelations.end())
8434 continue;
8435
8436 for (auto childRelation: itChild->second)
8437 stack.push_back(childRelation);
8438 }
8439
8440 return cache;
8441 }
8442
8447 GAIA_NODISCARD const cnt::darray<Entity>& as_targets_trav_cache(Entity relation) const {
8448 const auto key = EntityLookupKey(relation);
8449 const auto itCache = m_entityToAsTargetsTravCache.find(key);
8450 if (itCache != m_entityToAsTargetsTravCache.end())
8451 return itCache->second;
8452
8453 auto& cache = m_entityToAsTargetsTravCache[key];
8454 const auto it = m_entityToAsTargets.find(key);
8455 if (it == m_entityToAsTargets.end())
8456 return cache;
8457
8460 stack.reserve((uint32_t)it->second.size());
8461 for (auto target: it->second)
8462 stack.push_back(target);
8463
8464 while (!stack.empty()) {
8465 const auto targetKey = stack.back();
8466 stack.pop_back();
8467
8468 const auto target = targetKey.entity();
8469 cache.push_back(target);
8470
8471 const auto itChild = m_entityToAsTargets.find(targetKey);
8472 if (itChild == m_entityToAsTargets.end())
8473 continue;
8474
8475 for (auto childTarget: itChild->second)
8476 stack.push_back(childTarget);
8477 }
8478
8479 return cache;
8480 }
8481
8487 GAIA_NODISCARD const cnt::darray<Entity>& targets_trav_cache(Entity relation, Entity source) const {
8488 const auto key = EntityLookupKey(Pair(relation, source));
8489 const auto itCache = m_targetsTravCache.find(key);
8490 if (itCache != m_targetsTravCache.end())
8491 return itCache->second;
8492
8493 auto& cache = m_targetsTravCache[key];
8494 m_relationCachesPopulated = true;
8495 if (try_live_record(relation) == nullptr || try_live_record(source) == nullptr)
8496 return cache;
8497
8498 auto curr = source;
8499 GAIA_FOR(MAX_TRAV_DEPTH) {
8500 const auto next = target(curr, relation);
8501 if (next == EntityBad || next == curr)
8502 break;
8503
8504 cache.push_back(next);
8505 curr = next;
8506 }
8507
8508 return cache;
8509 }
8510
8515 GAIA_NODISCARD const cnt::darray<Entity>& targets_all_cache(Entity source) const {
8516 const auto key = EntityLookupKey(source);
8517 const auto itCache = m_targetsAllCache.find(key);
8518 if (itCache != m_targetsAllCache.end())
8519 return itCache->second;
8520
8521 auto& cache = m_targetsAllCache[key];
8522 m_relationCachesPopulated = true;
8523 const auto* pEc = try_live_record(source);
8524 if (pEc == nullptr)
8525 return cache;
8526
8527 const auto visitStamp = next_entity_visit_stamp();
8528
8529 for (const auto& it: m_nonFragmentingRelationsByRel) {
8530 const auto target = it.second.target(source);
8531 if (target != EntityBad && try_mark_entity_visited(target, visitStamp))
8532 cache.push_back(target);
8533 }
8534
8535 const auto* pArchetype = pEc->pArchetype;
8536 if (pArchetype->pairs() == 0)
8537 return cache;
8538
8539 const auto ids = pArchetype->ids_view();
8540 for (auto idsIdx: pArchetype->pair_indices()) {
8541 const auto id = ids[idsIdx];
8542 const auto target = pair_target_if_alive(id);
8543 if (target == EntityBad)
8544 continue;
8545 if (try_mark_entity_visited(target, visitStamp))
8546 cache.push_back(target);
8547 }
8548
8549 return cache;
8550 }
8551
8556 GAIA_NODISCARD const cnt::darray<Entity>& sources_all_cache(Entity target) const {
8557 const auto key = EntityLookupKey(target);
8558 const auto itCache = m_sourcesAllCache.find(key);
8559 if (itCache != m_sourcesAllCache.end())
8560 return itCache->second;
8561
8562 auto& cache = m_sourcesAllCache[key];
8563 m_relationCachesPopulated = true;
8564 if (try_live_record(target) == nullptr)
8565 return cache;
8566
8567 const auto visitStamp = next_entity_visit_stamp();
8568
8569 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
8570 (void)relKey;
8571 const auto* pSources = store.sources(target);
8572 if (pSources == nullptr)
8573 continue;
8574
8575 for (auto source: *pSources) {
8576 if (valid(source) && try_mark_entity_visited(source, visitStamp))
8577 cache.push_back(source);
8578 }
8579 }
8580
8581 const auto pair = Pair(All, target);
8582 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
8583 if (it == m_entityToArchetypeMap.end())
8584 return cache;
8585
8586 for (const auto& record: it->second) {
8587 const auto* pArchetype = record.pArchetype;
8588 if (pArchetype->is_req_del())
8589 continue;
8590
8591 for (const auto* pChunk: pArchetype->chunks()) {
8592 const auto entities = pChunk->entity_view();
8593 GAIA_EACH(entities) {
8594 const auto source = entities[i];
8595 if (!valid(source))
8596 continue;
8597 if (try_mark_entity_visited(source, visitStamp))
8598 cache.push_back(source);
8599 }
8600 }
8601 }
8602
8603 return cache;
8604 }
8605
8612 template <typename Func>
8613 void targets_trav(Entity relation, Entity source, Func func) const {
8614 if (try_live_record(relation) == nullptr || try_live_record(source) == nullptr)
8615 return;
8616
8617 auto curr = source;
8618 GAIA_FOR(MAX_TRAV_DEPTH) {
8619 const auto next = target(curr, relation);
8620 if (next == EntityBad || next == curr)
8621 break;
8622 if (!enabled(next))
8623 break;
8624
8625 func(next);
8626 curr = next;
8627 }
8628 }
8629
8637 template <typename Func>
8638 GAIA_NODISCARD bool targets_trav_if(Entity relation, Entity source, Func func) const {
8639 if (try_live_record(relation) == nullptr || try_live_record(source) == nullptr)
8640 return false;
8641
8642 auto curr = source;
8643 GAIA_FOR(MAX_TRAV_DEPTH) {
8644 const auto next = target(curr, relation);
8645 if (next == EntityBad || next == curr)
8646 break;
8647 if (!enabled(next))
8648 break;
8649
8650 if (!func(next))
8651 return true;
8652 curr = next;
8653 }
8654
8655 return false;
8656 }
8657
8663 GAIA_NODISCARD const cnt::darray<Entity>& sources_bfs_trav_cache(Entity relation, Entity rootTarget) const {
8664 const auto key = EntityLookupKey(Pair(relation, rootTarget));
8665 const auto itCache = m_srcBfsTravCache.find(key);
8666 if (itCache != m_srcBfsTravCache.end())
8667 return itCache->second;
8668
8669 auto& cache = m_srcBfsTravCache[key];
8670 m_relationCachesPopulated = true;
8671 if (!valid(relation) || !valid(rootTarget))
8672 return cache;
8673
8676 queue.push_back(rootTarget);
8677
8679 visited.insert(EntityLookupKey(rootTarget));
8680
8681 for (uint32_t i = 0; i < queue.size(); ++i) {
8682 const auto currTarget = queue[i];
8683
8684 children.clear();
8685 sources(relation, currTarget, [&](Entity source) {
8686 const auto keySource = EntityLookupKey(source);
8687 const auto& ins = visited.insert(keySource);
8688 if (!ins.second)
8689 return;
8690
8691 children.push_back(source);
8692 });
8693
8694 core::sort(children, SortEntityIdCond{});
8695
8696 for (auto child: children) {
8697 cache.push_back(child);
8698 queue.push_back(child);
8699 }
8700 }
8701
8702 return cache;
8703 }
8704
8711 GAIA_NODISCARD uint32_t depth_order_cache(Entity relation, Entity sourceTarget) const {
8712 const auto key = EntityLookupKey(Pair(relation, sourceTarget));
8713 const auto itCache = m_depthOrderCache.find(key);
8714 if (itCache != m_depthOrderCache.end()) {
8715 GAIA_ASSERT(itCache->second != GroupIdMax && "depth_order requires an acyclic relation graph");
8716 return itCache->second;
8717 }
8718
8719 if (!valid(relation) || !valid(sourceTarget))
8720 return 0;
8721
8722 // Mark this node as in-flight so cycles trip a debug assert instead of recursing forever.
8723 m_relationCachesPopulated = true;
8724 m_depthOrderCache[key] = GroupIdMax;
8725
8726 uint32_t depth = 1;
8727 targets(sourceTarget, relation, [&](Entity next) {
8728 const auto nextDepth = depth_order_cache(relation, next);
8729 if (nextDepth == 0)
8730 return;
8731 const auto candidate = nextDepth + 1;
8732 if (candidate > depth)
8733 depth = candidate;
8734 });
8735
8736 m_depthOrderCache[key] = depth;
8737 return depth;
8738 }
8739
8744 template <typename Func>
8745 void as_relations_trav(Entity target, Func func) const {
8746 if (!valid(target))
8747 return;
8748
8749 const auto& relations = as_relations_trav_cache(target);
8750 for (auto relation: relations)
8751 func(relation);
8752 }
8753
8759 template <typename Func>
8760 GAIA_NODISCARD bool as_relations_trav_if(Entity target, Func func) const {
8761 if (!valid(target))
8762 return false;
8763
8764 const auto& relations = as_relations_trav_cache(target);
8765 for (auto relation: relations) {
8766 if (func(relation))
8767 return true;
8768 }
8769
8770 return false;
8771 }
8772
8773 //----------------------------------------------------------------------
8774
8779 GAIA_NODISCARD const cnt::set<EntityLookupKey>* targets(Entity relation) const {
8780 return m_pairLookup.targets(relation);
8781 }
8782
8788 GAIA_NODISCARD Entity target(Entity entity, Entity relation) const {
8789 const auto* pEc = try_live_record(entity);
8790 if (pEc == nullptr)
8791 return EntityBad;
8792
8793 if (relation == All) {
8794 const auto& targets = targets_all_cache(entity);
8795 return targets.empty() ? EntityBad : targets[0];
8796 }
8797
8798 const auto* pRel = try_entity_record(relation);
8799 if (pRel == nullptr)
8800 return EntityBad;
8801
8802 if (record_uses_non_fragmenting_relation_storage(*pRel)) {
8803 const auto* pStore = nonfragmenting_relation_store(relation);
8804 if (pStore == nullptr)
8805 return EntityBad;
8806
8807 return pStore->target(entity);
8808 }
8809
8810 const auto* pArchetype = pEc->pArchetype;
8811
8812 // Early exit if there are no pairs on the archetype
8813 if (pArchetype->pairs() == 0)
8814 return EntityBad;
8815
8816 const auto ids = pArchetype->ids_view();
8817 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
8818 const auto e = ids[idsIdx];
8819 const auto target = pair_target_if_alive(e);
8820 if (target == EntityBad)
8821 continue;
8822 return target;
8823 }
8824
8825 return EntityBad;
8826 }
8827
8833 template <typename Func>
8834 void targets(Entity entity, Entity relation, Func func) const {
8835 const auto* pEc = try_live_record(entity);
8836 if (pEc == nullptr)
8837 return;
8838
8839 if (relation == All) {
8840 for (auto target: targets_all_cache(entity))
8841 func(target);
8842 return;
8843 }
8844
8845 const auto* pRel = try_entity_record(relation);
8846 if (pRel == nullptr)
8847 return;
8848
8849 if (record_uses_non_fragmenting_relation_storage(*pRel)) {
8850 const auto* pStore = nonfragmenting_relation_store(relation);
8851 if (pStore == nullptr)
8852 return;
8853
8854 const auto target = pStore->target(entity);
8855 if (target != EntityBad)
8856 func(target);
8857 return;
8858 }
8859
8860 const auto* pArchetype = pEc->pArchetype;
8861
8862 // Early exit if there are no pairs on the archetype
8863 if (pArchetype->pairs() == 0)
8864 return;
8865
8866 const auto ids = pArchetype->ids_view();
8867 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
8868 const auto e = ids[idsIdx];
8869 const auto target = pair_target_if_alive(e);
8870 if (target == EntityBad)
8871 continue;
8872 func(target);
8873 }
8874 }
8875
8882 template <typename Func>
8883 void targets_if(Entity entity, Entity relation, Func func) const {
8884 const auto* pEc = try_live_record(entity);
8885 if (pEc == nullptr)
8886 return;
8887
8888 if (relation == All) {
8889 for (auto target: targets_all_cache(entity)) {
8890 if (!func(target))
8891 return;
8892 }
8893 return;
8894 }
8895
8896 const auto* pRel = try_entity_record(relation);
8897 if (pRel == nullptr)
8898 return;
8899
8900 if (record_uses_non_fragmenting_relation_storage(*pRel)) {
8901 const auto* pStore = nonfragmenting_relation_store(relation);
8902 if (pStore == nullptr)
8903 return;
8904
8905 const auto target = pStore->target(entity);
8906 if (target != EntityBad)
8907 (void)func(target);
8908 return;
8909 }
8910
8911 const auto* pArchetype = pEc->pArchetype;
8912
8913 // Early exit if there are no pairs on the archetype
8914 if (pArchetype->pairs() == 0)
8915 return;
8916
8917 const auto ids = pArchetype->ids_view();
8918 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
8919 const auto e = ids[idsIdx];
8920 const auto target = pair_target_if_alive(e);
8921 if (target == EntityBad)
8922 continue;
8923 if (!func(target))
8924 return;
8925 }
8926 }
8927
8931 GAIA_NODISCARD Entity pair_target_if_alive(Entity pair) const {
8932 GAIA_ASSERT(pair.pair());
8933 if (!valid_entity_id((EntityId)pair.gen()))
8934 return EntityBad;
8935
8936 const auto& ecTarget = m_recs.entities[pair.gen()];
8937 if (ecTarget.pEntity == nullptr)
8938 return EntityBad;
8939
8940 const auto target = *ecTarget.pEntity;
8941 return valid(target) ? target : EntityBad;
8942 }
8943
8949 template <typename Func>
8950 void sources(Entity relation, Entity target, Func func) const {
8951 if (try_live_record(target) == nullptr)
8952 return;
8953
8954 if (relation == All) {
8955 for (auto source: sources_all_cache(target))
8956 func(source);
8957 return;
8958 }
8959
8960 const auto* pRel = try_entity_record(relation);
8961 if (pRel == nullptr)
8962 return;
8963
8964 if (record_uses_non_fragmenting_relation_storage(*pRel)) {
8965 const auto* pStore = nonfragmenting_relation_store(relation);
8966 if (pStore == nullptr)
8967 return;
8968
8969 const auto* pSources = pStore->sources(target);
8970 if (pSources == nullptr)
8971 return;
8972
8973 for (auto source: *pSources) {
8974 if (!valid(source))
8975 continue;
8976
8977 func(source);
8978 }
8979 return;
8980 }
8981
8982 const auto pair = Pair(relation, target);
8983 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
8984 if (it == m_entityToArchetypeMap.end())
8985 return;
8986
8987 for (const auto& record: it->second) {
8988 const auto* pArchetype = record.pArchetype;
8989 if (pArchetype->is_req_del())
8990 continue;
8991
8992 for (const auto* pChunk: pArchetype->chunks()) {
8993 auto entities = pChunk->entity_view();
8994 GAIA_EACH(entities) {
8995 const auto source = entities[i];
8996 if (!valid(source))
8997 continue;
8998 func(source);
8999 }
9000 }
9001 }
9002 }
9003
9010 template <typename Func>
9011 void sources_if(Entity relation, Entity target, Func func) const {
9012 if (try_live_record(target) == nullptr)
9013 return;
9014
9015 if (relation == All) {
9016 for (auto source: sources_all_cache(target)) {
9017 if (!func(source))
9018 return;
9019 }
9020 return;
9021 }
9022
9023 const auto* pRel = try_entity_record(relation);
9024 if (pRel == nullptr)
9025 return;
9026
9027 if (record_uses_non_fragmenting_relation_storage(*pRel)) {
9028 const auto* pStore = nonfragmenting_relation_store(relation);
9029 if (pStore == nullptr)
9030 return;
9031
9032 const auto* pSources = pStore->sources(target);
9033 if (pSources == nullptr)
9034 return;
9035
9036 for (auto source: *pSources) {
9037 if (!valid(source))
9038 continue;
9039
9040 if (!func(source))
9041 return;
9042 }
9043 return;
9044 }
9045
9046 const auto pair = Pair(relation, target);
9047 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
9048 if (it == m_entityToArchetypeMap.end())
9049 return;
9050
9051 for (const auto& record: it->second) {
9052 const auto* pArchetype = record.pArchetype;
9053 if (pArchetype->is_req_del())
9054 continue;
9055
9056 for (const auto* pChunk: pArchetype->chunks()) {
9057 auto entities = pChunk->entity_view();
9058 GAIA_EACH(entities) {
9059 const auto source = entities[i];
9060 if (!valid(source))
9061 continue;
9062 if (!func(source))
9063 return;
9064 }
9065 }
9066 }
9067 }
9068
9069 private:
9072 GAIA_NODISCARD uint64_t next_entity_visit_stamp() const {
9073 ++m_entityVisitStamp;
9074 if (m_entityVisitStamp != 0)
9075 return m_entityVisitStamp;
9076
9077 const auto cnt = (uint32_t)m_entityVisitStamps.size();
9078 GAIA_FOR(cnt) {
9079 m_entityVisitStamps[i] = 0;
9080 }
9081
9082 m_entityVisitStamp = 1;
9083 return m_entityVisitStamp;
9084 }
9085
9090 GAIA_NODISCARD bool try_mark_entity_visited(Entity entity, uint64_t stamp) const {
9091 GAIA_ASSERT(!entity.pair());
9092 if (entity.id() >= m_entityVisitStamps.size())
9093 m_entityVisitStamps.resize(m_recs.entities.size(), 0, cnt::ResizePolicy::Scale);
9094
9095 auto& slot = m_entityVisitStamps[entity.id()];
9096 if (slot == stamp)
9097 return false;
9098
9099 slot = stamp;
9100 return true;
9101 }
9102
9108 template <typename Func>
9109 GAIA_NODISCARD bool each_inherited_term_entity(Entity term, Func&& func) const {
9110 cnt::set<EntityLookupKey> seen;
9111 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
9112 if (it == m_entityToArchetypeMap.end())
9113 return true;
9114
9115 for (const auto& record: it->second) {
9116 const auto* pArchetype = record.pArchetype;
9117 if (pArchetype->is_req_del())
9118 continue;
9119
9120 for (const auto* pChunk: pArchetype->chunks()) {
9121 const auto entities = pChunk->entity_view();
9122 GAIA_EACH(entities) {
9123 const auto entity = entities[i];
9124 GAIA_ASSERT(valid(entity));
9125 const auto entityKey = EntityLookupKey(entity);
9126 if (seen.contains(entityKey))
9127 continue;
9128 seen.insert(entityKey);
9129
9130 if (!func(entity))
9131 return false;
9132
9133 const auto& descendants = as_relations_trav_cache(entity);
9134 for (const auto descendant: descendants) {
9135 GAIA_ASSERT(valid(descendant));
9136 const auto descendantKey = EntityLookupKey(descendant);
9137 if (seen.contains(descendantKey))
9138 continue;
9139 seen.insert(descendantKey);
9140
9141 if (!func(descendant))
9142 return false;
9143 }
9144 }
9145 }
9146 }
9147
9148 return true;
9149 }
9150
9156 GAIA_NODISCARD uint32_t count_direct_term_entities_inter(Entity term, bool allowSemanticIs) const {
9157 if (term == EntityBad)
9158 return 0;
9159
9160 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
9161 const auto target = get(term.gen());
9162 if (!valid(target))
9163 return 0;
9164
9165 return (uint32_t)as_relations_trav_cache(target).size() + 1;
9166 }
9167
9168 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
9169 uint32_t cnt = 0;
9170 (void)each_inherited_term_entity(term, [&](Entity) {
9171 ++cnt;
9172 return true;
9173 });
9174 return cnt;
9175 }
9176
9177 if (term.pair() && relation_uses_non_fragmenting_storage(pair_rel(*this, term))) {
9178 const auto relation = pair_rel(*this, term);
9179 const auto* pStore = nonfragmenting_relation_store(relation);
9180 if (pStore == nullptr)
9181 return 0;
9182
9183 if (is_wildcard(term.gen()))
9184 return pStore->source_count();
9185
9186 const auto* pSources = pStore->sources(pair_tgt(*this, term));
9187 return pSources != nullptr ? (uint32_t)pSources->size() : 0;
9188 }
9189
9190 if (!term.pair() && component_is_non_fragmenting(term)) {
9191 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
9192 return it != m_sparseComponentsByComp.end() ? it->second.func_count(it->second.pStore) : 0;
9193 }
9194
9195 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
9196 if (it == m_entityToArchetypeMap.end())
9197 return 0;
9198
9199 uint32_t cnt = 0;
9200 for (const auto& record: it->second) {
9201 const auto* pArchetype = record.pArchetype;
9202 if (pArchetype->is_req_del())
9203 continue;
9204 for (const auto* pChunk: pArchetype->chunks())
9205 cnt += pChunk->size();
9206 }
9207
9208 return cnt;
9209 }
9210
9215 void collect_direct_term_entities_inter(Entity term, cnt::darray<Entity>& out, bool allowSemanticIs) const {
9216 if (term == EntityBad)
9217 return;
9218
9219 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
9220 const auto target = get(term.gen());
9221 if (!valid(target))
9222 return;
9223
9224 out.push_back(target);
9225 const auto& relations = as_relations_trav_cache(target);
9226 out.reserve(out.size() + (uint32_t)relations.size());
9227 for (auto relation: relations)
9228 out.push_back(relation);
9229 return;
9230 }
9231
9232 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
9233 (void)each_inherited_term_entity(term, [&](Entity entity) {
9234 out.push_back(entity);
9235 return true;
9236 });
9237 return;
9238 }
9239
9240 if (term.pair() && relation_uses_non_fragmenting_storage(pair_rel(*this, term))) {
9241 const auto relation = pair_rel(*this, term);
9242 const auto* pStore = nonfragmenting_relation_store(relation);
9243 if (pStore == nullptr)
9244 return;
9245
9246 if (is_wildcard(term.gen())) {
9247 cnt::darray<EntityId> sourceIds;
9248 pStore->collect_source_ids(sourceIds);
9249 out.reserve(out.size() + pStore->source_count());
9250 for (auto sourceId: sourceIds) {
9251 if (!m_recs.entities.has(sourceId))
9252 continue;
9253 out.push_back(EntityContainer::handle(m_recs.entities[sourceId]));
9254 }
9255 pStore->collect_pair_sources(out);
9256 return;
9257 }
9258
9259 const auto* pSources = pStore->sources(pair_tgt(*this, term));
9260 if (pSources == nullptr)
9261 return;
9262
9263 out.reserve(out.size() + (uint32_t)pSources->size());
9264 for (auto source: *pSources)
9265 out.push_back(source);
9266 return;
9267 }
9268
9269 if (!term.pair() && component_is_non_fragmenting(term)) {
9270 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
9271 if (it != m_sparseComponentsByComp.end())
9272 it->second.func_collect_entities(it->second.pStore, out);
9273 return;
9274 }
9275
9276 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
9277 if (it == m_entityToArchetypeMap.end())
9278 return;
9279
9280 for (const auto& record: it->second) {
9281 const auto* pArchetype = record.pArchetype;
9282 if (pArchetype->is_req_del())
9283 continue;
9284
9285 for (const auto* pChunk: pArchetype->chunks()) {
9286 const auto entities = pChunk->entity_view();
9287 GAIA_EACH(entities)
9288 out.push_back(entities[i]);
9289 }
9290 }
9291 }
9292
9299 GAIA_NODISCARD bool
9300 each_direct_term_entity_inter(Entity term, void* ctx, bool (*func)(void*, Entity), bool allowSemanticIs) const {
9301 if (term == EntityBad)
9302 return true;
9303
9304 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
9305 const auto target = get(term.gen());
9306 if (!valid(target))
9307 return true;
9308
9309 if (!func(ctx, target))
9310 return false;
9311
9312 const auto& relations = as_relations_trav_cache(target);
9313 for (auto relation: relations) {
9314 if (!func(ctx, relation))
9315 return false;
9316 }
9317 return true;
9318 }
9319
9320 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
9321 return each_inherited_term_entity(term, [&](Entity entity) {
9322 return func(ctx, entity);
9323 });
9324 }
9325
9326 if (term.pair() && relation_uses_non_fragmenting_storage(pair_rel(*this, term))) {
9327 const auto relation = pair_rel(*this, term);
9328 const auto* pStore = nonfragmenting_relation_store(relation);
9329 if (pStore == nullptr)
9330 return true;
9331
9332 if (is_wildcard(term.gen())) {
9333 cnt::darray<EntityId> sourceIds;
9334 cnt::darray<Entity> pairSources;
9335 pStore->collect_source_ids(sourceIds);
9336 pStore->collect_pair_sources(pairSources);
9337 for (auto sourceId: sourceIds) {
9338 if (!m_recs.entities.has(sourceId))
9339 continue;
9340 if (!func(ctx, EntityContainer::handle(m_recs.entities[sourceId])))
9341 return false;
9342 }
9343 for (auto source: pairSources) {
9344 if (!func(ctx, source))
9345 return false;
9346 }
9347 return true;
9348 }
9349
9350 const auto* pSources = pStore->sources(pair_tgt(*this, term));
9351 if (pSources == nullptr)
9352 return true;
9353
9354 for (auto source: *pSources) {
9355 if (!func(ctx, source))
9356 return false;
9357 }
9358 return true;
9359 }
9360
9361 if (!term.pair() && component_is_non_fragmenting(term)) {
9362 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
9363 if (it == m_sparseComponentsByComp.end())
9364 return true;
9365 return it->second.func_each_entity(it->second.pStore, ctx, func);
9366 }
9367
9368 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
9369 if (it == m_entityToArchetypeMap.end())
9370 return true;
9371
9372 for (const auto& record: it->second) {
9373 const auto* pArchetype = record.pArchetype;
9374 if (pArchetype->is_req_del())
9375 continue;
9376
9377 for (const auto* pChunk: pArchetype->chunks()) {
9378 const auto entities = pChunk->entity_view();
9379 GAIA_EACH(entities) {
9380 if (!func(ctx, entities[i]))
9381 return false;
9382 }
9383 }
9384 }
9385
9386 return true;
9387 }
9388
9389 public:
9393 GAIA_NODISCARD uint32_t count_direct_term_entities(Entity term) const {
9394 return count_direct_term_entities_inter(term, true);
9395 }
9396
9400 GAIA_NODISCARD uint32_t count_direct_term_entities_direct(Entity term) const {
9401 return count_direct_term_entities_inter(term, false);
9402 }
9403
9408 collect_direct_term_entities_inter(term, out, true);
9409 }
9410
9415 collect_direct_term_entities_inter(term, out, false);
9416 }
9417
9423 GAIA_NODISCARD bool each_direct_term_entity(Entity term, void* ctx, bool (*func)(void*, Entity)) const {
9424 return each_direct_term_entity_inter(term, ctx, func, true);
9425 }
9426
9432 GAIA_NODISCARD bool each_direct_term_entity_direct(Entity term, void* ctx, bool (*func)(void*, Entity)) const {
9433 return each_direct_term_entity_inter(term, ctx, func, false);
9434 }
9435
9441 template <typename Func>
9442 void sources_bfs(Entity relation, Entity rootTarget, Func func) const {
9443 if (!valid(relation) || !valid(rootTarget))
9444 return;
9445
9446 if (m_enabledHierarchyVersion == 0) {
9447 const auto& cachedSources = sources_bfs_trav_cache(relation, rootTarget);
9448 for (auto source: cachedSources)
9449 func(source);
9450 return;
9451 }
9452
9455 queue.push_back(rootTarget);
9456
9458 visited.insert(EntityLookupKey(rootTarget));
9459
9460 for (uint32_t i = 0; i < queue.size(); ++i) {
9461 const auto currTarget = queue[i];
9462
9463 children.clear();
9464 sources(relation, currTarget, [&](Entity source) {
9465 const auto key = EntityLookupKey(source);
9466 const auto ins = visited.insert(key);
9467 if (!ins.second)
9468 return;
9469
9470 children.push_back(source);
9471 });
9472
9473 core::sort(children, SortEntityIdCond{});
9474
9475 for (auto child: children) {
9476 if (!enabled(child))
9477 continue;
9478 func(child);
9479 queue.push_back(child);
9480 }
9481 }
9482 }
9483
9491 template <typename Func>
9492 GAIA_NODISCARD bool sources_bfs_if(Entity relation, Entity rootTarget, Func func) const {
9493 if (!valid(relation) || !valid(rootTarget))
9494 return false;
9495
9496 if (m_enabledHierarchyVersion == 0) {
9497 const auto& cachedSources = sources_bfs_trav_cache(relation, rootTarget);
9498 for (auto source: cachedSources) {
9499 if (func(source))
9500 return true;
9501 }
9502
9503 return false;
9504 }
9505
9508 queue.push_back(rootTarget);
9509
9511 visited.insert(EntityLookupKey(rootTarget));
9512
9513 for (uint32_t i = 0; i < queue.size(); ++i) {
9514 const auto currTarget = queue[i];
9515
9516 children.clear();
9517 sources(relation, currTarget, [&](Entity source) {
9518 const auto key = EntityLookupKey(source);
9519 const auto ins = visited.insert(key);
9520 if (!ins.second)
9521 return;
9522
9523 children.push_back(source);
9524 });
9525
9526 core::sort(children, SortEntityIdCond{});
9527
9528 for (auto child: children) {
9529 if (!enabled(child))
9530 continue;
9531 if (func(child))
9532 return true;
9533
9534 queue.push_back(child);
9535 }
9536 }
9537
9538 return false;
9539 }
9540
9545 template <typename Func>
9546 void as_targets_trav(Entity relation, Func func) const {
9547 GAIA_ASSERT(valid(relation));
9548 if (!valid(relation))
9549 return;
9550
9551 const auto& targets = as_targets_trav_cache(relation);
9552 for (auto target: targets)
9553 func(target);
9554 }
9555
9561 template <typename Func>
9562 bool as_targets_trav_if(Entity relation, Func func) const {
9563 GAIA_ASSERT(valid(relation));
9564 if (!valid(relation))
9565 return false;
9566
9567 const auto& targets = as_targets_trav_cache(relation);
9568 for (auto target: targets)
9569 if (func(target))
9570 return true;
9571
9572 return false;
9573 }
9574
9575 //----------------------------------------------------------------------
9576
9580 return *m_pCmdBufferST;
9581 }
9582
9586 return *m_pCmdBufferMT;
9587 }
9588
9589 //----------------------------------------------------------------------
9590
9591#if GAIA_SYSTEMS_ENABLED
9592
9594 void systems_init();
9595
9614 void systems_run();
9615
9628 void systems_run(Entity phase);
9629
9632 SystemBuilder system();
9633
9635 SystemRegistry& systems() {
9636 return m_systems;
9637 }
9638
9640 const SystemRegistry& systems() const {
9641 return m_systems;
9642 }
9643
9644#endif
9645
9646#if GAIA_OBSERVERS_ENABLED
9647
9650 ObserverBuilder observer();
9651
9653 ObserverRegistry& observers() {
9654 return m_observers;
9655 }
9656
9658 const ObserverRegistry& observers() const {
9659 return m_observers;
9660 }
9661
9665 void defer_on_set_begin(uint32_t slotCount) {
9666 GAIA_ASSERT(m_deferOnSetDepth != (uint32_t)-1);
9667 if (m_deferOnSetDepth++ != 0)
9668 return;
9669
9670 // One queue per work item. Items are distributed to threads in disjoint ranges, so a
9671 // queue is only ever touched by the single thread processing that item and no
9672 // synchronization is needed while the region is open.
9673 m_deferredOnSet.resize(slotCount);
9674 for (auto& queue: m_deferredOnSet)
9675 queue.clear();
9676 }
9677
9681 void defer_on_set_end() {
9682 GAIA_ASSERT(m_deferOnSetDepth > 0);
9683 if (--m_deferOnSetDepth != 0)
9684 return;
9685
9686 // Observer callbacks may trigger writes of their own. Those are dispatched directly
9687 // because the recording region already ended, so move the queues aside before
9688 // walking them.
9689 auto pending = GAIA_MOV(m_deferredOnSet);
9690 m_deferredOnSet = {};
9691 for (const auto& queue: pending) {
9692 for (const auto& item: queue)
9693 world_notify_on_set_entity(*this, item.term, item.entity);
9694 }
9695 }
9696
9698 GAIA_NODISCARD bool defer_on_set_active() const {
9699 return m_deferOnSetDepth != 0;
9700 }
9701
9706 void defer_on_set_record(uint32_t slot, Entity term, Entity entity) {
9707 GAIA_ASSERT(slot < m_deferredOnSet.size());
9708 m_deferredOnSet[slot].push_back({term, entity});
9709 }
9710
9711#endif
9712
9716 void defer_sort_inv_begin(uint32_t slotCount) {
9717 GAIA_ASSERT(m_deferSortInvDepth != (uint32_t)-1);
9718 if (m_deferSortInvDepth++ != 0)
9719 return;
9720
9721 // One queue per work item. Items are distributed to threads in disjoint ranges, so a
9722 // queue is only ever touched by the single thread processing that item and no
9723 // synchronization is needed while the region is open.
9724 m_deferredSortInv.resize(slotCount);
9725 for (auto& queue: m_deferredSortInv)
9726 queue.clear();
9727 }
9728
9733 GAIA_ASSERT(m_deferSortInvDepth > 0);
9734 if (--m_deferSortInvDepth != 0)
9735 return;
9736
9737 // The coordinator may itself write during apply (e.g. through nested queries), so move
9738 // the queue aside before walking it, matching the observer path.
9739 auto pending = GAIA_MOV(m_deferredSortInv);
9740 m_deferredSortInv = {};
9741 for (const auto& queue: pending) {
9742 for (const auto& item: queue)
9743 invalidate_sorted_queries_for_entity(item.entity);
9744 }
9745 }
9746
9749 GAIA_NODISCARD bool defer_sort_inv_active() const {
9750 return m_deferSortInvDepth != 0;
9751 }
9752
9756 void defer_sort_inv_record(uint32_t slot, Entity entity) {
9757 GAIA_ASSERT(slot < m_deferredSortInv.size());
9758 m_deferredSortInv[slot].push_back({entity});
9759 }
9760
9761 //----------------------------------------------------------------------
9762
9767 void enable(Entity entity, bool enable) {
9768 GAIA_ASSERT(valid(entity));
9769
9770 auto& ec = cont(entity);
9771 auto& archetype = *ec.pArchetype;
9772 auto* pChunk = ec.pChunk;
9773 const bool wasEnabled = !ec.data.dis;
9774#if GAIA_ASSERT_ENABLED
9775 verify_enable(*this, archetype, entity);
9776#endif
9777 archetype.enable_entity(ec.pChunk, ec.row, enable, m_recs);
9778
9779 if (wasEnabled != enable) {
9780 pChunk->update_world_version();
9781 pChunk->update_entity_order_version();
9782 update_version(m_enabledHierarchyVersion);
9783 update_version(m_worldVersion);
9784 }
9785 }
9786
9791 void enable(Pair entity, bool enable) {
9792 const auto source = (Entity)entity;
9793 GAIA_ASSERT(valid(source));
9794
9795 auto& ec = fetch(source);
9796 auto& archetype = *ec.pArchetype;
9797 auto* pChunk = ec.pChunk;
9798 const bool wasEnabled = !ec.data.dis;
9799#if GAIA_ASSERT_ENABLED
9800 verify_enable(*this, archetype, source);
9801#endif
9802 archetype.enable_entity(ec.pChunk, ec.row, enable, m_recs);
9803
9804 if (wasEnabled != enable) {
9805 pChunk->update_world_version();
9806 pChunk->update_entity_order_version();
9807 update_version(m_enabledHierarchyVersion);
9808 update_version(m_worldVersion);
9809 }
9810 }
9811
9815 GAIA_NODISCARD bool enabled(const EntityContainer& ec) const {
9816 const bool entityStateInContainer = !ec.data.dis;
9817#if GAIA_ASSERT_ENABLED
9818 const bool entityStateInChunk = ec.pChunk->enabled(ec.row);
9819 GAIA_ASSERT(entityStateInChunk == entityStateInContainer);
9820#endif
9821 return entityStateInContainer;
9822 }
9823
9828 GAIA_NODISCARD bool enabled(Entity entity) const {
9829 GAIA_ASSERT(valid(entity));
9830
9831 return enabled(cont(entity));
9832 }
9833
9838 GAIA_NODISCARD bool enabled(Pair entity) const {
9839 const auto source = (Entity)entity;
9840 GAIA_ASSERT(valid(source));
9841
9842 return enabled(fetch(source));
9843 }
9844
9850 GAIA_NODISCARD bool enabled_hierarchy(Entity entity, Entity relation) const {
9851 const auto* pEc = try_live_record(entity);
9852 if (pEc == nullptr || try_live_record(relation) == nullptr || !enabled(*pEc))
9853 return false;
9854
9855 auto curr = entity;
9856 GAIA_FOR(MAX_TRAV_DEPTH) {
9857 const auto next = target(curr, relation);
9858 if (next == EntityBad || next == curr)
9859 break;
9860 if (!enabled(next))
9861 return false;
9862 curr = next;
9863 }
9864
9865 return true;
9866 }
9867
9873 GAIA_NODISCARD bool enabled_hierarchy(Pair entity, Entity relation) const {
9874 const auto source = (Entity)entity;
9875 const auto* pEc = try_live_record(source);
9876 if (pEc == nullptr || try_live_record(relation) == nullptr || !enabled(*pEc))
9877 return false;
9878
9879 auto curr = source;
9880 GAIA_FOR(MAX_TRAV_DEPTH) {
9881 const auto next = target(curr, relation);
9882 if (next == EntityBad || next == curr)
9883 break;
9884 if (!enabled(next))
9885 return false;
9886 curr = next;
9887 }
9888
9889 return true;
9890 }
9891
9892 //----------------------------------------------------------------------
9893
9897 GAIA_NODISCARD Chunk* get_chunk(Entity entity) const {
9898 return cont(entity).pChunk;
9899 }
9900
9904 GAIA_NODISCARD Chunk* get_chunk(Pair entity) const {
9905 return fetch((Entity)entity).pChunk;
9906 }
9907
9913 GAIA_NODISCARD Chunk* get_chunk(Entity entity, uint32_t& row) const {
9914 const auto& ec = cont(entity);
9915 row = ec.row;
9916 return ec.pChunk;
9917 }
9918
9923 GAIA_NODISCARD Chunk* get_chunk(Pair entity, uint32_t& row) const {
9924 const auto& ec = fetch((Entity)entity);
9925 row = ec.row;
9926 return ec.pChunk;
9927 }
9928
9931 GAIA_NODISCARD uint32_t size() const {
9932 return m_recs.entities.item_count();
9933 }
9934
9941 uint32_t& outArchetypes, uint32_t& outChunks, uint32_t& outEntitiesTotal, uint32_t& outEntitiesActive) const {
9942 outArchetypes = (uint32_t)m_archetypes.size();
9943 outChunks = 0;
9944 outEntitiesTotal = 0;
9945 outEntitiesActive = 0;
9946
9947 for (const auto* pArchetype: m_archetypes) {
9948 if (pArchetype == nullptr)
9949 continue;
9950 const auto& chunks = pArchetype->chunks();
9951 outChunks += (uint32_t)chunks.size();
9952 for (const auto* pChunk: chunks) {
9953 if (pChunk == nullptr)
9954 continue;
9955 outEntitiesTotal += pChunk->size();
9956 outEntitiesActive += pChunk->size_enabled();
9957 }
9958 }
9959 }
9960
9963 GAIA_NODISCARD uint32_t& world_version() {
9964 return m_worldVersion;
9965 }
9966
9971 GAIA_NODISCARD uint32_t rel_version(Entity relation) const {
9972 const auto it = m_relationVersions.find(EntityLookupKey(relation));
9973 return it != m_relationVersions.end() ? it->second : 0;
9974 }
9975
9979 GAIA_NODISCARD uint32_t enabled_hierarchy_version() const {
9980 return m_enabledHierarchyVersion;
9981 }
9982
9985 GAIA_NODISCARD uint32_t archetype_delete_version() const {
9986 return m_archetypeDeleteVersion;
9987 }
9988
9989 friend uint32_t world_rel_version(const World& world, Entity relation);
9990 friend uint32_t world_version(const World& world);
9991 friend uint32_t world_archetype_delete_version(const World& world);
9992 friend uint32_t world_entity_archetype_version(const World& world, Entity entity);
9993
9997 const auto key = EntityLookupKey(entity);
9998 const auto it = m_srcEntityVersions.find(key);
9999 if (it == m_srcEntityVersions.end())
10000 return;
10001
10002 update_version(it->second);
10003 }
10004
10008 m_srcEntityVersions.erase(EntityLookupKey(entity));
10009 }
10010
10015 set_max_lifespan(entity, m_defaultMaxLifespan);
10016 }
10017
10022 void set_max_lifespan(Entity entity, uint32_t lifespan) {
10023 auto* pEc = try_live_record(entity);
10024 if (pEc == nullptr)
10025 return;
10026
10027 auto& ec = *pEc;
10028 const auto prevLifespan = ec.pArchetype->max_lifespan();
10029 ec.pArchetype->set_max_lifespan(lifespan);
10030
10031 if (prevLifespan == 0) {
10032 // The archetype used to be immortal but not anymore
10033 try_enqueue_archetype_for_deletion(*ec.pArchetype);
10034 }
10035 }
10036
10040 void set_default_max_lifespan(uint32_t lifespan) {
10041 GAIA_ASSERT(lifespan <= Archetype::MAX_ARCHETYPE_LIFESPAN);
10042 m_defaultMaxLifespan = lifespan;
10043 }
10044
10047 GAIA_NODISCARD uint32_t default_max_lifespan() const {
10048 return m_defaultMaxLifespan;
10049 }
10050
10051 //----------------------------------------------------------------------
10052
10064#if GAIA_OBSERVERS_ENABLED && GAIA_ASSERT_ENABLED
10065 GAIA_ASSERT(!observer_callback_active());
10066#endif
10067
10068#if GAIA_USE_SAFE_ENTITY
10069 del_released_safe_entities();
10070#endif
10071
10072 // Finish deleting entities
10073 del_finalize();
10074
10075 // Run garbage collector
10076 gc();
10077 }
10078
10088 void frame_end() {
10089 util::log_flush();
10090
10091 // Signal the end of the frame
10092 GAIA_PROF_FRAME();
10093 }
10094
10104 void update() {
10105#if GAIA_OBSERVERS_ENABLED && GAIA_ASSERT_ENABLED
10106 GAIA_ASSERT(!observer_callback_active());
10107#endif
10108
10109#if GAIA_SYSTEMS_ENABLED
10110 systems_run();
10111#endif
10112 frame_cleanup();
10113 frame_end();
10114 }
10115
10119 void teardown() {
10120 if GAIA_UNLIKELY (m_teardownActive)
10121 return;
10122 m_teardownActive = true;
10123
10124 GAIA_PROF_SCOPE(World::teardown);
10125
10126#if GAIA_SYSTEMS_ENABLED
10127 systems_done();
10128 m_systems.teardown();
10129#endif
10130
10131#if GAIA_OBSERVERS_ENABLED
10132 m_observers.teardown();
10133#endif
10134
10135 for (;;) {
10136 const auto prevReqArchetypes = m_reqArchetypesToDel.size();
10137 const auto prevReqEntities = m_reqEntitiesToDel.size();
10138 const auto prevChunks = m_chunksToDel.size();
10139 const auto prevArchetypes = m_archetypesToDel.size();
10140
10141 del_finalize();
10142 gc();
10143
10144 if (m_reqArchetypesToDel.empty() && m_reqEntitiesToDel.empty() && m_chunksToDel.empty() &&
10145 m_archetypesToDel.empty())
10146 break;
10147
10148 const bool madeProgress = m_reqArchetypesToDel.size() != prevReqArchetypes ||
10149 m_reqEntitiesToDel.size() != prevReqEntities ||
10150 m_chunksToDel.size() != prevChunks || m_archetypesToDel.size() != prevArchetypes;
10151 if (!madeProgress)
10152 break;
10153 }
10154
10155 util::log_flush();
10156 }
10157
10162 void cleanup() {
10163 cleanup_inter();
10164
10165 // Reinit
10166 m_pRootArchetype = nullptr;
10167 m_pEntityArchetype = nullptr;
10168 m_pCompArchetype = nullptr;
10169 m_nextArchetypeId = 0;
10170 m_defragLastArchetypeIdx = 0;
10171 m_worldVersion = 0;
10172 m_enabledHierarchyVersion = 0;
10173 m_archetypeDeleteVersion = 0;
10174 init();
10175 }
10176
10179 void defrag_entities_per_tick(uint32_t value) {
10180 m_defragEntitiesPerTick = value;
10181 }
10182
10183 //--------------------------------------------------------------------------------
10184
10186 void diag_archetypes() const {
10187 GAIA_LOG_N("Archetypes:%u", (uint32_t)m_archetypes.size());
10188 for (auto* pArchetype: m_archetypes)
10189 Archetype::diag(*this, *pArchetype);
10190 }
10191
10194 void diag_components() const {
10195 comp_cache().diag();
10196 }
10197
10200 void diag_entities() const {
10201 validate_entities();
10202
10203 GAIA_LOG_N("Deleted entities: %u", (uint32_t)m_recs.entities.get_free_items());
10204 if (m_recs.entities.get_free_items() != 0U) {
10205 GAIA_LOG_N(" --> %u", (uint32_t)m_recs.entities.get_next_free_item());
10206
10207 uint32_t iters = 0;
10208 auto fe = m_recs.entities.next_free(m_recs.entities.get_next_free_item());
10209 while (fe != IdentifierIdBad) {
10210 GAIA_LOG_N(" --> %u", m_recs.entities.next_free(fe));
10211 fe = m_recs.entities.next_free(fe);
10212 ++iters;
10213 if (iters > m_recs.entities.get_free_items())
10214 break;
10215 }
10216
10217 if ((iters == 0U) || iters > m_recs.entities.get_free_items())
10218 GAIA_LOG_E(" Entities recycle list contains inconsistent data!");
10219 }
10220 }
10221
10223 void diag() const {
10224 diag_archetypes();
10225 diag_components();
10226 diag_entities();
10227 }
10228
10229 private:
10231 void cleanup_inter() {
10232 GAIA_PROF_SCOPE(World::cleanup_inter);
10233
10234 // Shutdown bypasses the regular GC path, so clear raw-pointer tracking first.
10235 // Chunk/component dtors that run while archetypes are freed can still drop cached queries,
10236 // but after this point they must not touch stale archetype/chunk reverse indices.
10237 {
10238 m_queryCache.clear_archetype_tracking();
10239 m_reqArchetypesToDel = {};
10240 m_reqEntitiesToDel = {};
10241 m_archetypeRowsToDel = {};
10242 m_entitiesDeleting = {};
10243 m_entitiesDeletingSet = {};
10244#if GAIA_USE_SAFE_ENTITY
10245 m_safeEntityDels = {};
10246#endif
10247 m_chunksToDel = {};
10248 m_archetypesToDel = {};
10249 }
10250
10251 // Reset every WeakEntity before its record goes. Cleanup restarts ids, so a surviving one
10252 // could name a new entity, and one held outside a destroyed world would point into it.
10253 // Components destroyed below then find no tracker to release.
10254#if GAIA_USE_WEAK_ENTITY
10255 for (auto& ec: m_recs.entities)
10256 invalidate_weak_entities(ec);
10257 for (auto it = m_recs.pair_record_begin(); it != m_recs.pair_record_end(); ++it)
10258 invalidate_weak_entities(it->second);
10259#endif
10260
10261 // Clear entities
10262 m_recs.entities = {};
10263 m_recs.pair_records_clear();
10264
10265 // Clear archetypes
10266 {
10267 // Delete all allocated chunks and their parent archetypes
10268 for (auto* pArchetype: m_archetypes)
10269 Archetype::destroy(pArchetype);
10270
10271 m_entityToAsRelations = {};
10272 m_entityToAsRelationsTravCache = {};
10273 m_entityToAsTargets = {};
10274 m_entityToAsTargetsTravCache = {};
10275 m_targetsTravCache = {};
10276 m_srcBfsTravCache = {};
10277 m_depthOrderCache = {};
10278 m_sourcesAllCache = {};
10279 m_targetsAllCache = {};
10280 m_relationCachesPopulated = false;
10281 m_hasOnDeleteTargetPolicy = false;
10282 m_hasCantCombinePolicy = false;
10283 m_pairLookup.clear();
10284 m_nonFragmentingRelationsByRel = {};
10285 for (auto& [compKey, store]: m_sparseComponentsByComp) {
10286 (void)compKey;
10287 store.func_clear_store(store.pStore);
10288 store.func_del_store(store.pStore);
10289 }
10290 m_sparseComponentsByComp = {};
10291 m_relationVersions = {};
10292 m_lastRelationVersionRelation = EntityBad;
10293 m_pLastRelationVersion = nullptr;
10294 m_srcEntityVersions = {};
10295
10296 m_archetypes = {};
10297 m_archetypesById = {};
10298 m_archetypesByHash = {};
10299 }
10300
10301 // Clear caches
10302 {
10303 m_entityToArchetypeMap = {};
10304 m_entityToArchetypeMapVersions = {};
10305 m_queryCache.clear();
10306 for (auto* pScratch: m_queryMatchScratchStack)
10307 delete pScratch;
10308 m_queryMatchScratchStack = {};
10309 m_queryMatchScratchDepth = 0;
10310 m_querySerMap = {};
10311 m_nextQuerySerId = 0;
10312 }
10313
10314 // Clear entity aliases
10315 {
10316 for (auto& pair: m_aliasToEntity) {
10317 if (!pair.first.owned())
10318 continue;
10319 // Release any memory allocated for owned names
10320 mem::mem_free((void*)pair.first.str());
10321 }
10322 m_aliasToEntity = {};
10323 }
10324
10325 // Clear entity names
10326 {
10327 for (auto& pair: m_nameToEntity) {
10328 if (!pair.first.owned())
10329 continue;
10330 // Release any memory allocated for owned names
10331 mem::mem_free((void*)pair.first.str());
10332 }
10333 m_nameToEntity = {};
10334 }
10335
10336 // Clear component cache
10337 m_compCache.clear();
10338 }
10339
10344 GAIA_NODISCARD bool valid(const EntityContainer& ec, [[maybe_unused]] Entity entityExpected) const {
10345 if ((ec.flags & EntityContainerFlags::Load) != 0) {
10346 return entityExpected.id() == ec.idx && entityExpected.gen() == ec.data.gen &&
10347 entityExpected.entity() == (bool)ec.data.ent && entityExpected.pair() == (bool)ec.data.pair;
10348 }
10349
10350 if (is_req_del(ec))
10351 return false;
10352
10353 // The entity in the chunk must match the index in the entity container
10354 const auto* pChunk = ec.pChunk;
10355 if (pChunk == nullptr || ec.row >= pChunk->size())
10356 return false;
10357
10358 const auto entityPresent = pChunk->entity_view()[ec.row];
10359 // Public validity checks can legitimately observe a recycled slot with a different generation.
10360 // Treat that as stale instead of aborting.
10361 return entityExpected == entityPresent;
10362 }
10363
10367 GAIA_NODISCARD bool valid_pair(Entity entity) const {
10368 if (entity == EntityBad)
10369 return false;
10370
10371 GAIA_ASSERT(entity.pair());
10372 if (!entity.pair())
10373 return false;
10374
10375 // Ignore wildcards because they can't be attached to entities
10376 if (is_wildcard(entity))
10377 return true;
10378
10379 const auto* pPair = m_recs.pair_record_find(entity);
10380 if (pPair == nullptr)
10381 return false;
10382
10383 const auto& ec = *pPair;
10384 return valid(ec, entity);
10385 }
10386
10390 GAIA_NODISCARD bool valid_entity(Entity entity) const {
10391 if (entity == EntityBad)
10392 return false;
10393
10394 GAIA_ASSERT(!entity.pair());
10395 if (entity.pair())
10396 return false;
10397
10398 // Entity ID has to fit inside the entity array
10399 if (entity.id() >= m_recs.entities.size())
10400 return false;
10401
10402 const auto* pEc = m_recs.entities.try_get(entity.id());
10403 if (pEc == nullptr)
10404 return false;
10405
10406 return valid(*pEc, entity);
10407 }
10408
10413 GAIA_NODISCARD bool valid_entity_id(EntityId entityId) const {
10414 if (entityId == EntityBad.id())
10415 return false;
10416
10417 // Entity ID has to fit inside the entity array
10418 if (entityId >= m_recs.entities.size())
10419 return false;
10420
10421 const auto* pEc = m_recs.entities.try_get(entityId);
10422 if (pEc == nullptr)
10423 return false;
10424
10425 const auto& ec = *pEc;
10426 if (ec.data.pair != 0)
10427 return false;
10428
10429 return valid(ec, Entity(entityId, ec.data.gen, (bool)ec.data.ent, (bool)ec.data.pair));
10430 }
10431
10435 void lock() {
10436 GAIA_ASSERT(m_structuralChangesLocked != (uint32_t)-1);
10437 ++m_structuralChangesLocked;
10438 }
10439
10443 void unlock() {
10444 GAIA_ASSERT(m_structuralChangesLocked > 0);
10445 --m_structuralChangesLocked;
10446 }
10447
10448#if GAIA_SYSTEMS_ENABLED
10450 void systems_done();
10451#endif
10452
10453 public:
10456 GAIA_NODISCARD bool locked() const {
10457 return m_structuralChangesLocked != 0;
10458 }
10459
10463 GAIA_NODISCARD bool tearing_down() const {
10464 return m_teardownActive;
10465 }
10466
10467 private:
10468 static constexpr uint32_t WorldSerializerVersion = 6;
10469#if GAIA_JSON_ENABLED
10470 static constexpr uint32_t WorldSerializerJSONVersion = 1;
10471#endif
10472
10476 void save_component_layout(ser::serializer& s, uint32_t lastCoreComponentId) const {
10478 components.reserve((uint32_t)m_compCache.m_compByEntityId.size());
10479 for (const auto& [entityId, pItem]: m_compCache.m_compByEntityId) {
10480 if (entityId > lastCoreComponentId) {
10481 GAIA_ASSERT(pItem != nullptr);
10482 components.push_back(pItem);
10483 }
10484 }
10485 core::sort(components, [](const ComponentCacheItem* pLeft, const ComponentCacheItem* pRight) {
10486 return pLeft->entity.id() < pRight->entity.id();
10487 });
10488
10489 s.save((uint32_t)components.size());
10490 for (const auto* pItem: components) {
10491 const auto symbol = pItem->symbol_name();
10492 s.save(pItem->entity.val);
10493 s.save(pItem->comp.val);
10494 s.save(symbol.size());
10495 s.save_raw(symbol.data(), symbol.size(), ser::serialization_type_id::c8);
10496 }
10497 }
10498
10504 bool validate_component_layout(
10505 ser::serializer& s, uint32_t savedLastCoreComponentId, uint32_t currLastCoreComponentId) const {
10506 uint32_t savedComponentCnt = 0;
10507 s.load(savedComponentCnt);
10508
10509 uint32_t currentComponentCnt = 0;
10510 for (const auto& [entityId, pItem]: m_compCache.m_compByEntityId) {
10511 (void)pItem;
10512 if (entityId > currLastCoreComponentId)
10513 ++currentComponentCnt;
10514 }
10515 if (savedComponentCnt != currentComponentCnt) {
10516 GAIA_LOG_E(
10517 "World snapshot component count mismatch. Snapshot has %u, target world has %u.", savedComponentCnt,
10518 currentComponentCnt);
10519 return false;
10520 }
10521
10522 GAIA_FOR(savedComponentCnt) {
10523 Identifier savedEntityValue = IdentifierBad;
10524 Identifier savedComponentValue = IdentifierBad;
10525 uint32_t symbolLen = 0;
10526 s.load(savedEntityValue);
10527 s.load(savedComponentValue);
10528 s.load(symbolLen);
10529
10530 const auto* pSymbol = (const char*)(s.data() + s.tell());
10531 s.seek(s.tell() + symbolLen);
10532 const util::str_view symbol(pSymbol, symbolLen);
10533 const auto* pItem = comp_cache().symbol(symbol);
10534 if (pItem == nullptr) {
10535 GAIA_LOG_E("World snapshot component '%.*s' is not registered.", (int)symbolLen, pSymbol);
10536 return false;
10537 }
10538
10539 const auto savedEntity = Entity(savedEntityValue);
10540 const auto expectedEntity =
10541 detail::remap_loaded_entity(savedEntity, savedLastCoreComponentId, currLastCoreComponentId);
10542 Component expectedComponent;
10543 expectedComponent.val = savedComponentValue;
10544 expectedComponent.data.id =
10545 detail::remap_loaded_entity_id(expectedComponent.id(), savedLastCoreComponentId, currLastCoreComponentId);
10546 if (pItem->entity != expectedEntity || pItem->comp != expectedComponent) {
10547 GAIA_LOG_E(
10548 "World snapshot component layout mismatch for '%.*s'. Register components in the same order and "
10549 "with the same storage layout before loading.",
10550 (int)symbolLen, pSymbol);
10551 return false;
10552 }
10553 }
10554
10555 return true;
10556 }
10557
10560 void save_sparse_component_stores(ser::serializer& s) const {
10561 if (m_sparseComponentsByComp.empty()) {
10562 s.save((uint32_t)0);
10563 return;
10564 }
10565
10566 cnt::darray<Entity> components;
10567 components.reserve((uint32_t)m_sparseComponentsByComp.size());
10568 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
10569 if (store.func_count(store.pStore) != 0)
10570 components.push_back(compKey.entity());
10571 }
10572 core::sort(components, core::is_smaller<Entity>{});
10573
10574 s.save((uint32_t)components.size());
10575 for (auto component: components) {
10576 const auto storeIt = m_sparseComponentsByComp.find(EntityLookupKey(component));
10577 GAIA_ASSERT(storeIt != m_sparseComponentsByComp.end());
10578 const auto& store = storeIt->second;
10579 const auto* pItem =
10580 component.pair() ? comp_cache().find_pair_payload(component) : comp_cache().find(component);
10581 GAIA_ASSERT(pItem != nullptr);
10582
10583 cnt::darray<Entity> owners;
10584 store.func_collect_entities(store.pStore, owners);
10585 core::sort(owners, core::is_smaller<Entity>{});
10586
10587 s.save(component);
10588 s.save((uint32_t)owners.size());
10589 for (auto owner: owners) {
10590 GAIA_ASSERT(valid(owner));
10591 s.save(owner);
10592 if (pItem->comp.size() == 0)
10593 continue;
10594
10595 const auto* pData = store.func_get(store.pStore, owner);
10596 GAIA_ASSERT(pData != nullptr);
10597 pItem->save(s, pData, 0, 1, 1);
10598 }
10599 }
10600 }
10601
10605 bool load_sparse_component_stores(ser::serializer& s) {
10606 uint32_t componentCnt = 0;
10607 s.load(componentCnt);
10608 GAIA_FOR(componentCnt) {
10609 Entity component;
10610 uint32_t ownerCnt = 0;
10611 s.load(component);
10612 s.load(ownerCnt);
10613
10614 const auto* pItem =
10615 component.pair() ? comp_cache().find_pair_payload(component) : comp_cache().find(component);
10616 // Empty DontFragment tags keep a table-sized descriptor and still serialize through
10617 // the membership store. Non-empty payloads must be Sparse.
10618 if (pItem == nullptr || (pItem->comp.size() != 0 && pItem->comp.storage_type() != DataStorageType::Sparse)) {
10619 GAIA_LOG_E("Missing sparse component metadata for entity %u", component.id());
10620 return false;
10621 }
10622 auto& store = sparse_component_store_erased_mut(component, *pItem);
10623 GAIA_FOR_(ownerCnt, ownerIdx) {
10624 Entity owner;
10625 s.load(owner);
10626 auto* pData = store.func_add(store.pStore, owner);
10627 if (pItem->comp.size() == 0)
10628 continue;
10629
10630 GAIA_ASSERT(pData != nullptr);
10631 pItem->load(s, pData, 0, 1, 1);
10632 }
10633 }
10634 return true;
10635 }
10636
10641 void load_record_location(EntityContainer& ec, Entity entity) {
10642 const auto archetypeIdx = (uint32_t)((uintptr_t)ec.pArchetype); // Decode the archetype idx
10643 const auto chunkIdx = (uint32_t)((uintptr_t)ec.pChunk); // Decode the chunk idx
10644 if (archetypeIdx == (uint32_t)BadIndex) {
10645 ec.pArchetype = nullptr;
10646 ec.pChunk = nullptr;
10647 ec.pEntity = nullptr;
10648 m_reqEntitiesToDel.insert(EntityLookupKey(entity));
10649 return;
10650 }
10651
10652 ec.pArchetype = m_archetypes[archetypeIdx];
10653 ec.pChunk = ec.pArchetype->chunks()[chunkIdx];
10654 ec.pEntity = &ec.pChunk->entity_view()[ec.row];
10655 }
10656
10659 void save_to(ser::serializer s) const {
10660 GAIA_ASSERT(s.valid());
10661
10662 // Snapshot format version.
10663 s.save((uint32_t)WorldSerializerVersion);
10664
10665 // Store the index of the last core component.
10666 // TODO: As this changes, we will have to modify entity ids accordingly.
10667 const auto lastCoreComponentId = GAIA_ID(LastCoreComponent).id();
10668 s.save(lastCoreComponentId);
10669 save_component_layout(s, lastCoreComponentId);
10670
10671 // Entities
10672 {
10673 auto saveEntityContainer = [&](const EntityContainer& ec) {
10674 s.save(ec.idx);
10675 s.save(ec.dataRaw);
10676 s.save(ec.row);
10677 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) == 0);
10678
10679 // A deleted entity whose record is not freed yet, because archetypes still use its id or a
10680 // SafeEntity references it, has no storage left. It is saved without a location and loads
10681 // as a pending deletion, which the next frame_cleanup() finishes.
10682 const bool deleted = (ec.flags & EntityContainerFlags::DeleteRequested) != 0;
10683 auto flags = ec.flags;
10684#if GAIA_USE_SAFE_ENTITY
10685 // SafeEntity handles do not survive a load, so only the world's own reference is saved.
10686 // A deletion that waited for handles is dropped along with them.
10687 if (!deleted)
10688 flags &= (EntityContainerFlagsType)(~(EntityContainerFlagsType)EntityContainerFlags::RefDecreased);
10689 s.save(flags); // ignore Load
10690 s.save((uint32_t)(deleted ? 0 : 1));
10691#else
10692 s.save(flags); // ignore Load
10693 s.save((uint32_t)0);
10694#endif
10695
10696 const uint32_t archetypeIdx = deleted ? (uint32_t)BadIndex : ec.pArchetype->list_idx();
10697 s.save(archetypeIdx);
10698 const uint32_t chunkIdx = deleted ? (uint32_t)BadIndex : ec.pChunk->idx();
10699 s.save(chunkIdx);
10700 };
10701
10702 const auto recEntities = (uint32_t)m_recs.entities.size();
10703 const auto newEntities = recEntities - lastCoreComponentId;
10704 s.save(newEntities);
10705 GAIA_FOR2(lastCoreComponentId, recEntities) {
10706 const bool isAlive = m_recs.entities.has(i);
10707 s.save(isAlive);
10708 if (isAlive)
10709 saveEntityContainer(m_recs.entities[i]);
10710 else {
10711 s.save(m_recs.entities.handle(i).val);
10712 s.save(m_recs.entities.next_free(i));
10713 }
10714 }
10715
10716 {
10717 uint32_t pairsCnt = 0;
10718 for (auto it = m_recs.pair_record_begin(); it != m_recs.pair_record_end(); ++it) {
10719 const auto& pair = *it;
10720 // Skip core pairs
10721 if (pair.first.entity().id() < lastCoreComponentId && pair.first.entity().gen() < lastCoreComponentId)
10722 continue;
10723
10724 ++pairsCnt;
10725 }
10726 s.save(pairsCnt);
10727 }
10728 {
10729 for (auto it = m_recs.pair_record_begin(); it != m_recs.pair_record_end(); ++it) {
10730 const auto& pair = *it;
10731 // Skip core pairs
10732 if (pair.first.entity().id() < lastCoreComponentId && pair.first.entity().gen() < lastCoreComponentId)
10733 continue;
10734
10735 saveEntityContainer(pair.second);
10736 }
10737 }
10738
10739 s.save(m_recs.entities.m_nextFreeIdx);
10740 s.save(m_recs.entities.m_freeItems);
10741 }
10742
10743 // World
10744 {
10745 s.save((uint32_t)m_archetypes.size());
10746 for (auto* pArchetype: m_archetypes) {
10747 s.save((uint32_t)pArchetype->ids_view().size());
10748 for (auto e: pArchetype->ids_view())
10749 s.save(e);
10750
10751 pArchetype->save(s);
10752 }
10753
10754 s.save(m_worldVersion);
10755 }
10756
10757 // Sparse payloads live outside chunk columns and need their own snapshot section.
10758 save_sparse_component_stores(s);
10759
10760 // Non-fragmenting exclusive relation edges.
10761 {
10762 uint32_t edgeCnt = 0;
10763 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
10764 (void)relKey;
10765 edgeCnt += store.source_count();
10766 }
10767 s.save(edgeCnt);
10768
10769 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
10770 const auto relation = relKey.entity();
10771 cnt::darray<EntityId> sourceIds;
10772 cnt::darray<Entity> pairSources;
10773 store.collect_source_ids(sourceIds);
10774 store.collect_pair_sources(pairSources);
10775 for (auto sourceId: sourceIds) {
10776 const auto source = get(sourceId);
10777 GAIA_ASSERT(valid(source));
10778 const auto target = store.target(source);
10779 GAIA_ASSERT(target != EntityBad);
10780 s.save(source);
10781 s.save(relation);
10782 s.save(target);
10783 }
10784 for (auto source: pairSources) {
10785 GAIA_ASSERT(valid(source));
10786 const auto target = store.target(source);
10787 GAIA_ASSERT(target != EntityBad);
10788 s.save(source);
10789 s.save(relation);
10790 s.save(target);
10791 }
10792 }
10793 }
10794
10795 // Entity names
10796 {
10797 s.save((uint32_t)m_nameToEntity.size());
10798 for (const auto& pair: m_nameToEntity) {
10799 s.save(pair.second);
10800 const bool isOwnedStr = pair.first.owned();
10801 s.save(isOwnedStr);
10802
10803 // For owner string we copy the entire string into the buffer
10804 if (isOwnedStr) {
10805 const auto* str = pair.first.str();
10806 const uint32_t len = pair.first.len();
10807 s.save(len);
10808 s.save_raw(str, len, ser::serialization_type_id::c8);
10809 }
10810 // Non-owned strings will only store the pointer.
10811 // However, if it is a component, we do not store anything at all because we can reconstruct
10812 // the name from our component cache.
10813 else if (!pair.second.comp()) {
10814 const auto* str = pair.first.str();
10815 const uint32_t len = pair.first.len();
10816 s.save(len);
10817 const auto ptr_val = (uint64_t)str;
10818 s.save_raw(&ptr_val, sizeof(ptr_val), ser::serialization_type_id::u64);
10819 }
10820 }
10821 }
10822
10823 // Entity aliases
10824 {
10825 uint32_t aliasCnt = 0;
10826 GAIA_FOR((uint32_t)m_recs.entities.size()) {
10827 const auto entity = get((EntityId)i);
10828 if (!valid(entity) || entity.pair())
10829 continue;
10830
10831 const auto& ec = m_recs.entities[i];
10832 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
10833 if (compIdx == BadIndex)
10834 continue;
10835
10836 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
10837 if (pDesc->alias != nullptr)
10838 ++aliasCnt;
10839 }
10840
10841 s.save(aliasCnt);
10842 GAIA_FOR((uint32_t)m_recs.entities.size()) {
10843 const auto entity = get((EntityId)i);
10844 if (!valid(entity) || entity.pair())
10845 continue;
10846
10847 const auto& ec = m_recs.entities[i];
10848 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
10849 if (compIdx == BadIndex)
10850 continue;
10851
10852 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
10853 if (pDesc->alias == nullptr)
10854 continue;
10855
10856 s.save(entity);
10857 s.save(pDesc->alias_len);
10858 s.save_raw(pDesc->alias, pDesc->alias_len, ser::serialization_type_id::c8);
10859 }
10860 }
10861 }
10862
10863 public:
10870 void save() {
10871 auto s = m_serializer;
10872 GAIA_ASSERT(s.valid());
10873
10874 s.reset();
10875 save_to(s);
10876 }
10877
10878#if GAIA_JSON_ENABLED
10886 bool save_json(
10887 ser::ser_json& writer, ser::JsonSaveFlags flags = ser::JsonSaveFlags::Default,
10888 const ser::RuntimeJsonPolicy& policy = {}) const;
10889
10894 ser::json_str save_json(bool& ok, ser::JsonSaveFlags flags = ser::JsonSaveFlags::Default) const;
10895
10898 uint64_t runtime_schema_hash() const;
10899
10903 bool save_runtime_schema_json(ser::ser_json& writer) const;
10904
10907 ser::json_str save_runtime_schema_json() const;
10908
10918 bool patch_comp_json(
10919 Entity entity, Entity component, ser::json_str_view pointer, ser::json_str_view value,
10920 ser::JsonDiagnostics& diagnostics, const ser::RuntimeJsonPolicy& policy = {},
10921 uint64_t expectedRuntimeSchemaHash = 0);
10922
10929 bool load_json(
10930 const char* json, uint32_t len, ser::JsonDiagnostics& diagnostics, const ser::RuntimeJsonPolicy& policy = {});
10935 bool load_json(const char* json, uint32_t len);
10936
10942 bool
10943 load_json(ser::json_str_view json, ser::JsonDiagnostics& diagnostics, const ser::RuntimeJsonPolicy& policy = {});
10944
10948 bool load_json(ser::json_str_view json);
10949#endif
10950
10962 bool load(ser::serializer inputSerializer = {}) {
10963 auto s = inputSerializer.valid() ? inputSerializer : m_serializer;
10964 GAIA_ASSERT(s.valid());
10965
10966 // Move back to the beginning of the stream
10967 s.seek(0);
10968
10969 // Snapshot format version.
10970 uint32_t version = 0;
10971 s.load(version);
10972 if (version < 2 || version > WorldSerializerVersion) {
10973 GAIA_LOG_E("Unsupported world version %u. Expected 2..%u.", version, WorldSerializerVersion);
10974 return false;
10975 }
10976
10977 // Store the index of the last core component. As they change, we will have to modify entity ids accordingly.
10978 uint32_t lastCoreComponentId = 0;
10979 s.load(lastCoreComponentId);
10980
10981 // Append-only core ids are handled via load-time entity remapping.
10982 // Snapshots from a runtime with a larger core-id boundary are not supported.
10983 const auto currLastCoreComponentId = GAIA_ID(LastCoreComponent).id();
10984 if (lastCoreComponentId > currLastCoreComponentId) {
10985 GAIA_LOG_E(
10986 "Unsupported world core boundary %u. Current runtime supports up to %u.", lastCoreComponentId,
10987 currLastCoreComponentId);
10988 return false;
10989 }
10990 if (version >= 6 && !validate_component_layout(s, lastCoreComponentId, currLastCoreComponentId))
10991 return false;
10992 // Install the append-only core-id remap for nested Entity::load() calls.
10993 // This keeps the serializer API unchanged, at the cost of relying on
10994 // scoped thread-local state instead of explicit serializer-local context.
10995 const detail::EntityLoadRemapGuard entityLoadRemapGuard(
10996 lastCoreComponentId, currLastCoreComponentId, version >= 5);
10997 auto remapLoadedEntityId = [&](uint32_t id) {
10998 return detail::remap_loaded_entity_id(id, lastCoreComponentId, currLastCoreComponentId);
10999 };
11000
11001 // Entities
11002 {
11003 auto loadEntityContainer = [&](EntityContainer& ec) {
11004 s.load(ec.idx);
11005 s.load(ec.dataRaw);
11006 s.load(ec.row);
11007 s.load(ec.flags);
11008 if ((ec.flags & EntityContainerFlags::HasCantCombine) != 0)
11009 m_hasCantCombinePolicy = true;
11010 if ((ec.flags & (EntityContainerFlags::OnDeleteTarget_Delete | EntityContainerFlags::OnDeleteTarget_Remove |
11011 EntityContainerFlags::OnDeleteTarget_Error)) != 0) {
11012 m_hasOnDeleteTargetPolicy = true;
11013 }
11014 ec.flags |= EntityContainerFlags::Load;
11015
11016 ec.idx = remapLoadedEntityId(ec.idx);
11017 if (ec.data.pair != 0)
11018 ec.data.gen = remapLoadedEntityId(ec.data.gen);
11019
11020#if GAIA_USE_SAFE_ENTITY
11021 s.load(ec.refCnt);
11022#else
11023 s.load(ec.unused);
11024 // if this value is different from zero, it means we are trying to load data
11025 // that was previously saved with GAIA_USE_SAFE_ENTITY. It's probably not a good idea
11026 // because if your program used reference counting it probably won't work correctly.
11027 GAIA_ASSERT(ec.unused == 0);
11028#endif
11029 // Store the archetype idx inside the pointer. We will decode this once archetypes are created.
11030 uint32_t archetypeIdx = 0;
11031 s.load(archetypeIdx);
11032 ec.pArchetype = (Archetype*)((uintptr_t)archetypeIdx);
11033 // Store the chunk idx inside the pointer. We will decode this once chunks are created.
11034 uint32_t chunkIdx = 0;
11035 s.load(chunkIdx);
11036 ec.pChunk = (Chunk*)((uintptr_t)chunkIdx);
11037 };
11038
11039 uint32_t newEntities = 0;
11040 s.load(newEntities);
11041 GAIA_FOR(newEntities) {
11042 bool isAlive = false;
11043 s.load(isAlive);
11044 if (isAlive) {
11045 EntityContainer ec{};
11046 loadEntityContainer(ec);
11047 m_recs.entities.add_live(GAIA_MOV(ec));
11048 } else {
11049 Identifier id = IdentifierBad;
11050 uint32_t nextFreeIdx = Entity::IdMask;
11051 s.load(id);
11052 s.load(nextFreeIdx);
11053 auto entity = Entity(id);
11054 entity = detail::remap_loaded_entity(entity, lastCoreComponentId, currLastCoreComponentId);
11055 nextFreeIdx = remapLoadedEntityId(nextFreeIdx);
11056 GAIA_ASSERT(entity.id() == remapLoadedEntityId(lastCoreComponentId + i));
11057 m_recs.entities.add_free(entity, nextFreeIdx);
11058 }
11059 }
11060
11061 uint32_t pairsCnt = 0;
11062 s.load(pairsCnt);
11063 GAIA_FOR(pairsCnt) {
11064 EntityContainer ec{};
11065 loadEntityContainer(ec);
11066 // Pair record containers identify their chunk row as an entity. Rebuild the canonical
11067 // pair key from its live endpoints so the lookup key keeps the entity bit clear.
11068 const Entity pair = Pair(m_recs.entities.handle(ec.idx), m_recs.entities.handle(ec.data.gen));
11069 [[maybe_unused]] const bool added = m_recs.pair_record_try_add(pair, GAIA_MOV(ec));
11070 GAIA_ASSERT(added);
11071 }
11072
11073 s.load(m_recs.entities.m_nextFreeIdx);
11074 m_recs.entities.m_nextFreeIdx = remapLoadedEntityId(m_recs.entities.m_nextFreeIdx);
11075 s.load(m_recs.entities.m_freeItems);
11076 }
11077
11078 // World
11079 {
11080 uint32_t archetypesSize = 0;
11081 s.load(archetypesSize);
11082 m_archetypes.reserve(archetypesSize);
11083 GAIA_FOR(archetypesSize) {
11084 uint32_t idsSize = 0;
11085 s.load(idsSize);
11086 Entity ids[ChunkHeader::MAX_COMPONENTS];
11087 GAIA_FOR_(idsSize, j) {
11088 s.load(ids[j]);
11089 }
11090
11091 // Calculate the lookup hash
11092 const auto hashLookup = calc_lookup_hash({&ids[0], idsSize}).hash;
11093
11094 auto* pArchetype = find_archetype({hashLookup}, {&ids[0], idsSize});
11095 if (pArchetype == nullptr) {
11096 // Create the archetype
11097 pArchetype = create_archetype({&ids[0], idsSize});
11098 pArchetype->set_hashes({hashLookup});
11099
11100 // No need to do anything with the archetype graph. It will build itself naturally.
11101 // pArchetype->build_graph_edges(pArchetypeRight, entity);
11102
11103 // Register the archetype in the world
11104 reg_archetype(pArchetype);
11105 }
11106
11107 // Load archetype data
11108 pArchetype->load(s);
11109 }
11110
11111 s.load(m_worldVersion);
11112 }
11113
11114 if (version >= 5 && !load_sparse_component_stores(s))
11115 return false;
11116
11117 // Update entity records.
11118 // We previously encoded the archetype id into refCnt.
11119 // Now we need to convert it back to the pointer.
11120 {
11121 for (auto& ec: m_recs.entities) {
11122 if ((ec.flags & EntityContainerFlags::Load) == 0)
11123 continue;
11124 ec.flags &= (EntityContainerFlagsType)(~(
11125 EntityContainerFlagsType)EntityContainerFlags::Load); // Clear the load flag
11126
11127 load_record_location(ec, EntityContainer::handle(ec));
11128 }
11129
11130 for (auto it = m_recs.pair_record_begin(); it != m_recs.pair_record_end(); ++it) {
11131 auto& pair = *it;
11132 auto& ec = pair.second;
11133
11134 // Core pairs remain in-world during load and were not serialized into the stream.
11135 if ((ec.flags & EntityContainerFlags::Load) == 0)
11136 continue;
11137
11138 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) != 0);
11139 ec.flags &= (EntityContainerFlagsType)(~(
11140 EntityContainerFlagsType)EntityContainerFlags::Load); // Clear the load flag
11141
11142 load_record_location(ec, pair.first.entity());
11143 }
11144 }
11145
11146 if (version < WorldSerializerVersion) {
11147 for (const auto& [entityId, pItem]: m_compCache.m_compByEntityId) {
11148 (void)entityId;
11149 GAIA_ASSERT(pItem != nullptr);
11150 auto comp = pItem->comp;
11151 comp.data.id = pItem->entity.id();
11152 sync_component_record(pItem->entity, comp);
11153 }
11154 }
11155
11156 if (version >= 4) {
11157 uint32_t edgeCnt = 0;
11158 s.load(edgeCnt);
11159 GAIA_FOR(edgeCnt) {
11160 Entity source;
11161 Entity relation;
11162 Entity target;
11163 s.load(source);
11164 s.load(relation);
11165 s.load(target);
11166
11167 if (!valid(source) || !valid(relation) || !valid(target) ||
11168 !relation_uses_non_fragmenting_storage(relation))
11169 continue;
11170
11171 assign_pair(Pair(relation, target), *m_pEntityArchetype);
11172 nonfragmenting_relation_set(source, relation, target);
11173 }
11174 }
11175
11176#if GAIA_ASSERT_ENABLED
11177 for (const auto& ec: m_recs.entities) {
11178 GAIA_ASSERT(ec.idx < m_recs.entities.size());
11179 GAIA_ASSERT(m_recs.entities.handle(ec.idx) == EntityContainer::handle(ec));
11180 // Deleted entities waiting for their record to be freed have no storage
11181 if ((ec.flags & EntityContainerFlags::DeleteRequested) != 0)
11182 continue;
11183 GAIA_ASSERT(ec.pArchetype != nullptr);
11184 GAIA_ASSERT(ec.pChunk != nullptr);
11185 GAIA_ASSERT(ec.pEntity != nullptr);
11186 }
11187#endif
11188 // Entity names
11189 {
11190 m_nameToEntity = {};
11191 uint32_t cnt = 0;
11192 s.load(cnt);
11193 GAIA_FOR(cnt) {
11194 Entity entity;
11195 s.load(entity);
11196 // entity.data.gen = 0; // Reset generation to zero
11197
11198 const auto& ec = fetch(entity);
11199 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
11200 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
11201 GAIA_ASSERT(core::check_alignment(pDesc));
11202
11203 bool isOwned = false;
11204 s.load(isOwned);
11205 if (!isOwned) {
11206 if (entity.comp()) {
11207 // Make components point back to their component cache record because if we save the world and load
11208 // it back in runtime, EntityDesc would still point to the old pointers to component names.
11209 const auto& ci = comp_cache().get(entity);
11210 const auto symbol = ci.symbol_name();
11211 pDesc->name = symbol.data();
11212 // Length should still be the same. Only the pointer has changed.
11213 GAIA_ASSERT(pDesc->name_len == symbol.size());
11214 m_nameToEntity.try_emplace(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0), entity);
11215 } else {
11216 uint32_t len = 0;
11217 s.load(len);
11218 uint64_t ptr_val = 0;
11219 s.load_raw(&ptr_val, sizeof(ptr_val), ser::serialization_type_id::u64);
11220
11221 // Simply point to whereever the original pointer pointed to
11222 pDesc->name = (const char*)ptr_val;
11223 pDesc->name_len = len;
11224 m_nameToEntity.try_emplace(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0), entity);
11225 }
11226
11227 continue;
11228 }
11229
11230 uint32_t len = 0;
11231 s.load(len);
11232
11233 // Get a pointer to where the string begins and seek to the end of the string
11234 const char* entityStr = (const char*)(s.data() + s.tell());
11235 s.seek(s.tell() + len);
11236
11237 // Make sure EntityDesc does not point anywhere right now.
11238 {
11239 pDesc->name = nullptr;
11240 pDesc->name_len = 0;
11241 }
11242
11243 // Name the entity using an owned string
11244 name(entity, entityStr, len);
11245 }
11246 }
11247
11248 // Entity aliases
11249 {
11250 m_aliasToEntity = {};
11251 for (auto& ec: m_recs.entities) {
11252 const auto entity = EntityContainer::handle(ec);
11253 // Deleted entities waiting for their record to be freed have no storage
11254 if (entity.pair() || ec.pChunk == nullptr)
11255 continue;
11256
11257 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
11258 if (compIdx == BadIndex)
11259 continue;
11260
11261 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
11262 GAIA_ASSERT(core::check_alignment(pDesc));
11263 pDesc->alias = nullptr;
11264 pDesc->alias_len = 0;
11265 }
11266
11267 uint32_t cnt = 0;
11268 s.load(cnt);
11269 GAIA_FOR(cnt) {
11270 Entity entity;
11271 s.load(entity);
11272
11273 const auto& ec = fetch(entity);
11274 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
11275 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
11276 GAIA_ASSERT(core::check_alignment(pDesc));
11277
11278 uint32_t len = 0;
11279 s.load(len);
11280
11281 // Get a pointer to where the string begins and seek to the end of the string
11282 const char* aliasStr = (const char*)(s.data() + s.tell());
11283 s.seek(s.tell() + len);
11284
11285 pDesc->alias = nullptr;
11286 pDesc->alias_len = 0;
11287 alias(entity, aliasStr, len);
11288 }
11289 }
11290
11291 return true;
11292 }
11293
11297 template <typename TSerializer>
11298 bool load(TSerializer& inputSerializer) {
11299 return load(ser::make_serializer(inputSerializer));
11300 }
11301
11302 private:
11304 void sort_archetypes() {
11305 struct sort_cond {
11306 bool operator()(const Archetype* a, const Archetype* b) const {
11307 return a->id() < b->id();
11308 }
11309 };
11310
11311 core::sort(m_archetypes, sort_cond{}, [&](uint32_t left, uint32_t right) {
11312 Archetype* tmp = m_archetypes[left];
11313
11314 m_archetypes[right]->list_idx(left);
11315 m_archetypes[left]->list_idx(right);
11316
11317 m_archetypes.data()[left] = (Archetype*)m_archetypes[right];
11318 m_archetypes.data()[right] = tmp;
11319 });
11320 }
11321
11322#if GAIA_USE_SAFE_ENTITY
11325 class CompDtorScope {
11326 World& m_world;
11327
11328 public:
11329 explicit CompDtorScope(World& world): m_world(world) {
11330 ++m_world.m_compDtorDepth;
11331 }
11332 ~CompDtorScope() {
11333 --m_world.m_compDtorDepth;
11334 }
11335 CompDtorScope(const CompDtorScope&) = delete;
11336 CompDtorScope& operator=(const CompDtorScope&) = delete;
11337 };
11338#endif
11339
11343 void remove_chunk(Archetype& archetype, Chunk& chunk) {
11344#if GAIA_USE_SAFE_ENTITY
11345 CompDtorScope dtorScope(*this);
11346#endif
11347 archetype.del(&chunk);
11348 try_enqueue_archetype_for_deletion(archetype);
11349 }
11350
11353 void remove_chunk_from_delete_queue(uint32_t idx) {
11354 GAIA_ASSERT(idx < m_chunksToDel.size());
11355
11356 auto* pChunk = m_chunksToDel[idx].pChunk;
11357 pChunk->clear_delete_queue_index();
11358
11359 const auto lastIdx = (uint32_t)m_chunksToDel.size() - 1;
11360 if (idx != lastIdx) {
11361 auto* pMovedChunk = m_chunksToDel[lastIdx].pChunk;
11362 pMovedChunk->delete_queue_index(idx);
11363 }
11364
11365 core::swap_erase(m_chunksToDel, idx);
11366 }
11367
11372 void remove_entity(Archetype& archetype, Chunk& chunk, uint16_t row) {
11373#if GAIA_USE_SAFE_ENTITY
11374 CompDtorScope dtorScope(*this);
11375#endif
11376 archetype.remove_entity(chunk, row, m_recs);
11377 try_enqueue_chunk_for_deletion(archetype, chunk);
11378 }
11379
11381 void del_empty_chunks() {
11382 GAIA_PROF_SCOPE(World::del_empty_chunks);
11383
11384 for (uint32_t i = 0; i < m_chunksToDel.size();) {
11385 auto* pArchetype = m_chunksToDel[i].pArchetype;
11386 auto* pChunk = m_chunksToDel[i].pChunk;
11387
11388 // Revive reclaimed chunks
11389 if (!pChunk->empty()) {
11390 pChunk->revive();
11391 revive_archetype(*pArchetype);
11392 remove_chunk_from_delete_queue(i);
11393 continue;
11394 }
11395
11396 // Skip chunks which still have some lifespan left
11397 if (pChunk->progress_death()) {
11398 ++i;
11399 continue;
11400 }
11401
11402 // Delete unused chunks that are past their lifespan
11403 remove_chunk_from_delete_queue(i);
11404 remove_chunk(*pArchetype, *pChunk);
11405 }
11406 }
11407
11410 void del_empty_archetype(Archetype* pArchetype) {
11411 GAIA_PROF_SCOPE(World::del_empty_archetype);
11412
11413 GAIA_ASSERT(pArchetype != nullptr);
11414 GAIA_ASSERT(pArchetype->empty() || pArchetype->is_req_del());
11415 GAIA_ASSERT(!pArchetype->dying() || pArchetype->is_req_del());
11416
11417 unreg_archetype(pArchetype);
11418 for (auto& ec: m_recs.entities) {
11419 if (ec.pArchetype != pArchetype)
11420 continue;
11421
11422 ec.pArchetype = nullptr;
11423 ec.pChunk = nullptr;
11424 ec.pEntity = nullptr;
11425 }
11426 for (auto it = m_recs.pair_record_begin(); it != m_recs.pair_record_end(); ++it) {
11427 auto& ec = it->second;
11428 if (ec.pArchetype != pArchetype)
11429 continue;
11430
11431 ec.pArchetype = nullptr;
11432 ec.pChunk = nullptr;
11433 ec.pEntity = nullptr;
11434 }
11435 Archetype::destroy(pArchetype);
11436 }
11437
11439 void del_empty_archetypes() {
11440 GAIA_PROF_SCOPE(World::del_empty_archetypes);
11441
11442 cnt::sarray_ext<Archetype*, 512> tmp;
11443
11444 // Remove all dead archetypes from query caches.
11445 // Because the number of cached queries is way higher than the number of archetypes
11446 // we want to remove, we flip the logic around and iterate over all query caches
11447 // and match against our lists.
11448 // Note, all archetype pointers in the tmp array are invalid at this point and can
11449 // be used only for comparison. They can't be dereferenced.
11450 auto remove_from_queries = [&]() {
11451 if (tmp.empty())
11452 return;
11453
11454 for (auto* pArchetype: tmp) {
11455 m_queryCache.remove_archetype_from_queries(pArchetype);
11456 del_empty_archetype(pArchetype);
11457 }
11458 tmp.clear();
11459 };
11460
11461 for (uint32_t i = 0; i < m_archetypesToDel.size();) {
11462 auto* pArchetype = m_archetypesToDel[i];
11463
11464 // Skip reclaimed archetypes or archetypes that became immortal
11465 if (!pArchetype->empty() || pArchetype->max_lifespan() == 0) {
11466 revive_archetype(*pArchetype);
11467 core::swap_erase(m_archetypesToDel, i);
11468 continue;
11469 }
11470
11471 // Skip archetypes which still have some lifespan left unless
11472 // they are force-deleted.
11473 if (!pArchetype->is_req_del() && pArchetype->progress_death()) {
11474 ++i;
11475 continue;
11476 }
11477
11478 tmp.push_back(pArchetype);
11479
11480 // Remove the unused archetypes
11481 core::swap_erase(m_archetypesToDel, i);
11482
11483 // Clear what we have once the capacity is reached
11484 if (tmp.size() == tmp.max_size())
11485 remove_from_queries();
11486 }
11487
11488 remove_from_queries();
11489 }
11490
11493 void revive_archetype(Archetype& archetype) {
11494 const bool wasReqDel = archetype.is_req_del();
11495 archetype.revive();
11496 if (wasReqDel)
11497 update_version(m_archetypeDeleteVersion);
11498 m_reqArchetypesToDel.erase(ArchetypeLookupKey(archetype.lookup_hash(), &archetype));
11499 }
11500
11504 void try_enqueue_chunk_for_deletion(Archetype& archetype, Chunk& chunk) {
11505 if (chunk.dying() || !chunk.empty())
11506 return;
11507
11508 // When the chunk is emptied we want it to be removed. We can't do it
11509 // rowB away and need to wait for world::gc() to be called.
11510 //
11511 // However, we need to prevent the following:
11512 // 1) chunk is emptied, add it to some removal list
11513 // 2) chunk is reclaimed
11514 // 3) chunk is emptied, add it to some removal list again
11515 //
11516 // Therefore, we have a flag telling us the chunk is already waiting to
11517 // be removed. The chunk might be reclaimed before garbage collection happens
11518 // but it simply ignores such requests. This way we always have at most one
11519 // record for removal for any given chunk.
11520 chunk.start_dying();
11521
11522 m_chunksToDel.push_back({&archetype, &chunk});
11523 chunk.delete_queue_index((uint32_t)m_chunksToDel.size() - 1);
11524 }
11525
11528 void try_enqueue_archetype_for_deletion(Archetype& archetype) {
11529 if (!archetype.ready_to_die())
11530 return;
11531
11532 // When the chunk is emptied we want it to be removed. We can't do it
11533 // rowB away and need to wait for world::gc() to be called.
11534 //
11535 // However, we need to prevent the following:
11536 // 1) archetype is emptied, add it to some removal list
11537 // 2) archetype is reclaimed
11538 // 3) archetype is emptied, add it to some removal list again
11539 //
11540 // Therefore, we have a flag telling us the chunk is already waiting to
11541 // be removed. The archetype might be reclaimed before garbage collection happens
11542 // but it simply ignores such requests. This way we always have at most one
11543 // record for removal for any given chunk.
11544 archetype.start_dying();
11545
11546 m_archetypesToDel.push_back(&archetype);
11547 }
11548
11551 void defrag_chunks(uint32_t maxEntities) {
11552 GAIA_PROF_SCOPE(World::defrag_chunks);
11553
11554 const auto maxIters = m_archetypes.size();
11555 // There has to be at least the root archetype present
11556 GAIA_ASSERT(maxIters > 0);
11557
11558 GAIA_FOR(maxIters) {
11559 const auto idx = (m_defragLastArchetypeIdx + 1) % maxIters;
11560 auto* pArchetype = m_archetypes[idx];
11561 defrag_archetype(*pArchetype, maxEntities);
11562 if (maxEntities == 0)
11563 return;
11564
11565 m_defragLastArchetypeIdx = idx;
11566 }
11567 }
11568
11572 void defrag_archetype(Archetype& archetype, uint32_t& maxEntities) {
11573 // Assuming the following chunk layout:
11574 // Chunk_1: 10/10
11575 // Chunk_2: 1/10
11576 // Chunk_3: 7/10
11577 // Chunk_4: 10/10
11578 // Chunk_5: 9/10
11579 // After full defragmentation we end up with:
11580 // Chunk_1: 10/10
11581 // Chunk_2: 10/10 (7 entities from Chunk_3 + 2 entities from Chunk_5)
11582 // Chunk_3: 0/10 (empty, ready for removal)
11583 // Chunk_4: 10/10
11584 // Chunk_5: 7/10
11585 // TODO: Implement mask of semi-full chunks so we can pick one easily when searching
11586 // for a chunk to fill with a new entity and when defragmenting.
11587 // NOTE 1:
11588 // Even though entity movement might be present during defragmentation, we do
11589 // not update the world version here because no real structural changes happen.
11590 // All entities and components remain intact, they just move to a different place.
11591
11592 if (maxEntities == 0)
11593 return;
11594
11595 const auto& chunks = archetype.chunks();
11596 if (chunks.size() < 2)
11597 return;
11598
11599 uint32_t front = 0;
11600 uint32_t back = chunks.size() - 1;
11601
11602 auto* pDstChunk = chunks[front];
11603 auto* pSrcChunk = chunks[back];
11604
11605 // Find the first semi-full chunk in the front
11606 while (front < back && (pDstChunk->full() || !pDstChunk->is_semi()))
11607 pDstChunk = chunks[++front];
11608 // Find the last semi-full chunk in the back
11609 while (front < back && (pSrcChunk->empty() || !pSrcChunk->is_semi()))
11610 pSrcChunk = chunks[--back];
11611
11612 // Find the first semi-empty chunk in the back
11613 while (front < back) {
11614 pDstChunk = chunks[front];
11615 pSrcChunk = chunks[back];
11616
11617 const uint32_t entitiesInSrcChunk = pSrcChunk->size();
11618 const uint32_t spaceInDstChunk = pDstChunk->capacity() - pDstChunk->size();
11619 const uint32_t entitiesToMoveSrc = core::get_min(entitiesInSrcChunk, maxEntities);
11620 const uint32_t entitiesToMove = core::get_min(entitiesToMoveSrc, spaceInDstChunk);
11621
11622 GAIA_FOR(entitiesToMove) {
11623 const auto lastSrcEntityIdx = entitiesInSrcChunk - i - 1;
11624 const auto entity = pSrcChunk->entity_view()[lastSrcEntityIdx];
11625
11626 auto& ec = m_recs[entity];
11627
11628 const auto srcRow = ec.row;
11629 const auto dstRow = pDstChunk->add_entity(entity);
11630 const bool wasEnabled = !ec.data.dis;
11631
11632 // Make sure the old entity becomes enabled now
11633 archetype.enable_entity(pSrcChunk, srcRow, true, m_recs);
11634 // We go back-to-front in the chunk so enabling the entity is not expected to change its row
11635 GAIA_ASSERT(srcRow == ec.row);
11636
11637 // Move data from the old chunk to the new one
11638 pDstChunk->move_entity_data(entity, dstRow, m_recs);
11639
11640 // Remove the entity record from the old chunk.
11641 // Normally we'd call remove_entity but we don't want to trigger world
11642 // version updated all the time. It's enough to do it just once at the
11643 // end of defragmentation.
11644 // remove_entity(archetype, *pSrcChunk, srcRow);
11645 archetype.remove_entity_raw(*pSrcChunk, srcRow, m_recs);
11646 try_enqueue_chunk_for_deletion(archetype, *pSrcChunk);
11647
11648 // Bring the entity container record up-to-date
11649 ec.pChunk = pDstChunk;
11650 ec.row = (uint16_t)dstRow;
11651 ec.pEntity = &pDstChunk->entity_view()[dstRow];
11652
11653 // Transfer the original enabled state to the new chunk
11654 archetype.enable_entity(pDstChunk, dstRow, wasEnabled, m_recs);
11655 }
11656
11657 // Update world versions
11658 if (entitiesToMove > 0) {
11659 pSrcChunk->update_world_version();
11660 pDstChunk->update_world_version();
11661 pSrcChunk->update_entity_order_version();
11662 pDstChunk->update_entity_order_version();
11663 update_version(m_worldVersion);
11664 }
11665
11666 maxEntities -= entitiesToMove;
11667 if (maxEntities == 0)
11668 return;
11669
11670 // The source is empty, find another semi-empty source
11671 if (pSrcChunk->empty()) {
11672 while (front < back) {
11673 if (chunks[--back]->is_semi())
11674 break;
11675 }
11676 }
11677
11678 // The destination chunk is full, we need to move to the next one.
11679 // The idea is to fill the destination as much as possible.
11680 while (front < back && pDstChunk->full())
11681 pDstChunk = chunks[++front];
11682 }
11683 }
11684
11689 GAIA_NODISCARD Archetype* find_archetype(Archetype::LookupHash hashLookup, EntitySpan ids) {
11690 auto tmpArchetype = ArchetypeLookupChecker(ids);
11691 ArchetypeLookupKey key(hashLookup, &tmpArchetype);
11692
11693 // Search for the archetype in the map
11694 const auto it = m_archetypesByHash.find(key);
11695 if (it == m_archetypesByHash.end())
11696 return nullptr;
11697
11698 auto* pArchetype = it->second;
11699 return pArchetype;
11700 }
11701
11702 GAIA_NODISCARD static auto
11707 find_component_index_record(ComponentIndexEntryArray& records, const Archetype* pArchetype) {
11708 return core::get_index_if(records, [&](const auto& record) {
11709 return record.matches(pArchetype);
11710 });
11711 }
11712
11713 GAIA_NODISCARD static auto
11718 find_component_index_record(const ComponentIndexEntryArray& records, const Archetype* pArchetype) {
11719 return core::get_index_if(records, [&](const auto& record) {
11720 return record.matches(pArchetype);
11721 });
11722 }
11723
11727 void update_entity_archetype_lookup_revision(EntityLookupKey entityKey) {
11728 auto [it, _] = m_entityToArchetypeMapVersions.try_emplace(entityKey, 0);
11729 (void)_;
11730 ++it->second;
11731 if (it->second == 0)
11732 it->second = 1;
11733 }
11734
11741 void add_entity_archetype_pair(
11742 Entity entity, Archetype* pArchetype, uint16_t compIdx = ComponentIndexBad, uint16_t matchCount = 1) {
11743 GAIA_ASSERT(pArchetype != nullptr);
11744 GAIA_ASSERT(matchCount > 0);
11745
11746 EntityLookupKey entityKey(entity);
11747 auto prepared = m_entityToArchetypeMap.prepare_insert(entityKey);
11748 if (!prepared.found()) {
11749 ComponentIndexEntryArray records;
11750 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
11751 m_entityToArchetypeMap.emplace_prepared(prepared, entityKey, GAIA_MOV(records));
11752 return;
11753 }
11754
11755 auto it = m_entityToArchetypeMap.prepared_iterator(prepared);
11756 auto& records = it->second;
11757 const auto idx = find_component_index_record(records, pArchetype);
11758 if (idx == BadIndex) {
11759 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
11760 return;
11761 }
11762
11763 auto& record = records[idx];
11764 record.matchCount = (uint16_t)(record.matchCount + matchCount);
11765 if (compIdx != ComponentIndexBad)
11766 record.compIdx = compIdx;
11767 }
11768
11776 void add_new_entity_archetype_pair(
11777 Entity entity, Archetype* pArchetype, uint16_t compIdx = ComponentIndexBad, uint16_t matchCount = 1) {
11778 GAIA_ASSERT(pArchetype != nullptr);
11779 GAIA_ASSERT(matchCount > 0);
11780
11781 EntityLookupKey entityKey(entity);
11782 auto prepared = m_entityToArchetypeMap.prepare_insert(entityKey);
11783 if (!prepared.found()) {
11784 ComponentIndexEntryArray records;
11785 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
11786 m_entityToArchetypeMap.emplace_prepared(prepared, entityKey, GAIA_MOV(records));
11787 return;
11788 }
11789
11790 auto it = m_entityToArchetypeMap.prepared_iterator(prepared);
11791 auto& records = it->second;
11792 if (!records.empty()) {
11793 auto& record = records.back();
11794 if (record.matches(pArchetype)) {
11795 record.matchCount = (uint16_t)(record.matchCount + matchCount);
11796 if (compIdx != ComponentIndexBad)
11797 record.compIdx = compIdx;
11798 return;
11799 }
11800 }
11801
11802 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
11803 }
11804
11809 void add_pair_archetype_query_pairs(Entity pair, Archetype* pArchetype, uint16_t matchCount = 1) {
11810 GAIA_ASSERT(pair.pair());
11811 GAIA_ASSERT(pArchetype != nullptr);
11812 GAIA_ASSERT(matchCount > 0);
11813
11814 const auto first = get(pair.id());
11815 const auto second = get(pair.gen());
11816
11817 add_entity_archetype_pair(Pair(All, second), pArchetype, ComponentIndexBad, matchCount);
11818 add_entity_archetype_pair(Pair(first, All), pArchetype, ComponentIndexBad, matchCount);
11819 add_entity_archetype_pair(Pair(All, All), pArchetype, ComponentIndexBad, matchCount);
11820 }
11821
11826 void add_new_pair_archetype_query_pairs(Entity pair, Archetype* pArchetype, uint16_t matchCount = 1) {
11827 GAIA_ASSERT(pair.pair());
11828 GAIA_ASSERT(pArchetype != nullptr);
11829 GAIA_ASSERT(matchCount > 0);
11830
11831 const auto first = get(pair.id());
11832 const auto second = get(pair.gen());
11833
11834 add_new_entity_archetype_pair(Pair(All, second), pArchetype, ComponentIndexBad, matchCount);
11835 add_new_entity_archetype_pair(Pair(first, All), pArchetype, ComponentIndexBad, matchCount);
11836 add_new_entity_archetype_pair(Pair(All, All), pArchetype, ComponentIndexBad, matchCount);
11837 }
11838
11842 void del_entity_query_pair(Pair pair, Entity entityToRemove) {
11843 const auto entityKey = EntityLookupKey(pair);
11844 auto it = m_entityToArchetypeMap.find(entityKey);
11845 if (it == m_entityToArchetypeMap.end())
11846 return;
11847 auto& records = it->second;
11848 bool changed = false;
11849
11850 // Remove any reference to the found archetype from the array.
11851 // We don't know the archetype so we remove/decrement any archetype record that contains our entity.
11852 for (uint32_t i = records.size() - 1; i != (uint32_t)-1; --i) {
11853 auto& record = records[i];
11854 const auto* pArchetype = record.pArchetype;
11855 if (!pArchetype->has(entityToRemove))
11856 continue;
11857
11858 if ((!is_wildcard(pair.first()) && !is_wildcard(pair.second())) || record.matchCount <= 1)
11859 core::swap_erase_unsafe(records, i);
11860 else
11861 --record.matchCount;
11862 changed = true;
11863 }
11864
11865 if (changed)
11866 update_entity_archetype_lookup_revision(entityKey);
11867
11868 if (records.empty())
11869 m_entityToArchetypeMap.erase(it);
11870 }
11871
11876 void del_entity_query_pair(Pair pair, Archetype* pArchetypeToRemove) {
11877 GAIA_ASSERT(pArchetypeToRemove != nullptr);
11878
11879 const auto entityKey = EntityLookupKey(pair);
11880 auto it = m_entityToArchetypeMap.find(entityKey);
11881 if (it == m_entityToArchetypeMap.end())
11882 return;
11883
11884 auto& records = it->second;
11885 const auto idx = find_component_index_record(records, pArchetypeToRemove);
11886 if (idx != BadIndex) {
11887 core::swap_erase_unsafe(records, idx);
11888 update_entity_archetype_lookup_revision(entityKey);
11889 }
11890
11891 if (records.empty())
11892 m_entityToArchetypeMap.erase(it);
11893 }
11894
11898 void del_pair_archetype_query_pairs(Entity pair, Archetype* pArchetypeToRemove) {
11899 GAIA_ASSERT(pair.pair());
11900 GAIA_ASSERT(pArchetypeToRemove != nullptr);
11901
11902 GAIA_ASSERT(pair.id() < m_recs.entities.size());
11903 GAIA_ASSERT(pair.gen() < m_recs.entities.size());
11904 const auto first = m_recs.entities.handle(pair.id());
11905 const auto second = m_recs.entities.handle(pair.gen());
11906
11907 del_entity_query_pair(Pair(All, second), pArchetypeToRemove);
11908 del_entity_query_pair(Pair(first, All), pArchetypeToRemove);
11909 del_entity_query_pair(Pair(All, All), pArchetypeToRemove);
11910 }
11911
11915 void del_pair_archetype_query_pairs(Entity pair, Entity entityToRemove) {
11916 GAIA_ASSERT(pair.pair());
11917
11918 GAIA_ASSERT(pair.id() < m_recs.entities.size());
11919 GAIA_ASSERT(pair.gen() < m_recs.entities.size());
11920 const auto first = m_recs.entities.handle(pair.id());
11921 const auto second = m_recs.entities.handle(pair.gen());
11922
11923 del_entity_query_pair(Pair(All, second), entityToRemove);
11924 del_entity_query_pair(Pair(first, All), entityToRemove);
11925 del_entity_query_pair(Pair(All, All), entityToRemove);
11926 }
11927
11932 void del_entity_archetype_pair(Entity entity, Archetype* pArchetypeToRemove) {
11933 GAIA_ASSERT(entity != Pair(All, All));
11934 GAIA_ASSERT(pArchetypeToRemove != nullptr);
11935
11936 const auto entityKey = EntityLookupKey(entity);
11937 auto it = m_entityToArchetypeMap.find(entityKey);
11938 if (it == m_entityToArchetypeMap.end())
11939 return;
11940
11941 auto& records = it->second;
11942 const auto idx = find_component_index_record(records, pArchetypeToRemove);
11943 if (idx != BadIndex) {
11944 core::swap_erase_unsafe(records, idx);
11945 update_entity_archetype_lookup_revision(entityKey);
11946 }
11947
11948 if (records.empty())
11949 m_entityToArchetypeMap.erase(it);
11950 }
11951
11954 void del_archetype_entity_pairs(Archetype* pArchetype) {
11955 GAIA_ASSERT(pArchetype != nullptr);
11956
11957 for (const auto entity: pArchetype->ids_view()) {
11958 del_entity_archetype_pair(entity, pArchetype);
11959
11960 if (!entity.pair())
11961 continue;
11962
11963 // Archetype unregistration can run while the pair's relation or target entity is already
11964 // invalid. Rebuild wildcard pair lookup keys from the stored entity records instead of
11965 // calling get(), which asserts on invalidated ids.
11966 GAIA_ASSERT(entity.id() < m_recs.entities.size());
11967 GAIA_ASSERT(entity.gen() < m_recs.entities.size());
11968 del_pair_archetype_query_pairs(entity, pArchetype);
11969 }
11970 }
11971
11975 void del_entity_archetype_pairs(Entity entity, Archetype* pArchetype) {
11976 GAIA_ASSERT(entity != Pair(All, All));
11977
11978 const auto entityKey = EntityLookupKey(entity);
11979 if (m_entityToArchetypeMap.erase(entityKey) != 0)
11980 update_entity_archetype_lookup_revision(entityKey);
11981
11982 if (entity.pair()) {
11983 if (pArchetype != nullptr) {
11984 del_pair_archetype_query_pairs(entity, pArchetype);
11985 } else {
11986 del_pair_archetype_query_pairs(entity, entity);
11987 }
11988 }
11989 }
11990
11994 GAIA_NODISCARD Archetype* create_archetype(EntitySpan entities) {
11995 GAIA_ASSERT(m_nextArchetypeId < (decltype(m_nextArchetypeId))-1);
11996 auto* pArchetype = Archetype::create(*this, m_nextArchetypeId++, m_worldVersion, entities);
11997 pArchetype->set_max_lifespan(m_defaultMaxLifespan);
11998
11999 const auto entityCnt = (uint32_t)entities.size();
12000 GAIA_FOR(entityCnt) {
12001 auto entity = entities[i];
12002 add_new_entity_archetype_pair(entity, pArchetype, (uint16_t)i);
12003
12004#if GAIA_OBSERVERS_ENABLED
12005 auto& ec = fetch(entity);
12006 if ((ec.flags & EntityContainerFlags::IsObserved) != 0 || m_observers.has_observers(entity)) {
12007 ec.flags |= EntityContainerFlags::IsObserved;
12008 pArchetype->observed_terms_inc();
12009 }
12010#endif
12011
12012 // If the entity is a pair, make sure to create special wildcard records for it
12013 // as well so wildcard queries can find the archetype.
12014 if (entity.pair()) {
12015 add_new_pair_archetype_query_pairs(entity, pArchetype);
12016 }
12017 }
12018
12019 return pArchetype;
12020 }
12021
12024 void reg_archetype(Archetype* pArchetype) {
12025 GAIA_ASSERT(pArchetype != nullptr);
12026
12027 // // Make sure hashes were set already
12028 // GAIA_ASSERT(
12029 // (m_archetypesById.empty() || pArchetype == m_pRootArchetype) || (pArchetype->lookup_hash().hash != 0));
12030
12031 // Make sure the archetype is not registered yet
12032 GAIA_ASSERT(pArchetype->list_idx() == BadIndex);
12033
12034 // Register the archetype
12035 [[maybe_unused]] const auto it0 =
12036 m_archetypesById.emplace(ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash()), pArchetype);
12037 [[maybe_unused]] const auto it1 =
12038 m_archetypesByHash.emplace(ArchetypeLookupKey(pArchetype->lookup_hash(), pArchetype), pArchetype);
12039
12040 GAIA_ASSERT(it0.second);
12041 GAIA_ASSERT(it1.second);
12042
12043 pArchetype->list_idx(m_archetypes.size());
12044 m_archetypes.emplace_back(pArchetype);
12045
12046 m_queryCache.register_archetype_with_queries(pArchetype);
12047 }
12048
12051 void unreg_archetype(Archetype* pArchetype) {
12052 GAIA_ASSERT(pArchetype != nullptr);
12053
12054 // Make sure hashes were set already
12055 GAIA_ASSERT(
12056 (m_archetypesById.empty() || pArchetype == m_pRootArchetype) || (pArchetype->lookup_hash().hash != 0));
12057
12058 // Make sure the archetype was registered already
12059 GAIA_ASSERT(pArchetype->list_idx() != BadIndex);
12060
12061 // Query rematching uses the entity -> archetype lookup map as an input. Remove this
12062 // archetype from all of its lookup buckets before destroying it so dead archetype
12063 // pointers cannot be reintroduced into cached query state during the next rematch.
12064 del_archetype_entity_pairs(pArchetype);
12065
12066 // Break graph connections
12067 {
12068 auto& edgeLefts = pArchetype->left_edges();
12069 for (auto& itLeft: edgeLefts)
12070 remove_edge_from_archetype(pArchetype, itLeft.second, itLeft.first.entity());
12071 }
12072
12073 auto tmpArchetype = ArchetypeLookupChecker(pArchetype->ids_view());
12074 [[maybe_unused]] const auto res0 =
12075 m_archetypesById.erase(ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash()));
12076 [[maybe_unused]] const auto res1 =
12077 m_archetypesByHash.erase(ArchetypeLookupKey(pArchetype->lookup_hash(), &tmpArchetype));
12078 GAIA_ASSERT(res0 != 0);
12079 GAIA_ASSERT(res1 != 0);
12080
12081 const auto idx = pArchetype->list_idx();
12082 GAIA_ASSERT(idx == core::get_index(m_archetypes, pArchetype));
12083 core::swap_erase(m_archetypes, idx);
12084 update_entity_archetype_lookup_revision(EntityBadLookupKey);
12085 if (!m_archetypes.empty() && idx != m_archetypes.size())
12086 m_archetypes[idx]->list_idx(idx);
12087 }
12088
12089#if GAIA_ASSERT_ENABLED
12095 static void print_archetype_entities(const World& world, const Archetype& archetype, Entity entity, bool adding) {
12096 auto ids = archetype.ids_view();
12097
12098 GAIA_LOG_W("Currently present:");
12099 GAIA_EACH(ids) {
12100 const auto name = entity_name(world, ids[i]);
12101 GAIA_LOG_W("> [%u] %.*s", i, (int)name.size(), name.empty() ? "" : name.data());
12102 }
12103
12104 GAIA_LOG_W("Trying to %s:", adding ? "add" : "del");
12105 const auto name = entity_name(world, entity);
12106 GAIA_LOG_W("> %.*s", (int)name.size(), name.empty() ? "" : name.data());
12107 }
12108
12114 static void verify_add(const World& world, Archetype& archetype, Entity entity, Entity addEntity) {
12115 // Make sure the world is not locked
12116 if (world.locked()) {
12117 GAIA_ASSERT2(false, "Trying to add an entity while the world is locked");
12118 GAIA_LOG_W("Trying to add an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
12119 print_archetype_entities(world, archetype, entity, false);
12120 return;
12121 }
12122
12123 // Makes sure no wildcard entities are added
12124 if (is_wildcard(addEntity)) {
12125 GAIA_ASSERT2(false, "Adding wildcard pairs is not supported");
12126 print_archetype_entities(world, archetype, addEntity, true);
12127 return;
12128 }
12129
12130 // Make sure not to add too many entities/components.
12131 // Non-fragmenting ids live outside archetype identity, so they do not consume a column.
12132 if (!world.component_is_non_fragmenting(addEntity)) {
12133 auto ids = archetype.ids_view();
12134 if GAIA_UNLIKELY (ids.size() + 1 > ChunkHeader::MAX_COMPONENTS) {
12135 GAIA_ASSERT2(false, "Trying to add too many entities to entity!");
12136 GAIA_LOG_W(
12137 "Trying to add an entity to entity [%u:%u] but there's no space left!", entity.id(), entity.gen());
12138 print_archetype_entities(world, archetype, addEntity, true);
12139 return;
12140 }
12141 }
12142 }
12143
12149 static void verify_del(const World& world, Archetype& archetype, Entity entity, Entity func_del) {
12150 // Make sure the world is not locked
12151 if (world.locked()) {
12152 GAIA_ASSERT2(false, "Trying to delete an entity while the world is locked");
12153 GAIA_LOG_W("Trying to delete an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
12154 print_archetype_entities(world, archetype, entity, false);
12155 return;
12156 }
12157
12158 // Make sure the id is present on the archetype or in non-fragmenting membership storage.
12159 if GAIA_UNLIKELY (
12160 !archetype.has(func_del) &&
12161 (func_del.pair() || !world.has_direct_sparse_component_inter(entity, func_del))) {
12162 GAIA_ASSERT2(false, "Trying to remove an entity which wasn't added");
12163 GAIA_LOG_W("Trying to del an entity from entity [%u:%u] but it was never added", entity.id(), entity.gen());
12164 print_archetype_entities(world, archetype, func_del, false);
12165 return;
12166 }
12167 }
12168
12173 static void verify_enable(const World& world, Archetype& archetype, Entity entity) {
12174 if (world.locked()) {
12175 GAIA_ASSERT2(false, "Trying to enable/disable an entity while the world is locked");
12176 GAIA_LOG_W("Trying to enable/disable an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
12177 print_archetype_entities(world, archetype, entity, false);
12178 }
12179 }
12180
12185 static void verify_move(const World& world, Archetype& archetype, Entity entity) {
12186 if (world.locked()) {
12187 GAIA_ASSERT2(false, "Trying to move an entity while the world is locked");
12188 GAIA_LOG_W("Trying to move an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
12189 print_archetype_entities(world, archetype, entity, false);
12190 }
12191 }
12192#endif
12193
12199 GAIA_NODISCARD Archetype* foc_archetype_add(Archetype* pArchetypeLeft, Entity entity) {
12200 // Check if the component is found when following the "add" edges
12201 bool edgeNeedsRebuild = false;
12202 {
12203 const auto edge = pArchetypeLeft->find_edge_right(entity);
12204 if (edge != ArchetypeIdHashPairBad) {
12205 auto it = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
12206 if (it != m_archetypesById.end() && it->second != nullptr)
12207 return it->second;
12208
12209 // Drop stale local cache edge and rebuild it below.
12210 pArchetypeLeft->del_graph_edge_right_local(entity);
12211 edgeNeedsRebuild = true;
12212 }
12213 }
12214
12215 // Prepare a joint array of components of old + the newly added component
12216 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
12217 {
12218 auto entsOld = pArchetypeLeft->ids_view();
12219 const auto entsOldCnt = entsOld.size();
12220 entsNew.resize((uint32_t)entsOld.size() + 1);
12221 GAIA_FOR(entsOldCnt) entsNew[i] = entsOld[i];
12222 entsNew[(uint32_t)entsOld.size()] = entity;
12223 }
12224
12225 // Make sure to sort the components so we receive the same hash no matter the order in which components
12226 // are provided Bubble sort is okay. We're dealing with at most ChunkHeader::MAX_COMPONENTS items.
12227 sort(entsNew, SortComponentCond{});
12228
12229 // Once sorted we can calculate the hashes
12230 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
12231 auto* pArchetypeRight = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
12232 if (pArchetypeRight == nullptr) {
12233 pArchetypeRight = create_archetype({entsNew.data(), entsNew.size()});
12234 pArchetypeRight->set_hashes({hashLookup});
12235 reg_archetype(pArchetypeRight);
12236 edgeNeedsRebuild = true;
12237 }
12238
12239 if (edgeNeedsRebuild)
12240 pArchetypeLeft->build_graph_edges(pArchetypeRight, entity);
12241
12242 return pArchetypeRight;
12243 }
12244
12250 GAIA_NODISCARD Archetype* foc_archetype_add_no_graph(Archetype* pArchetypeLeft, Entity entity) {
12251 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
12252 {
12253 auto entsOld = pArchetypeLeft->ids_view();
12254 const auto entsOldCnt = entsOld.size();
12255 entsNew.resize((uint32_t)entsOld.size() + 1);
12256 GAIA_FOR(entsOldCnt) entsNew[i] = entsOld[i];
12257 entsNew[(uint32_t)entsOld.size()] = entity;
12258 }
12259
12260 sort(entsNew, SortComponentCond{});
12261
12262 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
12263 auto* pArchetypeRight = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
12264 if (pArchetypeRight != nullptr)
12265 return pArchetypeRight;
12266
12267 pArchetypeRight = create_archetype({entsNew.data(), entsNew.size()});
12268 pArchetypeRight->set_hashes({hashLookup});
12269 reg_archetype(pArchetypeRight);
12270 return pArchetypeRight;
12271 }
12272
12278 GAIA_NODISCARD Archetype* foc_archetype_del(Archetype* pArchetypeRight, Entity entity) {
12279 // Check if the component is found when following the "del" edges
12280 bool edgeNeedsRebuild = false;
12281 {
12282 const auto edge = pArchetypeRight->find_edge_left(entity);
12283 if (edge != ArchetypeIdHashPairBad) {
12284 const auto it = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
12285 if (it != m_archetypesById.end()) {
12286 auto* pArchetypeLeft = it->second;
12287 if (pArchetypeLeft != nullptr)
12288 return pArchetypeLeft;
12289 }
12290
12291 // Drop stale local cache edge and rebuild it below.
12292 pArchetypeRight->del_graph_edge_left_local(entity);
12293 edgeNeedsRebuild = true;
12294 }
12295 }
12296
12297 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
12298 auto entsOld = pArchetypeRight->ids_view();
12299
12300 // Find the intersection
12301 for (const auto e: entsOld) {
12302 if (e == entity)
12303 continue;
12304
12305 entsNew.push_back(e);
12306 }
12307
12308 // Verify there was a change
12309 GAIA_ASSERT(entsNew.size() != entsOld.size());
12310
12311 // Calculate the hashes
12312 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
12313 auto* pArchetype = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
12314 if (pArchetype == nullptr) {
12315 pArchetype = create_archetype({entsNew.data(), entsNew.size()});
12316 pArchetype->set_hashes({hashLookup});
12317 reg_archetype(pArchetype);
12318 edgeNeedsRebuild = true;
12319 }
12320
12321 if (edgeNeedsRebuild)
12322 pArchetype->build_graph_edges(pArchetypeRight, entity);
12323
12324 return pArchetype;
12325 }
12326
12332 GAIA_NODISCARD Archetype* foc_archetype_del_no_graph(Archetype* pArchetypeRight, Entity entity) {
12333 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
12334 auto entsOld = pArchetypeRight->ids_view();
12335
12336 for (const auto e: entsOld) {
12337 if (e == entity)
12338 continue;
12339
12340 entsNew.push_back(e);
12341 }
12342
12343 GAIA_ASSERT(entsNew.size() != entsOld.size());
12344
12345 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
12346 auto* pArchetype = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
12347 if (pArchetype != nullptr)
12348 return pArchetype;
12349
12350 pArchetype = create_archetype({entsNew.data(), entsNew.size()});
12351 pArchetype->set_hashes({hashLookup});
12352 reg_archetype(pArchetype);
12353 return pArchetype;
12354 }
12355
12358 GAIA_NODISCARD const auto& archetypes() const {
12359 return m_archetypes;
12360 }
12361
12365 GAIA_NODISCARD Archetype& archetype(Entity entity) {
12366 const auto& ec = fetch(entity);
12367 return *ec.pArchetype;
12368 }
12369
12373 void del_name(EntityContainer& ec, Entity entity) {
12374 EntityBuilder(*this, entity, ec).del_name();
12375 }
12376
12379 void del_name(Entity entity) {
12380 EntityBuilder(*this, entity).del_name();
12381 }
12382
12386 static void del_name_lookup(cnt::map<EntityNameLookupKey, Entity>& lookup, EntityNameLookupKey key) {
12387 const auto it = lookup.find(key);
12388 // If the assert is hit it means the pointer to the name string was invalidated or became dangling.
12389 // That should not be possible for strings managed internally so the only other option is user-managed
12390 // strings are broken.
12391 GAIA_ASSERT(it != lookup.end());
12392 if (it != lookup.end()) {
12393 // Release memory allocated for the string if we own it
12394 if (it->first.owned())
12395 mem::mem_free((void*)key.str());
12396
12397 lookup.erase(it);
12398 }
12399 }
12400
12405 void del_entity_names(EntityContainer& ec, Entity entity) {
12406 if (entity.pair())
12407 return;
12408
12409 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
12410 if (compIdx == BadIndex)
12411 return;
12412
12413 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut_gen<false>(compIdx, ec.row));
12414 GAIA_ASSERT(core::check_alignment(pDesc));
12415 if (pDesc->name != nullptr) {
12416 del_name_lookup(m_nameToEntity, EntityNameLookupKey(pDesc->name, pDesc->name_len, 0));
12417 invalidate_scope_path_cache();
12418 pDesc->name = nullptr;
12419 pDesc->name_len = 0;
12420 }
12421 if (pDesc->alias != nullptr) {
12422 del_name_lookup(m_aliasToEntity, EntityNameLookupKey(pDesc->alias, pDesc->alias_len, 0));
12423 pDesc->alias = nullptr;
12424 pDesc->alias_len = 0;
12425 }
12426 }
12427
12432 void del_entity(Entity entity, bool invalidate) {
12433 if (entity.pair() || entity == EntityBad)
12434 return;
12435
12436 auto& ec = fetch(entity);
12437 del_entity_inter(ec, entity, invalidate);
12438 }
12439
12445 void del_entity(EntityContainer& ec, Entity entity, bool invalidate) {
12446 if (entity == EntityBad)
12447 return;
12448
12449 del_entity_inter(ec, entity, invalidate);
12450 }
12451
12456 void del_entity_inter(EntityContainer& ec, Entity entity, bool invalidate) {
12457 GAIA_ASSERT(entity.pair() || entity.id() > GAIA_ID(LastCoreComponent).id());
12458
12459 // if (!is_req_del(ec))
12460 {
12461 if (m_recs.entities.item_count() == 0)
12462 return;
12463
12464#if GAIA_ASSERT_ENABLED
12465 auto* pChunk = ec.pChunk;
12466 GAIA_ASSERT(pChunk != nullptr);
12467#endif
12468
12469 // Remove the entity from its chunk.
12470 // We release the names first because remove_entity calls component destructors.
12471 // If the call was made inside invalidate_entity we would access a memory location
12472 // which has already been destructed which is not nice.
12473 del_entity_names(ec, entity);
12474 remove_entity(*ec.pArchetype, *ec.pChunk, ec.row);
12475 remove_src_entity_version(entity);
12476 }
12477
12478 // Invalidate on-demand.
12479 // We delete as a separate step in the delayed deletion.
12480 if (invalidate)
12481 invalidate_entity(entity);
12482 }
12483
12487 void del_entities(Archetype& archetype) {
12488 // Rows that joined after the request still need their own deletion.
12489 for (;;) {
12490 cnt::darray<Entity> rows;
12491 collect_unrequested_rows(archetype, rows);
12492 if (rows.empty())
12493 break;
12494
12495 ArchetypeRowDelScope scope(*this);
12496 prepare_del_archetype_rows(archetype, rows);
12497 for (const auto entity: rows)
12498 m_archetypeRowsToDel.push_back(entity);
12499 }
12500
12501 for (auto* pChunk: archetype.chunks()) {
12502 validate_chunk(pChunk);
12503
12504 // The rows go with the chunk. Their records wait in m_reqEntitiesToDel.
12505 for (auto e: pChunk->entity_view()) {
12506 auto* pEc = try_fetch_record(e);
12507 if (pEc == nullptr)
12508 continue;
12509
12510 GAIA_ASSERT((pEc->flags & EntityContainerFlags::DeleteRequested) != 0);
12511 pEc->pArchetype = nullptr;
12512 pEc->pChunk = nullptr;
12513 pEc->pEntity = nullptr;
12514 }
12515
12516 // If the chunk was already dying we need to remove it from the delete list
12517 // because we can delete it right away.
12518 if (pChunk->queued_for_deletion())
12519 remove_chunk_from_delete_queue(pChunk->delete_queue_index());
12520
12521 remove_chunk(archetype, *pChunk);
12522 }
12523
12524 validate_entities();
12525 }
12526
12529 void del_inter(Entity entity) {
12530 auto on_delete = [this](Entity entityToDel) {
12531 auto& ec = fetch(entityToDel);
12532 handle_del_entity(ec, entityToDel, EntitySpan{});
12533 };
12534
12535 if (is_wildcard(entity)) {
12536 const auto rel = get(entity.id());
12537 const auto tgt = get(entity.gen());
12538
12539 // (*,*)
12540 if (rel == All && tgt == All) {
12541 GAIA_ASSERT2(false, "Not supported yet");
12542 }
12543 // (*,X)
12544 else if (rel == All) {
12545 if (const auto* pTargets = relations(tgt)) {
12546 // handle_del might invalidate the targets map so we need to make a copy
12547 // TODO: this is suboptimal at best, needs to be optimized
12548 cnt::darray_ext<Entity, 64> tmp;
12549 for (auto key: *pTargets)
12550 tmp.push_back(key.entity());
12551 for (auto e: tmp)
12552 on_delete(Pair(e, tgt));
12553 }
12554 }
12555 // (X,*)
12556 else if (tgt == All) {
12557 if (const auto* pRelations = targets(rel)) {
12558 // handle_del might invalidate the targets map so we need to make a copy
12559 // TODO: this is suboptimal at best, needs to be optimized
12560 cnt::darray_ext<Entity, 64> tmp;
12561 for (auto key: *pRelations)
12562 tmp.push_back(key.entity());
12563 for (auto e: tmp)
12564 on_delete(Pair(rel, e));
12565 }
12566 }
12567 } else {
12568 on_delete(entity);
12569 }
12570 }
12571
12573 void del_finalize_archetypes() {
12574 GAIA_PROF_SCOPE(World::del_finalize_archetypes);
12575
12576 // Deleting rows that joined late can request more archetypes, so take them one at a time.
12577 while (!m_reqArchetypesToDel.empty()) {
12578 const auto it = m_reqArchetypesToDel.begin();
12579 auto* pArchetype = it->archetype();
12580 m_reqArchetypesToDel.erase(it);
12581 if (pArchetype == nullptr)
12582 continue;
12583
12584 del_entities(*pArchetype);
12585
12586 // Now that all entities are deleted, all their chunks are requested to get deleted
12587 // and in turn the archetype itself as well. Therefore, it is added to the archetype
12588 // delete list and picked up by del_empty_archetypes. No need to call deletion from here.
12589 // > del_empty_archetype(pArchetype);
12590 }
12591 }
12592
12594 void del_finalize_entities() {
12595 GAIA_PROF_SCOPE(World::del_finalize_entities);
12596
12597 for (auto it = m_reqEntitiesToDel.begin(); it != m_reqEntitiesToDel.end();) {
12598 const auto e = it->entity();
12599
12600 // Entities that form archetypes need to stay until the archetype itself is gone
12601 if (m_entityToArchetypeMap.contains(*it)) {
12602 ++it;
12603 continue;
12604 }
12605
12606#if GAIA_USE_SAFE_ENTITY
12607 // A cascade deletes the entity's data even while a SafeEntity references it. Keep the record
12608 // until the last SafeEntity releases it, so the handle never touches a freed record.
12609 if (const auto* pEc = try_fetch_record(e); pEc != nullptr && pEc->refCnt != 0) {
12610 ++it;
12611 continue;
12612 }
12613#endif
12614
12615 // Requested entities are partially deleted. We only need to invalidate them.
12616 invalidate_entity(e);
12617
12618 it = m_reqEntitiesToDel.erase(it);
12619 }
12620 }
12621
12623 void del_finalize() {
12624 GAIA_PROF_SCOPE(World::del_finalize);
12625
12626 del_finalize_archetypes();
12627 del_finalize_entities();
12628 }
12629
12635 GAIA_NODISCARD bool archetype_cond_match(Archetype& archetype, Pair cond, Entity target) const {
12636 // E.g.:
12637 // target = (All, entity)
12638 // cond = (OnDeleteTarget, delete)
12639 // Delete the entity if it matches the cond
12640 auto ids = archetype.ids_view();
12641
12642 if (target.pair()) {
12643 for (auto e: ids) {
12644 // Find the pair which matches (All, entity)
12645 if (!e.pair())
12646 continue;
12647 if (e.gen() != target.gen())
12648 continue;
12649
12650 const auto& ec = m_recs.entities[e.id()];
12651 const auto entity = ec.pChunk->entity_view()[ec.row];
12652 if (!has(entity, cond))
12653 continue;
12654
12655 return true;
12656 }
12657 } else {
12658 for (auto e: ids) {
12659 if (e.pair())
12660 continue;
12661 if (!has(e, cond))
12662 continue;
12663
12664 return true;
12665 }
12666 }
12667
12668 return false;
12669 }
12670
12674 void move_to_archetype(Archetype& srcArchetype, Archetype& dstArchetype) {
12675 GAIA_ASSERT(&srcArchetype != &dstArchetype);
12676
12677 bool updated = false;
12678
12679 for (auto* pSrcChunk: srcArchetype.chunks()) {
12680 auto srcEnts = pSrcChunk->entity_view();
12681 if (srcEnts.empty())
12682 continue;
12683
12684 // Copy entities back-to-front to avoid unnecessary data movements.
12685 // TODO: Handle disabled entities efficiently.
12686 // If there are disabled entities, we still do data movements if there already
12687 // are enabled entities in the chunk.
12688 // TODO: If the header was of some fixed size, e.g. if we always acted as if we had
12689 // ChunkHeader::MAX_COMPONENTS, certain data movements could be done pretty much instantly.
12690 // E.g. when removing tags or pairs, we would simply replace the chunk pointer
12691 // with a pointer to another one. The same goes for archetypes. Component data
12692 // would not have to move at all internal chunk header pointers would remain unchanged.
12693
12694 uint32_t i = (uint32_t)srcEnts.size();
12695 while (i != 0) {
12696 auto* pDstChunk = dstArchetype.foc_free_chunk();
12697 const uint32_t dstSpaceLeft = pDstChunk->capacity() - pDstChunk->size();
12698 const uint32_t cnt = core::get_min(dstSpaceLeft, i);
12699 for (uint32_t j = 0; j < cnt; ++j) {
12700 auto e = srcEnts[i - j - 1];
12701 move_entity(e, fetch(e), dstArchetype, *pDstChunk);
12702 }
12703
12704 pDstChunk->update_world_version();
12705 pDstChunk->update_entity_order_version();
12706
12707 GAIA_ASSERT(cnt <= i);
12708 i -= cnt;
12709 }
12710
12711 pSrcChunk->update_world_version();
12712 pSrcChunk->update_entity_order_version();
12713 updated = true;
12714 }
12715
12716 if (updated)
12717 update_version(m_worldVersion);
12718 }
12719
12724 GAIA_NODISCARD Archetype* calc_dst_archetype_ent(Archetype* pArchetype, Entity entity) {
12725 GAIA_ASSERT(!is_wildcard(entity));
12726
12727 auto ids = pArchetype->ids_view();
12728 for (auto id: ids) {
12729 if (id != entity)
12730 continue;
12731
12732 return foc_archetype_del(pArchetype, id);
12733 }
12734
12735 return nullptr;
12736 }
12737
12742 GAIA_NODISCARD Archetype* calc_dst_archetype_all_ent(Archetype* pArchetype, Entity entity) {
12743 GAIA_ASSERT(is_wildcard(entity));
12744
12745 Archetype* pDstArchetype = pArchetype;
12746
12747 auto ids = pArchetype->ids_view();
12748 for (auto id: ids) {
12749 if (!id.pair() || id.gen() != entity.gen())
12750 continue;
12751
12752 pDstArchetype = foc_archetype_del(pDstArchetype, id);
12753 }
12754
12755 return pArchetype != pDstArchetype ? pDstArchetype : nullptr;
12756 }
12757
12762 GAIA_NODISCARD Archetype* calc_dst_archetype_ent_all(Archetype* pArchetype, Entity entity) {
12763 GAIA_ASSERT(is_wildcard(entity));
12764
12765 Archetype* pDstArchetype = pArchetype;
12766
12767 auto ids = pArchetype->ids_view();
12768 for (auto id: ids) {
12769 if (!id.pair() || id.id() != entity.id())
12770 continue;
12771
12772 pDstArchetype = foc_archetype_del(pDstArchetype, id);
12773 }
12774
12775 return pArchetype != pDstArchetype ? pDstArchetype : nullptr;
12776 }
12777
12782 GAIA_NODISCARD Archetype* calc_dst_archetype_all_all(Archetype* pArchetype, [[maybe_unused]] Entity entity) {
12783 GAIA_ASSERT(is_wildcard(entity));
12784
12785 Archetype* pDstArchetype = pArchetype;
12786 bool found = false;
12787
12788 auto ids = pArchetype->ids_view();
12789 for (auto id: ids) {
12790 if (!id.pair())
12791 continue;
12792
12793 pDstArchetype = foc_archetype_del(pDstArchetype, id);
12794 found = true;
12795 }
12796
12797 return found ? pDstArchetype : nullptr;
12798 }
12799
12805 GAIA_NODISCARD Archetype* calc_dst_archetype(Archetype* pArchetype, Entity entity) {
12806 if (entity.pair()) {
12807 auto rel = entity.id();
12808 auto tgt = entity.gen();
12809
12810 // Removing a wildcard pair. We need to find all pairs matching it.
12811 if (rel == All.id() || tgt == All.id()) {
12812 // (first, All) means we need to match (first, A), (first, B), ...
12813 if (rel != All.id() && tgt == All.id())
12814 return calc_dst_archetype_ent_all(pArchetype, entity);
12815
12816 // (All, second) means we need to match (A, second), (B, second), ...
12817 if (rel == All.id() && tgt != All.id())
12818 return calc_dst_archetype_all_ent(pArchetype, entity);
12819
12820 // (All, All) means we need to match all relationships
12821 return calc_dst_archetype_all_all(pArchetype, EntityBad);
12822 }
12823 }
12824
12825 // Non-wildcard pair or entity
12826 return calc_dst_archetype_ent(pArchetype, entity);
12827 }
12828
12831 void req_del(Archetype& archetype) {
12832 if (archetype.is_req_del())
12833 return;
12834
12835 cnt::darray<Entity> rows;
12836 collect_unrequested_rows(archetype, rows);
12837 prepare_del_archetype_rows(archetype, rows);
12838
12839 archetype.req_del();
12840 update_version(m_archetypeDeleteVersion);
12841 m_reqArchetypesToDel.insert(ArchetypeLookupKey(archetype.lookup_hash(), &archetype));
12842
12843 // A requested deletion outweighs the lifespan. An archetype kept indefinitely is never queued for
12844 // deletion, so it becomes mortal: its last chunk queues it when it goes, or it is queued here when
12845 // it holds no chunk.
12846 if (archetype.max_lifespan() == 0) {
12847 archetype.set_max_lifespan(1);
12848 try_enqueue_archetype_for_deletion(archetype);
12849 }
12850
12851 // The rows' own deletion rules move entities between archetypes. They run once the outermost
12852 // request has marked every archetype it deletes, so no entity moves out of or into one.
12853 GAIA_ASSERT(m_archetypeRowDelDepth != 0);
12854 for (const auto entity: rows)
12855 m_archetypeRowsToDel.push_back(entity);
12856 }
12857
12859 class ArchetypeRowDelScope {
12860 World& m_world;
12861
12862 public:
12863 explicit ArchetypeRowDelScope(World& world): m_world(world) {
12864 ++m_world.m_archetypeRowDelDepth;
12865 }
12866 ~ArchetypeRowDelScope() {
12867 if (--m_world.m_archetypeRowDelDepth == 0)
12868 m_world.req_del_archetype_rows();
12869 }
12870 ArchetypeRowDelScope(const ArchetypeRowDelScope&) = delete;
12871 ArchetypeRowDelScope& operator=(const ArchetypeRowDelScope&) = delete;
12872 };
12873
12877 void collect_unrequested_rows(const Archetype& archetype, cnt::darray<Entity>& rows) {
12878 for (const auto* pChunk: archetype.chunks()) {
12879 for (const auto entity: pChunk->entity_view()) {
12880 const auto* pEc = try_fetch_record(entity);
12881 if (pEc != nullptr && (pEc->flags & EntityContainerFlags::DeleteRequested) == 0)
12882 rows.push_back(entity);
12883 }
12884 }
12885 }
12886
12891 void prepare_del_archetype_rows(const Archetype& archetype, const cnt::darray<Entity>& rows) {
12892 if (archetype.pairs_is() != 0) {
12893 for (const auto entity: rows)
12894 unlink_live_is_relations(entity);
12895 }
12896
12897 // Unregister before notifying, as the per-entity path does. An entity already being deleted on
12898 // its own unregisters there.
12899 for (const auto entity: rows) {
12900 if (!entity_deletion_active(entity))
12901 del_entity_registrations(entity);
12902 }
12903
12904 if (!may_notify_del_entity_components(archetype) || tearing_down())
12905 return;
12906
12907 cnt::darray_ext<Entity, 32> entitiesToNotify;
12908 for (const auto entity: rows) {
12909 if (!entity_deletion_active(entity)) {
12910 entity_deletion_enter(entity);
12911 entitiesToNotify.push_back(entity);
12912 }
12913 }
12914
12915 // Mark every entity active before callbacks run. A callback can then request
12916 // deletion of any entity in this archetype without recursively notifying it.
12917 for (const auto entity: entitiesToNotify)
12918 notify_del_entity_components(fetch(entity), entity);
12919 for (uint32_t i = entitiesToNotify.size(); i > 0; --i)
12920 entity_deletion_leave(entitiesToNotify[i - 1]);
12921 }
12922
12927 void req_del_archetype_rows() {
12928 // Requests made by the rows' rules queue more rows for this loop
12929 ++m_archetypeRowDelDepth;
12930 while (!m_archetypeRowsToDel.empty()) {
12931 auto rows = GAIA_MOV(m_archetypeRowsToDel);
12932 m_archetypeRowsToDel = {};
12933 // Grow the queue once rather than rehash it repeatedly while whole archetypes go in
12934 m_reqEntitiesToDel.reserve(m_reqEntitiesToDel.size() + rows.size());
12935 for (const auto entity: rows)
12936 req_del_archetype_row(entity);
12937 }
12938 --m_archetypeRowDelDepth;
12939 }
12940
12943 void req_del_archetype_row(Entity entity) {
12944 auto* pEc = try_fetch_record(entity);
12945 // A callback or an earlier row's rules may have requested it already. A row that left its
12946 // archetype keeps its chunk row, so it is not deleted here.
12947 if (pEc == nullptr || (pEc->flags & EntityContainerFlags::DeleteRequested) != 0 || pEc->pArchetype == nullptr ||
12948 !pEc->pArchetype->is_req_del())
12949 return;
12950
12951 auto& ec = *pEc;
12952 if ((ec.flags & (EntityContainerFlags::OnDelete_Error | EntityContainerFlags::OnDeleteTarget_Error)) != 0) {
12953 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted");
12954 GAIA_LOG_E(
12955 "Trying to delete an entity [%u.%u] that is forbidden from being deleted", entity.id(), entity.gen());
12956 // Its chunk goes regardless. Keep the record and everything referencing it untouched.
12957 ec.req_del();
12958 invalidate_weak_entities(ec);
12959 return;
12960 }
12961
12962#if GAIA_USE_SAFE_ENTITY
12963 release_world_ref(ec);
12964#endif
12965
12966 // An entity already being deleted on its own applies its rules and deletes its pairs there,
12967 // once everything its rules delete is marked. Deleting a pair earlier would remove it from
12968 // entities that are about to be deleted, letting them survive.
12969 {
12970 // The rules resolve the row the way they resolve an entity deleted on its own
12971 GAIA_ASSERT(m_pArchetypeRowInRules == nullptr);
12972 m_pArchetypeRowInRules = &ec;
12973
12974 // Also covers an entity whose own target rules deleted its archetype, which returns before
12975 // unregistering. Unregistering twice is harmless.
12976 del_entity_registrations(entity);
12977
12978 if (!entity_deletion_active(entity)) {
12979 cnt::darray_ext<Entity, 64> pairEntities;
12980 if (!entity.pair())
12981 collect_entity_pairs(entity, pairEntities);
12982
12983 entity_deletion_enter(entity);
12984 // A pair row does not apply its target's OnDeleteTarget rules. Deleting the target does.
12985 if (!entity.pair())
12986 del_entity_target_rules(ec, entity);
12987 del_entity_id_rules(ec, entity);
12988 del_pair_entities(EntitySpan{pairEntities.data(), pairEntities.size()});
12989 entity_deletion_leave(entity);
12990 }
12991
12992 m_pArchetypeRowInRules = nullptr;
12993 }
12994
12995 // A structural change made from a callback while the rules ran may have moved the row out. It then
12996 // keeps its new chunk row and must not be deleted here.
12997 if (ec.pArchetype == nullptr || !ec.pArchetype->is_req_del())
12998 return;
12999
13000#if GAIA_OBSERVERS_ENABLED
13001 del_nonfragmenting_relation_source_observed(entity);
13002#endif
13003 del_nonfragmenting_relation_source(entity);
13004 del_entity_names(ec, entity);
13005 remove_src_entity_version(entity);
13006 del_sparse_components(entity);
13007
13008 ec.req_del();
13009 invalidate_weak_entities(ec);
13010 m_reqEntitiesToDel.insert(EntityLookupKey(entity));
13011 }
13012
13013#if GAIA_USE_SAFE_ENTITY
13017 static void release_world_ref(EntityContainer& ec) {
13018 if ((ec.flags & EntityContainerFlags::RefDecreased) != 0)
13019 return;
13020
13021 --ec.refCnt;
13022 ec.flags |= EntityContainerFlags::RefDecreased;
13023 }
13024
13029 void safe_ref_add(Entity entity) {
13030 ++fetch(entity).refCnt;
13031 if (!entity.pair())
13032 return;
13033
13034 ++m_recs.entities[entity.id()].refCnt;
13035 ++m_recs.entities[entity.gen()].refCnt;
13036 }
13037
13042 void safe_ref_del(Entity entity) {
13043 auto* pEc = try_fetch_record(entity);
13044 if GAIA_UNLIKELY (pEc == nullptr)
13045 return;
13046
13047 safe_ref_del(*pEc, entity);
13048 if (!entity.pair())
13049 return;
13050
13051 // The references keep both endpoint records, so their ids still name the same entities
13052 const EntityId endpoints[] = {(EntityId)entity.id(), (EntityId)entity.gen()};
13053 for (const auto id: endpoints) {
13054 auto* pEndpoint = m_recs.entities.try_get(id);
13055 GAIA_ASSERT(pEndpoint != nullptr);
13056 if (pEndpoint != nullptr)
13057 safe_ref_del(*pEndpoint, EntityContainer::handle(*pEndpoint));
13058 }
13059 }
13060
13064 void safe_ref_del(EntityContainer& ec, Entity entity) {
13065 GAIA_ASSERT(ec.refCnt > 0);
13066 if (--ec.refCnt != 0)
13067 return;
13068
13069 // Inside a component destructor or while iteration locks the world, deleting now would change
13070 // structures in use. The next frame_cleanup() deletes the entity instead.
13071 if (m_compDtorDepth != 0 || locked())
13072 m_safeEntityDels.push_back(entity);
13073 else
13074 del(entity);
13075 }
13076
13079 void del_released_safe_entities() {
13080 // Deleting them can release more references from destructors, which queue again
13081 while (!m_safeEntityDels.empty()) {
13082 auto dels = GAIA_MOV(m_safeEntityDels);
13083 m_safeEntityDels = {};
13084 for (const auto entity: dels) {
13085 const auto* pEc = try_fetch_record(entity);
13086 if (pEc != nullptr && pEc->refCnt == 0)
13087 del(entity);
13088 }
13089 }
13090 }
13091#endif
13092
13097 template <typename Container>
13098 void collect_entity_pairs(Entity entity, Container& pairEntities) const {
13099 if (const auto* pTargets = targets(entity)) {
13100 for (auto targetKey: *pTargets)
13101 pairEntities.push_back(Pair(entity, targetKey.entity()));
13102 }
13103 if (const auto* pRelations = relations(entity)) {
13104 for (auto relationKey: *pRelations) {
13105 const auto relation = relationKey.entity();
13106 if (relation != entity)
13107 pairEntities.push_back(Pair(relation, entity));
13108 }
13109 }
13110 }
13111
13115 void del_entity_target_rules(const EntityContainer& ec, Entity entity) {
13116 const bool deleteTargets = (ec.flags & EntityContainerFlags::OnDeleteTarget_Delete) != 0 ||
13117 has_nonfragmenting_relation_target_cond(entity, Pair(OnDeleteTarget, Delete));
13118#if GAIA_OBSERVERS_ENABLED
13119 cnt::darray<Entity> cascadeTargets;
13120 if (deleteTargets)
13121 collect_delete_cascade_sources(entity, Pair(OnDeleteTarget, Delete), cascadeTargets);
13122 auto cascadeDelDiffCtx = ObserverRegistry::DiffDispatchCtx{};
13123 if (!cascadeTargets.empty()) {
13124 cascadeDelDiffCtx = m_observers.prepare_diff(
13125 *this, ObserverEvent::OnDel, EntitySpan{cascadeTargets.data(), cascadeTargets.size()},
13126 EntitySpan{cascadeTargets.data(), cascadeTargets.size()});
13127 }
13128#endif
13129
13130 if (deleteTargets) {
13131 // Delete all entities referencing this one as a relationship pair's target
13132 req_del_entities_with(Pair(All, entity), Pair(OnDeleteTarget, Delete));
13133 } else {
13134 // Remove from all entities referencing this one as a relationship pair's target
13135 rem_from_entities(Pair(All, entity));
13136 }
13137
13138#if GAIA_OBSERVERS_ENABLED
13139 m_observers.finish_diff(*this, GAIA_MOV(cascadeDelDiffCtx));
13140#endif
13141 }
13142
13145 void del_entity_registrations([[maybe_unused]] Entity entity) {
13146#if GAIA_OBSERVERS_ENABLED
13147 observers().del(*this, entity);
13148#endif
13149#if GAIA_SYSTEMS_ENABLED
13150 systems().del(entity);
13151#endif
13152 }
13153
13157 void del_entity_id_rules(const EntityContainer& ec, Entity entity) {
13158 if ((ec.flags & EntityContainerFlags::OnDelete_Delete) != 0) {
13159 // Delete all references to the entity
13160 req_del_entities_with(entity);
13161 } else {
13162 // Entities are only removed by default
13163 rem_from_entities(entity);
13164 }
13165 }
13166
13171 void req_del_inter(EntityContainer& ec, Entity entity) {
13172 // req_del_archetype_rows deletes the rows of delete-requested archetypes
13173 if (is_req_del_or_queued(ec))
13174 return;
13175
13176#if GAIA_USE_SAFE_ENTITY
13177 // Cascades request deletion directly. del_finalize_entities keeps the record while
13178 // a SafeEntity still references it.
13179 release_world_ref(ec);
13180#endif
13181 unlink_live_is_relations(entity);
13182#if GAIA_OBSERVERS_ENABLED
13183 del_nonfragmenting_relation_source_observed(entity);
13184 del_nonfragmenting_relation_source(entity);
13185 auto delDiffCtx =
13186 m_observers.prepare_diff(*this, ObserverEvent::OnDel, EntitySpan{&entity, 1}, EntitySpan{&entity, 1});
13187#else
13188 del_nonfragmenting_relation_source(entity);
13189#endif
13190 notify_del_entity_components(ec, entity);
13191 del_entity(ec, entity, false);
13192#if GAIA_OBSERVERS_ENABLED
13193 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
13194#endif
13195 del_sparse_components(entity);
13196
13197 ec.req_del();
13198 // Every per-entity deletion request passes here, including cascades that bypass
13199 // handle_del_entity, so WeakEntities reset at the request.
13200 invalidate_weak_entities(ec);
13201 m_reqEntitiesToDel.insert(EntityLookupKey(entity));
13202 }
13203
13207 void req_del(EntityContainer& ec, Entity entity) {
13208 if (entity_deletion_active(entity))
13209 return;
13210
13211 entity_deletion_enter(entity);
13212 req_del_inter(ec, entity);
13213 entity_deletion_leave(entity);
13214 }
13215
13218 void invalidate_pair_removal_caches(Entity entity) {
13219 if (!entity.pair())
13220 return;
13221
13222 auto invalidate_relation = [this](Entity relation) {
13223 if (relation == EntityBad)
13224 return;
13225 touch_rel_version(relation);
13226 invalidate_queries_for_rel(relation);
13227 };
13228
13229 if (entity.id() != All.id()) {
13230 invalidate_relation(try_get(entity.id()));
13231 } else if (entity.gen() != All.id()) {
13232 const auto target = try_get(entity.gen());
13233 if (target != EntityBad) {
13234 if (const auto* pRelations = relations(target)) {
13235 for (auto relationKey: *pRelations)
13236 invalidate_relation(relationKey.entity());
13237 }
13238 }
13239 } else {
13240 for (auto it = m_pairLookup.relation_begin(); it != m_pairLookup.relation_end(); ++it)
13241 invalidate_relation(it->first.entity());
13242 }
13243
13244 clear_relation_caches();
13245 }
13246
13250 void unlink_live_is_relation(Entity source, Entity target) {
13251 const auto sourceKey = EntityLookupKey(source);
13252 const auto targetKey = EntityLookupKey(target);
13253
13254 invalidate_queries_for_entity({Is, target});
13255
13256 if (const auto itTargets = m_entityToAsTargets.find(sourceKey); itTargets != m_entityToAsTargets.end()) {
13257 itTargets->second.erase(targetKey);
13258 if (itTargets->second.empty())
13259 m_entityToAsTargets.erase(itTargets);
13260 }
13261 m_entityToAsTargetsTravCache = {};
13262
13263 if (const auto itRelations = m_entityToAsRelations.find(targetKey);
13264 itRelations != m_entityToAsRelations.end()) {
13265 itRelations->second.erase(sourceKey);
13266 if (itRelations->second.empty())
13267 m_entityToAsRelations.erase(itRelations);
13268 }
13269 m_entityToAsRelationsTravCache = {};
13270 }
13271
13274 void unlink_live_is_relations(Entity source) {
13275 const auto itTargets = m_entityToAsTargets.find(EntityLookupKey(source));
13276 if (itTargets == m_entityToAsTargets.end())
13277 return;
13278
13279 cnt::darray_ext<Entity, 4> targets;
13280 for (auto targetKey: itTargets->second)
13281 targets.push_back(targetKey.entity());
13282
13283 for (auto target: targets)
13284 unlink_live_is_relation(source, target);
13285 }
13286
13290 void unlink_stale_is_relations_by_target_id(Entity source, EntityId targetId) {
13291 const auto sourceKey = EntityLookupKey(source);
13292 const auto itTargets = m_entityToAsTargets.find(sourceKey);
13293 if (itTargets == m_entityToAsTargets.end())
13294 return;
13295
13296 cnt::darray_ext<EntityLookupKey, 4> removedTargets;
13297 for (auto targetKey: itTargets->second) {
13298 if (targetKey.entity().id() == targetId)
13299 removedTargets.push_back(targetKey);
13300 }
13301
13302 for (auto targetKey: removedTargets) {
13303 invalidate_queries_for_structural_entity(EntityLookupKey(Pair{Is, targetKey.entity()}));
13304 itTargets->second.erase(targetKey);
13305
13306 const auto itRelations = m_entityToAsRelations.find(targetKey);
13307 if (itRelations != m_entityToAsRelations.end()) {
13308 itRelations->second.erase(sourceKey);
13309 if (itRelations->second.empty())
13310 m_entityToAsRelations.erase(itRelations);
13311 }
13312 }
13313
13314 if (itTargets->second.empty())
13315 m_entityToAsTargets.erase(itTargets);
13316
13317 m_entityToAsTargetsTravCache = {};
13318 m_entityToAsRelationsTravCache = {};
13319 }
13320
13326 template <typename Func>
13327 void each_delete_cascade_direct_source(Entity target, Pair cond, Func&& func) {
13328 GAIA_ASSERT(!target.pair());
13329
13330 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
13331 const auto relation = relKey.entity();
13332 if (!has(relation, cond))
13333 continue;
13334
13335 const auto* pSources = store.sources(target);
13336 if (pSources == nullptr)
13337 continue;
13338
13339 for (auto source: *pSources)
13340 func(source);
13341 }
13342
13343 const auto pairEntity = Pair(All, target);
13344 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pairEntity));
13345 if (it != m_entityToArchetypeMap.end()) {
13346 for (const auto& record: it->second) {
13347 auto* pArchetype = record.pArchetype;
13348 if (pArchetype == nullptr || pArchetype->is_req_del())
13349 continue;
13350 if (!archetype_cond_match(*pArchetype, cond, pairEntity))
13351 continue;
13352
13353 for (const auto* pChunk: pArchetype->chunks()) {
13354 const auto entities = pChunk->entity_view();
13355 GAIA_EACH(entities)
13356 func(entities[i]);
13357 }
13358 }
13359 }
13360 }
13361
13366 GAIA_NODISCARD bool has_delete_cascade_direct_sources(Entity target, Pair cond) const {
13367 GAIA_ASSERT(!target.pair());
13368
13369 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
13370 if (store.sources(target) != nullptr && has(relKey.entity(), cond))
13371 return true;
13372 }
13373
13374 const auto pairEntity = Pair(All, target);
13375 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pairEntity));
13376 if (it == m_entityToArchetypeMap.end())
13377 return false;
13378
13379 for (const auto& record: it->second) {
13380 auto* pArchetype = record.pArchetype;
13381 if (pArchetype == nullptr || pArchetype->is_req_del())
13382 continue;
13383 if (!archetype_cond_match(*pArchetype, cond, pairEntity))
13384 continue;
13385
13386 for (const auto* pChunk: pArchetype->chunks()) {
13387 if (!pChunk->empty())
13388 return true;
13389 }
13390 }
13391
13392 return false;
13393 }
13394
13399 void collect_delete_cascade_direct_sources(Entity target, Pair cond, cnt::darray<Entity>& out) {
13400 GAIA_ASSERT(!target.pair());
13401 const auto visitStamp = next_entity_visit_stamp();
13402 each_delete_cascade_direct_source(target, cond, [&](Entity source) {
13403 if (!valid(source))
13404 return;
13405 if (!try_mark_entity_visited(source, visitStamp))
13406 return;
13407 out.push_back(source);
13408 });
13409 }
13410
13415 void collect_delete_cascade_sources(Entity target, Pair cond, cnt::darray<Entity>& out) {
13416 GAIA_ASSERT(!target.pair());
13417 const auto visitStamp = next_entity_visit_stamp();
13418 cnt::darray_ext<Entity, 32> targetsToVisit;
13419 (void)try_mark_entity_visited(target, visitStamp);
13420 targetsToVisit.push_back(target);
13421
13422 for (uint32_t i = 0; i < targetsToVisit.size(); ++i) {
13423 const auto currTarget = targetsToVisit[i];
13424 each_delete_cascade_direct_source(currTarget, cond, [&](Entity source) {
13425 if (!valid(source))
13426 return;
13427 if (!try_mark_entity_visited(source, visitStamp))
13428 return;
13429
13430 out.push_back(source);
13431 targetsToVisit.push_back(source);
13432 });
13433 }
13434 }
13435
13438 void req_del_entities_with(Entity entity) {
13439 GAIA_PROF_SCOPE(World::req_del_entities_with);
13440 ArchetypeRowDelScope scope(*this);
13441
13442 GAIA_ASSERT(entity != Pair(All, All));
13443
13444 auto req_del_nonfragmenting_pair = [&](Entity relation, Entity target) {
13445 cnt::darray<Entity> sourcesToDel;
13446 sources(relation, target, [&](Entity source) {
13447 sourcesToDel.push_back(source);
13448 });
13449
13450 for (auto source: sourcesToDel)
13451 del_cascade_entity(source);
13452 };
13453
13454 if (entity.pair()) {
13455 if (entity.id() != All.id()) {
13456 const auto relation = try_get(entity.id());
13457 const auto target = try_get(entity.gen());
13458 if (relation != EntityBad && target != EntityBad && relation_uses_non_fragmenting_storage(relation))
13459 req_del_nonfragmenting_pair(relation, target);
13460 } else {
13461 const auto target = try_get(entity.gen());
13462 if (target == EntityBad)
13463 goto skip_req_del_all_target;
13464 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
13465 if (store.sources(target) == nullptr)
13466 continue;
13467
13468 req_del_nonfragmenting_pair(relKey.entity(), target);
13469 }
13470 skip_req_del_all_target:;
13471 }
13472 } else if (relation_uses_non_fragmenting_storage(entity)) {
13473 if (const auto* pTargets = targets(entity)) {
13474 for (auto targetKey: *pTargets)
13475 req_del_nonfragmenting_pair(entity, targetKey.entity());
13476 }
13477 }
13478
13479 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
13480 if (it == m_entityToArchetypeMap.end())
13481 return;
13482
13483 for (const auto& record: it->second)
13484 req_del(*record.pArchetype);
13485 }
13486
13491 void req_del_entities_with(Entity entity, Pair cond) {
13492 cnt::set<EntityLookupKey> visited;
13493 req_del_entities_with(entity, cond, visited);
13494 }
13495
13500 void req_del_entities_with(Entity entity, Pair cond, cnt::set<EntityLookupKey>& visited) {
13501 GAIA_PROF_SCOPE(World::req_del_entities_with);
13502 ArchetypeRowDelScope scope(*this);
13503
13504 GAIA_ASSERT(entity != Pair(All, All));
13505 if (!visited.insert(EntityLookupKey(entity)).second)
13506 return;
13507
13508 cnt::darray<Entity> cascadeTargets;
13509 if (entity.pair() && entity.id() == All.id()) {
13510 const auto target = try_get(entity.gen());
13511 if (target != EntityBad)
13512 collect_delete_cascade_direct_sources(target, cond, cascadeTargets);
13513 }
13514
13515 auto req_del_nonfragmenting_pair = [&](Entity relation, Entity target) {
13516 if (!has(relation, cond))
13517 return;
13518
13519 cnt::darray<Entity> sourcesToDel;
13520 sources(relation, target, [&](Entity source) {
13521 sourcesToDel.push_back(source);
13522 });
13523
13524 for (auto source: sourcesToDel)
13525 del_cascade_entity(source);
13526 };
13527
13528 for (auto source: cascadeTargets) {
13529 if (has_delete_cascade_direct_sources(source, cond))
13530 req_del_entities_with(Pair(All, source), cond, visited);
13531 }
13532
13533 if (entity.pair()) {
13534 if (entity.id() != All.id()) {
13535 const auto relation = try_get(entity.id());
13536 const auto target = try_get(entity.gen());
13537 if (relation != EntityBad && target != EntityBad && relation_uses_non_fragmenting_storage(relation))
13538 req_del_nonfragmenting_pair(relation, target);
13539 } else {
13540 const auto target = try_get(entity.gen());
13541 if (target == EntityBad)
13542 goto skip_req_del_all_target_cond;
13543 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
13544 if (store.sources(target) == nullptr)
13545 continue;
13546
13547 req_del_nonfragmenting_pair(relKey.entity(), target);
13548 }
13549 skip_req_del_all_target_cond:;
13550 }
13551 } else if (relation_uses_non_fragmenting_storage(entity)) {
13552 if (const auto* pTargets = targets(entity)) {
13553 for (auto targetKey: *pTargets)
13554 req_del_nonfragmenting_pair(entity, targetKey.entity());
13555 }
13556 }
13557
13558 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
13559 if (it == m_entityToArchetypeMap.end())
13560 return;
13561
13562 for (const auto& record: it->second) {
13563 auto* pArchetype = record.pArchetype;
13564 // Evaluate the condition if a valid pair is given
13565 if (!archetype_cond_match(*pArchetype, cond, entity))
13566 continue;
13567
13568 req_del(*pArchetype);
13569 }
13570 }
13571
13574 GAIA_NODISCARD static bool pair_matches_removal_selector(Entity pair, Entity selector) {
13575 GAIA_ASSERT(pair.pair());
13576 GAIA_ASSERT(selector.pair());
13577
13578 if (selector.id() != All.id() && pair.id() != selector.id())
13579 return false;
13580 return selector.gen() == All.id() || pair.gen() == selector.gen();
13581 }
13582
13584 void del_sparse_pair_payloads(Archetype& archetype, Entity selector) {
13585 GAIA_ASSERT(selector.pair());
13586
13587 for (const auto pair: archetype.ids_view()) {
13588 if (!pair.pair() || !pair_matches_removal_selector(pair, selector))
13589 continue;
13590
13591 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(pair));
13592 if (it == m_sparseComponentsByComp.end())
13593 continue;
13594
13595 for (const auto* pChunk: archetype.chunks()) {
13596 const auto entities = pChunk->entity_view();
13597 GAIA_EACH(entities)
13598 it->second.func_del(it->second.pStore, entities[i]);
13599 }
13600 }
13601 }
13602
13609 GAIA_NODISCARD static bool archetype_has_rows(const Archetype& archetype) {
13610 for (const auto* pChunk: archetype.chunks()) {
13611 if (pChunk->size() != 0)
13612 return true;
13613 }
13614 return false;
13615 }
13616
13619 void rem_from_entities(Entity entity) {
13620 GAIA_PROF_SCOPE(World::rem_from_entities);
13621
13622 invalidate_pair_removal_caches(entity);
13623
13624 auto rem_nonfragmenting_pair = [&](Entity relation, Entity target) {
13625 cnt::darray<Entity> sourcesToRem;
13626 sources(relation, target, [&](Entity source) {
13627 sourcesToRem.push_back(source);
13628 });
13629
13630 for (auto source: sourcesToRem)
13631 del(source, Pair(relation, target));
13632 };
13633
13634 if (entity.pair()) {
13635 if (entity.id() != All.id()) {
13636 const auto relation = try_get(entity.id());
13637 const auto target = try_get(entity.gen());
13638 if (relation != EntityBad && target != EntityBad && relation_uses_non_fragmenting_storage(relation))
13639 rem_nonfragmenting_pair(relation, target);
13640 } else {
13641 const auto target = try_get(entity.gen());
13642 if (target == EntityBad)
13643 goto skip_rem_all_target;
13644 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
13645 if (store.sources(target) == nullptr)
13646 continue;
13647
13648 rem_nonfragmenting_pair(relKey.entity(), target);
13649 }
13650 skip_rem_all_target:;
13651 }
13652 } else if (relation_uses_non_fragmenting_storage(entity)) {
13653 if (const auto* pTargets = targets(entity)) {
13654 for (auto targetKey: *pTargets)
13655 rem_nonfragmenting_pair(entity, targetKey.entity());
13656 }
13657 }
13658
13659 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
13660 if (it == m_entityToArchetypeMap.end())
13661 return;
13662
13663 // Copy the reverse-index records. calc_dst_archetype can insert into
13664 // m_entityToArchetypeMap and rehash, which invalidates "it".
13665 const auto srcRecords = it->second;
13666
13667 // Invalidate the singleton status if necessary
13668 if (!entity.pair()) {
13669 auto& ec = fetch(entity);
13670 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0) {
13671 auto ids = ec.pArchetype->ids_view();
13672 const auto idx = core::get_index(ids, entity);
13673 if (idx != BadIndex)
13674 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::IsSingleton, false);
13675 }
13676 }
13677
13678#if GAIA_OBSERVERS_ENABLED
13679 cnt::set<EntityLookupKey> diffTermSet;
13680 cnt::darray<Entity> diffTerms;
13681 cnt::darray<Entity> diffTargets;
13682 for (const auto& record: srcRecords) {
13683 auto* pArchetype = record.pArchetype;
13684 if (pArchetype->is_req_del() || !archetype_has_rows(*pArchetype))
13685 continue;
13686
13687 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
13688 if (pDstArchetype == nullptr)
13689 continue;
13690
13691 for (auto id: pArchetype->ids_view()) {
13692 bool matches = false;
13693 if (entity.pair()) {
13694 if (entity.id() == All.id() && entity.gen() == All.id())
13695 matches = id.pair();
13696 else if (entity.id() == All.id())
13697 matches = id.pair() && id.gen() == entity.gen();
13698 else if (entity.gen() == All.id())
13699 matches = id.pair() && id.id() == entity.id();
13700 else
13701 matches = id == entity;
13702 } else
13703 matches = id == entity;
13704
13705 if (!matches)
13706 continue;
13707
13708 if (diffTermSet.insert(EntityLookupKey(id)).second)
13709 diffTerms.push_back(id);
13710 }
13711
13712 for (const auto* pChunk: pArchetype->chunks()) {
13713 const auto entities = pChunk->entity_view();
13714 GAIA_EACH(entities)
13715 diffTargets.push_back(entities[i]);
13716 }
13717 }
13718 auto delDiffCtx = diffTargets.empty() || diffTerms.empty()
13719 ? ObserverRegistry::DiffDispatchCtx{}
13720 : m_observers.prepare_diff(
13721 *this, ObserverEvent::OnDel, EntitySpan{diffTerms.data(), diffTerms.size()},
13722 EntitySpan{diffTargets.data(), diffTargets.size()});
13723#endif
13724
13725 // Update archetypes of all affected entities
13726 for (const auto& record: srcRecords) {
13727 auto* pArchetype = record.pArchetype;
13728 if (pArchetype->is_req_del() || !archetype_has_rows(*pArchetype))
13729 continue;
13730
13731 if (entity.pair()) {
13732 cnt::darray_ext<Entity, 16> removedIsTargets;
13733 for (auto id: pArchetype->ids_view()) {
13734 bool matches = false;
13735 if (entity.id() == All.id() && entity.gen() == All.id())
13736 matches = id.pair();
13737 else if (entity.id() == All.id())
13738 matches = id.pair() && id.gen() == entity.gen();
13739 else if (entity.gen() == All.id())
13740 matches = id.pair() && id.id() == entity.id();
13741 else
13742 matches = id == entity;
13743
13744 if (!matches || !id.pair() || id.id() != Is.id())
13745 continue;
13746
13747 const auto target = try_get(id.gen());
13748 if (target != EntityBad)
13749 removedIsTargets.push_back(target);
13750 }
13751
13752 if (!removedIsTargets.empty()) {
13753 for (const auto* pChunk: pArchetype->chunks()) {
13754 auto entities = pChunk->entity_view();
13755 GAIA_EACH(entities) {
13756 for (const auto target: removedIsTargets)
13757 unlink_live_is_relation(entities[i], target);
13758 }
13759 }
13760 }
13761 }
13762
13763 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
13764 if (pDstArchetype != nullptr) {
13765 if (entity.pair())
13766 del_sparse_pair_payloads(*pArchetype, entity);
13767 move_to_archetype(*pArchetype, *pDstArchetype);
13768 }
13769 }
13770
13771#if GAIA_OBSERVERS_ENABLED
13772 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
13773#endif
13774 expire_emptied_archetypes(srcRecords);
13775 }
13776
13781 void expire_emptied_archetypes(const ComponentIndexEntryArray& records) {
13782 for (const auto& record: records) {
13783 auto* pArchetype = record.pArchetype;
13784 if (pArchetype->is_req_del() || archetype_has_rows(*pArchetype))
13785 continue;
13786
13787 pArchetype->set_max_lifespan(1);
13788 try_enqueue_archetype_for_deletion(*pArchetype);
13789 }
13790 }
13791
13796 void rem_from_entities(Entity entity, Pair cond) {
13797 GAIA_PROF_SCOPE(World::rem_from_entities);
13798
13799 invalidate_pair_removal_caches(entity);
13800
13801 auto rem_nonfragmenting_pair = [&](Entity relation, Entity target) {
13802 if (!has(relation, cond))
13803 return;
13804
13805 cnt::darray<Entity> sourcesToRem;
13806 sources(relation, target, [&](Entity source) {
13807 sourcesToRem.push_back(source);
13808 });
13809
13810 for (auto source: sourcesToRem)
13811 del(source, Pair(relation, target));
13812 };
13813
13814 if (entity.pair()) {
13815 if (entity.id() != All.id()) {
13816 const auto relation = try_get(entity.id());
13817 const auto target = try_get(entity.gen());
13818 if (relation != EntityBad && target != EntityBad && relation_uses_non_fragmenting_storage(relation))
13819 rem_nonfragmenting_pair(relation, target);
13820 } else {
13821 const auto target = try_get(entity.gen());
13822 if (target == EntityBad)
13823 goto skip_rem_all_target_cond;
13824 for (const auto& [relKey, store]: m_nonFragmentingRelationsByRel) {
13825 if (store.sources(target) == nullptr)
13826 continue;
13827
13828 rem_nonfragmenting_pair(relKey.entity(), target);
13829 }
13830 skip_rem_all_target_cond:;
13831 }
13832 } else if (relation_uses_non_fragmenting_storage(entity)) {
13833 if (const auto* pTargets = targets(entity)) {
13834 for (auto targetKey: *pTargets)
13835 rem_nonfragmenting_pair(entity, targetKey.entity());
13836 }
13837 }
13838
13839 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
13840 if (it == m_entityToArchetypeMap.end())
13841 return;
13842
13843 // Copy the reverse-index records. calc_dst_archetype can insert into
13844 // m_entityToArchetypeMap and rehash, which invalidates `it`.
13845 const auto srcRecords = it->second;
13846
13847 // Invalidate the singleton status if necessary
13848 if (!entity.pair()) {
13849 auto& ec = fetch(entity);
13850 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0) {
13851 auto ids = ec.pArchetype->ids_view();
13852 const auto idx = core::get_index(ids, entity);
13853 if (idx != BadIndex)
13854 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::IsSingleton, false);
13855 }
13856 }
13857
13858#if GAIA_OBSERVERS_ENABLED
13859 cnt::set<EntityLookupKey> diffTermSet;
13860 cnt::darray<Entity> diffTerms;
13861 cnt::darray<Entity> diffTargets;
13862 for (const auto& record: srcRecords) {
13863 auto* pArchetype = record.pArchetype;
13864 if (pArchetype->is_req_del() || !archetype_has_rows(*pArchetype))
13865 continue;
13866
13867 if (!archetype_cond_match(*pArchetype, cond, entity))
13868 continue;
13869
13870 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
13871 if (pDstArchetype == nullptr)
13872 continue;
13873
13874 for (auto id: pArchetype->ids_view()) {
13875 bool matches = false;
13876 if (entity.pair()) {
13877 if (entity.id() == All.id() && entity.gen() == All.id())
13878 matches = id.pair();
13879 else if (entity.id() == All.id())
13880 matches = id.pair() && id.gen() == entity.gen();
13881 else if (entity.gen() == All.id())
13882 matches = id.pair() && id.id() == entity.id();
13883 else
13884 matches = id == entity;
13885 } else
13886 matches = id == entity;
13887
13888 if (!matches)
13889 continue;
13890
13891 if (diffTermSet.insert(EntityLookupKey(id)).second)
13892 diffTerms.push_back(id);
13893 }
13894
13895 for (const auto* pChunk: pArchetype->chunks()) {
13896 const auto entities = pChunk->entity_view();
13897 GAIA_EACH(entities)
13898 diffTargets.push_back(entities[i]);
13899 }
13900 }
13901 auto delDiffCtx = diffTargets.empty() || diffTerms.empty()
13902 ? ObserverRegistry::DiffDispatchCtx{}
13903 : m_observers.prepare_diff(
13904 *this, ObserverEvent::OnDel, EntitySpan{diffTerms.data(), diffTerms.size()},
13905 EntitySpan{diffTargets.data(), diffTargets.size()});
13906#endif
13907
13908 for (const auto& record: srcRecords) {
13909 auto* pArchetype = record.pArchetype;
13910 if (pArchetype->is_req_del() || !archetype_has_rows(*pArchetype))
13911 continue;
13912
13913 // Evaluate the condition if a valid pair is given
13914 if (!archetype_cond_match(*pArchetype, cond, entity))
13915 continue;
13916
13917 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
13918 if (pDstArchetype != nullptr) {
13919 if (entity.pair())
13920 del_sparse_pair_payloads(*pArchetype, entity);
13921 move_to_archetype(*pArchetype, *pDstArchetype);
13922 }
13923 }
13924
13925#if GAIA_OBSERVERS_ENABLED
13926 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
13927#endif
13928 expire_emptied_archetypes(srcRecords);
13929 }
13930
13934 void del_cascade_entity(Entity entity) {
13935 if (!valid(entity))
13936 return;
13937
13938 cnt::darray_ext<Entity, 64> pairEntities;
13939 collect_entity_pairs(entity, pairEntities);
13940 handle_del_entity(fetch(entity), entity, EntitySpan{pairEntities.data(), pairEntities.size()}, true);
13941 }
13942
13947 void del_pair_entities(EntitySpan pairEntities) {
13948 for (auto pair: pairEntities) {
13949 if (valid(pair))
13950 handle_del_entity(fetch(pair), pair, EntitySpan{}, true);
13951 }
13952 }
13953
13970 void handle_del_entity(
13971 EntityContainer& ec, Entity entity, EntitySpan pairEntities, [[maybe_unused]] bool cascade = false) {
13972 GAIA_PROF_SCOPE(World::handle_del_entity);
13973 ArchetypeRowDelScope scope(*this);
13974
13975 GAIA_ASSERT(!is_wildcard(entity));
13976 if (entity_deletion_active(entity))
13977 return;
13978
13979 if (entity.pair()) {
13980 if ((ec.flags & EntityContainerFlags::OnDelete_Error) != 0) {
13981 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted");
13982 GAIA_LOG_E(
13983 "Trying to delete a pair [%u.%u] %s that is forbidden from being deleted", entity.id(), entity.gen(),
13984 name(entity));
13985 return;
13986 }
13987
13988 const auto tgt = try_get(entity.gen());
13989 const bool hasLiveTarget = tgt != EntityBad;
13990 if (hasLiveTarget) {
13991 const auto& ecTgt = fetch(tgt);
13992 if ((ecTgt.flags & EntityContainerFlags::OnDeleteTarget_Error) != 0 ||
13993 has_nonfragmenting_relation_target_cond(tgt, Pair(OnDeleteTarget, Error))) {
13994 GAIA_ASSERT2(
13995 false, "Trying to delete an entity that is forbidden from being deleted (target restriction)");
13996 GAIA_LOG_E(
13997 "Trying to delete a pair [%u.%u] %s that is forbidden from being deleted (target restriction)",
13998 entity.id(), entity.gen(), name(entity));
13999 return;
14000 }
14001 }
14002
14003#if GAIA_USE_SAFE_ENTITY
14004 // Decrement the ref count at this point.
14005 release_world_ref(ec);
14006
14007 // Don't delete so long something still references us. A pair cannot outlive its endpoints,
14008 // so one losing an endpoint goes anyway, and its record stays reserved until released.
14009 if (ec.refCnt != 0 && !cascade)
14010 return;
14011#endif
14012
14013 entity_deletion_enter(entity);
14014
14015 // The target's OnDeleteTarget rules are not applied here. They cover every (*, target) pair,
14016 // such as ChildOf children, and belong to deleting the target, which applies them itself.
14017
14018 // This entity has been requested to be deleted already. Nothing more for us to do here
14019 if (is_req_del_or_queued(ec)) {
14020 entity_deletion_leave(entity);
14021 return;
14022 }
14023
14024 del_entity_registrations(entity);
14025 del_entity_id_rules(ec, entity);
14026 } else {
14027 if ((ec.flags & EntityContainerFlags::OnDelete_Error) != 0) {
14028 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted");
14029 GAIA_LOG_E(
14030 "Trying to delete an entity [%u.%u] %s that is forbidden from being deleted", entity.id(), entity.gen(),
14031 name(entity));
14032 return;
14033 }
14034
14035 if ((ec.flags & EntityContainerFlags::OnDeleteTarget_Error) != 0 ||
14036 has_nonfragmenting_relation_target_cond(entity, Pair(OnDeleteTarget, Error))) {
14037 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted (a pair's target)");
14038 GAIA_LOG_E(
14039 "Trying to delete an entity [%u.%u] %s that is forbidden from being deleted (a pair's target)",
14040 entity.id(), entity.gen(), name(entity));
14041 return;
14042 }
14043
14044#if GAIA_USE_SAFE_ENTITY
14045 // Decrement the ref count at this point.
14046 release_world_ref(ec);
14047
14048 // Don't delete so long something still references us, unless a cleanup rule deletes us
14049 if (ec.refCnt != 0 && !cascade)
14050 return;
14051#endif
14052
14053 entity_deletion_enter(entity);
14054
14055 del_entity_target_rules(ec, entity);
14056
14057 // This entity is has been requested to be deleted already. Nothing more for us to do here
14058 if (is_req_del_or_queued(ec)) {
14059 del_pair_entities(pairEntities);
14060 entity_deletion_leave(entity);
14061 return;
14062 }
14063
14064 del_entity_registrations(entity);
14065 del_entity_id_rules(ec, entity);
14066 }
14067
14068 del_pair_entities(pairEntities);
14069
14070 // Mark the entity with the "delete requested" flag
14071 req_del_inter(ec, entity);
14072
14073 entity_deletion_leave(entity);
14074 }
14075
14080 static void invalidate_weak_entities([[maybe_unused]] EntityContainer& ec) {
14081#if GAIA_USE_WEAK_ENTITY
14082 while (ec.pWeakTracker != nullptr) {
14083 auto* pTracker = ec.pWeakTracker;
14084 ec.pWeakTracker = pTracker->next;
14085 if (ec.pWeakTracker != nullptr)
14086 ec.pWeakTracker->prev = nullptr;
14087
14088 auto* pWeakEntity = pTracker->pWeakEntity;
14089 GAIA_ASSERT(pWeakEntity != nullptr);
14090 GAIA_ASSERT(pWeakEntity->m_pTracker == pTracker);
14091 pWeakEntity->m_pTracker = nullptr;
14092 pWeakEntity->m_entity = EntityBad;
14093 delete pTracker;
14094 }
14095#endif
14096 }
14097
14102 void remove_edge_from_archetype(Archetype* pArchetype, ArchetypeGraphEdge edgeLeft, Entity edgeEntity) {
14103 GAIA_ASSERT(pArchetype != nullptr);
14104
14105 const auto edgeLeftIt = m_archetypesById.find(ArchetypeIdLookupKey(edgeLeft.id, edgeLeft.hash));
14106 if (edgeLeftIt == m_archetypesById.end())
14107 return;
14108
14109 auto* pArchetypeLeft = edgeLeftIt->second;
14110 GAIA_ASSERT(pArchetypeLeft != nullptr);
14111
14112 // Remove the connection with the current archetype
14113 pArchetypeLeft->del_graph_edges(pArchetype, edgeEntity);
14114
14115 // Traverse all archetypes on the right
14116 auto& archetypesRight = pArchetype->right_edges();
14117 for (auto& it: archetypesRight) {
14118 const auto& edgeRight = it.second;
14119 const auto edgeRightIt = m_archetypesById.find(ArchetypeIdLookupKey(edgeRight.id, edgeRight.hash));
14120 if (edgeRightIt == m_archetypesById.end())
14121 continue;
14122
14123 auto* pArchetypeRight = edgeRightIt->second;
14124
14125 // Remove the connection with the current archetype
14126 pArchetype->del_graph_edges(pArchetypeRight, it.first.entity());
14127 }
14128 }
14129
14132 void remove_edges(Entity entityToRemove) {
14133 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entityToRemove));
14134 if (it == m_entityToArchetypeMap.end())
14135 return;
14136
14137 for (const auto& record: it->second) {
14138 auto* pArchetype = record.pArchetype;
14139 remove_edge_from_archetype(pArchetype, pArchetype->find_edge_left(entityToRemove), entityToRemove);
14140 }
14141 }
14142
14145 void remove_edges_from_pairs(Entity entity) {
14146 if (entity.pair())
14147 return;
14148
14149 // Make sure to remove all pairs containing the entity
14150 // (X, something)
14151 const auto* tgts = targets(entity);
14152 if (tgts != nullptr) {
14153 for (auto target: *tgts)
14154 remove_edges(Pair(entity, target.entity()));
14155 }
14156 // (something, X)
14157 const auto* rels = relations(entity);
14158 if (rels != nullptr) {
14159 for (auto relation: *rels)
14160 remove_edges(Pair(relation.entity(), entity));
14161 }
14162 }
14163
14166 void del_graph_edges(Entity entity) {
14167 remove_edges(entity);
14168 remove_edges_from_pairs(entity);
14169 }
14170
14173 void touch_rel_version(Entity relation) {
14174 if (m_pLastRelationVersion != nullptr && m_lastRelationVersionRelation == relation) {
14175 ++*m_pLastRelationVersion;
14176 if (*m_pLastRelationVersion == 0)
14177 *m_pLastRelationVersion = 1;
14178 return;
14179 }
14180
14181 const EntityLookupKey key(relation);
14182 const auto ret = m_relationVersions.try_emplace(key, 1);
14183 auto it = ret.first;
14184 if (!ret.second) {
14185 ++it->second;
14186 if (it->second == 0)
14187 it->second = 1;
14188 }
14189
14190 m_lastRelationVersionRelation = relation;
14191 m_pLastRelationVersion = &it->second;
14192 }
14193
14195 void clear_relation_caches() {
14196 if (!m_relationCachesPopulated)
14197 return;
14198 m_relationCachesPopulated = false;
14199
14200 m_targetsTravCache = {};
14201 m_srcBfsTravCache = {};
14202 m_depthOrderCache = {};
14203 m_sourcesAllCache = {};
14204 m_targetsAllCache = {};
14205 }
14206
14209 void invalidate_relation_caches(Entity relation) {
14210 touch_rel_version(relation);
14211 invalidate_queries_for_rel(relation);
14212 clear_relation_caches();
14213 }
14214
14221 bool del_pair_record(Entity entity, Archetype*& pArchetype, Entity& rel, Entity& tgt) {
14222 GAIA_ASSERT(entity.pair());
14223
14224 EntityContainer ec{};
14225 if (!m_recs.pair_record_remove(entity, ec))
14226 return false;
14227
14228 pArchetype = ec.pArchetype;
14229
14230 // The relation or target entity may already be invalid at this point. Rebuild the
14231 // lookup keys from the stored entity records instead of calling get().
14232 GAIA_ASSERT(entity.id() < m_recs.entities.size());
14233 GAIA_ASSERT(entity.gen() < m_recs.entities.size());
14234 rel = m_recs.entities.handle(entity.id());
14235 tgt = m_recs.entities.handle(entity.gen());
14236 return true;
14237 }
14238
14242 void del_pair_lookup(Entity rel, Entity tgt) {
14243 m_pairLookup.del_pair(rel, tgt);
14244 }
14245
14248 void del_entity_pair_lookup(Entity entity) {
14249 m_pairLookup.del_entity_pairs(entity);
14250 }
14251
14254 void del_pair_data_for_entity(Entity entity) {
14255 Archetype* pArchetype = nullptr;
14256 // Exact pair records are valid Gaia entities and can own out-of-archetype state.
14257 del_sparse_components(entity);
14258 del_nonfragmenting_relation_source(entity);
14259
14260 if (entity.pair()) {
14261 // The pair entity identifies the store for sparse relationship payloads.
14262 del_sparse_component_store(entity);
14263 // A WeakEntity created after the deletion request must not outlive the record.
14264 if (auto* pEc = m_recs.pair_record_find(entity))
14265 invalidate_weak_entities(*pEc);
14266 Entity rel;
14267 Entity tgt;
14268 if (del_pair_record(entity, pArchetype, rel, tgt))
14269 del_pair_lookup(rel, tgt);
14270 } else {
14271 auto& rec = m_recs.entities[entity.id()];
14272 // A WeakEntity created after the deletion request must not outlive the record.
14273 invalidate_weak_entities(rec);
14274
14275 // Update the container record
14276 auto ec = rec;
14277 m_recs.entities.free(entity);
14278
14279 // If the deleted entity is itself a non-fragmenting exclusive relation, drop its store.
14280 del_nonfragmenting_relation(entity);
14281 // If the deleted entity is itself a sparse-storage component, drop its store.
14282 del_sparse_component_store(entity);
14283
14284 // If this is a singleton entity its archetype needs to be deleted,
14285 // unless the archetype went first
14286 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0 && ec.pArchetype != nullptr) {
14287 ArchetypeRowDelScope scope(*this);
14288 req_del(*ec.pArchetype);
14289 }
14290
14291 ec.pArchetype = nullptr;
14292 ec.pChunk = nullptr;
14293 ec.pEntity = nullptr;
14294 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::DeleteRequested, false);
14295
14296 // Update pair lookup entries.
14297 del_entity_pair_lookup(entity);
14298 }
14299
14300 del_entity_archetype_pairs(entity, pArchetype);
14301 }
14302
14305 void invalidate_entity(Entity entity) {
14306 del_graph_edges(entity);
14307 del_pair_data_for_entity(entity);
14308 }
14309
14315 void store_entity(EntityContainer& ec, Entity entity, Archetype* pArchetype, Chunk* pChunk) {
14316 GAIA_ASSERT(pArchetype != nullptr);
14317 GAIA_ASSERT(pChunk != nullptr);
14318 GAIA_ASSERT(
14319 !locked() && "Entities can't be stored while the world is locked "
14320 "(structural changes are forbidden during this time!)");
14321
14322 ec.pArchetype = pArchetype;
14323 ec.pChunk = pChunk;
14324 ec.row = pChunk->add_entity(entity);
14325 ec.pEntity = &pChunk->entity_view()[ec.row];
14326 GAIA_ASSERT(entity.pair() || ec.data.gen == entity.gen());
14327 ec.data.dis = 0;
14328 }
14329
14335 void move_entity(Entity entity, EntityContainer& ec, Archetype& dstArchetype, Chunk& dstChunk) {
14336 GAIA_PROF_SCOPE(World::move_entity);
14337
14338 auto* pDstChunk = &dstChunk;
14339 auto* pSrcChunk = ec.pChunk;
14340
14341 GAIA_ASSERT(pDstChunk != pSrcChunk);
14342
14343 const auto srcRow0 = ec.row;
14344 const auto dstRow = pDstChunk->add_entity(entity);
14345 const bool wasEnabled = !ec.data.dis;
14346
14347 auto& srcArchetype = *ec.pArchetype;
14348 const bool archetypeChanged = srcArchetype.id() != dstArchetype.id();
14349#if GAIA_ASSERT_ENABLED
14350 verify_move(*this, srcArchetype, entity);
14351#endif
14352
14353 // Make sure the old entity becomes enabled now
14354 srcArchetype.enable_entity(pSrcChunk, srcRow0, true, m_recs);
14355 // Enabling the entity might have changed its chunk index so fetch it again
14356 const auto srcRow = ec.row;
14357
14358 // Move data from the old chunk to the new one
14359 if (dstArchetype.id() == srcArchetype.id()) {
14360 pDstChunk->move_entity_data(entity, dstRow, m_recs);
14361 } else {
14362 pDstChunk->move_foreign_entity_data(pSrcChunk, srcRow, pDstChunk, dstRow);
14363 }
14364
14365 // Remove the entity record from the old chunk
14366 remove_entity(srcArchetype, *pSrcChunk, srcRow);
14367
14368 // An entity might have moved, try updating the free chunk index
14369 dstArchetype.try_update_free_chunk_idx();
14370
14371 // Bring the entity container record up-to-date
14372 ec.pArchetype = &dstArchetype;
14373 ec.pChunk = pDstChunk;
14374 ec.row = (uint16_t)dstRow;
14375 ec.pEntity = &pDstChunk->entity_view()[dstRow];
14376 if (archetypeChanged)
14377 update_src_entity_version(entity);
14378
14379 // Make the enabled state in the new chunk match the original state
14380 dstArchetype.enable_entity(pDstChunk, dstRow, wasEnabled, m_recs);
14381
14382 // End-state validation
14383 GAIA_ASSERT(valid(entity));
14384 validate_chunk(pSrcChunk);
14385 validate_chunk(pDstChunk);
14386 validate_entities();
14387 }
14388
14393 void move_entity_raw(Entity entity, EntityContainer& ec, Archetype& dstArchetype) {
14394 // Update the old chunk's world version first
14395 ec.pChunk->update_world_version();
14396 ec.pChunk->update_entity_order_version();
14397
14398 auto* pDstChunk = dstArchetype.foc_free_chunk();
14399 move_entity(entity, ec, dstArchetype, *pDstChunk);
14400
14401 // Update world versions
14402 pDstChunk->update_world_version();
14403 pDstChunk->update_entity_order_version();
14404 update_version(m_worldVersion);
14405 }
14406
14411 Chunk* move_entity(Entity entity, Archetype& dstArchetype) {
14412 // Archetypes need to be different
14413 auto& ec = fetch(entity);
14414 if (ec.pArchetype == &dstArchetype)
14415 return nullptr;
14416
14417 // Update the old chunk's world version first
14418 ec.pChunk->update_world_version();
14419 ec.pChunk->update_entity_order_version();
14420
14421 auto* pDstChunk = dstArchetype.foc_free_chunk();
14422 move_entity(entity, ec, dstArchetype, *pDstChunk);
14423
14424 // Update world versions
14425 pDstChunk->update_world_version();
14426 pDstChunk->update_entity_order_version();
14427 update_version(m_worldVersion);
14428
14429 return pDstChunk;
14430 }
14431
14434 void validate_archetype_edges([[maybe_unused]] const Archetype* pArchetype) const {
14435#if GAIA_ECS_VALIDATE_ARCHETYPE_GRAPH && GAIA_ASSERT_ENABLED
14436 GAIA_ASSERT(pArchetype != nullptr);
14437
14438 // Validate left edges
14439 const auto& archetypesLeft = pArchetype->left_edges();
14440 for (const auto& it: archetypesLeft) {
14441 const auto& edge = it.second;
14442 const auto edgeIt = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
14443 if (edgeIt == m_archetypesById.end())
14444 continue;
14445
14446 const auto entity = it.first.entity();
14447 const auto* pArchetypeRight = edgeIt->second;
14448
14449 // Edge must be found
14450 const auto edgeRight = pArchetypeRight->find_edge_right(entity);
14451 GAIA_ASSERT(edgeRight != ArchetypeIdHashPairBad);
14452
14453 // The edge must point to pArchetype
14454 const auto it2 = m_archetypesById.find(ArchetypeIdLookupKey(edgeRight.id, edgeRight.hash));
14455 GAIA_ASSERT(it2 != m_archetypesById.end());
14456 const auto* pArchetype2 = it2->second;
14457 GAIA_ASSERT(pArchetype2 == pArchetype);
14458 }
14459
14460 // Validate right edges
14461 const auto& archetypesRight = pArchetype->right_edges();
14462 for (const auto& it: archetypesRight) {
14463 const auto& edge = it.second;
14464 const auto edgeIt = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
14465 if (edgeIt == m_archetypesById.end())
14466 continue;
14467
14468 const auto entity = it.first.entity();
14469 const auto* pArchetypeRight = edgeIt->second;
14470
14471 // Edge must be found
14472 const auto edgeLeft = pArchetypeRight->find_edge_left(entity);
14473 GAIA_ASSERT(edgeLeft != ArchetypeIdHashPairBad);
14474
14475 // The edge must point to pArchetype
14476 const auto it2 = m_archetypesById.find(ArchetypeIdLookupKey(edgeLeft.id, edgeLeft.hash));
14477 GAIA_ASSERT(it2 != m_archetypesById.end());
14478 const auto* pArchetype2 = it2->second;
14479 GAIA_ASSERT(pArchetype2 == pArchetype);
14480 }
14481#endif
14482 }
14483
14485 void validate_entities() const {
14486#if GAIA_ECS_VALIDATE_ENTITY_LIST
14487 m_recs.entities.validate();
14488#endif
14489 }
14490
14493 void validate_chunk([[maybe_unused]] Chunk* pChunk) const {
14494#if GAIA_ECS_VALIDATE_CHUNKS && GAIA_ASSERT_ENABLED
14495 GAIA_ASSERT(pChunk != nullptr);
14496
14497 const auto entities = pChunk->entity_view();
14498 for (uint16_t row = 0; row < entities.size(); ++row) {
14499 const auto entity = entities[row];
14500 const EntityContainer* pEc = nullptr;
14501 if (entity.pair()) {
14502 pEc = m_recs.pair_record_find(entity);
14503 } else if (entity.id() < m_recs.entities.size()) {
14504 pEc = &m_recs.entities[entity.id()];
14505 }
14506
14507 if (pEc == nullptr) {
14508 GAIA_ASSERT(!valid(entity));
14509 continue;
14510 }
14511 if (!valid(entity)) {
14512 GAIA_ASSERT(is_req_del_or_queued(*pEc));
14513 continue;
14514 }
14515 GAIA_ASSERT(pEc->pChunk == pChunk);
14516 GAIA_ASSERT(pEc->row == row);
14517 GAIA_ASSERT(pEc->pEntity == &entities[row]);
14518 }
14519
14520 for (const auto& ec: m_recs.entities) {
14521 if (ec.pChunk != pChunk)
14522 continue;
14523 const Entity entity(ec.idx, ec.data.gen, ec.data.ent != 0, false);
14524 if (!valid(entity)) {
14525 GAIA_ASSERT(is_req_del_or_queued(ec));
14526 continue;
14527 }
14528 GAIA_ASSERT(ec.row < entities.size());
14529 GAIA_ASSERT(entities[ec.row] == entity);
14530 GAIA_ASSERT(ec.pEntity == &entities[ec.row]);
14531 }
14532
14533 for (auto it = m_recs.pair_record_begin(); it != m_recs.pair_record_end(); ++it) {
14534 const auto& pair = *it;
14535 const auto& ec = pair.second;
14536 if (ec.pChunk != pChunk)
14537 continue;
14538 const auto entity = pair.first.entity();
14539 if (!valid(entity)) {
14540 GAIA_ASSERT(is_req_del_or_queued(ec));
14541 continue;
14542 }
14543 GAIA_ASSERT(ec.row < entities.size());
14544 GAIA_ASSERT(entities[ec.row] == entity);
14545 GAIA_ASSERT(ec.pEntity == &entities[ec.row]);
14546 }
14547#endif
14548 }
14549
14556 template <bool CheckIn>
14557 GAIA_NODISCARD bool is_inter(Entity entity, Entity entityBase) const {
14558 if GAIA_UNLIKELY (entity.pair() || entityBase.pair())
14559 return false;
14560
14561 GAIA_ASSERT(valid_entity(entity));
14562 GAIA_ASSERT(valid_entity(entityBase));
14563
14564 if constexpr (!CheckIn) {
14565 if (entity == entityBase)
14566 return true;
14567 }
14568
14569 const auto& targets = as_targets_trav_cache(entity);
14570 for (auto target: targets) {
14571 if (target == entityBase)
14572 return true;
14573 }
14574
14575 return false;
14576 }
14577
14583 template <bool CheckIn, typename Func>
14584 void as_up_trav(Entity entity, Func func) {
14585 if GAIA_UNLIKELY (entity.pair())
14586 return;
14587
14588 // A delete-requested base is not valid() but keeps its record and archetype until finalization.
14589 // Bulk deletion unlinks an instance's Is edge after marking its prefab's archetype.
14590 GAIA_ASSERT(try_entity_record(entity) != nullptr);
14591
14592 if constexpr (!CheckIn) {
14593 func(entity);
14594 }
14595
14596 const auto& ec = m_recs.entities[entity.id()];
14597 const auto* pArchetype = ec.pArchetype;
14598
14599 // Early exit if there are no Is relationship pairs on the archetype
14600 if (pArchetype->pairs_is() == 0)
14601 return;
14602
14603 for (uint32_t i = 0; i < pArchetype->pairs_is(); ++i) {
14604 auto e = pArchetype->entity_from_pairs_as_idx(i);
14605 const auto& ecTarget = m_recs.entities[e.gen()];
14606 auto target = *ecTarget.pEntity;
14607 func(target);
14608
14609 as_up_trav<CheckIn>(target, func);
14610 }
14611 }
14612
14618 template <typename T>
14619 const ComponentCacheItem& reg_core_entity(Entity id, Archetype* pArchetype) {
14620 auto comp = add(*pArchetype, id.entity(), id.pair());
14621 const auto& ci = comp_cache_mut().add<T>(id);
14622 GAIA_ASSERT(ci.entity == id);
14623 GAIA_ASSERT(comp == id);
14624 (void)comp;
14625 return ci;
14626 }
14627
14632 template <typename T>
14633 const ComponentCacheItem& reg_core_entity(Entity id) {
14634 return reg_core_entity<T>(id, m_pRootArchetype);
14635 }
14636
14642 static ComponentDesc primitive_type_desc(const char* name, uint32_t nameLen, uint32_t size) {
14643 ComponentDesc desc{};
14644 desc.name = util::str_view(name, nameLen);
14645 desc.size = size;
14646 desc.alig = size;
14647 desc.storageType = DataStorageType::Table;
14648 desc.runtimeType.typeKind = RuntimeTypeKind::Primitive;
14649 return desc;
14650 }
14651
14658 const ComponentCacheItem& reg_core_primitive_type(Entity id, const char* name, uint32_t nameLen, uint32_t size) {
14659 auto comp = add(*m_pCompArchetype, id.entity(), id.pair());
14660 const auto desc = primitive_type_desc(name, nameLen, size);
14661 const auto& ci = comp_cache_mut().add(id, desc);
14662 GAIA_ASSERT(ci.entity == id);
14663 GAIA_ASSERT(comp == id);
14664 (void)comp;
14665 finalize_component_registration(ci, false);
14666 return ci;
14667 }
14668
14670 void init();
14671
14673 void done() {
14674 cleanup_inter();
14675
14676#if GAIA_ECS_CHUNK_ALLOCATOR
14677 ChunkAllocator::get().flush();
14678#endif
14679 }
14680
14684 void assign_entity(Entity entity, Archetype& archetype) {
14685 GAIA_ASSERT(!entity.pair());
14686
14687 auto* pChunk = archetype.foc_free_chunk();
14688 store_entity(m_recs.entities[entity.id()], entity, &archetype, pChunk);
14689 pChunk->update_versions();
14690 archetype.try_update_free_chunk_idx();
14691
14692 // Call constructors for components on the newly added entity if necessary
14693 pChunk->call_ctors(pChunk->size() - 1, 1);
14694
14695#if GAIA_ASSERT_ENABLED
14696 const auto& ec = m_recs.entities[entity.id()];
14697 GAIA_ASSERT(ec.pChunk == pChunk);
14698 auto entityExpected = pChunk->entity_view()[ec.row];
14699 GAIA_ASSERT(entityExpected == entity);
14700#endif
14701 }
14702
14707 bool assign_pair_record(Entity entity, Archetype& archetype) {
14708 GAIA_ASSERT(entity.pair());
14709
14710 // Pairs are always added to m_pEntityArchetype initially and this can't change.
14711 GAIA_ASSERT(&archetype == m_pEntityArchetype);
14712
14713 auto init_record = [&](EntityContainer& ec) {
14714 ec = {};
14715 ec.idx = entity.id();
14716 ec.data.gen = entity.gen();
14717 ec.data.pair = 1;
14718 ec.data.ent = 1;
14719
14720 auto* pChunk = archetype.foc_free_chunk();
14721 store_entity(ec, entity, &archetype, pChunk);
14722 pChunk->update_versions();
14723 archetype.try_update_free_chunk_idx();
14724 };
14725
14726 auto* pRecord = m_recs.pair_record_find(entity);
14727 if (pRecord != nullptr) {
14728 if ((pRecord->flags & EntityContainerFlags::DeleteRequested) == 0)
14729 return false;
14730
14731 m_reqEntitiesToDel.erase(EntityLookupKey(entity));
14732 // Pair ids carry no generation, so the revived pair is a new incarnation. Reset the
14733 // WeakEntities created since the deletion request before the reset drops their trackers.
14734 invalidate_weak_entities(*pRecord);
14735 init_record(*pRecord);
14736 return true;
14737 }
14738
14739 EntityContainer ec{};
14740 init_record(ec);
14741 m_recs.pair_record_add(entity, GAIA_MOV(ec));
14742 return true;
14743 }
14744
14747 void add_pair_lookup(Entity entity) {
14748 GAIA_ASSERT(entity.pair());
14749
14750 const auto rel = get(entity.id());
14751 const auto tgt = get(entity.gen());
14752 m_pairLookup.add_pair(rel, tgt);
14753 }
14754
14758 void assign_pair(Entity entity, Archetype& archetype) {
14759 if (!assign_pair_record(entity, archetype))
14760 return;
14761
14762 add_pair_lookup(entity);
14763
14764#if GAIA_OBSERVERS_ENABLED
14765 m_observers.try_mark_term_observed(*this, entity);
14766#endif
14767 }
14768
14774 GAIA_NODISCARD Entity add(Archetype& archetype, bool isEntity, bool isPair) {
14775 EntityContainerCtx ctx{isEntity, isPair};
14776 const auto entity = m_recs.entities.alloc(&ctx);
14777 assign_entity(entity, archetype);
14778 return entity;
14779 }
14780
14786 template <typename Func>
14787 void add_entity_n(Archetype& archetype, uint32_t count, Func func) {
14788 EntityContainerCtx ctx{true, false};
14789#if GAIA_OBSERVERS_ENABLED
14790 const auto addedIds = EntitySpan{archetype.ids_view()};
14791 ObserverRegistry::DiffDispatchCtx addDiffCtx{};
14792 if (!addedIds.empty())
14793 addDiffCtx = m_observers.prepare_diff_add_new(*this, addedIds);
14794#endif
14795
14796 uint32_t left = count;
14797 do {
14798 auto* pChunk = archetype.foc_free_chunk();
14799 const uint32_t originalChunkSize = pChunk->size();
14800 const uint32_t freeSlotsInChunk = pChunk->capacity() - originalChunkSize;
14801 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
14802
14803 GAIA_FOR(toCreate) {
14804 const auto entityNew = m_recs.entities.alloc(&ctx);
14805 auto& ecNew = m_recs.entities[entityNew.id()];
14806 store_entity(ecNew, entityNew, &archetype, pChunk);
14807
14808#if GAIA_ASSERT_ENABLED
14809 GAIA_ASSERT(ecNew.pChunk == pChunk);
14810 auto entityExpected = pChunk->entity_view()[ecNew.row];
14811 GAIA_ASSERT(entityExpected == entityNew);
14812#endif
14813 }
14814
14815 // New entities were added, try updating the free chunk index
14816 archetype.try_update_free_chunk_idx();
14817
14818 // Call constructors for components on the newly added entity if necessary
14819 pChunk->call_ctors(originalChunkSize, toCreate);
14820
14821 // Call functors
14822 {
14823 auto entities = pChunk->entity_view();
14824 GAIA_FOR2(originalChunkSize, pChunk->size()) func(entities[i]);
14825 }
14826
14827 pChunk->update_versions();
14828
14829#if GAIA_OBSERVERS_ENABLED
14830 if (!addedIds.empty()) {
14831 auto entities = pChunk->entity_view();
14832 const auto targets = EntitySpan{entities.data() + originalChunkSize, toCreate};
14833 m_observers.add_diff_targets(*this, addDiffCtx, targets);
14834 m_observers.on_add(*this, archetype, addedIds, targets);
14835 }
14836#endif
14837
14838 left -= toCreate;
14839 } while (left > 0);
14840
14841#if GAIA_OBSERVERS_ENABLED
14842 if (!addedIds.empty())
14843 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
14844#endif
14845 }
14846
14848 void gc() {
14849 GAIA_PROF_SCOPE(World::gc);
14850
14851 del_empty_chunks();
14852 defrag_chunks(m_defragEntitiesPerTick);
14853 del_empty_archetypes();
14854 }
14855
14856 public:
14861 QuerySerBuffer& query_buffer(QueryId& serId) {
14862 // No serialization id set on the query, try creating a new record
14863 if GAIA_UNLIKELY (serId == QueryIdBad) {
14864#if GAIA_ASSERT_ENABLED
14865 uint32_t safetyCounter = 0;
14866#endif
14867
14868 while (true) {
14869#if GAIA_ASSERT_ENABLED
14870 // Make sure we don't cross some safety threshold
14871 ++safetyCounter;
14872 GAIA_ASSERT(safetyCounter < 100000);
14873#endif
14874
14875 serId = ++m_nextQuerySerId;
14876 // Make sure we do not overflow
14877 GAIA_ASSERT(serId != 0);
14878
14879 // If the id is already found, try again.
14880 // Note, this is essentially never going to repeat. We would have to prepare millions if
14881 // not billions of queries for which we only added inputs but never queried them.
14882 auto ret = m_querySerMap.try_emplace(serId);
14883 if (!ret.second)
14884 continue;
14885
14886 return ret.first->second;
14887 };
14888 }
14889
14890 return m_querySerMap[serId];
14891 }
14892
14895 void query_buffer_reset(QueryId& serId) {
14896 auto it = m_querySerMap.find(serId);
14897 if (it == m_querySerMap.end())
14898 return;
14899
14900 m_querySerMap.erase(it);
14901 serId = QueryIdBad;
14902 }
14903
14907 m_queryCache.invalidate_queries_for_entity(entityKey, QueryCache::ChangeKind::Structural);
14908 }
14909
14913 if (!m_queryCache.has_relation_query_dependencies())
14914 return;
14915
14916 m_queryCache.invalidate_queries_for_rel(relation, QueryCache::ChangeKind::DynamicResult);
14917 }
14918
14921 GAIA_NODISCARD bool has_sorted_queries() const {
14922 return m_queryCache.has_sorted_queries();
14923 }
14924
14928 GAIA_NODISCARD bool has_sorted_queries_for_entity(Entity entity) const {
14929 return m_queryCache.has_sorted_queries_for_entity(entity);
14930 }
14931
14935 m_queryCache.invalidate_sorted_queries_for_entity(entity);
14936 }
14937
14940 m_queryCache.invalidate_sorted_queries();
14941 }
14942
14946 GAIA_ASSERT(is_pair.first() == Is);
14947
14948 // We still need to handle invalidation "down-the-tree".
14949 // E.g. following setup:
14950 // q = w.query().all({Is,animal});
14951 // w.as(wolf, carnivore);
14952 // w.as(carnivore, animal);
14953 // q.each() ...; // animal, carnivore, wolf
14954 // w.del(wolf, {Is,carnivore}) // wolf is no longer a carnivore and thus no longer an animal
14955 // After this deletion, we need to invalidate "q" because wolf is no longer an animal
14956 // and we don't want q to include it.
14957 // q.each() ...; // animal
14958
14959 auto e = is_pair.second();
14960 as_up_trav<false>(e, [&](Entity target) {
14961 // Invalidate all queries that contain everything in our path.
14962 invalidate_queries_for_structural_entity(EntityLookupKey(Pair{Is, target}));
14963 });
14964 }
14965
14970 Entity name_to_entity(std::span<const char> exprRaw) const {
14971 auto expr = util::trim(exprRaw);
14972 if (expr.empty())
14973 return EntityBad;
14974
14975 if (expr[0] == '(') {
14976 if (expr.size() < 5 || expr.back() != ')')
14977 return EntityBad;
14978
14979 const auto idStr = expr.subspan(1, expr.size() - 2);
14980 const auto commaIdx = core::get_index(idStr, ',');
14981 if (commaIdx == BadIndex)
14982 return EntityBad;
14983
14984 const auto first = name_to_entity(idStr.subspan(0, commaIdx));
14985 if (first == EntityBad || first.pair())
14986 return EntityBad;
14987 const auto second = name_to_entity(idStr.subspan(commaIdx + 1));
14988 if (second == EntityBad || second.pair())
14989 return EntityBad;
14990
14991 return ecs::Pair(first, second);
14992 }
14993
14994 // Wildcard character
14995 if (expr.size() == 1 && expr[0] == '*')
14996 return All;
14997
14998 return get_inter(expr.data(), (uint32_t)expr.size());
14999 }
15000
15006 Entity expr_to_entity(va_list& args, std::span<const char> exprRaw) const {
15007 auto expr = util::trim(exprRaw);
15008
15009 if (expr[0] == '%') {
15010 if (expr[1] != 'e') {
15011 GAIA_ASSERT2(false, "Expression '%' not terminated");
15012 return EntityBad;
15013 }
15014
15015 auto id = (Identifier)va_arg(args, unsigned long long);
15016 return Entity(id);
15017 }
15018
15019 if (expr[0] == '(') {
15020 if (expr.back() != ')') {
15021 GAIA_ASSERT2(false, "Expression '(' not terminated");
15022 return EntityBad;
15023 }
15024
15025 const auto idStr = expr.subspan(1, expr.size() - 2);
15026 const auto commaIdx = core::get_index(idStr, ',');
15027
15028 const auto first = expr_to_entity(args, idStr.subspan(0, commaIdx));
15029 if (first == EntityBad)
15030 return EntityBad;
15031 const auto second = expr_to_entity(args, idStr.subspan(commaIdx + 1));
15032 if (second == EntityBad)
15033 return EntityBad;
15034
15035 return ecs::Pair(first, second);
15036 }
15037
15038 {
15039 auto idStr = util::trim(expr);
15040
15041 // Wildcard character
15042 if (idStr.size() == 1 && idStr[0] == '*')
15043 return All;
15044
15045 // Anything else is a component name
15046 const auto* pItem = resolve_component_name_inter(idStr.data(), (uint32_t)idStr.size());
15047 if (pItem == nullptr) {
15048 GAIA_ASSERT2(false, "Component not found");
15049 GAIA_LOG_W("Component '%.*s' not found", (uint32_t)idStr.size(), idStr.data());
15050 return EntityBad;
15051 }
15052
15053 return pItem->entity;
15054 }
15055 }
15056 };
15057
15059 inline ComponentRawView world_get_raw(const World& world, Entity entity, Entity component) {
15060 return world.get_raw(entity, component);
15061 }
15062
15068 inline bool world_bind_raw_sparse_store(const World& world, Entity component, RawSparseStoreOps& ops) {
15069 const auto* pStore = world.sparse_component_store_erased(component);
15070 if (pStore == nullptr)
15071 return false;
15072
15073 const auto* pItem =
15074 component.pair() ? world.comp_cache().find_pair_payload(component) : world.comp_cache().find(component);
15075 GAIA_ASSERT(pItem != nullptr);
15076 ops = {pStore->pStore, pStore->func_get, pStore->func_mut, pStore->func_has, pItem->comp.size()};
15077 return true;
15078 }
15079
15081 inline ComponentRawMutView world_mut_raw(World& world, Entity entity, Entity component) {
15082 return world.mut_raw(entity, component);
15083 }
15084
15086 inline ComponentRawView
15087 world_get_raw_field(const World& world, Entity entity, Entity component, uint32_t fieldIdx) {
15088 return world.get_raw_field(entity, component, fieldIdx);
15089 }
15090
15092 inline ComponentRawMutView world_mut_raw_field(World& world, Entity entity, Entity component, uint32_t fieldIdx) {
15093 return world.mut_raw_field(entity, component, fieldIdx);
15094 }
15095
15096 inline ComponentCursor World::cursor(Entity entity, Entity component) const {
15097 if (component != EntityBad && valid(entity)) {
15098 const auto owner = id_owner_inter(entity, component);
15099 if (owner != EntityBad) {
15100 const auto* pItem = component_item(owner, component);
15101 if (pItem != nullptr && soa_field_supported(*pItem)) {
15102 const auto& ec = fetch(owner);
15103 if (ec.pChunk->comp_idx(component) != ComponentIndexBad)
15104 return ComponentCursor::from_soa(*this, comp_cache(), owner, component, pItem->comp.size());
15105 }
15106 }
15107 }
15108
15109 return ComponentCursor::from_raw(comp_cache(), component, get_raw(entity, component));
15110 }
15111
15112 inline ComponentCursor World::cursor_mut(Entity entity, Entity component) {
15113 if (component != EntityBad && valid(entity)) {
15114 const auto& ec = fetch(entity);
15115 const auto* pItem = !is_req_del(ec) ? component_item(entity, component) : nullptr;
15116 if (pItem != nullptr && soa_field_supported(*pItem) &&
15117 core::get_index(ec.pChunk->ids_view(), component) != BadIndex)
15118 return ComponentCursor::from_soa(*this, comp_cache(), entity, component, pItem->comp.size());
15119 }
15120
15121 return ComponentCursor::from_raw(*this, comp_cache(), entity, component, mut_raw(entity, component));
15122 }
15123
15126 } // namespace ecs
15127} // namespace gaia
15128
15129#include "api.inl"
15130#if GAIA_OBSERVERS_ENABLED
15131 #include "observer_registry.inl"
15132#endif
15133
15134#include "observer.inl"
15135#include "system.inl"
15136
15137namespace gaia {
15138 namespace ecs {
15139 inline void World::init() {
15140 // Use the default serializer
15141 set_serializer(nullptr);
15142
15143 // Register the root archetype
15144 {
15145 m_pRootArchetype = create_archetype({});
15146 m_pRootArchetype->set_hashes({calc_lookup_hash({})});
15147 reg_archetype(m_pRootArchetype);
15148 }
15149
15150 (void)reg_core_entity<Core_>(Core);
15151
15152 // Entity archetype matches the root archetype for now
15153 m_pEntityArchetype = m_pRootArchetype;
15154
15155 // Register the component archetype (entity + EntityDesc + Component)
15156 {
15157 Archetype* pCompArchetype{};
15158 {
15159 const auto id = GAIA_ID(EntityDesc);
15160 const auto& ci = reg_core_entity<EntityDesc>(id);
15161 EntityBuilder(*this, id).add_inter_init(ci.entity);
15162 const auto symbol = ci.symbol_name();
15163 sset<EntityDesc>(id) = {symbol.data(), symbol.size(), nullptr, 0};
15164 pCompArchetype = m_recs.entities[id.id()].pArchetype;
15165 }
15166 {
15167 const auto id = GAIA_ID(Component);
15168 const auto& ci = reg_core_entity<Component>(id, pCompArchetype);
15169 EntityBuilder(*this, id).add_inter_init(ci.entity);
15170 const auto symbol = ci.symbol_name();
15171 acc_mut(id)
15172 // Entity descriptor
15173 .sset<EntityDesc>({symbol.data(), symbol.size(), nullptr, 0})
15174 // Component
15175 .sset<Component>(ci.comp);
15176 m_pCompArchetype = m_recs.entities[id.id()].pArchetype;
15177 }
15178 }
15179
15180 // Core components.
15181 // Their order must correspond to the value sequence in id.h.
15182 {
15183 (void)reg_core_entity<OnDelete_>(OnDelete);
15184 (void)reg_core_entity<OnDeleteTarget_>(OnDeleteTarget);
15185 (void)reg_core_entity<Remove_>(Remove);
15186 (void)reg_core_entity<Delete_>(Delete);
15187 (void)reg_core_entity<Error_>(Error);
15188 (void)reg_core_entity<Requires_>(Requires);
15189 (void)reg_core_entity<CantCombine_>(CantCombine);
15190 (void)reg_core_entity<Exclusive_>(Exclusive);
15191 (void)reg_core_entity<DontFragment_>(DontFragment);
15192 (void)reg_core_entity<Sparse_>(Sparse);
15193 (void)reg_core_entity<Acyclic_>(Acyclic);
15194 (void)reg_core_entity<Traversable_>(Traversable);
15195 (void)reg_core_entity<All_>(All);
15196 (void)reg_core_entity<ChildOf_>(ChildOf);
15197 (void)reg_core_entity<Parent_>(Parent);
15198 (void)reg_core_entity<Is_>(Is);
15199 (void)reg_core_entity<Prefab_>(Prefab);
15200 (void)reg_core_entity<OnInstantiate_>(OnInstantiate);
15201 (void)reg_core_entity<Override_>(Override);
15202 (void)reg_core_entity<Inherit_>(Inherit);
15203 (void)reg_core_entity<DontInherit_>(DontInherit);
15204 (void)reg_core_entity<System_>(System);
15205 (void)reg_core_entity<DependsOn_>(DependsOn);
15206 (void)reg_core_entity<Observer_>(Observer);
15207
15208 (void)reg_core_entity<_Var0>(Var0);
15209 (void)reg_core_entity<_Var1>(Var1);
15210 (void)reg_core_entity<_Var2>(Var2);
15211 (void)reg_core_entity<_Var3>(Var3);
15212 (void)reg_core_entity<_Var4>(Var4);
15213 (void)reg_core_entity<_Var5>(Var5);
15214 (void)reg_core_entity<_Var6>(Var6);
15215 (void)reg_core_entity<_Var7>(Var7);
15216
15217 (void)reg_core_primitive_type(S8, "gaia::ecs::S8", 13, 1);
15218 (void)reg_core_primitive_type(U8, "gaia::ecs::U8", 13, 1);
15219 (void)reg_core_primitive_type(S16, "gaia::ecs::S16", 14, 2);
15220 (void)reg_core_primitive_type(U16, "gaia::ecs::U16", 14, 2);
15221 (void)reg_core_primitive_type(S32, "gaia::ecs::S32", 14, 4);
15222 (void)reg_core_primitive_type(U32, "gaia::ecs::U32", 14, 4);
15223 (void)reg_core_primitive_type(S64, "gaia::ecs::S64", 14, 8);
15224 (void)reg_core_primitive_type(U64, "gaia::ecs::U64", 14, 8);
15225 (void)reg_core_primitive_type(Bool, "gaia::ecs::Bool", 15, 1);
15226 (void)reg_core_primitive_type(Char8, "gaia::ecs::Char8", 16, 1);
15227 (void)reg_core_primitive_type(Char16, "gaia::ecs::Char16", 17, 2);
15228 (void)reg_core_primitive_type(Char32, "gaia::ecs::Char32", 17, 4);
15229 (void)reg_core_primitive_type(F8, "gaia::ecs::F8", 13, 1);
15230 (void)reg_core_primitive_type(F16, "gaia::ecs::F16", 14, 2);
15231 (void)reg_core_primitive_type(F32, "gaia::ecs::F32", 14, 4);
15232 (void)reg_core_primitive_type(F64, "gaia::ecs::F64", 14, 8);
15233 }
15234
15235 // Add special properties for core components.
15236 // Their order must correspond to the value sequence in id.h.
15237 {
15238 EntityBuilder(*this, Core) //
15239 .add(Core)
15240 .add(Pair(OnDelete, Error));
15241 EntityBuilder(*this, GAIA_ID(EntityDesc)) //
15242 .add(Core)
15243 .add(Pair(OnDelete, Error));
15244 EntityBuilder(*this, GAIA_ID(Component)) //
15245 .add(Core)
15246 .add(Pair(OnDelete, Error));
15247 EntityBuilder(*this, OnDelete) //
15248 .add(Core)
15249 .add(Exclusive)
15250 .add(Pair(OnDelete, Error));
15251 EntityBuilder(*this, OnDeleteTarget) //
15252 .add(Core)
15253 .add(Exclusive)
15254 .add(Pair(OnDelete, Error));
15255 EntityBuilder(*this, Remove) //
15256 .add(Core)
15257 .add(Pair(OnDelete, Error));
15258 EntityBuilder(*this, Delete) //
15259 .add(Core)
15260 .add(Pair(OnDelete, Error));
15261 EntityBuilder(*this, Error) //
15262 .add(Core)
15263 .add(Pair(OnDelete, Error));
15264 EntityBuilder(*this, All) //
15265 .add(Core)
15266 .add(Pair(OnDelete, Error));
15267 EntityBuilder(*this, Requires) //
15268 .add(Core)
15269 .add(Acyclic)
15270 .add(Pair(OnDelete, Error));
15271 EntityBuilder(*this, CantCombine) //
15272 .add(Core)
15273 .add(Acyclic)
15274 .add(Pair(OnDelete, Error));
15275 EntityBuilder(*this, Exclusive) //
15276 .add(Core)
15277 .add(Pair(OnDelete, Error))
15278 .add(Acyclic);
15279 EntityBuilder(*this, DontFragment) //
15280 .add(Core)
15281 .add(Requires)
15282 .add(Pair(OnDelete, Error))
15283 .add(Acyclic);
15284 EntityBuilder(*this, Sparse) //
15285 .add(Core)
15286 .add(Requires)
15287 .add(Pair(OnDelete, Error))
15288 .add(Acyclic);
15289 EntityBuilder(*this, Acyclic) //
15290 .add(Core)
15291 .add(Pair(OnDelete, Error));
15292 EntityBuilder(*this, Traversable) //
15293 .add(Core)
15294 .add(Pair(OnDelete, Error));
15295
15296 EntityBuilder(*this, ChildOf) //
15297 .add(Core)
15298 .add(Acyclic)
15299 .add(Exclusive)
15300 .add(Traversable)
15301 .add(Pair(OnDelete, Error))
15302 .add(Pair(OnDeleteTarget, Delete));
15303 EntityBuilder(*this, Parent) //
15304 .add(Core)
15305 .add(Acyclic)
15306 .add(Exclusive)
15307 .add(DontFragment)
15308 .add(Traversable)
15309 .add(Pair(OnDelete, Error))
15310 .add(Pair(OnDeleteTarget, Delete));
15311 EntityBuilder(*this, Is) //
15312 .add(Core)
15313 .add(Acyclic)
15314 .add(Pair(OnDelete, Error));
15315 EntityBuilder(*this, Prefab) //
15316 .add(Core)
15317 .add(Pair(OnDelete, Error));
15318 EntityBuilder(*this, OnInstantiate) //
15319 .add(Core)
15320 .add(Acyclic)
15321 .add(Exclusive)
15322 .add(DontFragment)
15323 .add(Pair(OnDelete, Error));
15324 EntityBuilder(*this, Override) //
15325 .add(Core)
15326 .add(Pair(OnDelete, Error));
15327 EntityBuilder(*this, Inherit) //
15328 .add(Core)
15329 .add(Pair(OnDelete, Error));
15330 EntityBuilder(*this, DontInherit) //
15331 .add(Core)
15332 .add(Pair(OnDelete, Error));
15333
15334 EntityBuilder(*this, System) //
15335 .add(Core)
15336 .add(Acyclic)
15337 .add(Pair(OnDelete, Error));
15338 EntityBuilder(*this, DependsOn) //
15339 .add(Core)
15340 .add(Acyclic)
15341 .add(Pair(OnDelete, Error));
15342 EntityBuilder(*this, Observer) //
15343 .add(Core)
15344 .add(Acyclic)
15345 .add(Pair(OnDelete, Error));
15346
15347 EntityBuilder(*this, Var0) //
15348 .add(Core)
15349 .add(Pair(OnDelete, Error));
15350 EntityBuilder(*this, Var1) //
15351 .add(Core)
15352 .add(Pair(OnDelete, Error));
15353 EntityBuilder(*this, Var2) //
15354 .add(Core)
15355 .add(Pair(OnDelete, Error));
15356 EntityBuilder(*this, Var3) //
15357 .add(Core)
15358 .add(Pair(OnDelete, Error));
15359 EntityBuilder(*this, Var4) //
15360 .add(Core)
15361 .add(Pair(OnDelete, Error));
15362 EntityBuilder(*this, Var5) //
15363 .add(Core)
15364 .add(Pair(OnDelete, Error));
15365 EntityBuilder(*this, Var6) //
15366 .add(Core)
15367 .add(Pair(OnDelete, Error));
15368 EntityBuilder(*this, Var7) //
15369 .add(Core)
15370 .add(Pair(OnDelete, Error));
15371
15372 EntityBuilder(*this, S8) //
15373 .add(Core)
15374 .add(Pair(OnDelete, Error));
15375 EntityBuilder(*this, U8) //
15376 .add(Core)
15377 .add(Pair(OnDelete, Error));
15378 EntityBuilder(*this, S16) //
15379 .add(Core)
15380 .add(Pair(OnDelete, Error));
15381 EntityBuilder(*this, U16) //
15382 .add(Core)
15383 .add(Pair(OnDelete, Error));
15384 EntityBuilder(*this, S32) //
15385 .add(Core)
15386 .add(Pair(OnDelete, Error));
15387 EntityBuilder(*this, U32) //
15388 .add(Core)
15389 .add(Pair(OnDelete, Error));
15390 EntityBuilder(*this, S64) //
15391 .add(Core)
15392 .add(Pair(OnDelete, Error));
15393 EntityBuilder(*this, U64) //
15394 .add(Core)
15395 .add(Pair(OnDelete, Error));
15396 EntityBuilder(*this, Bool) //
15397 .add(Core)
15398 .add(Pair(OnDelete, Error));
15399 EntityBuilder(*this, Char8) //
15400 .add(Core)
15401 .add(Pair(OnDelete, Error));
15402 EntityBuilder(*this, Char16) //
15403 .add(Core)
15404 .add(Pair(OnDelete, Error));
15405 EntityBuilder(*this, Char32) //
15406 .add(Core)
15407 .add(Pair(OnDelete, Error));
15408 EntityBuilder(*this, F8) //
15409 .add(Core)
15410 .add(Pair(OnDelete, Error));
15411 EntityBuilder(*this, F16) //
15412 .add(Core)
15413 .add(Pair(OnDelete, Error));
15414 EntityBuilder(*this, F32) //
15415 .add(Core)
15416 .add(Pair(OnDelete, Error));
15417 EntityBuilder(*this, F64) //
15418 .add(Core)
15419 .add(Pair(OnDelete, Error));
15420 }
15421
15422 // Remove all archetypes with no chunks. We don't want any leftovers after
15423 // archetype movements.
15424 {
15425 ArchetypeRowDelScope scope(*this);
15426 for (uint32_t i = 1; i < m_archetypes.size(); ++i) {
15427 auto* pArchetype = m_archetypes[i];
15428 if (!pArchetype->chunks().empty())
15429 continue;
15430
15431 // Request deletion the standard way.
15432 // We could simply add archetypes into m_archetypesToDel but this way
15433 // we can actually replicate what the system really does on the inside
15434 // and it will require more work at the cost of easier maintenance.
15435 // The amount of archetypes cleanup is very small after init and the code
15436 // only runs after the world is created so this is not a big deal.
15437 req_del(*pArchetype);
15438 }
15439
15440 // Cleanup
15441 {
15442 del_finalize();
15443 while (!m_chunksToDel.empty() || !m_archetypesToDel.empty())
15444 gc();
15445
15446 // Make sure everything has been cleared
15447 GAIA_ASSERT(m_reqArchetypesToDel.empty());
15448 GAIA_ASSERT(m_chunksToDel.empty());
15449 GAIA_ASSERT(m_archetypesToDel.empty());
15450 }
15451
15452 sort_archetypes();
15453
15454 // Make sure archetypes have valid graphs after the cleanup
15455 for (const auto* pArchetype: m_archetypes)
15456 validate_archetype_edges(pArchetype);
15457 }
15458
15459 // Make sure archetype pointers are up-to-date
15460 m_pCompArchetype = m_recs.entities[GAIA_ID(Component).id()].pArchetype;
15461
15462#if GAIA_SYSTEMS_ENABLED
15463 // Initialize the systems query
15464 systems_init();
15465#endif
15466 }
15467
15473 inline GroupId
15474 group_by_func_default([[maybe_unused]] const World& world, const Archetype& archetype, Entity groupBy) {
15475 if (archetype.pairs() > 0) {
15476 auto ids = archetype.ids_view();
15477 for (auto id: ids) {
15478 if (!id.pair() || id.id() != groupBy.id())
15479 continue;
15480
15481 // Consider the pair's target the groupId
15482 return id.gen();
15483 }
15484 }
15485
15486 // No group
15487 return 0;
15488 }
15489
15491 inline GroupId group_by_func_depth_order(const World& world, const Archetype& archetype, Entity relation) {
15492 GAIA_ASSERT(!relation.pair());
15493
15494 // Depth ordering only makes sense for fragmenting relations whose target participates in archetype identity.
15495 // Non-fragmenting relations such as Parent must stay on per-entity traversal, because their targets vary per
15496 // entity and cannot be represented by one cached archetype depth. The level is derived from the cached upward
15497 // traversal chain so normal query iteration can stay cheap.
15498 if (!world.relation_supports_depth_order(relation) || archetype.pairs() == 0)
15499 return 0;
15500
15501 auto ids = archetype.ids_view();
15502 GroupId maxDepth = 0;
15503 bool found = false;
15504
15505 for (auto idsIdx: archetype.pair_rel_indices(relation)) {
15506 const auto pair = ids[idsIdx];
15507 const auto target = world.pair_target_if_alive(pair);
15508 if (target == EntityBad)
15509 continue;
15510
15511 const GroupId depth = GroupId(world.depth_order_cache(relation, target));
15512
15513 if (!found || depth > maxDepth) {
15514 maxDepth = depth;
15515 found = true;
15516 }
15517 }
15518
15519 return found ? maxDepth : 0;
15520 }
15521 } // namespace ecs
15522} // namespace gaia
15523
15524#if GAIA_JSON_ENABLED
15525 #include "gaia/ecs/impl/world_json.h"
15526 #include "gaia/ecs/impl/world_json_patch.h"
15527 #include "gaia/ecs/impl/world_schema_json.h"
15528#endif
15529
15530#if GAIA_SYSTEMS_ENABLED
15531namespace gaia {
15532 namespace ecs {
15533 namespace detail {
15538 GAIA_NODISCARD inline bool entity_schedule_less(Entity lhs, Entity rhs) {
15539 if (lhs.id() != rhs.id())
15540 return lhs.id() < rhs.id();
15541 return lhs.gen() < rhs.gen();
15542 }
15543
15548 GAIA_NODISCARD inline bool system_schedule_stack_contains(const cnt::darray<Entity>& stack, Entity entity) {
15549 for (auto item: stack) {
15550 if (item == entity)
15551 return true;
15552 }
15553 return false;
15554 }
15555
15562 GAIA_NODISCARD inline uint32_t
15563 system_schedule_dep_depth(World& world, Entity entity, Entity skipTarget, cnt::darray<Entity>& stack) {
15564 if (entity == EntityBad || system_schedule_stack_contains(stack, entity))
15565 return 0;
15566
15567 stack.push_back(entity);
15568 uint32_t depth = 0;
15569 world.targets(entity, DependsOn, [&](Entity target) {
15570 if (target == skipTarget)
15571 return;
15572 const auto targetDepth = system_schedule_dep_depth(world, target, EntityBad, stack) + 1;
15573 if (targetDepth > depth)
15574 depth = targetDepth;
15575 });
15576 stack.pop_back();
15577 return depth;
15578 }
15579
15584 GAIA_NODISCARD inline Entity system_phase(World& world, Entity systemEntity) {
15585 const auto phase = world.target(systemEntity, ChildOf);
15586 if (phase == EntityBad)
15587 return EntityBad;
15588 if (!world.has(systemEntity, Pair(DependsOn, phase)))
15589 return EntityBad;
15590 return phase;
15591 }
15592
15597 GAIA_NODISCARD inline SystemScheduleItem system_schedule_item(World& world, Entity systemEntity) {
15598 SystemScheduleItem item{};
15599 item.entity = systemEntity;
15600 item.phase = system_phase(world, systemEntity);
15601 item.hasPhase = item.phase != EntityBad;
15602 return item;
15603 }
15604
15607 inline void submit_pending_system_jobs(cnt::darray<PendingSystemJob>& pending) {
15608 for (auto& item: pending)
15609 item.job.submit();
15610 }
15611
15614 inline void finish_pending_system_jobs(cnt::darray<PendingSystemJob>& pending) {
15615 for (auto& item: pending)
15616 item.job.wait();
15617 for (auto& item: pending)
15618 item.job.del();
15619 pending.clear();
15620 }
15621
15624 inline void flush_pending_system_jobs(cnt::darray<PendingSystemJob>& pending) {
15625 submit_pending_system_jobs(pending);
15626 finish_pending_system_jobs(pending);
15627 }
15628
15632 GAIA_NODISCARD inline bool sched_supports_deferred_system_jobs(const Sched& sched) {
15633 const auto& resolved = sched_resolve(sched);
15634 return resolved.add != nullptr && resolved.add_par != nullptr && resolved.submit != nullptr &&
15635 resolved.dep != nullptr && resolved.wait != nullptr && resolved.del != nullptr;
15636 }
15637
15641 inline void
15642 system_schedule_entity_indices(const cnt::darray<SystemScheduleItem>& items, cnt::darray<uint32_t>& outIndices) {
15643 outIndices.clear();
15644 outIndices.reserve(items.size());
15645 for (uint32_t i = 0; i < items.size(); ++i)
15646 outIndices.push_back(i);
15647 core::sort(outIndices, [&](uint32_t lhs, uint32_t rhs) {
15648 return entity_schedule_less(items[lhs].entity, items[rhs].entity);
15649 });
15650 }
15651
15657 GAIA_NODISCARD inline uint32_t system_schedule_find_item_by_entity(
15658 const cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices, Entity entity) {
15659 uint32_t lo = 0;
15660 uint32_t hi = entityIndices.size();
15661 while (lo < hi) {
15662 const uint32_t mid = lo + (hi - lo) / 2;
15663 const auto midEntity = items[entityIndices[mid]].entity;
15664 if (midEntity == entity)
15665 return entityIndices[mid];
15666 if (entity_schedule_less(midEntity, entity))
15667 lo = mid + 1;
15668 else
15669 hi = mid;
15670 }
15671 return UINT32_MAX;
15672 }
15673
15678 GAIA_NODISCARD inline bool
15679 system_schedule_same_group(const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
15680 if (lhs.hasPhase != rhs.hasPhase)
15681 return false;
15682 if (!lhs.hasPhase)
15683 return true;
15684 return lhs.phase == rhs.phase;
15685 }
15686
15693 GAIA_NODISCARD inline uint32_t system_schedule_primary_target_idx(
15694 World& world, const cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices,
15695 uint32_t itemIdx) {
15696 uint32_t bestIdx = UINT32_MAX;
15697 uint32_t bestDepth = 0;
15698 const auto& item = items[itemIdx];
15699 world.targets(item.entity, DependsOn, [&](Entity target) {
15700 if (target == item.phase)
15701 return;
15702
15703 const auto targetIdx = system_schedule_find_item_by_entity(items, entityIndices, target);
15704 if (targetIdx == UINT32_MAX)
15705 return;
15706 if (!system_schedule_same_group(item, items[targetIdx]))
15707 return;
15708
15709 const auto targetDepth = items[targetIdx].systemDepth;
15710 if (bestIdx == UINT32_MAX || targetDepth > bestDepth ||
15711 (targetDepth == bestDepth && entity_schedule_less(items[targetIdx].entity, items[bestIdx].entity))) {
15712 bestIdx = targetIdx;
15713 bestDepth = targetDepth;
15714 }
15715 });
15716 return bestIdx;
15717 }
15718
15726 inline void system_schedule_visit_primary_children(
15727 cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& firstChildren,
15728 const cnt::darray<uint32_t>& nextSiblings, cnt::darray<uint8_t>& states, uint32_t itemIdx, uint32_t& order) {
15729 if (states[itemIdx] != 0)
15730 return;
15731
15732 states[itemIdx] = 1;
15733 for (uint32_t childIdx = firstChildren[itemIdx]; childIdx != UINT32_MAX; childIdx = nextSiblings[childIdx])
15734 system_schedule_visit_primary_children(items, firstChildren, nextSiblings, states, childIdx, order);
15735 items[itemIdx].systemOrder = order++;
15736 states[itemIdx] = 2;
15737 }
15738
15744 inline void system_schedule_assign_group_orders(
15745 World& world, cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& groupIndices,
15746 const cnt::darray<uint32_t>& entityIndices, SystemScheduleScratch& scratch) {
15747 auto& sortedGroupIndices = scratch.sortedGroupIndices;
15748 auto& primaryTargets = scratch.primaryTargets;
15749 auto& firstChildren = scratch.firstChildren;
15750 auto& nextSiblings = scratch.nextSiblings;
15751 auto& states = scratch.states;
15752
15753 sortedGroupIndices = groupIndices;
15754 core::sort(sortedGroupIndices, [&](uint32_t lhs, uint32_t rhs) {
15755 return entity_schedule_less(items[lhs].entity, items[rhs].entity);
15756 });
15757
15758 primaryTargets.clear();
15759 primaryTargets.resize(items.size(), UINT32_MAX);
15760 for (auto itemIdx: groupIndices)
15761 primaryTargets[itemIdx] = system_schedule_primary_target_idx(world, items, entityIndices, itemIdx);
15762
15763 firstChildren.clear();
15764 nextSiblings.clear();
15765 firstChildren.resize(items.size(), UINT32_MAX);
15766 nextSiblings.resize(items.size(), UINT32_MAX);
15767 for (uint32_t i = sortedGroupIndices.size(); i > 0; --i) {
15768 const auto itemIdx = sortedGroupIndices[i - 1];
15769 const auto targetIdx = primaryTargets[itemIdx];
15770 if (targetIdx == UINT32_MAX)
15771 continue;
15772 nextSiblings[itemIdx] = firstChildren[targetIdx];
15773 firstChildren[targetIdx] = itemIdx;
15774 }
15775
15776 states.clear();
15777 states.resize(items.size(), 0);
15778
15779 uint32_t order = 0;
15780 for (auto itemIdx: sortedGroupIndices) {
15781 if (states[itemIdx] == 0 && primaryTargets[itemIdx] == UINT32_MAX) {
15782 system_schedule_visit_primary_children(items, firstChildren, nextSiblings, states, itemIdx, order);
15783 }
15784 }
15785
15786 // Remaining items are in a DependsOn cycle. The cycle is invalid, but the update stays deterministic.
15787 for (auto itemIdx: sortedGroupIndices) {
15788 if (states[itemIdx] == 0) {
15789 items[itemIdx].systemOrder = order++;
15790 states[itemIdx] = 2;
15791 }
15792 }
15793 }
15794
15799 GAIA_NODISCARD inline uint32_t
15800 system_schedule_find_phase(const cnt::darray<SystemPhaseScheduleItem>& phases, Entity phase) {
15801 for (uint32_t i = 0; i < phases.size(); ++i) {
15802 if (phases[i].phase == phase)
15803 return i;
15804 }
15805 return UINT32_MAX;
15806 }
15807
15813 GAIA_NODISCARD inline uint32_t system_schedule_primary_phase_idx(
15814 World& world, const cnt::darray<SystemPhaseScheduleItem>& phases, uint32_t phaseIdx) {
15815 uint32_t bestIdx = UINT32_MAX;
15816 uint32_t bestDepth = 0;
15817 world.targets(phases[phaseIdx].phase, DependsOn, [&](Entity target) {
15818 const auto targetIdx = system_schedule_find_phase(phases, target);
15819 if (targetIdx == UINT32_MAX)
15820 return;
15821
15822 const auto targetDepth = phases[targetIdx].depth;
15823 if (bestIdx == UINT32_MAX || targetDepth > bestDepth ||
15824 (targetDepth == bestDepth && entity_schedule_less(phases[targetIdx].phase, phases[bestIdx].phase))) {
15825 bestIdx = targetIdx;
15826 bestDepth = targetDepth;
15827 }
15828 });
15829 return bestIdx;
15830 }
15831
15839 inline void system_schedule_visit_phase_children(
15840 cnt::darray<SystemPhaseScheduleItem>& phases, const cnt::darray<uint32_t>& firstChildren,
15841 const cnt::darray<uint32_t>& nextSiblings, cnt::darray<uint8_t>& states, uint32_t phaseIdx, uint32_t& order) {
15842 if (states[phaseIdx] != 0)
15843 return;
15844
15845 states[phaseIdx] = 1;
15846 for (uint32_t childIdx = firstChildren[phaseIdx]; childIdx != UINT32_MAX; childIdx = nextSiblings[childIdx])
15847 system_schedule_visit_phase_children(phases, firstChildren, nextSiblings, states, childIdx, order);
15848 phases[phaseIdx].order = order++;
15849 states[phaseIdx] = 2;
15850 }
15851
15856 inline void system_schedule_assign_phase_orders(
15857 World& world, cnt::darray<SystemPhaseScheduleItem>& phases, SystemScheduleScratch& scratch) {
15858 // Phase ordering has two layers:
15859 // 1. A cheap DFS postorder over each phase's primary DependsOn target. This gives stable depth/path
15860 // keys and a deterministic fallback for cycles.
15861 // 2. A direct DependsOn topological pass. This preserves all explicit phase targets, including
15862 // multi-target dependencies that the primary-target path cannot represent by itself.
15863 auto& sortedPhases = scratch.sortedPhases;
15864 auto& primaryPhases = scratch.primaryPhases;
15865 auto& firstChildren = scratch.firstChildren;
15866 auto& nextSiblings = scratch.nextSiblings;
15867 auto& states = scratch.states;
15868
15869 // Work in entity-id order whenever the graph does not force a different order. That keeps
15870 // independent phases deterministic and makes cycle fallback stable.
15871 sortedPhases.clear();
15872 sortedPhases.reserve(phases.size());
15873 for (uint32_t i = 0; i < phases.size(); ++i)
15874 sortedPhases.push_back(i);
15875 core::sort(sortedPhases, [&](uint32_t lhs, uint32_t rhs) {
15876 return entity_schedule_less(phases[lhs].phase, phases[rhs].phase);
15877 });
15878
15879 // Collapse each phase's dependency path to one primary target for the DFS ordering key. Direct
15880 // multi-target dependencies are handled later by the topological pass.
15881 primaryPhases.clear();
15882 primaryPhases.resize(phases.size(), UINT32_MAX);
15883 for (uint32_t i = 0; i < phases.size(); ++i)
15884 primaryPhases[i] = system_schedule_primary_phase_idx(world, phases, i);
15885
15886 // Build primary-target adjacency once, then walk it instead of repeatedly scanning all phases for
15887 // children. Reverse insertion preserves sorted child traversal.
15888 firstChildren.clear();
15889 nextSiblings.clear();
15890 firstChildren.resize(phases.size(), UINT32_MAX);
15891 nextSiblings.resize(phases.size(), UINT32_MAX);
15892 for (uint32_t i = sortedPhases.size(); i > 0; --i) {
15893 const auto phaseIdx = sortedPhases[i - 1];
15894 const auto targetIdx = primaryPhases[phaseIdx];
15895 if (targetIdx == UINT32_MAX)
15896 continue;
15897 nextSiblings[phaseIdx] = firstChildren[targetIdx];
15898 firstChildren[targetIdx] = phaseIdx;
15899 }
15900
15901 states.clear();
15902 states.resize(phases.size(), 0);
15903
15904 // Assign the primary DFS order from roots first. Any phase left unvisited participates in a
15905 // primary-target cycle, so it receives a deterministic entity-order fallback below.
15906 uint32_t order = 0;
15907 for (auto phaseIdx: sortedPhases) {
15908 if (states[phaseIdx] == 0 && primaryPhases[phaseIdx] == UINT32_MAX) {
15909 system_schedule_visit_phase_children(phases, firstChildren, nextSiblings, states, phaseIdx, order);
15910 }
15911 }
15912
15913 // Remaining phases are in a DependsOn cycle. The cycle is invalid, but the update stays deterministic.
15914 for (auto phaseIdx: sortedPhases) {
15915 if (states[phaseIdx] == 0) {
15916 phases[phaseIdx].order = order++;
15917 states[phaseIdx] = 2;
15918 }
15919 }
15920
15921 auto& edges = scratch.edges;
15922 auto& childCounts = scratch.childCounts;
15923 auto& firstEdges = scratch.firstEdges;
15924 auto& readyNext = scratch.readyNext;
15925 auto& ordered = scratch.sortedIndices;
15926 auto& visited = scratch.visited;
15927
15928 edges.clear();
15929 childCounts.clear();
15930 childCounts.resize(phases.size(), 0);
15931
15932 // Build explicit phase edges from the actual DependsOn pairs. We count incoming children per
15933 // target so a phase becomes ready only after all phases depending on it have been emitted.
15934 for (uint32_t phaseIdx = 0; phaseIdx < phases.size(); ++phaseIdx) {
15935 world.targets(phases[phaseIdx].phase, DependsOn, [&](Entity target) {
15936 const auto targetIdx = system_schedule_find_phase(phases, target);
15937 if (targetIdx == UINT32_MAX || targetIdx == phaseIdx)
15938 return;
15939
15940 SystemScheduleEdge edge{};
15941 edge.child = phaseIdx;
15942 edge.target = targetIdx;
15943 edges.push_back(edge);
15944 ++childCounts[targetIdx];
15945 });
15946 }
15947 if (edges.empty())
15948 return;
15949
15950 // Link outgoing edges per child phase. This lets the ready-list pass touch only edges affected by
15951 // the phase it just emitted.
15952 firstEdges.clear();
15953 firstEdges.resize(phases.size(), UINT32_MAX);
15954 for (uint32_t edgeIdx = edges.size(); edgeIdx > 0; --edgeIdx) {
15955 auto& edge = edges[edgeIdx - 1];
15956 edge.next = firstEdges[edge.child];
15957 firstEdges[edge.child] = edgeIdx - 1;
15958 }
15959
15960 auto phase_less = [&](uint32_t lhs, uint32_t rhs) {
15961 const auto& lhsPhase = phases[lhs];
15962 const auto& rhsPhase = phases[rhs];
15963 if (lhsPhase.order != rhsPhase.order)
15964 return lhsPhase.order < rhsPhase.order;
15965 if (lhsPhase.depth != rhsPhase.depth)
15966 return lhsPhase.depth > rhsPhase.depth;
15967 return entity_schedule_less(lhsPhase.phase, rhsPhase.phase);
15968 };
15969 auto ready_insert = [&](uint32_t& readyHead, uint32_t phaseIdx) {
15970 uint32_t* ppCurr = &readyHead;
15971 while (*ppCurr != UINT32_MAX && phase_less(*ppCurr, phaseIdx))
15972 ppCurr = &readyNext[*ppCurr];
15973 readyNext[phaseIdx] = *ppCurr;
15974 *ppCurr = phaseIdx;
15975 };
15976
15977 readyNext.clear();
15978 readyNext.resize(phases.size(), UINT32_MAX);
15979
15980 // Kahn pass in Gaia's scheduler direction: children run before their DependsOn targets. The
15981 // ordered ready list preserves the primary DFS order whenever several phases are unblocked.
15982 uint32_t readyHead = UINT32_MAX;
15983 for (auto phaseIdx: sortedPhases) {
15984 if (childCounts[phaseIdx] == 0)
15985 ready_insert(readyHead, phaseIdx);
15986 }
15987
15988 ordered.clear();
15989 ordered.reserve(phases.size());
15990 while (readyHead != UINT32_MAX) {
15991 const auto phaseIdx = readyHead;
15992 readyHead = readyNext[readyHead];
15993 ordered.push_back(phaseIdx);
15994
15995 for (uint32_t edgeIdx = firstEdges[phaseIdx]; edgeIdx != UINT32_MAX; edgeIdx = edges[edgeIdx].next) {
15996 const auto targetIdx = edges[edgeIdx].target;
15997 GAIA_ASSERT(childCounts[targetIdx] > 0);
15998 --childCounts[targetIdx];
15999 if (childCounts[targetIdx] == 0)
16000 ready_insert(readyHead, targetIdx);
16001 }
16002 }
16003
16004 if (ordered.size() != phases.size()) {
16005 // A direct phase dependency cycle cannot be topologically sorted. Keep the acyclic prefix and
16006 // append the remaining phases in entity order so execution is still deterministic.
16007 visited.clear();
16008 visited.resize(phases.size(), 0);
16009 for (auto phaseIdx: ordered)
16010 visited[phaseIdx] = 1;
16011 for (auto phaseIdx: sortedPhases) {
16012 if (visited[phaseIdx] == 0)
16013 ordered.push_back(phaseIdx);
16014 }
16015 }
16016
16017 for (uint32_t i = 0; i < ordered.size(); ++i)
16018 phases[ordered[i]].order = i;
16019 }
16020
16025 inline void system_schedule_assign_order_keys(
16026 World& world, cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices,
16027 SystemScheduleScratch& scratch) {
16028 auto& stack = scratch.entityStack;
16029 auto& phases = scratch.phases;
16030 auto& groupIndices = scratch.groupIndices;
16031
16032 for (auto& item: items) {
16033 stack.clear();
16034 item.systemDepth = system_schedule_dep_depth(world, item.entity, item.phase, stack);
16035 }
16036
16037 phases.clear();
16038 for (auto& item: items) {
16039 if (!item.hasPhase || system_schedule_find_phase(phases, item.phase) != UINT32_MAX)
16040 continue;
16041 SystemPhaseScheduleItem phaseItem{};
16042 phaseItem.phase = item.phase;
16043 stack.clear();
16044 phaseItem.depth = system_schedule_dep_depth(world, phaseItem.phase, EntityBad, stack);
16045 phases.push_back(phaseItem);
16046 }
16047
16048 system_schedule_assign_phase_orders(world, phases, scratch);
16049 for (auto& item: items) {
16050 if (!item.hasPhase)
16051 continue;
16052 const auto phaseIdx = system_schedule_find_phase(phases, item.phase);
16053 if (phaseIdx == UINT32_MAX)
16054 continue;
16055 item.phaseOrder = phases[phaseIdx].order;
16056 item.phaseDepth = phases[phaseIdx].depth;
16057 }
16058
16059 groupIndices.clear();
16060 groupIndices.reserve(items.size());
16061 for (uint32_t i = 0; i < items.size(); ++i) {
16062 if (!items[i].hasPhase)
16063 groupIndices.push_back(i);
16064 }
16065 if (!groupIndices.empty())
16066 system_schedule_assign_group_orders(world, items, groupIndices, entityIndices, scratch);
16067
16068 for (auto& phase: phases) {
16069 groupIndices.clear();
16070 for (uint32_t i = 0; i < items.size(); ++i) {
16071 if (items[i].phase == phase.phase)
16072 groupIndices.push_back(i);
16073 }
16074 if (!groupIndices.empty())
16075 system_schedule_assign_group_orders(world, items, groupIndices, entityIndices, scratch);
16076 }
16077 }
16078
16088 GAIA_NODISCARD inline bool system_schedule_less(const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
16089 if (lhs.hasPhase != rhs.hasPhase)
16090 return lhs.hasPhase;
16091
16092 if (lhs.hasPhase && lhs.phase != rhs.phase) {
16093 if (lhs.phaseOrder != rhs.phaseOrder)
16094 return lhs.phaseOrder < rhs.phaseOrder;
16095 if (lhs.phaseDepth != rhs.phaseDepth)
16096 return lhs.phaseDepth > rhs.phaseDepth;
16097 return entity_schedule_less(lhs.phase, rhs.phase);
16098 }
16099
16100 if (lhs.systemOrder != rhs.systemOrder)
16101 return lhs.systemOrder < rhs.systemOrder;
16102 if (lhs.systemDepth != rhs.systemDepth)
16103 return lhs.systemDepth > rhs.systemDepth;
16104 return entity_schedule_less(lhs.entity, rhs.entity);
16105 }
16106
16112 inline void system_schedule_add_edge(
16113 cnt::darray<SystemScheduleEdge>& edges, cnt::darray<uint32_t>& childCounts, uint32_t childIdx,
16114 uint32_t targetIdx) {
16115 SystemScheduleEdge edge{};
16116 edge.child = childIdx;
16117 edge.target = targetIdx;
16118 edges.push_back(edge);
16119 ++childCounts[targetIdx];
16120 }
16121
16128 inline void system_schedule_build_edges(
16129 World& world, const cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices,
16130 cnt::darray<SystemScheduleEdge>& edges, cnt::darray<uint32_t>& childCounts) {
16131 childCounts.resize(items.size(), 0);
16132 for (uint32_t childIdx = 0; childIdx < items.size(); ++childIdx) {
16133 const auto& child = items[childIdx];
16134 world.targets(child.entity, DependsOn, [&](Entity target) {
16135 if (target == child.phase)
16136 return;
16137 const auto targetIdx = system_schedule_find_item_by_entity(items, entityIndices, target);
16138 if (targetIdx == UINT32_MAX || targetIdx == childIdx)
16139 return;
16140 if (!system_schedule_same_group(child, items[targetIdx]))
16141 return;
16142
16143 system_schedule_add_edge(edges, childCounts, childIdx, targetIdx);
16144 });
16145 }
16146 }
16147
16153 inline void system_schedule_ready_insert(
16154 const cnt::darray<SystemScheduleItem>& items, cnt::darray<uint32_t>& readyNext, uint32_t& readyHead,
16155 uint32_t itemIdx) {
16156 uint32_t* ppCurr = &readyHead;
16157 while (*ppCurr != UINT32_MAX && system_schedule_less(items[*ppCurr], items[itemIdx]))
16158 ppCurr = &readyNext[*ppCurr];
16159 readyNext[itemIdx] = *ppCurr;
16160 *ppCurr = itemIdx;
16161 }
16162
16170 inline void order_system_schedule_items(
16171 World& world, cnt::darray<SystemScheduleItem>& items, SystemScheduleScratch& scratch) {
16172 auto& entityIndices = scratch.entityIndices;
16173 auto& edges = scratch.edges;
16174 auto& childCounts = scratch.childCounts;
16175 auto& sortedIndices = scratch.sortedIndices;
16176 auto& firstEdges = scratch.firstEdges;
16177 auto& ordered = scratch.ordered;
16178 auto& visited = scratch.visited;
16179 auto& readyNext = scratch.readyNext;
16180
16181 system_schedule_entity_indices(items, entityIndices);
16182 system_schedule_assign_order_keys(world, items, entityIndices, scratch);
16183
16184 edges.clear();
16185 childCounts.clear();
16186 edges.reserve(items.size());
16187 system_schedule_build_edges(world, items, entityIndices, edges, childCounts);
16188 if (edges.empty()) {
16189 core::sort(items, [](const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
16190 return system_schedule_less(lhs, rhs);
16191 });
16192 return;
16193 }
16194
16195 sortedIndices.clear();
16196 sortedIndices.reserve(items.size());
16197 for (uint32_t i = 0; i < items.size(); ++i)
16198 sortedIndices.push_back(i);
16199 core::sort(sortedIndices, [&](uint32_t lhs, uint32_t rhs) {
16200 return system_schedule_less(items[lhs], items[rhs]);
16201 });
16202
16203 firstEdges.clear();
16204 firstEdges.resize(items.size(), UINT32_MAX);
16205 for (uint32_t i = edges.size(); i > 0; --i) {
16206 auto& edge = edges[i - 1];
16207 edge.next = firstEdges[edge.child];
16208 firstEdges[edge.child] = i - 1;
16209 }
16210
16211 ordered.clear();
16212 ordered.reserve(items.size());
16213
16214 visited.clear();
16215 visited.resize(items.size(), 0);
16216 readyNext.clear();
16217 readyNext.resize(items.size(), UINT32_MAX);
16218
16219 uint32_t readyHead = UINT32_MAX;
16220 for (uint32_t i = sortedIndices.size(); i > 0; --i) {
16221 const auto itemIdx = sortedIndices[i - 1];
16222 if (childCounts[itemIdx] == 0) {
16223 readyNext[itemIdx] = readyHead;
16224 readyHead = itemIdx;
16225 }
16226 }
16227
16228 while (ordered.size() < items.size()) {
16229 uint32_t bestIdx = readyHead;
16230 if (bestIdx != UINT32_MAX)
16231 readyHead = readyNext[bestIdx];
16232 if (bestIdx == UINT32_MAX) {
16233 for (auto itemIdx: sortedIndices) {
16234 if (visited[itemIdx] == 0) {
16235 bestIdx = itemIdx;
16236 break;
16237 }
16238 }
16239 }
16240
16241 GAIA_ASSERT(bestIdx != UINT32_MAX);
16242 if (bestIdx == UINT32_MAX)
16243 break;
16244
16245 visited[bestIdx] = 1;
16246 ordered.push_back(items[bestIdx]);
16247
16248 for (uint32_t edgeIdx = firstEdges[bestIdx]; edgeIdx != UINT32_MAX; edgeIdx = edges[edgeIdx].next) {
16249 const auto targetIdx = edges[edgeIdx].target;
16250 if (visited[targetIdx] != 0)
16251 continue;
16252 GAIA_ASSERT(childCounts[targetIdx] > 0);
16253 if (childCounts[targetIdx] > 0) {
16254 --childCounts[targetIdx];
16255 if (childCounts[targetIdx] == 0)
16256 system_schedule_ready_insert(items, readyNext, readyHead, targetIdx);
16257 }
16258 }
16259 }
16260
16261 items = ordered;
16262 }
16263
16268 GAIA_NODISCARD inline bool
16269 system_schedule_batch_changed(const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
16270 if (lhs.hasPhase != rhs.hasPhase)
16271 return true;
16272 if (lhs.hasPhase)
16273 return lhs.phase != rhs.phase || lhs.systemDepth != rhs.systemDepth;
16274 return lhs.systemDepth != rhs.systemDepth;
16275 }
16276
16280 GAIA_NODISCARD inline bool system_exec_uses_scheduler(QueryExecType type) {
16281 return type == QueryExecType::Parallel || type == QueryExecType::ParallelPerf ||
16282 type == QueryExecType::ParallelEff;
16283 }
16284
16288 inline void run_system_entity_erased(void* pCtx, const SystemScheduleItem& item) {
16289 auto& ctx = *static_cast<SystemRunCtx*>(pCtx);
16290 GAIA_ASSERT(ctx.pWorld != nullptr);
16291 GAIA_ASSERT(ctx.pPending != nullptr);
16292 if (ctx.pWorld == nullptr || ctx.pPending == nullptr)
16293 return;
16294
16295 auto& world = *ctx.pWorld;
16296 auto& pending = *ctx.pPending;
16297 const auto systemEntity = item.entity;
16298 if (!world.valid(systemEntity) || !world.has(systemEntity, System))
16299 return;
16300 if (!world.enabled_hierarchy(systemEntity, ChildOf))
16301 return;
16302
16303 if (!ctx.hasCurrent) {
16304 ctx.current = item;
16305 ctx.hasCurrent = true;
16306 } else if (system_schedule_batch_changed(ctx.current, item)) {
16307 flush_pending_system_jobs(pending);
16308 ctx.current = item;
16309 }
16310
16311 auto ss = world.acc_mut(systemEntity);
16312 auto& sys = ss.smut<ecs::System_>();
16313 if (!ctx.canScheduleSystems || !system_exec_uses_scheduler(sys.execType) || sys.query.main_thread_required()) {
16314 flush_pending_system_jobs(pending);
16315 sys.exec(world);
16316 return;
16317 }
16318
16319 auto job = sys.job(world);
16320 if (!job.valid())
16321 return;
16322
16323 for (auto& pendingJob: pending) {
16324 if (!world.valid(pendingJob.entity) || !world.has(pendingJob.entity, System))
16325 continue;
16326
16327 auto prevSs = world.acc_mut(pendingJob.entity);
16328 auto& prevSys = prevSs.smut<ecs::System_>();
16329 if (!prevSys.query.can_run_parallel(sys.query))
16330 job.dep(pendingJob.job);
16331 }
16332
16333 pending.emplace_back(systemEntity, GAIA_MOV(job));
16334 }
16335
16339 inline void collect_system_schedule_item_erased(void* pCtx, Entity systemEntity) {
16340 auto& ctx = *static_cast<SystemCollectCtx*>(pCtx);
16341 GAIA_ASSERT(ctx.pWorld != nullptr);
16342 GAIA_ASSERT(ctx.pItems != nullptr);
16343 if (ctx.pWorld == nullptr || ctx.pItems == nullptr)
16344 return;
16345 ctx.pItems->push_back(system_schedule_item(*ctx.pWorld, systemEntity));
16346 }
16347
16349 inline void collect_system_entity_erased(void* pCtx, Entity systemEntity) {
16350 auto& out = *static_cast<cnt::darray<Entity>*>(pCtx);
16351 out.push_back(systemEntity);
16352 }
16353
16359 inline void
16360 collect_phase_system_schedule_items(World& world, Entity phase, cnt::darray<SystemScheduleItem>& items) {
16361 world.sources(ChildOf, phase, [&](Entity source) {
16362 if (!world.has(source, System))
16363 return;
16364 if (!world.has(source, Pair(DependsOn, phase)))
16365 return;
16366 if (!world.enabled(source))
16367 return;
16368 items.push_back(system_schedule_item(world, source));
16369 });
16370 }
16371
16375 inline void run_system_schedule(World& world, const cnt::darray<SystemScheduleItem>& items) {
16376 cnt::darray<PendingSystemJob> pending;
16377 SystemRunCtx ctx{};
16378 ctx.pWorld = &world;
16379 ctx.pPending = &pending;
16380 ctx.canScheduleSystems = sched_supports_deferred_system_jobs(world_sched(world));
16381
16382 for (const auto& item: items)
16383 run_system_entity_erased(&ctx, item);
16384 flush_pending_system_jobs(pending);
16385 }
16386
16390 inline void order_and_run_system_schedule(World& world, SystemScheduleScratch& scratch) {
16391 order_system_schedule_items(world, scratch.items, scratch);
16392 run_system_schedule(world, scratch.items);
16393 }
16394 } // namespace detail
16395
16396 inline void World::systems_init() {
16397 m_systemsQuery = query().all(System);
16398 }
16399
16400 inline void World::systems_run() {
16401 if GAIA_UNLIKELY (tearing_down())
16402 return;
16403
16404 auto& items = m_systemScheduleScratch.items;
16405 items.clear();
16406
16407 detail::SystemCollectCtx collectCtx{};
16408 collectCtx.pWorld = this;
16409 collectCtx.pItems = &items;
16410 m_systemsQuery.each_entity_enabled(&collectCtx, detail::collect_system_schedule_item_erased);
16411 detail::order_and_run_system_schedule(*this, m_systemScheduleScratch);
16412 }
16413
16414 inline void World::systems_run(Entity phase) {
16415 if GAIA_UNLIKELY (tearing_down())
16416 return;
16417
16418 GAIA_ASSERT(valid(phase));
16419 GAIA_ASSERT(!phase.pair());
16420 if (!valid(phase) || phase.pair())
16421 return;
16422 if (!enabled_hierarchy(phase, ChildOf))
16423 return;
16424
16425 auto& items = m_systemScheduleScratch.items;
16426 items.clear();
16427 detail::collect_phase_system_schedule_items(*this, phase, items);
16428 detail::order_and_run_system_schedule(*this, m_systemScheduleScratch);
16429 }
16430
16431 inline void World::systems_done() {
16432 cnt::darray<Entity> tmpEntities;
16433 m_systemsQuery.each_entity_enabled(&tmpEntities, detail::collect_system_entity_erased);
16434
16435 // Wait for every outstanding system job before mutating any system runtime state.
16436 // This keeps dependency chains intact while jobs are still live.
16437 for (auto entity: tmpEntities) {
16438 if (!valid(entity) || !has(entity, System))
16439 continue;
16440
16441 auto ss = acc_mut(entity);
16442 auto& sys = ss.smut<ecs::System_>();
16443 if (sys.jobHandle != (mt::JobHandle)mt::JobNull_t{}) {
16444 auto& tp = mt::ThreadPool::get();
16445 tp.wait(sys.jobHandle);
16446 }
16447 }
16448
16449 // With all system jobs complete we can release their runtime state safely.
16450 for (auto entity: tmpEntities) {
16451 if (!valid(entity) || !has(entity, System))
16452 continue;
16453
16454 auto ss = acc_mut(entity);
16455 auto& sys = ss.smut<ecs::System_>();
16456 if (sys.jobHandle != (mt::JobHandle)mt::JobNull_t{}) {
16457 auto& tp = mt::ThreadPool::get();
16458 tp.del(sys.jobHandle);
16459 sys.jobHandle = mt::JobNull;
16460 }
16461 sys.query = {};
16462 }
16463
16464 m_systemsQuery = {};
16465 tmpEntities.clear();
16466 }
16467
16468 inline SystemBuilder World::system() {
16469 // Create the system
16470 auto e = add();
16471 EntityBuilder(*this, e) //
16472 .add<System_>();
16473
16474 auto ss = acc_mut(e);
16475 auto& sys = ss.smut<System_>();
16476 auto& sysRuntime = systems().data_add(e);
16477 {
16478 sys.entity = e;
16479 sys.query = query();
16480 sysRuntime.on_each_func = {};
16481 }
16482 return SystemBuilder(*this, e);
16483 }
16484 } // namespace ecs
16485} // namespace gaia
16486#endif
16487
16488namespace gaia {
16489 namespace ecs {
16491 inline uint32_t world_version(const World& world) {
16492 return world.m_worldVersion;
16493 }
16494
16496 inline uint32_t world_archetype_delete_version(const World& world) {
16497 return world.m_archetypeDeleteVersion;
16498 }
16499
16503 inline const Sched& world_sched(const World& world) {
16504 return world.sched();
16505 }
16506
16513 inline void
16514 world_each_target(const World& world, Entity entity, Entity relation, void* ctx, void (*func)(void*, Entity)) {
16515 world.targets(entity, relation, [ctx, func](Entity target) {
16516 func(ctx, target);
16517 });
16518 }
16519
16523 inline QueryMatchScratch& query_match_scratch_acquire(World& world) {
16524 if (world.m_queryMatchScratchDepth == world.m_queryMatchScratchStack.size())
16525 world.m_queryMatchScratchStack.push_back(new QueryMatchScratch());
16526
16527 auto& scratch = *world.m_queryMatchScratchStack[world.m_queryMatchScratchDepth++];
16528 scratch.clear_temporary_matches();
16529 return scratch;
16530 }
16531
16535 inline void query_match_scratch_release(World& world, bool keepStamps) {
16536 GAIA_ASSERT(world.m_queryMatchScratchDepth > 0);
16537 auto& scratch = *world.m_queryMatchScratchStack[--world.m_queryMatchScratchDepth];
16538 if (keepStamps)
16539 scratch.clear_temporary_matches_keep_stamps();
16540 else
16541 scratch.clear_temporary_matches();
16542 }
16543
16547 GAIA_FORCEINLINE void world_invalidate_sorted_queries_for_entity(World& world, Entity entity) {
16548 // Sorting is rarely used; one predictable branch keeps the hot write path free of the
16549 // invalidation machinery for both serial and parallel writes.
16550 if GAIA_LIKELY (!world.has_sorted_queries())
16551 return;
16552
16553 if (world.defer_sort_inv_active()) {
16554 // Only record entities a sorted query actually depends on, so the defer queue stays
16555 // empty for the common case where no component carried a sorted query.
16556 if (!world.has_sorted_queries_for_entity(entity))
16557 return;
16558 const auto slot = defer_slot();
16559 GAIA_ASSERT(slot != BadDeferSlot);
16560 world.defer_sort_inv_record(slot, entity);
16561 return;
16562 }
16564 }
16565
16568 inline void world_invalidate_sorted_queries(World& world) {
16569 world.invalidate_sorted_queries();
16570 }
16571
16575 inline void world_defer_sort_inv_begin(World& world, uint32_t slotCount) {
16576 world.defer_sort_inv_begin(slotCount);
16577 }
16578
16582 inline void world_defer_sort_inv_end(World& world) {
16583 world.defer_sort_inv_end();
16584 }
16585
16591 inline void world_defer_parallel_begin(World& world, uint32_t itemCount) {
16592#if GAIA_OBSERVERS_ENABLED
16593 world_defer_on_set_begin(world, itemCount);
16594#endif
16595 world_defer_sort_inv_begin(world, itemCount);
16596 }
16597
16601 inline void world_defer_parallel_end(World& world) {
16602 world_defer_sort_inv_end(world);
16603#if GAIA_OBSERVERS_ENABLED
16604 world_defer_on_set_end(world);
16605#endif
16606 }
16607
16608#if GAIA_OBSERVERS_ENABLED
16612 inline void world_defer_on_set_begin(World& world, uint32_t slotCount) {
16613 world.defer_on_set_begin(slotCount);
16614 }
16615
16619 inline void world_defer_on_set_end(World& world) {
16620 world.defer_on_set_end();
16621 }
16622#endif
16623
16629 inline bool world_has_entity_term(const World& world, Entity entity, Entity term) {
16630 if (term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
16631 const auto target = world.get(term.gen());
16632 return world.valid(target) && world.is(entity, target);
16633 }
16634
16635 return world.has(entity, term);
16636 }
16637
16643 inline bool world_has_entity_term_in(const World& world, Entity entity, Entity term) {
16644 if (term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
16645 const auto target = world.get(term.gen());
16646 return world.valid(target) && world.in(entity, target);
16647 }
16648
16649 return false;
16650 }
16651
16656 inline bool world_term_uses_inherit_policy(const World& world, Entity term) {
16657 return !is_wildcard(term) && world.target(term, OnInstantiate) == Inherit;
16658 }
16659
16665 inline bool world_has_entity_term_direct(const World& world, Entity entity, Entity term) {
16666 return world.has_direct(entity, term);
16667 }
16668
16673 inline bool world_relation_uses_non_fragmenting_storage(const World& world, Entity relation) {
16674 return world.relation_uses_non_fragmenting_storage(relation);
16675 }
16676
16681 inline bool world_relation_is_non_fragmenting(const World& world, Entity relation) {
16682 return world.relation_is_non_fragmenting(relation);
16683 }
16684
16689 inline bool world_component_uses_sparse_storage(const World& world, Entity component) {
16690 return world.component_uses_sparse_storage(component);
16691 }
16692
16697 inline bool world_component_is_non_fragmenting(const World& world, Entity component) {
16698 return world.component_is_non_fragmenting(component);
16699 }
16700
16705 inline uint32_t world_count_direct_term_entities(const World& world, Entity term) {
16706 return world.count_direct_term_entities(term);
16707 }
16708
16713 inline uint32_t world_count_in_term_entities(const World& world, Entity term) {
16714 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
16715 return 0;
16716
16717 const auto target = world.get(term.gen());
16718 return world.valid(target) ? (uint32_t)world.as_relations_trav_cache(target).size() : 0U;
16719 }
16720
16725 inline uint32_t world_count_direct_term_entities_direct(const World& world, Entity term) {
16726 return world.count_direct_term_entities_direct(term);
16727 }
16728
16733 inline void world_collect_direct_term_entities(const World& world, Entity term, cnt::darray<Entity>& out) {
16734 world.collect_direct_term_entities(term, out);
16735 }
16736
16741 inline void world_collect_in_term_entities(const World& world, Entity term, cnt::darray<Entity>& out) {
16742 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
16743 return;
16744
16745 const auto target = world.get(term.gen());
16746 if (!world.valid(target))
16747 return;
16748
16749 const auto& relations = world.as_relations_trav_cache(target);
16750 out.reserve(out.size() + (uint32_t)relations.size());
16751 for (auto relation: relations)
16752 out.push_back(relation);
16753 }
16754
16759 inline void world_collect_direct_term_entities_direct(const World& world, Entity term, cnt::darray<Entity>& out) {
16760 world.collect_direct_term_entities_direct(term, out);
16761 }
16762
16769 inline bool world_each_direct_term_entity(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
16770 return world.each_direct_term_entity(term, ctx, func);
16771 }
16772
16779 inline bool world_each_in_term_entity(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
16780 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
16781 return true;
16782
16783 const auto target = world.get(term.gen());
16784 if (!world.valid(target))
16785 return true;
16786
16787 for (auto relation: world.as_relations_trav_cache(target)) {
16788 if (!func(ctx, relation))
16789 return false;
16790 }
16791
16792 return true;
16793 }
16794
16801 inline bool
16802 world_each_direct_term_entity_direct(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
16803 return world.each_direct_term_entity_direct(term, ctx, func);
16804 }
16805
16810 inline bool world_entity_enabled(const World& world, Entity entity) {
16811 return world.enabled(entity);
16812 }
16813
16818 inline Entity world_pair_target_if_alive(const World& world, Entity pair) {
16819 return world.pair_target_if_alive(pair);
16820 }
16821
16827 inline bool world_entity_enabled_hierarchy(const World& world, Entity entity, Entity relation) {
16828 return world.enabled_hierarchy(entity, relation);
16829 }
16830
16834 inline uint32_t world_enabled_hierarchy_version(const World& world) {
16835 return world.enabled_hierarchy_version();
16836 }
16837
16842 inline bool world_relation_is_hierarchy(const World& world, Entity relation) {
16843 return world.relation_is_hierarchy(relation);
16844 }
16845
16850 inline bool world_relation_is_fragmenting(const World& world, Entity relation) {
16851 return world.relation_is_fragmenting(relation);
16852 }
16853
16858 inline bool world_relation_is_fragmenting_hierarchy(const World& world, Entity relation) {
16859 return world.relation_is_fragmenting_hierarchy(relation);
16860 }
16861
16866 inline bool world_relation_supports_depth_order(const World& world, Entity relation) {
16867 return world.relation_supports_depth_order(relation);
16868 }
16869
16874 inline bool world_relation_depth_order_prunes_disabled_subtrees(const World& world, Entity relation) {
16875 return world.relation_depth_order_prunes_disabled_subtrees(relation);
16876 }
16877
16882 inline bool world_entity_prefab(const World& world, Entity entity) {
16883 const auto& ec = world.fetch(entity);
16884 return ec.pArchetype != nullptr && ec.pArchetype->has(Prefab);
16885 }
16886
16892 inline Entity world_query_first_inherited_owner(const World& world, const Archetype& archetype, Entity term) {
16893 const auto& chunks = archetype.chunks();
16894 const Chunk* pFirstChunk = nullptr;
16895 for (const auto* pChunk: chunks) {
16896 if (pChunk == nullptr || pChunk->size() == 0)
16897 continue;
16898 pFirstChunk = pChunk;
16899 break;
16900 }
16901
16902 if (pFirstChunk == nullptr)
16903 return EntityBad;
16904
16905 const auto firstEntity = pFirstChunk->entity_view()[0];
16906 for (const auto target: world.as_targets_trav_cache(firstEntity)) {
16907 if (!world.has_direct(target, term))
16908 continue;
16909 return target;
16910 }
16911
16912 return EntityBad;
16913 }
16914
16919 inline const Archetype* world_entity_archetype(const World& world, Entity entity) {
16920 return world.fetch(entity).pArchetype;
16921 }
16922
16927 inline uint32_t world_component_index_bucket_size(const World& world, Entity term) {
16928 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
16929 if (it == world.m_entityToArchetypeMap.end())
16930 return 0;
16931
16932 return (uint32_t)it->second.size();
16933 }
16934
16940 inline uint32_t world_component_index_comp_idx(const World& world, const Archetype& archetype, Entity term) {
16941 if (is_wildcard(term))
16942 return BadIndex;
16943
16944 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
16945 if (it == world.m_entityToArchetypeMap.end())
16946 return BadIndex;
16947
16948 const auto idx = core::get_index_if(it->second, [&](const auto& entry) {
16949 return entry.matches(&archetype);
16950 });
16951 if (idx == BadIndex)
16952 return BadIndex;
16953
16954 return it->second[idx].compIdx;
16955 }
16956
16962 inline uint32_t world_component_index_match_count(const World& world, const Archetype& archetype, Entity term) {
16963 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
16964 if (it == world.m_entityToArchetypeMap.end())
16965 return 0;
16966
16967 const auto idx = core::get_index_if(it->second, [&](const auto& entry) {
16968 return entry.matches(&archetype);
16969 });
16970 if (idx == BadIndex)
16971 return 0;
16972
16973 return it->second[idx].matchCount;
16974 }
16975
16980 inline Pair world_pair_record(const World& world, Entity entity) {
16981 GAIA_ASSERT(entity.pair());
16982 return Pair(world.get(entity.id()), world.get(entity.gen()));
16983 }
16984
16991 template <typename T>
16992 GAIA_NOINLINE decltype(auto) world_query_pair_record_arg_by_id_const(World& world, Entity entity, Entity term) {
16993 return world.template get<T>(world_pair_record(world, entity), term);
16994 }
16995
17002 template <typename T>
17003 inline const std::remove_cv_t<std::remove_reference_t<T>>*
17004 world_query_inherited_arg_data_const(World& world, Entity owner, Entity id) {
17005 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
17006 return &world.template get<Arg>(owner, id);
17007 }
17008
17014 inline const void* world_query_inherited_arg_data_const_ptr(const World& world, Entity owner, Entity id) {
17015 const auto& ec = world.fetch(owner);
17016 const auto row = ec.row;
17017 return ec.pChunk->comp_ptr(ec.pChunk->comp_idx(id), row);
17018 }
17019
17025 template <typename T>
17026 inline decltype(auto) world_direct_entity_arg(World& world, Entity entity) {
17027 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
17028 if constexpr (std::is_same_v<Arg, Entity>)
17029 return entity;
17030 else if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>) {
17031 if GAIA_UNLIKELY (entity.pair())
17032 return world.template mut<Arg>(world_pair_record(world, entity));
17033 return world.template mut_im<Arg>(entity);
17034 } else {
17035 if GAIA_UNLIKELY (entity.pair())
17036 return world.template get<Arg>(world_pair_record(world, entity));
17037 return world.template get<Arg>(entity);
17038 }
17039 }
17040
17046 template <typename T>
17047 inline decltype(auto) world_direct_entity_arg_raw(World& world, Entity entity) {
17048 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
17049 if constexpr (std::is_same_v<Arg, Entity>)
17050 return entity;
17051 else if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>) {
17052 if GAIA_UNLIKELY (entity.pair())
17053 return world.template mut<Arg>(world_pair_record(world, entity));
17054 return world.template mut<Arg>(entity);
17055 } else {
17056 if GAIA_UNLIKELY (entity.pair())
17057 return world.template get<Arg>(world_pair_record(world, entity));
17058 return world.template get<Arg>(entity);
17059 }
17060 }
17061
17067 template <typename T>
17068 inline void* world_typed_sparse_store_ptr(World& world, Entity component) {
17069 return &world.template sparse_component_store_mut<T>(component);
17070 }
17071
17077 template <typename T>
17078 inline const T& world_typed_sparse_store_get(const void* pStore, Entity entity) {
17079 return static_cast<const detail::SparseComponentStore<T>*>(pStore)->get(entity);
17080 }
17081
17087 template <typename T>
17088 inline T& world_typed_sparse_store_mut(void* pStore, Entity entity) {
17089 return static_cast<detail::SparseComponentStore<T>*>(pStore)->mut(entity);
17090 }
17091
17097 template <typename T>
17098 inline bool world_typed_sparse_store_has(const void* pStore, Entity entity) {
17099 return static_cast<const detail::SparseComponentStore<T>*>(pStore)->has(entity);
17100 }
17101
17107 template <typename T>
17108 inline T* world_typed_sparse_store_try_mut(void* pStore, Entity entity) {
17109 return static_cast<detail::SparseComponentStore<T>*>(pStore)->try_mut(entity);
17110 }
17111
17117 template <typename T>
17118 inline const T* world_typed_sparse_store_try_get(const void* pStore, Entity entity) {
17119 return static_cast<const detail::SparseComponentStore<T>*>(pStore)->try_get(entity);
17120 }
17121
17126 template <typename T>
17127 inline Entity world_query_arg_id(World& world) {
17128 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
17129 using FT = typename component_type_t<Arg>::TypeFull;
17130 if constexpr (is_pair<FT>::value) {
17131 const auto rel = comp_cache(world).template get<typename FT::rel>().entity;
17132 const auto tgt = comp_cache(world).template get<typename FT::tgt>().entity;
17133 return (Entity)Pair(rel, tgt);
17134 } else
17135 return comp_cache(world).template get<FT>().entity;
17136 }
17137
17143 template <typename T>
17144 inline decltype(auto) world_query_entity_arg(World& world, Entity entity) {
17145 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
17146 if constexpr (std::is_same_v<Arg, Entity>)
17147 return entity;
17148 else {
17149 const auto id = world_query_arg_id<Arg>(world);
17150 return world_query_entity_arg_by_id<T>(world, entity, id);
17151 }
17152 }
17153
17161 template <typename T>
17162 inline decltype(auto) world_query_entity_arg_by_id(World& world, Entity entity, Entity id) {
17163 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
17164 if constexpr (std::is_same_v<Arg, Entity>)
17165 return entity;
17166 else {
17167 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
17168 if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>) {
17169 if (!world.has_direct(entity, termId)) {
17170 if constexpr (is_pair<Arg>::value)
17171 (void)world.override(entity, termId);
17172 else
17173 (void)world.template override<Arg>(entity, termId);
17174 }
17175
17176 return world.template mut_im<Arg>(entity, termId);
17177 } else {
17178 if GAIA_UNLIKELY (entity.pair())
17179 return world_query_pair_record_arg_by_id_const<Arg>(world, entity, termId);
17180 return world.template get<Arg>(entity, termId);
17181 }
17182 }
17183 }
17184
17192 template <typename T>
17193 inline decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id) {
17194 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
17195 if constexpr (std::is_same_v<Arg, Entity>)
17196 return entity;
17197 else {
17198 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
17199 if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>) {
17200 if (!world.has_direct(entity, termId)) {
17201 if constexpr (is_pair<Arg>::value)
17202 (void)world.override(entity, termId);
17203 else
17204 (void)world.template override<Arg>(entity, termId);
17205 }
17206
17207 return world.template mut<Arg>(entity, termId);
17208 } else {
17209 return world.template get<Arg>(entity, termId);
17210 }
17211 }
17212 }
17213
17223 template <typename T>
17224 inline void world_init_query_entity_arg_by_id_chunk_stable_const(
17225 World& world, const Chunk& chunk, const Entity* pEntities, Entity id, bool& direct, uint32_t& compIdx,
17226 const std::remove_cv_t<std::remove_reference_t<T>>*& pDataInherited) {
17227 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
17228 if constexpr (std::is_same_v<Arg, Entity>) {
17229 direct = false;
17230 compIdx = BadIndex;
17231 pDataInherited = nullptr;
17232 return;
17233 }
17234
17235 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
17236 direct = chunk.has(termId);
17237 compIdx = BadIndex;
17238 pDataInherited = nullptr;
17239
17240 if (direct) {
17241 compIdx = chunk.comp_idx(termId);
17242 GAIA_ASSERT(compIdx != BadIndex);
17243 return;
17244 }
17245
17246 auto owner = EntityBad;
17247 const auto firstEntity = pEntities[0];
17248 for (const auto target: world.as_targets_trav_cache(firstEntity)) {
17249 if (!world.has_direct(target, termId))
17250 continue;
17251
17252 owner = target;
17253 break;
17254 }
17255
17256 GAIA_ASSERT(owner != EntityBad);
17257 pDataInherited = &world.template get<Arg>(owner, termId);
17258 }
17259
17269 template <typename T>
17270 inline decltype(auto) world_query_entity_arg_by_id_cached_const(
17271 World& world, Entity entity, Entity id, const Archetype*& pLastArchetype, Entity& cachedOwner,
17272 bool& cachedDirect) {
17273 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
17274 if constexpr (std::is_same_v<Arg, Entity>)
17275 return entity;
17276
17277 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
17278 const auto& ec = world.fetch(entity);
17279 if (ec.pArchetype != pLastArchetype) {
17280 pLastArchetype = ec.pArchetype;
17281 cachedDirect = ec.pArchetype->has(termId);
17282 cachedOwner = EntityBad;
17283
17284 if (!cachedDirect) {
17285 for (const auto target: world.as_targets_trav_cache(entity)) {
17286 if (!world.has_direct(target, termId))
17287 continue;
17288
17289 cachedOwner = target;
17290 break;
17291 }
17292
17293 GAIA_ASSERT(cachedOwner != EntityBad);
17294 }
17295 }
17296
17297 if (cachedDirect)
17298 return ComponentGetter{world, ec.pChunk, entity, ec.row}.template get<Arg>(termId);
17299
17300 return world.template get<Arg>(cachedOwner, termId);
17301 }
17302
17309 inline void world_notify_on_set(World& world, Entity term, Chunk& chunk, uint16_t from, uint16_t to) {
17310#if GAIA_OBSERVERS_ENABLED
17311 if (world.tearing_down())
17312 return;
17313 if (!world.observers().has_on_set_observers(term))
17314 return;
17315
17316 auto entities = chunk.entity_view();
17317 if (from >= entities.size())
17318 return;
17319 if (to > entities.size())
17320 to = (uint16_t)entities.size();
17321 if (from >= to)
17322 return;
17323
17324 // Observer dispatch mutates world-owned state and runs user code. Neither is safe from a
17325 // worker thread, so inside a deferring region the notification is only recorded here and
17326 // the coordinator dispatches it after the region joins.
17327 if (world.defer_on_set_active()) {
17328 const auto slot = defer_slot();
17329 GAIA_ASSERT(slot != BadDeferSlot);
17330 for (uint32_t row = from; row < to; ++row)
17331 world.defer_on_set_record(slot, term, entities[row]);
17332 return;
17333 }
17334
17335 world.observers().on_set(world, term, EntitySpan{entities.data() + from, uint32_t(to - from)});
17336#else
17337 (void)world;
17338 (void)term;
17339 (void)chunk;
17340 (void)from;
17341 (void)to;
17342#endif
17343 }
17344
17345 //----------------------------------------------------------------------
17346
17348 GAIA_ASSERT(m_pWorld != nullptr);
17349 GAIA_ASSERT(m_entity != EntityBad);
17350 return m_pWorld->get_raw(m_entity, component);
17351 }
17352
17353 inline ComponentRawView ComponentGetter::get_raw_field(Entity component, uint32_t fieldIdx) const {
17354 GAIA_ASSERT(m_pWorld != nullptr);
17355 GAIA_ASSERT(m_entity != EntityBad);
17356 return m_pWorld->get_raw_field(m_entity, component, fieldIdx);
17357 }
17358
17360 GAIA_ASSERT(m_pWorld != nullptr);
17361 GAIA_ASSERT(m_entity != EntityBad);
17362 return const_cast<World*>(m_pWorld)->mut_raw(m_entity, component);
17363 }
17364
17365 inline ComponentRawMutView ComponentSetter::mut_raw_field(Entity component, uint32_t fieldIdx) {
17366 GAIA_ASSERT(m_pWorld != nullptr);
17367 GAIA_ASSERT(m_entity != EntityBad);
17368 return const_cast<World*>(m_pWorld)->mut_raw_field(m_entity, component, fieldIdx);
17369 }
17370
17371 inline ComponentSetter& ComponentSetter::set_raw(Entity component, const void* data, uint32_t size) {
17372 GAIA_ASSERT(m_pWorld != nullptr);
17373 GAIA_ASSERT(m_entity != EntityBad);
17374 const auto ok = const_cast<World*>(m_pWorld)->set_raw(m_entity, component, data, size);
17375 GAIA_ASSERT(ok);
17376 (void)ok;
17377 return *this;
17378 }
17379
17381 GAIA_ASSERT(m_pWorld != nullptr);
17382 GAIA_ASSERT(m_entity != EntityBad);
17383 const_cast<World*>(m_pWorld)->modify_raw(m_entity, component);
17384 return *this;
17385 }
17386
17391 template <typename T>
17392 GAIA_NODISCARD decltype(auto) ComponentGetter::get(Entity type) const {
17393 GAIA_ASSERT(m_pWorld != nullptr);
17394 GAIA_ASSERT(m_entity != EntityBad);
17395
17396 using FT = typename component_type_t<T>::TypeFull;
17397 if constexpr (World::template supports_sparse_component_storage<FT>()) {
17398 if (m_pWorld->template can_use_sparse_component_storage<FT>(type)) {
17399 return m_pWorld->template sparse_component_get_value<FT>(type, m_entity);
17400 }
17401 }
17402
17403 const auto row = m_row;
17404 return m_pChunk->template get<T>(row, type);
17405 }
17406
17407 template <typename T>
17408 decltype(auto) ComponentSetter::mut(Entity type) {
17409 return smut<T>(type);
17410 }
17411
17412 template <typename T>
17413 decltype(auto) ComponentSetter::smut(Entity type) {
17414 GAIA_ASSERT(m_pWorld != nullptr);
17415 GAIA_ASSERT(m_entity != EntityBad);
17416
17417 using FT = typename component_type_t<T>::TypeFull;
17418 if constexpr (World::template supports_sparse_component_storage<FT>()) {
17419 auto& world = *const_cast<World*>(m_pWorld);
17420 if (world.template can_use_sparse_component_storage<FT>(type))
17421 return world.template sparse_component_mut_value<FT>(type, m_entity);
17422 }
17423
17424 const auto row = m_row;
17425 return const_cast<Chunk*>(m_pChunk)->template sset<T>(row, type);
17426 }
17427
17428 template <typename T>
17430 smut<T>(type) = GAIA_FWD(value);
17431 return *this;
17432 }
17433
17434 template <typename T, typename U>
17436 GAIA_ASSERT(m_pWorld != nullptr);
17437 GAIA_ASSERT(m_entity != EntityBad);
17438 GAIA_ASSERT(m_pChunk != nullptr);
17439 smut<T>() = GAIA_FWD(value);
17440 if constexpr (uses_ct_sparse_storage_v<T>) {
17441 auto& world = *const_cast<World*>(m_pWorld);
17442 world.finish_write(m_entity, m_pChunk->template comp_entity<T>());
17443 } else {
17444 auto& chunk = *const_cast<Chunk*>(m_pChunk);
17445 chunk.template modify<T, true>();
17446 world_notify_on_set(chunk.world(), chunk.template comp_entity<T>(), chunk, m_row, (uint16_t)(m_row + 1));
17447 }
17448 return *this;
17449 }
17450
17451 template <typename T>
17453 GAIA_ASSERT(m_pWorld != nullptr);
17454 GAIA_ASSERT(m_entity != EntityBad);
17455
17456 smut<T>(type) = GAIA_FWD(value);
17457 auto& world = *const_cast<World*>(m_pWorld);
17458
17459 world.finish_write(m_entity, type);
17460 return *this;
17461 }
17462
17463 //----------------------------------------------------------------------
17464
17469 inline void world_notify_on_set_entity(World& world, Entity term, Entity entity) {
17470#if GAIA_OBSERVERS_ENABLED
17471 if (world.tearing_down())
17472 return;
17473 if (world.try_live_record(entity) == nullptr)
17474 return;
17475 if (!world.observers().has_on_set_observers(term))
17476 return;
17477
17478 // See world_notify_on_set(): inside a deferring region the notification is recorded and
17479 // dispatched by the coordinator once the region joins.
17480 if (world.defer_on_set_active()) {
17481 const auto slot = defer_slot();
17482 GAIA_ASSERT(slot != BadDeferSlot);
17483 world.defer_on_set_record(slot, term, entity);
17484 return;
17485 }
17486
17487 world.observers().on_set(world, term, EntitySpan{&entity, 1});
17488#else
17489 (void)world;
17490 (void)term;
17491 (void)entity;
17492#endif
17493 }
17494
17496 inline void world_finish_write(World& world, Entity term, Entity entity) {
17497 world.finish_write(entity, term);
17498 }
17499
17500 } // namespace ecs
17501} // namespace gaia
17502
17503namespace gaia {
17504 namespace ecs {
17506 inline uint32_t world_rel_version(const World& world, Entity relation) {
17507 return world.rel_version(relation);
17508 }
17509
17516 inline uint32_t world_entity_archetype_version(const World& world, Entity entity) {
17517 if (world.try_live_record(entity) == nullptr)
17518 return 0;
17519
17520 const auto key = EntityLookupKey(entity);
17521 auto it = world.m_srcEntityVersions.find(key);
17522 if (it != world.m_srcEntityVersions.end())
17523 return it->second;
17524
17525 it = world.m_srcEntityVersions.try_emplace(key, 1).first;
17526 return it->second;
17527 }
17528 } // namespace ecs
17529} // namespace gaia
17530
17531#if GAIA_OBSERVERS_ENABLED
17532namespace gaia {
17533 namespace ecs {
17534 inline ObserverBuilder World::observer() {
17535 // Create the observer
17536 auto e = add();
17537 EntityBuilder(*this, e) //
17538 .add<Observer_>();
17539
17540 auto ss = acc_mut(e);
17541 auto& hdr = ss.smut<Observer_>();
17542 auto& obs = observers().data_add(e);
17543 {
17544 hdr.entity = e;
17545 obs.entity = e;
17546 obs.query = query();
17547 }
17548 return ObserverBuilder(*this, e);
17549 }
17550 } // namespace ecs
17551} // namespace gaia
17552#endif
Array of elements of type.
Definition darray_ext_impl.h:31
void pop_back() noexcept
Removes the last element.
Definition darray_ext_impl.h:400
GAIA_NODISCARD size_type size() const noexcept
Returns the number of elements.
Definition darray_ext_impl.h:561
iterator insert(iterator pos, const T &arg)
Insert the element to the position given by iterator pos.
Definition darray_ext_impl.h:414
void reserve(size_type cap)
Ensures storage for at least the requested number of elements.
Definition darray_ext_impl.h:266
void push_back(const T &arg)
Appends an element.
Definition darray_ext_impl.h:360
GAIA_NODISCARD auto end() noexcept
Returns an iterator one past the last element.
Definition darray_ext_impl.h:649
void resize(size_type count, ResizePolicy policy=ResizePolicy::Exact)
Changes the number of elements.
Definition darray_ext_impl.h:289
void clear() noexcept
Removes all elements.
Definition darray_ext_impl.h:518
GAIA_NODISCARD decltype(auto) back() noexcept
Accesses the last element.
Definition darray_ext_impl.h:599
GAIA_NODISCARD pointer data() noexcept
Returns a pointer to the element storage.
Definition darray_ext_impl.h:236
GAIA_NODISCARD bool empty() const noexcept
Checks whether the container has no elements.
Definition darray_ext_impl.h:567
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
GAIA_NODISCARD EntitySpan ids_view() const
Span over the component and entity identifiers defining the archetype shape.
Definition archetype.h:800
void set_hashes(LookupHash hashLookup)
Sets hashes for each component type and lookup.
Definition archetype.h:660
GAIA_NODISCARD uint32_t pairs() const
Returns the number of pairs registered in the archetype.
Definition archetype.h:812
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:861
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:826
Fixed-capacity archetype storage unit holding entities and their component columns.
Definition chunk.h:36
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:304
GAIA_NODISCARD EntitySpan ids_view() const
Span over the component and entity identifiers held by this chunk.
Definition chunk.h:809
GAIA_NODISCARD uint8_t * comp_ptr_mut(uint32_t compIdx)
Mutable pointer to the start of a component column.
Definition chunk.h:822
GAIA_NODISCARD uint32_t comp_idx(Entity entity) const
Returns the internal index of a component based on the provided entity.
Definition chunk.h:1623
GAIA_NODISCARD const uint8_t * comp_ptr(uint32_t compIdx) const
Const pointer to the start of a component column.
Definition chunk.h:839
GAIA_NODISCARD bool has(Entity entity) const
Checks if a component/entity entity is present in the chunk.
Definition chunk.h:1450
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:1295
GAIA_NODISCARD uint32_t & world_version()
Returns the current version of the world.
Definition world.h:9963
void defer_sort_inv_end()
Stops recording sorted-query invalidations and applies everything recorded since the matching defer_s...
Definition world.h:9732
void as(Entity entity, Entity entityBase)
Shortcut for add(entity, Pair(Is, entityBase)).
Definition world.h:6826
GAIA_NODISCARD const EntityContainer & fetch(Entity entity) const
Returns the internal record for entity.
Definition world.h:1081
void invalidate_queries_for_structural_entity(EntityLookupKey entityKey)
Invalidates cached queries structurally affected by entityKey.
Definition world.h:14906
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:9442
GAIA_NODISCARD uint32_t count_direct_term_entities_direct(Entity term) const
Counts entities directly matching term without semantic Is expansion.
Definition world.h:9400
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:1774
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:8931
void scope(Entity scopeEntity, Func &&func)
Executes func with a temporary component scope and restores the previous scope afterwards....
Definition world.h:7922
GAIA_NODISCARD bool is_child(Entity entity, Entity parent) const
Checks whether entity has a ChildOf relationship to parent.
Definition world.h:6885
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:1395
void defrag_entities_per_tick(uint32_t value)
Sets the maximum number of entities defragmented per world tick.
Definition world.h:10179
ComponentGetter acc(Pair entity) const
Starts a bulk get operation on an exact pair record.
Definition world.h:7494
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:6589
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:7343
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:1701
GAIA_NODISCARD bool has_sorted_queries() const
Checks whether any cached sorted query is registered in this world.
Definition world.h:14921
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:8556
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:8638
GAIA_NODISCARD const cnt::set< EntityLookupKey > * targets(Entity relation) const
Returns targets for relation.
Definition world.h:8779
void enable(Pair entity, bool enable)
Enables or disables an exact pair record.
Definition world.h:9791
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:6969
void update_src_entity_version(Entity entity)
Updates a tracked source-entity version after the entity changes archetype membership.
Definition world.h:9996
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:1836
bool alias(Entity entity, const char *alias, uint32_t len=0)
Assigns an alias name to an entity.
Definition world.h:3933
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:1475
GAIA_NODISCARD auto set(Pair entity, Entity object)
Returns a write-back proxy for a runtime-selected component on an exact pair record.
Definition world.h:7107
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:8487
ser::serializer get_serializer() const
Returns the currently bound runtime serializer handle.
Definition world.h:1991
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:8711
EntityBuilder build(Entity entity)
Starts a bulk add/remove operation on entity.
Definition world.h:4330
GAIA_NODISCARD const EntityContainer * try_live_record(Entity entity) const
Returns a record that is present and not delete-requested. This is the one-lookup replacement for val...
Definition world.h:1136
GAIA_NODISCARD util::str_view symbol(Entity component) const
Returns the registered symbol name for a component entity.
Definition world.h:3862
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:1955
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:7391
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:7884
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:7216
Query uquery()
Provides an uncached query set up to work with the parent world. Uncached queries keep only a local i...
Definition world.h:1009
void enable(Entity entity, bool enable)
Enables or disables an entire entity.
Definition world.h:9767
void add(Pair entity, U &&value)
Attaches a new component T to an exact pair record and initializes its value.
Definition world.h:4665
void add(Pair entity, Pair pair)
Attaches a relationship pair to an exact pair record.
Definition world.h:4530
GAIA_NODISCARD const ComponentCache & comp_cache() const
Returns read-only access to the world component cache.
Definition world.h:3841
GAIA_NODISCARD decltype(auto) get(Pair entity, Entity object) const
Returns a runtime-selected component value from an exact pair record.
Definition world.h:7600
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:1805
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:15006
GAIA_NODISCARD decltype(auto) get(Pair entity) const
Returns the value stored in component T on an exact pair record.
Definition world.h:7544
void set_sched(const Sched &sched)
Installs a custom scheduler used by ECS parallel execution paths.
Definition world.h:1032
void child(Entity entity, Entity parent)
Shortcut for add(entity, Pair(ChildOf, parent)).
Definition world.h:6870
GAIA_NODISCARD Entity symbol(const char *symbol, uint32_t len=0) const
Finds a component entity by its exact registered symbol.
Definition world.h:3851
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:1378
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:6667
void set_serializer(ser::serializer serializer)
Binds a pre-built runtime serializer handle.
Definition world.h:1977
void clear(Entity entity)
Removes any component or entity attached to entity.
Definition world.h:4729
GAIA_NODISCARD bool enabled(Pair entity) const
Checks whether an exact pair record is enabled.
Definition world.h:9838
GAIA_NODISCARD bool override(Entity entity, Entity object)
Materializes an inherited id as directly owned storage on entity.
Definition world.h:4677
GAIA_NODISCARD const ComponentCacheItem & add()
Creates a new component if not found already.
Definition world.h:4413
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:3952
void add(Entity entity, Entity object)
Attaches entity object to entity entity.
Definition world.h:4500
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:1445
GAIA_NODISCARD util::str_view name(EntityId entityId) const
Returns the entity name assigned to entityId.
Definition world.h:8113
GAIA_NODISCARD const cnt::set< EntityLookupKey > * relations(Entity target) const
Returns relations for target.
Definition world.h:8303
void del_nonfragmenting_relation(Entity relation)
Removes every stored source-target mapping for a non-fragmenting relation.
Definition world.h:1932
Entity scope(Entity scope)
Sets the current component scope used for component registration and relative component lookup....
Definition world.h:7906
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:7576
GAIA_NODISCARD bool can_add_component_storage_trait(Entity component, Entity trait) const
Returns whether a runtime storage trait can be attached to component. Compile-time component payload ...
Definition world.h:1511
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:1415
GAIA_NODISCARD Entity get() const
Returns the entity registered for component type T.
Definition world.h:4308
GAIA_NODISCARD decltype(auto) sset(Pair entity, Entity object)
Silently accesses a runtime-selected component on an exact pair record.
Definition world.h:7172
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:7122
GAIA_NODISCARD bool enabled_hierarchy(Pair entity, Entity relation) const
Checks whether an exact pair record is enabled together with all of its ancestors reachable through r...
Definition world.h:9873
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:1646
GAIA_NODISCARD bool enabled(const EntityContainer &ec) const
Checks if an entity is enabled.
Definition world.h:9815
void add(Entity entity, U &&value)
Attaches a new component T to entity. Also sets its value.
Definition world.h:4627
GAIA_NODISCARD decltype(auto) mut(Pair entity)
Returns silent mutable access to a component on an exact pair record.
Definition world.h:7204
void invalidate_sorted_queries_for_entity(Entity entity)
Invalidates cached sorted queries whose row ordering depends on entity.
Definition world.h:14934
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:6680
GAIA_NODISCARD bool is(Entity entity, Entity entityBase) const
Checks if entity inherits from entityBase.
Definition world.h:6838
void sources(Entity relation, Entity target, Func func) const
Returns relationship sources for the relation and target.
Definition world.h:8950
void del_sparse_component_store(Entity component)
Deletes the sparse component store associated with component.
Definition world.h:1761
GAIA_NODISCARD bool defer_sort_inv_active() const
Returns whether sorted-query invalidations are currently being recorded.
Definition world.h:9749
void add(Entity entity)
Attaches a new component T to entity.
Definition world.h:4544
GAIA_NODISCARD EntityContainer & fetch(Entity entity)
Returns the internal record for entity.
Definition world.h:1052
void del(Entity entity)
Removes an entity along with all data associated with it.
Definition world.h:6727
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:8087
void del(Entity entity)
Removes a component T from entity.
Definition world.h:6796
GAIA_NODISCARD SparseStorageMode sparse_storage_mode(Entity component) const
Returns the sparse storage mode used by a component.
Definition world.h:1340
void remove_src_entity_version(Entity entity)
Removes sparse source-version state for an entity that is being destroyed.
Definition world.h:10007
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:4448
void ids_if(Entity entity, Func &&func, EntityIdOptions options={}) const
Invokes func for each id assigned to entity until func returns false. Membership and visit order matc...
Definition world.h:8053
void entity_deletion_enter(Entity entity)
Marks entity as active in the deletion pipeline.
Definition world.h:1186
GAIA_NODISCARD bool locked() const
Checks if the chunk is locked for structural changes.
Definition world.h:10456
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:8408
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:8515
GAIA_NODISCARD bool is_req_del(const EntityContainer &ec) const
Returns whether the record is already in delete-requested state. Covers both explicit per-entity dele...
Definition world.h:1115
void defer_sort_inv_record(uint32_t slot, Entity entity)
Records a sorted-query invalidation for later application.
Definition world.h:9756
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:1795
void invalidate_queries_for_entity(Pair is_pair)
Invalidates semantic Is queries affected by removing or changing is_pair.
Definition world.h:14945
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:9716
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:14928
GAIA_NODISCARD const ComponentCacheItem & reg_comp()
Returns the registered component cache item for T, auto-registering it when enabled.
Definition world.h:4316
void entity_deletion_leave(Entity entity)
Removes entity from the active deletion stack.
Definition world.h:1195
void set_max_lifespan(Entity entity)
Resets maximal lifespan of an archetype entity belongs to to the world's default.
Definition world.h:10014
GAIA_NOINLINE void entity_deletion_set_enter()
Adds the entity just pushed on a deep m_entitiesDeleting to m_entitiesDeletingSet....
Definition world.h:1205
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:7237
void invalidate_queries_for_rel(Entity relation)
Invalidates cached queries whose dynamic result depends on relation.
Definition world.h:14912
GAIA_NODISCARD auto set(Pair entity)
Returns a write-back proxy for component T on an exact pair record.
Definition world.h:7073
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:6536
GAIA_NODISCARD Entity get(EntityId id) const
Returns the entity located at the index id.
Definition world.h:4287
void add_n(Pair entity, uint32_t count, Func func=func_void_with_entity)
Creates count ordinary entities with the same type as an exact pair record. Table component payloads ...
Definition world.h:4405
GAIA_NODISCARD const Sched & sched() const
Returns the resolved scheduler used by this world.
Definition world.h:1043
GAIA_NODISCARD ComponentSetter acc_mut(Pair entity)
Starts a bulk set operation on an exact pair record.
Definition world.h:7039
void update()
Runs systems and then finishes the current frame.
Definition world.h:10104
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:8148
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:8070
GAIA_NODISCARD const auto * sparse_component_store(Entity component) const
Returns the sparse component store for component, or nullptr if it does not exist.
Definition world.h:1614
void add(Entity entity, Entity object, T &&value)
Attaches object to entity. Also sets its value.
Definition world.h:4574
ComponentGetter acc(Entity entity) const
Starts a bulk get operation on an entity.
Definition world.h:7482
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:8663
GAIA_NODISCARD bool enabled(Entity entity) const
Checks if an entity is enabled.
Definition world.h:9828
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:8447
GAIA_NODISCARD Entity register_component_id()
Registers the component or exact pair represented by compile-time type T.
Definition world.h:1547
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:1678
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:1386
void parent(Entity entity, Entity parentEntity)
Adds a direct non-fragmenting Parent relationship to parentEntity. The relationship is non-fragmentin...
Definition world.h:6893
void diag_components() const
Performs diagnostics on registered components. Prints basic info about them and reports and detected ...
Definition world.h:10194
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:9407
void targets_if(Entity entity, Entity relation, Func func) const
Returns the relationship targets for the relation entity on entity.
Definition world.h:8883
GAIA_NODISCARD bool valid(Entity entity) const
Checks if entity is valid.
Definition world.h:4276
CommandBufferST & cmd_buffer_st() const
Returns the single-threaded deferred command buffer owned by the world.
Definition world.h:9579
GAIA_NODISCARD auto * sparse_component_store(Entity component)
Returns the sparse component store for component, or nullptr if it does not exist.
Definition world.h:1600
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:6575
static GAIA_NODISCARD bool is_req_del_or_queued(const EntityContainer &ec)
Returns whether the record is delete-requested or waits as a row of a delete-requested archetype....
Definition world.h:1125
void add(Entity entity, Pair pair, T &&value)
Attaches a relationship pair to entity and initializes its payload.
Definition world.h:4616
void modify(Pair entity)
Marks component type T as modified on an exact pair record. If TriggerSetEffects is true,...
Definition world.h:6947
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity is currently used by the world.
Definition world.h:7622
bool path(Entity component, const char *path, uint32_t len=0)
Assigns a scoped path name to a component entity.
Definition world.h:3892
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:8613
GAIA_NODISCARD Entity copy(Entity srcEntity)
Creates a new ordinary entity by cloning an already existing one. Does not trigger observers....
Definition world.h:4773
void diag() const
Performs all diagnostics.
Definition world.h:10223
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:9913
GAIA_NODISCARD bool has(Entity entity, Pair pair) const
Checks if entity contains pair.
Definition world.h:7991
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:7941
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:1252
static GAIA_NODISCARD constexpr bool uses_ct_sparse_storage()
Returns whether T has compile-time sparse payload storage.
Definition world.h:1539
GAIA_NODISCARD bool has(Pair pair) const
Checks if pair is currently used by the world.
Definition world.h:7677
void save()
Saves contents of the world to a buffer. The buffer is reset, not appended. NOTE: In order for custom...
Definition world.h:10870
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:7877
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:1577
bool nonfragmenting_relation_del(Entity source, Entity relation, Entity target)
Removes an exclusive non-fragmenting relation from a source entity.
Definition world.h:1815
GAIA_NODISCARD decltype(auto) sparse_component_get_entity_value(Entity component, Entity entity) const
Returns a read-only sparse value for an ordinary entity without pair dispatch.
Definition world.h:1691
void add(Pair entity)
Attaches a new component T to an exact pair record.
Definition world.h:4562
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:1243
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:1286
GAIA_NODISCARD decltype(auto) get(Entity entity) const
Returns the value stored in the component T on entity.
Definition world.h:7510
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:1264
void add(Entity entity, Pair pair)
Attaches a relationship pair to entity.
Definition world.h:4517
void ids(Entity entity, Func &&func, EntityIdOptions options={}) const
Invokes func for each id assigned to entity. By default this is the complete direct type: archetype-r...
Definition world.h:8039
GAIA_NODISCARD uint32_t count_direct_term_entities(Entity term) const
Counts entities directly matching term, including semantic Is inheritance expansion.
Definition world.h:9393
void add_n(uint32_t count, Func func=func_void_with_entity)
Creates count new empty entities.
Definition world.h:4352
static bool s_enableUniqueNameDuplicateAssert
Allows asserts on duplicate name/alias assignment.
Definition world.h:83
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:9979
void child(Pair entity, Entity parent)
Adds a ChildOf relationship from an exact pair record to parent.
Definition world.h:6877
GAIA_NODISCARD decltype(auto) sset(Pair entity)
Returns silent mutable access to component type T on an exact pair record.
Definition world.h:7139
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:9940
void set_component_dont_fragment(Entity component, EntityContainer &ec)
Latches DontFragment on a component entity record. Payload-bearing components are moved to sparse sto...
Definition world.h:1457
void del(Entity entity, Entity object)
Removes an object from entity if possible.
Definition world.h:6753
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:7695
void diag_entities() const
Performs diagnostics on entities of the world. Also performs validation of internal structures which ...
Definition world.h:10200
GAIA_NODISCARD bool is_dont_fragment(Entity entity) const
Returns whether entity is marked DontFragment.
Definition world.h:1227
GAIA_NODISCARD util::str_view path(Entity component) const
Returns the scoped path name for a component entity.
Definition world.h:3882
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:7194
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:1368
void reset_sched()
Resets the world back to the default Gaia scheduler.
Definition world.h:1037
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:6565
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:8127
GAIA_NODISCARD Entity path(const char *path, uint32_t len=0) const
Finds a component entity by its exact scoped path.
Definition world.h:3871
GAIA_NODISCARD Chunk * get_chunk(Pair entity, uint32_t &row) const
Returns the chunk and row containing an exact pair record.
Definition world.h:9923
GAIA_NODISCARD Chunk * get_chunk(Pair entity) const
Returns the chunk containing an exact pair record.
Definition world.h:9904
void frame_cleanup()
Performs deferred cleanup for the current frame.
Definition world.h:10063
void diag_archetypes() const
Performs diagnostics on archetypes. Prints basic info about them and the chunks they contain.
Definition world.h:10186
void set_max_lifespan(Entity entity, uint32_t lifespan)
Sets maximal lifespan of an archetype entity belongs to.
Definition world.h:10022
void modify(Pair entity, Entity object)
Marks a component selected by object as modified on an exact pair record. If TriggerSetEffects is tru...
Definition world.h:7003
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:6848
GAIA_NODISCARD ComponentCacheItem & add(const ComponentDesc &desc)
Creates a new runtime component from a plain component descriptor if not found already.
Definition world.h:4480
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:1711
GAIA_NODISCARD bool has_direct(Entity entity, Pair pair) const
Checks if entity directly contains pair, without semantic inheritance expansion.
Definition world.h:7703
void relations_if(Entity entity, Entity target, Func func) const
Returns the relationship relations for the target entity on entity.
Definition world.h:8377
void del_sparse_components(Entity entity)
Removes all sparse component instances owned by entity.
Definition world.h:1752
void copy_n(Entity entity, uint32_t count, Func func=func_void_with_entity)
Creates count new ordinary entities by cloning an already existing one. Exact pair records are cloned...
Definition world.h:4822
GAIA_NODISCARD util::str_view name(Entity entity) const
Returns the name assigned to entity.
Definition world.h:8095
void sources_if(Entity relation, Entity target, Func func) const
Returns relationship sources for the relation and target.
Definition world.h:9011
GAIA_NODISCARD Entity add()
Creates a new empty entity.
Definition world.h:4336
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:7058
bool as_targets_trav_if(Entity relation, Func func) const
Traverses transitive Is targets of relation until func returns true.
Definition world.h:9562
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:9546
GAIA_NODISCARD uint32_t rel_version(Entity relation) const
Returns structural version for a given relation. Increments whenever any Pair(relation,...
Definition world.h:9971
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:1276
Query query()
Provides a cached query set up to work with the parent world. Cached queries use local scope by defau...
Definition world.h:999
GAIA_NODISCARD Entity prefab()
Creates a new prefab entity.
Definition world.h:4342
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:10962
GAIA_NODISCARD uint32_t default_max_lifespan() const
Returns the lifespan archetypes created from now on start with.
Definition world.h:10047
GAIA_NODISCARD EntityContainer * try_live_record(Entity entity)
Returns a record that is present and not delete-requested.
Definition world.h:1146
GAIA_NOINLINE void entity_deletion_set_leave()
Removes the top of a deep m_entitiesDeleting from m_entitiesDeletingSet before it is popped....
Definition world.h:1216
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:8745
void teardown()
Performs world shutdown maintenance without running systems or observers. Runtime callbacks are shut ...
Definition world.h:10119
void del(Entity entity, Pair pair)
Removes an existing entity relationship pair.
Definition world.h:6786
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:14970
GAIA_NODISCARD Entity scope() const
Returns the current component scope used for component registration and relative component lookup.
Definition world.h:7898
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:7094
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:7156
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:1303
GAIA_NODISCARD Chunk * get_chunk(Entity entity) const
Returns a chunk containing the entity.
Definition world.h:9897
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:9492
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:1665
void del_nonfragmenting_relation_source(Entity source)
Removes all outgoing non-fragmenting relations from a source entity.
Definition world.h:1878
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:7273
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:6855
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:8183
GAIA_NODISCARD bool 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:9432
GAIA_NODISCARD util::str_view alias(Entity entity) const
Returns the alias assigned to an entity.
Definition world.h:3914
static GAIA_NODISCARD constexpr bool supports_sparse_component_storage()
Sparse storage supports plain components and exact relationship pairs. SoA layouts stay on the normal...
Definition world.h:1530
void targets(Entity entity, Entity relation, Func func) const
Returns the relationship targets for the relation entity on entity.
Definition world.h:8834
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:7311
GAIA_NODISCARD bool has(Entity entity, Entity object) const
Checks if entity contains the entity object.
Definition world.h:7687
GAIA_NODISCARD NonFragmentingRelationStore & nonfragmenting_relation_store_mut(Entity relation)
Returns the non-fragmenting relation store for relation, creating it if needed.
Definition world.h:1785
GAIA_NODISCARD uint32_t archetype_delete_version() const
Returns the version that changes when archetype deletion visibility changes.
Definition world.h:9985
void set_serializer(TSerializer &serializer)
Binds a concrete serializer object through ser::make_serializer().
Definition world.h:1985
GAIA_NODISCARD uint32_t size() const
Returns the number of active entities.
Definition world.h:9931
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity contains the component T.
Definition world.h:8002
void set_serializer(std::nullptr_t)
Resets runtime serializer binding to the default internal bin_stream backend.
Definition world.h:1970
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:9414
GAIA_NODISCARD Entity instantiate(Entity prefabEntity)
Instantiates a prefab as a normal entity. The instance copies the prefab's direct data,...
Definition world.h:6517
GAIA_NODISCARD GAIA_FORCEINLINE Entity register_sparse_component_id(SparseStorageMode &mode)
Registers a compile-time sparse component or exact pair and resolves its runtime fragmentation mode....
Definition world.h:1562
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:3970
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:7440
GAIA_NODISCARD ComponentSetter acc_mut(Entity entity)
Starts a bulk set operation on entity.
Definition world.h:7027
void add_n(Entity entity, uint32_t count, Func func=func_void_with_entity)
Creates count ordinary entities with the same type as entity. Table component payloads are left unini...
Definition world.h:4368
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:7457
void frame_end()
Marks the end of the current frame.
Definition world.h:10088
GAIA_NODISCARD auto & sparse_component_store_mut(Entity component)
Returns the sparse component store for component, creating it if needed.
Definition world.h:1628
GAIA_NODISCARD Entity relation(Entity entity, Entity target) const
Returns the first relationship relation for the target entity on entity.
Definition world.h:8312
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:6915
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:1730
GAIA_NODISCARD Entity alias(const char *alias, uint32_t len=0) const
Finds an entity by its exact alias.
Definition world.h:3901
QuerySerBuffer & query_buffer(QueryId &serId)
Returns the temporary serialization buffer used while building a query. A fresh query id is allocated...
Definition world.h:14861
void cleanup()
Clears the world so that all its entities and components are released. Invalidates retained queries,...
Definition world.h:10162
GAIA_NODISCARD Entity target(Entity entity, Entity relation) const
Returns the first relationship target for the relation entity on entity.
Definition world.h:8788
GAIA_NODISCARD ComponentCache & comp_cache_mut()
Returns mutable access to the world component cache.
Definition world.h:3835
void relations(Entity entity, Entity target, Func func) const
Returns the relationship relations for the target entity on entity.
Definition world.h:8344
void query_buffer_reset(QueryId &serId)
Releases the temporary serialization buffer associated with serId.
Definition world.h:14895
GAIA_NODISCARD bool relation_is_non_fragmenting(Entity relation) const
Returns whether relation is non-fragmenting.
Definition world.h:1235
GAIA_NODISCARD bool component_is_non_fragmenting(Entity component) const
Returns whether component is non-fragmenting. Non-fragmenting component ids do not participate in arc...
Definition world.h:1325
void invalidate_sorted_queries()
Invalidates all cached sorted queries after row-order changes.
Definition world.h:14939
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:9850
bool load(TSerializer &inputSerializer)
Loads a world state from a serializer-compatible stream wrapper.
Definition world.h:11298
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:10463
GAIA_NODISCARD decltype(auto) mut(Pair entity, Entity object)
Returns silent mutable access to a runtime-selected component on an exact pair record.
Definition world.h:7227
GAIA_NODISCARD bool 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:9423
void set_default_max_lifespan(uint32_t lifespan)
Sets the lifespan archetypes created from now on start with.
Definition world.h:10040
GAIA_NODISCARD bool entity_deletion_active(Entity entity) const
Returns whether entity is already being processed by the deletion pipeline.
Definition world.h:1173
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:8760
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:4300
CommandBufferMT & cmd_buffer_mt() const
Returns the multi-thread-safe deferred command buffer owned by the world.
Definition world.h:9585
GAIA_NODISCARD bool is_parent(Entity entity, Entity parentEntity) const
Checks whether entity has a direct non-fragmenting Parent relationship to parentEntity.
Definition world.h:6901
Buffer for deferred execution of some operations on entities.
Definition command_buffer.h:56
Builds, caches, and executes a Gaia-ECS query.
Definition query.h:539
Wrapper for two Entities forming a relationship pair. Conversion to Entity stores only id() of each e...
Definition id.h:559
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:197
static ThreadPool & get()
Returns the process-wide thread-pool instance.
Definition threadpool.h:168
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:164
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
Plain component registration descriptor shared by typed and runtime component paths....
Definition component_desc.h:277
RuntimeTypeDesc runtimeType
Runtime reflection metadata copied during registration.
Definition component_desc.h:313
util::str_view name
Registered component symbol.
Definition component_desc.h:298
Entity-scoped component accessor bound to a specific world, chunk and row. It is not a standalone chu...
Definition component_getter.h:16
const Chunk * m_pChunk
Chunk containing the entity's table-backed data.
Definition component_getter.h:20
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:17347
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:17353
uint16_t m_row
Entity row within the chunk.
Definition component_getter.h:24
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:17359
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:17365
ComponentSetter & set(U &&value)
Sets the value of the component.
Definition world.h:17435
decltype(auto) smut()
Returns a mutable reference to component without triggering a world version update.
Definition component_setter.h:60
ComponentSetter & modify_raw(Entity component)
Marks a raw payload or SoA field returned by a mutable raw view as modified.
Definition world.h:17380
ComponentSetter & sset(U &&value)
Sets the value of the component without triggering a world version update.
Definition component_setter.h:75
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:17371
Identifier of a registered component type. Packs the component id, size, alignment,...
Definition id.h:45
Component used to describe the entity name.
Definition id.h:540
Options for World::ids / World::ids_if. Defaults visit the complete direct type: every storage source...
Definition id.h:887
Hashmap lookup structure used for Entity.
Definition id.h:486
Identifier of an entity or component instance in the world. Packs the entity index,...
Definition id.h:231
void load(Serializer &s)
Deserializes the entity id, remapping it to the current world.
Definition id.h:467
GAIA_NODISCARD constexpr auto gen() const noexcept
Generation index of the entity.
Definition id.h:301
GAIA_NODISCARD constexpr bool comp() const noexcept
Whether this id refers to a component.
Definition id.h:319
InternalData data
Structured view of the packed value.
Definition id.h:263
GAIA_NODISCARD constexpr bool pair() const noexcept
Whether this id refers to a relationship pair.
Definition id.h:313
GAIA_NODISCARD constexpr auto id() const noexcept
Entity index in the entity array.
Definition id.h:295
GAIA_NODISCARD constexpr bool entity() const noexcept
Whether this id refers to an entity.
Definition id.h:307
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:116
Runtime reflection metadata embedded in a component descriptor or supplied to typed registration....
Definition component_desc.h:240
Entity semantic
Optional named entity identifying the authored semantic.
Definition component_desc.h:244
const RuntimeFieldInit * fields
Runtime field initializers copied during registration.
Definition component_desc.h:252
uint32_t fieldCount
Number of field initializers.
Definition component_desc.h:254
Scheduler descriptor used by ECS runtime code. All callbacks may be null when the descriptor is only ...
Definition sched.h:71
Orders entities by id only, ignoring the generation and the other packed bits. Sorts that need this o...
Definition id.h:369
Applies structural and value changes to one entity.
Definition world.h:1998
EntityBuilder & as(Entity entityBase)
Shortcut for add(Pair(Is, entityBase)). Effectively makes an entity inherit from entityBase.
Definition world.h:2381
void commit()
Commits all gathered changes and performs an archetype movement.
Definition world.h:2098
void del_name()
Removes any name associated with the entity.
Definition world.h:2284
EntityBuilder & child(Entity parent)
Shortcut for add(Pair(ChildOf, parent))
Definition world.h:2404
Archetype * m_pArchetype
Target archetype we want to move to.
Definition world.h:2014
EntityBuilder & del()
Removes the registered component or pair type from the pending entity state.
Definition world.h:2453
GAIA_NODISCARD bool as(Entity entity, Entity entityBase) const
Check if entity inherits from entityBase.
Definition world.h:2397
EntityBuilder & add()
Adds the registered component or pair type to the pending entity state.
Definition world.h:2420
EntityBuilder & add(Pair pair)
Prepares an archetype movement by following the "add" edge of the current archetype.
Definition world.h:2364
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:2263
EntityBuilder & add(Entity entity)
Prepares an archetype movement by following the "add" edge of the current archetype.
Definition world.h:2351
EntityBuilder(const EntityBuilder &)=delete
Entity builders own one pending commit and therefore cannot be copied.
EntityBuilder(World &world, Entity entity)
Creates a builder and fetches the entity's current storage record.
Definition world.h:2057
EntityBuilder & del(Pair pair)
Prepares an archetype movement by following the "del" edge of the current archetype.
Definition world.h:2440
EntityBuilder(World &world, Entity entity, EntityContainer &ec)
Creates a builder from an already fetched entity record.
Definition world.h:2047
Entity register_component()
Takes care of registering the component type used by T.
Definition world.h:2412
EntityBuilder & del(Entity entity)
Prepares an archetype movement by following the "del" edge of the current archetype.
Definition world.h:2429
Entity m_entity
Source entity.
Definition world.h:2020
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:2271
EntityNameLookupKey m_targetAliasKey
Target alias string pointer.
Definition world.h:2018
EntityBuilder & prefab()
Marks the entity as a prefab.
Definition world.h:2389
EntityNameLookupKey m_targetNameKey
Target name.
Definition world.h:2016
void del_alias()
Removes any alias associated with the entity.
Definition world.h:2317
EntityBuilder(EntityBuilder &&other) noexcept
Transfers ownership of a pending entity mutation.
Definition world.h:2071
World & m_world
World receiving the accumulated entity changes.
Definition world.h:2006
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:2248
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:2279
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:15
GAIA_NODISCARD Entity target(Entity source) const
Returns the target currently bound to source.
Definition nonfragmenting_relation_store.h:99
Detects whether a type is a relationship pair.
Definition id.h:220
static constexpr bool value
True when the type derives from the pair base.
Definition id.h:222
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:358
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:380
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 save(const T &arg)
Serializes a value through generic traversal.
Definition ser_rt.h:115
GAIA_NODISCARD bool valid() const
Checks whether mandatory backend callbacks are bound.
Definition ser_rt.h:106
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. Appends that would push the length past 32 bits are rejected.
Definition str.h:391
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:443
GAIA_NODISCARD str_view view() const
Returns a non-owning view over the current contents.
Definition str.h:449
void reserve(uint32_t len)
Reserves capacity for at least len characters.
Definition str.h:359