Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
world.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <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/entity_container.h"
39#include "gaia/ecs/id.h"
40#include "gaia/ecs/observer.h"
41#include "gaia/ecs/observer_registry.h"
42#include "gaia/ecs/query.h"
43#include "gaia/ecs/query_cache.h"
44#include "gaia/ecs/query_common.h"
45#include "gaia/ecs/query_info.h"
46#include "gaia/ecs/system.h"
47#include "gaia/mem/mem_alloc.h"
48#include "gaia/ser/ser_binary.h"
49#include "gaia/ser/ser_json.h"
50#include "gaia/ser/ser_rt.h"
51#include "gaia/util/logging.h"
52#include "gaia/util/str.h"
53
54namespace gaia {
55 namespace ecs {
56 template <typename T>
57 struct SparseComponentRecord;
58 }
59
60 namespace cnt {
61 template <typename T>
62 struct to_sparse_id<ecs::SparseComponentRecord<T>> {
63 static sparse_id get(const ecs::SparseComponentRecord<T>& item) noexcept {
64 return (sparse_id)item.entity.id();
65 }
66 };
67 } // namespace cnt
68
69 namespace ecs {
70#if GAIA_SYSTEMS_ENABLED
71 class SystemBuilder;
72#endif
73#if GAIA_OBSERVERS_ENABLED
74 class ObserverBuilder;
75 class ObserverRegistry;
76#endif
77 class World;
78
79 void world_notify_on_set_entity(World& world, Entity term, Entity entity);
80 void world_finish_write(World& world, Entity term, Entity entity);
81 template <typename T>
82 decltype(auto) world_direct_entity_arg_raw(World& world, Entity entity);
83 template <typename T>
84 decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id);
85
86 template <typename T>
88 Entity entity;
89 T value{};
90 };
91
92 namespace detail {
96 Entity entity = EntityBad;
98 Entity phase = EntityBad;
100 uint32_t phaseDepth = 0;
102 uint32_t systemDepth = 0;
104 uint32_t phaseOrder = 0;
106 uint32_t systemOrder = 0;
108 bool hasPhase = false;
109 };
110
114 Entity phase = EntityBad;
116 uint32_t depth = 0;
118 uint32_t order = 0;
119 };
120
124 uint32_t child = 0;
126 uint32_t target = 0;
128 uint32_t next = UINT32_MAX;
129 };
130
172 } // namespace detail
173
174 class GAIA_API World final {
175 public:
177 inline static bool s_enableUniqueNameDuplicateAssert = true;
178
179 private:
180 friend CommandBufferST;
181 friend CommandBufferMT;
182#if GAIA_OBSERVERS_ENABLED
183 friend class ObserverRegistry;
184 friend struct ObserverRegistry::DiffDispatcher;
185 friend struct ObserverRegistry::DirectDispatcher;
186 friend struct ObserverRegistry::SharedDispatch;
187#endif
188 friend struct ComponentGetter;
189 friend struct ComponentSetter;
190 friend void lock(World&);
191 friend void unlock(World&);
192 friend QueryMatchScratch& query_match_scratch_acquire(World&);
193 friend void query_match_scratch_release(World&, bool);
194 friend uint32_t world_component_index_bucket_size(const World&, Entity);
195 friend uint32_t world_component_index_comp_idx(const World&, const Archetype&, Entity);
196 friend uint32_t world_component_index_match_count(const World&, const Archetype&, Entity);
197 template <typename T>
198 friend decltype(auto) world_direct_entity_arg(World& world, Entity entity);
199 template <typename T>
200 friend decltype(auto) world_direct_entity_arg_raw(World& world, Entity entity);
201 template <typename T>
202 friend decltype(auto) world_query_entity_arg_by_id(World& world, Entity entity, Entity id);
203 template <typename T>
204 friend decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id);
205 friend void world_finish_write(World& world, Entity term, Entity entity);
206
207 ser::bin_stream m_stream;
208 ser::serializer m_serializer{};
209
210 using TFunc_Void_With_Entity = void(Entity);
211 static void func_void_with_entity([[maybe_unused]] Entity entity) {}
212
213 using EntityNameLookupKey = core::StringLookupKey<256>;
215 using EntityArrayMap = cnt::map<EntityLookupKey, cnt::darray<Entity>>;
216
217 struct ExclusiveAdjunctStore {
219 cnt::darray<Entity> srcToTgt;
221 cnt::darray<uint32_t> srcToTgtIdx;
223 uint32_t srcToTgtCnt = 0;
226 };
227
228 struct SparseComponentStoreErased {
229 void* pStore = nullptr;
230 void (*func_del)(void*, Entity) = nullptr;
231 bool (*func_has)(const void*, Entity) = nullptr;
232 bool (*func_copy_entity)(void*, Entity, Entity) = nullptr;
233 uint32_t (*func_count)(const void*) = nullptr;
234 void (*func_collect_entities)(const void*, cnt::darray<Entity>&) = nullptr;
235 bool (*func_for_each_entity)(const void*, void*, bool (*)(void*, Entity)) = nullptr;
236 void (*func_clear_store)(void*) = nullptr;
237 void (*func_del_store)(void*) = nullptr;
238 };
239
240 template <typename T>
241 struct SparseComponentStore final {
242 GAIA_USE_SMALLBLOCK(SparseComponentStore)
243
244 cnt::sparse_storage<SparseComponentRecord<T>> data;
245
246 static cnt::sparse_id sid(Entity entity) {
247 return (cnt::sparse_id)entity.id();
248 }
249
250 T& add(Entity entity) {
251 const auto sparseId = sid(entity);
252 if (data.has(sparseId))
253 return data[sparseId].value;
254
255 auto& item = data.add(SparseComponentRecord<T>{entity});
256 return item.value;
257 }
258
259 T& mut(Entity entity) {
260 GAIA_ASSERT(data.has(sid(entity)));
261 return data[sid(entity)].value;
262 }
263
264 const T& get(Entity entity) const {
265 GAIA_ASSERT(data.has(sid(entity)));
266 return data[sid(entity)].value;
267 }
268
269 void del_entity(Entity entity) {
270 const auto sparseId = sid(entity);
271 if (!data.empty() && data.has(sparseId))
272 data.del(sparseId);
273 }
274
275 bool has(Entity entity) const {
276 if (data.empty())
277 return false;
278 return data.has(sid(entity));
279 }
280
281 uint32_t count() const {
282 return (uint32_t)data.size();
283 }
284
285 void collect_entities(cnt::darray<Entity>& out) const {
286 out.reserve(out.size() + (uint32_t)data.size());
287 for (const auto& item: data)
288 out.push_back(item.entity);
289 }
290
291 void clear_store() {
292 data.clear();
293 }
294 };
295
296 template <typename T>
297 static SparseComponentStoreErased make_sparse_component_store_erased(SparseComponentStore<T>* pStore) {
298 SparseComponentStoreErased store{};
299 store.pStore = pStore;
300 store.func_del = [](void* pStoreRaw, Entity entity) {
301 static_cast<SparseComponentStore<T>*>(pStoreRaw)->del_entity(entity);
302 };
303 store.func_has = [](const void* pStoreRaw, Entity entity) {
304 return static_cast<const SparseComponentStore<T>*>(pStoreRaw)->has(entity);
305 };
306 store.func_copy_entity = [](void* pStoreRaw, Entity dstEntity, Entity srcEntity) {
307 auto* pStore = static_cast<SparseComponentStore<T>*>(pStoreRaw);
308 if (!pStore->has(srcEntity))
309 return false;
310
311 auto& dst = pStore->add(dstEntity);
312 dst = pStore->get(srcEntity);
313 return true;
314 };
315 store.func_count = [](const void* pStoreRaw) {
316 return static_cast<const SparseComponentStore<T>*>(pStoreRaw)->count();
317 };
318 store.func_collect_entities = [](const void* pStoreRaw, cnt::darray<Entity>& out) {
319 static_cast<const SparseComponentStore<T>*>(pStoreRaw)->collect_entities(out);
320 };
321 store.func_for_each_entity = [](const void* pStoreRaw, void* pCtx, bool (*func)(void*, Entity)) {
322 for (const auto& item: static_cast<const SparseComponentStore<T>*>(pStoreRaw)->data) {
323 if (!func(pCtx, item.entity))
324 return false;
325 }
326 return true;
327 };
328 store.func_clear_store = [](void* pStoreRaw) {
329 static_cast<SparseComponentStore<T>*>(pStoreRaw)->clear_store();
330 };
331 store.func_del_store = [](void* pStoreRaw) {
332 delete static_cast<SparseComponentStore<T>*>(pStoreRaw);
333 };
334 return store;
335 }
336
337 template <
338 typename TApi, typename TValue, bool DeriveFromValue = std::is_class_v<TValue> && !std::is_final_v<TValue>>
339 class SetWriteProxyTyped;
340
341 template <typename TApi, typename TValue>
342 class SetWriteProxyTyped<TApi, TValue, true>: public TValue {
343 World* m_pWorld = nullptr;
344 Entity m_entity = EntityBad;
345 Entity m_term = EntityBad;
346
347 void commit() {
348 if (m_pWorld == nullptr)
349 return;
350
351 m_pWorld->template write_back_set_typed<TApi, TValue>(m_entity, m_term, static_cast<const TValue&>(*this));
352 m_pWorld = nullptr;
353 }
354
355 public:
356 SetWriteProxyTyped(World& world, Entity entity, Entity term, const TValue& value):
357 TValue(value), m_pWorld(&world), m_entity(entity), m_term(term) {}
358
359 SetWriteProxyTyped(World& world, Entity entity, Entity term, TValue&& value):
360 TValue(GAIA_MOV(value)), m_pWorld(&world), m_entity(entity), m_term(term) {}
361
362 SetWriteProxyTyped(const SetWriteProxyTyped&) = delete;
363 SetWriteProxyTyped& operator=(const SetWriteProxyTyped&) = delete;
364
365 SetWriteProxyTyped(SetWriteProxyTyped&& other) noexcept:
366 TValue(static_cast<TValue&&>(other)), m_pWorld(other.m_pWorld), m_entity(other.m_entity),
367 m_term(other.m_term) {
368 other.m_pWorld = nullptr;
369 }
370
371 ~SetWriteProxyTyped() {
372 commit();
373 }
374
375 SetWriteProxyTyped& operator=(const TValue& value) {
376 static_cast<TValue&>(*this) = value;
377 return *this;
378 }
379
380 SetWriteProxyTyped& operator=(TValue&& value) {
381 static_cast<TValue&>(*this) = GAIA_MOV(value);
382 return *this;
383 }
384
385 GAIA_NODISCARD operator TValue&() {
386 return *this;
387 }
388
389 GAIA_NODISCARD operator const TValue&() const {
390 return *this;
391 }
392 };
393
394 template <typename TApi, typename TValue>
395 class SetWriteProxyTyped<TApi, TValue, false> {
396 World* m_pWorld = nullptr;
397 Entity m_entity = EntityBad;
398 Entity m_term = EntityBad;
399 TValue m_value{};
400
401 void commit() {
402 if (m_pWorld == nullptr)
403 return;
404
405 m_pWorld->template write_back_set_typed<TApi, TValue>(m_entity, m_term, m_value);
406 m_pWorld = nullptr;
407 }
408
409 public:
410 SetWriteProxyTyped(World& world, Entity entity, Entity term, const TValue& value):
411 m_pWorld(&world), m_entity(entity), m_term(term), m_value(value) {}
412
413 SetWriteProxyTyped(World& world, Entity entity, Entity term, TValue&& value):
414 m_pWorld(&world), m_entity(entity), m_term(term), m_value(GAIA_MOV(value)) {}
415
416 SetWriteProxyTyped(const SetWriteProxyTyped&) = delete;
417 SetWriteProxyTyped& operator=(const SetWriteProxyTyped&) = delete;
418
419 SetWriteProxyTyped(SetWriteProxyTyped&& other) noexcept:
420 m_pWorld(other.m_pWorld), m_entity(other.m_entity), m_term(other.m_term), m_value(GAIA_MOV(other.m_value)) {
421 other.m_pWorld = nullptr;
422 }
423
424 ~SetWriteProxyTyped() {
425 commit();
426 }
427
428 SetWriteProxyTyped& operator=(const TValue& value) {
429 m_value = value;
430 return *this;
431 }
432
433 SetWriteProxyTyped& operator=(TValue&& value) {
434 m_value = GAIA_MOV(value);
435 return *this;
436 }
437
438 GAIA_NODISCARD operator TValue&() {
439 return m_value;
440 }
441
442 GAIA_NODISCARD operator const TValue&() const {
443 return m_value;
444 }
445
446 GAIA_NODISCARD TValue* operator->() {
447 return &m_value;
448 }
449
450 GAIA_NODISCARD const TValue* operator->() const {
451 return &m_value;
452 }
453 };
454
455 template <typename TValue, bool DeriveFromValue = std::is_class_v<TValue> && !std::is_final_v<TValue>>
456 class SetWriteProxyObject;
457
458 template <typename TValue>
459 class SetWriteProxyObject<TValue, true>: public TValue {
460 World* m_pWorld = nullptr;
461 Entity m_entity = EntityBad;
462 Entity m_term = EntityBad;
463
464 void commit() {
465 if (m_pWorld == nullptr)
466 return;
467
468 m_pWorld->template write_back_set_object<TValue>(m_entity, m_term, static_cast<const TValue&>(*this));
469 m_pWorld = nullptr;
470 }
471
472 public:
473 SetWriteProxyObject(World& world, Entity entity, Entity term, const TValue& value):
474 TValue(value), m_pWorld(&world), m_entity(entity), m_term(term) {}
475
476 SetWriteProxyObject(World& world, Entity entity, Entity term, TValue&& value):
477 TValue(GAIA_MOV(value)), m_pWorld(&world), m_entity(entity), m_term(term) {}
478
479 SetWriteProxyObject(const SetWriteProxyObject&) = delete;
480 SetWriteProxyObject& operator=(const SetWriteProxyObject&) = delete;
481
482 SetWriteProxyObject(SetWriteProxyObject&& other) noexcept:
483 TValue(static_cast<TValue&&>(other)), m_pWorld(other.m_pWorld), m_entity(other.m_entity),
484 m_term(other.m_term) {
485 other.m_pWorld = nullptr;
486 }
487
488 ~SetWriteProxyObject() {
489 commit();
490 }
491
492 SetWriteProxyObject& operator=(const TValue& value) {
493 static_cast<TValue&>(*this) = value;
494 return *this;
495 }
496
497 SetWriteProxyObject& operator=(TValue&& value) {
498 static_cast<TValue&>(*this) = GAIA_MOV(value);
499 return *this;
500 }
501
502 GAIA_NODISCARD operator TValue&() {
503 return *this;
504 }
505
506 GAIA_NODISCARD operator const TValue&() const {
507 return *this;
508 }
509 };
510
511 template <typename TValue>
512 class SetWriteProxyObject<TValue, false> {
513 World* m_pWorld = nullptr;
514 Entity m_entity = EntityBad;
515 Entity m_term = EntityBad;
516 TValue m_value{};
517
518 void commit() {
519 if (m_pWorld == nullptr)
520 return;
521
522 m_pWorld->template write_back_set_object<TValue>(m_entity, m_term, m_value);
523 m_pWorld = nullptr;
524 }
525
526 public:
527 SetWriteProxyObject(World& world, Entity entity, Entity term, const TValue& value):
528 m_pWorld(&world), m_entity(entity), m_term(term), m_value(value) {}
529
530 SetWriteProxyObject(World& world, Entity entity, Entity term, TValue&& value):
531 m_pWorld(&world), m_entity(entity), m_term(term), m_value(GAIA_MOV(value)) {}
532
533 SetWriteProxyObject(const SetWriteProxyObject&) = delete;
534 SetWriteProxyObject& operator=(const SetWriteProxyObject&) = delete;
535
536 SetWriteProxyObject(SetWriteProxyObject&& other) noexcept:
537 m_pWorld(other.m_pWorld), m_entity(other.m_entity), m_term(other.m_term), m_value(GAIA_MOV(other.m_value)) {
538 other.m_pWorld = nullptr;
539 }
540
541 ~SetWriteProxyObject() {
542 commit();
543 }
544
545 SetWriteProxyObject& operator=(const TValue& value) {
546 m_value = value;
547 return *this;
548 }
549
550 SetWriteProxyObject& operator=(TValue&& value) {
551 m_value = GAIA_MOV(value);
552 return *this;
553 }
554
555 GAIA_NODISCARD operator TValue&() {
556 return m_value;
557 }
558
559 GAIA_NODISCARD operator const TValue&() const {
560 return m_value;
561 }
562
563 GAIA_NODISCARD TValue* operator->() {
564 return &m_value;
565 }
566
567 GAIA_NODISCARD const TValue* operator->() const {
568 return &m_value;
569 }
570 };
571
572 template <typename T>
573 GAIA_NODISCARD decltype(auto) mut_im(Entity entity) {
574 static_assert(!is_pair<T>::value);
575 using FT = typename component_type_t<T>::TypeFull;
576 const auto& item = add<FT>();
577 if constexpr (supports_out_of_line_component<FT>()) {
578 if (out_of_line_mode(item.entity) != OutOfLineMode::None)
579 return sparse_component_store_mut<FT>(item.entity).mut(entity);
580 }
581
582 const auto& ec = m_recs.entities[entity.id()];
583 if constexpr (entity_kind_v<T> == EntityKind::EK_Gen)
584 return ec.pChunk->template set<T>(ec.row);
585 else
586 return ec.pChunk->template set<T>();
587 }
588
590 GAIA_NODISCARD static bool raw_component_supported(const ComponentCacheItem& item) noexcept {
591 return item.comp.storage_type() == DataStorageType::Table && item.comp.soa() == 0;
592 }
593
595 GAIA_NODISCARD static bool
596 raw_component_payload_args_valid(const ComponentCacheItem& item, const void* data, uint32_t size) noexcept {
597 if (!raw_component_supported(item))
598 return false;
599 if (size != item.comp.size())
600 return false;
601 return size == 0 || data != nullptr;
602 }
603
604 template <typename T>
605 GAIA_NODISCARD decltype(auto) mut_im(Entity entity, Entity object) {
606 static_assert(!is_pair<T>::value);
607 using FT = typename component_type_t<T>::TypeFull;
608 if constexpr (supports_out_of_line_component<FT>()) {
609 if (can_use_out_of_line_component<FT>(object))
610 return sparse_component_store_mut<FT>(object).mut(entity);
611 }
612
613 const auto& ec = m_recs.entities[entity.id()];
614 return ec.pChunk->template set<T>(ec.row, object);
615 }
616
622 void finish_write(Entity entity, Entity term) {
623 if (tearing_down() || !valid(entity))
624 return;
625
626 if (out_of_line_mode(term) != OutOfLineMode::None) {
627 world_notify_on_set_entity(*this, term, entity);
628 return;
629 }
630
631 auto compIdx = uint32_t(BadIndex);
632 {
633 const auto& ec = fetch(entity);
634 compIdx = world_component_index_comp_idx(*this, *ec.pArchetype, term);
635 }
636
637 if (compIdx == BadIndex) {
638 (void) override(entity, term);
639 const auto& ec = fetch(entity);
640 compIdx = world_component_index_comp_idx(*this, *ec.pArchetype, term);
641 if (compIdx == BadIndex)
642 return;
643 }
644
645 const auto& ec = fetch(entity);
646 const auto row = uint16_t(ec.row * (1U - (uint32_t)term.kind()));
647 (void)ec.pChunk->comp_ptr_mut_gen<true>(compIdx, row);
648 world_notify_on_set_entity(*this, term, entity);
649 }
650
651 template <typename TApi, typename TValue>
652 void write_back_set_typed(Entity entity, Entity term, const TValue& value) {
653 using FT = typename component_type_t<TApi>::TypeFull;
654 ::gaia::ecs::update_version(m_worldVersion);
655
656 if constexpr (supports_out_of_line_component<FT>()) {
657 if (out_of_line_mode(term) != OutOfLineMode::None) {
658 sparse_component_store_mut<FT>(term).add(entity) = value;
659 finish_write(entity, term);
660 return;
661 }
662 }
663
664 const auto& ec = fetch(entity);
665 const auto row = uint16_t(ec.row * (1U - (uint32_t)term.kind()));
666 ComponentSetter{*this, ec.pChunk, entity, row}.sset<TApi>(value);
667 finish_write(entity, term);
668 }
669
670 template <typename TValue>
671 void write_back_set_object(Entity entity, Entity term, const TValue& value) {
672 using FT = typename component_type_t<TValue>::TypeFull;
673 ::gaia::ecs::update_version(m_worldVersion);
674 if constexpr (supports_out_of_line_component<FT>()) {
675 if (can_use_out_of_line_component<FT>(term)) {
676 sparse_component_store_mut<FT>(term).add(entity) = value;
677 finish_write(entity, term);
678 return;
679 }
680 }
681
682 const auto& ec = fetch(entity);
683 const auto row = uint16_t(ec.row * (1U - (uint32_t)term.kind()));
684 ComponentSetter{*this, ec.pChunk, entity, row}.template smut<TValue>(term) = value;
685 finish_write(entity, term);
686 }
687
688 //----------------------------------------------------------------------
689
690 //----------------------------------------------------------------------
691
693 ComponentCache m_compCache;
695 QueryCache m_queryCache;
698 cnt::darray<QueryMatchScratch*> m_queryMatchScratchStack;
700 uint32_t m_queryMatchScratchDepth = 0;
706 QuerySerMap m_querySerMap;
707 uint32_t m_nextQuerySerId = 0;
708
710 uint32_t m_emptySpace0 = 0;
711
713 EntityToArchetypeMap m_entityToArchetypeMap;
715 EntityToArchetypeVersionMap m_entityToArchetypeMapVersions;
724 PairMap m_entityToAsTargets;
727 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_entityToAsTargetsTravCache;
735 PairMap m_entityToAsRelations;
738 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_entityToAsRelationsTravCache;
741 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_targetsTravCache;
744 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_srcBfsTravCache;
747 mutable cnt::map<EntityLookupKey, uint32_t> m_depthOrderCache;
750 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_sourcesAllCache;
753 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_targetsAllCache;
755 PairMap m_relToTgt;
757 PairMap m_tgtToRel;
759 cnt::map<EntityLookupKey, ExclusiveAdjunctStore> m_exclusiveAdjunctByRel;
761 EntityArrayMap m_srcToExclusiveAdjunctRel;
763 cnt::map<EntityLookupKey, SparseComponentStoreErased> m_sparseComponentsByComp;
765 cnt::map<EntityLookupKey, uint32_t> m_relationVersions;
768 mutable cnt::map<EntityLookupKey, uint32_t> m_srcEntityVersions;
769
770 enum class OutOfLineMode : uint8_t { None, Fragmenting, NonFragmenting };
771
773 ArchetypeDArray m_archetypes;
775 ArchetypeMapByHash m_archetypesByHash;
777 ArchetypeMapById m_archetypesById;
778
780 Archetype* m_pRootArchetype = nullptr;
782 Archetype* m_pEntityArchetype = nullptr;
784 Archetype* m_pCompArchetype = nullptr;
786 ArchetypeId m_nextArchetypeId = 0;
787
789 uint32_t m_emptySpace1 = 0;
790
792 EntityContainers m_recs;
793
795 cnt::map<EntityNameLookupKey, Entity> m_nameToEntity;
797 cnt::map<EntityNameLookupKey, Entity> m_aliasToEntity;
799 Entity m_componentScope = EntityBad;
801 cnt::darray<Entity> m_componentLookupPath;
803 mutable util::str m_componentScopePathCache;
805 mutable Entity m_componentScopePathCacheEntity = EntityBad;
807 mutable bool m_componentScopePathCacheValid = false;
808
810 cnt::set<ArchetypeLookupKey> m_reqArchetypesToDel;
812 cnt::set<EntityLookupKey> m_reqEntitiesToDel;
813
814#if GAIA_OBSERVERS_ENABLED
816 ObserverRegistry m_observers;
817#endif
818
819#if GAIA_SYSTEMS_ENABLED
821 SystemRegistry m_systems;
822#endif
823
825 CommandBufferST* m_pCmdBufferST;
827 CommandBufferMT* m_pCmdBufferMT;
829 bool m_teardownActive = false;
831 Query m_systemsQuery;
833 detail::SystemScheduleScratch m_systemScheduleScratch;
835 Sched m_sched{};
837 mutable cnt::darray<uint64_t> m_entityVisitStamps;
839 mutable uint64_t m_entityVisitStamp = 0;
840
842 cnt::set<EntityLookupKey> m_entitiesToDel;
844 cnt::darray<ArchetypeChunkPair> m_chunksToDel;
846 ArchetypeDArray m_archetypesToDel;
848 uint32_t m_defragLastArchetypeIdx = 0;
850 uint32_t m_defragEntitiesPerTick = 100;
851
853 uint32_t m_worldVersion = 0;
855 uint32_t m_enabledHierarchyVersion = 0;
856
857 uint32_t m_structuralChangesLocked = 0;
858
859 public:
860 World():
861 // Command buffer for the main thread
862 m_pCmdBufferST(cmd_buffer_st_create(*this)),
863 // Command buffer safe for concurrent access
864 m_pCmdBufferMT(cmd_buffer_mt_create(*this)) {
865 init();
866 }
867
868 ~World() {
869 teardown();
870 done();
871 cmd_buffer_destroy(*m_pCmdBufferST);
872 cmd_buffer_destroy(*m_pCmdBufferMT);
873 }
874
875 World(World&&) = delete;
876 World(const World&) = delete;
877 World& operator=(World&&) = delete;
878 World& operator=(const World&) = delete;
879
880 //----------------------------------------------------------------------
881
886 return Query(
887 *const_cast<World*>(this), m_queryCache,
888 //
889 m_nextArchetypeId, m_worldVersion, m_entityToArchetypeMap, m_entityToArchetypeMapVersions, m_archetypes);
890 }
891
896 auto q = query();
897 q.kind(QueryCacheKind::None);
898 return q;
899 }
900
901#if GAIA_ECS_TEST_HOOKS
903 GAIA_NODISCARD bool verify_query_cache() const {
904 return m_queryCache.verify_archetype_tracking();
905 }
906
909 GAIA_NODISCARD uint32_t test_query_cache_count() const {
910 return m_queryCache.test_query_count();
911 }
912#endif
913
914 //----------------------------------------------------------------------
915
918 void set_sched(const Sched& sched) {
919 m_sched = sched;
920 }
921
923 void reset_sched() {
924 m_sched = {};
925 }
926
929 GAIA_NODISCARD const Sched& sched() const {
930 return sched_resolve(m_sched);
931 }
932
933 //----------------------------------------------------------------------
934
938 GAIA_NODISCARD EntityContainer& fetch(Entity entity) {
939#if GAIA_ASSERT_ENABLED
940 // Wildcard pairs are not a real entity so we can't accept them
941 GAIA_ASSERT(!entity.pair() || !is_wildcard(entity));
942 if (!valid(entity)) {
943 // Delete-time cleanup can still reference an exact pair record after one endpoint
944 // has already become invalid. As long as the pair record itself still exists,
945 // allow fetch() so cleanup can finish removing it.
946 const bool allowStaleExactPair = entity.pair() && m_recs.pairs.contains(EntityLookupKey(entity));
947 GAIA_ASSERT(allowStaleExactPair);
948 }
949#endif
950 return m_recs[entity];
951 }
952
956 GAIA_NODISCARD const EntityContainer& fetch(Entity entity) const {
957#if GAIA_ASSERT_ENABLED
958 // Wildcard pairs are not a real entity so we can't accept them
959 GAIA_ASSERT(!entity.pair() || !is_wildcard(entity));
960 if (!valid(entity)) {
961 // Delete-time cleanup can still reference an exact pair record after one endpoint
962 // has already become invalid. As long as the pair record itself still exists,
963 // allow fetch() so cleanup can finish removing it.
964 const bool allowStaleExactPair = entity.pair() && m_recs.pairs.contains(EntityLookupKey(entity));
965 GAIA_ASSERT(allowStaleExactPair);
966 }
967#endif
968 return m_recs[entity];
969 }
970
971 //----------------------------------------------------------------------
972
977 GAIA_NODISCARD static bool is_req_del(const EntityContainer& ec) {
978 if ((ec.flags & EntityContainerFlags::DeleteRequested) != 0)
979 return true;
980 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) == 0);
981 return ec.pArchetype != nullptr && ec.pArchetype->is_req_del();
982 }
983
987 GAIA_NODISCARD bool is_dont_fragment(Entity entity) const {
988 return (fetch(entity).flags & EntityContainerFlags::IsDontFragment) != 0;
989 }
990
994 GAIA_NODISCARD bool is_dont_fragment_relation(Entity relation) const {
995 return valid(relation) && !relation.pair() && is_dont_fragment(relation);
996 }
997
1002 GAIA_NODISCARD bool is_exclusive_dont_fragment_relation(Entity relation) const {
1003 if (!valid(relation) || relation.pair())
1004 return false;
1005
1006 const auto& ec = fetch(relation);
1007 return (ec.flags & EntityContainerFlags::IsExclusive) != 0 &&
1008 (ec.flags & EntityContainerFlags::IsDontFragment) != 0;
1009 }
1010
1013 GAIA_NODISCARD bool is_hierarchy_relation(Entity relation) const {
1014 if (!valid(relation) || relation.pair())
1015 return false;
1016
1017 return has(relation, Exclusive) && has(relation, Traversable);
1018 }
1019
1022 GAIA_NODISCARD bool is_fragmenting_relation(Entity relation) const {
1023 return valid(relation) && !relation.pair() && !is_dont_fragment(relation);
1024 }
1025
1028 GAIA_NODISCARD bool is_fragmenting_hierarchy_relation(Entity relation) const {
1029 return is_hierarchy_relation(relation) && is_fragmenting_relation(relation);
1030 }
1031
1036 GAIA_NODISCARD bool supports_depth_order(Entity relation) const {
1037 return is_fragmenting_relation(relation);
1038 }
1039
1043 GAIA_NODISCARD bool depth_order_prunes_disabled_subtrees(Entity relation) const {
1044 return is_fragmenting_hierarchy_relation(relation);
1045 }
1046
1051 GAIA_NODISCARD bool is_out_of_line_component(Entity component) const {
1052 if (!valid(component) || component.pair() || component.entity())
1053 return false;
1054
1055 const auto* pItem = comp_cache().find(component);
1056 if (pItem == nullptr || component.kind() != EntityKind::EK_Gen || pItem->comp.soa() != 0)
1057 return false;
1058
1059 return component_has_out_of_line_data(pItem->comp);
1060 }
1061
1066 GAIA_NODISCARD bool is_non_fragmenting_out_of_line_component(Entity component) const {
1067 if (!is_out_of_line_component(component))
1068 return false;
1069
1070 return (fetch(component).flags & EntityContainerFlags::IsDontFragment) != 0;
1071 }
1072
1073 GAIA_NODISCARD OutOfLineMode out_of_line_mode(Entity component) const {
1074 if (!is_out_of_line_component(component))
1075 return OutOfLineMode::None;
1076
1077 return is_non_fragmenting_out_of_line_component(component) ? OutOfLineMode::NonFragmenting
1078 : OutOfLineMode::Fragmenting;
1079 }
1080
1081 GAIA_NODISCARD bool
1082 copies_sparse_payload_inter(Entity comp, Entity srcEntity, const SparseComponentStoreErased& store) const {
1083 return store.func_has(store.pStore, srcEntity) && out_of_line_mode(comp) != OutOfLineMode::None;
1084 }
1085
1086 GAIA_NODISCARD bool copies_non_frag_sparse_payload_inter(
1087 Entity comp, Entity srcEntity, const SparseComponentStoreErased& store) const {
1088 return copies_sparse_payload_inter(comp, srcEntity, store) &&
1089 out_of_line_mode(comp) == OutOfLineMode::NonFragmenting;
1090 }
1091
1092 GAIA_NODISCARD bool sparse_copy_adds_id_inter(Entity comp) const {
1093 return out_of_line_mode(comp) == OutOfLineMode::NonFragmenting;
1094 }
1095
1096 //----------------------------------------------------------------------
1097
1102 auto& item = comp_cache_mut().get(component);
1103 item.comp = comp;
1104
1105 auto& ec = m_recs.entities[component.id()];
1106 if (ec.pArchetype == nullptr || ec.pChunk == nullptr)
1107 return;
1108
1109 const auto compIdx = core::get_index(ec.pArchetype->ids_view(), GAIA_ID(Component));
1110 if (compIdx == BadIndex)
1111 return;
1112
1113 auto* pComp = reinterpret_cast<Component*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
1114 *pComp = comp;
1115 }
1116
1122 void finalize_component_registration(const ComponentCacheItem& item, bool addSparseTrait) {
1123 sync_component_record(item.entity, item.comp);
1124 name_raw(item.entity, item.name.str(), item.name.len());
1125 if (addSparseTrait && item.comp.storage_type() == DataStorageType::Sparse)
1126 add(item.entity, Sparse);
1127 }
1128
1134 if (component.comp())
1135 set_component_sparse_storage(component);
1136
1137 if ((ec.flags & EntityContainerFlags::IsDontFragment) != 0)
1138 return;
1139
1140 ec.flags |= EntityContainerFlags::IsDontFragment;
1141 }
1142
1148 GAIA_ASSERT(valid(component));
1149 GAIA_ASSERT(component.comp());
1150 GAIA_ASSERT(!component.pair());
1151 GAIA_ASSERT(!component.entity());
1152 GAIA_ASSERT(component.kind() == EntityKind::EK_Gen);
1153
1154 const auto& item = comp_cache().get(component);
1155 GAIA_ASSERT(item.entity == component);
1156
1157 if (item.comp.storage_type() == DataStorageType::Sparse)
1158 return;
1159
1160 GAIA_ASSERT(item.comp.soa() == 0);
1161 if (item.comp.soa() != 0)
1162 return;
1163
1164 const auto directTermEntityCnt = count_direct_term_entities_direct(component);
1165 GAIA_ASSERT(directTermEntityCnt == 0);
1166 if (directTermEntityCnt != 0)
1167 return;
1168
1169 auto comp = item.comp;
1170 comp.data.storage = (IdentifierData)DataStorageType::Sparse;
1171 sync_component_record(component, comp);
1172 }
1173
1178 template <typename T>
1179 GAIA_NODISCARD static constexpr bool supports_out_of_line_component() {
1180 using U = typename actual_type_t<T>::Type;
1181 return !is_pair<T>::value && entity_kind_v<T> == EntityKind::EK_Gen && !mem::is_soa_layout_v<U>;
1182 }
1183
1188 template <typename T>
1189 GAIA_NODISCARD bool can_use_out_of_line_component(Entity object) const {
1190 if constexpr (!supports_out_of_line_component<T>())
1191 return false;
1192 else {
1193 if (object.pair())
1194 return false;
1195
1196 const auto* pItem = comp_cache().find(object);
1197 if (pItem == nullptr || pItem->entity != object || out_of_line_mode(object) == OutOfLineMode::None)
1198 return false;
1199
1200 using U = typename actual_type_t<T>::Type;
1201 return pItem->comp.size() == (uint32_t)sizeof(U) && pItem->comp.alig() == (uint32_t)alignof(U) &&
1202 pItem->comp.soa() == 0 && object.kind() == entity_kind_v<T>;
1203 }
1204 }
1205
1210 template <typename T>
1211 GAIA_NODISCARD SparseComponentStore<T>* sparse_component_store(Entity component) {
1212 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1213 if (it == m_sparseComponentsByComp.end())
1214 return nullptr;
1215
1216 return static_cast<SparseComponentStore<T>*>(it->second.pStore);
1217 }
1218
1223 template <typename T>
1224 GAIA_NODISCARD const SparseComponentStore<T>* sparse_component_store(Entity component) const {
1225 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1226 if (it == m_sparseComponentsByComp.end())
1227 return nullptr;
1228
1229 return static_cast<const SparseComponentStore<T>*>(it->second.pStore);
1230 }
1231
1236 template <typename T>
1237 GAIA_NODISCARD SparseComponentStore<T>& sparse_component_store_mut(Entity component) {
1238 const auto key = EntityLookupKey(component);
1239 const auto it = m_sparseComponentsByComp.find(key);
1240 if (it != m_sparseComponentsByComp.end())
1241 return *static_cast<SparseComponentStore<T>*>(it->second.pStore);
1242
1243 auto* pStore = new SparseComponentStore<T>();
1244 m_sparseComponentsByComp.emplace(key, make_sparse_component_store_erased(pStore));
1245 return *pStore;
1246 }
1247
1253 void finish_out_of_line_add_inter(Entity entity, Entity object, OutOfLineMode mode) {
1254 GAIA_ASSERT(mode != OutOfLineMode::None);
1255
1256#if GAIA_OBSERVERS_ENABLED
1257 auto ctx =
1258 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
1259#endif
1260 if (mode == OutOfLineMode::Fragmenting) {
1261 GAIA_ASSERT(!locked());
1262 EntityBuilder eb(*this, entity);
1263 eb.add_inter_init(object);
1264 eb.commit();
1265 }
1266
1267 notify_add_single(entity, object);
1268#if GAIA_OBSERVERS_ENABLED
1269 m_observers.finish_diff(*this, GAIA_MOV(ctx));
1270#endif
1271 }
1272
1276 for (auto& [compKey, store]: m_sparseComponentsByComp) {
1277 (void)compKey;
1278 store.func_del(store.pStore, entity);
1279 }
1280 }
1281
1285 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
1286 if (it == m_sparseComponentsByComp.end())
1287 return;
1288
1289 it->second.func_clear_store(it->second.pStore);
1290 it->second.func_del_store(it->second.pStore);
1291 m_sparseComponentsByComp.erase(it);
1292 }
1293
1297 GAIA_NODISCARD const ExclusiveAdjunctStore* exclusive_adjunct_store(Entity relation) const {
1298 const auto it = m_exclusiveAdjunctByRel.find(EntityLookupKey(relation));
1299 if (it == m_exclusiveAdjunctByRel.end())
1300 return nullptr;
1301
1302 return &it->second;
1303 }
1304
1308 GAIA_NODISCARD ExclusiveAdjunctStore& exclusive_adjunct_store_mut(Entity relation) {
1309 return m_exclusiveAdjunctByRel[EntityLookupKey(relation)];
1310 }
1311
1312 static void ensure_exclusive_adjunct_src_capacity(ExclusiveAdjunctStore& store, Entity source) {
1313 const auto required = (uint32_t)source.id() + 1;
1314 if (store.srcToTgt.size() >= required)
1315 return;
1316
1317 const auto oldSize = (uint32_t)store.srcToTgt.size();
1318 auto newSize = oldSize == 0 ? 16U : oldSize;
1319 while (newSize < required)
1320 newSize *= 2U;
1321
1322 store.srcToTgt.resize(newSize, EntityBad);
1323 store.srcToTgtIdx.resize(newSize, BadIndex);
1324 }
1325
1326 static void ensure_exclusive_adjunct_tgt_capacity(ExclusiveAdjunctStore& store, Entity target) {
1327 const auto required = target.id() + 1;
1328 if (store.tgtToSrc.size() >= required)
1329 return;
1330
1331 const auto oldSize = (uint32_t)store.tgtToSrc.size();
1332 auto newSize = oldSize == 0 ? 16U : oldSize;
1333 while (newSize < required)
1334 newSize *= 2U;
1335
1336 store.tgtToSrc.resize(newSize);
1337 }
1338
1339 GAIA_NODISCARD static Entity exclusive_adjunct_target(const ExclusiveAdjunctStore& store, Entity source) {
1340 if (source.id() >= store.srcToTgt.size())
1341 return EntityBad;
1342
1343 return store.srcToTgt[source.id()];
1344 }
1345
1346 GAIA_NODISCARD static const cnt::darray<Entity>*
1347 exclusive_adjunct_sources(const ExclusiveAdjunctStore& store, Entity target) {
1348 if (target.id() >= store.tgtToSrc.size())
1349 return nullptr;
1350
1351 const auto& sources = store.tgtToSrc[target.id()];
1352 return sources.empty() ? nullptr : &sources;
1353 }
1354
1355 static void del_exclusive_adjunct_target_source(ExclusiveAdjunctStore& store, Entity target, Entity source) {
1356 GAIA_ASSERT(target.id() < store.tgtToSrc.size());
1357 if (target.id() >= store.tgtToSrc.size())
1358 return;
1359
1360 auto& sources = store.tgtToSrc[target.id()];
1361 const auto idx = source.id() < store.srcToTgtIdx.size() ? store.srcToTgtIdx[source.id()] : BadIndex;
1362 GAIA_ASSERT(idx != BadIndex && idx < sources.size());
1363 if (idx == BadIndex || idx >= sources.size())
1364 return;
1365
1366 const auto lastIdx = (uint32_t)sources.size() - 1;
1367 if (idx != lastIdx) {
1368 const auto movedSource = sources[lastIdx];
1369 sources[idx] = movedSource;
1370 GAIA_ASSERT(movedSource.id() < store.srcToTgtIdx.size());
1371 store.srcToTgtIdx[movedSource.id()] = idx;
1372 }
1373
1374 sources.pop_back();
1375 }
1376
1377 void exclusive_adjunct_track_src_relation(Entity source, Entity relation) {
1378 auto& rels = m_srcToExclusiveAdjunctRel[EntityLookupKey(source)];
1379 if (!core::has(rels, relation))
1380 rels.push_back(relation);
1381 }
1382
1383 void exclusive_adjunct_untrack_src_relation(Entity source, Entity relation) {
1384 const auto it = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(source));
1385 if (it == m_srcToExclusiveAdjunctRel.end())
1386 return;
1387
1388 auto& rels = it->second;
1389 const auto idx = core::get_index(rels, relation);
1390 if (idx != BadIndex)
1391 core::swap_erase_unsafe(rels, idx);
1392 if (rels.empty())
1393 m_srcToExclusiveAdjunctRel.erase(it);
1394 }
1395
1396 void exclusive_adjunct_set(Entity source, Entity relation, Entity target) {
1397 GAIA_ASSERT(is_exclusive_dont_fragment_relation(relation));
1398
1399 auto& store = exclusive_adjunct_store_mut(relation);
1400 ensure_exclusive_adjunct_src_capacity(store, source);
1401 const auto oldTarget = store.srcToTgt[source.id()];
1402 if (oldTarget != EntityBad) {
1403 if (oldTarget == target)
1404 return;
1405
1406 del_exclusive_adjunct_target_source(store, oldTarget, source);
1407 } else {
1408 ++store.srcToTgtCnt;
1409 exclusive_adjunct_track_src_relation(source, relation);
1410 }
1411
1412 ensure_exclusive_adjunct_tgt_capacity(store, target);
1413 auto& sources = store.tgtToSrc[target.id()];
1414 store.srcToTgt[source.id()] = target;
1415 store.srcToTgtIdx[source.id()] = (uint32_t)sources.size();
1416 sources.push_back(source);
1417
1418 touch_rel_version(relation);
1419 invalidate_queries_for_rel(relation);
1420 m_targetsTravCache = {};
1421 m_srcBfsTravCache = {};
1422 m_depthOrderCache = {};
1423 m_sourcesAllCache = {};
1424 m_targetsAllCache = {};
1425 }
1426
1427 bool exclusive_adjunct_del(Entity source, Entity relation, Entity target) {
1428 const auto itStore = m_exclusiveAdjunctByRel.find(EntityLookupKey(relation));
1429 if (itStore == m_exclusiveAdjunctByRel.end())
1430 return false;
1431
1432 auto& store = itStore->second;
1433 const auto oldTarget = exclusive_adjunct_target(store, source);
1434 if (oldTarget == EntityBad)
1435 return false;
1436 if (target != EntityBad && oldTarget != target)
1437 return false;
1438
1439 del_exclusive_adjunct_target_source(store, oldTarget, source);
1440 store.srcToTgt[source.id()] = EntityBad;
1441 store.srcToTgtIdx[source.id()] = BadIndex;
1442 GAIA_ASSERT(store.srcToTgtCnt > 0);
1443 --store.srcToTgtCnt;
1444
1445 exclusive_adjunct_untrack_src_relation(source, relation);
1446 if (store.srcToTgtCnt == 0)
1447 m_exclusiveAdjunctByRel.erase(itStore);
1448
1449 touch_rel_version(relation);
1450 invalidate_queries_for_rel(relation);
1451 m_targetsTravCache = {};
1452 m_srcBfsTravCache = {};
1453 m_depthOrderCache = {};
1454 m_sourcesAllCache = {};
1455 m_targetsAllCache = {};
1456
1457 return true;
1458 }
1459
1460 GAIA_NODISCARD bool has_exclusive_adjunct_pair(Entity source, Entity object) const {
1461 if (!object.pair())
1462 return false;
1463
1464 const auto srcRelsIt = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(source));
1465 if (srcRelsIt == m_srcToExclusiveAdjunctRel.end())
1466 return false;
1467
1468 const auto relId = object.id();
1469 const auto tgtId = object.gen();
1470
1471 if (relId != All.id()) {
1472 const auto relation = get(relId);
1473 if (!is_exclusive_dont_fragment_relation(relation))
1474 return false;
1475
1476 const auto* pStore = exclusive_adjunct_store(relation);
1477 if (pStore == nullptr)
1478 return false;
1479
1480 const auto target = exclusive_adjunct_target(*pStore, source);
1481 if (target == EntityBad)
1482 return false;
1483
1484 return tgtId == All.id() || target.id() == tgtId;
1485 }
1486
1487 if (tgtId == All.id())
1488 return !srcRelsIt->second.empty();
1489
1490 const auto target = get(tgtId);
1491 for (auto relKey: srcRelsIt->second) {
1492 const auto relation = relKey;
1493 const auto* pStore = exclusive_adjunct_store(relation);
1494 if (pStore == nullptr)
1495 continue;
1496
1497 if (exclusive_adjunct_target(*pStore, source) == target)
1498 return true;
1499 }
1500
1501 return false;
1502 }
1503
1504 void del_exclusive_adjunct_source(Entity source) {
1505 const auto it = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(source));
1506 if (it == m_srcToExclusiveAdjunctRel.end())
1507 return;
1508
1509 cnt::darray<Entity> relations;
1510 for (auto relKey: it->second)
1511 relations.push_back(relKey);
1512
1513 for (auto relation: relations) {
1514 touch_rel_version(relation);
1515 invalidate_queries_for_rel(relation);
1516 (void)exclusive_adjunct_del(source, relation, EntityBad);
1517 }
1518 m_targetsTravCache = {};
1519 m_srcBfsTravCache = {};
1520 m_depthOrderCache = {};
1521 m_sourcesAllCache = {};
1522 m_targetsAllCache = {};
1523 }
1524
1525 void del_exclusive_adjunct_relation(Entity relation) {
1526 const auto itStore = m_exclusiveAdjunctByRel.find(EntityLookupKey(relation));
1527 if (itStore == m_exclusiveAdjunctByRel.end())
1528 return;
1529
1530 cnt::darray<Entity> sources;
1531 const auto& srcToTgt = itStore->second.srcToTgt;
1532 GAIA_FOR((uint32_t)srcToTgt.size()) {
1533 if (srcToTgt[i] == EntityBad)
1534 continue;
1535
1536 sources.push_back(get((EntityId)i));
1537 }
1538
1539 touch_rel_version(relation);
1540 invalidate_queries_for_rel(relation);
1541 for (auto source: sources)
1542 (void)exclusive_adjunct_del(source, relation, EntityBad);
1543 m_targetsTravCache = {};
1544 m_srcBfsTravCache = {};
1545 m_depthOrderCache = {};
1546 m_sourcesAllCache = {};
1547 m_targetsAllCache = {};
1548 }
1549
1551 GAIA_NODISCARD bool has_exclusive_adjunct_target_cond(Entity target, Pair cond) const {
1552 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
1553 if (exclusive_adjunct_sources(store, target) == nullptr)
1554 continue;
1555
1556 if (has(relKey.entity(), cond))
1557 return true;
1558 }
1559
1560 return false;
1561 }
1562
1563 //----------------------------------------------------------------------
1564
1566 void set_serializer(std::nullptr_t) {
1567 // Always use the binary serializer as the default.
1568 m_serializer = ser::make_serializer(m_stream);
1569 }
1570
1574 GAIA_ASSERT(serializer.valid());
1575 m_serializer = serializer;
1576 }
1577
1580 template <typename TSerializer>
1581 void set_serializer(TSerializer& serializer) {
1582 set_serializer(ser::make_serializer(serializer));
1583 }
1584
1588 return m_serializer;
1589 }
1590
1591 //----------------------------------------------------------------------
1592
1593 struct EntityBuilder final {
1594 friend class World;
1595
1596 World& m_world;
1598 Archetype* m_pArchetypeSrc = nullptr;
1600 Chunk* m_pChunkSrc = nullptr;
1602 uint32_t m_rowSrc = 0;
1604 Archetype* m_pArchetype = nullptr;
1612 Entity m_graphEdgeEntity = EntityBad;
1614 uint8_t m_graphEdgeOpCount = 0;
1616 bool m_graphEdgeIsAdd = false;
1617
1619 enum class IdMode : uint8_t {
1621 Normal,
1623 Adjunct,
1625 Sticky
1626 };
1627
1628#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
1629 static constexpr uint32_t MAX_TERMS = 32;
1630 static_assert(MAX_TERMS <= ChunkHeader::MAX_COMPONENTS);
1631
1634#endif
1635
1636 EntityBuilder(World& world, Entity entity, EntityContainer& ec):
1637 m_world(world), m_pArchetypeSrc(ec.pArchetype), m_pChunkSrc(ec.pChunk), m_rowSrc(ec.row),
1638 m_pArchetype(ec.pArchetype), m_entity(entity) {
1639 // Make sure entity matches the provided entity container record
1640 GAIA_ASSERT(ec.pChunk->entity_view()[ec.row] == entity);
1641 }
1642
1643 EntityBuilder(World& world, Entity entity): m_world(world), m_entity(entity) {
1644 const auto& ec = world.fetch(entity);
1645 m_pArchetypeSrc = ec.pArchetype;
1646 m_pChunkSrc = ec.pChunk;
1647 m_rowSrc = ec.row;
1648
1649 m_pArchetype = ec.pArchetype;
1650 }
1651
1652 EntityBuilder(const EntityBuilder&) = default;
1653 EntityBuilder(EntityBuilder&&) = delete;
1654 EntityBuilder& operator=(const EntityBuilder&) = delete;
1655 EntityBuilder& operator=(EntityBuilder&&) = delete;
1656
1657 ~EntityBuilder() {
1658 commit();
1659 }
1660
1663 void commit() {
1664 // No requests to change the archetype were made
1665 if (m_pArchetype == nullptr) {
1666 reset_graph_edge_tracking();
1667 return;
1668 }
1669
1670 // Change in archetype detected
1671 if (m_pArchetypeSrc != m_pArchetype) {
1672 auto& ec = m_world.fetch(m_entity);
1673 GAIA_ASSERT(ec.pArchetype == m_pArchetypeSrc);
1674#if GAIA_OBSERVERS_ENABLED
1675 auto delDiffCtx = tl_del_comps.empty() ? ObserverRegistry::DiffDispatchCtx{}
1676 : m_world.m_observers.prepare_diff(
1677 m_world, ObserverEvent::OnDel, EntitySpan{tl_del_comps},
1678 EntitySpan{&m_entity, 1});
1679 auto addDiffCtx = tl_new_comps.empty() ? ObserverRegistry::DiffDispatchCtx{}
1680 : m_world.m_observers.prepare_diff(
1681 m_world, ObserverEvent::OnAdd, EntitySpan{tl_new_comps},
1682 EntitySpan{&m_entity, 1});
1683#endif
1684
1685 // Trigger remove hooks if there are any
1686 trigger_del_hooks(*m_pArchetype);
1687
1688 // Now that we have the final archetype move the entity to it
1689 m_world.move_entity_raw(m_entity, ec, *m_pArchetype);
1690
1691 // Batched builder operations resolve intermediate archetypes without touching the
1692 // graph. Recreate the cached edge only for the single-step case.
1693 if (m_graphEdgeOpCount == 1 && m_graphEdgeEntity != EntityBad) {
1694 if (m_graphEdgeIsAdd)
1695 rebuild_graph_edge(m_pArchetypeSrc, m_pArchetype, m_graphEdgeEntity);
1696 else
1697 rebuild_graph_edge(m_pArchetype, m_pArchetypeSrc, m_graphEdgeEntity);
1698 }
1699
1700 if (m_targetNameKey.str() != nullptr || m_targetAliasKey.str() != nullptr) {
1701 const auto compIdx = ec.pChunk->comp_idx(GAIA_ID(EntityDesc));
1702 // No need to update version, entity move did it already.
1703 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut_gen<false>(compIdx, ec.row));
1704 GAIA_ASSERT(core::check_alignment(pDesc));
1705
1706 // Update the entity name string pointers if necessary
1707 if (m_targetNameKey.str() != nullptr) {
1708 pDesc->name = m_targetNameKey.str();
1709 pDesc->name_len = m_targetNameKey.len();
1710 }
1711
1712 // Update the entity alias string pointers if necessary
1713 if (m_targetAliasKey.str() != nullptr) {
1714 pDesc->alias = m_targetAliasKey.str();
1715 pDesc->alias_len = m_targetAliasKey.len();
1716 }
1717 }
1718
1719 // Trigger add hooks if there are any
1720 trigger_add_hooks(*m_pArchetype);
1721#if GAIA_OBSERVERS_ENABLED
1722 m_world.m_observers.finish_diff(m_world, GAIA_MOV(delDiffCtx));
1723 m_world.m_observers.finish_diff(m_world, GAIA_MOV(addDiffCtx));
1724#endif
1725 cleanup_deleted_out_of_line_components();
1726
1727 m_pArchetypeSrc = ec.pArchetype;
1728 m_pChunkSrc = ec.pChunk;
1729 m_rowSrc = ec.row;
1730 }
1731 // Archetype is still the same. Make sure no chunk movement has happened.
1732 else {
1733#if GAIA_ASSERT_ENABLED
1734 auto& ec = m_world.fetch(m_entity);
1735 GAIA_ASSERT(ec.pChunk == m_pChunkSrc);
1736#endif
1737
1738#if GAIA_OBSERVERS_ENABLED
1739 auto delDiffCtx = tl_del_comps.empty() ? ObserverRegistry::DiffDispatchCtx{}
1740 : m_world.m_observers.prepare_diff(
1741 m_world, ObserverEvent::OnDel, EntitySpan{tl_del_comps},
1742 EntitySpan{&m_entity, 1});
1743 auto addDiffCtx = tl_new_comps.empty() ? ObserverRegistry::DiffDispatchCtx{}
1744 : m_world.m_observers.prepare_diff(
1745 m_world, ObserverEvent::OnAdd, EntitySpan{tl_new_comps},
1746 EntitySpan{&m_entity, 1});
1747#endif
1748
1749 if (m_targetNameKey.str() != nullptr || m_targetAliasKey.str() != nullptr) {
1750 const auto compIdx = m_pChunkSrc->comp_idx(GAIA_ID(EntityDesc));
1751 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<true>(compIdx, m_rowSrc));
1752 GAIA_ASSERT(core::check_alignment(pDesc));
1753
1754 // Update the entity name string pointers if necessary
1755 if (m_targetNameKey.str() != nullptr) {
1756 pDesc->name = m_targetNameKey.str();
1757 pDesc->name_len = m_targetNameKey.len();
1758 }
1759
1760 // Update the entity alias string pointers if necessary
1761 if (m_targetAliasKey.str() != nullptr) {
1762 pDesc->alias = m_targetAliasKey.str();
1763 pDesc->alias_len = m_targetAliasKey.len();
1764 }
1765 }
1766
1767#if GAIA_OBSERVERS_ENABLED
1768 m_world.m_observers.finish_diff(m_world, GAIA_MOV(delDiffCtx));
1769 m_world.m_observers.finish_diff(m_world, GAIA_MOV(addDiffCtx));
1770#endif
1771 cleanup_deleted_out_of_line_components();
1772 }
1773
1774 // Finalize the builder by reseting the archetype pointer
1775 m_pArchetype = nullptr;
1776 m_targetNameKey = {};
1777 m_targetAliasKey = {};
1778 reset_graph_edge_tracking();
1779 }
1780
1788 void name(const char* name, uint32_t len = 0) {
1789 name_inter<true>(name, len);
1790 }
1791
1803 void name_raw(const char* name, uint32_t len = 0) {
1804 name_inter<false>(name, len);
1805 }
1806
1811 void alias(const char* alias, uint32_t len = 0) {
1812 alias_inter<true>(alias, len);
1813 }
1814
1819 void alias_raw(const char* alias, uint32_t len = 0) {
1820 alias_inter<false>(alias, len);
1821 }
1822
1824 void del_name() {
1825 if (m_entity.pair())
1826 return;
1827
1828 // The following block is essentially the same as this but without the archetype pointer access:
1829 // const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
1830 const auto compIdx = core::get_index(m_pChunkSrc->ids_view(), GAIA_ID(EntityDesc));
1831 if (compIdx == BadIndex)
1832 return;
1833
1834 {
1835 const auto* pDesc = reinterpret_cast<const EntityDesc*>(m_pChunkSrc->comp_ptr(compIdx, m_rowSrc));
1836 GAIA_ASSERT(core::check_alignment(pDesc));
1837 if (pDesc->name == nullptr)
1838 return;
1839 }
1840
1841 // TODO: Trigger the hooks/observers manually here? I do not like essentially calling comp_ptr twice here.
1842 // The second one could be replaced with const cast + emit.
1843 // No need to update version, commit() will do it
1844 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<false>(compIdx, m_rowSrc));
1845 del_name_inter(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0));
1846 m_world.invalidate_scope_path_cache();
1847
1848 pDesc->name = nullptr;
1849 pDesc->name_len = 0;
1850 if (pDesc->alias == nullptr)
1851 del_inter(GAIA_ID(EntityDesc));
1852
1853 m_targetNameKey = {};
1854 }
1855
1857 void del_alias() {
1858 if (m_entity.pair())
1859 return;
1860
1861 // The following block is essentially the same as this but without the archetype pointer access:
1862 // const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
1863 const auto compIdx = core::get_index(m_pChunkSrc->ids_view(), GAIA_ID(EntityDesc));
1864 if (compIdx == BadIndex)
1865 return;
1866
1867 {
1868 const auto* pDesc = reinterpret_cast<const EntityDesc*>(m_pChunkSrc->comp_ptr(compIdx, m_rowSrc));
1869 GAIA_ASSERT(core::check_alignment(pDesc));
1870 if (pDesc->alias == nullptr)
1871 return;
1872 }
1873
1874 // TODO: Trigger the hooks/observers manually here? I do not like essentially calling comp_ptr twice here.
1875 // The second one could be replaced with const cast + emit.
1876 // No need to update version, commit() will do it
1877 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<false>(compIdx, m_rowSrc));
1878 del_alias_inter(EntityNameLookupKey(pDesc->alias, pDesc->alias_len, 0));
1879
1880 pDesc->alias = nullptr;
1881 pDesc->alias_len = 0;
1882 if (pDesc->name == nullptr)
1883 del_inter(GAIA_ID(EntityDesc));
1884
1885 m_targetAliasKey = {};
1886 }
1887
1891 GAIA_PROF_SCOPE(EntityBuilder::add);
1892 GAIA_ASSERT(m_world.valid(m_entity));
1893 GAIA_ASSERT(m_world.valid(entity));
1894
1895 add_inter(entity);
1896 return *this;
1897 }
1898
1902 GAIA_PROF_SCOPE(EntityBuilder::add);
1903 GAIA_ASSERT(m_world.valid(m_entity));
1904 GAIA_ASSERT(m_world.valid(pair.first()));
1905 GAIA_ASSERT(m_world.valid(pair.second()));
1906
1907 add_inter(pair);
1908 return *this;
1909 }
1910
1914 EntityBuilder& as(Entity entityBase) {
1915 return add(Pair(Is, entityBase));
1916 }
1917
1920 return add(Prefab);
1921 }
1922
1927 GAIA_NODISCARD bool as(Entity entity, Entity entityBase) const {
1928 return static_cast<const World&>(m_world).is(entity, entityBase);
1929 }
1930
1933 return add(Pair(ChildOf, parent));
1934 }
1935
1938 template <typename T>
1940 if constexpr (is_pair<T>::value) {
1941 const auto rel = m_world.template reg_comp<typename T::rel>().entity;
1942 const auto tgt = m_world.template reg_comp<typename T::tgt>().entity;
1943 const Entity ent = Pair(rel, tgt);
1944 add_inter(ent);
1945 return ent;
1946 } else {
1947 return m_world.template reg_comp<T>().entity;
1948 }
1949 }
1950
1951 template <typename T>
1952 EntityBuilder& add() {
1953 verify_comp<T>();
1954 add(register_component<T>());
1955 return *this;
1956 }
1957
1961 GAIA_PROF_SCOPE(EntityBuilder::del);
1962 GAIA_ASSERT(m_world.valid(m_entity));
1963 GAIA_ASSERT(m_world.valid(entity));
1964 del_inter(entity);
1965 return *this;
1966 }
1967
1971 GAIA_PROF_SCOPE(EntityBuilder::add);
1972 GAIA_ASSERT(m_world.valid(m_entity));
1973 GAIA_ASSERT(m_world.valid(pair.first()));
1974 GAIA_ASSERT(m_world.valid(pair.second()));
1975 del_inter(pair);
1976 return *this;
1977 }
1978
1979 template <typename T>
1980 EntityBuilder& del() {
1981 verify_comp<T>();
1982 del(register_component<T>());
1983 return *this;
1984 }
1985
1986 private:
1989 void trigger_add_hooks(const Archetype& newArchetype) {
1990#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
1991 if GAIA_UNLIKELY (m_world.tearing_down()) {
1992 tl_new_comps.clear();
1993 (void)newArchetype;
1994 return;
1995 }
1996
1997 m_world.lock();
1998
1999 #if GAIA_ENABLE_ADD_DEL_HOOKS
2000 // if (hookCnt > 0)
2001 {
2002 // Trigger component hooks first
2003 for (auto entity: tl_new_comps) {
2004 if (!entity.comp())
2005 continue;
2006
2007 const auto& item = m_world.comp_cache().get(entity);
2008 const auto& hooks = ComponentCache::hooks(item);
2009 if (hooks.func_add != nullptr)
2010 hooks.func_add(m_world, item, m_entity);
2011 }
2012 }
2013 #endif
2014
2015 #if GAIA_OBSERVERS_ENABLED
2016 // Trigger observers second
2017 m_world.m_observers.on_add(m_world, newArchetype, std::span<Entity>{tl_new_comps}, {&m_entity, 1});
2018 #else
2019 (void)newArchetype;
2020 #endif
2021
2022 tl_new_comps.clear();
2023
2024 m_world.unlock();
2025#endif
2026 }
2027
2030 void trigger_del_hooks(const Archetype& newArchetype) {
2031#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2032 if GAIA_UNLIKELY (m_world.tearing_down()) {
2033 tl_del_comps.clear();
2034 (void)newArchetype;
2035 return;
2036 }
2037
2038 m_world.notify_inherited_del_dependents(m_entity, std::span<Entity>{tl_del_comps});
2039 m_world.lock();
2040
2041 #if GAIA_OBSERVERS_ENABLED
2042 // Trigger observers first
2043 m_world.m_observers.on_del(m_world, newArchetype, std::span<Entity>{tl_del_comps}, {&m_entity, 1});
2044 #else
2045 (void)newArchetype;
2046 #endif
2047
2048 #if GAIA_ENABLE_ADD_DEL_HOOKS
2049 // if (hookCnt > 0)
2050 {
2051 // Trigger component hooks second
2052 for (auto entity: tl_del_comps) {
2053 if (!entity.comp())
2054 continue;
2055
2056 const auto& item = m_world.comp_cache().get(entity);
2057 const auto& hooks = ComponentCache::hooks(item);
2058 if (hooks.func_del != nullptr)
2059 hooks.func_del(m_world, item, m_entity);
2060 }
2061 }
2062 #endif
2063
2064 m_world.unlock();
2065#endif
2066 }
2067
2068 void cleanup_deleted_out_of_line_components() {
2069 for (auto entity: tl_del_comps) {
2070 if (entity.pair() || !m_world.is_out_of_line_component(entity) ||
2071 m_world.is_non_fragmenting_out_of_line_component(entity))
2072 continue;
2073
2074 const auto it = m_world.m_sparseComponentsByComp.find(EntityLookupKey(entity));
2075 if (it != m_world.m_sparseComponentsByComp.end())
2076 it->second.func_del(it->second.pStore, m_entity);
2077 }
2078
2079 tl_del_comps.clear();
2080 }
2081
2082 bool handle_add_entity(Entity entity) {
2083 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> targets;
2084
2085 const auto& ecMain = m_world.fetch(entity);
2086 if (entity.pair())
2087 m_world.invalidate_scope_path_cache();
2088
2089 // Handle entity combinations that can't be together
2090 if ((ecMain.flags & EntityContainerFlags::HasCantCombine) != 0) {
2091 m_world.targets(entity, CantCombine, [&targets](Entity target) {
2092 targets.push_back(target);
2093 });
2094 for (auto e: targets) {
2095 if (m_pArchetype->has(e)) {
2096#if GAIA_ASSERT_ENABLED
2097 GAIA_ASSERT2(false, "Trying to add an entity which can't be combined with the source");
2098 print_archetype_entities(m_world, *m_pArchetype, entity, true);
2099#endif
2100 return false;
2101 }
2102 }
2103 }
2104
2105 // Handle exclusivity
2106 if (entity.pair()) {
2107 // Check if (rel, tgt)'s rel part is exclusive
2108 const auto& ecRel = m_world.m_recs.entities[entity.id()];
2109 if ((ecRel.flags & EntityContainerFlags::IsExclusive) != 0) {
2110 auto rel = Entity(
2111 entity.id(), ecRel.data.gen, (bool)ecRel.data.ent, (bool)ecRel.data.pair,
2112 (EntityKind)ecRel.data.kind);
2113 auto tgt = m_world.try_get(entity.gen());
2114 if (tgt == EntityBad)
2115 return false;
2116
2117 // Make sure to remove the (rel, tgt0) so only the new (rel, tgt1) remains.
2118 // However, before that we need to make sure there only exists one target at most.
2119 targets.clear();
2120 m_world.targets_if(m_entity, rel, [&targets](Entity target) {
2121 targets.push_back(target);
2122 // Stop the moment we have more than 1 target. This kind of scenario is not supported
2123 // and can happen only if Exclusive is added after multiple relationships already exist.
2124 return targets.size() < 2;
2125 });
2126
2127 const auto targetsCnt = targets.size();
2128 if (targetsCnt > 1) {
2129#if GAIA_ASSERT_ENABLED
2130 GAIA_ASSERT2(
2131 false, "Trying to add a pair with exclusive relationship but there are multiple targets present. "
2132 "Make sure to add the Exclusive property before any relationships with it are created.");
2133 print_archetype_entities(m_world, *m_pArchetype, entity, true);
2134#endif
2135 return false;
2136 }
2137
2138 // Remove the previous relationship if possible.
2139 // We avoid self-removal.
2140 const auto tgtNew = *targets.begin();
2141 if (targetsCnt == 1 && tgt != tgtNew) {
2142 // Exclusive relationship replaces the previous one.
2143 // We need to check if the old one can be removed.
2144 // This is what del_inter does on the inside.
2145 // It first checks if entity can be deleted and calls handle_del afterwards.
2146 if (!can_del(entity)) {
2147#if GAIA_ASSERT_ENABLED
2148 GAIA_ASSERT2(
2149 false, "Trying to replace an exclusive relationship but the entity which is getting removed has "
2150 "dependencies.");
2151 print_archetype_entities(m_world, *m_pArchetype, entity, true);
2152#endif
2153 return false;
2154 }
2155
2156 handle_del(ecs::Pair(rel, tgtNew));
2157 }
2158 }
2159 }
2160
2161 // Handle requirements
2162 {
2163 targets.clear();
2164 m_world.targets(entity, Requires, [&targets](Entity target) {
2165 targets.push_back(target);
2166 });
2167
2168 for (auto e: targets) {
2169 auto* pArchetype = m_pArchetype;
2170 handle_add<false>(e);
2171 if (m_pArchetype != pArchetype)
2172 handle_add_entity(e);
2173 }
2174 }
2175
2176 return true;
2177 }
2178
2179 GAIA_NODISCARD bool has_Requires_tgt(Entity entity) const {
2180 // Don't allow to delete entity if something in the archetype requires it
2181 auto ids = m_pArchetype->ids_view();
2182 for (auto e: ids) {
2183 if (m_world.has(e, Pair(Requires, entity)))
2184 return true;
2185 }
2186
2187 return false;
2188 }
2189
2190 static void set_flag(EntityContainerFlagsType& flags, EntityContainerFlags flag, bool enable) {
2191 if (enable)
2192 flags |= flag;
2193 else
2194 flags &= ~flag;
2195 }
2196
2197 void set_flag(Entity entity, EntityContainerFlags flag, bool enable) {
2198 auto& ec = m_world.fetch(entity);
2199 set_flag(ec.flags, flag, enable);
2200 }
2201
2202 void try_set_flags(Entity entity, bool enable) {
2203 auto& ecMain = m_world.fetch(m_entity);
2204 try_set_CantCombine(ecMain, entity, enable);
2205
2206 auto& ec = m_world.fetch(entity);
2207 try_set_Is(ec, entity, enable);
2208 try_set_IsExclusive(ecMain, entity, enable);
2209 if (enable)
2210 try_set_sticky_component_traits(ecMain, entity);
2211 try_set_IsSingleton(ecMain, entity, enable);
2212 try_set_OnDelete(ecMain, entity, enable);
2213 try_set_OnDeleteTarget(entity, enable);
2214 }
2215
2216 void try_set_Is(EntityContainer& ec, Entity entity, bool enable) {
2217 if (!entity.pair() || entity.id() != Is.id())
2218 return;
2219
2220 set_flag(ec.flags, EntityContainerFlags::HasAliasOf, enable);
2221 }
2222
2223 void try_set_CantCombine(EntityContainer& ec, Entity entity, bool enable) {
2224 if (!entity.pair() || entity.id() != CantCombine.id())
2225 return;
2226
2227 GAIA_ASSERT(entity != m_entity);
2228
2229 // Setting the flag can be done right away.
2230 // One bit can only contain information about one pair but there
2231 // can be any amount of CanCombine pairs formed with an entity.
2232 // Therefore, when resetting the flag, we first need to check if there
2233 // are any other targets with this flag set and only reset the flag
2234 // if there is only one present.
2235 if (enable)
2236 set_flag(ec.flags, EntityContainerFlags::HasCantCombine, true);
2237 else if ((ec.flags & EntityContainerFlags::HasCantCombine) != 0) {
2238 uint32_t targets = 0;
2239 m_world.targets(m_entity, CantCombine, [&targets]([[maybe_unused]] Entity entity) {
2240 ++targets;
2241 });
2242 if (targets == 1)
2243 set_flag(ec.flags, EntityContainerFlags::HasCantCombine, false);
2244 }
2245 }
2246
2247 void try_set_IsExclusive(EntityContainer& ec, Entity entity, bool enable) {
2248 if (entity.pair() || entity.id() != Exclusive.id())
2249 return;
2250
2251 set_flag(ec.flags, EntityContainerFlags::IsExclusive, enable);
2252 }
2253
2254 void try_set_sticky_component_traits(EntityContainer& ecMain, Entity entity) {
2255 if (entity.pair())
2256 return;
2257
2258 if (entity.id() == DontFragment.id()) {
2259 m_world.set_component_dont_fragment(m_entity, ecMain);
2260 return;
2261 }
2262
2263 if (entity.id() == Sparse.id())
2264 m_world.set_component_sparse_storage(m_entity);
2265 }
2266
2270 GAIA_NODISCARD IdMode id_mode(Entity entity) const noexcept {
2271 if (!entity.pair()) {
2272 if (m_entity.comp() && (entity.id() == DontFragment.id() || entity.id() == Sparse.id()))
2273 return IdMode::Sticky;
2274
2275 return IdMode::Normal;
2276 }
2277
2278 return m_world.is_exclusive_dont_fragment_relation(m_world.get(entity.id())) ? IdMode::Adjunct
2279 : IdMode::Normal;
2280 }
2281
2286 GAIA_NODISCARD bool has_id(IdMode mode, Entity entity) const {
2287 return mode == IdMode::Adjunct ? m_world.has(m_entity, entity) : m_pArchetype->has(entity);
2288 }
2289
2294 GAIA_NODISCARD bool add_id(IdMode mode, Entity entity) {
2295 if (mode == IdMode::Adjunct) {
2296 const auto relation = m_world.try_get(entity.id());
2297 const auto target = m_world.try_get(entity.gen());
2298 if (relation == EntityBad || target == EntityBad)
2299 return false;
2300
2301 m_world.exclusive_adjunct_set(m_entity, relation, target);
2302 return true;
2303 }
2304
2305 m_pArchetype = m_world.foc_archetype_add_no_graph(m_pArchetype, entity);
2306 note_graph_edge(entity, true);
2307 return true;
2308 }
2309
2313 void del_id(IdMode mode, Entity entity) {
2314 if (mode == IdMode::Adjunct) {
2315 const auto relation = m_world.try_get(entity.id());
2316 const auto target = m_world.try_get(entity.gen());
2317 if (relation != EntityBad && target != EntityBad)
2318 (void)m_world.exclusive_adjunct_del(m_entity, relation, target);
2319 return;
2320 }
2321
2322 m_pArchetype = m_world.foc_archetype_del_no_graph(m_pArchetype, entity);
2323 note_graph_edge(entity, false);
2324 }
2325
2326 void try_set_OnDeleteTarget(Entity entity, bool enable) {
2327 if (!entity.pair())
2328 return;
2329
2330 const auto rel = m_world.try_get(entity.id());
2331 const auto tgt = m_world.try_get(entity.gen());
2332 if (rel == EntityBad || tgt == EntityBad)
2333 return;
2334
2335 // Adding a pair to an entity with OnDeleteTarget relationship.
2336 // We need to update the target entity's flags.
2337 if (m_world.has(rel, Pair(OnDeleteTarget, Delete)))
2338 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Delete, enable);
2339 else if (m_world.has(rel, Pair(OnDeleteTarget, Remove)))
2340 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Remove, enable);
2341 else if (m_world.has(rel, Pair(OnDeleteTarget, Error)))
2342 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Error, enable);
2343 }
2344
2345 void try_set_OnDelete(EntityContainer& ec, Entity entity, bool enable) {
2346 if (entity == Pair(OnDelete, Delete))
2347 set_flag(ec.flags, EntityContainerFlags::OnDelete_Delete, enable);
2348 else if (entity == Pair(OnDelete, Remove))
2349 set_flag(ec.flags, EntityContainerFlags::OnDelete_Remove, enable);
2350 else if (entity == Pair(OnDelete, Error))
2351 set_flag(ec.flags, EntityContainerFlags::OnDelete_Error, enable);
2352 }
2353
2354 void try_set_IsSingleton(EntityContainer& ec, Entity entity, bool enable) {
2355 const bool isSingleton = enable && m_entity == entity;
2356 set_flag(ec.flags, EntityContainerFlags::IsSingleton, isSingleton);
2357 }
2358
2359 void handle_DependsOn(Entity entity, bool enable) {
2360 (void)entity;
2361 (void)enable;
2362 // auto& ec = m_world.fetch(entity);
2363 // if (enable) {
2364 // // Calculate the depth in the dependency tree
2365 // uint32_t depth = 1;
2366
2367 // auto e = entity;
2368 // if (m_world.valid(e)) {
2369 // while (true) {
2370 // auto tgt = m_world.target(e, DependsOn);
2371 // if (tgt == EntityBad)
2372 // break;
2373
2374 // ++depth;
2375 // e = tgt;
2376 // }
2377 // }
2378 // ec.depthDependsOn = (uint8_t)depth;
2379
2380 // // Update depth for all entities depending on this one
2381 // auto q = m_world.uquery();
2382 // q.all(ecs::Pair(DependsOn, m_entity)) //
2383 // .each([&](Entity dependingEntity) {
2384 // auto& ecDependingEntity = m_world.fetch(dependingEntity);
2385 // ecDependingEntity.depthDependsOn += (uint8_t)depth;
2386 // });
2387 // } else {
2388 // // Update depth for all entities depending on this one
2389 // auto q = m_world.uquery();
2390 // q.all(ecs::Pair(DependsOn, m_entity)) //
2391 // .each([&](Entity dependingEntity) {
2392 // auto& ecDependingEntity = m_world.fetch(dependingEntity);
2393 // ecDependingEntity.depthDependsOn -= ec.depthDependsOn;
2394 // });
2395
2396 // // Reset the depth
2397 // ec.depthDependsOn = 0;
2398 // }
2399 }
2400
2401 template <bool IsBootstrap>
2402 bool handle_add(Entity entity) {
2403 const auto mode = id_mode(entity);
2404#if GAIA_ASSERT_ENABLED
2405 if (mode != IdMode::Adjunct)
2406 World::verify_add(m_world, *m_pArchetype, m_entity, entity);
2407#endif
2408
2409 // Don't add the same entity twice
2410 if (has_id(mode, entity))
2411 return false;
2412
2413 if (entity.pair()) {
2414 auto relation = m_world.get(entity.id());
2415 m_world.touch_rel_version(relation);
2416 m_world.invalidate_queries_for_rel(relation);
2417 m_world.m_targetsTravCache = {};
2418 m_world.m_srcBfsTravCache = {};
2419 m_world.m_depthOrderCache = {};
2420 m_world.m_sourcesAllCache = {};
2421 m_world.m_targetsAllCache = {};
2422 }
2423
2424 try_set_flags(entity, true);
2425
2426 // Update the Is relationship base counter if necessary
2427 if (entity.pair() && entity.id() == Is.id()) {
2428 auto e = m_world.try_get(entity.gen());
2429 if (e == EntityBad)
2430 return false;
2431
2432 EntityLookupKey entityKey(m_entity);
2433 EntityLookupKey eKey(e);
2434
2435 // m_entity -> {..., e}
2436 auto& entity_to_e = m_world.m_entityToAsTargets[entityKey];
2437 entity_to_e.insert(eKey);
2438 m_world.m_entityToAsTargetsTravCache = {};
2439 // e -> {..., m_entity}
2440 auto& e_to_entity = m_world.m_entityToAsRelations[eKey];
2441 e_to_entity.insert(entityKey);
2442 m_world.m_entityToAsRelationsTravCache = {};
2443
2444 // Make sure the relation entity is registered as archetype so queries can find it
2445 // auto& ec = m_world.fetch(tgt);
2446 // m_world.add_entity_archetype_pair(m_entity, ec.pArchetype);
2447
2448 // Cached queries might need to be invalidated.
2449 m_world.invalidate_queries_for_entity({Is, e});
2450 }
2451
2452 if (!add_id(mode, entity))
2453 return false;
2454
2455 if constexpr (!IsBootstrap) {
2456 handle_DependsOn(entity, true);
2457
2458#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2459 tl_new_comps.push_back(entity);
2460#endif
2461 }
2462
2463 return true;
2464 }
2465
2466 void handle_del(Entity entity) {
2467 if (entity.pair() && !m_world.valid(entity)) {
2468 if (m_pArchetype->has(entity)) {
2469 const auto relation = m_world.try_get(entity.id());
2470 if (relation != EntityBad) {
2471 m_world.touch_rel_version(relation);
2472 m_world.invalidate_queries_for_rel(relation);
2473 m_world.m_targetsTravCache = {};
2474 m_world.m_srcBfsTravCache = {};
2475 m_world.m_depthOrderCache = {};
2476 m_world.m_sourcesAllCache = {};
2477 m_world.m_targetsAllCache = {};
2478 }
2479
2480 if (entity.id() == Is.id())
2481 m_world.unlink_stale_is_relations_by_target_id(m_entity, entity.gen());
2482
2483 m_pArchetype = m_world.foc_archetype_del_no_graph(m_pArchetype, entity);
2484 note_graph_edge(entity, false);
2485 }
2486 return;
2487 }
2488
2489 const auto mode = id_mode(entity);
2490 if (entity.pair())
2491 m_world.invalidate_scope_path_cache();
2492
2493#if GAIA_ASSERT_ENABLED
2494 if (mode != IdMode::Adjunct)
2495 World::verify_del(m_world, *m_pArchetype, m_entity, entity);
2496#endif
2497
2498 // Don't delete what has not beed added
2499 if (!has_id(mode, entity))
2500 return;
2501
2502 if (entity.pair()) {
2503 auto relation = m_world.get(entity.id());
2504 m_world.touch_rel_version(relation);
2505 m_world.invalidate_queries_for_rel(relation);
2506 m_world.m_targetsTravCache = {};
2507 m_world.m_srcBfsTravCache = {};
2508 m_world.m_depthOrderCache = {};
2509 m_world.m_sourcesAllCache = {};
2510 m_world.m_targetsAllCache = {};
2511 }
2512
2513 try_set_flags(entity, false);
2514 handle_DependsOn(entity, false);
2515
2516 // Update the Is relationship base counter if necessary
2517 if (entity.pair() && entity.id() == Is.id()) {
2518 auto e = m_world.try_get(entity.gen());
2519 if (e != EntityBad) {
2520 m_world.unlink_live_is_relation(m_entity, e);
2521 }
2522 }
2523
2524 del_id(mode, entity);
2525
2526#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2527 tl_del_comps.push_back(entity);
2528#endif
2529 }
2530
2531 void add_inter(Entity entity) {
2532 GAIA_ASSERT(!is_wildcard(entity));
2533
2534 if (entity.pair()) {
2535 // Make sure the entity container record exists if it is a pair
2536 m_world.assign_pair(entity, *m_world.m_pEntityArchetype);
2537 }
2538
2539 if (!handle_add_entity(entity))
2540 return;
2541
2542 handle_add<false>(entity);
2543 }
2544
2545 void note_graph_edge(Entity entity, bool isAdd) {
2546 ++m_graphEdgeOpCount;
2547 m_graphEdgeEntity = entity;
2548 m_graphEdgeIsAdd = isAdd;
2549 }
2550
2551 void reset_graph_edge_tracking() {
2552 m_graphEdgeEntity = EntityBad;
2553 m_graphEdgeOpCount = 0;
2554 m_graphEdgeIsAdd = false;
2555 }
2556
2559 static void rebuild_graph_edge(Archetype* pArchetypeLeft, Archetype* pArchetypeRight, Entity entity) {
2560 pArchetypeLeft->del_graph_edge_right_local(entity);
2561 pArchetypeRight->del_graph_edge_left_local(entity);
2562 pArchetypeLeft->build_graph_edges(pArchetypeRight, entity);
2563 }
2564
2565 void add_inter_init(Entity entity) {
2566 GAIA_ASSERT(!is_wildcard(entity));
2567
2568 if (entity.pair()) {
2569 // Make sure the entity container record exists if it is a pair
2570 m_world.assign_pair(entity, *m_world.m_pEntityArchetype);
2571 }
2572
2573 if (!handle_add_entity(entity))
2574 return;
2575
2576 handle_add<true>(entity);
2577 }
2578
2579 GAIA_NODISCARD bool can_del(Entity entity) const noexcept {
2580 if (has_Requires_tgt(entity))
2581 return false;
2582
2583 return true;
2584 }
2585
2586 GAIA_NODISCARD bool is_sticky_component_trait(Entity entity) const noexcept {
2587 return id_mode(entity) == IdMode::Sticky;
2588 }
2589
2590 bool del_inter(Entity entity) {
2591 if (is_sticky_component_trait(entity))
2592 return true;
2593
2594 if (!can_del(entity))
2595 return false;
2596
2597 handle_del(entity);
2598 return true;
2599 }
2600
2601 void del_name_inter(EntityNameLookupKey key) {
2602 const auto it = m_world.m_nameToEntity.find(key);
2603 // If the assert is hit it means the pointer to the name string was invalidated or became dangling.
2604 // That should not be possible for strings managed internally so the only other option is user-managed
2605 // strings are broken.
2606 GAIA_ASSERT(it != m_world.m_nameToEntity.end());
2607 if (it != m_world.m_nameToEntity.end()) {
2608 // Release memory allocated for the string if we own it
2609 if (it->first.owned())
2610 mem::mem_free((void*)key.str());
2611
2612 m_world.m_nameToEntity.erase(it);
2613 }
2614 }
2615
2616 void del_alias_inter(EntityNameLookupKey key) {
2617 const auto it = m_world.m_aliasToEntity.find(key);
2618 // If the assert is hit it means the pointer to the name string was invalidated or became dangling.
2619 // That should not be possible for strings managed internally so the only other option is user-managed
2620 // strings are broken.
2621 GAIA_ASSERT(it != m_world.m_aliasToEntity.end());
2622 if (it != m_world.m_aliasToEntity.end()) {
2623 // Release memory allocated for the string if we own it
2624 if (it->first.owned())
2625 mem::mem_free((void*)key.str());
2626
2627 m_world.m_aliasToEntity.erase(it);
2628 }
2629 }
2630
2631 template <bool IsOwned>
2632 void name_inter(const char* name, uint32_t len) {
2634 GAIA_ASSERT(!m_entity.pair());
2635 if (m_entity.pair())
2636 return;
2637
2638 // When nullptr is passed for the name it means the user wants to delete the current one
2639 if (name == nullptr) {
2640 GAIA_ASSERT(len == 0);
2641 del_name();
2642 return;
2643 }
2644
2645 GAIA_ASSERT(len < ComponentCacheItem::MaxNameLength);
2646
2647 // Make sure the name does not contain a dot because this character is reserved for
2648 // hierarchical lookups, e.g. "parent.child.subchild".
2649 GAIA_FOR(len) {
2650 const bool hasInvalidCharacter = name[i] == '.';
2651 GAIA_ASSERT(!hasInvalidCharacter && "Character '.' can't be used in entity names");
2652 if (hasInvalidCharacter)
2653 return;
2654 }
2655
2656 EntityNameLookupKey key(
2657 name, len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len, IsOwned);
2658
2659 // Make sure the name is unique. Ignore setting the same name twice on the same entity.
2660 // If it is not, there is nothing to do.
2661 auto it = m_world.m_nameToEntity.find(key);
2662 if (it == m_world.m_nameToEntity.end()) {
2663 // If we already had some name, remove the pair from the map first.
2664 if (m_targetNameKey.str() != nullptr) {
2665 del_name_inter(m_targetNameKey);
2666 } else {
2667 const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
2668 if (compIdx != BadIndex) {
2669 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut(compIdx, m_rowSrc));
2670 GAIA_ASSERT(core::check_alignment(pDesc));
2671 if (pDesc->name != nullptr) {
2672 del_name_inter(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0));
2673 pDesc->name = nullptr;
2674 }
2675 } else {
2676 // Make sure EntityDesc is added to the entity.
2677 add_inter(GAIA_ID(EntityDesc));
2678 }
2679 }
2680
2681 // Insert the new pair
2682 it = m_world.m_nameToEntity.emplace(key, m_entity).first;
2683 } else {
2684#if GAIA_ASSERT_ENABLED
2685 if (it->second != m_entity && World::s_enableUniqueNameDuplicateAssert)
2686 GAIA_ASSERT(false && "Trying to set non-unique name for an entity");
2687#endif
2688
2689 // Attempts to set the same name again, or not a unique name, will be dropped.
2690 return;
2691 }
2692
2693 if constexpr (IsOwned) {
2694 // Allocate enough storage for the name
2695 char* entityStr = (char*)mem::mem_alloc(key.len() + 1);
2696 memcpy((void*)entityStr, (const void*)name, key.len());
2697 entityStr[key.len()] = 0;
2698
2699 m_targetNameKey = EntityNameLookupKey(entityStr, key.len(), 1, {key.hash()});
2700
2701 // Update the map so it points to the newly allocated string.
2702 // We replace the pointer we provided in try_emplace with an internally allocated string.
2703 auto p = robin_hood::pair(std::make_pair(m_targetNameKey, m_entity));
2704 it->swap(p);
2705 } else {
2706 m_targetNameKey = key;
2707
2708 // We tell the map the string is non-owned.
2709 auto p = robin_hood::pair(std::make_pair(key, m_entity));
2710 it->swap(p);
2711 }
2712
2713 m_world.invalidate_scope_path_cache();
2714 }
2715
2716 template <bool IsOwned>
2717 void alias_inter(const char* alias, uint32_t len) {
2719 GAIA_ASSERT(!m_entity.pair());
2720 if (m_entity.pair())
2721 return;
2722
2723 // When nullptr is passed for the alias it means the user wants to delete the current one
2724 if (alias == nullptr) {
2725 GAIA_ASSERT(len == 0);
2726 del_alias();
2727 return;
2728 }
2729
2730 GAIA_ASSERT(len < ComponentCacheItem::MaxNameLength);
2731
2732 // Make sure the name does not contain a dot because this character is reserved for
2733 // hierarchical lookups, e.g. "parent.child.subchild".
2734 GAIA_FOR(len) {
2735 const bool hasInvalidCharacter = alias[i] == '.';
2736 GAIA_ASSERT(!hasInvalidCharacter && "Character '.' can't be used in entity aliases");
2737 if (hasInvalidCharacter)
2738 return;
2739 }
2740
2741 EntityNameLookupKey key(
2742 alias, len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len, IsOwned);
2743
2744 auto it = m_world.m_aliasToEntity.find(key);
2745 if (it == m_world.m_aliasToEntity.end()) {
2746 // If we already had some alias, remove the pair from the map first.
2747 if (m_targetAliasKey.str() != nullptr) {
2748 del_alias_inter(m_targetAliasKey);
2749 } else {
2750 const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
2751 if (compIdx != BadIndex) {
2752 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut(compIdx, m_rowSrc));
2753 GAIA_ASSERT(core::check_alignment(pDesc));
2754 if (pDesc->alias != nullptr) {
2755 del_alias_inter(EntityNameLookupKey(pDesc->alias, pDesc->alias_len, 0));
2756 pDesc->alias = nullptr;
2757 }
2758 } else {
2759 // Make sure EntityDesc is added to the entity.
2760 add_inter(GAIA_ID(EntityDesc));
2761 }
2762 }
2763
2764 it = m_world.m_aliasToEntity.emplace(key, m_entity).first;
2765 } else {
2766#if GAIA_ASSERT_ENABLED
2767 if (it->second != m_entity && World::s_enableUniqueNameDuplicateAssert)
2768 GAIA_ASSERT(false && "Trying to set non-unique alias for an entity");
2769#endif
2770
2771 // Attempts to set the same alias again, or not a unique alias, will be dropped.
2772 return;
2773 }
2774
2775 if constexpr (IsOwned) {
2776 // Allocate enough storage for the alias
2777 char* aliasStr = (char*)mem::mem_alloc(key.len() + 1);
2778 memcpy((void*)aliasStr, (const void*)alias, key.len());
2779 aliasStr[key.len()] = 0;
2780
2781 m_targetAliasKey = EntityNameLookupKey(aliasStr, key.len(), 1, {key.hash()});
2782
2783 // Update the map so it points to the newly allocated string.
2784 // We replace the pointer we provided in try_emplace with an internally allocated string.
2785 auto p = robin_hood::pair(std::make_pair(m_targetAliasKey, m_entity));
2786 it->swap(p);
2787 } else {
2788 m_targetAliasKey = key;
2789
2790 // We tell the map the string is non-owned.
2791 auto p = robin_hood::pair(std::make_pair(key, m_entity));
2792 it->swap(p);
2793 }
2794 }
2795 };
2796
2797 //----------------------------------------------------------------------
2798
2801 GAIA_NODISCARD ComponentCache& comp_cache_mut() {
2802 return m_compCache;
2803 }
2804
2807 GAIA_NODISCARD const ComponentCache& comp_cache() const {
2808 return m_compCache;
2809 }
2810
2811 //----------------------------------------------------------------------
2812
2817 GAIA_NODISCARD Entity symbol(const char* symbol, uint32_t len = 0) const {
2818 if (symbol == nullptr || symbol[0] == 0)
2819 return EntityBad;
2820
2821 const auto* pItem = comp_cache().symbol(symbol, len);
2822 return pItem != nullptr ? pItem->entity : EntityBad;
2823 }
2824
2828 GAIA_NODISCARD util::str_view symbol(Entity component) const {
2829 const auto* pItem = comp_cache().find(component);
2830 return pItem != nullptr ? comp_cache().symbol_name(*pItem) : util::str_view{};
2831 }
2832
2837 GAIA_NODISCARD Entity path(const char* path, uint32_t len = 0) const {
2838 if (path == nullptr || path[0] == 0)
2839 return EntityBad;
2840
2841 const auto* pItem = comp_cache().path(path, len);
2842 return pItem != nullptr ? pItem->entity : EntityBad;
2843 }
2844
2848 GAIA_NODISCARD util::str_view path(Entity component) const {
2849 const auto* pItem = comp_cache().find(component);
2850 return pItem != nullptr ? comp_cache().path_name(*pItem) : util::str_view{};
2851 }
2852
2858 bool path(Entity component, const char* path, uint32_t len = 0) {
2859 auto* pItem = comp_cache_mut().find(component);
2860 return pItem != nullptr ? comp_cache_mut().path(*pItem, path, len) : false;
2861 }
2862
2867 GAIA_NODISCARD Entity alias(const char* alias, uint32_t len = 0) const {
2868 if (alias == nullptr || alias[0] == 0)
2869 return EntityBad;
2870
2871 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
2872 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
2873 const auto it = m_aliasToEntity.find(EntityNameLookupKey(alias, l, 0));
2874 return it != m_aliasToEntity.end() ? it->second : EntityBad;
2875 }
2876
2880 GAIA_NODISCARD util::str_view alias(Entity entity) const {
2881 if (entity.pair())
2882 return {};
2883
2884 const auto& ec = m_recs.entities[entity.id()];
2885 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
2886 if (compIdx == BadIndex)
2887 return {};
2888
2889 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
2890 GAIA_ASSERT(core::check_alignment(pDesc));
2891 return {pDesc->alias, pDesc->alias_len};
2892 }
2893
2899 bool alias(Entity entity, const char* alias, uint32_t len = 0) {
2900 if (!valid(entity) || entity.pair())
2901 return false;
2902
2903 const auto before = this->alias(entity);
2904 EntityBuilder(*this, entity).alias(alias, len);
2905 const auto after = this->alias(entity);
2906 if (alias == nullptr)
2907 return !before.empty() && after.empty();
2908
2909 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
2910 return after == util::str_view(alias, l);
2911 }
2912
2918 bool alias_raw(Entity entity, const char* alias, uint32_t len = 0) {
2919 if (!valid(entity) || entity.pair())
2920 return false;
2921
2922 const auto before = this->alias(entity);
2923 EntityBuilder(*this, entity).alias_raw(alias, len);
2924 const auto after = this->alias(entity);
2925 if (alias == nullptr)
2926 return !before.empty() && after.empty();
2927
2928 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
2929 return after == util::str_view(alias, l);
2930 }
2931
2936 GAIA_NODISCARD util::str_view display_name(Entity entity) const {
2937 const auto* pItem = comp_cache().find(entity);
2938 if (pItem == nullptr)
2939 return {};
2940
2941 const auto aliasValue = alias(entity);
2942 if (!aliasValue.empty())
2943 return aliasValue;
2944
2945 const auto pathValue = path(entity);
2946 if (!pathValue.empty()) {
2947 const auto symbolEntity = symbol(pathValue.data(), pathValue.size());
2948 if (symbolEntity == EntityBad || symbolEntity == entity)
2949 return pathValue;
2950 }
2951
2952 return symbol(entity);
2953 }
2954
2955 //----------------------------------------------------------------------
2956
2957 private:
2958 void invalidate_scope_path_cache() const {
2959 m_componentScopePathCache.clear();
2960 m_componentScopePathCacheEntity = EntityBad;
2961 m_componentScopePathCacheValid = false;
2962 }
2963
2968 GAIA_NODISCARD bool build_scope_path(Entity scope, util::str& out) const {
2969 out.clear();
2970 if (!valid(scope) || scope.pair())
2971 return false;
2972
2974 auto curr = scope;
2975 while (curr != EntityBad) {
2976 const auto currName = name(curr);
2977 if (currName.empty()) {
2978 out.clear();
2979 return false;
2980 }
2981
2982 segments.push_back(currName);
2983 curr = target(curr, ChildOf);
2984 }
2985
2986 if (segments.empty())
2987 return false;
2988
2989 uint32_t totalLen = 0;
2990 for (auto segment: segments)
2991 totalLen += segment.size();
2992 totalLen += (uint32_t)segments.size() - 1;
2993
2994 out.reserve(totalLen);
2995 for (uint32_t i = (uint32_t)segments.size(); i > 0; --i) {
2996 if (!out.empty())
2997 out.append('.');
2998 out.append(segments[i - 1]);
2999 }
3000
3001 return true;
3002 }
3003
3007 GAIA_NODISCARD bool current_scope_path(util::str& out) const {
3008 if (m_componentScope == EntityBad) {
3009 invalidate_scope_path_cache();
3010 out.clear();
3011 return false;
3012 }
3013
3014 if (m_componentScopePathCacheValid && m_componentScopePathCacheEntity == m_componentScope) {
3015 out.assign(m_componentScopePathCache.view());
3016 return true;
3017 }
3018
3019 if (!build_scope_path(m_componentScope, out)) {
3020 invalidate_scope_path_cache();
3021 return false;
3022 }
3023
3024 m_componentScopePathCache.assign(out.view());
3025 m_componentScopePathCacheEntity = m_componentScope;
3026 m_componentScopePathCacheValid = true;
3027 return true;
3028 }
3029
3030 GAIA_NODISCARD const ComponentCacheItem*
3031 find_comp_scope_chain_inter(Entity scopeEntity, const char* name, uint32_t len) const {
3032 if (scopeEntity == EntityBad)
3033 return nullptr;
3034
3035 util::str scopePath;
3036 if (!build_scope_path(scopeEntity, scopePath))
3037 return nullptr;
3038
3039 util::str scopedName;
3040 scopedName.reserve(scopePath.size() + 1 + len);
3041
3042 while (!scopePath.empty()) {
3043 scopedName.clear();
3044 scopedName.append(scopePath.view());
3045 scopedName.append('.');
3046 scopedName.append(name, len);
3047
3048 if (const auto* pItem = m_compCache.path(scopedName.data(), (uint32_t)scopedName.size()); pItem != nullptr)
3049 return pItem;
3050
3051 const auto parentSepIdx = scopePath.view().find_last_of('.');
3052 if (parentSepIdx == BadIndex)
3053 break;
3054
3055 scopePath.assign(util::str_view(scopePath.data(), parentSepIdx));
3056 }
3057
3058 return nullptr;
3059 }
3060
3061 void add_comp_scope_chain_hits_inter(
3062 cnt::darray<Entity>& out, Entity scopeEntity, const char* name, uint32_t len) const {
3063 if (scopeEntity == EntityBad)
3064 return;
3065
3066 util::str scopePath;
3067 if (!build_scope_path(scopeEntity, scopePath))
3068 return;
3069
3070 util::str scopedName;
3071 scopedName.reserve(scopePath.size() + 1 + len);
3072
3073 while (!scopePath.empty()) {
3074 scopedName.clear();
3075 scopedName.append(scopePath.view());
3076 scopedName.append('.');
3077 scopedName.append(name, len);
3078
3079 if (const auto* pItem = m_compCache.path(scopedName.data(), (uint32_t)scopedName.size()); pItem != nullptr)
3080 ComponentCache::push_unique_entity(out, pItem->entity);
3081
3082 const auto parentSepIdx = scopePath.view().find_last_of('.');
3083 if (parentSepIdx == BadIndex)
3084 break;
3085
3086 scopePath.assign(util::str_view(scopePath.data(), parentSepIdx));
3087 }
3088 }
3089
3090 GAIA_NODISCARD const ComponentCacheItem* find_comp_lookup_inter(const char* name, uint32_t len) const {
3091 if (const auto* pItem = find_comp_scope_chain_inter(m_componentScope, name, len); pItem != nullptr)
3092 return pItem;
3093
3094 for (const auto scopeEntity: m_componentLookupPath) {
3095 if (scopeEntity == m_componentScope)
3096 continue;
3097
3098 if (const auto* pItem = find_comp_scope_chain_inter(scopeEntity, name, len); pItem != nullptr)
3099 return pItem;
3100 }
3101
3102 return nullptr;
3103 }
3104
3105 void add_comp_lookup_hits_inter(cnt::darray<Entity>& out, const char* name, uint32_t len) const {
3106 add_comp_scope_chain_hits_inter(out, m_componentScope, name, len);
3107 for (const auto scopeEntity: m_componentLookupPath) {
3108 if (scopeEntity == m_componentScope)
3109 continue;
3110
3111 add_comp_scope_chain_hits_inter(out, scopeEntity, name, len);
3112 }
3113 }
3114
3115 GAIA_NODISCARD const ComponentCacheItem*
3116 find_comp_exact_inter(const char* name, uint32_t len, bool isPath, bool isSymbol) const {
3117 if (const auto* pItem = m_compCache.symbol(name, len); pItem != nullptr)
3118 return pItem;
3119
3120 if (!isPath) {
3121 if (const auto* pItem = m_compCache.path(name, len); pItem != nullptr)
3122 return pItem;
3123 if (!isSymbol) {
3124 if (const auto* pItem = m_compCache.short_symbol(name, len); pItem != nullptr)
3125 return pItem;
3126 }
3127 }
3128
3129 return nullptr;
3130 }
3131
3132 void add_comp_exact_hits_inter(
3133 cnt::darray<Entity>& out, const char* name, uint32_t len, bool isPath, bool isSymbol) const {
3134 if (const auto* pItem = m_compCache.symbol(name, len); pItem != nullptr)
3135 ComponentCache::push_unique_entity(out, pItem->entity);
3136
3137 m_compCache.add_path_matches(out, util::str_view(name, len));
3138
3139 if (out.empty() && !isPath && !isSymbol) {
3140 if (const auto* pItem = m_compCache.short_symbol(name, len); pItem != nullptr)
3141 ComponentCache::push_unique_entity(out, pItem->entity);
3142 }
3143 }
3144
3145 GAIA_NODISCARD bool has_comp_lookup_ctx_inter() const noexcept {
3146 return m_componentScope != EntityBad || !m_componentLookupPath.empty();
3147 }
3148
3149 GAIA_NODISCARD static bool is_unqualified_comp_name_inter(const char* name, uint32_t len) noexcept {
3150 return memchr(name, '.', len) == nullptr && memchr(name, ':', len) == nullptr;
3151 }
3152
3153 GAIA_NODISCARD Entity pick_name_or_comp_inter(Entity namedEntity, const ComponentCacheItem* pCompItem) const {
3154 if (pCompItem == nullptr)
3155 return namedEntity;
3156
3157 if (namedEntity == EntityBad)
3158 return pCompItem->entity;
3159
3160 return m_compCache.find(namedEntity) != nullptr ? pCompItem->entity : namedEntity;
3161 }
3162
3170 GAIA_NODISCARD const ComponentCacheItem* resolve_component_name_inter(const char* name, uint32_t len = 0) const {
3171 GAIA_ASSERT(name != nullptr);
3172
3173 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
3174 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
3175 const bool isPath = memchr(name, '.', l) != nullptr;
3176 const bool isSymbol = memchr(name, ':', l) != nullptr;
3177
3178 if (isPath) {
3179 if (const auto* pItem = m_compCache.path(name, l); pItem != nullptr)
3180 return pItem;
3181 }
3182
3183 if (!isPath && !isSymbol) {
3184 if (const auto* pItem = find_comp_lookup_inter(name, l); pItem != nullptr)
3185 return pItem;
3186 }
3187
3188 if (const auto* pItem = find_comp_exact_inter(name, l, isPath, isSymbol); pItem != nullptr)
3189 return pItem;
3190
3191 const auto aliasEntity = alias(name, l);
3192 return aliasEntity != EntityBad ? m_compCache.find(aliasEntity) : nullptr;
3193 }
3194
3195 public:
3196 //----------------------------------------------------------------------
3197
3201 GAIA_NODISCARD bool valid(Entity entity) const {
3202 return entity.pair() //
3203 ? valid_pair(entity)
3204 : valid_entity(entity);
3205 }
3206
3207 //----------------------------------------------------------------------
3208
3212 GAIA_NODISCARD Entity get(EntityId id) const {
3213 // Cleanup, observer propagation, and wildcard expansion can briefly encounter stale ids.
3214 // Treat those as absent instead of crashing the world.
3215 if (!valid_entity_id(id))
3216 return EntityBad;
3217
3218 const auto& ec = m_recs.entities[id];
3219 return Entity(id, ec.data.gen, (bool)ec.data.ent, (bool)ec.data.pair, (EntityKind)ec.data.kind);
3220 }
3221
3223 GAIA_NODISCARD Entity try_get(EntityId id) const {
3224 return valid_entity_id(id) ? get(id) : EntityBad;
3225 }
3226
3230 template <typename T>
3231 GAIA_NODISCARD Entity get() const {
3232 return comp_cache().get<T>().entity;
3233 }
3234
3238 template <typename T>
3239 GAIA_NODISCARD const ComponentCacheItem& reg_comp() {
3240#if GAIA_ECS_AUTO_COMPONENT_REGISTRATION
3241 return add<T>();
3242#else
3243 return comp_cache().template get<T>();
3244#endif
3245 }
3246
3247 //----------------------------------------------------------------------
3248
3254 return EntityBuilder(*this, entity);
3255 }
3256
3260 GAIA_NODISCARD Entity add(EntityKind kind = EntityKind::EK_Gen) {
3261 return add(*m_pEntityArchetype, true, false, kind);
3262 }
3263
3267 GAIA_NODISCARD Entity prefab(EntityKind kind = EntityKind::EK_Gen) {
3268 const auto entity = add(kind);
3269 add(entity, Prefab);
3270 return entity;
3271 }
3272
3276 template <typename Func = TFunc_Void_With_Entity>
3277 void add_n(uint32_t count, Func func = func_void_with_entity) {
3278 add_entity_n(*m_pEntityArchetype, count, func);
3279 }
3280
3286 template <typename Func = TFunc_Void_With_Entity>
3287 void add_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
3288 auto& ec = m_recs.entities[entity.id()];
3289
3290 GAIA_ASSERT(ec.pArchetype != nullptr);
3291 GAIA_ASSERT(ec.pChunk != nullptr);
3292
3293 add_entity_n(*ec.pArchetype, count, func);
3294 }
3295
3299 template <typename T>
3300 GAIA_NODISCARD const ComponentCacheItem& add() {
3301 static_assert(!is_pair<T>::value, "Pairs can't be registered as components");
3302
3303 using CT = component_type_t<T>;
3304 using FT = typename CT::TypeFull;
3305 constexpr auto kind = CT::Kind;
3306
3307 const auto* pItem = comp_cache().find<FT>();
3308 if (pItem != nullptr)
3309 return *pItem;
3310
3311 const auto entity = add(*m_pCompArchetype, false, false, kind);
3312 util::str scopePath;
3313 (void)current_scope_path(scopePath);
3314
3315 const auto& item = comp_cache_mut().add<FT>(entity, scopePath.view());
3316 finalize_component_registration(item, false);
3317#if GAIA_ECS_AUTO_COMPONENT_FIELDS
3318 auto_populate_component_fields<FT>(comp_cache_mut().get(item.entity));
3319#endif
3320
3321 return item;
3322 }
3323
3328 GAIA_NODISCARD const ComponentCacheItem&
3329 add(const ComponentCacheItem::ComponentCacheItemCtx& item, EntityKind kind = EntityKind::EK_Gen) {
3330 GAIA_ASSERT(!item.name.empty());
3331 GAIA_ASSERT(item.name.size() < ComponentCacheItem::MaxNameLength);
3332
3333 if (const auto* pItem = comp_cache().symbol(item.name); pItem != nullptr)
3334 return *pItem;
3335
3336 const auto entity = add(*m_pCompArchetype, false, false, kind);
3337 util::str scopePath;
3338 (void)current_scope_path(scopePath);
3339 const auto& itemInfo = comp_cache_mut().add(entity, item, scopePath.view());
3340 finalize_component_registration(itemInfo, true);
3341 return itemInfo;
3342 }
3343
3348 void add(Entity entity, Entity object) {
3349#if GAIA_ASSERT_ENABLED
3350 if (!object.pair()) {
3351 const auto* pItem = comp_cache().find(object);
3352 if (pItem != nullptr && pItem->entity == object && is_out_of_line_component(object))
3353 GAIA_ASSERT2(
3354 false, "Out-of-line runtime components require an explicit typed value when added by entity id");
3355 }
3356#endif
3357 EntityBuilder(*this, entity).add(object);
3358 }
3359
3365 void add(Entity entity, Pair pair) {
3366 EntityBuilder(*this, entity).add(pair);
3367 }
3368
3374 template <typename T>
3375 void add(Entity entity) {
3376 using FT = typename component_type_t<T>::TypeFull;
3377 const auto& item = add<FT>();
3378 if constexpr (supports_out_of_line_component<FT>()) {
3379 const auto mode = out_of_line_mode(item.entity);
3380 if (mode != OutOfLineMode::None) {
3381 (void)sparse_component_store_mut<FT>(item.entity).add(entity);
3382 finish_out_of_line_add_inter(entity, item.entity, mode);
3383 return;
3384 }
3385 }
3386
3387 EntityBuilder(*this, entity).add<T>();
3388 }
3389
3397 template <typename T>
3398 void add(Entity entity, Entity object, T&& value) {
3399 static_assert(core::is_raw_v<T>);
3400
3401 if constexpr (supports_out_of_line_component<typename component_type_t<T>::TypeFull>()) {
3402 using FT = typename component_type_t<T>::TypeFull;
3403 if (can_use_out_of_line_component<FT>(object)) {
3404 const auto mode = out_of_line_mode(object);
3405 if (mode != OutOfLineMode::None) {
3406 auto& data = sparse_component_store_mut<FT>(object).add(entity);
3407 data = GAIA_FWD(value);
3408 finish_out_of_line_add_inter(entity, object, mode);
3409 return;
3410 }
3411 }
3412 }
3413
3414 EntityBuilder eb(*this, entity);
3415#if GAIA_OBSERVERS_ENABLED
3416 auto addDiffCtx =
3417 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
3418#endif
3419 eb.add_inter_init(object);
3420 eb.commit();
3421
3422 const auto& ec = fetch(entity);
3423 // Make sure the idx is 0 for unique entities
3424 const auto idx = uint16_t(ec.row * (1U - (uint32_t)object.kind()));
3425 ComponentSetter{*this, ec.pChunk, entity, idx}.sset(object, GAIA_FWD(value));
3426 notify_add_single(entity, object);
3427#if GAIA_OBSERVERS_ENABLED
3428 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
3429#endif
3430 }
3431
3438 template <typename T, typename U = typename actual_type_t<T>::Type>
3439 void add(Entity entity, U&& value) {
3440 using FT = typename component_type_t<T>::TypeFull;
3441 if constexpr (!is_pair<FT>::value && supports_out_of_line_component<FT>()) {
3442 const auto& item = add<FT>();
3443 const auto mode = out_of_line_mode(item.entity);
3444 if (mode != OutOfLineMode::None) {
3445 auto& data = sparse_component_store_mut<FT>(item.entity).add(entity);
3446 data = GAIA_FWD(value);
3447 finish_out_of_line_add_inter(entity, item.entity, mode);
3448 return;
3449 }
3450 }
3451
3452 EntityBuilder builder(*this, entity);
3453 auto object = builder.register_component<T>();
3454#if GAIA_OBSERVERS_ENABLED
3455 auto addDiffCtx =
3456 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
3457#endif
3458 // Materialize the component first, write the initial value, and only then dispatch OnAdd.
3459 // This keeps observer-visible state aligned with the final stored payload.
3460 builder.add_inter_init(object);
3461 builder.commit();
3462
3463 const auto& ec = m_recs.entities[entity.id()];
3464 // Make sure the idx is 0 for unique entities
3465 const auto idx = uint16_t(ec.row * (1U - (uint32_t)object.kind()));
3466 ComponentSetter{*this, ec.pChunk, entity, idx}.sset<T>(GAIA_FWD(value));
3467 notify_add_single(entity, object);
3468#if GAIA_OBSERVERS_ENABLED
3469 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
3470#endif
3471 }
3472
3475 GAIA_NODISCARD bool override(Entity entity, Entity object) {
3476 return override_inter(entity, object);
3477 }
3478
3481 GAIA_NODISCARD bool override(Entity entity, Pair pair) {
3482 return override_inter(entity, (Entity)pair);
3483 }
3484
3487 template <typename T>
3488 GAIA_NODISCARD bool override(Entity entity) {
3489 static_assert(!is_pair<T>::value);
3490 using FT = typename component_type_t<T>::TypeFull;
3491 const auto& item = add<FT>();
3492
3493 if constexpr (supports_out_of_line_component<FT>()) {
3494 if (out_of_line_mode(item.entity) != OutOfLineMode::None)
3495 return override_out_of_line_inter(entity, item.entity);
3496 }
3497
3498 return override_inter(entity, item.entity);
3499 }
3500
3503 template <typename T>
3504 GAIA_NODISCARD bool override(Entity entity, Entity object) {
3505 static_assert(!is_pair<T>::value);
3506 using FT = typename component_type_t<T>::TypeFull;
3507
3508 if constexpr (supports_out_of_line_component<FT>()) {
3509 if (can_use_out_of_line_component<FT>(object))
3510 return override_out_of_line_inter(entity, object);
3511 }
3512
3513 return override_inter(entity, object);
3514 }
3515
3516 //----------------------------------------------------------------------
3517
3522 void clear(Entity entity) {
3523 GAIA_ASSERT(!entity.pair());
3524 GAIA_ASSERT(valid(entity));
3525
3526 EntityBuilder eb(*this, entity);
3527
3528 // Remove back to front because it's better for the archetype graph
3529 auto ids = eb.m_pArchetype->ids_view();
3530 for (uint32_t i = (uint32_t)ids.size() - 1; i != (uint32_t)-1; --i)
3531 eb.del(ids[i]);
3532
3533 eb.commit();
3534 }
3535
3536 //----------------------------------------------------------------------
3537
3545 GAIA_NODISCARD Entity copy(Entity srcEntity) {
3546 GAIA_ASSERT(!srcEntity.pair());
3547 GAIA_ASSERT(valid(srcEntity));
3548
3549 auto& ec = m_recs.entities[srcEntity.id()];
3550 GAIA_ASSERT(ec.pArchetype != nullptr);
3551 GAIA_ASSERT(ec.pChunk != nullptr);
3552
3553 auto* pDstArchetype = ec.pArchetype;
3554 Entity dstEntity;
3555
3556 // Names have to be unique so if we see that EntityDesc is present during copy
3557 // we navigate towards a version of the archetype without the EntityDesc.
3558 if (pDstArchetype->has<EntityDesc>()) {
3559 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
3560
3561 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
3562 auto& ecDst = m_recs.entities[dstEntity.id()];
3563 Chunk::copy_foreign_entity_data(ec.pChunk, ec.row, ecDst.pChunk, ecDst.row);
3564 } else {
3565 // No description associated with the entity, direct copy is possible
3566 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
3567 Chunk::copy_entity_data(srcEntity, dstEntity, m_recs);
3568 }
3569
3570 copy_all_sparse_entity_data(srcEntity, dstEntity);
3571
3572 return dstEntity;
3573 }
3574
3584 template <typename Func = TFunc_Void_With_Entity>
3585 void copy_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
3586 copy_n_inter(entity, count, func, EntitySpan{});
3587 }
3588
3589#if GAIA_OBSERVERS_ENABLED
3597 GAIA_NODISCARD Entity copy_ext(Entity srcEntity) {
3598 GAIA_ASSERT(!srcEntity.pair());
3599 GAIA_ASSERT(valid(srcEntity));
3600
3601 auto& ec = m_recs.entities[srcEntity.id()];
3602 GAIA_ASSERT(ec.pArchetype != nullptr);
3603 GAIA_ASSERT(ec.pChunk != nullptr);
3604
3605 auto* pDstArchetype = ec.pArchetype;
3606 // Names have to be unique so if we see that EntityDesc is present during copy
3607 // we navigate towards a version of the archetype without the EntityDesc.
3608 const bool hasEntityDesc = pDstArchetype->has<EntityDesc>();
3609 if (hasEntityDesc)
3610 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
3611
3612 const auto archetypeIdCount = (uint32_t)pDstArchetype->ids_view().size();
3613 const auto sparseIdCount = copied_non_frag_sparse_id_count(srcEntity);
3614 const auto addedIdCount = archetypeIdCount + sparseIdCount;
3615 auto* pAddedIds = addedIdCount != 0U ? (Entity*)alloca(sizeof(Entity) * addedIdCount) : nullptr;
3616 write_archetype_ids(*pDstArchetype, pAddedIds);
3617 write_copied_non_frag_sparse_ids(srcEntity, pAddedIds + archetypeIdCount);
3618 #if GAIA_OBSERVERS_ENABLED
3619 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{pAddedIds, addedIdCount});
3620 #endif
3621
3622 Entity dstEntity;
3623 if (hasEntityDesc) {
3624 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
3625 auto& ecDst = m_recs.entities[dstEntity.id()];
3626 Chunk::copy_foreign_entity_data(ec.pChunk, ec.row, ecDst.pChunk, ecDst.row);
3627 } else {
3628 // No description associated with the entity, direct copy is possible
3629 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
3630 Chunk::copy_entity_data(srcEntity, dstEntity, m_recs);
3631 }
3632
3633 (void)copy_all_sparse_entity_data(srcEntity, dstEntity);
3634 m_observers.add_diff_targets(*this, addDiffCtx, EntitySpan{&dstEntity, 1});
3635
3636 m_observers.on_add(*this, *pDstArchetype, EntitySpan{pAddedIds, addedIdCount}, EntitySpan{&dstEntity, 1});
3637 #if GAIA_OBSERVERS_ENABLED
3638 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
3639 #endif
3640
3641 return dstEntity;
3642 }
3643
3653 template <typename Func = TFunc_Void_With_Entity>
3654 void copy_ext_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
3655 auto& ec = m_recs.entities[entity.id()];
3656 auto* pDstArchetype = ec.pArchetype;
3657 if (pDstArchetype->has<EntityDesc>())
3658 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
3659
3660 const auto archetypeIdCount = (uint32_t)pDstArchetype->ids_view().size();
3661 const auto sparseIdCount = copied_non_frag_sparse_id_count(entity);
3662 const auto addedIdCount = archetypeIdCount + sparseIdCount;
3663 auto* pAddedIds = addedIdCount != 0U ? (Entity*)alloca(sizeof(Entity) * addedIdCount) : nullptr;
3664 write_archetype_ids(*pDstArchetype, pAddedIds);
3665 write_copied_non_frag_sparse_ids(entity, pAddedIds + archetypeIdCount);
3666 #if GAIA_OBSERVERS_ENABLED
3667 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{pAddedIds, addedIdCount});
3668 #endif
3669 copy_n_inter(
3670 entity, count, func, EntitySpan{pAddedIds, addedIdCount}, EntityBad
3671 #if GAIA_OBSERVERS_ENABLED
3672 ,
3673 &addDiffCtx
3674 #endif
3675 );
3676 #if GAIA_OBSERVERS_ENABLED
3677 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
3678 #endif
3679 }
3680#endif
3681
3682 private:
3683 struct CopyIterGroupState {
3684 Archetype* pArchetype = nullptr;
3685 Chunk* pChunk = nullptr;
3686 uint16_t startRow = 0;
3687 uint16_t count = 0;
3688 };
3689
3690 struct PrefabInstantiatePlanNode {
3691 Entity prefab = EntityBad;
3692 uint32_t parentIdx = BadIndex;
3693 Archetype* pDstArchetype = nullptr;
3694 cnt::darray_ext<Entity, 16> copiedSparseIds;
3695 cnt::darray_ext<Entity, 16> addedIds;
3696 cnt::darray_ext<Entity, 16> addHookIds;
3697 };
3698
3699 template <typename Func>
3700 void invoke_copy_batch_callback(
3701 Func& func, Archetype* pDstArchetype, Chunk* pDstChunk, uint32_t originalChunkSize, uint32_t toCreate) {
3702 if constexpr (std::is_invocable_v<Func, CopyIter&>) {
3703 CopyIter it;
3704 it.set_world(this);
3705 it.set_archetype(pDstArchetype);
3706 it.set_chunk(pDstChunk);
3707 it.set_range((uint16_t)originalChunkSize, (uint16_t)toCreate);
3708 func(it);
3709 } else {
3710 auto entities = pDstChunk->entity_view();
3711 GAIA_FOR2(originalChunkSize, pDstChunk->size()) func(entities[i]);
3712 }
3713 }
3714
3715 template <typename Func>
3716 void flush_copy_iter_group(Func& func, CopyIterGroupState& group) {
3717 if (group.count == 0)
3718 return;
3719
3720 CopyIter it;
3721 it.set_world(this);
3722 it.set_archetype(group.pArchetype);
3723 it.set_chunk(group.pChunk);
3724 it.set_range(group.startRow, group.count);
3725 func(it);
3726 group.count = 0;
3727 }
3728
3729 template <typename Func>
3730 void push_copy_iter_group(Func& func, CopyIterGroupState& group, Entity instance) {
3731 const auto& ec = fetch(instance);
3732
3733 if (group.count != 0 && ec.pArchetype == group.pArchetype && ec.pChunk == group.pChunk &&
3734 ec.row == uint16_t(group.startRow + group.count)) {
3735 ++group.count;
3736 return;
3737 }
3738
3739 flush_copy_iter_group(func, group);
3740 group.pArchetype = ec.pArchetype;
3741 group.pChunk = ec.pChunk;
3742 group.startRow = ec.row;
3743 group.count = 1;
3744 }
3745
3746 void prepare_parent_batch(Entity parentEntity) {
3747 GAIA_ASSERT(valid(parentEntity));
3748
3749 const auto parentPair = Pair(Parent, parentEntity);
3750 assign_pair(parentPair, *m_pEntityArchetype);
3751
3752 touch_rel_version(Parent);
3753 invalidate_queries_for_rel(Parent);
3754 m_targetsTravCache = {};
3755 m_srcBfsTravCache = {};
3756 m_depthOrderCache = {};
3757 m_sourcesAllCache = {};
3758 m_targetsAllCache = {};
3759
3760 auto& ecParent = fetch(parentEntity);
3761 EntityBuilder::set_flag(ecParent.flags, EntityContainerFlags::OnDeleteTarget_Delete, true);
3762 }
3763
3764 void parent_batch(
3765 Entity parentEntity, Archetype& archetype, Chunk& chunk, uint32_t originalChunkSize, uint32_t toCreate) {
3766 GAIA_ASSERT(valid(parentEntity));
3767
3768 if (toCreate == 0)
3769 return;
3770
3771 auto entities = chunk.entity_view();
3772#if GAIA_OBSERVERS_ENABLED
3773 const Entity parentPair = Pair(Parent, parentEntity);
3774 auto addDiffCtx = m_observers.prepare_diff(
3775 *this, ObserverEvent::OnAdd, EntitySpan{&parentPair, 1},
3776 EntitySpan{entities.data() + originalChunkSize, toCreate});
3777#endif
3778 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
3779 exclusive_adjunct_set(entities[rowIdx], Parent, parentEntity);
3780 }
3781
3782#if GAIA_OBSERVERS_ENABLED
3783 m_observers.on_add(
3784 *this, archetype, EntitySpan{&parentPair, 1}, EntitySpan{entities.data() + originalChunkSize, toCreate});
3785 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
3786#endif
3787 }
3788
3789 void parent_direct(Entity entity, Entity parentEntity) {
3790 GAIA_ASSERT(valid(entity));
3791 GAIA_ASSERT(valid(parentEntity));
3792
3793 prepare_parent_batch(parentEntity);
3794#if GAIA_OBSERVERS_ENABLED
3795 const Entity parentPair = Pair(Parent, parentEntity);
3796 auto addDiffCtx =
3797 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&parentPair, 1}, EntitySpan{&entity, 1});
3798#endif
3799 exclusive_adjunct_set(entity, Parent, parentEntity);
3800
3801#if GAIA_OBSERVERS_ENABLED
3802 const auto& ec = fetch(entity);
3803 m_observers.on_add(*this, *ec.pArchetype, EntitySpan{&parentPair, 1}, EntitySpan{&entity, 1});
3804 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
3805#endif
3806 }
3807
3808 void notify_add_single(Entity entity, Entity object) {
3809#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
3810 if GAIA_UNLIKELY (tearing_down())
3811 return;
3812
3813 const auto& ec = fetch(entity);
3814
3815 lock();
3816
3817 #if GAIA_ENABLE_ADD_DEL_HOOKS
3818 if (object.comp()) {
3819 const auto& item = comp_cache().get(object);
3820 const auto& hooks = ComponentCache::hooks(item);
3821 if (hooks.func_add != nullptr)
3822 hooks.func_add(*this, item, entity);
3823 }
3824 #endif
3825
3826 #if GAIA_OBSERVERS_ENABLED
3827 m_observers.on_add(*this, *ec.pArchetype, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
3828 #endif
3829
3830 unlock();
3831#else
3832 (void)entity;
3833 (void)object;
3834#endif
3835 }
3836
3837 void notify_del_single(Entity entity, Entity object) {
3838#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
3839 if GAIA_UNLIKELY (tearing_down())
3840 return;
3841
3842 const auto& ec = fetch(entity);
3843
3844 lock();
3845
3846 #if GAIA_OBSERVERS_ENABLED
3847 m_observers.on_del(*this, *ec.pArchetype, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
3848 #endif
3849
3850 #if GAIA_ENABLE_ADD_DEL_HOOKS
3851 if (object.comp()) {
3852 const auto& item = comp_cache().get(object);
3853 const auto& hooks = ComponentCache::hooks(item);
3854 if (hooks.func_del != nullptr)
3855 hooks.func_del(*this, item, entity);
3856 }
3857 #endif
3858
3859 unlock();
3860#else
3861 (void)entity;
3862 (void)object;
3863#endif
3864 }
3865
3866 GAIA_NODISCARD bool has_semantic_match_without_source(
3867 Entity entity, Entity object, Entity excludedSource, cnt::set<EntityLookupKey>& visited) const {
3868 const auto inserted = visited.insert(EntityLookupKey(entity));
3869 if (!inserted.second)
3870 return false;
3871
3872 if (entity != excludedSource && has_direct(entity, object))
3873 return true;
3874
3875 const auto it = m_entityToAsTargets.find(EntityLookupKey(entity));
3876 if (it == m_entityToAsTargets.end())
3877 return false;
3878
3879 for (const auto baseKey: it->second) {
3880 if (has_semantic_match_without_source(baseKey.entity(), object, excludedSource, visited))
3881 return true;
3882 }
3883
3884 return false;
3885 }
3886
3887 void notify_inherited_del_dependents(Entity source, Entity object) {
3888#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
3889 const auto& descendants = as_relations_trav_cache(source);
3890 for (const auto descendant: descendants) {
3891 if (descendant == source)
3892 continue;
3893 if (has_direct(descendant, object) || !has(descendant, object))
3894 continue;
3895
3896 cnt::set<EntityLookupKey> visited;
3897 if (has_semantic_match_without_source(descendant, object, source, visited))
3898 continue;
3899
3900 notify_del_single(descendant, object);
3901 }
3902#else
3903 (void)source;
3904 (void)object;
3905#endif
3906 }
3907
3908 void notify_inherited_del_dependents(Entity source, EntitySpan removedObjects) {
3909 for (const auto object: removedObjects)
3910 notify_inherited_del_dependents(source, object);
3911 }
3912
3913 template <typename Func>
3914 void copy_n_inter(
3915 Entity entity, uint32_t count, Func& func, EntitySpan addedIds, Entity parentInstance = EntityBad
3916#if GAIA_OBSERVERS_ENABLED
3917 ,
3918 ObserverRegistry::DiffDispatchCtx* pAddDiffCtx = nullptr
3919#endif
3920 ) {
3921 GAIA_ASSERT(!entity.pair());
3922 GAIA_ASSERT(valid(entity));
3923 GAIA_ASSERT(parentInstance == EntityBad || valid(parentInstance));
3924
3925 if (count == 0U)
3926 return;
3927
3928#if GAIA_OBSERVERS_ENABLED
3929 const bool useLocalAddDiff = !addedIds.empty() && pAddDiffCtx == nullptr;
3930 ObserverRegistry::DiffDispatchCtx addDiffCtx{};
3931 if (useLocalAddDiff)
3932 addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{addedIds});
3933#endif
3934
3935 auto& ec = m_recs.entities[entity.id()];
3936
3937 GAIA_ASSERT(ec.pChunk != nullptr);
3938 GAIA_ASSERT(ec.pArchetype != nullptr);
3939
3940 auto* pSrcChunk = ec.pChunk;
3941 auto* pDstArchetype = ec.pArchetype;
3942 const auto hasEntityDesc = pDstArchetype->has<EntityDesc>();
3943 if (hasEntityDesc)
3944 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
3945
3946 if (parentInstance != EntityBad)
3947 prepare_parent_batch(parentInstance);
3948
3949 // Entities array might get reallocated after m_recs.entities.alloc
3950 // so instead of fetching the container again we simply cache the row
3951 // of our source entity.
3952 const auto srcRow = ec.row;
3953
3954 EntityContainerCtx ctx{true, false, EntityKind::EK_Gen};
3955
3956 uint32_t left = count;
3957 do {
3958 auto* pDstChunk = pDstArchetype->foc_free_chunk();
3959 const uint32_t originalChunkSize = pDstChunk->size();
3960 const uint32_t freeSlotsInChunk = pDstChunk->capacity() - originalChunkSize;
3961 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
3962
3963 GAIA_FOR(toCreate) {
3964 const auto entityNew = m_recs.entities.alloc(&ctx);
3965 auto& ecNew = m_recs.entities[entityNew.id()];
3966 store_entity(ecNew, entityNew, pDstArchetype, pDstChunk);
3967
3968#if GAIA_ASSERT_ENABLED
3969 GAIA_ASSERT(ecNew.pChunk == pDstChunk);
3970 auto entityExpected = pDstChunk->entity_view()[ecNew.row];
3971 GAIA_ASSERT(entityExpected == entityNew);
3972#endif
3973
3974 if (hasEntityDesc) {
3975 Chunk::copy_foreign_entity_data(pSrcChunk, srcRow, pDstChunk, ecNew.row);
3976 }
3977
3978 copy_all_sparse_entity_data(entity, entityNew);
3979 }
3980
3981 pDstArchetype->try_update_free_chunk_idx();
3982
3983 if (!hasEntityDesc) {
3984 pDstChunk->call_gen_ctors(originalChunkSize, toCreate);
3985
3986 {
3987 GAIA_PROF_SCOPE(World::copy_n_entity_data);
3988 Chunk::copy_entity_data_n_same_chunk(pSrcChunk, srcRow, pDstChunk, originalChunkSize, toCreate);
3989 }
3990 }
3991
3992 pDstChunk->update_versions();
3993
3994#if GAIA_OBSERVERS_ENABLED
3995 if (!addedIds.empty()) {
3996 auto entities = pDstChunk->entity_view();
3997 if (pAddDiffCtx != nullptr)
3998 m_observers.add_diff_targets(
3999 *this, *pAddDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
4000 else if (useLocalAddDiff)
4001 m_observers.add_diff_targets(
4002 *this, addDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
4003 m_observers.on_add(
4004 *this, *pDstArchetype, addedIds, EntitySpan{entities.data() + originalChunkSize, toCreate});
4005 }
4006#endif
4007
4008 if (parentInstance != EntityBad)
4009 parent_batch(parentInstance, *pDstArchetype, *pDstChunk, originalChunkSize, toCreate);
4010
4011 invoke_copy_batch_callback(func, pDstArchetype, pDstChunk, originalChunkSize, toCreate);
4012
4013 left -= toCreate;
4014 } while (left > 0);
4015#if GAIA_OBSERVERS_ENABLED
4016 if (useLocalAddDiff)
4017 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4018#endif
4019 }
4020
4021 GAIA_NODISCARD bool id_uses_inherit_policy(Entity id) const {
4022 return !is_wildcard(id) && valid(id) && target(id, OnInstantiate) == Inherit;
4023 }
4024
4025 GAIA_NODISCARD Entity inherited_id_owner(Entity entity, Entity id) const {
4026 if (!id_uses_inherit_policy(id))
4027 return EntityBad;
4028
4029 const auto& targets = as_targets_trav_cache(entity);
4030 for (const auto target: targets) {
4031 if (has_inter(target, id, false))
4032 return target;
4033 }
4034
4035 return EntityBad;
4036 }
4037
4042 GAIA_NODISCARD bool has_direct_out_of_line_inter(Entity entity, Entity object) const {
4043 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
4044 return itSparseStore != m_sparseComponentsByComp.end() &&
4045 itSparseStore->second.func_has(itSparseStore->second.pStore, entity);
4046 }
4047
4054 GAIA_NODISCARD Entity id_owner_inter(Entity entity, Entity object) const {
4055 GAIA_ASSERT(valid(entity));
4056 GAIA_ASSERT(object != EntityBad);
4057 GAIA_ASSERT(!is_wildcard(object));
4058
4059 const auto& ec = fetch(entity);
4060 if (is_req_del(ec))
4061 return EntityBad;
4062
4063 if (object.pair()) {
4064 if (has_exclusive_adjunct_pair(entity, object) || ec.pArchetype->has(object))
4065 return entity;
4066 } else {
4067 if (has_direct_out_of_line_inter(entity, object) || ec.pArchetype->has(object))
4068 return entity;
4069 }
4070
4071 return inherited_id_owner(entity, object);
4072 }
4073
4074 GAIA_NODISCARD bool instantiate_copies_id(Entity id) const {
4075 const auto policy = target(id, OnInstantiate);
4076 if (policy == EntityBad || policy == Override)
4077 return true;
4078 if (policy == DontInherit || policy == Inherit)
4079 return false;
4080 return true;
4081 }
4082
4083 template <typename T>
4084 void gather_sorted_prefab_children(Entity prefabEntity, T& outChildren) {
4085 sources(Parent, prefabEntity, [&](Entity childPrefab) {
4086 if (!has_direct(childPrefab, Prefab))
4087 return;
4088 outChildren.push_back(childPrefab);
4089 });
4090
4091 core::sort(outChildren, [](Entity left, Entity right) {
4092 return left.id() < right.id();
4093 });
4094 }
4095
4096 GAIA_NODISCARD bool copy_sparse_store_inter(
4097 Entity srcEntity, Entity dstEntity, Entity comp, const SparseComponentStoreErased& store) {
4098 if (!copies_sparse_payload_inter(comp, srcEntity, store))
4099 return false;
4100
4101 GAIA_ASSERT(store.func_copy_entity != nullptr);
4102 return store.func_copy_entity(store.pStore, dstEntity, srcEntity);
4103 }
4104
4105 uint32_t copy_all_sparse_entity_data(Entity srcEntity, Entity dstEntity, Entity* pCopiedIds = nullptr) {
4106 uint32_t copiedCnt = 0;
4107 for (auto& [compKey, store]: m_sparseComponentsByComp) {
4108 const auto comp = compKey.entity();
4109 if (!copy_sparse_store_inter(srcEntity, dstEntity, comp, store))
4110 continue;
4111
4112 if (pCopiedIds != nullptr)
4113 pCopiedIds[copiedCnt] = comp;
4114 ++copiedCnt;
4115 }
4116
4117 return copiedCnt;
4118 }
4119
4120 uint32_t copy_sparse_entity_data(
4121 Entity srcEntity, Entity dstEntity, EntitySpan copiedSparseIds, Entity* pCopiedIds = nullptr) {
4122 uint32_t copiedCnt = 0;
4123 for (const auto comp: copiedSparseIds) {
4124 auto it = m_sparseComponentsByComp.find(EntityLookupKey(comp));
4125 GAIA_ASSERT(it != m_sparseComponentsByComp.end());
4126
4127 auto& store = it->second;
4128 if (!copy_sparse_store_inter(srcEntity, dstEntity, comp, store))
4129 continue;
4130
4131 if (pCopiedIds != nullptr)
4132 pCopiedIds[copiedCnt] = comp;
4133 ++copiedCnt;
4134 }
4135
4136 return copiedCnt;
4137 }
4138
4139 void write_archetype_ids(const Archetype& dstArchetype, Entity* pDst) const {
4140 for (const auto id: dstArchetype.ids_view())
4141 *pDst++ = id;
4142 }
4143
4144 GAIA_NODISCARD uint32_t copied_non_frag_sparse_id_count(Entity srcEntity) const {
4145 uint32_t count = 0;
4146 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
4147 const auto comp = compKey.entity();
4148 if (!copies_non_frag_sparse_payload_inter(comp, srcEntity, store))
4149 continue;
4150 ++count;
4151 }
4152
4153 return count;
4154 }
4155
4156 void write_copied_non_frag_sparse_ids(Entity srcEntity, Entity* pDst) const {
4157 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
4158 const auto comp = compKey.entity();
4159 if (!copies_non_frag_sparse_payload_inter(comp, srcEntity, store))
4160 continue;
4161 *pDst++ = comp;
4162 }
4163 }
4164
4165 GAIA_NODISCARD bool copy_sparse_payload_inter(Entity dstEntity, Entity srcEntity, Entity object) {
4166 const auto mode = out_of_line_mode(object);
4167 if (mode == OutOfLineMode::None)
4168 return false;
4169
4170 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
4171 if (itSparseStore == m_sparseComponentsByComp.end())
4172 return false;
4173
4174 return copy_sparse_store_inter(srcEntity, dstEntity, object, itSparseStore->second);
4175 }
4176
4181 GAIA_NODISCARD bool override_out_of_line_inter(Entity entity, Entity object) {
4182 GAIA_ASSERT(valid(entity));
4183 GAIA_ASSERT(valid(object));
4184 GAIA_ASSERT(out_of_line_mode(object) != OutOfLineMode::None);
4185
4186 if (has_direct(entity, object))
4187 return false;
4188
4189 const auto inheritedOwner = inherited_id_owner(entity, object);
4190 if (inheritedOwner == EntityBad)
4191 return false;
4192
4193 if (!copy_sparse_payload_inter(entity, inheritedOwner, object))
4194 return false;
4195
4196 if (out_of_line_mode(object) == OutOfLineMode::Fragmenting)
4197 make_sparse_copy_direct_inter(entity, object);
4198 return true;
4199 }
4200
4207 GAIA_NODISCARD bool copy_owned_out_of_line_inter(Entity srcEntity, Entity dstEntity, Entity object) {
4208 GAIA_ASSERT(valid(srcEntity));
4209 GAIA_ASSERT(valid(dstEntity));
4210 GAIA_ASSERT(valid(object));
4211 GAIA_ASSERT(out_of_line_mode(object) != OutOfLineMode::None);
4212
4213 const auto mode = out_of_line_mode(object);
4214 if (mode == OutOfLineMode::Fragmenting) {
4215 if (!copy_sparse_payload_inter(dstEntity, srcEntity, object))
4216 return false;
4217
4218 make_sparse_copy_direct_inter(dstEntity, object);
4219 notify_add_single(dstEntity, object);
4220 return true;
4221 }
4222#if GAIA_OBSERVERS_ENABLED
4223 auto addDiffCtx =
4224 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&dstEntity, 1});
4225#endif
4226 if (!copy_sparse_payload_inter(dstEntity, srcEntity, object))
4227 return false;
4228 notify_add_single(dstEntity, object);
4229#if GAIA_OBSERVERS_ENABLED
4230 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4231#endif
4232 return true;
4233 }
4234
4235 void make_sparse_copy_direct_inter(Entity entity, Entity object) {
4236 GAIA_ASSERT(out_of_line_mode(object) == OutOfLineMode::Fragmenting);
4237 EntityBuilder eb(*this, entity);
4238 eb.add_inter_init(object);
4239 eb.commit();
4240 }
4241
4247 void copy_direct_component_data_inter(
4248 Entity srcEntity, Entity dstEntity, Entity object, const ComponentCacheItem& item) {
4249 GAIA_ASSERT(valid(srcEntity));
4250 GAIA_ASSERT(valid(dstEntity));
4251 GAIA_ASSERT(valid(object));
4252 GAIA_ASSERT(item.entity == object);
4253 GAIA_ASSERT(item.comp.size() != 0U);
4254
4255 const auto& ecDst = fetch(dstEntity);
4256 const auto& ecSrc = fetch(srcEntity);
4257 const auto compIdxDst = ecDst.pChunk->comp_idx(object);
4258 const auto compIdxSrc = ecSrc.pChunk->comp_idx(object);
4259 GAIA_ASSERT(compIdxDst != BadIndex && compIdxSrc != BadIndex);
4260
4261 const auto idxDst = uint16_t(ecDst.row * (1U - (uint32_t)object.kind()));
4262 const auto idxSrc = uint16_t(ecSrc.row * (1U - (uint32_t)object.kind()));
4263 void* pDst = ecDst.pChunk->comp_ptr_mut(compIdxDst);
4264 const void* pSrc = ecSrc.pChunk->comp_ptr(compIdxSrc);
4265 item.copy(pDst, pSrc, idxDst, idxSrc, ecDst.pChunk->capacity(), ecSrc.pChunk->capacity());
4266 }
4267
4268 GAIA_NODISCARD bool override_inter(Entity entity, Entity object) {
4269 GAIA_ASSERT(valid(entity));
4270 GAIA_ASSERT(object.pair() || valid(object));
4271
4272 if (has_direct(entity, object))
4273 return false;
4274
4275 const auto inheritedOwner = inherited_id_owner(entity, object);
4276 if (inheritedOwner == EntityBad)
4277 return false;
4278
4279 if (!object.pair()) {
4280 const auto* pItem = comp_cache().find(object);
4281 if (pItem != nullptr && pItem->entity == object) {
4282 const auto mode = out_of_line_mode(object);
4283 if (mode != OutOfLineMode::None)
4284 return override_out_of_line_inter(entity, object);
4285
4286 if (pItem->comp.size() != 0U) {
4287 add(entity, object);
4288 copy_direct_component_data_inter(inheritedOwner, entity, object, *pItem);
4289 return true;
4290 }
4291 }
4292 }
4293
4294 add(entity, object);
4295 return true;
4296 }
4297
4298 GAIA_NODISCARD bool copy_owned_id_from_entity(Entity srcEntity, Entity dstEntity, Entity object) {
4299 GAIA_ASSERT(valid(srcEntity));
4300 GAIA_ASSERT(valid(dstEntity));
4301 GAIA_ASSERT(object.pair() || valid(object));
4302
4303 if (has_direct(dstEntity, object))
4304 return false;
4305
4306 if (!object.pair()) {
4307 const auto* pItem = comp_cache().find(object);
4308 if (pItem != nullptr && pItem->entity == object) {
4309 if (out_of_line_mode(object) != OutOfLineMode::None)
4310 return copy_owned_out_of_line_inter(srcEntity, dstEntity, object);
4311
4312 if (pItem->comp.size() != 0U) {
4313 EntityBuilder eb(*this, dstEntity);
4314 eb.add_inter_init(object);
4315 eb.commit();
4316 copy_direct_component_data_inter(srcEntity, dstEntity, object, *pItem);
4317 notify_add_single(dstEntity, object);
4318 return true;
4319 }
4320 }
4321 }
4322
4323 add(dstEntity, object);
4324 return true;
4325 }
4326
4327 GAIA_NODISCARD Archetype* instantiate_prefab_dst_archetype(Entity prefabEntity) {
4328 GAIA_ASSERT(!prefabEntity.pair());
4329 GAIA_ASSERT(valid(prefabEntity));
4330 GAIA_ASSERT(has_direct(prefabEntity, Prefab));
4331
4332 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab))
4333 return fetch(prefabEntity).pArchetype;
4334
4335 auto& ecSrc = m_recs.entities[prefabEntity.id()];
4336 GAIA_ASSERT(ecSrc.pArchetype != nullptr);
4337
4338 auto* pDstArchetype = ecSrc.pArchetype;
4339 if (pDstArchetype->has<EntityDesc>())
4340 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4341 if (pDstArchetype->has(Prefab))
4342 pDstArchetype = foc_archetype_del(pDstArchetype, Prefab);
4343
4344 for (const auto id: ecSrc.pArchetype->ids_view()) {
4345 if (id.pair() && id.id() == Is.id()) {
4346 pDstArchetype = foc_archetype_del(pDstArchetype, id);
4347 continue;
4348 }
4349
4350 if (!instantiate_copies_id(id))
4351 pDstArchetype = foc_archetype_del(pDstArchetype, id);
4352 }
4353
4354 const auto isPair = Pair(Is, prefabEntity);
4355 assign_pair(isPair, *m_pEntityArchetype);
4356 pDstArchetype = foc_archetype_add(pDstArchetype, isPair);
4357
4358 return pDstArchetype;
4359 }
4360
4361 template <typename T>
4362 void collect_prefab_copied_sparse_ids(Entity prefabEntity, T& outCopiedSparseIds) {
4363 outCopiedSparseIds.clear();
4364 if (m_sparseComponentsByComp.empty())
4365 return;
4366
4367 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
4368 const auto comp = compKey.entity();
4369 if (!instantiate_copies_id(comp) || !copies_sparse_payload_inter(comp, prefabEntity, store))
4370 continue;
4371 outCopiedSparseIds.push_back(comp);
4372 }
4373 }
4374
4375 template <typename T>
4376 void collect_prefab_added_ids(Archetype* pDstArchetype, EntitySpan copiedSparseIds, T& outAddedIds) {
4377 outAddedIds.clear();
4378 for (const auto id: pDstArchetype->ids_view())
4379 outAddedIds.push_back(id);
4380
4381 for (const auto comp: copiedSparseIds) {
4382 if (sparse_copy_adds_id_inter(comp))
4383 outAddedIds.push_back(comp);
4384 }
4385 }
4386
4387 template <typename T>
4388 void collect_prefab_add_hook_ids(EntitySpan addedIds, T& outHookIds) {
4389 outHookIds.clear();
4390 for (const auto id: addedIds) {
4391 if (!id.comp())
4392 continue;
4393
4394 const auto& item = comp_cache().get(id);
4395 if (ComponentCache::hooks(item).func_add != nullptr)
4396 outHookIds.push_back(id);
4397 }
4398 }
4399
4400 GAIA_NODISCARD Entity instantiate_prefab_node_inter(
4401 Entity prefabEntity, Archetype* pDstArchetype, Entity parentInstance, EntitySpan copiedSparseIds,
4402 EntitySpan addedIds, EntitySpan addHookIds) {
4403 GAIA_ASSERT(!prefabEntity.pair());
4404 GAIA_ASSERT(valid(prefabEntity));
4405 GAIA_ASSERT(has_direct(prefabEntity, Prefab));
4406 GAIA_ASSERT(pDstArchetype != nullptr);
4407#if GAIA_OBSERVERS_ENABLED
4408 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{addedIds});
4409#endif
4410
4411 auto& ecSrc = m_recs.entities[prefabEntity.id()];
4412 GAIA_ASSERT(ecSrc.pArchetype != nullptr);
4413 GAIA_ASSERT(ecSrc.pChunk != nullptr);
4414
4415 EntityContainerCtx ctx{true, false, prefabEntity.kind()};
4416 const auto instance = m_recs.entities.alloc(&ctx);
4417 auto& ecDst = m_recs.entities[instance.id()];
4418 auto* pDstChunk = pDstArchetype->foc_free_chunk();
4419 store_entity(ecDst, instance, pDstArchetype, pDstChunk);
4420 pDstArchetype->try_update_free_chunk_idx();
4421 Chunk::copy_foreign_entity_data(ecSrc.pChunk, ecSrc.row, pDstChunk, ecDst.row);
4422 pDstChunk->update_versions();
4423
4424 ecDst.flags |= EntityContainerFlags::HasAliasOf;
4425
4426 // Keep payload copy and observer/add-id reporting separate:
4427 // fragmenting sparse payloads must still be copied here even though their id is
4428 // already present in the destination archetype and therefore absent from addedIds tail.
4429 (void)copy_sparse_entity_data(prefabEntity, instance, copiedSparseIds);
4430#if GAIA_OBSERVERS_ENABLED
4431 m_observers.add_diff_targets(*this, addDiffCtx, EntitySpan{&instance, 1});
4432#endif
4433
4434 touch_rel_version(Is);
4435 invalidate_queries_for_rel(Is);
4436 m_targetsTravCache = {};
4437 m_srcBfsTravCache = {};
4438 m_depthOrderCache = {};
4439 m_sourcesAllCache = {};
4440 m_targetsAllCache = {};
4441
4442 const auto instanceKey = EntityLookupKey(instance);
4443 const auto prefabKey = EntityLookupKey(prefabEntity);
4444 m_entityToAsTargets[instanceKey].insert(prefabKey);
4445 m_entityToAsTargetsTravCache = {};
4446 m_entityToAsRelations[prefabKey].insert(instanceKey);
4447 m_entityToAsRelationsTravCache = {};
4448 invalidate_queries_for_entity({Is, prefabEntity});
4449
4450#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
4451 if GAIA_UNLIKELY (tearing_down()) {
4452 (void)pDstArchetype;
4453 (void)addedIds;
4454 } else {
4455 lock();
4456
4457 #if GAIA_ENABLE_ADD_DEL_HOOKS
4458 for (const auto id: addHookIds) {
4459 const auto& item = comp_cache().get(id);
4460 const auto& hooks = ComponentCache::hooks(item);
4461 GAIA_ASSERT(hooks.func_add != nullptr);
4462 hooks.func_add(*this, item, instance);
4463 }
4464 #endif
4465
4466 #if GAIA_OBSERVERS_ENABLED
4467 m_observers.on_add(*this, *pDstArchetype, addedIds, EntitySpan{&instance, 1});
4468 #endif
4469
4470 unlock();
4471 }
4472#endif
4473
4474#if GAIA_OBSERVERS_ENABLED
4475 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4476#endif
4477
4478 if (parentInstance != EntityBad)
4479 parent_direct(instance, parentInstance);
4480
4481 return instance;
4482 }
4483
4484 GAIA_NODISCARD Entity instantiate_prefab_node_inter(Entity prefabEntity, Entity parentInstance) {
4485 auto* pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
4486 cnt::darray_ext<Entity, 16> copiedSparseIds;
4487 cnt::darray_ext<Entity, 16> addedIds;
4488 cnt::darray_ext<Entity, 16> addHookIds;
4489 collect_prefab_copied_sparse_ids(prefabEntity, copiedSparseIds);
4490 collect_prefab_added_ids(pDstArchetype, EntitySpan{copiedSparseIds}, addedIds);
4491 collect_prefab_add_hook_ids(EntitySpan{addedIds}, addHookIds);
4492 return instantiate_prefab_node_inter(
4493 prefabEntity, pDstArchetype, parentInstance, EntitySpan{copiedSparseIds}, EntitySpan{addedIds},
4494 EntitySpan{addHookIds});
4495 }
4496
4497 template <typename Func>
4498 void instantiate_prefab_n_inter(
4499 const PrefabInstantiatePlanNode& node, Entity parentInstance, uint32_t count, Func& func) {
4500 GAIA_ASSERT(node.prefab != EntityBad);
4501 GAIA_ASSERT(node.pDstArchetype != nullptr);
4502
4503 if (count == 0U)
4504 return;
4505#if GAIA_OBSERVERS_ENABLED
4506 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{node.addedIds});
4507#endif
4508
4509 auto& ecSrc = m_recs.entities[node.prefab.id()];
4510 GAIA_ASSERT(ecSrc.pChunk != nullptr);
4511
4512 if (parentInstance != EntityBad)
4513 prepare_parent_batch(parentInstance);
4514
4515 const auto srcRow = ecSrc.row;
4516 auto* pSrcChunk = ecSrc.pChunk;
4517 auto* pDstArchetype = node.pDstArchetype;
4518 const auto prefabKey = EntityLookupKey(node.prefab);
4519 EntityContainerCtx ctx{true, false, node.prefab.kind()};
4520
4521 uint32_t left = count;
4522 do {
4523 auto* pDstChunk = pDstArchetype->foc_free_chunk();
4524 const uint32_t originalChunkSize = pDstChunk->size();
4525 const uint32_t freeSlotsInChunk = pDstChunk->capacity() - originalChunkSize;
4526 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
4527
4528 GAIA_FOR_(toCreate, rowOffset) {
4529 const auto instance = m_recs.entities.alloc(&ctx);
4530 auto& ecDst = m_recs.entities[instance.id()];
4531 store_entity(ecDst, instance, pDstArchetype, pDstChunk);
4532 ecDst.flags |= EntityContainerFlags::HasAliasOf;
4533
4534 (void)copy_sparse_entity_data(node.prefab, instance, EntitySpan{node.copiedSparseIds});
4535 }
4536
4537 pDstArchetype->try_update_free_chunk_idx();
4538 Chunk::copy_foreign_entity_data_n(pSrcChunk, srcRow, pDstChunk, originalChunkSize, toCreate);
4539 pDstChunk->update_versions();
4540
4541 touch_rel_version(Is);
4542 invalidate_queries_for_rel(Is);
4543 m_targetsTravCache = {};
4544 m_srcBfsTravCache = {};
4545 m_depthOrderCache = {};
4546 m_sourcesAllCache = {};
4547 m_targetsAllCache = {};
4548
4549 auto entities = pDstChunk->entity_view();
4550 auto& asRelations = m_entityToAsRelations[prefabKey];
4551 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
4552 const auto instance = entities[rowIdx];
4553 m_entityToAsTargets[EntityLookupKey(instance)].insert(prefabKey);
4554 asRelations.insert(EntityLookupKey(instance));
4555 }
4556 m_entityToAsTargetsTravCache = {};
4557 m_entityToAsRelationsTravCache = {};
4558 invalidate_queries_for_entity({Is, node.prefab});
4559
4560#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
4561 if GAIA_UNLIKELY (tearing_down()) {
4562 (void)entities;
4563 (void)originalChunkSize;
4564 (void)toCreate;
4565 } else {
4566 lock();
4567
4568 #if GAIA_ENABLE_ADD_DEL_HOOKS
4569 for (const auto id: node.addHookIds) {
4570 const auto& item = comp_cache().get(id);
4571 const auto& hooks = ComponentCache::hooks(item);
4572 GAIA_ASSERT(hooks.func_add != nullptr);
4573
4574 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
4575 hooks.func_add(*this, item, entities[rowIdx]);
4576 }
4577 }
4578 #endif
4579
4580 #if GAIA_OBSERVERS_ENABLED
4581 m_observers.add_diff_targets(*this, addDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
4582 m_observers.on_add(
4583 *this, *pDstArchetype, EntitySpan{node.addedIds},
4584 EntitySpan{entities.data() + originalChunkSize, toCreate});
4585 #endif
4586
4587 unlock();
4588 }
4589#endif
4590
4591 if (parentInstance != EntityBad)
4592 parent_batch(parentInstance, *pDstArchetype, *pDstChunk, originalChunkSize, toCreate);
4593
4594 invoke_copy_batch_callback(func, pDstArchetype, pDstChunk, originalChunkSize, toCreate);
4595
4596 left -= toCreate;
4597 } while (left > 0);
4598#if GAIA_OBSERVERS_ENABLED
4599 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4600#endif
4601 }
4602
4603 template <typename T>
4604 void build_prefab_instantiate_plan(Entity prefabEntity, uint32_t parentIdx, T& plan) {
4605 PrefabInstantiatePlanNode node{};
4606 node.prefab = prefabEntity;
4607 node.parentIdx = parentIdx;
4608 node.pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
4609 collect_prefab_copied_sparse_ids(prefabEntity, node.copiedSparseIds);
4610 collect_prefab_added_ids(node.pDstArchetype, EntitySpan{node.copiedSparseIds}, node.addedIds);
4611 collect_prefab_add_hook_ids(EntitySpan{node.addedIds}, node.addHookIds);
4612
4613 const auto nodeIdx = (uint32_t)plan.size();
4614 plan.push_back(GAIA_MOV(node));
4615
4616 cnt::darray_ext<Entity, 16> prefabChildren;
4617 gather_sorted_prefab_children(prefabEntity, prefabChildren);
4618
4619 for (const auto childPrefab: prefabChildren)
4620 build_prefab_instantiate_plan(childPrefab, nodeIdx, plan);
4621 }
4622
4623 GAIA_NODISCARD bool instance_has_prefab_child(Entity parentInstance, Entity childPrefab) const {
4624 bool found = false;
4625 sources(Parent, parentInstance, [&](Entity child) {
4626 if (found)
4627 return;
4628 if (has_direct(child, Pair(Is, childPrefab)))
4629 found = true;
4630 });
4631 return found;
4632 }
4633
4634 uint32_t sync_prefab_instance(
4635 Entity prefabEntity, Entity instance, const PrefabInstantiatePlanNode& node, EntitySpan prefabChildren) {
4636 uint32_t changes = 0;
4637
4638 const auto isPair = Pair(Is, prefabEntity);
4639 for (const auto id: node.pDstArchetype->ids_view()) {
4640 if (id == isPair || has_direct(instance, id))
4641 continue;
4642 if (copy_owned_id_from_entity(prefabEntity, instance, id))
4643 ++changes;
4644 }
4645
4646 for (const auto comp: node.copiedSparseIds) {
4647 if (has_direct(instance, comp))
4648 continue;
4649 if (copy_owned_id_from_entity(prefabEntity, instance, comp))
4650 ++changes;
4651 }
4652
4653 for (const auto childPrefab: prefabChildren) {
4654 if (instance_has_prefab_child(instance, childPrefab))
4655 continue;
4656 (void)instantiate(childPrefab, instance);
4657 ++changes;
4658 }
4659
4660 return changes;
4661 }
4662
4663 uint32_t sync_prefab_inter(Entity prefabEntity, cnt::set<EntityLookupKey>& visited) {
4664 GAIA_ASSERT(!prefabEntity.pair());
4665 GAIA_ASSERT(valid(prefabEntity));
4666
4667 if (!has_direct(prefabEntity, Prefab))
4668 return 0;
4669
4670 const auto ins = visited.insert(EntityLookupKey(prefabEntity));
4671 if (!ins.second)
4672 return 0;
4673
4674 PrefabInstantiatePlanNode node{};
4675 node.prefab = prefabEntity;
4676 node.pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
4677 collect_prefab_copied_sparse_ids(prefabEntity, node.copiedSparseIds);
4678 collect_prefab_added_ids(node.pDstArchetype, EntitySpan{node.copiedSparseIds}, node.addedIds);
4679 collect_prefab_add_hook_ids(EntitySpan{node.addedIds}, node.addHookIds);
4680
4681 cnt::darray_ext<Entity, 16> prefabChildren;
4682 gather_sorted_prefab_children(prefabEntity, prefabChildren);
4683
4684 uint32_t changes = 0;
4685 const auto& descendants = as_relations_trav_cache(prefabEntity);
4686 for (const auto entity: descendants) {
4687 if (has_direct(entity, Prefab))
4688 continue;
4689 changes += sync_prefab_instance(prefabEntity, entity, node, EntitySpan{prefabChildren});
4690 }
4691
4692 for (const auto childPrefab: prefabChildren)
4693 changes += sync_prefab_inter(childPrefab, visited);
4694
4695 return changes;
4696 }
4697
4699 GAIA_NODISCARD Entity instantiate_inter(Entity prefabEntity, Entity parentInstance) {
4700 const auto instance = instantiate_prefab_node_inter(prefabEntity, parentInstance);
4701
4702 cnt::darray_ext<Entity, 16> prefabChildren;
4703 gather_sorted_prefab_children(prefabEntity, prefabChildren);
4704
4705 for (const auto childPrefab: prefabChildren)
4706 (void)instantiate_inter(childPrefab, instance);
4707
4708 return instance;
4709 }
4710
4711 public:
4715 GAIA_NODISCARD Entity instantiate(Entity prefabEntity) {
4716 GAIA_ASSERT(!prefabEntity.pair());
4717 GAIA_ASSERT(valid(prefabEntity));
4718
4719 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab))
4720 return copy(prefabEntity);
4721
4722 return instantiate_inter(prefabEntity, EntityBad);
4723 }
4724
4729 GAIA_NODISCARD Entity instantiate(Entity prefabEntity, Entity parentInstance) {
4730 GAIA_ASSERT(!prefabEntity.pair());
4731 GAIA_ASSERT(valid(prefabEntity));
4732 GAIA_ASSERT(valid(parentInstance));
4733
4734 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab)) {
4735 const auto instance = copy(prefabEntity);
4736 parent_direct(instance, parentInstance);
4737 return instance;
4738 }
4739
4740 return instantiate_inter(prefabEntity, parentInstance);
4741 }
4742
4755 template <typename Func = TFunc_Void_With_Entity>
4756 void instantiate_n(Entity prefabEntity, uint32_t count, Func func = func_void_with_entity) {
4757 instantiate_n(prefabEntity, EntityBad, count, func);
4758 }
4759
4761 void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count) {
4762 instantiate_n(prefabEntity, parentInstance, count, func_void_with_entity);
4763 }
4764
4767 template <typename Func>
4768 void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count, Func func) {
4769 GAIA_ASSERT(!prefabEntity.pair());
4770 GAIA_ASSERT(valid(prefabEntity));
4771 GAIA_ASSERT(parentInstance == EntityBad || valid(parentInstance));
4772
4773 if (count == 0U)
4774 return;
4775
4776 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab)) {
4777 if (parentInstance == EntityBad) {
4778 copy_n(prefabEntity, count, func);
4779 return;
4780 }
4781
4782 copy_n_inter(prefabEntity, count, func, EntitySpan{}, parentInstance);
4783 return;
4784 }
4785
4788
4789 if constexpr (std::is_invocable_v<Func, CopyIter&>) {
4790 build_prefab_instantiate_plan(prefabEntity, BadIndex, plan);
4791 if (plan.size() == 1) {
4792 instantiate_prefab_n_inter(plan[0], parentInstance, count, func);
4793 return;
4794 }
4795
4796 CopyIterGroupState group;
4797 cnt::darray<Entity> roots;
4798 roots.reserve(count);
4799 auto collectRoot = [&](Entity instance) {
4800 roots.push_back(instance);
4801 };
4802 instantiate_prefab_n_inter(plan[0], parentInstance, count, collectRoot);
4803
4804 spawned.resize((uint32_t)plan.size());
4805
4806 GAIA_FOR_(count, rootIdx) {
4807 spawned[0] = roots[rootIdx];
4808 GAIA_FOR2_(1, (uint32_t)plan.size(), planIdx) {
4809 const auto parent = spawned[plan[planIdx].parentIdx];
4810 spawned[planIdx] = instantiate_prefab_node_inter(
4811 plan[planIdx].prefab, plan[planIdx].pDstArchetype, parent, EntitySpan{plan[planIdx].copiedSparseIds},
4812 EntitySpan{plan[planIdx].addedIds}, EntitySpan{plan[planIdx].addHookIds});
4813 }
4814
4815 push_copy_iter_group(func, group, roots[rootIdx]);
4816 }
4817
4818 flush_copy_iter_group(func, group);
4819 } else {
4820 build_prefab_instantiate_plan(prefabEntity, BadIndex, plan);
4821 if (plan.size() == 1) {
4822 instantiate_prefab_n_inter(plan[0], parentInstance, count, func);
4823 return;
4824 }
4825
4826 cnt::darray<Entity> roots;
4827 roots.reserve(count);
4828 auto collectRoot = [&](Entity instance) {
4829 roots.push_back(instance);
4830 };
4831 instantiate_prefab_n_inter(plan[0], parentInstance, count, collectRoot);
4832
4833 spawned.resize((uint32_t)plan.size());
4834
4835 GAIA_FOR_(count, rootIdx) {
4836 spawned[0] = roots[rootIdx];
4837 GAIA_FOR2_(1, (uint32_t)plan.size(), planIdx) {
4838 const auto parent = spawned[plan[planIdx].parentIdx];
4839 spawned[planIdx] = instantiate_prefab_node_inter(
4840 plan[planIdx].prefab, plan[planIdx].pDstArchetype, parent, EntitySpan{plan[planIdx].copiedSparseIds},
4841 EntitySpan{plan[planIdx].addedIds}, EntitySpan{plan[planIdx].addHookIds});
4842 }
4843
4844 func(roots[rootIdx]);
4845 }
4846 }
4847 }
4848
4852 GAIA_NODISCARD uint32_t sync(Entity prefabEntity) {
4853 GAIA_ASSERT(!prefabEntity.pair());
4854 GAIA_ASSERT(valid(prefabEntity));
4855
4857 return sync_prefab_inter(prefabEntity, visited);
4858 }
4859
4860 //----------------------------------------------------------------------
4861
4864 void del(Entity entity) {
4865 if (!entity.pair()) {
4866 // Delete all relationships associated with this entity (if any)
4867 del_inter(Pair(entity, All));
4868 del_inter(Pair(All, entity));
4869 }
4870
4871 del_inter(entity);
4872 }
4873
4878 void del(Entity entity, Entity object) {
4879 if (!object.pair()) {
4880 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
4881 if (itSparseStore != m_sparseComponentsByComp.end()) {
4882 if (!is_non_fragmenting_out_of_line_component(object)) {
4883 {
4884 EntityBuilder eb(*this, entity);
4885 eb.del(object);
4886 }
4887 itSparseStore->second.func_del(itSparseStore->second.pStore, entity);
4888 return;
4889 }
4890#if GAIA_OBSERVERS_ENABLED
4891 auto delDiffCtx =
4892 m_observers.prepare_diff(*this, ObserverEvent::OnDel, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
4893#endif
4894 notify_inherited_del_dependents(entity, object);
4895 notify_del_single(entity, object);
4896 itSparseStore->second.func_del(itSparseStore->second.pStore, entity);
4897#if GAIA_OBSERVERS_ENABLED
4898 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
4899#endif
4900 return;
4901 }
4902 }
4903 EntityBuilder(*this, entity).del(object);
4904 }
4905
4911 void del(Entity entity, Pair pair) {
4912 EntityBuilder(*this, entity).del(pair);
4913 }
4914
4920 template <typename T>
4921 void del(Entity entity) {
4922 using CT = component_type_t<T>;
4923 using FT = typename CT::TypeFull;
4924
4925 if constexpr (supports_out_of_line_component<FT>()) {
4926 if (const auto* pItem = comp_cache().template find<FT>();
4927 pItem != nullptr && out_of_line_mode(pItem->entity) != OutOfLineMode::None) {
4928 if (sparse_component_store<FT>(pItem->entity) != nullptr)
4929 del(entity, pItem->entity);
4930 return;
4931 }
4932 }
4933
4934 EntityBuilder(*this, entity).del<FT>();
4935 }
4936
4937 //----------------------------------------------------------------------
4938
4940 void as(Entity entity, Entity entityBase) {
4941 // Form the relationship
4942 add(entity, Pair(Is, entityBase));
4943 }
4944
4949 GAIA_NODISCARD bool is(Entity entity, Entity entityBase) const {
4950 return is_inter<false>(entity, entityBase);
4951 }
4952
4959 GAIA_NODISCARD bool in(Entity entity, Entity entityBase) const {
4960 return is_inter<true>(entity, entityBase);
4961 }
4962
4963 GAIA_NODISCARD bool is_base(Entity target) const {
4964 GAIA_ASSERT(valid_entity(target));
4965
4966 // Pairs are not supported
4967 if (target.pair())
4968 return false;
4969
4970 const auto it = m_entityToAsRelations.find(EntityLookupKey(target));
4971 return it != m_entityToAsRelations.end();
4972 }
4973
4974 //----------------------------------------------------------------------
4975
4977 void child(Entity entity, Entity parent) {
4978 add(entity, Pair(ChildOf, parent));
4979 }
4980
4985 GAIA_NODISCARD bool child(Entity entity, Entity parent) const {
4986 return has(entity, Pair(ChildOf, parent));
4987 }
4988
4990 void parent(Entity entity, Entity parentEntity) {
4991 add(entity, Pair(Parent, parentEntity));
4992 }
4993
4998 GAIA_NODISCARD bool parent(Entity entity, Entity parentEntity) const {
4999 return has(entity, Pair(Parent, parentEntity));
5000 }
5001
5002 //----------------------------------------------------------------------
5003
5010 template <
5011 typename T
5012#if GAIA_ENABLE_HOOKS
5013 ,
5014 bool TriggerSetEffects
5015#endif
5016 >
5017 void modify(Entity entity) {
5018 GAIA_ASSERT(valid(entity));
5019
5020 if constexpr (supports_out_of_line_component<T>()) {
5021 const auto* pItem = comp_cache().template find<T>();
5022 if (pItem != nullptr && out_of_line_mode(pItem->entity) != OutOfLineMode::None) {
5023#if GAIA_ASSERT_ENABLED
5024 auto* pStore = sparse_component_store<typename component_type_t<T>::TypeFull>(pItem->entity);
5025 GAIA_ASSERT(pStore != nullptr);
5026 GAIA_ASSERT(has_direct_out_of_line_inter(entity, pItem->entity));
5027#endif
5028
5029 ::gaia::ecs::update_version(m_worldVersion);
5030
5031#if GAIA_OBSERVERS_ENABLED
5032 if constexpr (TriggerSetEffects)
5033 world_notify_on_set_entity(*this, pItem->entity, entity);
5034#endif
5035 return;
5036 }
5037 }
5038
5039 auto& ec = m_recs.entities[entity.id()];
5040 ec.pChunk->template modify<
5041 T
5042#if GAIA_ENABLE_HOOKS
5043 ,
5044 TriggerSetEffects
5045#endif
5046 >();
5047
5048#if GAIA_OBSERVERS_ENABLED
5049 if constexpr (TriggerSetEffects) {
5050 Entity term = EntityBad;
5051 if constexpr (is_pair<T>::value) {
5052 const auto rel = comp_cache().template get<typename T::rel>().entity;
5053 const auto tgt = comp_cache().template get<typename T::tgt>().entity;
5054 term = (Entity)Pair(rel, tgt);
5055 } else
5056 term = comp_cache().template get<T>().entity;
5057
5058 world_notify_on_set(*this, term, *ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
5059 }
5060#endif
5061 }
5062
5070 template <
5071 typename T
5072#if GAIA_ENABLE_HOOKS
5073 ,
5074 bool TriggerSetEffects
5075#endif
5076 >
5077 void modify(Entity entity, Entity object) {
5078 GAIA_ASSERT(valid(entity));
5079 GAIA_ASSERT(valid(object));
5080
5081 using FT = typename component_type_t<T>::TypeFull;
5082 if constexpr (supports_out_of_line_component<FT>()) {
5083 if (can_use_out_of_line_component<FT>(object)) {
5084#if GAIA_ASSERT_ENABLED
5085 auto* pStore = sparse_component_store<FT>(object);
5086 GAIA_ASSERT(pStore != nullptr);
5087 GAIA_ASSERT(has_direct_out_of_line_inter(entity, object));
5088#endif
5089
5090 ::gaia::ecs::update_version(m_worldVersion);
5091
5092#if GAIA_OBSERVERS_ENABLED
5093 if constexpr (TriggerSetEffects)
5094 world_notify_on_set_entity(*this, object, entity);
5095#endif
5096 return;
5097 }
5098 }
5099
5100 auto& ec = m_recs.entities[entity.id()];
5101 const auto compIdx = ec.pChunk->comp_idx(object);
5102 GAIA_ASSERT(compIdx != ComponentIndexBad);
5103
5104 if constexpr (TriggerSetEffects)
5105 ec.pChunk->finish_write(compIdx, ec.row, (uint16_t)(ec.row + 1));
5106 else
5107 ec.pChunk->update_world_version(compIdx);
5108 }
5109
5110 //----------------------------------------------------------------------
5111
5117 GAIA_NODISCARD ComponentSetter acc_mut(Entity entity) {
5118 GAIA_ASSERT(valid(entity));
5119
5120 const auto& ec = m_recs.entities[entity.id()];
5121 return ComponentSetter{*this, ec.pChunk, entity, ec.row};
5122 }
5123
5134 template <typename T>
5135 GAIA_NODISCARD auto set(Entity entity) {
5136 static_assert(!is_pair<T>::value);
5137 using FT = typename component_type_t<T>::TypeFull;
5138 using ValueType = typename actual_type_t<T>::Type;
5139 const auto& item = add<FT>();
5140 return SetWriteProxyTyped<T, ValueType>{*this, entity, item.entity, get<T>(entity)};
5141 }
5142
5154 template <typename T>
5155 GAIA_NODISCARD auto set(Entity entity, Entity object) {
5156 static_assert(!is_pair<T>::value);
5157 return SetWriteProxyObject<typename actual_type_t<T>::Type>{*this, entity, object, get<T>(entity, object)};
5158 }
5159
5167 template <typename T>
5168 GAIA_NODISCARD decltype(auto) sset(Entity entity) {
5169 static_assert(!is_pair<T>::value);
5170 using FT = typename component_type_t<T>::TypeFull;
5171 const auto& item = add<FT>();
5172 if constexpr (supports_out_of_line_component<FT>()) {
5173 if (out_of_line_mode(item.entity) != OutOfLineMode::None)
5174 return sparse_component_store_mut<FT>(item.entity).mut(entity);
5175 }
5176 return acc_mut(entity).smut<T>();
5177 }
5178
5181 template <typename T>
5182 GAIA_NODISCARD decltype(auto) sset(Entity entity, Entity object) {
5183 static_assert(!is_pair<T>::value);
5184 using FT = typename component_type_t<T>::TypeFull;
5185 if constexpr (supports_out_of_line_component<FT>()) {
5186 if (can_use_out_of_line_component<FT>(object))
5187 return sparse_component_store_mut<FT>(object).mut(entity);
5188 }
5189 return acc_mut(entity).smut<T>(object);
5190 }
5191
5192 //----------------------------------------------------------------------
5193
5201 template <typename T>
5202 GAIA_NODISCARD decltype(auto) mut(Entity entity) {
5203 static_assert(!is_pair<T>::value);
5204 return sset<T>(entity);
5205 }
5206
5209 template <typename T>
5210 GAIA_NODISCARD decltype(auto) mut(Entity entity, Entity object) {
5211 static_assert(!is_pair<T>::value);
5212 return sset<T>(entity, object);
5213 }
5214
5220 GAIA_NODISCARD ComponentRawView get_raw(Entity entity, Entity component) const {
5221 if (component == EntityBad || component.pair() || !valid(entity))
5222 return {};
5223
5224 const auto* pItem = comp_cache().find(component);
5225 if (pItem == nullptr || !raw_component_supported(*pItem))
5226 return {};
5227
5228 const auto owner = id_owner_inter(entity, component);
5229 if (owner == EntityBad)
5230 return {};
5231
5232 const auto& ec = fetch(owner);
5233 const auto compIdx = ec.pChunk->comp_idx(component);
5234 if (compIdx == ComponentIndexBad)
5235 return {};
5236
5237 const auto size = pItem->comp.size();
5238 if (size == 0)
5239 return {nullptr, 0, ComponentRawViewFlag_Valid};
5240
5241 const auto row = uint32_t(ec.row * (1U - (uint32_t)component.kind()));
5242 return {ec.pChunk->comp_ptr(compIdx, row), size, ComponentRawViewFlag_Valid};
5243 }
5244
5250 GAIA_NODISCARD ComponentRawMutView mut_raw(Entity entity, Entity component) {
5251 if (component == EntityBad || component.pair() || !valid(entity))
5252 return {};
5253
5254 const auto* pItem = comp_cache().find(component);
5255 if (pItem == nullptr || !raw_component_supported(*pItem))
5256 return {};
5257
5258 const auto& ec = fetch(entity);
5259 if (is_req_del(ec))
5260 return {};
5261
5262 const auto compIdx = ec.pChunk->comp_idx(component);
5263 if (compIdx == ComponentIndexBad)
5264 return {};
5265
5266 const auto size = pItem->comp.size();
5267 if (size == 0)
5268 return {nullptr, 0, ComponentRawViewFlag_Valid};
5269
5270 const auto row = uint32_t(ec.row * (1U - (uint32_t)component.kind()));
5271 return {ec.pChunk->comp_ptr_mut(compIdx, row), size, ComponentRawViewFlag_Valid};
5272 }
5273
5279 GAIA_NODISCARD ComponentCursor cursor(Entity entity, Entity component) const;
5280
5287 GAIA_NODISCARD ComponentCursor cursor_mut(Entity entity, Entity component);
5288
5295 bool add_raw(Entity entity, Entity component, const void* data, uint32_t size) {
5296 if (component == EntityBad || component.pair() || !valid(entity))
5297 return false;
5298
5299 const auto* pItem = comp_cache().find(component);
5300 if (pItem == nullptr || !raw_component_payload_args_valid(*pItem, data, size))
5301 return false;
5302 if (has_direct(entity, component))
5303 return false;
5304
5305 EntityBuilder eb(*this, entity);
5306#if GAIA_OBSERVERS_ENABLED
5307 auto addDiffCtx =
5308 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&component, 1}, EntitySpan{&entity, 1});
5309#endif
5310 eb.add_inter_init(component);
5311 eb.commit();
5312
5313 const auto payload = mut_raw(entity, component);
5314 GAIA_ASSERT(payload.valid());
5315 if (payload.valid() && size != 0)
5316 memcpy(payload.data, data, size);
5317
5318 notify_add_single(entity, component);
5319#if GAIA_OBSERVERS_ENABLED
5320 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5321#endif
5322 return payload.valid();
5323 }
5324
5331 bool set_raw(Entity entity, Entity component, const void* data, uint32_t size) {
5332 if (component == EntityBad || component.pair())
5333 return false;
5334
5335 const auto* pItem = comp_cache().find(component);
5336 if (pItem == nullptr || !raw_component_payload_args_valid(*pItem, data, size))
5337 return false;
5338
5339 const auto payload = mut_raw(entity, component);
5340 if (!payload.valid())
5341 return false;
5342 if (size != 0)
5343 memcpy(payload.data, data, size);
5344
5345 modify_raw(entity, component);
5346 return true;
5347 }
5348
5352 void modify_raw(Entity entity, Entity component) {
5353 if (!mut_raw(entity, component).valid())
5354 return;
5355 finish_write(entity, component);
5356 }
5357
5358 //----------------------------------------------------------------------
5359
5366 GAIA_ASSERT(valid(entity));
5367
5368 const auto& ec = m_recs.entities[entity.id()];
5369 return ComponentGetter{*this, ec.pChunk, entity, ec.row};
5370 }
5371
5379 template <typename T>
5380 GAIA_NODISCARD decltype(auto) get(Entity entity) const {
5381 using FT = typename component_type_t<T>::TypeFull;
5382 const auto compEntity = [&]() {
5383 if constexpr (is_pair<FT>::value) {
5384 const auto rel = comp_cache().template get<typename FT::rel>().entity;
5385 const auto tgt = comp_cache().template get<typename FT::tgt>().entity;
5386 return (Entity)Pair(rel, tgt);
5387 } else {
5388 return comp_cache().template get<FT>().entity;
5389 }
5390 }();
5391 if constexpr (supports_out_of_line_component<FT>()) {
5392 const auto* pItem = comp_cache().template find<FT>();
5393 if (pItem != nullptr && out_of_line_mode(pItem->entity) != OutOfLineMode::None) {
5394 const auto* pStore = sparse_component_store<FT>(pItem->entity);
5395 GAIA_ASSERT(pStore != nullptr);
5396 const auto owner = id_owner_inter(entity, compEntity);
5397 GAIA_ASSERT(owner != EntityBad);
5398 return pStore->get(owner);
5399 }
5400 }
5401
5402 const auto owner = id_owner_inter(entity, compEntity);
5403 GAIA_ASSERT(owner != EntityBad);
5404 return acc(owner).template get<T>();
5405 }
5406
5408 template <typename T>
5409 GAIA_NODISCARD decltype(auto) get(Entity entity, Entity object) const {
5410 using FT = typename component_type_t<T>::TypeFull;
5411 if constexpr (supports_out_of_line_component<FT>()) {
5412 if (can_use_out_of_line_component<FT>(object)) {
5413 const auto* pStore = sparse_component_store<FT>(object);
5414 GAIA_ASSERT(pStore != nullptr);
5415 const auto owner = id_owner_inter(entity, object);
5416 GAIA_ASSERT(owner != EntityBad);
5417 return pStore->get(owner);
5418 }
5419 }
5420
5421 const auto owner = id_owner_inter(entity, object);
5422 GAIA_ASSERT(owner != EntityBad);
5423 return acc(owner).template get<T>(object);
5424 }
5425
5426 //----------------------------------------------------------------------
5427
5431 GAIA_NODISCARD bool has(Entity entity) const {
5432 // Pair
5433 if (entity.pair()) {
5434 if (entity == Pair(All, All))
5435 return true;
5436
5437 if (is_wildcard(entity)) {
5438 if (!m_entityToArchetypeMap.contains(EntityLookupKey(entity)))
5439 return false;
5440
5441 // If the pair is found, both entities forming it need to be found as well
5442 GAIA_ASSERT(has(get(entity.id())) && has(get(entity.gen())));
5443
5444 return true;
5445 }
5446
5447 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
5448 if (it == m_recs.pairs.end())
5449 return false;
5450
5451 const auto& ec = it->second;
5452 if (is_req_del(ec))
5453 return false;
5454
5455#if GAIA_ASSERT_ENABLED
5456 // If the pair is found, both entities forming it need to be found as well
5457 GAIA_ASSERT(has(get(entity.id())) && has(get(entity.gen())));
5458
5459 // Index of the entity must fit inside the chunk
5460 auto* pChunk = ec.pChunk;
5461 GAIA_ASSERT(pChunk != nullptr && ec.row < pChunk->size());
5462#endif
5463
5464 return true;
5465 }
5466
5467 // Regular entity
5468 {
5469 // Entity ID has to fit inside the entity array
5470 if (entity.id() >= m_recs.entities.size() || !m_recs.entities.has(entity.id()))
5471 return false;
5472
5473 // Index of the entity must fit inside the chunk
5474 const auto& ec = m_recs.entities[entity.id()];
5475 if (is_req_del(ec))
5476 return false;
5477
5478 auto* pChunk = ec.pChunk;
5479 return pChunk != nullptr && ec.row < pChunk->size();
5480 }
5481 }
5482
5486 GAIA_NODISCARD bool has(Pair pair) const {
5487 return has((Entity)pair);
5488 }
5489
5496 GAIA_NODISCARD bool has(Entity entity, Entity object) const {
5497 return has_inter(entity, object, true);
5498 }
5499
5504 GAIA_NODISCARD bool has_direct(Entity entity, Entity object) const {
5505 return has_inter(entity, object, false);
5506 }
5507
5509 GAIA_NODISCARD bool has_direct(Entity entity, Pair pair) const {
5510 return has_inter(entity, (Entity)pair, false);
5511 }
5512
5513 private:
5514 GAIA_NODISCARD bool has_inter(Entity entity, Entity object, bool allowSemanticIs) const {
5515 const auto& ec = fetch(entity);
5516 if (is_req_del(ec))
5517 return false;
5518
5519 if (object.pair() && has_exclusive_adjunct_pair(entity, object))
5520 return true;
5521 if (!object.pair()) {
5522 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
5523 if (itSparseStore != m_sparseComponentsByComp.end())
5524 return has_direct_out_of_line_inter(entity, object) ||
5525 (allowSemanticIs && inherited_id_owner(entity, object) != EntityBad);
5526 }
5527
5528 const auto* pArchetype = ec.pArchetype;
5529
5530 if (object.pair()) {
5531 if (allowSemanticIs && object.id() == Is.id() && !is_wildcard(object.gen())) {
5532 const auto target = get(object.gen());
5533 return valid(target) && is(entity, target);
5534 }
5535
5536 // Early exit if there are no pairs on the archetype
5537 if (pArchetype->pairs() == 0)
5538 return false;
5539
5540 EntityId rel = object.id();
5541 EntityId tgt = object.gen();
5542
5543 // (*,*)
5544 if (rel == All.id() && tgt == All.id())
5545 return true;
5546
5547 // (X,*)
5548 if (rel != All.id() && tgt == All.id()) {
5549 auto ids = pArchetype->ids_view();
5550 for (auto id: ids) {
5551 if (!id.pair())
5552 continue;
5553 if (id.id() == rel)
5554 return true;
5555 }
5556
5557 return false;
5558 }
5559
5560 // (*,X)
5561 if (rel == All.id() && tgt != All.id()) {
5562 auto ids = pArchetype->ids_view();
5563 for (auto id: ids) {
5564 if (!id.pair())
5565 continue;
5566 if (id.gen() == tgt)
5567 return true;
5568 }
5569
5570 return false;
5571 }
5572 }
5573
5574 if (pArchetype->has(object))
5575 return true;
5576
5577 return allowSemanticIs && inherited_id_owner(entity, object) != EntityBad;
5578 }
5579
5580 public:
5584 GAIA_NODISCARD std::span<const Entity> lookup_path() const {
5585 return {m_componentLookupPath.data(), m_componentLookupPath.size()};
5586 }
5587
5592 m_componentLookupPath.clear();
5593 m_componentLookupPath.reserve((uint32_t)scopes.size());
5594 for (const auto scopeEntity: scopes) {
5595 GAIA_ASSERT(scopeEntity != EntityBad && valid(scopeEntity) && !scopeEntity.pair());
5596 if (scopeEntity == EntityBad || !valid(scopeEntity) || scopeEntity.pair())
5597 continue;
5598
5599 m_componentLookupPath.push_back(scopeEntity);
5600 }
5601 }
5602
5605 GAIA_NODISCARD Entity scope() const {
5606 return m_componentScope;
5607 }
5608
5614 GAIA_ASSERT(scope == EntityBad || (valid(scope) && !scope.pair()));
5615 const auto prev = m_componentScope;
5616 if (scope == EntityBad || (valid(scope) && !scope.pair())) {
5617 m_componentScope = scope;
5618 invalidate_scope_path_cache();
5619 }
5620 return prev;
5621 }
5622
5628 template <typename Func>
5629 void scope(Entity scopeEntity, Func&& func) {
5630 struct ComponentScopeRestore final {
5631 World& world;
5632 Entity prevScope;
5633 ~ComponentScopeRestore() {
5634 world.scope(prevScope);
5635 }
5636 };
5637
5638 ComponentScopeRestore restore{*this, scope(scopeEntity)};
5639 func();
5640 }
5641
5648 Entity module(const char* path, uint32_t len = 0) {
5649 if (path == nullptr || path[0] == 0)
5650 return EntityBad;
5651
5652 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(path, ComponentCacheItem::MaxNameLength) : len;
5653 if (l == 0 || l >= ComponentCacheItem::MaxNameLength)
5654 return EntityBad;
5655 if (path[l - 1] == '.')
5656 return EntityBad;
5657
5658 Entity parent = EntityBad;
5659 uint32_t partBeg = 0;
5660 while (partBeg < l) {
5661 uint32_t partEnd = partBeg;
5662 while (partEnd < l && path[partEnd] != '.')
5663 ++partEnd;
5664 if (partEnd == partBeg)
5665 return EntityBad;
5666
5667 const auto partLen = partEnd - partBeg;
5668 const auto key = EntityNameLookupKey(path + partBeg, partLen, 0);
5669 const auto it = m_nameToEntity.find(key);
5670 Entity curr = EntityBad;
5671
5672 if (it != m_nameToEntity.end()) {
5673 curr = it->second;
5674 if (parent != EntityBad && !static_cast<const World&>(*this).child(curr, parent)) {
5675 GAIA_ASSERT2(false, "Module path collides with an existing entity name outside the requested scope");
5676 return EntityBad;
5677 }
5678 } else {
5679 curr = add();
5680 name(curr, path + partBeg, partLen);
5681 if (parent != EntityBad)
5682 child(curr, parent);
5683 }
5684
5685 parent = curr;
5686 partBeg = partEnd + 1;
5687 }
5688
5689 return parent;
5690 }
5691
5698 GAIA_NODISCARD bool has(Entity entity, Pair pair) const {
5699 return has(entity, (Entity)pair);
5700 }
5701
5708 template <typename T>
5709 GAIA_NODISCARD bool has(Entity entity) const {
5710 GAIA_ASSERT(valid(entity));
5711
5712 using FT = typename component_type_t<T>::TypeFull;
5713 const auto compEntity = [&]() {
5714 if constexpr (is_pair<FT>::value) {
5715 const auto* pRel = comp_cache().template find<typename FT::rel>();
5716 const auto* pTgt = comp_cache().template find<typename FT::tgt>();
5717 if (pRel == nullptr || pTgt == nullptr)
5718 return EntityBad;
5719
5720 const auto rel = pRel->entity;
5721 const auto tgt = pTgt->entity;
5722 return (Entity)Pair(rel, tgt);
5723 } else {
5724 const auto* pItem = comp_cache().template find<FT>();
5725 return pItem != nullptr ? pItem->entity : EntityBad;
5726 }
5727 }();
5728 if (compEntity == EntityBad)
5729 return false;
5730
5731 if constexpr (supports_out_of_line_component<FT>()) {
5732 const auto* pItem = comp_cache().template find<FT>();
5733 if (pItem != nullptr && out_of_line_mode(pItem->entity) != OutOfLineMode::None) {
5734 return id_owner_inter(entity, compEntity) != EntityBad;
5735 }
5736 }
5737
5738 return id_owner_inter(entity, compEntity) != EntityBad;
5739 }
5740
5741 //----------------------------------------------------------------------
5742
5752 void name(Entity entity, const char* name, uint32_t len = 0) {
5753 EntityBuilder(*this, entity).name(name, len);
5754 }
5755
5769 void name_raw(Entity entity, const char* name, uint32_t len = 0) {
5770 EntityBuilder(*this, entity).name_raw(name, len);
5771 }
5772
5777 GAIA_NODISCARD util::str_view name(Entity entity) const {
5778 if (entity.pair())
5779 return {};
5780
5781 const auto& ec = m_recs.entities[entity.id()];
5782 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
5783 if (compIdx == BadIndex)
5784 return {};
5785
5786 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
5787 GAIA_ASSERT(core::check_alignment(pDesc));
5788 return {pDesc->name, pDesc->name_len};
5789 }
5790
5795 GAIA_NODISCARD util::str_view name(EntityId entityId) const {
5796 auto entity = get(entityId);
5797 return name(entity);
5798 }
5799
5800 //----------------------------------------------------------------------
5801
5809 GAIA_NODISCARD Entity resolve(const char* name, uint32_t len = 0) const {
5810 if (name == nullptr || name[0] == 0)
5811 return EntityBad;
5812
5813 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
5814 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
5815
5816 if (memchr(name, '.', l) != nullptr) {
5817 const auto entity = get_entity_inter(name, l);
5818 if (entity != EntityBad)
5819 return entity;
5820 }
5821
5822 return get_inter(name, l);
5823 }
5824
5830 void resolve(cnt::darray<Entity>& out, const char* name, uint32_t len = 0) const {
5831 out.clear();
5832 if (name == nullptr || name[0] == 0)
5833 return;
5834
5835 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
5836 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
5837
5838 auto push_unique = [&](Entity entity) {
5839 if (entity == EntityBad)
5840 return;
5841 for (const auto existing: out) {
5842 if (existing == entity)
5843 return;
5844 }
5845 out.push_back(entity);
5846 };
5847
5848 push_unique(get_entity_inter(name, l));
5849
5850 if (memchr(name, '.', l) == nullptr && memchr(name, ':', l) == nullptr)
5851 add_comp_lookup_hits_inter(out, name, l);
5852
5853 const bool isPath = memchr(name, '.', l) != nullptr;
5854 const bool isSymbol = memchr(name, ':', l) != nullptr;
5855 add_comp_exact_hits_inter(out, name, l, isPath, isSymbol);
5856
5857 push_unique(alias(name, l));
5858 }
5859
5865 GAIA_NODISCARD Entity get(const char* name, uint32_t len = 0) const {
5866 return resolve(name, len);
5867 }
5868
5869 private:
5870 GAIA_NODISCARD Entity find_named_entity_inter(const char* name, uint32_t len = 0) const {
5871 if (name == nullptr || name[0] == 0)
5872 return EntityBad;
5873
5874 const auto key = EntityNameLookupKey(name, len, 0);
5875 const auto it = m_nameToEntity.find(key);
5876 return it != m_nameToEntity.end() ? it->second : EntityBad;
5877 }
5878
5879 GAIA_NODISCARD Entity get_entity_inter(const char* name, uint32_t len = 0) const {
5880 if (name == nullptr || name[0] == 0)
5881 return EntityBad;
5882
5883 if (len == 0) {
5884 while (name[len] != '\0')
5885 ++len;
5886 }
5887
5888 Entity parent = EntityBad;
5889 Entity child = EntityBad;
5890 uint32_t posDot = 0;
5891 std::span<const char> str(name, len);
5892
5893 posDot = core::get_index(str, '.');
5894 if (posDot == BadIndex)
5895 return find_named_entity_inter(str.data(), (uint32_t)str.size());
5896
5897 if (posDot == 0)
5898 return EntityBad;
5899
5900 parent = find_named_entity_inter(str.data(), posDot);
5901 if (parent == EntityBad)
5902 return EntityBad;
5903
5904 str = str.subspan(posDot + 1);
5905 while (!str.empty()) {
5906 posDot = core::get_index(str, '.');
5907
5908 if (posDot == BadIndex) {
5909 child = find_named_entity_inter(str.data(), (uint32_t)str.size());
5910 if (child == EntityBad || !this->child(child, parent))
5911 return EntityBad;
5912
5913 return child;
5914 }
5915
5916 if (posDot == 0)
5917 return EntityBad;
5918
5919 child = find_named_entity_inter(str.data(), posDot);
5920 if (child == EntityBad || !this->child(child, parent))
5921 return EntityBad;
5922
5923 parent = child;
5924 str = str.subspan(posDot + 1);
5925 }
5926
5927 return parent;
5928 }
5929
5930 GAIA_NODISCARD Entity get_inter(const char* name, uint32_t len = 0) const {
5931 if (name == nullptr || name[0] == 0)
5932 return EntityBad;
5933
5934 auto key = EntityNameLookupKey(name, len, 0);
5935 const auto l = key.len();
5936 const bool isUnqualifiedCompName = is_unqualified_comp_name_inter(name, l);
5937 const auto namedEntity = find_named_entity_inter(name, l);
5938
5939 if (has_comp_lookup_ctx_inter() && isUnqualifiedCompName) {
5940 if (const auto* pScopedItem = resolve_component_name_inter(name, l); pScopedItem != nullptr)
5941 return pick_name_or_comp_inter(namedEntity, pScopedItem);
5942 }
5943
5944 if (namedEntity != EntityBad)
5945 return namedEntity;
5946
5947 if (const auto* pItem = resolve_component_name_inter(name, l); pItem != nullptr)
5948 return pItem->entity;
5949
5950 const auto aliasEntity = alias(name, l);
5951 if (aliasEntity != EntityBad)
5952 return aliasEntity;
5953
5954 // No entity with the given name exists. Return a bad entity
5955 return EntityBad;
5956 }
5957
5958 public:
5959 //----------------------------------------------------------------------
5960
5964 GAIA_NODISCARD const cnt::set<EntityLookupKey>* relations(Entity target) const {
5965 const auto it = m_tgtToRel.find(EntityLookupKey(target));
5966 if (it == m_tgtToRel.end())
5967 return nullptr;
5968
5969 return &it->second;
5970 }
5971
5977 GAIA_NODISCARD Entity relation(Entity entity, Entity target) const {
5978 GAIA_ASSERT(valid(entity));
5979 if (!valid(target))
5980 return EntityBad;
5981
5982 const auto itAdjunctRels = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(entity));
5983 if (itAdjunctRels != m_srcToExclusiveAdjunctRel.end()) {
5984 for (auto relKey: itAdjunctRels->second) {
5985 const auto relation = relKey;
5986 const auto* pStore = exclusive_adjunct_store(relation);
5987 if (pStore == nullptr)
5988 continue;
5989
5990 if (exclusive_adjunct_target(*pStore, entity) == target)
5991 return relation;
5992 }
5993 }
5994
5995 const auto& ec = fetch(entity);
5996 const auto* pArchetype = ec.pArchetype;
5997
5998 // Early exit if there are no pairs on the archetype
5999 if (pArchetype->pairs() == 0)
6000 return EntityBad;
6001
6002 const auto indices = pArchetype->pair_tgt_indices(target);
6003 if (indices.empty())
6004 return EntityBad;
6005
6006 const auto ids = pArchetype->ids_view();
6007 const auto e = ids[indices[0]];
6008 const auto& ecRel = m_recs.entities[e.id()];
6009 return *ecRel.pEntity;
6010 }
6011
6017 template <typename Func>
6018 void relations(Entity entity, Entity target, Func func) const {
6019 GAIA_ASSERT(valid(entity));
6020 if (!valid(target))
6021 return;
6022
6023 const auto itAdjunctRels = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(entity));
6024 if (itAdjunctRels != m_srcToExclusiveAdjunctRel.end()) {
6025 for (auto relKey: itAdjunctRels->second) {
6026 const auto relation = relKey;
6027 const auto* pStore = exclusive_adjunct_store(relation);
6028 if (pStore == nullptr)
6029 continue;
6030
6031 if (exclusive_adjunct_target(*pStore, entity) == target)
6032 func(relation);
6033 }
6034 }
6035
6036 const auto& ec = fetch(entity);
6037 const auto* pArchetype = ec.pArchetype;
6038
6039 // Early exit if there are no pairs on the archetype
6040 if (pArchetype->pairs() == 0)
6041 return;
6042
6043 const auto ids = pArchetype->ids_view();
6044 for (auto idsIdx: pArchetype->pair_tgt_indices(target)) {
6045 const auto e = ids[idsIdx];
6046
6047 const auto& ecRel = m_recs.entities[e.id()];
6048 auto relation = *ecRel.pEntity;
6049 func(relation);
6050 }
6051 }
6052
6059 template <typename Func>
6060 void relations_if(Entity entity, Entity target, Func func) const {
6061 GAIA_ASSERT(valid(entity));
6062 if (!valid(target))
6063 return;
6064
6065 const auto itAdjunctRels = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(entity));
6066 if (itAdjunctRels != m_srcToExclusiveAdjunctRel.end()) {
6067 for (auto relKey: itAdjunctRels->second) {
6068 const auto relation = relKey;
6069 const auto* pStore = exclusive_adjunct_store(relation);
6070 if (pStore == nullptr)
6071 continue;
6072
6073 if (exclusive_adjunct_target(*pStore, entity) == target) {
6074 if (!func(relation))
6075 return;
6076 }
6077 }
6078 }
6079
6080 const auto& ec = fetch(entity);
6081 const auto* pArchetype = ec.pArchetype;
6082
6083 // Early exit if there are no pairs on the archetype
6084 if (pArchetype->pairs() == 0)
6085 return;
6086
6087 const auto ids = pArchetype->ids_view();
6088 for (auto idsIdx: pArchetype->pair_tgt_indices(target)) {
6089 const auto e = ids[idsIdx];
6090
6091 const auto& ecRel = m_recs.entities[e.id()];
6092 auto relation = *ecRel.pEntity;
6093 if (!func(relation))
6094 return;
6095 }
6096 }
6097
6100 GAIA_NODISCARD const cnt::darray<Entity>& as_relations_trav_cache(Entity target) const {
6101 const auto key = EntityLookupKey(target);
6102 const auto itCache = m_entityToAsRelationsTravCache.find(key);
6103 if (itCache != m_entityToAsRelationsTravCache.end())
6104 return itCache->second;
6105
6106 auto& cache = m_entityToAsRelationsTravCache[key];
6107 const auto it = m_entityToAsRelations.find(key);
6108 if (it == m_entityToAsRelations.end())
6109 return cache;
6110
6113 stack.reserve((uint32_t)it->second.size());
6114 for (auto relation: it->second)
6115 stack.push_back(relation);
6116
6117 while (!stack.empty()) {
6118 const auto relationKey = stack.back();
6119 stack.pop_back();
6120
6121 const auto relation = relationKey.entity();
6122 cache.push_back(relation);
6123
6124 const auto itChild = m_entityToAsRelations.find(relationKey);
6125 if (itChild == m_entityToAsRelations.end())
6126 continue;
6127
6128 for (auto childRelation: itChild->second)
6129 stack.push_back(childRelation);
6130 }
6131
6132 return cache;
6133 }
6134
6137 GAIA_NODISCARD const cnt::darray<Entity>& as_targets_trav_cache(Entity relation) const {
6138 const auto key = EntityLookupKey(relation);
6139 const auto itCache = m_entityToAsTargetsTravCache.find(key);
6140 if (itCache != m_entityToAsTargetsTravCache.end())
6141 return itCache->second;
6142
6143 auto& cache = m_entityToAsTargetsTravCache[key];
6144 const auto it = m_entityToAsTargets.find(key);
6145 if (it == m_entityToAsTargets.end())
6146 return cache;
6147
6150 stack.reserve((uint32_t)it->second.size());
6151 for (auto target: it->second)
6152 stack.push_back(target);
6153
6154 while (!stack.empty()) {
6155 const auto targetKey = stack.back();
6156 stack.pop_back();
6157
6158 const auto target = targetKey.entity();
6159 cache.push_back(target);
6160
6161 const auto itChild = m_entityToAsTargets.find(targetKey);
6162 if (itChild == m_entityToAsTargets.end())
6163 continue;
6164
6165 for (auto childTarget: itChild->second)
6166 stack.push_back(childTarget);
6167 }
6168
6169 return cache;
6170 }
6171
6174 GAIA_NODISCARD const cnt::darray<Entity>& targets_trav_cache(Entity relation, Entity source) const {
6175 const auto key = EntityLookupKey(Pair(relation, source));
6176 const auto itCache = m_targetsTravCache.find(key);
6177 if (itCache != m_targetsTravCache.end())
6178 return itCache->second;
6179
6180 auto& cache = m_targetsTravCache[key];
6181 if (!valid(relation) || !valid(source))
6182 return cache;
6183
6184 auto curr = source;
6185 GAIA_FOR(MAX_TRAV_DEPTH) {
6186 const auto next = target(curr, relation);
6187 if (next == EntityBad || next == curr)
6188 break;
6189
6190 cache.push_back(next);
6191 curr = next;
6192 }
6193
6194 return cache;
6195 }
6196
6199 GAIA_NODISCARD const cnt::darray<Entity>& targets_all_cache(Entity source) const {
6200 const auto key = EntityLookupKey(source);
6201 const auto itCache = m_targetsAllCache.find(key);
6202 if (itCache != m_targetsAllCache.end())
6203 return itCache->second;
6204
6205 auto& cache = m_targetsAllCache[key];
6206 if (!valid(source))
6207 return cache;
6208
6209 const auto visitStamp = next_entity_visit_stamp();
6210
6211 const auto itAdjunctRels = m_srcToExclusiveAdjunctRel.find(key);
6212 if (itAdjunctRels != m_srcToExclusiveAdjunctRel.end()) {
6213 for (auto rel: itAdjunctRels->second) {
6214 const auto* pStore = exclusive_adjunct_store(rel);
6215 if (pStore == nullptr)
6216 continue;
6217
6218 const auto target = exclusive_adjunct_target(*pStore, source);
6219 if (target != EntityBad && try_mark_entity_visited(target, visitStamp))
6220 cache.push_back(target);
6221 }
6222 }
6223
6224 const auto& ec = fetch(source);
6225 const auto* pArchetype = ec.pArchetype;
6226 if (pArchetype->pairs() == 0)
6227 return cache;
6228
6229 const auto ids = pArchetype->ids_view();
6230 for (auto idsIdx: pArchetype->pair_indices()) {
6231 const auto id = ids[idsIdx];
6232 const auto target = pair_target_if_alive(id);
6233 if (target == EntityBad)
6234 continue;
6235 if (try_mark_entity_visited(target, visitStamp))
6236 cache.push_back(target);
6237 }
6238
6239 return cache;
6240 }
6241
6244 GAIA_NODISCARD const cnt::darray<Entity>& sources_all_cache(Entity target) const {
6245 const auto key = EntityLookupKey(target);
6246 const auto itCache = m_sourcesAllCache.find(key);
6247 if (itCache != m_sourcesAllCache.end())
6248 return itCache->second;
6249
6250 auto& cache = m_sourcesAllCache[key];
6251 if (!valid(target))
6252 return cache;
6253
6254 const auto visitStamp = next_entity_visit_stamp();
6255
6256 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
6257 (void)relKey;
6258 const auto* pSources = exclusive_adjunct_sources(store, target);
6259 if (pSources == nullptr)
6260 continue;
6261
6262 for (auto source: *pSources) {
6263 if (valid(source) && try_mark_entity_visited(source, visitStamp))
6264 cache.push_back(source);
6265 }
6266 }
6267
6268 const auto pair = Pair(All, target);
6269 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
6270 if (it == m_entityToArchetypeMap.end())
6271 return cache;
6272
6273 for (const auto& record: it->second) {
6274 const auto* pArchetype = record.pArchetype;
6275 if (pArchetype->is_req_del())
6276 continue;
6277
6278 for (const auto* pChunk: pArchetype->chunks()) {
6279 const auto entities = pChunk->entity_view();
6280 GAIA_EACH(entities) {
6281 const auto source = entities[i];
6282 if (!valid(source))
6283 continue;
6284 if (try_mark_entity_visited(source, visitStamp))
6285 cache.push_back(source);
6286 }
6287 }
6288 }
6289
6290 return cache;
6291 }
6292
6295 template <typename Func>
6296 void targets_trav(Entity relation, Entity source, Func func) const {
6297 if (!valid(relation) || !valid(source))
6298 return;
6299
6300 auto curr = source;
6301 GAIA_FOR(MAX_TRAV_DEPTH) {
6302 const auto next = target(curr, relation);
6303 if (next == EntityBad || next == curr)
6304 break;
6305 if (!enabled(next))
6306 break;
6307
6308 func(next);
6309 curr = next;
6310 }
6311 }
6312
6316 template <typename Func>
6317 GAIA_NODISCARD bool targets_trav_if(Entity relation, Entity source, Func func) const {
6318 if (!valid(relation) || !valid(source))
6319 return false;
6320
6321 auto curr = source;
6322 GAIA_FOR(MAX_TRAV_DEPTH) {
6323 const auto next = target(curr, relation);
6324 if (next == EntityBad || next == curr)
6325 break;
6326 if (!enabled(next))
6327 break;
6328
6329 if (!func(next))
6330 return true;
6331 curr = next;
6332 }
6333
6334 return false;
6335 }
6336
6339 GAIA_NODISCARD const cnt::darray<Entity>& sources_bfs_trav_cache(Entity relation, Entity rootTarget) const {
6340 const auto key = EntityLookupKey(Pair(relation, rootTarget));
6341 const auto itCache = m_srcBfsTravCache.find(key);
6342 if (itCache != m_srcBfsTravCache.end())
6343 return itCache->second;
6344
6345 auto& cache = m_srcBfsTravCache[key];
6346 if (!valid(relation) || !valid(rootTarget))
6347 return cache;
6348
6350 queue.push_back(rootTarget);
6351
6353 visited.insert(EntityLookupKey(rootTarget));
6354
6355 for (uint32_t i = 0; i < queue.size(); ++i) {
6356 const auto currTarget = queue[i];
6357
6358 cnt::darray<Entity> children;
6359 sources(relation, currTarget, [&](Entity source) {
6360 const auto keySource = EntityLookupKey(source);
6361 const auto ins = visited.insert(keySource);
6362 if (!ins.second)
6363 return;
6364
6365 children.push_back(source);
6366 });
6367
6368 core::sort(children, [](Entity left, Entity right) {
6369 return left.id() < right.id();
6370 });
6371
6372 for (auto child: children) {
6373 cache.push_back(child);
6374 queue.push_back(child);
6375 }
6376 }
6377
6378 return cache;
6379 }
6380
6384 GAIA_NODISCARD uint32_t depth_order_cache(Entity relation, Entity sourceTarget) const {
6385 const auto key = EntityLookupKey(Pair(relation, sourceTarget));
6386 const auto itCache = m_depthOrderCache.find(key);
6387 if (itCache != m_depthOrderCache.end()) {
6388 GAIA_ASSERT(itCache->second != GroupIdMax && "depth_order requires an acyclic relation graph");
6389 return itCache->second;
6390 }
6391
6392 if (!valid(relation) || !valid(sourceTarget))
6393 return 0;
6394
6395 // Mark this node as in-flight so cycles trip a debug assert instead of recursing forever.
6396 m_depthOrderCache[key] = GroupIdMax;
6397
6398 uint32_t depth = 1;
6399 targets(sourceTarget, relation, [&](Entity next) {
6400 const auto nextDepth = depth_order_cache(relation, next);
6401 if (nextDepth == 0)
6402 return;
6403 const auto candidate = nextDepth + 1;
6404 if (candidate > depth)
6405 depth = candidate;
6406 });
6407
6408 m_depthOrderCache[key] = depth;
6409 return depth;
6410 }
6411
6416 template <typename Func>
6417 void as_relations_trav(Entity target, Func func) const {
6418 if (!valid(target))
6419 return;
6420
6421 const auto& relations = as_relations_trav_cache(target);
6422 for (auto relation: relations)
6423 func(relation);
6424 }
6425
6431 template <typename Func>
6432 GAIA_NODISCARD bool as_relations_trav_if(Entity target, Func func) const {
6433 if (!valid(target))
6434 return false;
6435
6436 const auto& relations = as_relations_trav_cache(target);
6437 for (auto relation: relations) {
6438 if (func(relation))
6439 return true;
6440 }
6441
6442 return false;
6443 }
6444
6445 //----------------------------------------------------------------------
6446
6450 GAIA_NODISCARD const cnt::set<EntityLookupKey>* targets(Entity relation) const {
6451 const auto it = m_relToTgt.find(EntityLookupKey(relation));
6452 if (it == m_relToTgt.end())
6453 return nullptr;
6454
6455 return &it->second;
6456 }
6457
6463 GAIA_NODISCARD Entity target(Entity entity, Entity relation) const {
6464 if (!valid(entity))
6465 return EntityBad;
6466 if (relation != All && !valid(relation))
6467 return EntityBad;
6468
6469 if (relation == All) {
6470 const auto& targets = targets_all_cache(entity);
6471 return targets.empty() ? EntityBad : targets[0];
6472 }
6473
6474 if (is_exclusive_dont_fragment_relation(relation)) {
6475 const auto* pStore = exclusive_adjunct_store(relation);
6476 if (pStore == nullptr)
6477 return EntityBad;
6478
6479 return exclusive_adjunct_target(*pStore, entity);
6480 }
6481
6482 const auto& ec = fetch(entity);
6483 const auto* pArchetype = ec.pArchetype;
6484
6485 // Early exit if there are no pairs on the archetype
6486 if (pArchetype->pairs() == 0)
6487 return EntityBad;
6488
6489 const auto ids = pArchetype->ids_view();
6490 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
6491 const auto e = ids[idsIdx];
6492 const auto target = pair_target_if_alive(e);
6493 if (target == EntityBad)
6494 continue;
6495 return target;
6496 }
6497
6498 return EntityBad;
6499 }
6500
6506 template <typename Func>
6507 void targets(Entity entity, Entity relation, Func func) const {
6508 if (!valid(entity))
6509 return;
6510 if (relation != All && !valid(relation))
6511 return;
6512
6513 if (relation == All) {
6514 for (auto target: targets_all_cache(entity))
6515 func(target);
6516 return;
6517 }
6518
6519 if (is_exclusive_dont_fragment_relation(relation)) {
6520 const auto target = this->target(entity, relation);
6521 if (target != EntityBad)
6522 func(target);
6523 return;
6524 }
6525
6526 const auto& ec = fetch(entity);
6527 const auto* pArchetype = ec.pArchetype;
6528
6529 // Early exit if there are no pairs on the archetype
6530 if (pArchetype->pairs() == 0)
6531 return;
6532
6533 const auto ids = pArchetype->ids_view();
6534 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
6535 const auto e = ids[idsIdx];
6536 const auto target = pair_target_if_alive(e);
6537 if (target == EntityBad)
6538 continue;
6539 func(target);
6540 }
6541 }
6542
6549 template <typename Func>
6550 void targets_if(Entity entity, Entity relation, Func func) const {
6551 GAIA_ASSERT(valid(entity));
6552 if (relation != All && !valid(relation))
6553 return;
6554
6555 if (relation == All) {
6556 for (auto target: targets_all_cache(entity)) {
6557 if (!func(target))
6558 return;
6559 }
6560 return;
6561 }
6562
6563 if (is_exclusive_dont_fragment_relation(relation)) {
6564 const auto target = this->target(entity, relation);
6565 if (target != EntityBad)
6566 (void)func(target);
6567 return;
6568 }
6569
6570 const auto& ec = fetch(entity);
6571 const auto* pArchetype = ec.pArchetype;
6572
6573 // Early exit if there are no pairs on the archetype
6574 if (pArchetype->pairs() == 0)
6575 return;
6576
6577 const auto ids = pArchetype->ids_view();
6578 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
6579 const auto e = ids[idsIdx];
6580 const auto target = pair_target_if_alive(e);
6581 if (target == EntityBad)
6582 continue;
6583 if (!func(target))
6584 return;
6585 }
6586 }
6587
6589 GAIA_NODISCARD Entity pair_target_if_alive(Entity pair) const {
6590 GAIA_ASSERT(pair.pair());
6591 if (!valid_entity_id((EntityId)pair.gen()))
6592 return EntityBad;
6593
6594 const auto& ecTarget = m_recs.entities[pair.gen()];
6595 if (ecTarget.pEntity == nullptr)
6596 return EntityBad;
6597
6598 const auto target = *ecTarget.pEntity;
6599 return valid(target) ? target : EntityBad;
6600 }
6601
6607 template <typename Func>
6608 void sources(Entity relation, Entity target, Func func) const {
6609 if ((relation != All && !valid(relation)) || !valid(target))
6610 return;
6611
6612 if (relation == All) {
6613 for (auto source: sources_all_cache(target))
6614 func(source);
6615 return;
6616 }
6617
6618 if (is_exclusive_dont_fragment_relation(relation)) {
6619 const auto* pStore = exclusive_adjunct_store(relation);
6620 if (pStore == nullptr)
6621 return;
6622
6623 const auto* pSources = exclusive_adjunct_sources(*pStore, target);
6624 if (pSources == nullptr)
6625 return;
6626
6627 for (auto source: *pSources) {
6628 if (!valid(source))
6629 continue;
6630
6631 func(source);
6632 }
6633 return;
6634 }
6635
6636 const auto pair = Pair(relation, target);
6637 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
6638 if (it == m_entityToArchetypeMap.end())
6639 return;
6640
6641 for (const auto& record: it->second) {
6642 const auto* pArchetype = record.pArchetype;
6643 if (pArchetype->is_req_del())
6644 continue;
6645
6646 for (const auto* pChunk: pArchetype->chunks()) {
6647 auto entities = pChunk->entity_view();
6648 GAIA_EACH(entities) {
6649 const auto source = entities[i];
6650 if (!valid(source))
6651 continue;
6652 func(source);
6653 }
6654 }
6655 }
6656 }
6657
6664 template <typename Func>
6665 void sources_if(Entity relation, Entity target, Func func) const {
6666 if ((relation != All && !valid(relation)) || !valid(target))
6667 return;
6668
6669 if (relation == All) {
6670 for (auto source: sources_all_cache(target)) {
6671 if (!func(source))
6672 return;
6673 }
6674 return;
6675 }
6676
6677 if (is_exclusive_dont_fragment_relation(relation)) {
6678 const auto* pStore = exclusive_adjunct_store(relation);
6679 if (pStore == nullptr)
6680 return;
6681
6682 const auto* pSources = exclusive_adjunct_sources(*pStore, target);
6683 if (pSources == nullptr)
6684 return;
6685
6686 for (auto source: *pSources) {
6687 if (!valid(source))
6688 continue;
6689
6690 if (!func(source))
6691 return;
6692 }
6693 return;
6694 }
6695
6696 const auto pair = Pair(relation, target);
6697 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
6698 if (it == m_entityToArchetypeMap.end())
6699 return;
6700
6701 for (const auto& record: it->second) {
6702 const auto* pArchetype = record.pArchetype;
6703 if (pArchetype->is_req_del())
6704 continue;
6705
6706 for (const auto* pChunk: pArchetype->chunks()) {
6707 auto entities = pChunk->entity_view();
6708 GAIA_EACH(entities) {
6709 const auto source = entities[i];
6710 if (!valid(source))
6711 continue;
6712 if (!func(source))
6713 return;
6714 }
6715 }
6716 }
6717 }
6718
6719 private:
6720 GAIA_NODISCARD uint64_t next_entity_visit_stamp() const {
6721 ++m_entityVisitStamp;
6722 if (m_entityVisitStamp != 0)
6723 return m_entityVisitStamp;
6724
6725 const auto cnt = (uint32_t)m_entityVisitStamps.size();
6726 GAIA_FOR(cnt) {
6727 m_entityVisitStamps[i] = 0;
6728 }
6729
6730 m_entityVisitStamp = 1;
6731 return m_entityVisitStamp;
6732 }
6733
6734 GAIA_NODISCARD bool try_mark_entity_visited(Entity entity, uint64_t stamp) const {
6735 GAIA_ASSERT(!entity.pair());
6736 if (entity.id() >= m_entityVisitStamps.size())
6737 m_entityVisitStamps.resize(m_recs.entities.size(), 0);
6738
6739 auto& slot = m_entityVisitStamps[entity.id()];
6740 if (slot == stamp)
6741 return false;
6742
6743 slot = stamp;
6744 return true;
6745 }
6746
6747 template <typename Func>
6748 GAIA_NODISCARD bool for_each_inherited_term_entity(Entity term, Func&& func) const {
6749 cnt::set<EntityLookupKey> seen;
6750 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
6751 if (it == m_entityToArchetypeMap.end())
6752 return true;
6753
6754 for (const auto& record: it->second) {
6755 const auto* pArchetype = record.pArchetype;
6756 if (pArchetype->is_req_del())
6757 continue;
6758
6759 for (const auto* pChunk: pArchetype->chunks()) {
6760 const auto entities = pChunk->entity_view();
6761 GAIA_EACH(entities) {
6762 const auto entity = entities[i];
6763 GAIA_ASSERT(valid(entity));
6764 const auto entityKey = EntityLookupKey(entity);
6765 if (seen.contains(entityKey))
6766 continue;
6767 seen.insert(entityKey);
6768
6769 if (!func(entity))
6770 return false;
6771
6772 const auto& descendants = as_relations_trav_cache(entity);
6773 for (const auto descendant: descendants) {
6774 GAIA_ASSERT(valid(descendant));
6775 const auto descendantKey = EntityLookupKey(descendant);
6776 if (seen.contains(descendantKey))
6777 continue;
6778 seen.insert(descendantKey);
6779
6780 if (!func(descendant))
6781 return false;
6782 }
6783 }
6784 }
6785 }
6786
6787 return true;
6788 }
6789
6792 GAIA_NODISCARD uint32_t count_direct_term_entities_inter(Entity term, bool allowSemanticIs) const {
6793 if (term == EntityBad)
6794 return 0;
6795
6796 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
6797 const auto target = get(term.gen());
6798 if (!valid(target))
6799 return 0;
6800
6801 return (uint32_t)as_relations_trav_cache(target).size() + 1;
6802 }
6803
6804 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
6805 uint32_t cnt = 0;
6806 (void)for_each_inherited_term_entity(term, [&](Entity) {
6807 ++cnt;
6808 return true;
6809 });
6810 return cnt;
6811 }
6812
6813 if (term.pair() && is_exclusive_dont_fragment_relation(pair_rel(*this, term))) {
6814 const auto relation = pair_rel(*this, term);
6815 const auto* pStore = exclusive_adjunct_store(relation);
6816 if (pStore == nullptr)
6817 return 0;
6818
6819 if (is_wildcard(term.gen()))
6820 return pStore->srcToTgtCnt;
6821
6822 const auto* pSources = exclusive_adjunct_sources(*pStore, pair_tgt(*this, term));
6823 return pSources != nullptr ? (uint32_t)pSources->size() : 0;
6824 }
6825
6826 if (!term.pair() && is_non_fragmenting_out_of_line_component(term)) {
6827 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
6828 return it != m_sparseComponentsByComp.end() ? it->second.func_count(it->second.pStore) : 0;
6829 }
6830
6831 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
6832 if (it == m_entityToArchetypeMap.end())
6833 return 0;
6834
6835 uint32_t cnt = 0;
6836 for (const auto& record: it->second) {
6837 const auto* pArchetype = record.pArchetype;
6838 if (pArchetype->is_req_del())
6839 continue;
6840 for (const auto* pChunk: pArchetype->chunks())
6841 cnt += pChunk->size();
6842 }
6843
6844 return cnt;
6845 }
6846
6848 void collect_direct_term_entities_inter(Entity term, cnt::darray<Entity>& out, bool allowSemanticIs) const {
6849 if (term == EntityBad)
6850 return;
6851
6852 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
6853 const auto target = get(term.gen());
6854 if (!valid(target))
6855 return;
6856
6857 out.push_back(target);
6858 const auto& relations = as_relations_trav_cache(target);
6859 out.reserve(out.size() + (uint32_t)relations.size());
6860 for (auto relation: relations)
6861 out.push_back(relation);
6862 return;
6863 }
6864
6865 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
6866 (void)for_each_inherited_term_entity(term, [&](Entity entity) {
6867 out.push_back(entity);
6868 return true;
6869 });
6870 return;
6871 }
6872
6873 if (term.pair() && is_exclusive_dont_fragment_relation(pair_rel(*this, term))) {
6874 const auto relation = pair_rel(*this, term);
6875 const auto* pStore = exclusive_adjunct_store(relation);
6876 if (pStore == nullptr)
6877 return;
6878
6879 if (is_wildcard(term.gen())) {
6880 out.reserve(out.size() + pStore->srcToTgtCnt);
6881 const auto cnt = (uint32_t)pStore->srcToTgt.size();
6882 GAIA_FOR(cnt) {
6883 const auto target = pStore->srcToTgt[i];
6884 if (target == EntityBad)
6885 continue;
6886 if (!m_recs.entities.has(i))
6887 continue;
6888 out.push_back(EntityContainer::handle(m_recs.entities[i]));
6889 }
6890 return;
6891 }
6892
6893 const auto* pSources = exclusive_adjunct_sources(*pStore, pair_tgt(*this, term));
6894 if (pSources == nullptr)
6895 return;
6896
6897 out.reserve(out.size() + (uint32_t)pSources->size());
6898 for (auto source: *pSources)
6899 out.push_back(source);
6900 return;
6901 }
6902
6903 if (!term.pair() && is_non_fragmenting_out_of_line_component(term)) {
6904 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
6905 if (it != m_sparseComponentsByComp.end())
6906 it->second.func_collect_entities(it->second.pStore, out);
6907 return;
6908 }
6909
6910 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
6911 if (it == m_entityToArchetypeMap.end())
6912 return;
6913
6914 for (const auto& record: it->second) {
6915 const auto* pArchetype = record.pArchetype;
6916 if (pArchetype->is_req_del())
6917 continue;
6918
6919 for (const auto* pChunk: pArchetype->chunks()) {
6920 const auto entities = pChunk->entity_view();
6921 out.reserve(out.size() + (uint32_t)entities.size());
6922 GAIA_EACH(entities)
6923 out.push_back(entities[i]);
6924 }
6925 }
6926 }
6927
6929 GAIA_NODISCARD bool for_each_direct_term_entity_inter(
6930 Entity term, void* ctx, bool (*func)(void*, Entity), bool allowSemanticIs) const {
6931 if (term == EntityBad)
6932 return true;
6933
6934 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
6935 const auto target = get(term.gen());
6936 if (!valid(target))
6937 return true;
6938
6939 if (!func(ctx, target))
6940 return false;
6941
6942 const auto& relations = as_relations_trav_cache(target);
6943 for (auto relation: relations) {
6944 if (!func(ctx, relation))
6945 return false;
6946 }
6947 return true;
6948 }
6949
6950 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
6951 return for_each_inherited_term_entity(term, [&](Entity entity) {
6952 return func(ctx, entity);
6953 });
6954 }
6955
6956 if (term.pair() && is_exclusive_dont_fragment_relation(pair_rel(*this, term))) {
6957 const auto relation = pair_rel(*this, term);
6958 const auto* pStore = exclusive_adjunct_store(relation);
6959 if (pStore == nullptr)
6960 return true;
6961
6962 if (is_wildcard(term.gen())) {
6963 const auto cnt = (uint32_t)pStore->srcToTgt.size();
6964 GAIA_FOR(cnt) {
6965 const auto target = pStore->srcToTgt[i];
6966 if (target == EntityBad)
6967 continue;
6968 if (!m_recs.entities.has(i))
6969 continue;
6970 if (!func(ctx, EntityContainer::handle(m_recs.entities[i])))
6971 return false;
6972 }
6973 return true;
6974 }
6975
6976 const auto* pSources = exclusive_adjunct_sources(*pStore, pair_tgt(*this, term));
6977 if (pSources == nullptr)
6978 return true;
6979
6980 for (auto source: *pSources) {
6981 if (!func(ctx, source))
6982 return false;
6983 }
6984 return true;
6985 }
6986
6987 if (!term.pair() && is_non_fragmenting_out_of_line_component(term)) {
6988 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
6989 if (it == m_sparseComponentsByComp.end())
6990 return true;
6991 return it->second.func_for_each_entity(it->second.pStore, ctx, func);
6992 }
6993
6994 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
6995 if (it == m_entityToArchetypeMap.end())
6996 return true;
6997
6998 for (const auto& record: it->second) {
6999 const auto* pArchetype = record.pArchetype;
7000 if (pArchetype->is_req_del())
7001 continue;
7002
7003 for (const auto* pChunk: pArchetype->chunks()) {
7004 const auto entities = pChunk->entity_view();
7005 GAIA_EACH(entities) {
7006 if (!func(ctx, entities[i]))
7007 return false;
7008 }
7009 }
7010 }
7011
7012 return true;
7013 }
7014
7015 public:
7019 GAIA_NODISCARD uint32_t count_direct_term_entities(Entity term) const {
7020 return count_direct_term_entities_inter(term, true);
7021 }
7022
7026 GAIA_NODISCARD uint32_t count_direct_term_entities_direct(Entity term) const {
7027 return count_direct_term_entities_inter(term, false);
7028 }
7029
7034 collect_direct_term_entities_inter(term, out, true);
7035 }
7036
7041 collect_direct_term_entities_inter(term, out, false);
7042 }
7043
7049 GAIA_NODISCARD bool for_each_direct_term_entity(Entity term, void* ctx, bool (*func)(void*, Entity)) const {
7050 return for_each_direct_term_entity_inter(term, ctx, func, true);
7051 }
7052
7058 GAIA_NODISCARD bool
7059 for_each_direct_term_entity_direct(Entity term, void* ctx, bool (*func)(void*, Entity)) const {
7060 return for_each_direct_term_entity_inter(term, ctx, func, false);
7061 }
7062
7068 template <typename Func>
7069 void sources_bfs(Entity relation, Entity rootTarget, Func func) const {
7070 if (!valid(relation) || !valid(rootTarget))
7071 return;
7072
7073 cnt::darray<Entity> queue;
7074 queue.push_back(rootTarget);
7075
7077 visited.insert(EntityLookupKey(rootTarget));
7078
7079 for (uint32_t i = 0; i < queue.size(); ++i) {
7080 const auto currTarget = queue[i];
7081
7082 cnt::darray<Entity> children;
7083 sources(relation, currTarget, [&](Entity source) {
7084 const auto key = EntityLookupKey(source);
7085 const auto ins = visited.insert(key);
7086 if (!ins.second)
7087 return;
7088
7089 children.push_back(source);
7090 });
7091
7092 core::sort(children, [](Entity left, Entity right) {
7093 return left.id() < right.id();
7094 });
7095
7096 for (auto child: children) {
7097 if (!enabled(child))
7098 continue;
7099 func(child);
7100 queue.push_back(child);
7101 }
7102 }
7103 }
7104
7112 template <typename Func>
7113 GAIA_NODISCARD bool sources_bfs_if(Entity relation, Entity rootTarget, Func func) const {
7114 if (!valid(relation) || !valid(rootTarget))
7115 return false;
7116
7117 cnt::darray<Entity> queue;
7118 queue.push_back(rootTarget);
7119
7121 visited.insert(EntityLookupKey(rootTarget));
7122
7123 for (uint32_t i = 0; i < queue.size(); ++i) {
7124 const auto currTarget = queue[i];
7125
7126 cnt::darray<Entity> children;
7127 sources(relation, currTarget, [&](Entity source) {
7128 const auto key = EntityLookupKey(source);
7129 const auto ins = visited.insert(key);
7130 if (!ins.second)
7131 return;
7132
7133 children.push_back(source);
7134 });
7135
7136 core::sort(children, [](Entity left, Entity right) {
7137 return left.id() < right.id();
7138 });
7139
7140 for (auto child: children) {
7141 if (!enabled(child))
7142 continue;
7143 if (func(child))
7144 return true;
7145
7146 queue.push_back(child);
7147 }
7148 }
7149
7150 return false;
7151 }
7152
7156 template <typename Func>
7157 void children(Entity parent, Func func) const {
7158 sources(ChildOf, parent, func);
7159 }
7160
7165 template <typename Func>
7166 void children_if(Entity parent, Func func) const {
7167 sources_if(ChildOf, parent, func);
7168 }
7169
7173 template <typename Func>
7174 void children_bfs(Entity root, Func func) const {
7175 sources_bfs(ChildOf, root, func);
7176 }
7177
7183 template <typename Func>
7184 GAIA_NODISCARD bool children_bfs_if(Entity root, Func func) const {
7185 return sources_bfs_if(ChildOf, root, func);
7186 }
7187
7192 template <typename Func>
7193 void as_targets_trav(Entity relation, Func func) const {
7194 GAIA_ASSERT(valid(relation));
7195 if (!valid(relation))
7196 return;
7197
7198 const auto& targets = as_targets_trav_cache(relation);
7199 for (auto target: targets)
7200 func(target);
7201 }
7202
7208 template <typename Func>
7209 bool as_targets_trav_if(Entity relation, Func func) const {
7210 GAIA_ASSERT(valid(relation));
7211 if (!valid(relation))
7212 return false;
7213
7214 const auto& targets = as_targets_trav_cache(relation);
7215 for (auto target: targets)
7216 if (func(target))
7217 return true;
7218
7219 return false;
7220 }
7221
7222 //----------------------------------------------------------------------
7223
7227 return *m_pCmdBufferST;
7228 }
7229
7233 return *m_pCmdBufferMT;
7234 }
7235
7236 //----------------------------------------------------------------------
7237
7238#if GAIA_SYSTEMS_ENABLED
7239
7241 void systems_init();
7242
7260 void systems_run();
7261
7264 SystemBuilder system();
7265
7267 SystemRegistry& systems() {
7268 return m_systems;
7269 }
7270
7272 const SystemRegistry& systems() const {
7273 return m_systems;
7274 }
7275
7276#endif
7277
7278#if GAIA_OBSERVERS_ENABLED
7279
7282 ObserverBuilder observer();
7283
7285 ObserverRegistry& observers() {
7286 return m_observers;
7287 }
7288
7290 const ObserverRegistry& observers() const {
7291 return m_observers;
7292 }
7293
7294#endif
7295
7296 //----------------------------------------------------------------------
7297
7302 void enable(Entity entity, bool enable) {
7303 GAIA_ASSERT(valid(entity));
7304
7305 auto& ec = m_recs.entities[entity.id()];
7306 auto& archetype = *ec.pArchetype;
7307 auto* pChunk = ec.pChunk;
7308 const bool wasEnabled = !ec.data.dis;
7309#if GAIA_ASSERT_ENABLED
7310 verify_enable(*this, archetype, entity);
7311#endif
7312 archetype.enable_entity(ec.pChunk, ec.row, enable, m_recs);
7313
7314 if (wasEnabled != enable) {
7315 pChunk->update_world_version();
7316 pChunk->update_entity_order_version();
7317 update_version(m_enabledHierarchyVersion);
7318 update_version(m_worldVersion);
7319 }
7320 }
7321
7325 GAIA_NODISCARD bool enabled(const EntityContainer& ec) const {
7326 const bool entityStateInContainer = !ec.data.dis;
7327#if GAIA_ASSERT_ENABLED
7328 const bool entityStateInChunk = ec.pChunk->enabled(ec.row);
7329 GAIA_ASSERT(entityStateInChunk == entityStateInContainer);
7330#endif
7331 return entityStateInContainer;
7332 }
7333
7338 GAIA_NODISCARD bool enabled(Entity entity) const {
7339 GAIA_ASSERT(valid(entity));
7340
7341 const auto& ec = m_recs.entities[entity.id()];
7342 return enabled(ec);
7343 }
7344
7347 GAIA_NODISCARD bool enabled_hierarchy(Entity entity, Entity relation) const {
7348 GAIA_ASSERT(valid(entity));
7349 GAIA_ASSERT(valid(relation));
7350 if (!valid(entity) || !valid(relation))
7351 return false;
7352 if (!enabled(entity))
7353 return false;
7354
7355 auto curr = entity;
7356 GAIA_FOR(MAX_TRAV_DEPTH) {
7357 const auto next = target(curr, relation);
7358 if (next == EntityBad || next == curr)
7359 break;
7360 if (!enabled(next))
7361 return false;
7362 curr = next;
7363 }
7364
7365 return true;
7366 }
7367
7368 //----------------------------------------------------------------------
7369
7373 GAIA_NODISCARD Chunk* get_chunk(Entity entity) const {
7374 GAIA_ASSERT(entity.id() < m_recs.entities.size());
7375 const auto& ec = m_recs.entities[entity.id()];
7376 return ec.pChunk;
7377 }
7378
7384 GAIA_NODISCARD Chunk* get_chunk(Entity entity, uint32_t& row) const {
7385 GAIA_ASSERT(entity.id() < m_recs.entities.size());
7386 const auto& ec = m_recs.entities[entity.id()];
7387 row = ec.row;
7388 return ec.pChunk;
7389 }
7390
7393 GAIA_NODISCARD uint32_t size() const {
7394 return m_recs.entities.item_count();
7395 }
7396
7399 uint32_t& outArchetypes, uint32_t& outChunks, uint32_t& outEntitiesTotal, uint32_t& outEntitiesActive) const {
7400 outArchetypes = (uint32_t)m_archetypes.size();
7401 outChunks = 0;
7402 outEntitiesTotal = 0;
7403 outEntitiesActive = 0;
7404
7405 for (const auto* pArchetype: m_archetypes) {
7406 if (pArchetype == nullptr)
7407 continue;
7408 const auto& chunks = pArchetype->chunks();
7409 outChunks += (uint32_t)chunks.size();
7410 for (const auto* pChunk: chunks) {
7411 if (pChunk == nullptr)
7412 continue;
7413 outEntitiesTotal += pChunk->size();
7414 outEntitiesActive += pChunk->size_enabled();
7415 }
7416 }
7417 }
7418
7421 GAIA_NODISCARD uint32_t& world_version() {
7422 return m_worldVersion;
7423 }
7424
7427 GAIA_NODISCARD uint32_t rel_version(Entity relation) const {
7428 const auto it = m_relationVersions.find(EntityLookupKey(relation));
7429 return it != m_relationVersions.end() ? it->second : 0;
7430 }
7431
7435 GAIA_NODISCARD uint32_t enabled_hierarchy_version() const {
7436 return m_enabledHierarchyVersion;
7437 }
7438
7439 friend uint32_t world_rel_version(const World& world, Entity relation);
7440 friend uint32_t world_version(const World& world);
7441 friend uint32_t world_entity_archetype_version(const World& world, Entity entity);
7442
7445 const auto key = EntityLookupKey(entity);
7446 const auto it = m_srcEntityVersions.find(key);
7447 if (it == m_srcEntityVersions.end())
7448 return;
7449
7450 update_version(it->second);
7451 }
7452
7455 m_srcEntityVersions.erase(EntityLookupKey(entity));
7456 }
7457
7462 void set_max_lifespan(Entity entity, uint32_t lifespan = Archetype::MAX_ARCHETYPE_LIFESPAN) {
7463 if (!valid(entity))
7464 return;
7465
7466 auto& ec = fetch(entity);
7467 const auto prevLifespan = ec.pArchetype->max_lifespan();
7468 ec.pArchetype->set_max_lifespan(lifespan);
7469
7470 if (prevLifespan == 0) {
7471 // The archetype used to be immortal but not anymore
7472 try_enqueue_archetype_for_deletion(*ec.pArchetype);
7473 }
7474 }
7475
7476 //----------------------------------------------------------------------
7477
7488 // Finish deleting entities
7489 del_finalize();
7490
7491 // Run garbage collector
7492 gc();
7493 }
7494
7504 void frame_end() {
7505 util::log_flush();
7506
7507 // Signal the end of the frame
7508 GAIA_PROF_FRAME();
7509 }
7510
7519 void update() {
7520 systems_run();
7521 frame_cleanup();
7522 frame_end();
7523 }
7524
7528 void teardown() {
7529 if GAIA_UNLIKELY (m_teardownActive)
7530 return;
7531 m_teardownActive = true;
7532
7533 GAIA_PROF_SCOPE(World::teardown);
7534
7535#if GAIA_SYSTEMS_ENABLED
7536 systems_done();
7537 m_systems.teardown();
7538#endif
7539
7540#if GAIA_OBSERVERS_ENABLED
7541 m_observers.teardown();
7542#endif
7543
7544 for (;;) {
7545 const auto prevReqArchetypes = m_reqArchetypesToDel.size();
7546 const auto prevReqEntities = m_reqEntitiesToDel.size();
7547 const auto prevChunks = m_chunksToDel.size();
7548 const auto prevArchetypes = m_archetypesToDel.size();
7549
7550 del_finalize();
7551 gc();
7552
7553 if (m_reqArchetypesToDel.empty() && m_reqEntitiesToDel.empty() && m_chunksToDel.empty() &&
7554 m_archetypesToDel.empty())
7555 break;
7556
7557 const bool madeProgress = m_reqArchetypesToDel.size() != prevReqArchetypes ||
7558 m_reqEntitiesToDel.size() != prevReqEntities ||
7559 m_chunksToDel.size() != prevChunks || m_archetypesToDel.size() != prevArchetypes;
7560 if (!madeProgress)
7561 break;
7562 }
7563
7564 util::log_flush();
7565 }
7566
7568 void cleanup() {
7569 cleanup_inter();
7570
7571 // Reinit
7572 m_pRootArchetype = nullptr;
7573 m_pEntityArchetype = nullptr;
7574 m_pCompArchetype = nullptr;
7575 m_nextArchetypeId = 0;
7576 m_defragLastArchetypeIdx = 0;
7577 m_worldVersion = 0;
7578 m_enabledHierarchyVersion = 0;
7579 init();
7580 }
7581
7584 void defrag_entities_per_tick(uint32_t value) {
7585 m_defragEntitiesPerTick = value;
7586 }
7587
7588 //--------------------------------------------------------------------------------
7589
7591 void diag_archetypes() const {
7592 GAIA_LOG_N("Archetypes:%u", (uint32_t)m_archetypes.size());
7593 for (auto* pArchetype: m_archetypes)
7594 Archetype::diag(*this, *pArchetype);
7595 }
7596
7599 void diag_components() const {
7600 comp_cache().diag();
7601 }
7602
7605 void diag_entities() const {
7606 validate_entities();
7607
7608 GAIA_LOG_N("Deleted entities: %u", (uint32_t)m_recs.entities.get_free_items());
7609 if (m_recs.entities.get_free_items() != 0U) {
7610 GAIA_LOG_N(" --> %u", (uint32_t)m_recs.entities.get_next_free_item());
7611
7612 uint32_t iters = 0;
7613 auto fe = m_recs.entities.next_free(m_recs.entities.get_next_free_item());
7614 while (fe != IdentifierIdBad) {
7615 GAIA_LOG_N(" --> %u", m_recs.entities.next_free(fe));
7616 fe = m_recs.entities.next_free(fe);
7617 ++iters;
7618 if (iters > m_recs.entities.get_free_items())
7619 break;
7620 }
7621
7622 if ((iters == 0U) || iters > m_recs.entities.get_free_items())
7623 GAIA_LOG_E(" Entities recycle list contains inconsistent data!");
7624 }
7625 }
7626
7628 void diag() const {
7629 diag_archetypes();
7630 diag_components();
7631 diag_entities();
7632 }
7633
7634 private:
7636 void cleanup_inter() {
7637 GAIA_PROF_SCOPE(World::cleanup_inter);
7638
7639 // Shutdown bypasses the regular GC path, so clear raw-pointer tracking first.
7640 // Chunk/component dtors that run while archetypes are freed can still drop cached queries,
7641 // but after this point they must not touch stale archetype/chunk reverse indices.
7642 {
7643 m_queryCache.clear_archetype_tracking();
7644 m_reqArchetypesToDel = {};
7645 m_reqEntitiesToDel = {};
7646 m_entitiesToDel = {};
7647 m_chunksToDel = {};
7648 m_archetypesToDel = {};
7649 }
7650
7651 // Clear entities
7652 m_recs.entities = {};
7653 m_recs.pairs = {};
7654
7655 // Clear archetypes
7656 {
7657 // Delete all allocated chunks and their parent archetypes
7658 for (auto* pArchetype: m_archetypes)
7659 Archetype::destroy(pArchetype);
7660
7661 m_entityToAsRelations = {};
7662 m_entityToAsRelationsTravCache = {};
7663 m_entityToAsTargets = {};
7664 m_entityToAsTargetsTravCache = {};
7665 m_targetsTravCache = {};
7666 m_srcBfsTravCache = {};
7667 m_depthOrderCache = {};
7668 m_sourcesAllCache = {};
7669 m_targetsAllCache = {};
7670 m_tgtToRel = {};
7671 m_relToTgt = {};
7672 m_exclusiveAdjunctByRel = {};
7673 m_srcToExclusiveAdjunctRel = {};
7674 for (auto& [compKey, store]: m_sparseComponentsByComp) {
7675 (void)compKey;
7676 store.func_clear_store(store.pStore);
7677 store.func_del_store(store.pStore);
7678 }
7679 m_sparseComponentsByComp = {};
7680 m_relationVersions = {};
7681 m_srcEntityVersions = {};
7682
7683 m_archetypes = {};
7684 m_archetypesById = {};
7685 m_archetypesByHash = {};
7686 }
7687
7688 // Clear caches
7689 {
7690 m_entityToArchetypeMap = {};
7691 m_entityToArchetypeMapVersions = {};
7692 m_queryCache.clear();
7693 for (auto* pScratch: m_queryMatchScratchStack)
7694 delete pScratch;
7695 m_queryMatchScratchStack = {};
7696 m_queryMatchScratchDepth = 0;
7697 m_querySerMap = {};
7698 m_nextQuerySerId = 0;
7699 }
7700
7701 // Clear entity aliases
7702 {
7703 for (auto& pair: m_aliasToEntity) {
7704 if (!pair.first.owned())
7705 continue;
7706 // Release any memory allocated for owned names
7707 mem::mem_free((void*)pair.first.str());
7708 }
7709 m_aliasToEntity = {};
7710 }
7711
7712 // Clear entity names
7713 {
7714 for (auto& pair: m_nameToEntity) {
7715 if (!pair.first.owned())
7716 continue;
7717 // Release any memory allocated for owned names
7718 mem::mem_free((void*)pair.first.str());
7719 }
7720 m_nameToEntity = {};
7721 }
7722
7723 // Clear component cache
7724 m_compCache.clear();
7725 }
7726
7727 GAIA_NODISCARD static bool valid(const EntityContainer& ec, [[maybe_unused]] Entity entityExpected) {
7728 if ((ec.flags & EntityContainerFlags::Load) != 0) {
7729 return entityExpected.id() == ec.idx && entityExpected.gen() == ec.data.gen &&
7730 entityExpected.entity() == (bool)ec.data.ent && entityExpected.pair() == (bool)ec.data.pair &&
7731 entityExpected.kind() == (EntityKind)ec.data.kind;
7732 }
7733
7734 if (is_req_del(ec))
7735 return false;
7736
7737 // The entity in the chunk must match the index in the entity container
7738 const auto* pChunk = ec.pChunk;
7739 if (pChunk == nullptr || ec.row >= pChunk->size())
7740 return false;
7741
7742 const auto entityPresent = pChunk->entity_view()[ec.row];
7743 // Public validity checks can legitimately observe a recycled slot with a different generation.
7744 // Treat that as stale instead of aborting.
7745 return entityExpected == entityPresent;
7746 }
7747
7751 GAIA_NODISCARD bool valid_pair(Entity entity) const {
7752 if (entity == EntityBad)
7753 return false;
7754
7755 GAIA_ASSERT(entity.pair());
7756 if (!entity.pair())
7757 return false;
7758
7759 // Ignore wildcards because they can't be attached to entities
7760 if (is_wildcard(entity))
7761 return true;
7762
7763 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
7764 if (it == m_recs.pairs.end())
7765 return false;
7766
7767 const auto& ec = it->second;
7768 return valid(ec, entity);
7769 }
7770
7774 GAIA_NODISCARD bool valid_entity(Entity entity) const {
7775 if (entity == EntityBad)
7776 return false;
7777
7778 GAIA_ASSERT(!entity.pair());
7779 if (entity.pair())
7780 return false;
7781
7782 // Entity ID has to fit inside the entity array
7783 if (entity.id() >= m_recs.entities.size())
7784 return false;
7785
7786 const auto* pEc = m_recs.entities.try_get(entity.id());
7787 if (pEc == nullptr)
7788 return false;
7789
7790 return valid(*pEc, entity);
7791 }
7792
7797 GAIA_NODISCARD bool valid_entity_id(EntityId entityId) const {
7798 if (entityId == EntityBad.id())
7799 return false;
7800
7801 // Entity ID has to fit inside the entity array
7802 if (entityId >= m_recs.entities.size())
7803 return false;
7804
7805 const auto* pEc = m_recs.entities.try_get(entityId);
7806 if (pEc == nullptr)
7807 return false;
7808
7809 const auto& ec = *pEc;
7810 if (ec.data.pair != 0)
7811 return false;
7812
7813 return valid(
7814 ec, Entity(entityId, ec.data.gen, (bool)ec.data.ent, (bool)ec.data.pair, (EntityKind)ec.data.kind));
7815 }
7816
7820 void lock() {
7821 GAIA_ASSERT(m_structuralChangesLocked != (uint32_t)-1);
7822 ++m_structuralChangesLocked;
7823 }
7824
7828 void unlock() {
7829 GAIA_ASSERT(m_structuralChangesLocked > 0);
7830 --m_structuralChangesLocked;
7831 }
7832
7833#if GAIA_SYSTEMS_ENABLED
7834 void systems_done();
7835#endif
7836
7837 public:
7839 GAIA_NODISCARD bool locked() const {
7840 return m_structuralChangesLocked != 0;
7841 }
7842
7845 GAIA_NODISCARD bool tearing_down() const {
7846 return m_teardownActive;
7847 }
7848
7849 private:
7850 static constexpr uint32_t WorldSerializerVersion = 3;
7851 static constexpr uint32_t WorldSerializerJSONVersion = 1;
7852
7853 void save_to(ser::serializer s) const {
7854 GAIA_ASSERT(s.valid());
7855
7856 // Version number, currently unused
7857 s.save((uint32_t)WorldSerializerVersion);
7858
7859 // Store the index of the last core component.
7860 // TODO: As this changes, we will have to modify entity ids accordingly.
7861 const auto lastCoreComponentId = GAIA_ID(LastCoreComponent).id();
7862 s.save(lastCoreComponentId);
7863
7864 // Entities
7865 {
7866 auto saveEntityContainer = [&](const EntityContainer& ec) {
7867 s.save(ec.idx);
7868 s.save(ec.dataRaw);
7869 s.save(ec.row);
7870 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) == 0);
7871 s.save(ec.flags); // ignore Load
7872
7873#if GAIA_USE_SAFE_ENTITY
7874 s.save(ec.refCnt);
7875#else
7876 s.save((uint32_t)0);
7877#endif
7878
7879 uint32_t archetypeIdx = ec.pArchetype->list_idx();
7880 s.save(archetypeIdx);
7881 uint32_t chunkIdx = ec.pChunk->idx();
7882 s.save(chunkIdx);
7883 };
7884
7885 const auto recEntities = (uint32_t)m_recs.entities.size();
7886 const auto newEntities = recEntities - lastCoreComponentId;
7887 s.save(newEntities);
7888 GAIA_FOR2(lastCoreComponentId, recEntities) {
7889 const bool isAlive = m_recs.entities.has(i);
7890 s.save(isAlive);
7891 if (isAlive)
7892 saveEntityContainer(m_recs.entities[i]);
7893 else {
7894 s.save(m_recs.entities.handle(i).val);
7895 s.save(m_recs.entities.next_free(i));
7896 }
7897 }
7898
7899 {
7900 uint32_t pairsCnt = 0;
7901 for (const auto& pair: m_recs.pairs) {
7902 // Skip core pairs
7903 if (pair.first.entity().id() < lastCoreComponentId && pair.first.entity().gen() < lastCoreComponentId)
7904 continue;
7905
7906 ++pairsCnt;
7907 }
7908 s.save(pairsCnt);
7909 }
7910 {
7911 for (const auto& pair: m_recs.pairs) {
7912 // Skip core pairs
7913 if (pair.first.entity().id() < lastCoreComponentId && pair.first.entity().gen() < lastCoreComponentId)
7914 continue;
7915
7916 saveEntityContainer(pair.second);
7917 }
7918 }
7919
7920 s.save(m_recs.entities.m_nextFreeIdx);
7921 s.save(m_recs.entities.m_freeItems);
7922 }
7923
7924 // World
7925 {
7926 s.save((uint32_t)m_archetypes.size());
7927 for (auto* pArchetype: m_archetypes) {
7928 s.save((uint32_t)pArchetype->ids_view().size());
7929 for (auto e: pArchetype->ids_view())
7930 s.save(e);
7931
7932 pArchetype->save(s);
7933 }
7934
7935 s.save(m_worldVersion);
7936 }
7937
7938 // Entity names
7939 {
7940 s.save((uint32_t)m_nameToEntity.size());
7941 for (const auto& pair: m_nameToEntity) {
7942 s.save(pair.second);
7943 const bool isOwnedStr = pair.first.owned();
7944 s.save(isOwnedStr);
7945
7946 // For owner string we copy the entire string into the buffer
7947 if (isOwnedStr) {
7948 const auto* str = pair.first.str();
7949 const uint32_t len = pair.first.len();
7950 s.save(len);
7951 s.save_raw(str, len, ser::serialization_type_id::c8);
7952 }
7953 // Non-owned strings will only store the pointer.
7954 // However, if it is a component, we do not store anything at all because we can reconstruct
7955 // the name from our component cache.
7956 else if (!pair.second.comp()) {
7957 const auto* str = pair.first.str();
7958 const uint32_t len = pair.first.len();
7959 s.save(len);
7960 const auto ptr_val = (uint64_t)str;
7961 s.save_raw(&ptr_val, sizeof(ptr_val), ser::serialization_type_id::u64);
7962 }
7963 }
7964 }
7965
7966 // Entity aliases
7967 {
7968 uint32_t aliasCnt = 0;
7969 GAIA_FOR((uint32_t)m_recs.entities.size()) {
7970 const auto entity = get((EntityId)i);
7971 if (!valid(entity) || entity.pair())
7972 continue;
7973
7974 const auto& ec = m_recs.entities[i];
7975 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
7976 if (compIdx == BadIndex)
7977 continue;
7978
7979 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
7980 if (pDesc->alias != nullptr)
7981 ++aliasCnt;
7982 }
7983
7984 s.save(aliasCnt);
7985 GAIA_FOR((uint32_t)m_recs.entities.size()) {
7986 const auto entity = get((EntityId)i);
7987 if (!valid(entity) || entity.pair())
7988 continue;
7989
7990 const auto& ec = m_recs.entities[i];
7991 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
7992 if (compIdx == BadIndex)
7993 continue;
7994
7995 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
7996 if (pDesc->alias == nullptr)
7997 continue;
7998
7999 s.save(entity);
8000 s.save(pDesc->alias_len);
8001 s.save_raw(pDesc->alias, pDesc->alias_len, ser::serialization_type_id::c8);
8002 }
8003 }
8004 }
8005
8006 public:
8013 void save() {
8014 auto s = m_serializer;
8015 GAIA_ASSERT(s.valid());
8016
8017 s.reset();
8018 save_to(s);
8019 }
8020
8025 bool save_json(ser::ser_json& writer, ser::JsonSaveFlags flags = ser::JsonSaveFlags::Default) const;
8026
8028 ser::json_str save_json(bool& ok, ser::JsonSaveFlags flags = ser::JsonSaveFlags::Default) const;
8029
8033 bool load_json(const char* json, uint32_t len, ser::JsonDiagnostics& diagnostics);
8034
8039 bool load_json(const char* json, uint32_t len);
8040
8045 bool load_json(ser::json_str_view json, ser::JsonDiagnostics& diagnostics);
8046
8050 bool load_json(ser::json_str_view json);
8051
8058 bool load(ser::serializer inputSerializer = {}) {
8059 auto s = inputSerializer.valid() ? inputSerializer : m_serializer;
8060 GAIA_ASSERT(s.valid());
8061
8062 // Move back to the beginning of the stream
8063 s.seek(0);
8064
8065 // Version number, currently unused
8066 uint32_t version = 0;
8067 s.load(version);
8068 if (version < 2 || version > WorldSerializerVersion) {
8069 GAIA_LOG_E("Unsupported world version %u. Expected 2..%u.", version, WorldSerializerVersion);
8070 return false;
8071 }
8072
8073 // Store the index of the last core component. As they change, we will have to modify entity ids accordingly.
8074 uint32_t lastCoreComponentId = 0;
8075 s.load(lastCoreComponentId);
8076
8077 // Append-only core ids are handled via load-time entity remapping.
8078 // Snapshots from a runtime with a larger core-id boundary are not supported.
8079 const auto currLastCoreComponentId = GAIA_ID(LastCoreComponent).id();
8080 if (lastCoreComponentId > currLastCoreComponentId) {
8081 GAIA_LOG_E(
8082 "Unsupported world core boundary %u. Current runtime supports up to %u.", lastCoreComponentId,
8083 currLastCoreComponentId);
8084 return false;
8085 }
8086 // Install the append-only core-id remap for nested Entity::load() calls.
8087 // This keeps the serializer API unchanged, at the cost of relying on
8088 // scoped thread-local state instead of explicit serializer-local context.
8089 const detail::EntityLoadRemapGuard entityLoadRemapGuard(
8090 lastCoreComponentId, currLastCoreComponentId, version >= WorldSerializerVersion);
8091 auto remapLoadedEntityId = [&](uint32_t id) {
8092 return detail::remap_loaded_entity_id(id, lastCoreComponentId, currLastCoreComponentId);
8093 };
8094
8095 // Entities
8096 {
8097 auto loadEntityContainer = [&](EntityContainer& ec) {
8098 s.load(ec.idx);
8099 s.load(ec.dataRaw);
8100 s.load(ec.row);
8101 s.load(ec.flags);
8102 ec.flags |= EntityContainerFlags::Load;
8103
8104 ec.idx = remapLoadedEntityId(ec.idx);
8105 if (ec.data.pair != 0)
8106 ec.data.gen = remapLoadedEntityId(ec.data.gen);
8107
8108#if GAIA_USE_SAFE_ENTITY
8109 s.load(ec.refCnt);
8110#else
8111 s.load(ec.unused);
8112 // if this value is different from zero, it means we are trying to load data
8113 // that was previously saved with GAIA_USE_SAFE_ENTITY. It's probably not a good idea
8114 // because if your program used reference counting it probably won't work correctly.
8115 GAIA_ASSERT(ec.unused == 0);
8116#endif
8117 // Store the archetype idx inside the pointer. We will decode this once archetypes are created.
8118 uint32_t archetypeIdx = 0;
8119 s.load(archetypeIdx);
8120 ec.pArchetype = (Archetype*)((uintptr_t)archetypeIdx);
8121 // Store the chunk idx inside the pointer. We will decode this once chunks are created.
8122 uint32_t chunkIdx = 0;
8123 s.load(chunkIdx);
8124 ec.pChunk = (Chunk*)((uintptr_t)chunkIdx);
8125 };
8126
8127 uint32_t newEntities = 0;
8128 s.load(newEntities);
8129 GAIA_FOR(newEntities) {
8130 bool isAlive = false;
8131 s.load(isAlive);
8132 if (isAlive) {
8133 EntityContainer ec{};
8134 loadEntityContainer(ec);
8135 m_recs.entities.add_live(GAIA_MOV(ec));
8136 } else {
8137 Identifier id = IdentifierBad;
8138 uint32_t nextFreeIdx = Entity::IdMask;
8139 s.load(id);
8140 s.load(nextFreeIdx);
8141 auto entity = Entity(id);
8142 entity = detail::remap_loaded_entity(entity, lastCoreComponentId, currLastCoreComponentId);
8143 nextFreeIdx = remapLoadedEntityId(nextFreeIdx);
8144 GAIA_ASSERT(entity.id() == remapLoadedEntityId(lastCoreComponentId + i));
8145 m_recs.entities.add_free(entity, nextFreeIdx);
8146 }
8147 }
8148
8149 uint32_t pairsCnt = 0;
8150 s.load(pairsCnt);
8151 GAIA_FOR(pairsCnt) {
8152 EntityContainer ec{};
8153 loadEntityContainer(ec);
8154 Entity pair(ec.idx, ec.data.gen);
8155 m_recs.pairs.emplace(EntityLookupKey(pair), GAIA_MOV(ec));
8156 }
8157
8158 s.load(m_recs.entities.m_nextFreeIdx);
8159 m_recs.entities.m_nextFreeIdx = remapLoadedEntityId(m_recs.entities.m_nextFreeIdx);
8160 s.load(m_recs.entities.m_freeItems);
8161 }
8162
8163 // World
8164 {
8165 uint32_t archetypesSize = 0;
8166 s.load(archetypesSize);
8167 m_archetypes.reserve(archetypesSize);
8168 GAIA_FOR(archetypesSize) {
8169 uint32_t idsSize = 0;
8170 s.load(idsSize);
8171 Entity ids[ChunkHeader::MAX_COMPONENTS];
8172 GAIA_FOR_(idsSize, j) {
8173 s.load(ids[j]);
8174 }
8175
8176 // Calculate the lookup hash
8177 const auto hashLookup = calc_lookup_hash({&ids[0], idsSize}).hash;
8178
8179 auto* pArchetype = find_archetype({hashLookup}, {&ids[0], idsSize});
8180 if (pArchetype == nullptr) {
8181 // Create the archetype
8182 pArchetype = create_archetype({&ids[0], idsSize});
8183 pArchetype->set_hashes({hashLookup});
8184
8185 // No need to do anything with the archetype graph. It will build itself naturally.
8186 // pArchetype->build_graph_edges(pArchetypeRight, entity);
8187
8188 // Register the archetype in the world
8189 reg_archetype(pArchetype);
8190 }
8191
8192 // Load archetype data
8193 pArchetype->load(s);
8194 }
8195
8196 s.load(m_worldVersion);
8197 }
8198
8199 // Update entity records.
8200 // We previously encoded the archetype id into refCnt.
8201 // Now we need to convert it back to the pointer.
8202 {
8203 for (auto& ec: m_recs.entities) {
8204 if ((ec.flags & EntityContainerFlags::Load) == 0)
8205 continue;
8206 ec.flags &= ~EntityContainerFlags::Load; // Clear the load flag
8207
8208 const auto archetypeIdx = (ArchetypeId)((uintptr_t)ec.pArchetype); // Decode the archetype idx
8209 ec.pArchetype = m_archetypes[archetypeIdx];
8210 const uint32_t chunkIdx = (uint32_t)((uintptr_t)ec.pChunk); // Decode the chunk idx
8211 ec.pChunk = ec.pArchetype->chunks()[chunkIdx];
8212 ec.pEntity = &ec.pChunk->entity_view()[ec.row];
8213 }
8214
8215 for (auto& pair: m_recs.pairs) {
8216 auto& ec = pair.second;
8217
8218 // Core pairs remain in-world during load and were not serialized into the stream.
8219 if ((ec.flags & EntityContainerFlags::Load) == 0)
8220 continue;
8221
8222 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) != 0);
8223 ec.flags &= ~EntityContainerFlags::Load; // Clear the load flag
8224
8225 const auto archetypeIdx = (ArchetypeId)((uintptr_t)ec.pArchetype); // Decode the archetype idx
8226 ec.pArchetype = m_archetypes[archetypeIdx];
8227 const uint32_t chunkIdx = (uint32_t)((uintptr_t)ec.pChunk); // Decode the chunk idx
8228 ec.pChunk = ec.pArchetype->chunks()[chunkIdx];
8229 ec.pEntity = &ec.pChunk->entity_view()[ec.row];
8230 }
8231 }
8232
8233 if (version < WorldSerializerVersion) {
8234 for (const auto& [entityId, pItem]: m_compCache.m_compByEntityId) {
8235 (void)entityId;
8236 GAIA_ASSERT(pItem != nullptr);
8237 auto comp = pItem->comp;
8238 comp.data.id = pItem->entity.id();
8239 sync_component_record(pItem->entity, comp);
8240 }
8241 }
8242
8243#if GAIA_ASSERT_ENABLED
8244 for (const auto& ec: m_recs.entities) {
8245 GAIA_ASSERT(ec.idx < m_recs.entities.size());
8246 GAIA_ASSERT(m_recs.entities.handle(ec.idx) == EntityContainer::handle(ec));
8247 GAIA_ASSERT(ec.pArchetype != nullptr);
8248 GAIA_ASSERT(ec.pChunk != nullptr);
8249 GAIA_ASSERT(ec.pEntity != nullptr);
8250 }
8251#endif
8252 // Entity names
8253 {
8254 m_nameToEntity = {};
8255 uint32_t cnt = 0;
8256 s.load(cnt);
8257 GAIA_FOR(cnt) {
8258 Entity entity;
8259 s.load(entity);
8260 // entity.data.gen = 0; // Reset generation to zero
8261
8262 const auto& ec = fetch(entity);
8263 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
8264 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
8265 GAIA_ASSERT(core::check_alignment(pDesc));
8266
8267 bool isOwned = false;
8268 s.load(isOwned);
8269 if (!isOwned) {
8270 if (entity.comp()) {
8271 // Make components point back to their component cache record because if we save the world and load
8272 // it back in runtime, EntityDesc would still point to the old pointers to component names.
8273 const auto& ci = comp_cache().get(entity);
8274 pDesc->name = ci.name.str();
8275 // Length should still be the same. Only the pointer has changed.
8276 GAIA_ASSERT(pDesc->name_len == ci.name.len());
8277 m_nameToEntity.try_emplace(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0), entity);
8278 } else {
8279 uint32_t len = 0;
8280 s.load(len);
8281 uint64_t ptr_val = 0;
8282 s.load_raw(&ptr_val, sizeof(ptr_val), ser::serialization_type_id::u64);
8283
8284 // Simply point to whereever the original pointer pointed to
8285 pDesc->name = (const char*)ptr_val;
8286 pDesc->name_len = len;
8287 m_nameToEntity.try_emplace(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0), entity);
8288 }
8289
8290 continue;
8291 }
8292
8293 uint32_t len = 0;
8294 s.load(len);
8295
8296 // Get a pointer to where the string begins and seek to the end of the string
8297 const char* entityStr = (const char*)(s.data() + s.tell());
8298 s.seek(s.tell() + len);
8299
8300 // Make sure EntityDesc does not point anywhere right now.
8301 {
8302 pDesc->name = nullptr;
8303 pDesc->name_len = 0;
8304 }
8305
8306 // Name the entity using an owned string
8307 name(entity, entityStr, len);
8308 }
8309 }
8310
8311 // Entity aliases
8312 {
8313 m_aliasToEntity = {};
8314 for (auto& ec: m_recs.entities) {
8315 const auto entity = EntityContainer::handle(ec);
8316 if (entity.pair())
8317 continue;
8318
8319 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
8320 if (compIdx == BadIndex)
8321 continue;
8322
8323 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
8324 GAIA_ASSERT(core::check_alignment(pDesc));
8325 pDesc->alias = nullptr;
8326 pDesc->alias_len = 0;
8327 }
8328
8329 uint32_t cnt = 0;
8330 s.load(cnt);
8331 GAIA_FOR(cnt) {
8332 Entity entity;
8333 s.load(entity);
8334
8335 const auto& ec = fetch(entity);
8336 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
8337 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
8338 GAIA_ASSERT(core::check_alignment(pDesc));
8339
8340 uint32_t len = 0;
8341 s.load(len);
8342
8343 // Get a pointer to where the string begins and seek to the end of the string
8344 const char* aliasStr = (const char*)(s.data() + s.tell());
8345 s.seek(s.tell() + len);
8346
8347 pDesc->alias = nullptr;
8348 pDesc->alias_len = 0;
8349 alias(entity, aliasStr, len);
8350 }
8351 }
8352
8353 return true;
8354 }
8355
8359 template <typename TSerializer>
8360 bool load(TSerializer& inputSerializer) {
8361 return load(ser::make_serializer(inputSerializer));
8362 }
8363
8364 private:
8366 void sort_archetypes() {
8367 struct sort_cond {
8368 bool operator()(const Archetype* a, const Archetype* b) const {
8369 return a->id() < b->id();
8370 }
8371 };
8372
8373 core::sort(m_archetypes, sort_cond{}, [&](uint32_t left, uint32_t right) {
8374 Archetype* tmp = m_archetypes[left];
8375
8376 m_archetypes[right]->list_idx(left);
8377 m_archetypes[left]->list_idx(right);
8378
8379 m_archetypes.data()[left] = (Archetype*)m_archetypes[right];
8380 m_archetypes.data()[right] = tmp;
8381 });
8382 }
8383
8387 void remove_chunk(Archetype& archetype, Chunk& chunk) {
8388 archetype.del(&chunk);
8389 try_enqueue_archetype_for_deletion(archetype);
8390 }
8391
8393 void remove_chunk_from_delete_queue(uint32_t idx) {
8394 GAIA_ASSERT(idx < m_chunksToDel.size());
8395
8396 auto* pChunk = m_chunksToDel[idx].pChunk;
8397 pChunk->clear_delete_queue_index();
8398
8399 const auto lastIdx = (uint32_t)m_chunksToDel.size() - 1;
8400 if (idx != lastIdx) {
8401 auto* pMovedChunk = m_chunksToDel[lastIdx].pChunk;
8402 pMovedChunk->delete_queue_index(idx);
8403 }
8404
8405 core::swap_erase(m_chunksToDel, idx);
8406 }
8407
8412 void remove_entity(Archetype& archetype, Chunk& chunk, uint16_t row) {
8413 archetype.remove_entity(chunk, row, m_recs);
8414 try_enqueue_chunk_for_deletion(archetype, chunk);
8415 }
8416
8418 void del_empty_chunks() {
8419 GAIA_PROF_SCOPE(World::del_empty_chunks);
8420
8421 for (uint32_t i = 0; i < m_chunksToDel.size();) {
8422 auto* pArchetype = m_chunksToDel[i].pArchetype;
8423 auto* pChunk = m_chunksToDel[i].pChunk;
8424
8425 // Revive reclaimed chunks
8426 if (!pChunk->empty()) {
8427 pChunk->revive();
8428 revive_archetype(*pArchetype);
8429 remove_chunk_from_delete_queue(i);
8430 continue;
8431 }
8432
8433 // Skip chunks which still have some lifespan left
8434 if (pChunk->progress_death()) {
8435 ++i;
8436 continue;
8437 }
8438
8439 // Delete unused chunks that are past their lifespan
8440 remove_chunk(*pArchetype, *pChunk);
8441 remove_chunk_from_delete_queue(i);
8442 }
8443 }
8444
8446 void del_empty_archetype(Archetype* pArchetype) {
8447 GAIA_PROF_SCOPE(World::del_empty_archetype);
8448
8449 GAIA_ASSERT(pArchetype != nullptr);
8450 GAIA_ASSERT(pArchetype->empty() || pArchetype->is_req_del());
8451 GAIA_ASSERT(!pArchetype->dying() || pArchetype->is_req_del());
8452
8453 unreg_archetype(pArchetype);
8454 for (auto& ec: m_recs.entities) {
8455 if (ec.pArchetype != pArchetype)
8456 continue;
8457
8458 ec.pArchetype = nullptr;
8459 ec.pChunk = nullptr;
8460 ec.pEntity = nullptr;
8461 }
8462 for (auto& [_, ec]: m_recs.pairs) {
8463 if (ec.pArchetype != pArchetype)
8464 continue;
8465
8466 ec.pArchetype = nullptr;
8467 ec.pChunk = nullptr;
8468 ec.pEntity = nullptr;
8469 }
8470 Archetype::destroy(pArchetype);
8471 }
8472
8474 void del_empty_archetypes() {
8475 GAIA_PROF_SCOPE(World::del_empty_archetypes);
8476
8477 cnt::sarray_ext<Archetype*, 512> tmp;
8478
8479 // Remove all dead archetypes from query caches.
8480 // Because the number of cached queries is way higher than the number of archetypes
8481 // we want to remove, we flip the logic around and iterate over all query caches
8482 // and match against our lists.
8483 // Note, all archetype pointers in the tmp array are invalid at this point and can
8484 // be used only for comparison. They can't be dereferenced.
8485 auto remove_from_queries = [&]() {
8486 if (tmp.empty())
8487 return;
8488
8489 for (auto* pArchetype: tmp) {
8490 m_queryCache.remove_archetype_from_queries(pArchetype);
8491 del_empty_archetype(pArchetype);
8492 }
8493 tmp.clear();
8494 };
8495
8496 for (uint32_t i = 0; i < m_archetypesToDel.size();) {
8497 auto* pArchetype = m_archetypesToDel[i];
8498
8499 // Skip reclaimed archetypes or archetypes that became immortal
8500 if (!pArchetype->empty() || pArchetype->max_lifespan() == 0) {
8501 revive_archetype(*pArchetype);
8502 core::swap_erase(m_archetypesToDel, i);
8503 continue;
8504 }
8505
8506 // Skip archetypes which still have some lifespan left unless
8507 // they are force-deleted.
8508 if (!pArchetype->is_req_del() && pArchetype->progress_death()) {
8509 ++i;
8510 continue;
8511 }
8512
8513 tmp.push_back(pArchetype);
8514
8515 // Remove the unused archetypes
8516 core::swap_erase(m_archetypesToDel, i);
8517
8518 // Clear what we have once the capacity is reached
8519 if (tmp.size() == tmp.max_size())
8520 remove_from_queries();
8521 }
8522
8523 remove_from_queries();
8524 }
8525
8526 void revive_archetype(Archetype& archetype) {
8527 archetype.revive();
8528 m_reqArchetypesToDel.erase(ArchetypeLookupKey(archetype.lookup_hash(), &archetype));
8529 }
8530
8531 void try_enqueue_chunk_for_deletion(Archetype& archetype, Chunk& chunk) {
8532 if (chunk.dying() || !chunk.empty())
8533 return;
8534
8535 // When the chunk is emptied we want it to be removed. We can't do it
8536 // rowB away and need to wait for world::gc() to be called.
8537 //
8538 // However, we need to prevent the following:
8539 // 1) chunk is emptied, add it to some removal list
8540 // 2) chunk is reclaimed
8541 // 3) chunk is emptied, add it to some removal list again
8542 //
8543 // Therefore, we have a flag telling us the chunk is already waiting to
8544 // be removed. The chunk might be reclaimed before garbage collection happens
8545 // but it simply ignores such requests. This way we always have at most one
8546 // record for removal for any given chunk.
8547 chunk.start_dying();
8548
8549 m_chunksToDel.push_back({&archetype, &chunk});
8550 chunk.delete_queue_index((uint32_t)m_chunksToDel.size() - 1);
8551 }
8552
8553 void try_enqueue_archetype_for_deletion(Archetype& archetype) {
8554 if (!archetype.ready_to_die())
8555 return;
8556
8557 // When the chunk is emptied we want it to be removed. We can't do it
8558 // rowB away and need to wait for world::gc() to be called.
8559 //
8560 // However, we need to prevent the following:
8561 // 1) archetype is emptied, add it to some removal list
8562 // 2) archetype is reclaimed
8563 // 3) archetype is emptied, add it to some removal list again
8564 //
8565 // Therefore, we have a flag telling us the chunk is already waiting to
8566 // be removed. The archetype might be reclaimed before garbage collection happens
8567 // but it simply ignores such requests. This way we always have at most one
8568 // record for removal for any given chunk.
8569 archetype.start_dying();
8570
8571 m_archetypesToDel.push_back(&archetype);
8572 }
8573
8576 void defrag_chunks(uint32_t maxEntities) {
8577 GAIA_PROF_SCOPE(World::defrag_chunks);
8578
8579 const auto maxIters = m_archetypes.size();
8580 // There has to be at least the root archetype present
8581 GAIA_ASSERT(maxIters > 0);
8582
8583 GAIA_FOR(maxIters) {
8584 const auto idx = (m_defragLastArchetypeIdx + 1) % maxIters;
8585 auto* pArchetype = m_archetypes[idx];
8586 defrag_archetype(*pArchetype, maxEntities);
8587 if (maxEntities == 0)
8588 return;
8589
8590 m_defragLastArchetypeIdx = idx;
8591 }
8592 }
8593
8597 void defrag_archetype(Archetype& archetype, uint32_t& maxEntities) {
8598 // Assuming the following chunk layout:
8599 // Chunk_1: 10/10
8600 // Chunk_2: 1/10
8601 // Chunk_3: 7/10
8602 // Chunk_4: 10/10
8603 // Chunk_5: 9/10
8604 // After full defragmentation we end up with:
8605 // Chunk_1: 10/10
8606 // Chunk_2: 10/10 (7 entities from Chunk_3 + 2 entities from Chunk_5)
8607 // Chunk_3: 0/10 (empty, ready for removal)
8608 // Chunk_4: 10/10
8609 // Chunk_5: 7/10
8610 // TODO: Implement mask of semi-full chunks so we can pick one easily when searching
8611 // for a chunk to fill with a new entity and when defragmenting.
8612 // NOTE 1:
8613 // Even though entity movement might be present during defragmentation, we do
8614 // not update the world version here because no real structural changes happen.
8615 // All entities and components remain intact, they just move to a different place.
8616 // NOTE 2:
8617 // Entities belonging to chunks with uni components are locked to their chunk.
8618 // Therefore, we won't defragment them unless their uni components contain matching
8619 // values.
8620
8621 if (maxEntities == 0)
8622 return;
8623
8624 const auto& chunks = archetype.chunks();
8625 if (chunks.size() < 2)
8626 return;
8627
8628 uint32_t front = 0;
8629 uint32_t back = chunks.size() - 1;
8630
8631 auto* pDstChunk = chunks[front];
8632 auto* pSrcChunk = chunks[back];
8633
8634 // Find the first semi-full chunk in the front
8635 while (front < back && (pDstChunk->full() || !pDstChunk->is_semi()))
8636 pDstChunk = chunks[++front];
8637 // Find the last semi-full chunk in the back
8638 while (front < back && (pSrcChunk->empty() || !pSrcChunk->is_semi()))
8639 pSrcChunk = chunks[--back];
8640
8641 const auto& props = archetype.props();
8642 const bool hasUniEnts =
8643 props.cntEntities > 0 && archetype.ids_view()[props.cntEntities - 1].kind() == EntityKind::EK_Uni;
8644
8645 // Find the first semi-empty chunk in the back
8646 while (front < back) {
8647 pDstChunk = chunks[front];
8648 pSrcChunk = chunks[back];
8649
8650 const uint32_t entitiesInSrcChunk = pSrcChunk->size();
8651 const uint32_t spaceInDstChunk = pDstChunk->capacity() - pDstChunk->size();
8652 const uint32_t entitiesToMoveSrc = core::get_min(entitiesInSrcChunk, maxEntities);
8653 const uint32_t entitiesToMove = core::get_min(entitiesToMoveSrc, spaceInDstChunk);
8654
8655 // Make sure uni components have matching values
8656 if (hasUniEnts) {
8657 auto rec = pSrcChunk->comp_rec_view();
8658 bool res = true;
8659 GAIA_FOR2(props.genEntities, props.cntEntities) {
8660 const auto* pSrcVal = (const void*)pSrcChunk->comp_ptr(i, 0);
8661 const auto* pDstVal = (const void*)pDstChunk->comp_ptr(i, 0);
8662 if (rec[i].pItem->cmp(pSrcVal, pDstVal)) {
8663 res = false;
8664 break;
8665 }
8666 }
8667
8668 // When there is not a match we move to the next chunk
8669 if (!res) {
8670 pDstChunk = chunks[++front];
8671 goto next_iteration;
8672 }
8673 }
8674
8675 GAIA_FOR(entitiesToMove) {
8676 const auto lastSrcEntityIdx = entitiesInSrcChunk - i - 1;
8677 const auto entity = pSrcChunk->entity_view()[lastSrcEntityIdx];
8678
8679 auto& ec = m_recs[entity];
8680
8681 const auto srcRow = ec.row;
8682 const auto dstRow = pDstChunk->add_entity(entity);
8683 const bool wasEnabled = !ec.data.dis;
8684
8685 // Make sure the old entity becomes enabled now
8686 archetype.enable_entity(pSrcChunk, srcRow, true, m_recs);
8687 // We go back-to-front in the chunk so enabling the entity is not expected to change its row
8688 GAIA_ASSERT(srcRow == ec.row);
8689
8690 // Move data from the old chunk to the new one
8691 pDstChunk->move_entity_data(entity, dstRow, m_recs);
8692
8693 // Remove the entity record from the old chunk.
8694 // Normally we'd call remove_entity but we don't want to trigger world
8695 // version updated all the time. It's enough to do it just once at the
8696 // end of defragmentation.
8697 // remove_entity(archetype, *pSrcChunk, srcRow);
8698 archetype.remove_entity_raw(*pSrcChunk, srcRow, m_recs);
8699 try_enqueue_chunk_for_deletion(archetype, *pSrcChunk);
8700
8701 // Bring the entity container record up-to-date
8702 ec.pChunk = pDstChunk;
8703 ec.row = (uint16_t)dstRow;
8704 ec.pEntity = &pDstChunk->entity_view()[dstRow];
8705
8706 // Transfer the original enabled state to the new chunk
8707 archetype.enable_entity(pDstChunk, dstRow, wasEnabled, m_recs);
8708 }
8709
8710 // Update world versions
8711 if (entitiesToMove > 0) {
8712 pSrcChunk->update_world_version();
8713 pDstChunk->update_world_version();
8714 pSrcChunk->update_entity_order_version();
8715 pDstChunk->update_entity_order_version();
8716 update_version(m_worldVersion);
8717 }
8718
8719 maxEntities -= entitiesToMove;
8720 if (maxEntities == 0)
8721 return;
8722
8723 // The source is empty, find another semi-empty source
8724 if (pSrcChunk->empty()) {
8725 while (front < back) {
8726 if (chunks[--back]->is_semi())
8727 break;
8728 }
8729 }
8730
8731 next_iteration:
8732 // The destination chunk is full, we need to move to the next one.
8733 // The idea is to fill the destination as much as possible.
8734 while (front < back && pDstChunk->full())
8735 pDstChunk = chunks[++front];
8736 }
8737 }
8738
8743 GAIA_NODISCARD Archetype* find_archetype(Archetype::LookupHash hashLookup, EntitySpan ids) {
8744 auto tmpArchetype = ArchetypeLookupChecker(ids);
8745 ArchetypeLookupKey key(hashLookup, &tmpArchetype);
8746
8747 // Search for the archetype in the map
8748 const auto it = m_archetypesByHash.find(key);
8749 if (it == m_archetypesByHash.end())
8750 return nullptr;
8751
8752 auto* pArchetype = it->second;
8753 return pArchetype;
8754 }
8755
8756 GAIA_NODISCARD static auto
8757 find_component_index_record(ComponentIndexEntryArray& records, const Archetype* pArchetype) {
8758 return core::get_index_if(records, [&](const auto& record) {
8759 return record.matches(pArchetype);
8760 });
8761 }
8762
8763 GAIA_NODISCARD static auto
8764 find_component_index_record(const ComponentIndexEntryArray& records, const Archetype* pArchetype) {
8765 return core::get_index_if(records, [&](const auto& record) {
8766 return record.matches(pArchetype);
8767 });
8768 }
8769
8772 void update_entity_archetype_lookup_revision(EntityLookupKey entityKey) {
8773 auto [it, _] = m_entityToArchetypeMapVersions.try_emplace(entityKey, 0);
8774 (void)_;
8775 ++it->second;
8776 if (it->second == 0)
8777 it->second = 1;
8778 }
8779
8782 void add_entity_archetype_pair(
8783 Entity entity, Archetype* pArchetype, uint16_t compIdx = ComponentIndexBad, uint16_t matchCount = 1) {
8784 GAIA_ASSERT(pArchetype != nullptr);
8785 GAIA_ASSERT(matchCount > 0);
8786
8787 EntityLookupKey entityKey(entity);
8788 const auto it = m_entityToArchetypeMap.find(entityKey);
8789 if (it == m_entityToArchetypeMap.end()) {
8790 ComponentIndexEntryArray records;
8791 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
8792 m_entityToArchetypeMap.try_emplace(entityKey, GAIA_MOV(records));
8793 return;
8794 }
8795
8796 auto& records = it->second;
8797 const auto idx = find_component_index_record(records, pArchetype);
8798 if (idx == BadIndex) {
8799 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
8800 return;
8801 }
8802
8803 auto& record = records[idx];
8804 record.matchCount = (uint16_t)(record.matchCount + matchCount);
8805 if (compIdx != ComponentIndexBad)
8806 record.compIdx = compIdx;
8807 }
8808
8809 void add_pair_archetype_query_pairs(Entity pair, Archetype* pArchetype, uint16_t matchCount = 1) {
8810 GAIA_ASSERT(pair.pair());
8811 GAIA_ASSERT(pArchetype != nullptr);
8812 GAIA_ASSERT(matchCount > 0);
8813
8814 const auto first = get(pair.id());
8815 const auto second = get(pair.gen());
8816
8817 add_entity_archetype_pair(Pair(All, second), pArchetype, ComponentIndexBad, matchCount);
8818 add_entity_archetype_pair(Pair(first, All), pArchetype, ComponentIndexBad, matchCount);
8819 add_entity_archetype_pair(Pair(All, All), pArchetype, ComponentIndexBad, matchCount);
8820 }
8821
8825 void del_entity_query_pair(Pair pair, Entity entityToRemove) {
8826 const auto entityKey = EntityLookupKey(pair);
8827 auto it = m_entityToArchetypeMap.find(entityKey);
8828 if (it == m_entityToArchetypeMap.end())
8829 return;
8830 auto& records = it->second;
8831 bool changed = false;
8832
8833 // Remove any reference to the found archetype from the array.
8834 // We don't know the archetype so we remove/decrement any archetype record that contains our entity.
8835 for (uint32_t i = records.size() - 1; i != (uint32_t)-1; --i) {
8836 auto& record = records[i];
8837 const auto* pArchetype = record.pArchetype;
8838 if (!pArchetype->has(entityToRemove))
8839 continue;
8840
8841 if ((!is_wildcard(pair.first()) && !is_wildcard(pair.second())) || record.matchCount <= 1)
8842 core::swap_erase_unsafe(records, i);
8843 else
8844 --record.matchCount;
8845 changed = true;
8846 }
8847
8848 if (changed)
8849 update_entity_archetype_lookup_revision(entityKey);
8850
8851 if (records.empty())
8852 m_entityToArchetypeMap.erase(it);
8853 }
8854
8857 void del_entity_query_pair(Pair pair, Archetype* pArchetypeToRemove) {
8858 GAIA_ASSERT(pArchetypeToRemove != nullptr);
8859
8860 const auto entityKey = EntityLookupKey(pair);
8861 auto it = m_entityToArchetypeMap.find(entityKey);
8862 if (it == m_entityToArchetypeMap.end())
8863 return;
8864
8865 auto& records = it->second;
8866 const auto idx = find_component_index_record(records, pArchetypeToRemove);
8867 if (idx != BadIndex) {
8868 core::swap_erase_unsafe(records, idx);
8869 update_entity_archetype_lookup_revision(entityKey);
8870 }
8871
8872 if (records.empty())
8873 m_entityToArchetypeMap.erase(it);
8874 }
8875
8876 void del_pair_archetype_query_pairs(Entity pair, Archetype* pArchetypeToRemove) {
8877 GAIA_ASSERT(pair.pair());
8878 GAIA_ASSERT(pArchetypeToRemove != nullptr);
8879
8880 GAIA_ASSERT(pair.id() < m_recs.entities.size());
8881 GAIA_ASSERT(pair.gen() < m_recs.entities.size());
8882 const auto first = m_recs.entities.handle(pair.id());
8883 const auto second = m_recs.entities.handle(pair.gen());
8884
8885 del_entity_query_pair(Pair(All, second), pArchetypeToRemove);
8886 del_entity_query_pair(Pair(first, All), pArchetypeToRemove);
8887 del_entity_query_pair(Pair(All, All), pArchetypeToRemove);
8888 }
8889
8890 void del_pair_archetype_query_pairs(Entity pair, Entity entityToRemove) {
8891 GAIA_ASSERT(pair.pair());
8892
8893 GAIA_ASSERT(pair.id() < m_recs.entities.size());
8894 GAIA_ASSERT(pair.gen() < m_recs.entities.size());
8895 const auto first = m_recs.entities.handle(pair.id());
8896 const auto second = m_recs.entities.handle(pair.gen());
8897
8898 del_entity_query_pair(Pair(All, second), entityToRemove);
8899 del_entity_query_pair(Pair(first, All), entityToRemove);
8900 del_entity_query_pair(Pair(All, All), entityToRemove);
8901 }
8902
8905 void del_entity_archetype_pair(Entity entity, Archetype* pArchetypeToRemove) {
8906 GAIA_ASSERT(entity != Pair(All, All));
8907 GAIA_ASSERT(pArchetypeToRemove != nullptr);
8908
8909 const auto entityKey = EntityLookupKey(entity);
8910 auto it = m_entityToArchetypeMap.find(entityKey);
8911 if (it == m_entityToArchetypeMap.end())
8912 return;
8913
8914 auto& records = it->second;
8915 const auto idx = find_component_index_record(records, pArchetypeToRemove);
8916 if (idx != BadIndex) {
8917 core::swap_erase_unsafe(records, idx);
8918 update_entity_archetype_lookup_revision(entityKey);
8919 }
8920
8921 if (records.empty())
8922 m_entityToArchetypeMap.erase(it);
8923 }
8924
8926 void del_archetype_entity_pairs(Archetype* pArchetype) {
8927 GAIA_ASSERT(pArchetype != nullptr);
8928
8929 for (const auto entity: pArchetype->ids_view()) {
8930 del_entity_archetype_pair(entity, pArchetype);
8931
8932 if (!entity.pair())
8933 continue;
8934
8935 // Archetype unregistration can run while the pair's relation or target entity is already
8936 // invalid. Rebuild wildcard pair lookup keys from the stored entity records instead of
8937 // calling get(), which asserts on invalidated ids.
8938 GAIA_ASSERT(entity.id() < m_recs.entities.size());
8939 GAIA_ASSERT(entity.gen() < m_recs.entities.size());
8940 del_pair_archetype_query_pairs(entity, pArchetype);
8941 }
8942 }
8943
8947 void del_entity_archetype_pairs(Entity entity, Archetype* pArchetype) {
8948 GAIA_ASSERT(entity != Pair(All, All));
8949
8950 const auto entityKey = EntityLookupKey(entity);
8951 if (m_entityToArchetypeMap.erase(entityKey) != 0)
8952 update_entity_archetype_lookup_revision(entityKey);
8953
8954 if (entity.pair()) {
8955 if (pArchetype != nullptr) {
8956 del_pair_archetype_query_pairs(entity, pArchetype);
8957 } else {
8958 del_pair_archetype_query_pairs(entity, entity);
8959 }
8960 }
8961 }
8962
8966 GAIA_NODISCARD Archetype* create_archetype(EntitySpan entities) {
8967 GAIA_ASSERT(m_nextArchetypeId < (decltype(m_nextArchetypeId))-1);
8968 auto* pArchetype = Archetype::create(*this, m_nextArchetypeId++, m_worldVersion, entities);
8969
8970 const auto entityCnt = (uint32_t)entities.size();
8971 GAIA_FOR(entityCnt) {
8972 auto entity = entities[i];
8973 add_entity_archetype_pair(entity, pArchetype, (uint16_t)i);
8974
8975#if GAIA_OBSERVERS_ENABLED
8976 auto& ec = fetch(entity);
8977 if ((ec.flags & EntityContainerFlags::IsObserved) != 0 || m_observers.has_observers(entity)) {
8978 ec.flags |= EntityContainerFlags::IsObserved;
8979 pArchetype->observed_terms_inc();
8980 }
8981#endif
8982
8983 // If the entity is a pair, make sure to create special wildcard records for it
8984 // as well so wildcard queries can find the archetype.
8985 if (entity.pair()) {
8986 add_pair_archetype_query_pairs(entity, pArchetype);
8987 }
8988 }
8989
8990 return pArchetype;
8991 }
8992
8995 void reg_archetype(Archetype* pArchetype) {
8996 GAIA_ASSERT(pArchetype != nullptr);
8997
8998 // // Make sure hashes were set already
8999 // GAIA_ASSERT(
9000 // (m_archetypesById.empty() || pArchetype == m_pRootArchetype) || (pArchetype->lookup_hash().hash != 0));
9001
9002 // Make sure the archetype is not registered yet
9003 GAIA_ASSERT(pArchetype->list_idx() == BadIndex);
9004
9005 // Register the archetype
9006 [[maybe_unused]] const auto it0 =
9007 m_archetypesById.emplace(ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash()), pArchetype);
9008 [[maybe_unused]] const auto it1 =
9009 m_archetypesByHash.emplace(ArchetypeLookupKey(pArchetype->lookup_hash(), pArchetype), pArchetype);
9010
9011 GAIA_ASSERT(it0.second);
9012 GAIA_ASSERT(it1.second);
9013
9014 pArchetype->list_idx(m_archetypes.size());
9015 m_archetypes.emplace_back(pArchetype);
9016
9017 m_queryCache.register_archetype_with_queries(pArchetype);
9018 }
9019
9022 void unreg_archetype(Archetype* pArchetype) {
9023 GAIA_ASSERT(pArchetype != nullptr);
9024
9025 // Make sure hashes were set already
9026 GAIA_ASSERT(
9027 (m_archetypesById.empty() || pArchetype == m_pRootArchetype) || (pArchetype->lookup_hash().hash != 0));
9028
9029 // Make sure the archetype was registered already
9030 GAIA_ASSERT(pArchetype->list_idx() != BadIndex);
9031
9032 // Query rematching uses the entity -> archetype lookup map as an input. Remove this
9033 // archetype from all of its lookup buckets before destroying it so dead archetype
9034 // pointers cannot be reintroduced into cached query state during the next rematch.
9035 del_archetype_entity_pairs(pArchetype);
9036
9037 // Break graph connections
9038 {
9039 auto& edgeLefts = pArchetype->left_edges();
9040 for (auto& itLeft: edgeLefts)
9041 remove_edge_from_archetype(pArchetype, itLeft.second, itLeft.first.entity());
9042 }
9043
9044 auto tmpArchetype = ArchetypeLookupChecker(pArchetype->ids_view());
9045 [[maybe_unused]] const auto res0 =
9046 m_archetypesById.erase(ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash()));
9047 [[maybe_unused]] const auto res1 =
9048 m_archetypesByHash.erase(ArchetypeLookupKey(pArchetype->lookup_hash(), &tmpArchetype));
9049 GAIA_ASSERT(res0 != 0);
9050 GAIA_ASSERT(res1 != 0);
9051
9052 const auto idx = pArchetype->list_idx();
9053 GAIA_ASSERT(idx == core::get_index(m_archetypes, pArchetype));
9054 core::swap_erase(m_archetypes, idx);
9055 update_entity_archetype_lookup_revision(EntityBadLookupKey);
9056 if (!m_archetypes.empty() && idx != m_archetypes.size())
9057 m_archetypes[idx]->list_idx(idx);
9058 }
9059
9060#if GAIA_ASSERT_ENABLED
9061 static void print_archetype_entities(const World& world, const Archetype& archetype, Entity entity, bool adding) {
9062 auto ids = archetype.ids_view();
9063
9064 GAIA_LOG_W("Currently present:");
9065 GAIA_EACH(ids) {
9066 const auto name = entity_name(world, ids[i]);
9067 GAIA_LOG_W(
9068 "> [%u] %.*s [%s]", i, (int)name.size(), name.empty() ? "" : name.data(),
9069 EntityKindString[(uint32_t)ids[i].kind()]);
9070 }
9071
9072 GAIA_LOG_W("Trying to %s:", adding ? "add" : "del");
9073 const auto name = entity_name(world, entity);
9074 GAIA_LOG_W(
9075 "> %.*s [%s]", (int)name.size(), name.empty() ? "" : name.data(),
9076 EntityKindString[(uint32_t)entity.kind()]);
9077 }
9078
9079 static void verify_add(const World& world, Archetype& archetype, Entity entity, Entity addEntity) {
9080 // Make sure the world is not locked
9081 if (world.locked()) {
9082 GAIA_ASSERT2(false, "Trying to add an entity while the world is locked");
9083 GAIA_LOG_W("Trying to add an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
9084 print_archetype_entities(world, archetype, entity, false);
9085 return;
9086 }
9087
9088 // Makes sure no wildcard entities are added
9089 if (is_wildcard(addEntity)) {
9090 GAIA_ASSERT2(false, "Adding wildcard pairs is not supported");
9091 print_archetype_entities(world, archetype, addEntity, true);
9092 return;
9093 }
9094
9095 // Make sure not to add too many entities/components
9096 auto ids = archetype.ids_view();
9097 if GAIA_UNLIKELY (ids.size() + 1 >= ChunkHeader::MAX_COMPONENTS) {
9098 GAIA_ASSERT2(false, "Trying to add too many entities to entity!");
9099 GAIA_LOG_W("Trying to add an entity to entity [%u:%u] but there's no space left!", entity.id(), entity.gen());
9100 print_archetype_entities(world, archetype, addEntity, true);
9101 return;
9102 }
9103 }
9104
9105 static void verify_del(const World& world, Archetype& archetype, Entity entity, Entity func_del) {
9106 // Make sure the world is not locked
9107 if (world.locked()) {
9108 GAIA_ASSERT2(false, "Trying to delete an entity while the world is locked");
9109 GAIA_LOG_W("Trying to delete an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
9110 print_archetype_entities(world, archetype, entity, false);
9111 return;
9112 }
9113
9114 // Make sure the entity is present on the archetype
9115 if GAIA_UNLIKELY (!archetype.has(func_del)) {
9116 GAIA_ASSERT2(false, "Trying to remove an entity which wasn't added");
9117 GAIA_LOG_W("Trying to del an entity from entity [%u:%u] but it was never added", entity.id(), entity.gen());
9118 print_archetype_entities(world, archetype, func_del, false);
9119 return;
9120 }
9121 }
9122
9123 static void verify_enable(const World& world, Archetype& archetype, Entity entity) {
9124 if (world.locked()) {
9125 GAIA_ASSERT2(false, "Trying to enable/disable an entity while the world is locked");
9126 GAIA_LOG_W("Trying to enable/disable an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
9127 print_archetype_entities(world, archetype, entity, false);
9128 }
9129 }
9130
9131 static void verify_move(const World& world, Archetype& archetype, Entity entity) {
9132 if (world.locked()) {
9133 GAIA_ASSERT2(false, "Trying to move an entity while the world is locked");
9134 GAIA_LOG_W("Trying to move an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
9135 print_archetype_entities(world, archetype, entity, false);
9136 }
9137 }
9138#endif
9139
9145 GAIA_NODISCARD Archetype* foc_archetype_add(Archetype* pArchetypeLeft, Entity entity) {
9146 // Check if the component is found when following the "add" edges
9147 bool edgeNeedsRebuild = false;
9148 {
9149 const auto edge = pArchetypeLeft->find_edge_right(entity);
9150 if (edge != ArchetypeIdHashPairBad) {
9151 auto it = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
9152 if (it != m_archetypesById.end() && it->second != nullptr)
9153 return it->second;
9154
9155 // Drop stale local cache edge and rebuild it below.
9156 pArchetypeLeft->del_graph_edge_right_local(entity);
9157 edgeNeedsRebuild = true;
9158 }
9159 }
9160
9161 // Prepare a joint array of components of old + the newly added component
9162 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
9163 {
9164 auto entsOld = pArchetypeLeft->ids_view();
9165 const auto entsOldCnt = entsOld.size();
9166 entsNew.resize((uint32_t)entsOld.size() + 1);
9167 GAIA_FOR(entsOldCnt) entsNew[i] = entsOld[i];
9168 entsNew[(uint32_t)entsOld.size()] = entity;
9169 }
9170
9171 // Make sure to sort the components so we receive the same hash no matter the order in which components
9172 // are provided Bubble sort is okay. We're dealing with at most ChunkHeader::MAX_COMPONENTS items.
9173 sort(entsNew, SortComponentCond{});
9174
9175 // Once sorted we can calculate the hashes
9176 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
9177 auto* pArchetypeRight = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
9178 if (pArchetypeRight == nullptr) {
9179 pArchetypeRight = create_archetype({entsNew.data(), entsNew.size()});
9180 pArchetypeRight->set_hashes({hashLookup});
9181 reg_archetype(pArchetypeRight);
9182 edgeNeedsRebuild = true;
9183 }
9184
9185 if (edgeNeedsRebuild)
9186 pArchetypeLeft->build_graph_edges(pArchetypeRight, entity);
9187
9188 return pArchetypeRight;
9189 }
9190
9193 GAIA_NODISCARD Archetype* foc_archetype_add_no_graph(Archetype* pArchetypeLeft, Entity entity) {
9194 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
9195 {
9196 auto entsOld = pArchetypeLeft->ids_view();
9197 const auto entsOldCnt = entsOld.size();
9198 entsNew.resize((uint32_t)entsOld.size() + 1);
9199 GAIA_FOR(entsOldCnt) entsNew[i] = entsOld[i];
9200 entsNew[(uint32_t)entsOld.size()] = entity;
9201 }
9202
9203 sort(entsNew, SortComponentCond{});
9204
9205 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
9206 auto* pArchetypeRight = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
9207 if (pArchetypeRight != nullptr)
9208 return pArchetypeRight;
9209
9210 pArchetypeRight = create_archetype({entsNew.data(), entsNew.size()});
9211 pArchetypeRight->set_hashes({hashLookup});
9212 reg_archetype(pArchetypeRight);
9213 return pArchetypeRight;
9214 }
9215
9221 GAIA_NODISCARD Archetype* foc_archetype_del(Archetype* pArchetypeRight, Entity entity) {
9222 // Check if the component is found when following the "del" edges
9223 bool edgeNeedsRebuild = false;
9224 {
9225 const auto edge = pArchetypeRight->find_edge_left(entity);
9226 if (edge != ArchetypeIdHashPairBad) {
9227 const auto it = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
9228 if (it != m_archetypesById.end()) {
9229 auto* pArchetypeLeft = it->second;
9230 if (pArchetypeLeft != nullptr)
9231 return pArchetypeLeft;
9232 }
9233
9234 // Drop stale local cache edge and rebuild it below.
9235 pArchetypeRight->del_graph_edge_left_local(entity);
9236 edgeNeedsRebuild = true;
9237 }
9238 }
9239
9240 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
9241 auto entsOld = pArchetypeRight->ids_view();
9242
9243 // Find the intersection
9244 for (const auto e: entsOld) {
9245 if (e == entity)
9246 continue;
9247
9248 entsNew.push_back(e);
9249 }
9250
9251 // Verify there was a change
9252 GAIA_ASSERT(entsNew.size() != entsOld.size());
9253
9254 // Calculate the hashes
9255 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
9256 auto* pArchetype = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
9257 if (pArchetype == nullptr) {
9258 pArchetype = create_archetype({entsNew.data(), entsNew.size()});
9259 pArchetype->set_hashes({hashLookup});
9260 reg_archetype(pArchetype);
9261 edgeNeedsRebuild = true;
9262 }
9263
9264 if (edgeNeedsRebuild)
9265 pArchetype->build_graph_edges(pArchetypeRight, entity);
9266
9267 return pArchetype;
9268 }
9269
9272 GAIA_NODISCARD Archetype* foc_archetype_del_no_graph(Archetype* pArchetypeRight, Entity entity) {
9273 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
9274 auto entsOld = pArchetypeRight->ids_view();
9275
9276 for (const auto e: entsOld) {
9277 if (e == entity)
9278 continue;
9279
9280 entsNew.push_back(e);
9281 }
9282
9283 GAIA_ASSERT(entsNew.size() != entsOld.size());
9284
9285 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
9286 auto* pArchetype = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
9287 if (pArchetype != nullptr)
9288 return pArchetype;
9289
9290 pArchetype = create_archetype({entsNew.data(), entsNew.size()});
9291 pArchetype->set_hashes({hashLookup});
9292 reg_archetype(pArchetype);
9293 return pArchetype;
9294 }
9295
9298 GAIA_NODISCARD const auto& archetypes() const {
9299 return m_archetypes;
9300 }
9301
9305 GAIA_NODISCARD Archetype& archetype(Entity entity) {
9306 const auto& ec = fetch(entity);
9307 return *ec.pArchetype;
9308 }
9309
9313 void del_name(EntityContainer& ec, Entity entity) {
9314 EntityBuilder(*this, entity, ec).del_name();
9315 }
9316
9319 void del_name(Entity entity) {
9320 EntityBuilder(*this, entity).del_name();
9321 }
9322
9327 void del_entity(Entity entity, bool invalidate) {
9328 if (entity.pair() || entity == EntityBad)
9329 return;
9330
9331 auto& ec = fetch(entity);
9332 del_entity_inter(ec, entity, invalidate);
9333 }
9334
9340 void del_entity(EntityContainer& ec, Entity entity, bool invalidate) {
9341 if (entity.pair() || entity == EntityBad)
9342 return;
9343
9344 del_entity_inter(ec, entity, invalidate);
9345 }
9346
9351 void del_entity_inter(EntityContainer& ec, Entity entity, bool invalidate) {
9352 GAIA_ASSERT(entity.id() > GAIA_ID(LastCoreComponent).id());
9353
9354 // if (!is_req_del(ec))
9355 {
9356 if (m_recs.entities.item_count() == 0)
9357 return;
9358
9359#if GAIA_ASSERT_ENABLED
9360 auto* pChunk = ec.pChunk;
9361 GAIA_ASSERT(pChunk != nullptr);
9362#endif
9363
9364 // Remove the entity from its chunk.
9365 // We call del_name first because remove_entity calls component destructors.
9366 // If the call was made inside invalidate_entity we would access a memory location
9367 // which has already been destructed which is not nice.
9368 del_name(ec, entity);
9369 remove_entity(*ec.pArchetype, *ec.pChunk, ec.row);
9370 remove_src_entity_version(entity);
9371 }
9372
9373 // Invalidate on-demand.
9374 // We delete as a separate step in the delayed deletion.
9375 if (invalidate)
9376 invalidate_entity(entity);
9377 }
9378
9384 void del_entities(Archetype& archetype) {
9385 for (auto* pChunk: archetype.chunks()) {
9386 auto ids = pChunk->entity_view();
9387 for (auto e: ids) {
9388 if (!valid(e))
9389 continue;
9390
9391#if GAIA_ASSERT_ENABLED
9392 const auto& ec = fetch(e);
9393
9394 // We should never end up trying to delete a forbidden-to-delete entity
9395 GAIA_ASSERT((ec.flags & EntityContainerFlags::OnDeleteTarget_Error) == 0);
9396#endif
9397
9398 del_entity(e, true);
9399 }
9400
9401 validate_chunk(pChunk);
9402
9403 // If the chunk was already dying we need to remove it from the delete list
9404 // because we can delete it right away.
9405 if (pChunk->queued_for_deletion())
9406 remove_chunk_from_delete_queue(pChunk->delete_queue_index());
9407
9408 remove_chunk(archetype, *pChunk);
9409 }
9410
9411 validate_entities();
9412 }
9413
9416 void del_inter(Entity entity) {
9417 auto on_delete = [this](Entity entityToDel) {
9418 auto& ec = fetch(entityToDel);
9419 handle_del_entity(ec, entityToDel);
9420 };
9421
9422 if (is_wildcard(entity)) {
9423 const auto rel = get(entity.id());
9424 const auto tgt = get(entity.gen());
9425
9426 // (*,*)
9427 if (rel == All && tgt == All) {
9428 GAIA_ASSERT2(false, "Not supported yet");
9429 }
9430 // (*,X)
9431 else if (rel == All) {
9432 if (const auto* pTargets = relations(tgt)) {
9433 // handle_del might invalidate the targets map so we need to make a copy
9434 // TODO: this is suboptimal at best, needs to be optimized
9435 cnt::darray_ext<Entity, 64> tmp;
9436 for (auto key: *pTargets)
9437 tmp.push_back(key.entity());
9438 for (auto e: tmp)
9439 on_delete(Pair(e, tgt));
9440 }
9441 }
9442 // (X,*)
9443 else if (tgt == All) {
9444 if (const auto* pRelations = targets(rel)) {
9445 // handle_del might invalidate the targets map so we need to make a copy
9446 // TODO: this is suboptimal at best, needs to be optimized
9447 cnt::darray_ext<Entity, 64> tmp;
9448 for (auto key: *pRelations)
9449 tmp.push_back(key.entity());
9450 for (auto e: tmp)
9451 on_delete(Pair(rel, e));
9452 }
9453 }
9454 } else {
9455 on_delete(entity);
9456 }
9457 }
9458
9459 // Force-delete all entities from the requested archetypes along with the archetype itself
9460 void del_finalize_archetypes() {
9461 GAIA_PROF_SCOPE(World::del_finalize_archetypes);
9462
9463 for (auto& key: m_reqArchetypesToDel) {
9464 auto* pArchetype = key.archetype();
9465 if (pArchetype == nullptr)
9466 continue;
9467
9468 del_entities(*pArchetype);
9469
9470 // Now that all entities are deleted, all their chunks are requested to get deleted
9471 // and in turn the archetype itself as well. Therefore, it is added to the archetype
9472 // delete list and picked up by del_empty_archetypes. No need to call deletion from here.
9473 // > del_empty_archetype(pArchetype);
9474 }
9475 m_reqArchetypesToDel.clear();
9476 }
9477
9479 void del_finalize_entities() {
9480 GAIA_PROF_SCOPE(World::del_finalize_entities);
9481
9482 for (auto it = m_reqEntitiesToDel.begin(); it != m_reqEntitiesToDel.end();) {
9483 const auto e = it->entity();
9484
9485 // Entities that form archetypes need to stay until the archetype itself is gone
9486 if (m_entityToArchetypeMap.contains(*it)) {
9487 ++it;
9488 continue;
9489 }
9490
9491 // Requested entities are partially deleted. We only need to invalidate them.
9492 invalidate_entity(e);
9493
9494 it = m_reqEntitiesToDel.erase(it);
9495 }
9496 }
9497
9499 void del_finalize() {
9500 GAIA_PROF_SCOPE(World::del_finalize);
9501
9502 del_finalize_archetypes();
9503 del_finalize_entities();
9504 }
9505
9506 GAIA_NODISCARD bool archetype_cond_match(Archetype& archetype, Pair cond, Entity target) const {
9507 // E.g.:
9508 // target = (All, entity)
9509 // cond = (OnDeleteTarget, delete)
9510 // Delete the entity if it matches the cond
9511 auto ids = archetype.ids_view();
9512
9513 if (target.pair()) {
9514 for (auto e: ids) {
9515 // Find the pair which matches (All, entity)
9516 if (!e.pair())
9517 continue;
9518 if (e.gen() != target.gen())
9519 continue;
9520
9521 const auto& ec = m_recs.entities[e.id()];
9522 const auto entity = ec.pChunk->entity_view()[ec.row];
9523 if (!has(entity, cond))
9524 continue;
9525
9526 return true;
9527 }
9528 } else {
9529 for (auto e: ids) {
9530 if (e.pair())
9531 continue;
9532 if (!has(e, cond))
9533 continue;
9534
9535 return true;
9536 }
9537 }
9538
9539 return false;
9540 }
9541
9545 void move_to_archetype(Archetype& srcArchetype, Archetype& dstArchetype) {
9546 GAIA_ASSERT(&srcArchetype != &dstArchetype);
9547
9548 bool updated = false;
9549
9550 for (auto* pSrcChunk: srcArchetype.chunks()) {
9551 auto srcEnts = pSrcChunk->entity_view();
9552 if (srcEnts.empty())
9553 continue;
9554
9555 // Copy entities back-to-front to avoid unnecessary data movements.
9556 // TODO: Handle disabled entities efficiently.
9557 // If there are disabled entities, we still do data movements if there already
9558 // are enabled entities in the chunk.
9559 // TODO: If the header was of some fixed size, e.g. if we always acted as if we had
9560 // ChunkHeader::MAX_COMPONENTS, certain data movements could be done pretty much instantly.
9561 // E.g. when removing tags or pairs, we would simply replace the chunk pointer
9562 // with a pointer to another one. The same goes for archetypes. Component data
9563 // would not have to move at all internal chunk header pointers would remain unchanged.
9564
9565 uint32_t i = (uint32_t)srcEnts.size();
9566 while (i != 0) {
9567 auto* pDstChunk = dstArchetype.foc_free_chunk();
9568 const uint32_t dstSpaceLeft = pDstChunk->capacity() - pDstChunk->size();
9569 const uint32_t cnt = core::get_min(dstSpaceLeft, i);
9570 for (uint32_t j = 0; j < cnt; ++j) {
9571 auto e = srcEnts[i - j - 1];
9572 move_entity(e, fetch(e), dstArchetype, *pDstChunk);
9573 }
9574
9575 pDstChunk->update_world_version();
9576 pDstChunk->update_entity_order_version();
9577
9578 GAIA_ASSERT(cnt <= i);
9579 i -= cnt;
9580 }
9581
9582 pSrcChunk->update_world_version();
9583 pSrcChunk->update_entity_order_version();
9584 updated = true;
9585 }
9586
9587 if (updated)
9588 update_version(m_worldVersion);
9589 }
9590
9595 GAIA_NODISCARD Archetype* calc_dst_archetype_ent(Archetype* pArchetype, Entity entity) {
9596 GAIA_ASSERT(!is_wildcard(entity));
9597
9598 auto ids = pArchetype->ids_view();
9599 for (auto id: ids) {
9600 if (id != entity)
9601 continue;
9602
9603 return foc_archetype_del(pArchetype, id);
9604 }
9605
9606 return nullptr;
9607 }
9608
9613 GAIA_NODISCARD Archetype* calc_dst_archetype_all_ent(Archetype* pArchetype, Entity entity) {
9614 GAIA_ASSERT(is_wildcard(entity));
9615
9616 Archetype* pDstArchetype = pArchetype;
9617
9618 auto ids = pArchetype->ids_view();
9619 for (auto id: ids) {
9620 if (!id.pair() || id.gen() != entity.gen())
9621 continue;
9622
9623 pDstArchetype = foc_archetype_del(pDstArchetype, id);
9624 }
9625
9626 return pArchetype != pDstArchetype ? pDstArchetype : nullptr;
9627 }
9628
9633 GAIA_NODISCARD Archetype* calc_dst_archetype_ent_all(Archetype* pArchetype, Entity entity) {
9634 GAIA_ASSERT(is_wildcard(entity));
9635
9636 Archetype* pDstArchetype = pArchetype;
9637
9638 auto ids = pArchetype->ids_view();
9639 for (auto id: ids) {
9640 if (!id.pair() || id.id() != entity.id())
9641 continue;
9642
9643 pDstArchetype = foc_archetype_del(pDstArchetype, id);
9644 }
9645
9646 return pArchetype != pDstArchetype ? pDstArchetype : nullptr;
9647 }
9648
9653 GAIA_NODISCARD Archetype* calc_dst_archetype_all_all(Archetype* pArchetype, [[maybe_unused]] Entity entity) {
9654 GAIA_ASSERT(is_wildcard(entity));
9655
9656 Archetype* pDstArchetype = pArchetype;
9657 bool found = false;
9658
9659 auto ids = pArchetype->ids_view();
9660 for (auto id: ids) {
9661 if (!id.pair())
9662 continue;
9663
9664 pDstArchetype = foc_archetype_del(pDstArchetype, id);
9665 found = true;
9666 }
9667
9668 return found ? pDstArchetype : nullptr;
9669 }
9670
9676 GAIA_NODISCARD Archetype* calc_dst_archetype(Archetype* pArchetype, Entity entity) {
9677 if (entity.pair()) {
9678 auto rel = entity.id();
9679 auto tgt = entity.gen();
9680
9681 // Removing a wildcard pair. We need to find all pairs matching it.
9682 if (rel == All.id() || tgt == All.id()) {
9683 // (first, All) means we need to match (first, A), (first, B), ...
9684 if (rel != All.id() && tgt == All.id())
9685 return calc_dst_archetype_ent_all(pArchetype, entity);
9686
9687 // (All, second) means we need to match (A, second), (B, second), ...
9688 if (rel == All.id() && tgt != All.id())
9689 return calc_dst_archetype_all_ent(pArchetype, entity);
9690
9691 // (All, All) means we need to match all relationships
9692 return calc_dst_archetype_all_all(pArchetype, EntityBad);
9693 }
9694 }
9695
9696 // Non-wildcard pair or entity
9697 return calc_dst_archetype_ent(pArchetype, entity);
9698 }
9699
9700 void req_del(Archetype& archetype) {
9701 if (archetype.is_req_del())
9702 return;
9703
9704 archetype.req_del();
9705 m_reqArchetypesToDel.insert(ArchetypeLookupKey(archetype.lookup_hash(), &archetype));
9706 }
9707
9708 void req_del(EntityContainer& ec, Entity entity) {
9709 if (is_req_del(ec))
9710 return;
9711
9712#if GAIA_OBSERVERS_ENABLED
9713 auto delDiffCtx =
9714 m_observers.prepare_diff(*this, ObserverEvent::OnDel, EntitySpan{&entity, 1}, EntitySpan{&entity, 1});
9715#endif
9716 del_entity(ec, entity, false);
9717#if GAIA_OBSERVERS_ENABLED
9718 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
9719#endif
9720
9721 ec.req_del();
9722 m_reqEntitiesToDel.insert(EntityLookupKey(entity));
9723 }
9724
9725 void invalidate_pair_removal_caches(Entity entity) {
9726 if (!entity.pair())
9727 return;
9728
9729 auto invalidate_relation = [this](Entity relation) {
9730 if (relation == EntityBad)
9731 return;
9732 touch_rel_version(relation);
9733 invalidate_queries_for_rel(relation);
9734 };
9735
9736 if (entity.id() != All.id()) {
9737 invalidate_relation(try_get(entity.id()));
9738 } else if (entity.gen() != All.id()) {
9739 const auto target = try_get(entity.gen());
9740 if (target != EntityBad) {
9741 if (const auto* pRelations = relations(target)) {
9742 for (auto relationKey: *pRelations)
9743 invalidate_relation(relationKey.entity());
9744 }
9745 }
9746 } else {
9747 for (const auto& [relationKey, _]: m_relToTgt)
9748 invalidate_relation(relationKey.entity());
9749 }
9750
9751 m_targetsTravCache = {};
9752 m_srcBfsTravCache = {};
9753 m_depthOrderCache = {};
9754 m_sourcesAllCache = {};
9755 m_targetsAllCache = {};
9756 }
9757
9758 void unlink_live_is_relation(Entity source, Entity target) {
9759 const auto sourceKey = EntityLookupKey(source);
9760 const auto targetKey = EntityLookupKey(target);
9761
9762 invalidate_queries_for_entity({Is, target});
9763
9764 if (const auto itTargets = m_entityToAsTargets.find(sourceKey); itTargets != m_entityToAsTargets.end()) {
9765 itTargets->second.erase(targetKey);
9766 if (itTargets->second.empty())
9767 m_entityToAsTargets.erase(itTargets);
9768 }
9769 m_entityToAsTargetsTravCache = {};
9770
9771 if (const auto itRelations = m_entityToAsRelations.find(targetKey);
9772 itRelations != m_entityToAsRelations.end()) {
9773 itRelations->second.erase(sourceKey);
9774 if (itRelations->second.empty())
9775 m_entityToAsRelations.erase(itRelations);
9776 }
9777 m_entityToAsRelationsTravCache = {};
9778 }
9779
9780 void unlink_stale_is_relations_by_target_id(Entity source, EntityId targetId) {
9781 const auto sourceKey = EntityLookupKey(source);
9782 const auto itTargets = m_entityToAsTargets.find(sourceKey);
9783 if (itTargets == m_entityToAsTargets.end())
9784 return;
9785
9786 cnt::darray_ext<EntityLookupKey, 4> removedTargets;
9787 for (auto targetKey: itTargets->second) {
9788 if (targetKey.entity().id() == targetId)
9789 removedTargets.push_back(targetKey);
9790 }
9791
9792 for (auto targetKey: removedTargets) {
9793 invalidate_queries_for_structural_entity(EntityLookupKey(Pair{Is, targetKey.entity()}));
9794 itTargets->second.erase(targetKey);
9795
9796 const auto itRelations = m_entityToAsRelations.find(targetKey);
9797 if (itRelations != m_entityToAsRelations.end()) {
9798 itRelations->second.erase(sourceKey);
9799 if (itRelations->second.empty())
9800 m_entityToAsRelations.erase(itRelations);
9801 }
9802 }
9803
9804 if (itTargets->second.empty())
9805 m_entityToAsTargets.erase(itTargets);
9806
9807 m_entityToAsTargetsTravCache = {};
9808 m_entityToAsRelationsTravCache = {};
9809 }
9810
9811 template <typename Func>
9812 void each_delete_cascade_direct_source(Entity target, Pair cond, Func&& func) {
9813 GAIA_ASSERT(!target.pair());
9814
9815 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
9816 const auto relation = relKey.entity();
9817 if (!has(relation, cond))
9818 continue;
9819
9820 const auto* pSources = exclusive_adjunct_sources(store, target);
9821 if (pSources == nullptr)
9822 continue;
9823
9824 for (auto source: *pSources)
9825 func(source);
9826 }
9827
9828 const auto pairEntity = Pair(All, target);
9829 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pairEntity));
9830 if (it != m_entityToArchetypeMap.end()) {
9831 for (const auto& record: it->second) {
9832 auto* pArchetype = record.pArchetype;
9833 if (pArchetype == nullptr || pArchetype->is_req_del())
9834 continue;
9835 if (!archetype_cond_match(*pArchetype, cond, pairEntity))
9836 continue;
9837
9838 for (const auto* pChunk: pArchetype->chunks()) {
9839 const auto entities = pChunk->entity_view();
9840 GAIA_EACH(entities)
9841 func(entities[i]);
9842 }
9843 }
9844 }
9845 }
9846
9847 void collect_delete_cascade_direct_sources(Entity target, Pair cond, cnt::darray<Entity>& out) {
9848 GAIA_ASSERT(!target.pair());
9849 const auto visitStamp = next_entity_visit_stamp();
9850 each_delete_cascade_direct_source(target, cond, [&](Entity source) {
9851 if (!valid(source))
9852 return;
9853 if (!try_mark_entity_visited(source, visitStamp))
9854 return;
9855 out.push_back(source);
9856 });
9857 }
9858
9859 void collect_delete_cascade_sources(Entity target, Pair cond, cnt::darray<Entity>& out) {
9860 GAIA_ASSERT(!target.pair());
9861 const auto visitStamp = next_entity_visit_stamp();
9862 cnt::darray_ext<Entity, 32> targetsToVisit;
9863 (void)try_mark_entity_visited(target, visitStamp);
9864 targetsToVisit.push_back(target);
9865
9866 for (uint32_t i = 0; i < targetsToVisit.size(); ++i) {
9867 const auto currTarget = targetsToVisit[i];
9868 each_delete_cascade_direct_source(currTarget, cond, [&](Entity source) {
9869 if (!valid(source))
9870 return;
9871 if (!try_mark_entity_visited(source, visitStamp))
9872 return;
9873
9874 out.push_back(source);
9875 targetsToVisit.push_back(source);
9876 });
9877 }
9878 }
9879
9882 void req_del_entities_with(Entity entity) {
9883 GAIA_PROF_SCOPE(World::req_del_entities_with);
9884
9885 GAIA_ASSERT(entity != Pair(All, All));
9886
9887 auto req_del_adjunct_pair = [&](Entity relation, Entity target) {
9888 cnt::darray<Entity> sourcesToDel;
9889 sources(relation, target, [&](Entity source) {
9890 sourcesToDel.push_back(source);
9891 });
9892
9893 for (auto source: sourcesToDel)
9894 req_del(fetch(source), source);
9895 };
9896
9897 if (entity.pair()) {
9898 if (entity.id() != All.id()) {
9899 const auto relation = try_get(entity.id());
9900 const auto target = try_get(entity.gen());
9901 if (relation != EntityBad && target != EntityBad && is_exclusive_dont_fragment_relation(relation))
9902 req_del_adjunct_pair(relation, target);
9903 } else {
9904 const auto target = try_get(entity.gen());
9905 if (target == EntityBad)
9906 goto skip_req_del_all_target;
9907 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
9908 if (exclusive_adjunct_sources(store, target) == nullptr)
9909 continue;
9910
9911 req_del_adjunct_pair(relKey.entity(), target);
9912 }
9913 skip_req_del_all_target:;
9914 }
9915 } else if (is_exclusive_dont_fragment_relation(entity)) {
9916 if (const auto* pTargets = targets(entity)) {
9917 for (auto targetKey: *pTargets)
9918 req_del_adjunct_pair(entity, targetKey.entity());
9919 }
9920 }
9921
9922 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
9923 if (it == m_entityToArchetypeMap.end())
9924 return;
9925
9926 for (const auto& record: it->second)
9927 req_del(*record.pArchetype);
9928 }
9929
9934 void req_del_entities_with(Entity entity, Pair cond) {
9935 cnt::set<EntityLookupKey> visited;
9936 req_del_entities_with(entity, cond, visited);
9937 }
9938
9939 void req_del_entities_with(Entity entity, Pair cond, cnt::set<EntityLookupKey>& visited) {
9940 GAIA_PROF_SCOPE(World::req_del_entities_with);
9941
9942 GAIA_ASSERT(entity != Pair(All, All));
9943 if (!visited.insert(EntityLookupKey(entity)).second)
9944 return;
9945
9946 auto req_del_adjunct_pair = [&](Entity relation, Entity target) {
9947 if (!has(relation, cond))
9948 return;
9949
9950 cnt::darray<Entity> sourcesToDel;
9951 sources(relation, target, [&](Entity source) {
9952 sourcesToDel.push_back(source);
9953 });
9954
9955 for (auto source: sourcesToDel)
9956 req_del(fetch(source), source);
9957 };
9958
9959 if (entity.pair()) {
9960 if (entity.id() != All.id()) {
9961 const auto relation = try_get(entity.id());
9962 const auto target = try_get(entity.gen());
9963 if (relation != EntityBad && target != EntityBad && is_exclusive_dont_fragment_relation(relation))
9964 req_del_adjunct_pair(relation, target);
9965 } else {
9966 const auto target = try_get(entity.gen());
9967 if (target == EntityBad)
9968 goto skip_req_del_all_target_cond;
9969 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
9970 if (exclusive_adjunct_sources(store, target) == nullptr)
9971 continue;
9972
9973 req_del_adjunct_pair(relKey.entity(), target);
9974 }
9975 skip_req_del_all_target_cond:;
9976 }
9977 } else if (is_exclusive_dont_fragment_relation(entity)) {
9978 if (const auto* pTargets = targets(entity)) {
9979 for (auto targetKey: *pTargets)
9980 req_del_adjunct_pair(entity, targetKey.entity());
9981 }
9982 }
9983
9984 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
9985 if (it == m_entityToArchetypeMap.end())
9986 return;
9987
9988 cnt::darray<Entity> cascadeTargets;
9989 if (entity.pair() && entity.id() == All.id()) {
9990 const auto target = try_get(entity.gen());
9991 if (target != EntityBad)
9992 collect_delete_cascade_direct_sources(target, cond, cascadeTargets);
9993 }
9994
9995 for (auto source: cascadeTargets)
9996 req_del_entities_with(Pair(All, source), cond, visited);
9997
9998 for (const auto& record: it->second) {
9999 auto* pArchetype = record.pArchetype;
10000 // Evaluate the condition if a valid pair is given
10001 if (!archetype_cond_match(*pArchetype, cond, entity))
10002 continue;
10003
10004 req_del(*pArchetype);
10005 }
10006 }
10007
10010 void rem_from_entities(Entity entity) {
10011 GAIA_PROF_SCOPE(World::rem_from_entities);
10012
10013 invalidate_pair_removal_caches(entity);
10014
10015 auto rem_adjunct_pair = [&](Entity relation, Entity target) {
10016 cnt::darray<Entity> sourcesToRem;
10017 sources(relation, target, [&](Entity source) {
10018 sourcesToRem.push_back(source);
10019 });
10020
10021 for (auto source: sourcesToRem)
10022 del(source, Pair(relation, target));
10023 };
10024
10025 if (entity.pair()) {
10026 if (entity.id() != All.id()) {
10027 const auto relation = try_get(entity.id());
10028 const auto target = try_get(entity.gen());
10029 if (relation != EntityBad && target != EntityBad && is_exclusive_dont_fragment_relation(relation))
10030 rem_adjunct_pair(relation, target);
10031 } else {
10032 const auto target = try_get(entity.gen());
10033 if (target == EntityBad)
10034 goto skip_rem_all_target;
10035 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
10036 if (exclusive_adjunct_sources(store, target) == nullptr)
10037 continue;
10038
10039 rem_adjunct_pair(relKey.entity(), target);
10040 }
10041 skip_rem_all_target:;
10042 }
10043 } else if (is_exclusive_dont_fragment_relation(entity)) {
10044 if (const auto* pTargets = targets(entity)) {
10045 for (auto targetKey: *pTargets)
10046 rem_adjunct_pair(entity, targetKey.entity());
10047 }
10048 }
10049
10050 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
10051 if (it == m_entityToArchetypeMap.end())
10052 return;
10053
10054 // Invalidate the singleton status if necessary
10055 if (!entity.pair()) {
10056 auto& ec = fetch(entity);
10057 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0) {
10058 auto ids = ec.pArchetype->ids_view();
10059 const auto idx = core::get_index(ids, entity);
10060 if (idx != BadIndex)
10061 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::IsSingleton, false);
10062 }
10063 }
10064
10065#if GAIA_OBSERVERS_ENABLED
10066 cnt::set<EntityLookupKey> diffTermSet;
10067 cnt::darray<Entity> diffTerms;
10068 cnt::darray<Entity> diffTargets;
10069 for (const auto& record: it->second) {
10070 auto* pArchetype = record.pArchetype;
10071 if (pArchetype->is_req_del())
10072 continue;
10073
10074 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
10075 if (pDstArchetype == nullptr)
10076 continue;
10077
10078 for (auto id: pArchetype->ids_view()) {
10079 bool matches = false;
10080 if (entity.pair()) {
10081 if (entity.id() == All.id() && entity.gen() == All.id())
10082 matches = id.pair();
10083 else if (entity.id() == All.id())
10084 matches = id.pair() && id.gen() == entity.gen();
10085 else if (entity.gen() == All.id())
10086 matches = id.pair() && id.id() == entity.id();
10087 else
10088 matches = id == entity;
10089 } else
10090 matches = id == entity;
10091
10092 if (!matches)
10093 continue;
10094
10095 if (diffTermSet.insert(EntityLookupKey(id)).second)
10096 diffTerms.push_back(id);
10097 }
10098
10099 for (const auto* pChunk: pArchetype->chunks()) {
10100 const auto entities = pChunk->entity_view();
10101 GAIA_EACH(entities)
10102 diffTargets.push_back(entities[i]);
10103 }
10104 }
10105 auto delDiffCtx = diffTargets.empty() || diffTerms.empty()
10106 ? ObserverRegistry::DiffDispatchCtx{}
10107 : m_observers.prepare_diff(
10108 *this, ObserverEvent::OnDel, EntitySpan{diffTerms.data(), diffTerms.size()},
10109 EntitySpan{diffTargets.data(), diffTargets.size()});
10110#endif
10111
10112 // Update archetypes of all affected entities
10113 for (const auto& record: it->second) {
10114 auto* pArchetype = record.pArchetype;
10115 if (pArchetype->is_req_del())
10116 continue;
10117
10118 if (entity.pair()) {
10119 cnt::darray_ext<Entity, 16> removedIsTargets;
10120 for (auto id: pArchetype->ids_view()) {
10121 bool matches = false;
10122 if (entity.id() == All.id() && entity.gen() == All.id())
10123 matches = id.pair();
10124 else if (entity.id() == All.id())
10125 matches = id.pair() && id.gen() == entity.gen();
10126 else if (entity.gen() == All.id())
10127 matches = id.pair() && id.id() == entity.id();
10128 else
10129 matches = id == entity;
10130
10131 if (!matches || !id.pair() || id.id() != Is.id())
10132 continue;
10133
10134 const auto target = try_get(id.gen());
10135 if (target != EntityBad)
10136 removedIsTargets.push_back(target);
10137 }
10138
10139 if (!removedIsTargets.empty()) {
10140 for (const auto* pChunk: pArchetype->chunks()) {
10141 auto entities = pChunk->entity_view();
10142 GAIA_EACH(entities) {
10143 for (const auto target: removedIsTargets)
10144 unlink_live_is_relation(entities[i], target);
10145 }
10146 }
10147 }
10148 }
10149
10150 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
10151 if (pDstArchetype != nullptr)
10152 move_to_archetype(*pArchetype, *pDstArchetype);
10153 }
10154
10155#if GAIA_OBSERVERS_ENABLED
10156 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
10157#endif
10158 }
10159
10164 void rem_from_entities(Entity entity, Pair cond) {
10165 GAIA_PROF_SCOPE(World::rem_from_entities);
10166
10167 invalidate_pair_removal_caches(entity);
10168
10169 auto rem_adjunct_pair = [&](Entity relation, Entity target) {
10170 if (!has(relation, cond))
10171 return;
10172
10173 cnt::darray<Entity> sourcesToRem;
10174 sources(relation, target, [&](Entity source) {
10175 sourcesToRem.push_back(source);
10176 });
10177
10178 for (auto source: sourcesToRem)
10179 del(source, Pair(relation, target));
10180 };
10181
10182 if (entity.pair()) {
10183 if (entity.id() != All.id()) {
10184 const auto relation = try_get(entity.id());
10185 const auto target = try_get(entity.gen());
10186 if (relation != EntityBad && target != EntityBad && is_exclusive_dont_fragment_relation(relation))
10187 rem_adjunct_pair(relation, target);
10188 } else {
10189 const auto target = try_get(entity.gen());
10190 if (target == EntityBad)
10191 goto skip_rem_all_target_cond;
10192 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
10193 if (exclusive_adjunct_sources(store, target) == nullptr)
10194 continue;
10195
10196 rem_adjunct_pair(relKey.entity(), target);
10197 }
10198 skip_rem_all_target_cond:;
10199 }
10200 } else if (is_exclusive_dont_fragment_relation(entity)) {
10201 if (const auto* pTargets = targets(entity)) {
10202 for (auto targetKey: *pTargets)
10203 rem_adjunct_pair(entity, targetKey.entity());
10204 }
10205 }
10206
10207 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
10208 if (it == m_entityToArchetypeMap.end())
10209 return;
10210
10211 // Invalidate the singleton status if necessary
10212 if (!entity.pair()) {
10213 auto& ec = fetch(entity);
10214 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0) {
10215 auto ids = ec.pArchetype->ids_view();
10216 const auto idx = core::get_index(ids, entity);
10217 if (idx != BadIndex)
10218 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::IsSingleton, false);
10219 }
10220 }
10221
10222#if GAIA_OBSERVERS_ENABLED
10223 cnt::set<EntityLookupKey> diffTermSet;
10224 cnt::darray<Entity> diffTerms;
10225 cnt::darray<Entity> diffTargets;
10226 for (const auto& record: it->second) {
10227 auto* pArchetype = record.pArchetype;
10228 if (pArchetype->is_req_del())
10229 continue;
10230
10231 if (!archetype_cond_match(*pArchetype, cond, entity))
10232 continue;
10233
10234 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
10235 if (pDstArchetype == nullptr)
10236 continue;
10237
10238 for (auto id: pArchetype->ids_view()) {
10239 bool matches = false;
10240 if (entity.pair()) {
10241 if (entity.id() == All.id() && entity.gen() == All.id())
10242 matches = id.pair();
10243 else if (entity.id() == All.id())
10244 matches = id.pair() && id.gen() == entity.gen();
10245 else if (entity.gen() == All.id())
10246 matches = id.pair() && id.id() == entity.id();
10247 else
10248 matches = id == entity;
10249 } else
10250 matches = id == entity;
10251
10252 if (!matches)
10253 continue;
10254
10255 if (diffTermSet.insert(EntityLookupKey(id)).second)
10256 diffTerms.push_back(id);
10257 }
10258
10259 for (const auto* pChunk: pArchetype->chunks()) {
10260 const auto entities = pChunk->entity_view();
10261 GAIA_EACH(entities)
10262 diffTargets.push_back(entities[i]);
10263 }
10264 }
10265 auto delDiffCtx = diffTargets.empty() || diffTerms.empty()
10266 ? ObserverRegistry::DiffDispatchCtx{}
10267 : m_observers.prepare_diff(
10268 *this, ObserverEvent::OnDel, EntitySpan{diffTerms.data(), diffTerms.size()},
10269 EntitySpan{diffTargets.data(), diffTargets.size()});
10270#endif
10271
10272 for (const auto& record: it->second) {
10273 auto* pArchetype = record.pArchetype;
10274 if (pArchetype->is_req_del())
10275 continue;
10276
10277 // Evaluate the condition if a valid pair is given
10278 if (!archetype_cond_match(*pArchetype, cond, entity))
10279 continue;
10280
10281 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
10282 if (pDstArchetype != nullptr)
10283 move_to_archetype(*pArchetype, *pDstArchetype);
10284 }
10285
10286#if GAIA_OBSERVERS_ENABLED
10287 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
10288#endif
10289 }
10290
10303 void handle_del_entity(EntityContainer& ec, Entity entity) {
10304 GAIA_PROF_SCOPE(World::handle_del_entity);
10305
10306 GAIA_ASSERT(!is_wildcard(entity));
10307
10308 if (entity.pair()) {
10309 if ((ec.flags & EntityContainerFlags::OnDelete_Error) != 0) {
10310 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted");
10311 GAIA_LOG_E(
10312 "Trying to delete a pair [%u.%u] %s [%s] that is forbidden from being deleted", entity.id(),
10313 entity.gen(), name(entity), EntityKindString[entity.kind()]);
10314 return;
10315 }
10316
10317 const auto tgt = try_get(entity.gen());
10318 const bool hasLiveTarget = tgt != EntityBad;
10319 if (hasLiveTarget) {
10320 const auto& ecTgt = fetch(tgt);
10321 if ((ecTgt.flags & EntityContainerFlags::OnDeleteTarget_Error) != 0 ||
10322 has_exclusive_adjunct_target_cond(tgt, Pair(OnDeleteTarget, Error))) {
10323 GAIA_ASSERT2(
10324 false, "Trying to delete an entity that is forbidden from being deleted (target restriction)");
10325 GAIA_LOG_E(
10326 "Trying to delete a pair [%u.%u] %s [%s] that is forbidden from being deleted (target restriction)",
10327 entity.id(), entity.gen(), name(entity), EntityKindString[entity.kind()]);
10328 return;
10329 }
10330 }
10331
10332#if GAIA_USE_SAFE_ENTITY
10333 // Decrement the ref count at this point.
10334 if ((ec.flags & EntityContainerFlags::RefDecreased) == 0) {
10335 --ec.refCnt;
10336 ec.flags |= EntityContainerFlags::RefDecreased;
10337 }
10338
10339 // Don't delete so long something still references us
10340 if (ec.refCnt != 0)
10341 return;
10342#endif
10343
10344 if (hasLiveTarget) {
10345 const auto& ecTgt = fetch(tgt);
10346 if ((ecTgt.flags & EntityContainerFlags::OnDeleteTarget_Delete) != 0 ||
10347 has_exclusive_adjunct_target_cond(tgt, Pair(OnDeleteTarget, Delete))) {
10348#if GAIA_OBSERVERS_ENABLED
10349 cnt::darray<Entity> cascadeTargets;
10350 collect_delete_cascade_sources(tgt, Pair(OnDeleteTarget, Delete), cascadeTargets);
10351 auto cascadeDelDiffCtx =
10352 cascadeTargets.empty()
10353 ? ObserverRegistry::DiffDispatchCtx{}
10354 : m_observers.prepare_diff(
10355 *this, ObserverEvent::OnDel, EntitySpan{cascadeTargets.data(), cascadeTargets.size()},
10356 EntitySpan{cascadeTargets.data(), cascadeTargets.size()});
10357#endif
10358 // Delete all entities referencing this one as a relationship pair's target
10359 req_del_entities_with(Pair(All, tgt), Pair(OnDeleteTarget, Delete));
10360#if GAIA_OBSERVERS_ENABLED
10361 m_observers.finish_diff(*this, GAIA_MOV(cascadeDelDiffCtx));
10362#endif
10363 } else {
10364 // Remove from all entities referencing this one as a relationship pair's target
10365 rem_from_entities(Pair(All, tgt));
10366 }
10367 }
10368
10369 // This entity has been requested to be deleted already. Nothing more for us to do here
10370 if (is_req_del(ec))
10371 return;
10372
10373#if GAIA_OBSERVERS_ENABLED
10374 observers().del(*this, entity);
10375#endif
10376#if GAIA_SYSTEMS_ENABLED
10377 systems().del(entity);
10378#endif
10379
10380 if ((ec.flags & EntityContainerFlags::OnDelete_Delete) != 0) {
10381 // Delete all references to the entity
10382 req_del_entities_with(entity);
10383 } else {
10384 // Entities are only removed by default
10385 rem_from_entities(entity);
10386 }
10387 } else {
10388 if ((ec.flags & EntityContainerFlags::OnDelete_Error) != 0) {
10389 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted");
10390 GAIA_LOG_E(
10391 "Trying to delete an entity [%u.%u] %s [%s] that is forbidden from being deleted", entity.id(),
10392 entity.gen(), name(entity), EntityKindString[entity.kind()]);
10393 return;
10394 }
10395
10396 if ((ec.flags & EntityContainerFlags::OnDeleteTarget_Error) != 0 ||
10397 has_exclusive_adjunct_target_cond(entity, Pair(OnDeleteTarget, Error))) {
10398 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted (a pair's target)");
10399 GAIA_LOG_E(
10400 "Trying to delete an entity [%u.%u] %s [%s] that is forbidden from being deleted (a pair's target)",
10401 entity.id(), entity.gen(), name(entity), EntityKindString[entity.kind()]);
10402 return;
10403 }
10404
10405#if GAIA_USE_SAFE_ENTITY
10406 // Decrement the ref count at this point.
10407 if ((ec.flags & EntityContainerFlags::RefDecreased) == 0) {
10408 --ec.refCnt;
10409 ec.flags |= EntityContainerFlags::RefDecreased;
10410 }
10411
10412 // Don't delete so long something still references us
10413 if (ec.refCnt != 0)
10414 return;
10415#endif
10416
10417 const bool deleteTargets = (ec.flags & EntityContainerFlags::OnDeleteTarget_Delete) != 0 ||
10418 has_exclusive_adjunct_target_cond(entity, Pair(OnDeleteTarget, Delete));
10419 cnt::darray<Entity> cascadeTargets;
10420 if (deleteTargets)
10421 collect_delete_cascade_sources(entity, Pair(OnDeleteTarget, Delete), cascadeTargets);
10422#if GAIA_OBSERVERS_ENABLED
10423 auto cascadeDelDiffCtx = ObserverRegistry::DiffDispatchCtx{};
10424 if (!cascadeTargets.empty()) {
10425 cascadeDelDiffCtx = m_observers.prepare_diff(
10426 *this, ObserverEvent::OnDel, EntitySpan{cascadeTargets.data(), cascadeTargets.size()},
10427 EntitySpan{cascadeTargets.data(), cascadeTargets.size()});
10428 }
10429#endif
10430
10431 if (deleteTargets) {
10432 // Delete all entities referencing this one as a relationship pair's target
10433 req_del_entities_with(Pair(All, entity), Pair(OnDeleteTarget, Delete));
10434 } else {
10435 // Remove from all entities referencing this one as a relationship pair's target
10436 rem_from_entities(Pair(All, entity));
10437 }
10438
10439#if GAIA_OBSERVERS_ENABLED
10440 m_observers.finish_diff(*this, GAIA_MOV(cascadeDelDiffCtx));
10441#endif
10442
10443 // This entity is has been requested to be deleted already. Nothing more for us to do here
10444 if (is_req_del(ec))
10445 return;
10446
10447#if GAIA_OBSERVERS_ENABLED
10448 observers().del(*this, entity);
10449#endif
10450#if GAIA_SYSTEMS_ENABLED
10451 systems().del(entity);
10452#endif
10453
10454 if ((ec.flags & EntityContainerFlags::OnDelete_Delete) != 0) {
10455 // Delete all references to the entity
10456 req_del_entities_with(entity);
10457 } else {
10458 // Entities are only removed by default
10459 rem_from_entities(entity);
10460 }
10461 }
10462
10463 // Mark the entity with the "delete requested" flag
10464 req_del(ec, entity);
10465
10466#if GAIA_USE_WEAK_ENTITY
10467 // Invalidate WeakEntities
10468 while (ec.pWeakTracker != nullptr) {
10469 auto* pTracker = ec.pWeakTracker;
10470 ec.pWeakTracker = pTracker->next;
10471 if (ec.pWeakTracker != nullptr)
10472 ec.pWeakTracker->prev = nullptr;
10473
10474 auto* pWeakEntity = pTracker->pWeakEntity;
10475 GAIA_ASSERT(pWeakEntity != nullptr);
10476 GAIA_ASSERT(pWeakEntity->m_pTracker == pTracker);
10477 pWeakEntity->m_pTracker = nullptr;
10478 pWeakEntity->m_entity = EntityBad;
10479 delete pTracker;
10480 }
10481#endif
10482 }
10483
10488 void remove_edge_from_archetype(Archetype* pArchetype, ArchetypeGraphEdge edgeLeft, Entity edgeEntity) {
10489 GAIA_ASSERT(pArchetype != nullptr);
10490
10491 const auto edgeLeftIt = m_archetypesById.find(ArchetypeIdLookupKey(edgeLeft.id, edgeLeft.hash));
10492 if (edgeLeftIt == m_archetypesById.end())
10493 return;
10494
10495 auto* pArchetypeLeft = edgeLeftIt->second;
10496 GAIA_ASSERT(pArchetypeLeft != nullptr);
10497
10498 // Remove the connection with the current archetype
10499 pArchetypeLeft->del_graph_edges(pArchetype, edgeEntity);
10500
10501 // Traverse all archetypes on the right
10502 auto& archetypesRight = pArchetype->right_edges();
10503 for (auto& it: archetypesRight) {
10504 const auto& edgeRight = it.second;
10505 const auto edgeRightIt = m_archetypesById.find(ArchetypeIdLookupKey(edgeRight.id, edgeRight.hash));
10506 if (edgeRightIt == m_archetypesById.end())
10507 continue;
10508
10509 auto* pArchetypeRight = edgeRightIt->second;
10510
10511 // Remove the connection with the current archetype
10512 pArchetype->del_graph_edges(pArchetypeRight, it.first.entity());
10513 }
10514 }
10515
10516 void remove_edges(Entity entityToRemove) {
10517 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entityToRemove));
10518 if (it == m_entityToArchetypeMap.end())
10519 return;
10520
10521 for (const auto& record: it->second) {
10522 auto* pArchetype = record.pArchetype;
10523 remove_edge_from_archetype(pArchetype, pArchetype->find_edge_left(entityToRemove), entityToRemove);
10524 }
10525 }
10526
10527 void remove_edges_from_pairs(Entity entity) {
10528 if (entity.pair())
10529 return;
10530
10531 // Make sure to remove all pairs containing the entity
10532 // (X, something)
10533 const auto* tgts = targets(entity);
10534 if (tgts != nullptr) {
10535 for (auto target: *tgts)
10536 remove_edges(Pair(entity, target.entity()));
10537 }
10538 // (something, X)
10539 const auto* rels = relations(entity);
10540 if (rels != nullptr) {
10541 for (auto relation: *rels)
10542 remove_edges(Pair(relation.entity(), entity));
10543 }
10544 }
10545
10548 void del_graph_edges(Entity entity) {
10549 remove_edges(entity);
10550 remove_edges_from_pairs(entity);
10551 }
10552
10553 void touch_rel_version(Entity relation) {
10554 const EntityLookupKey key(relation);
10555 auto it = m_relationVersions.find(key);
10556 if (it == m_relationVersions.end())
10557 m_relationVersions.emplace(key, 1);
10558 else {
10559 ++it->second;
10560 if (it->second == 0)
10561 it->second = 1;
10562 }
10563 }
10564
10565 void del_reltgt_tgtrel_pairs(Entity entity) {
10566 auto delPair = [](PairMap& map, Entity source, Entity remove) {
10567 auto itTargets = map.find(EntityLookupKey(source));
10568 if (itTargets != map.end()) {
10569 auto& targets = itTargets->second;
10570 targets.erase(EntityLookupKey(remove));
10571 }
10572 };
10573
10574 Archetype* pArchetype = nullptr;
10575
10576 if (entity.pair()) {
10577 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
10578 if (it != m_recs.pairs.end()) {
10579 pArchetype = it->second.pArchetype;
10580 // Delete the container record
10581 m_recs.pairs.erase(it);
10582
10583 // Update pairs
10584 // The relation or target entity may already be invalid at this point. Rebuild the
10585 // lookup keys from the stored entity records instead of calling get().
10586 GAIA_ASSERT(entity.id() < m_recs.entities.size());
10587 GAIA_ASSERT(entity.gen() < m_recs.entities.size());
10588 auto rel = m_recs.entities.handle(entity.id());
10589 auto tgt = m_recs.entities.handle(entity.gen());
10590
10591 delPair(m_relToTgt, rel, tgt);
10592 delPair(m_relToTgt, All, tgt);
10593 delPair(m_tgtToRel, tgt, rel);
10594 delPair(m_tgtToRel, All, rel);
10595 }
10596 } else {
10597 // Update the container record
10598 auto ec = m_recs.entities[entity.id()];
10599 m_recs.entities.free(entity);
10600
10601 // Remove all outgoing non-fragmenting sparse components from this entity.
10602 del_sparse_components(entity);
10603 // Remove all outgoing non-fragmenting exclusive relations from this source entity.
10604 del_exclusive_adjunct_source(entity);
10605 // If the deleted entity is itself a non-fragmenting exclusive relation, drop its store.
10606 del_exclusive_adjunct_relation(entity);
10607 // If the deleted entity is itself a non-fragmenting sparse component, drop its store.
10608 del_sparse_component_store(entity);
10609
10610 // If this is a singleton entity its archetype needs to be deleted
10611 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0)
10612 req_del(*ec.pArchetype);
10613
10614 ec.pArchetype = nullptr;
10615 ec.pChunk = nullptr;
10616 ec.pEntity = nullptr;
10617 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::DeleteRequested, false);
10618
10619 // Update pairs
10620 delPair(m_relToTgt, All, entity);
10621 delPair(m_tgtToRel, All, entity);
10622 m_relToTgt.erase(EntityLookupKey(entity));
10623 m_tgtToRel.erase(EntityLookupKey(entity));
10624 }
10625
10626 del_entity_archetype_pairs(entity, pArchetype);
10627 }
10628
10631 void invalidate_entity(Entity entity) {
10632 del_graph_edges(entity);
10633 del_reltgt_tgtrel_pairs(entity);
10634 }
10635
10641 void store_entity(EntityContainer& ec, Entity entity, Archetype* pArchetype, Chunk* pChunk) {
10642 GAIA_ASSERT(pArchetype != nullptr);
10643 GAIA_ASSERT(pChunk != nullptr);
10644 GAIA_ASSERT(
10645 !locked() && "Entities can't be stored while the world is locked "
10646 "(structural changes are forbidden during this time!)");
10647
10648 ec.pArchetype = pArchetype;
10649 ec.pChunk = pChunk;
10650 ec.row = pChunk->add_entity(entity);
10651 ec.pEntity = &pChunk->entity_view()[ec.row];
10652 GAIA_ASSERT(entity.pair() || ec.data.gen == entity.gen());
10653 ec.data.dis = 0;
10654 }
10655
10661 void move_entity(Entity entity, EntityContainer& ec, Archetype& dstArchetype, Chunk& dstChunk) {
10662 GAIA_PROF_SCOPE(World::move_entity);
10663
10664 auto* pDstChunk = &dstChunk;
10665 auto* pSrcChunk = ec.pChunk;
10666
10667 GAIA_ASSERT(pDstChunk != pSrcChunk);
10668
10669 const auto srcRow0 = ec.row;
10670 const auto dstRow = pDstChunk->add_entity(entity);
10671 const bool wasEnabled = !ec.data.dis;
10672
10673 auto& srcArchetype = *ec.pArchetype;
10674 const bool archetypeChanged = srcArchetype.id() != dstArchetype.id();
10675#if GAIA_ASSERT_ENABLED
10676 verify_move(*this, srcArchetype, entity);
10677#endif
10678
10679 // Make sure the old entity becomes enabled now
10680 srcArchetype.enable_entity(pSrcChunk, srcRow0, true, m_recs);
10681 // Enabling the entity might have changed its chunk index so fetch it again
10682 const auto srcRow = ec.row;
10683
10684 // Move data from the old chunk to the new one
10685 if (dstArchetype.id() == srcArchetype.id()) {
10686 pDstChunk->move_entity_data(entity, dstRow, m_recs);
10687 } else {
10688 pDstChunk->move_foreign_entity_data(pSrcChunk, srcRow, pDstChunk, dstRow);
10689 }
10690
10691 // Remove the entity record from the old chunk
10692 remove_entity(srcArchetype, *pSrcChunk, srcRow);
10693
10694 // An entity might have moved, try updating the free chunk index
10695 dstArchetype.try_update_free_chunk_idx();
10696
10697 // Bring the entity container record up-to-date
10698 ec.pArchetype = &dstArchetype;
10699 ec.pChunk = pDstChunk;
10700 ec.row = (uint16_t)dstRow;
10701 ec.pEntity = &pDstChunk->entity_view()[dstRow];
10702 if (archetypeChanged)
10703 update_src_entity_version(entity);
10704
10705 // Make the enabled state in the new chunk match the original state
10706 dstArchetype.enable_entity(pDstChunk, dstRow, wasEnabled, m_recs);
10707
10708 // End-state validation
10709 GAIA_ASSERT(valid(entity));
10710 validate_chunk(pSrcChunk);
10711 validate_chunk(pDstChunk);
10712 validate_entities();
10713 }
10714
10719 void move_entity_raw(Entity entity, EntityContainer& ec, Archetype& dstArchetype) {
10720 // Update the old chunk's world version first
10721 ec.pChunk->update_world_version();
10722 ec.pChunk->update_entity_order_version();
10723
10724 auto* pDstChunk = dstArchetype.foc_free_chunk();
10725 move_entity(entity, ec, dstArchetype, *pDstChunk);
10726
10727 // Update world versions
10728 pDstChunk->update_world_version();
10729 pDstChunk->update_entity_order_version();
10730 update_version(m_worldVersion);
10731 }
10732
10736 Chunk* move_entity(Entity entity, Archetype& dstArchetype) {
10737 // Archetypes need to be different
10738 auto& ec = fetch(entity);
10739 if (ec.pArchetype == &dstArchetype)
10740 return nullptr;
10741
10742 // Update the old chunk's world version first
10743 ec.pChunk->update_world_version();
10744 ec.pChunk->update_entity_order_version();
10745
10746 auto* pDstChunk = dstArchetype.foc_free_chunk();
10747 move_entity(entity, ec, dstArchetype, *pDstChunk);
10748
10749 // Update world versions
10750 pDstChunk->update_world_version();
10751 pDstChunk->update_entity_order_version();
10752 update_version(m_worldVersion);
10753
10754 return pDstChunk;
10755 }
10756
10757 void validate_archetype_edges([[maybe_unused]] const Archetype* pArchetype) const {
10758#if GAIA_ECS_VALIDATE_ARCHETYPE_GRAPH && GAIA_ASSERT_ENABLED
10759 GAIA_ASSERT(pArchetype != nullptr);
10760
10761 // Validate left edges
10762 const auto& archetypesLeft = pArchetype->left_edges();
10763 for (const auto& it: archetypesLeft) {
10764 const auto& edge = it.second;
10765 const auto edgeIt = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
10766 if (edgeIt == m_archetypesById.end())
10767 continue;
10768
10769 const auto entity = it.first.entity();
10770 const auto* pArchetypeRight = edgeIt->second;
10771
10772 // Edge must be found
10773 const auto edgeRight = pArchetypeRight->find_edge_right(entity);
10774 GAIA_ASSERT(edgeRight != ArchetypeIdHashPairBad);
10775
10776 // The edge must point to pArchetype
10777 const auto it2 = m_archetypesById.find(ArchetypeIdLookupKey(edgeRight.id, edgeRight.hash));
10778 GAIA_ASSERT(it2 != m_archetypesById.end());
10779 const auto* pArchetype2 = it2->second;
10780 GAIA_ASSERT(pArchetype2 == pArchetype);
10781 }
10782
10783 // Validate right edges
10784 const auto& archetypesRight = pArchetype->right_edges();
10785 for (const auto& it: archetypesRight) {
10786 const auto& edge = it.second;
10787 const auto edgeIt = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
10788 if (edgeIt == m_archetypesById.end())
10789 continue;
10790
10791 const auto entity = it.first.entity();
10792 const auto* pArchetypeRight = edgeIt->second;
10793
10794 // Edge must be found
10795 const auto edgeLeft = pArchetypeRight->find_edge_left(entity);
10796 GAIA_ASSERT(edgeLeft != ArchetypeIdHashPairBad);
10797
10798 // The edge must point to pArchetype
10799 const auto it2 = m_archetypesById.find(ArchetypeIdLookupKey(edgeLeft.id, edgeLeft.hash));
10800 GAIA_ASSERT(it2 != m_archetypesById.end());
10801 const auto* pArchetype2 = it2->second;
10802 GAIA_ASSERT(pArchetype2 == pArchetype);
10803 }
10804#endif
10805 }
10806
10808 void validate_entities() const {
10809#if GAIA_ECS_VALIDATE_ENTITY_LIST
10810 m_recs.entities.validate();
10811#endif
10812 }
10813
10815 void validate_chunk([[maybe_unused]] Chunk* pChunk) const {
10816#if GAIA_ECS_VALIDATE_CHUNKS && GAIA_ASSERT_ENABLED
10817 GAIA_ASSERT(pChunk != nullptr);
10818
10819 if (!pChunk->empty()) {
10820 // Make sure a proper amount of entities reference the chunk
10821 uint32_t cnt = 0;
10822 for (const auto& ec: m_recs.entities) {
10823 if (ec.pChunk != pChunk)
10824 continue;
10825 ++cnt;
10826 }
10827 for (const auto& pair: m_recs.pairs) {
10828 if (pair.second.pChunk != pChunk)
10829 continue;
10830 ++cnt;
10831 }
10832 GAIA_ASSERT(cnt == pChunk->size());
10833 } else {
10834 // Make sure no entities reference the chunk
10835 for (const auto& ec: m_recs.entities) {
10836 GAIA_ASSERT(ec.pChunk != pChunk);
10837 }
10838 for (const auto& pair: m_recs.pairs) {
10839 GAIA_ASSERT(pair.second.pChunk != pChunk);
10840 }
10841 }
10842#endif
10843 }
10844
10851 template <bool CheckIn>
10852 GAIA_NODISCARD bool is_inter(Entity entity, Entity entityBase) const {
10853 GAIA_ASSERT(valid_entity(entity));
10854 GAIA_ASSERT(valid_entity(entityBase));
10855
10856 // Pairs are not supported
10857 if (entity.pair() || entityBase.pair())
10858 return false;
10859
10860 if constexpr (!CheckIn) {
10861 if (entity == entityBase)
10862 return true;
10863 }
10864
10865 const auto& targets = as_targets_trav_cache(entity);
10866 for (auto target: targets) {
10867 if (target == entityBase)
10868 return true;
10869 }
10870
10871 return false;
10872 }
10873
10875 template <bool CheckIn, typename Func>
10876 void as_up_trav(Entity entity, Func func) {
10877 GAIA_ASSERT(valid_entity(entity));
10878
10879 // Pairs are not supported
10880 if (entity.pair())
10881 return;
10882
10883 if constexpr (!CheckIn) {
10884 func(entity);
10885 }
10886
10887 const auto& ec = m_recs.entities[entity.id()];
10888 const auto* pArchetype = ec.pArchetype;
10889
10890 // Early exit if there are no Is relationship pairs on the archetype
10891 if (pArchetype->pairs_is() == 0)
10892 return;
10893
10894 for (uint32_t i = 0; i < pArchetype->pairs_is(); ++i) {
10895 auto e = pArchetype->entity_from_pairs_as_idx(i);
10896 const auto& ecTarget = m_recs.entities[e.gen()];
10897 auto target = *ecTarget.pEntity;
10898 func(target);
10899
10900 as_up_trav<CheckIn>(target, func);
10901 }
10902 }
10903
10904 template <typename T>
10905 const ComponentCacheItem& reg_core_entity(Entity id, Archetype* pArchetype) {
10906 auto comp = add(*pArchetype, id.entity(), id.pair(), id.kind());
10907 const auto& ci = comp_cache_mut().add<T>(id);
10908 GAIA_ASSERT(ci.entity == id);
10909 GAIA_ASSERT(comp == id);
10910 (void)comp;
10911 return ci;
10912 }
10913
10914 template <typename T>
10915 const ComponentCacheItem& reg_core_entity(Entity id) {
10916 return reg_core_entity<T>(id, m_pRootArchetype);
10917 }
10918
10919 static ComponentDesc
10920 primitive_type_desc(const char* name, uint32_t nameLen, uint32_t size, RuntimePrimitiveKind primitiveKind) {
10921 ComponentDesc desc{};
10922 desc.name = util::str_view(name, nameLen);
10923 desc.size = size;
10924 desc.alig = size;
10925 desc.storageType = DataStorageType::Table;
10926 desc.typeKind = RuntimeTypeKind::Primitive;
10927 desc.primitiveKind = primitiveKind;
10928 return desc;
10929 }
10930
10931 const ComponentCacheItem& reg_core_primitive_type(
10932 Entity id, const char* name, uint32_t nameLen, uint32_t size, RuntimePrimitiveKind primitiveKind) {
10933 auto comp = add(*m_pCompArchetype, id.entity(), id.pair(), id.kind());
10934 const auto desc = primitive_type_desc(name, nameLen, size, primitiveKind);
10935 const auto& ci = comp_cache_mut().add(id, desc);
10936 GAIA_ASSERT(ci.entity == id);
10937 GAIA_ASSERT(comp == id);
10938 (void)comp;
10939 finalize_component_registration(ci, false);
10940 return ci;
10941 }
10942
10943#if GAIA_ECS_AUTO_COMPONENT_FIELDS
10944 template <typename T>
10945 static Entity auto_component_field_type() noexcept {
10946 using U = core::raw_t<T>;
10947 if constexpr (std::is_same_v<U, bool>)
10948 return Bool;
10949 else if constexpr (std::is_same_v<U, char>)
10950 return Char8;
10951 else if constexpr (std::is_arithmetic_v<U>)
10952 return runtime_primitive_type_entity(ser::type_id<U>());
10953 else
10954 return EntityBad;
10955 }
10956
10957 template <typename T>
10958 static void auto_populate_component_fields(ComponentCacheItem& item) {
10959 if (!item.fields_empty())
10960 return;
10961
10962 using U = core::raw_t<T>;
10963 if constexpr (std::is_empty_v<U>)
10964 return;
10965 if constexpr (mem::is_soa_layout_v<U>)
10966 return;
10967
10968 if constexpr (!std::is_class_v<U>) {
10969 const auto fieldType = auto_component_field_type<U>();
10970 if (fieldType != EntityBad)
10971 (void)item.add_field({util::str_view("value", 5), fieldType, 0, 0});
10972 return;
10973 } else if constexpr (!std::is_aggregate_v<U>) {
10974 // Non-aggregate classes are not safe for offset extraction via meta::each_member.
10975 // Keep these components on serializer-based fallback rendering.
10976 return;
10977 } else {
10978 U tmp{};
10979 const auto* pBase = reinterpret_cast<const uint8_t*>(&tmp);
10980 uint32_t fieldIdx = 0;
10981 meta::each_member(tmp, [&](auto&... fields) {
10982 auto add_field = [&](auto& field) {
10983 using F = core::raw_t<decltype(field)>;
10984 char fieldName[24]{};
10985 (void)GAIA_STRFMT(fieldName, sizeof(fieldName), "f%u", fieldIdx++);
10986 const auto* pField = reinterpret_cast<const uint8_t*>(&field);
10987 const auto offset = (uint32_t)(pField - pBase);
10988 const auto fieldType = auto_component_field_type<F>();
10989 if (fieldType != EntityBad)
10990 (void)item.add_field({util::str_view(fieldName), fieldType, offset, 0});
10991 };
10992 (add(fields), ...);
10993 });
10994 }
10995 }
10996#endif
10997
10998 void init();
10999
11000 void done() {
11001 cleanup_inter();
11002
11003#if GAIA_ECS_CHUNK_ALLOCATOR
11004 ChunkAllocator::get().flush();
11005#endif
11006 }
11007
11011 void assign_entity(Entity entity, Archetype& archetype) {
11012 GAIA_ASSERT(!entity.pair());
11013
11014 auto* pChunk = archetype.foc_free_chunk();
11015 store_entity(m_recs.entities[entity.id()], entity, &archetype, pChunk);
11016 pChunk->update_versions();
11017 archetype.try_update_free_chunk_idx();
11018
11019 // Call constructors for the generic components on the newly added entity if necessary
11020 pChunk->call_gen_ctors(pChunk->size() - 1, 1);
11021
11022#if GAIA_ASSERT_ENABLED
11023 const auto& ec = m_recs.entities[entity.id()];
11024 GAIA_ASSERT(ec.pChunk == pChunk);
11025 auto entityExpected = pChunk->entity_view()[ec.row];
11026 GAIA_ASSERT(entityExpected == entity);
11027#endif
11028 }
11029
11033 void assign_pair(Entity entity, Archetype& archetype) {
11034 GAIA_ASSERT(entity.pair());
11035
11036 // Pairs are always added to m_pEntityArchetype initially and this can't change.
11037 GAIA_ASSERT(&archetype == m_pEntityArchetype);
11038
11039 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
11040 if (it != m_recs.pairs.end())
11041 return;
11042
11043 // Update the container record
11044 EntityContainer ec{};
11045 ec.idx = entity.id();
11046 ec.data.gen = entity.gen();
11047 ec.data.pair = 1;
11048 ec.data.ent = 1;
11049 ec.data.kind = EntityKind::EK_Gen;
11050
11051 auto* pChunk = archetype.foc_free_chunk();
11052 store_entity(ec, entity, &archetype, pChunk);
11053 pChunk->update_versions();
11054 archetype.try_update_free_chunk_idx();
11055
11056 m_recs.pairs.emplace(EntityLookupKey(entity), GAIA_MOV(ec));
11057
11058 // Update pair mappings
11059 const auto rel = get(entity.id());
11060 const auto tgt = get(entity.gen());
11061
11062 auto addPair = [](PairMap& map, Entity source, Entity add) {
11063 auto& ents = map[EntityLookupKey(source)];
11064 ents.insert(EntityLookupKey(add));
11065 };
11066
11067 addPair(m_relToTgt, rel, tgt);
11068 addPair(m_relToTgt, All, tgt);
11069 addPair(m_tgtToRel, tgt, rel);
11070 addPair(m_tgtToRel, All, rel);
11071
11072#if GAIA_OBSERVERS_ENABLED
11073 m_observers.try_mark_term_observed(*this, entity);
11074#endif
11075 }
11076
11083 GAIA_NODISCARD Entity add(Archetype& archetype, bool isEntity, bool isPair, EntityKind kind) {
11084 EntityContainerCtx ctx{isEntity, isPair, kind};
11085 const auto entity = m_recs.entities.alloc(&ctx);
11086 assign_entity(entity, archetype);
11087 return entity;
11088 }
11089
11095 template <typename Func>
11096 void add_entity_n(Archetype& archetype, uint32_t count, Func func) {
11097 EntityContainerCtx ctx{true, false, EntityKind::EK_Gen};
11098#if GAIA_OBSERVERS_ENABLED
11099 const auto addedIds = EntitySpan{archetype.ids_view()};
11100 ObserverRegistry::DiffDispatchCtx addDiffCtx{};
11101 if (!addedIds.empty())
11102 addDiffCtx = m_observers.prepare_diff_add_new(*this, addedIds);
11103#endif
11104
11105 uint32_t left = count;
11106 do {
11107 auto* pChunk = archetype.foc_free_chunk();
11108 const uint32_t originalChunkSize = pChunk->size();
11109 const uint32_t freeSlotsInChunk = pChunk->capacity() - originalChunkSize;
11110 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
11111
11112 GAIA_FOR(toCreate) {
11113 const auto entityNew = m_recs.entities.alloc(&ctx);
11114 auto& ecNew = m_recs.entities[entityNew.id()];
11115 store_entity(ecNew, entityNew, &archetype, pChunk);
11116
11117#if GAIA_ASSERT_ENABLED
11118 GAIA_ASSERT(ecNew.pChunk == pChunk);
11119 auto entityExpected = pChunk->entity_view()[ecNew.row];
11120 GAIA_ASSERT(entityExpected == entityNew);
11121#endif
11122 }
11123
11124 // New entities were added, try updating the free chunk index
11125 archetype.try_update_free_chunk_idx();
11126
11127 // Call constructors for the generic components on the newly added entity if necessary
11128 pChunk->call_gen_ctors(originalChunkSize, toCreate);
11129
11130 // Call functors
11131 {
11132 auto entities = pChunk->entity_view();
11133 GAIA_FOR2(originalChunkSize, pChunk->size()) func(entities[i]);
11134 }
11135
11136 pChunk->update_versions();
11137
11138#if GAIA_OBSERVERS_ENABLED
11139 if (!addedIds.empty()) {
11140 auto entities = pChunk->entity_view();
11141 const auto targets = EntitySpan{entities.data() + originalChunkSize, toCreate};
11142 m_observers.add_diff_targets(*this, addDiffCtx, targets);
11143 m_observers.on_add(*this, archetype, addedIds, targets);
11144 }
11145#endif
11146
11147 left -= toCreate;
11148 } while (left > 0);
11149
11150#if GAIA_OBSERVERS_ENABLED
11151 if (!addedIds.empty())
11152 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
11153#endif
11154 }
11155
11157 void gc() {
11158 GAIA_PROF_SCOPE(World::gc);
11159
11160 del_empty_chunks();
11161 defrag_chunks(m_defragEntitiesPerTick);
11162 del_empty_archetypes();
11163 }
11164
11165 public:
11170 QuerySerBuffer& query_buffer(QueryId& serId) {
11171 // No serialization id set on the query, try creating a new record
11172 if GAIA_UNLIKELY (serId == QueryIdBad) {
11173#if GAIA_ASSERT_ENABLED
11174 uint32_t safetyCounter = 0;
11175#endif
11176
11177 while (true) {
11178#if GAIA_ASSERT_ENABLED
11179 // Make sure we don't cross some safety threshold
11180 ++safetyCounter;
11181 GAIA_ASSERT(safetyCounter < 100000);
11182#endif
11183
11184 serId = ++m_nextQuerySerId;
11185 // Make sure we do not overflow
11186 GAIA_ASSERT(serId != 0);
11187
11188 // If the id is already found, try again.
11189 // Note, this is essentially never going to repeat. We would have to prepare millions if
11190 // not billions of queries for which we only added inputs but never queried them.
11191 auto ret = m_querySerMap.try_emplace(serId);
11192 if (!ret.second)
11193 continue;
11194
11195 return ret.first->second;
11196 };
11197 }
11198
11199 return m_querySerMap[serId];
11200 }
11201
11204 void query_buffer_reset(QueryId& serId) {
11205 auto it = m_querySerMap.find(serId);
11206 if (it == m_querySerMap.end())
11207 return;
11208
11209 m_querySerMap.erase(it);
11210 serId = QueryIdBad;
11211 }
11212
11216 m_queryCache.invalidate_queries_for_entity(entityKey, QueryCache::ChangeKind::Structural);
11217 }
11218
11222 m_queryCache.invalidate_queries_for_rel(relation, QueryCache::ChangeKind::DynamicResult);
11223 }
11224
11228 m_queryCache.invalidate_sorted_queries_for_entity(entity);
11229 }
11230
11233 m_queryCache.invalidate_sorted_queries();
11234 }
11235
11239 GAIA_ASSERT(is_pair.first() == Is);
11240
11241 // We still need to handle invalidation "down-the-tree".
11242 // E.g. following setup:
11243 // q = w.query().all({Is,animal});
11244 // w.as(wolf, carnivore);
11245 // w.as(carnivore, animal);
11246 // q.each() ...; // animal, carnivore, wolf
11247 // w.del(wolf, {Is,carnivore}) // wolf is no longer a carnivore and thus no longer an animal
11248 // After this deletion, we need to invalidate "q" because wolf is no longer an animal
11249 // and we don't want q to include it.
11250 // q.each() ...; // animal
11251
11252 auto e = is_pair.second();
11253 as_up_trav<false>(e, [&](Entity target) {
11254 // Invalidate all queries that contain everything in our path.
11255 invalidate_queries_for_structural_entity(EntityLookupKey(Pair{Is, target}));
11256 });
11257 }
11258
11264 auto expr = util::trim(exprRaw);
11265
11266 if (expr[0] == '(') {
11267 if (expr.back() != ')') {
11268 GAIA_ASSERT2(false, "Expression '(' not terminated");
11269 return EntityBad;
11270 }
11271
11272 const auto idStr = expr.subspan(1, expr.size() - 2);
11273 const auto commaIdx = core::get_index(idStr, ',');
11274
11275 const auto first = name_to_entity(idStr.subspan(0, commaIdx));
11276 if (first == EntityBad)
11277 return EntityBad;
11278 const auto second = name_to_entity(idStr.subspan(commaIdx + 1));
11279 if (second == EntityBad)
11280 return EntityBad;
11281
11282 return ecs::Pair(first, second);
11283 }
11284
11285 {
11286 auto idStr = util::trim(expr);
11287
11288 // Wildcard character
11289 if (idStr.size() == 1 && idStr[0] == '*')
11290 return All;
11291
11292 return get_inter(idStr.data(), (uint32_t)idStr.size());
11293 }
11294 }
11295
11301 Entity expr_to_entity(va_list& args, std::span<const char> exprRaw) const {
11302 auto expr = util::trim(exprRaw);
11303
11304 if (expr[0] == '%') {
11305 if (expr[1] != 'e') {
11306 GAIA_ASSERT2(false, "Expression '%' not terminated");
11307 return EntityBad;
11308 }
11309
11310 auto id = (Identifier)va_arg(args, unsigned long long);
11311 return Entity(id);
11312 }
11313
11314 if (expr[0] == '(') {
11315 if (expr.back() != ')') {
11316 GAIA_ASSERT2(false, "Expression '(' not terminated");
11317 return EntityBad;
11318 }
11319
11320 const auto idStr = expr.subspan(1, expr.size() - 2);
11321 const auto commaIdx = core::get_index(idStr, ',');
11322
11323 const auto first = expr_to_entity(args, idStr.subspan(0, commaIdx));
11324 if (first == EntityBad)
11325 return EntityBad;
11326 const auto second = expr_to_entity(args, idStr.subspan(commaIdx + 1));
11327 if (second == EntityBad)
11328 return EntityBad;
11329
11330 return ecs::Pair(first, second);
11331 }
11332
11333 {
11334 auto idStr = util::trim(expr);
11335
11336 // Wildcard character
11337 if (idStr.size() == 1 && idStr[0] == '*')
11338 return All;
11339
11340 // Anything else is a component name
11341 const auto* pItem = resolve_component_name_inter(idStr.data(), (uint32_t)idStr.size());
11342 if (pItem == nullptr) {
11343 GAIA_ASSERT2(false, "Component not found");
11344 GAIA_LOG_W("Component '%.*s' not found", (uint32_t)idStr.size(), idStr.data());
11345 return EntityBad;
11346 }
11347
11348 return pItem->entity;
11349 }
11350 }
11351 };
11352
11353 inline ComponentCursor World::cursor(Entity entity, Entity component) const {
11354 return ComponentCursor::from_raw(comp_cache(), component, get_raw(entity, component));
11355 }
11356
11358 return ComponentCursor::from_raw(*this, comp_cache(), entity, component, mut_raw(entity, component));
11359 }
11360
11362 } // namespace ecs
11363} // namespace gaia
11364
11365#include "api.inl"
11366#if GAIA_OBSERVERS_ENABLED
11367 #include "observer_registry.inl"
11368#endif
11369
11370#include "observer.inl"
11371#include "system.inl"
11372
11373namespace gaia {
11374 namespace ecs {
11375 inline void World::init() {
11376 // Use the default serializer
11377 set_serializer(nullptr);
11378
11379 // Register the root archetype
11380 {
11381 m_pRootArchetype = create_archetype({});
11382 m_pRootArchetype->set_hashes({calc_lookup_hash({})});
11383 reg_archetype(m_pRootArchetype);
11384 }
11385
11386 (void)reg_core_entity<Core_>(Core);
11387
11388 // Entity archetype matches the root archetype for now
11389 m_pEntityArchetype = m_pRootArchetype;
11390
11391 // Register the component archetype (entity + EntityDesc + Component)
11392 {
11393 Archetype* pCompArchetype{};
11394 {
11395 const auto id = GAIA_ID(EntityDesc);
11396 const auto& ci = reg_core_entity<EntityDesc>(id);
11397 EntityBuilder(*this, id).add_inter_init(ci.entity);
11398 sset<EntityDesc>(id) = {ci.name.str(), ci.name.len(), nullptr, 0};
11399 pCompArchetype = m_recs.entities[id.id()].pArchetype;
11400 }
11401 {
11402 const auto id = GAIA_ID(Component);
11403 const auto& ci = reg_core_entity<Component>(id, pCompArchetype);
11404 EntityBuilder(*this, id).add_inter_init(ci.entity);
11405 acc_mut(id)
11406 // Entity descriptor
11407 .sset<EntityDesc>({ci.name.str(), ci.name.len(), nullptr, 0})
11408 // Component
11409 .sset<Component>(ci.comp);
11410 m_pCompArchetype = m_recs.entities[id.id()].pArchetype;
11411 }
11412 }
11413
11414 // Core components.
11415 // Their order must correspond to the value sequence in id.h.
11416 {
11417 (void)reg_core_entity<OnDelete_>(OnDelete);
11418 (void)reg_core_entity<OnDeleteTarget_>(OnDeleteTarget);
11419 (void)reg_core_entity<Remove_>(Remove);
11420 (void)reg_core_entity<Delete_>(Delete);
11421 (void)reg_core_entity<Error_>(Error);
11422 (void)reg_core_entity<Requires_>(Requires);
11423 (void)reg_core_entity<CantCombine_>(CantCombine);
11424 (void)reg_core_entity<Exclusive_>(Exclusive);
11425 (void)reg_core_entity<DontFragment_>(DontFragment);
11426 (void)reg_core_entity<Sparse_>(Sparse);
11427 (void)reg_core_entity<Acyclic_>(Acyclic);
11428 (void)reg_core_entity<Traversable_>(Traversable);
11429 (void)reg_core_entity<All_>(All);
11430 (void)reg_core_entity<ChildOf_>(ChildOf);
11431 (void)reg_core_entity<Parent_>(Parent);
11432 (void)reg_core_entity<Is_>(Is);
11433 (void)reg_core_entity<Prefab_>(Prefab);
11434 (void)reg_core_entity<OnInstantiate_>(OnInstantiate);
11435 (void)reg_core_entity<Override_>(Override);
11436 (void)reg_core_entity<Inherit_>(Inherit);
11437 (void)reg_core_entity<DontInherit_>(DontInherit);
11438 (void)reg_core_entity<System_>(System);
11439 (void)reg_core_entity<DependsOn_>(DependsOn);
11440 (void)reg_core_entity<Observer_>(Observer);
11441
11442 (void)reg_core_entity<_Var0>(Var0);
11443 (void)reg_core_entity<_Var1>(Var1);
11444 (void)reg_core_entity<_Var2>(Var2);
11445 (void)reg_core_entity<_Var3>(Var3);
11446 (void)reg_core_entity<_Var4>(Var4);
11447 (void)reg_core_entity<_Var5>(Var5);
11448 (void)reg_core_entity<_Var6>(Var6);
11449 (void)reg_core_entity<_Var7>(Var7);
11450
11451 (void)reg_core_primitive_type(S8, "gaia::ecs::S8", 13, 1, RuntimePrimitiveKind::S8);
11452 (void)reg_core_primitive_type(U8, "gaia::ecs::U8", 13, 1, RuntimePrimitiveKind::U8);
11453 (void)reg_core_primitive_type(S16, "gaia::ecs::S16", 14, 2, RuntimePrimitiveKind::S16);
11454 (void)reg_core_primitive_type(U16, "gaia::ecs::U16", 14, 2, RuntimePrimitiveKind::U16);
11455 (void)reg_core_primitive_type(S32, "gaia::ecs::S32", 14, 4, RuntimePrimitiveKind::S32);
11456 (void)reg_core_primitive_type(U32, "gaia::ecs::U32", 14, 4, RuntimePrimitiveKind::U32);
11457 (void)reg_core_primitive_type(S64, "gaia::ecs::S64", 14, 8, RuntimePrimitiveKind::S64);
11458 (void)reg_core_primitive_type(U64, "gaia::ecs::U64", 14, 8, RuntimePrimitiveKind::U64);
11459 (void)reg_core_primitive_type(Bool, "gaia::ecs::Bool", 15, 1, RuntimePrimitiveKind::Bool);
11460 (void)reg_core_primitive_type(Char8, "gaia::ecs::Char8", 16, 1, RuntimePrimitiveKind::Char8);
11461 (void)reg_core_primitive_type(Char16, "gaia::ecs::Char16", 17, 2, RuntimePrimitiveKind::Char16);
11462 (void)reg_core_primitive_type(Char32, "gaia::ecs::Char32", 17, 4, RuntimePrimitiveKind::Char32);
11463 (void)reg_core_primitive_type(WChar, "gaia::ecs::WChar", 16, 8, RuntimePrimitiveKind::WChar);
11464 (void)reg_core_primitive_type(F8, "gaia::ecs::F8", 13, 1, RuntimePrimitiveKind::F8);
11465 (void)reg_core_primitive_type(F16, "gaia::ecs::F16", 14, 2, RuntimePrimitiveKind::F16);
11466 (void)reg_core_primitive_type(F32, "gaia::ecs::F32", 14, 4, RuntimePrimitiveKind::F32);
11467 (void)reg_core_primitive_type(F64, "gaia::ecs::F64", 14, 8, RuntimePrimitiveKind::F64);
11468 }
11469
11470 // Add special properties for core components.
11471 // Their order must correspond to the value sequence in id.h.
11472 {
11473 EntityBuilder(*this, Core) //
11474 .add(Core)
11475 .add(Pair(OnDelete, Error));
11476 EntityBuilder(*this, GAIA_ID(EntityDesc)) //
11477 .add(Core)
11478 .add(Pair(OnDelete, Error));
11479 EntityBuilder(*this, GAIA_ID(Component)) //
11480 .add(Core)
11481 .add(Pair(OnDelete, Error));
11482 EntityBuilder(*this, OnDelete) //
11483 .add(Core)
11484 .add(Exclusive)
11485 .add(Pair(OnDelete, Error));
11486 EntityBuilder(*this, OnDeleteTarget) //
11487 .add(Core)
11488 .add(Exclusive)
11489 .add(Pair(OnDelete, Error));
11490 EntityBuilder(*this, Remove) //
11491 .add(Core)
11492 .add(Pair(OnDelete, Error));
11493 EntityBuilder(*this, Delete) //
11494 .add(Core)
11495 .add(Pair(OnDelete, Error));
11496 EntityBuilder(*this, Error) //
11497 .add(Core)
11498 .add(Pair(OnDelete, Error));
11499 EntityBuilder(*this, All) //
11500 .add(Core)
11501 .add(Pair(OnDelete, Error));
11502 EntityBuilder(*this, Requires) //
11503 .add(Core)
11504 .add(Acyclic)
11505 .add(Pair(OnDelete, Error));
11506 EntityBuilder(*this, CantCombine) //
11507 .add(Core)
11508 .add(Acyclic)
11509 .add(Pair(OnDelete, Error));
11510 EntityBuilder(*this, Exclusive) //
11511 .add(Core)
11512 .add(Pair(OnDelete, Error))
11513 .add(Acyclic);
11514 EntityBuilder(*this, DontFragment) //
11515 .add(Core)
11516 .add(Pair(OnDelete, Error))
11517 .add(Acyclic);
11518 EntityBuilder(*this, Sparse) //
11519 .add(Core)
11520 .add(Pair(OnDelete, Error))
11521 .add(Acyclic);
11522 EntityBuilder(*this, Acyclic) //
11523 .add(Core)
11524 .add(Pair(OnDelete, Error));
11525 EntityBuilder(*this, Traversable) //
11526 .add(Core)
11527 .add(Pair(OnDelete, Error));
11528
11529 EntityBuilder(*this, ChildOf) //
11530 .add(Core)
11531 .add(Acyclic)
11532 .add(Exclusive)
11533 .add(Traversable)
11534 .add(Pair(OnDelete, Error))
11535 .add(Pair(OnDeleteTarget, Delete));
11536 EntityBuilder(*this, Parent) //
11537 .add(Core)
11538 .add(Acyclic)
11539 .add(Exclusive)
11540 .add(DontFragment)
11541 .add(Traversable)
11542 .add(Pair(OnDelete, Error))
11543 .add(Pair(OnDeleteTarget, Delete));
11544 EntityBuilder(*this, Is) //
11545 .add(Core)
11546 .add(Acyclic)
11547 .add(Pair(OnDelete, Error));
11548 EntityBuilder(*this, Prefab) //
11549 .add(Core)
11550 .add(Pair(OnDelete, Error));
11551 EntityBuilder(*this, OnInstantiate) //
11552 .add(Core)
11553 .add(Acyclic)
11554 .add(Exclusive)
11555 .add(DontFragment)
11556 .add(Pair(OnDelete, Error));
11557 EntityBuilder(*this, Override) //
11558 .add(Core)
11559 .add(Pair(OnDelete, Error));
11560 EntityBuilder(*this, Inherit) //
11561 .add(Core)
11562 .add(Pair(OnDelete, Error));
11563 EntityBuilder(*this, DontInherit) //
11564 .add(Core)
11565 .add(Pair(OnDelete, Error));
11566
11567 EntityBuilder(*this, System) //
11568 .add(Core)
11569 .add(Acyclic)
11570 .add(Pair(OnDelete, Error));
11571 EntityBuilder(*this, DependsOn) //
11572 .add(Core)
11573 .add(Acyclic)
11574 .add(Pair(OnDelete, Error));
11575 EntityBuilder(*this, Observer) //
11576 .add(Core)
11577 .add(Acyclic)
11578 .add(Pair(OnDelete, Error));
11579
11580 EntityBuilder(*this, Var0) //
11581 .add(Core)
11582 .add(Pair(OnDelete, Error));
11583 EntityBuilder(*this, Var1) //
11584 .add(Core)
11585 .add(Pair(OnDelete, Error));
11586 EntityBuilder(*this, Var2) //
11587 .add(Core)
11588 .add(Pair(OnDelete, Error));
11589 EntityBuilder(*this, Var3) //
11590 .add(Core)
11591 .add(Pair(OnDelete, Error));
11592 EntityBuilder(*this, Var4) //
11593 .add(Core)
11594 .add(Pair(OnDelete, Error));
11595 EntityBuilder(*this, Var5) //
11596 .add(Core)
11597 .add(Pair(OnDelete, Error));
11598 EntityBuilder(*this, Var6) //
11599 .add(Core)
11600 .add(Pair(OnDelete, Error));
11601 EntityBuilder(*this, Var7) //
11602 .add(Core)
11603 .add(Pair(OnDelete, Error));
11604
11605 EntityBuilder(*this, S8) //
11606 .add(Core)
11607 .add(Pair(OnDelete, Error));
11608 EntityBuilder(*this, U8) //
11609 .add(Core)
11610 .add(Pair(OnDelete, Error));
11611 EntityBuilder(*this, S16) //
11612 .add(Core)
11613 .add(Pair(OnDelete, Error));
11614 EntityBuilder(*this, U16) //
11615 .add(Core)
11616 .add(Pair(OnDelete, Error));
11617 EntityBuilder(*this, S32) //
11618 .add(Core)
11619 .add(Pair(OnDelete, Error));
11620 EntityBuilder(*this, U32) //
11621 .add(Core)
11622 .add(Pair(OnDelete, Error));
11623 EntityBuilder(*this, S64) //
11624 .add(Core)
11625 .add(Pair(OnDelete, Error));
11626 EntityBuilder(*this, U64) //
11627 .add(Core)
11628 .add(Pair(OnDelete, Error));
11629 EntityBuilder(*this, Bool) //
11630 .add(Core)
11631 .add(Pair(OnDelete, Error));
11632 EntityBuilder(*this, Char8) //
11633 .add(Core)
11634 .add(Pair(OnDelete, Error));
11635 EntityBuilder(*this, Char16) //
11636 .add(Core)
11637 .add(Pair(OnDelete, Error));
11638 EntityBuilder(*this, Char32) //
11639 .add(Core)
11640 .add(Pair(OnDelete, Error));
11641 EntityBuilder(*this, WChar) //
11642 .add(Core)
11643 .add(Pair(OnDelete, Error));
11644 EntityBuilder(*this, F8) //
11645 .add(Core)
11646 .add(Pair(OnDelete, Error));
11647 EntityBuilder(*this, F16) //
11648 .add(Core)
11649 .add(Pair(OnDelete, Error));
11650 EntityBuilder(*this, F32) //
11651 .add(Core)
11652 .add(Pair(OnDelete, Error));
11653 EntityBuilder(*this, F64) //
11654 .add(Core)
11655 .add(Pair(OnDelete, Error));
11656 }
11657
11658 // Remove all archetypes with no chunks. We don't want any leftovers after
11659 // archetype movements.
11660 {
11661 for (uint32_t i = 1; i < m_archetypes.size(); ++i) {
11662 auto* pArchetype = m_archetypes[i];
11663 if (!pArchetype->chunks().empty())
11664 continue;
11665
11666 // Request deletion the standard way.
11667 // We could simply add archetypes into m_archetypesToDel but this way
11668 // we can actually replicate what the system really does on the inside
11669 // and it will require more work at the cost of easier maintenance.
11670 // The amount of archetypes cleanup is very small after init and the code
11671 // only runs after the world is created so this is not a big deal.
11672 req_del(*pArchetype);
11673 }
11674
11675 // Cleanup
11676 {
11677 del_finalize();
11678 while (!m_chunksToDel.empty() || !m_archetypesToDel.empty())
11679 gc();
11680
11681 // Make sure everything has been cleared
11682 GAIA_ASSERT(m_reqArchetypesToDel.empty());
11683 GAIA_ASSERT(m_chunksToDel.empty());
11684 GAIA_ASSERT(m_archetypesToDel.empty());
11685 }
11686
11687 sort_archetypes();
11688
11689 // Make sure archetypes have valid graphs after the cleanup
11690 for (const auto* pArchetype: m_archetypes)
11691 validate_archetype_edges(pArchetype);
11692 }
11693
11694 // Make sure archetype pointers are up-to-date
11695 m_pCompArchetype = m_recs.entities[GAIA_ID(Component).id()].pArchetype;
11696
11697#if GAIA_SYSTEMS_ENABLED
11698 // Initialize the systems query
11699 systems_init();
11700#endif
11701 }
11702
11703 inline GroupId
11704 group_by_func_default([[maybe_unused]] const World& world, const Archetype& archetype, Entity groupBy) {
11705 if (archetype.pairs() > 0) {
11706 auto ids = archetype.ids_view();
11707 for (auto id: ids) {
11708 if (!id.pair() || id.id() != groupBy.id())
11709 continue;
11710
11711 // Consider the pair's target the groupId
11712 return id.gen();
11713 }
11714 }
11715
11716 // No group
11717 return 0;
11718 }
11719
11720 inline GroupId group_by_func_depth_order(const World& world, const Archetype& archetype, Entity relation) {
11721 GAIA_ASSERT(!relation.pair());
11722
11723 // Depth ordering only makes sense for fragmenting relations whose target participates in archetype identity.
11724 // Non-fragmenting relations such as Parent must stay on per-entity traversal, because their targets vary per
11725 // entity and cannot be represented by one cached archetype depth. The level is derived from the cached upward
11726 // traversal chain so normal query iteration can stay cheap.
11727 if (!world.supports_depth_order(relation) || archetype.pairs() == 0)
11728 return 0;
11729
11730 auto ids = archetype.ids_view();
11731 GroupId maxDepth = 0;
11732 bool found = false;
11733
11734 for (auto idsIdx: archetype.pair_rel_indices(relation)) {
11735 const auto pair = ids[idsIdx];
11736 const auto target = world.pair_target_if_alive(pair);
11737 if (target == EntityBad)
11738 continue;
11739
11740 const GroupId depth = GroupId(world.depth_order_cache(relation, target));
11741
11742 if (!found || depth > maxDepth) {
11743 maxDepth = depth;
11744 found = true;
11745 }
11746 }
11747
11748 return found ? maxDepth : 0;
11749 }
11750 } // namespace ecs
11751} // namespace gaia
11752
11753#include "gaia/ecs/impl/world_json.h"
11754
11755#if GAIA_SYSTEMS_ENABLED
11756namespace gaia {
11757 namespace ecs {
11758 namespace detail {
11760 struct PendingSystemJob {
11762 Entity entity = EntityBad;
11764 SchedJob job;
11765
11767 PendingSystemJob() = default;
11771 PendingSystemJob(Entity systemEntity, SchedJob&& systemJob): entity(systemEntity), job(GAIA_MOV(systemJob)) {}
11772 };
11773
11775 struct SystemCollectCtx {
11777 World* pWorld = nullptr;
11779 cnt::darray<SystemScheduleItem>* pItems = nullptr;
11780 };
11781
11783 struct SystemRunCtx {
11785 World* pWorld = nullptr;
11787 cnt::darray<PendingSystemJob>* pPending = nullptr;
11789 SystemScheduleItem current{};
11791 bool hasCurrent = false;
11793 bool canScheduleSystems = false;
11794 };
11795
11800 GAIA_NODISCARD inline bool entity_schedule_less(Entity lhs, Entity rhs) {
11801 if (lhs.id() != rhs.id())
11802 return lhs.id() < rhs.id();
11803 return lhs.gen() < rhs.gen();
11804 }
11805
11810 GAIA_NODISCARD inline bool system_schedule_stack_contains(const cnt::darray<Entity>& stack, Entity entity) {
11811 for (auto item: stack) {
11812 if (item == entity)
11813 return true;
11814 }
11815 return false;
11816 }
11817
11824 GAIA_NODISCARD inline uint32_t
11825 system_schedule_dep_depth(World& world, Entity entity, Entity skipTarget, cnt::darray<Entity>& stack) {
11826 if (entity == EntityBad || system_schedule_stack_contains(stack, entity))
11827 return 0;
11828
11829 stack.push_back(entity);
11830 uint32_t depth = 0;
11831 world.targets(entity, DependsOn, [&](Entity target) {
11832 if (target == skipTarget)
11833 return;
11834 const auto targetDepth = system_schedule_dep_depth(world, target, EntityBad, stack) + 1;
11835 if (targetDepth > depth)
11836 depth = targetDepth;
11837 });
11838 stack.pop_back();
11839 return depth;
11840 }
11841
11846 GAIA_NODISCARD inline Entity system_phase(World& world, Entity systemEntity) {
11847 const auto phase = world.target(systemEntity, ChildOf);
11848 if (phase == EntityBad)
11849 return EntityBad;
11850 if (!world.has(systemEntity, Pair(DependsOn, phase)))
11851 return EntityBad;
11852 return phase;
11853 }
11854
11859 GAIA_NODISCARD inline SystemScheduleItem system_schedule_item(World& world, Entity systemEntity) {
11860 SystemScheduleItem item{};
11861 item.entity = systemEntity;
11862 item.phase = system_phase(world, systemEntity);
11863 item.hasPhase = item.phase != EntityBad;
11864 return item;
11865 }
11866
11869 inline void submit_pending_system_jobs(cnt::darray<PendingSystemJob>& pending) {
11870 for (auto& item: pending)
11871 item.job.submit();
11872 }
11873
11876 inline void finish_pending_system_jobs(cnt::darray<PendingSystemJob>& pending) {
11877 for (auto& item: pending)
11878 item.job.wait();
11879 for (auto& item: pending)
11880 item.job.del();
11881 pending.clear();
11882 }
11883
11886 inline void flush_pending_system_jobs(cnt::darray<PendingSystemJob>& pending) {
11887 submit_pending_system_jobs(pending);
11888 finish_pending_system_jobs(pending);
11889 }
11890
11894 GAIA_NODISCARD inline bool sched_supports_deferred_system_jobs(const Sched& sched) {
11895 const auto& resolved = sched_resolve(sched);
11896 return resolved.add != nullptr && resolved.add_par != nullptr && resolved.submit != nullptr &&
11897 resolved.dep != nullptr && resolved.wait != nullptr && resolved.del != nullptr;
11898 }
11899
11903 inline void
11904 system_schedule_entity_indices(const cnt::darray<SystemScheduleItem>& items, cnt::darray<uint32_t>& outIndices) {
11905 outIndices.clear();
11906 outIndices.reserve(items.size());
11907 for (uint32_t i = 0; i < items.size(); ++i)
11908 outIndices.push_back(i);
11909 core::sort(outIndices, [&](uint32_t lhs, uint32_t rhs) {
11910 return entity_schedule_less(items[lhs].entity, items[rhs].entity);
11911 });
11912 }
11913
11919 GAIA_NODISCARD inline uint32_t system_schedule_find_item_by_entity(
11920 const cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices, Entity entity) {
11921 uint32_t lo = 0;
11922 uint32_t hi = entityIndices.size();
11923 while (lo < hi) {
11924 const uint32_t mid = lo + (hi - lo) / 2;
11925 const auto midEntity = items[entityIndices[mid]].entity;
11926 if (midEntity == entity)
11927 return entityIndices[mid];
11928 if (entity_schedule_less(midEntity, entity))
11929 lo = mid + 1;
11930 else
11931 hi = mid;
11932 }
11933 return UINT32_MAX;
11934 }
11935
11940 GAIA_NODISCARD inline bool
11941 system_schedule_same_group(const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
11942 if (lhs.hasPhase != rhs.hasPhase)
11943 return false;
11944 if (!lhs.hasPhase)
11945 return true;
11946 return lhs.phase == rhs.phase;
11947 }
11948
11955 GAIA_NODISCARD inline uint32_t system_schedule_primary_target_idx(
11956 World& world, const cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices,
11957 uint32_t itemIdx) {
11958 uint32_t bestIdx = UINT32_MAX;
11959 uint32_t bestDepth = 0;
11960 const auto& item = items[itemIdx];
11961 world.targets(item.entity, DependsOn, [&](Entity target) {
11962 if (target == item.phase)
11963 return;
11964
11965 const auto targetIdx = system_schedule_find_item_by_entity(items, entityIndices, target);
11966 if (targetIdx == UINT32_MAX)
11967 return;
11968 if (!system_schedule_same_group(item, items[targetIdx]))
11969 return;
11970
11971 const auto targetDepth = items[targetIdx].systemDepth;
11972 if (bestIdx == UINT32_MAX || targetDepth > bestDepth ||
11973 (targetDepth == bestDepth && entity_schedule_less(items[targetIdx].entity, items[bestIdx].entity))) {
11974 bestIdx = targetIdx;
11975 bestDepth = targetDepth;
11976 }
11977 });
11978 return bestIdx;
11979 }
11980
11988 inline void system_schedule_visit_primary_children(
11989 cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& firstChildren,
11990 const cnt::darray<uint32_t>& nextSiblings, cnt::darray<uint8_t>& states, uint32_t itemIdx, uint32_t& order) {
11991 if (states[itemIdx] != 0)
11992 return;
11993
11994 states[itemIdx] = 1;
11995 for (uint32_t childIdx = firstChildren[itemIdx]; childIdx != UINT32_MAX; childIdx = nextSiblings[childIdx])
11996 system_schedule_visit_primary_children(items, firstChildren, nextSiblings, states, childIdx, order);
11997 items[itemIdx].systemOrder = order++;
11998 states[itemIdx] = 2;
11999 }
12000
12006 inline void system_schedule_assign_group_orders(
12007 World& world, cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& groupIndices,
12008 const cnt::darray<uint32_t>& entityIndices, SystemScheduleScratch& scratch) {
12009 auto& sortedGroupIndices = scratch.sortedGroupIndices;
12010 auto& primaryTargets = scratch.primaryTargets;
12011 auto& firstChildren = scratch.firstChildren;
12012 auto& nextSiblings = scratch.nextSiblings;
12013 auto& states = scratch.states;
12014
12015 sortedGroupIndices = groupIndices;
12016 core::sort(sortedGroupIndices, [&](uint32_t lhs, uint32_t rhs) {
12017 return entity_schedule_less(items[lhs].entity, items[rhs].entity);
12018 });
12019
12020 primaryTargets.clear();
12021 primaryTargets.resize(items.size(), UINT32_MAX);
12022 for (auto itemIdx: groupIndices)
12023 primaryTargets[itemIdx] = system_schedule_primary_target_idx(world, items, entityIndices, itemIdx);
12024
12025 firstChildren.clear();
12026 nextSiblings.clear();
12027 firstChildren.resize(items.size(), UINT32_MAX);
12028 nextSiblings.resize(items.size(), UINT32_MAX);
12029 for (uint32_t i = sortedGroupIndices.size(); i > 0; --i) {
12030 const auto itemIdx = sortedGroupIndices[i - 1];
12031 const auto targetIdx = primaryTargets[itemIdx];
12032 if (targetIdx == UINT32_MAX)
12033 continue;
12034 nextSiblings[itemIdx] = firstChildren[targetIdx];
12035 firstChildren[targetIdx] = itemIdx;
12036 }
12037
12038 states.clear();
12039 states.resize(items.size(), 0);
12040
12041 uint32_t order = 0;
12042 for (auto itemIdx: sortedGroupIndices) {
12043 if (states[itemIdx] == 0 && primaryTargets[itemIdx] == UINT32_MAX) {
12044 system_schedule_visit_primary_children(items, firstChildren, nextSiblings, states, itemIdx, order);
12045 }
12046 }
12047
12048 // Remaining items are in a DependsOn cycle. The cycle is invalid, but the update stays deterministic.
12049 for (auto itemIdx: sortedGroupIndices) {
12050 if (states[itemIdx] == 0) {
12051 items[itemIdx].systemOrder = order++;
12052 states[itemIdx] = 2;
12053 }
12054 }
12055 }
12056
12061 GAIA_NODISCARD inline uint32_t
12062 system_schedule_find_phase(const cnt::darray<SystemPhaseScheduleItem>& phases, Entity phase) {
12063 for (uint32_t i = 0; i < phases.size(); ++i) {
12064 if (phases[i].phase == phase)
12065 return i;
12066 }
12067 return UINT32_MAX;
12068 }
12069
12075 GAIA_NODISCARD inline uint32_t system_schedule_primary_phase_idx(
12076 World& world, const cnt::darray<SystemPhaseScheduleItem>& phases, uint32_t phaseIdx) {
12077 uint32_t bestIdx = UINT32_MAX;
12078 uint32_t bestDepth = 0;
12079 world.targets(phases[phaseIdx].phase, DependsOn, [&](Entity target) {
12080 const auto targetIdx = system_schedule_find_phase(phases, target);
12081 if (targetIdx == UINT32_MAX)
12082 return;
12083
12084 const auto targetDepth = phases[targetIdx].depth;
12085 if (bestIdx == UINT32_MAX || targetDepth > bestDepth ||
12086 (targetDepth == bestDepth && entity_schedule_less(phases[targetIdx].phase, phases[bestIdx].phase))) {
12087 bestIdx = targetIdx;
12088 bestDepth = targetDepth;
12089 }
12090 });
12091 return bestIdx;
12092 }
12093
12101 inline void system_schedule_visit_phase_children(
12102 cnt::darray<SystemPhaseScheduleItem>& phases, const cnt::darray<uint32_t>& firstChildren,
12103 const cnt::darray<uint32_t>& nextSiblings, cnt::darray<uint8_t>& states, uint32_t phaseIdx, uint32_t& order) {
12104 if (states[phaseIdx] != 0)
12105 return;
12106
12107 states[phaseIdx] = 1;
12108 for (uint32_t childIdx = firstChildren[phaseIdx]; childIdx != UINT32_MAX; childIdx = nextSiblings[childIdx])
12109 system_schedule_visit_phase_children(phases, firstChildren, nextSiblings, states, childIdx, order);
12110 phases[phaseIdx].order = order++;
12111 states[phaseIdx] = 2;
12112 }
12113
12118 inline void system_schedule_assign_phase_orders(
12119 World& world, cnt::darray<SystemPhaseScheduleItem>& phases, SystemScheduleScratch& scratch) {
12120 // Phase ordering has two layers:
12121 // 1. A cheap DFS postorder over each phase's primary DependsOn target. This gives stable depth/path
12122 // keys and a deterministic fallback for cycles.
12123 // 2. A direct DependsOn topological pass. This preserves all explicit phase targets, including
12124 // multi-target dependencies that the primary-target path cannot represent by itself.
12125 auto& sortedPhases = scratch.sortedPhases;
12126 auto& primaryPhases = scratch.primaryPhases;
12127 auto& firstChildren = scratch.firstChildren;
12128 auto& nextSiblings = scratch.nextSiblings;
12129 auto& states = scratch.states;
12130
12131 // Work in entity-id order whenever the graph does not force a different order. That keeps
12132 // independent phases deterministic and makes cycle fallback stable.
12133 sortedPhases.clear();
12134 sortedPhases.reserve(phases.size());
12135 for (uint32_t i = 0; i < phases.size(); ++i)
12136 sortedPhases.push_back(i);
12137 core::sort(sortedPhases, [&](uint32_t lhs, uint32_t rhs) {
12138 return entity_schedule_less(phases[lhs].phase, phases[rhs].phase);
12139 });
12140
12141 // Collapse each phase's dependency path to one primary target for the DFS ordering key. Direct
12142 // multi-target dependencies are handled later by the topological pass.
12143 primaryPhases.clear();
12144 primaryPhases.resize(phases.size(), UINT32_MAX);
12145 for (uint32_t i = 0; i < phases.size(); ++i)
12146 primaryPhases[i] = system_schedule_primary_phase_idx(world, phases, i);
12147
12148 // Build primary-target adjacency once, then walk it instead of repeatedly scanning all phases for
12149 // children. Reverse insertion preserves sorted child traversal.
12150 firstChildren.clear();
12151 nextSiblings.clear();
12152 firstChildren.resize(phases.size(), UINT32_MAX);
12153 nextSiblings.resize(phases.size(), UINT32_MAX);
12154 for (uint32_t i = sortedPhases.size(); i > 0; --i) {
12155 const auto phaseIdx = sortedPhases[i - 1];
12156 const auto targetIdx = primaryPhases[phaseIdx];
12157 if (targetIdx == UINT32_MAX)
12158 continue;
12159 nextSiblings[phaseIdx] = firstChildren[targetIdx];
12160 firstChildren[targetIdx] = phaseIdx;
12161 }
12162
12163 states.clear();
12164 states.resize(phases.size(), 0);
12165
12166 // Assign the primary DFS order from roots first. Any phase left unvisited participates in a
12167 // primary-target cycle, so it receives a deterministic entity-order fallback below.
12168 uint32_t order = 0;
12169 for (auto phaseIdx: sortedPhases) {
12170 if (states[phaseIdx] == 0 && primaryPhases[phaseIdx] == UINT32_MAX) {
12171 system_schedule_visit_phase_children(phases, firstChildren, nextSiblings, states, phaseIdx, order);
12172 }
12173 }
12174
12175 // Remaining phases are in a DependsOn cycle. The cycle is invalid, but the update stays deterministic.
12176 for (auto phaseIdx: sortedPhases) {
12177 if (states[phaseIdx] == 0) {
12178 phases[phaseIdx].order = order++;
12179 states[phaseIdx] = 2;
12180 }
12181 }
12182
12183 auto& edges = scratch.edges;
12184 auto& childCounts = scratch.childCounts;
12185 auto& firstEdges = scratch.firstEdges;
12186 auto& readyNext = scratch.readyNext;
12187 auto& ordered = scratch.sortedIndices;
12188 auto& visited = scratch.visited;
12189
12190 edges.clear();
12191 childCounts.clear();
12192 childCounts.resize(phases.size(), 0);
12193
12194 // Build explicit phase edges from the actual DependsOn pairs. We count incoming children per
12195 // target so a phase becomes ready only after all phases depending on it have been emitted.
12196 for (uint32_t phaseIdx = 0; phaseIdx < phases.size(); ++phaseIdx) {
12197 world.targets(phases[phaseIdx].phase, DependsOn, [&](Entity target) {
12198 const auto targetIdx = system_schedule_find_phase(phases, target);
12199 if (targetIdx == UINT32_MAX || targetIdx == phaseIdx)
12200 return;
12201
12202 SystemScheduleEdge edge{};
12203 edge.child = phaseIdx;
12204 edge.target = targetIdx;
12205 edges.push_back(edge);
12206 ++childCounts[targetIdx];
12207 });
12208 }
12209 if (edges.empty())
12210 return;
12211
12212 // Link outgoing edges per child phase. This lets the ready-list pass touch only edges affected by
12213 // the phase it just emitted.
12214 firstEdges.clear();
12215 firstEdges.resize(phases.size(), UINT32_MAX);
12216 for (uint32_t edgeIdx = edges.size(); edgeIdx > 0; --edgeIdx) {
12217 auto& edge = edges[edgeIdx - 1];
12218 edge.next = firstEdges[edge.child];
12219 firstEdges[edge.child] = edgeIdx - 1;
12220 }
12221
12222 auto phase_less = [&](uint32_t lhs, uint32_t rhs) {
12223 const auto& lhsPhase = phases[lhs];
12224 const auto& rhsPhase = phases[rhs];
12225 if (lhsPhase.order != rhsPhase.order)
12226 return lhsPhase.order < rhsPhase.order;
12227 if (lhsPhase.depth != rhsPhase.depth)
12228 return lhsPhase.depth > rhsPhase.depth;
12229 return entity_schedule_less(lhsPhase.phase, rhsPhase.phase);
12230 };
12231 auto ready_insert = [&](uint32_t& readyHead, uint32_t phaseIdx) {
12232 uint32_t* ppCurr = &readyHead;
12233 while (*ppCurr != UINT32_MAX && phase_less(*ppCurr, phaseIdx))
12234 ppCurr = &readyNext[*ppCurr];
12235 readyNext[phaseIdx] = *ppCurr;
12236 *ppCurr = phaseIdx;
12237 };
12238
12239 readyNext.clear();
12240 readyNext.resize(phases.size(), UINT32_MAX);
12241
12242 // Kahn pass in Gaia's scheduler direction: children run before their DependsOn targets. The
12243 // ordered ready list preserves the primary DFS order whenever several phases are unblocked.
12244 uint32_t readyHead = UINT32_MAX;
12245 for (auto phaseIdx: sortedPhases) {
12246 if (childCounts[phaseIdx] == 0)
12247 ready_insert(readyHead, phaseIdx);
12248 }
12249
12250 ordered.clear();
12251 ordered.reserve(phases.size());
12252 while (readyHead != UINT32_MAX) {
12253 const auto phaseIdx = readyHead;
12254 readyHead = readyNext[readyHead];
12255 ordered.push_back(phaseIdx);
12256
12257 for (uint32_t edgeIdx = firstEdges[phaseIdx]; edgeIdx != UINT32_MAX; edgeIdx = edges[edgeIdx].next) {
12258 const auto targetIdx = edges[edgeIdx].target;
12259 GAIA_ASSERT(childCounts[targetIdx] > 0);
12260 --childCounts[targetIdx];
12261 if (childCounts[targetIdx] == 0)
12262 ready_insert(readyHead, targetIdx);
12263 }
12264 }
12265
12266 if (ordered.size() != phases.size()) {
12267 // A direct phase dependency cycle cannot be topologically sorted. Keep the acyclic prefix and
12268 // append the remaining phases in entity order so execution is still deterministic.
12269 visited.clear();
12270 visited.resize(phases.size(), 0);
12271 for (auto phaseIdx: ordered)
12272 visited[phaseIdx] = 1;
12273 for (auto phaseIdx: sortedPhases) {
12274 if (visited[phaseIdx] == 0)
12275 ordered.push_back(phaseIdx);
12276 }
12277 }
12278
12279 for (uint32_t i = 0; i < ordered.size(); ++i)
12280 phases[ordered[i]].order = i;
12281 }
12282
12287 inline void system_schedule_assign_order_keys(
12288 World& world, cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices,
12289 SystemScheduleScratch& scratch) {
12290 auto& stack = scratch.entityStack;
12291 auto& phases = scratch.phases;
12292 auto& groupIndices = scratch.groupIndices;
12293
12294 for (auto& item: items) {
12295 stack.clear();
12296 item.systemDepth = system_schedule_dep_depth(world, item.entity, item.phase, stack);
12297 }
12298
12299 phases.clear();
12300 for (auto& item: items) {
12301 if (!item.hasPhase || system_schedule_find_phase(phases, item.phase) != UINT32_MAX)
12302 continue;
12303 SystemPhaseScheduleItem phaseItem{};
12304 phaseItem.phase = item.phase;
12305 stack.clear();
12306 phaseItem.depth = system_schedule_dep_depth(world, phaseItem.phase, EntityBad, stack);
12307 phases.push_back(phaseItem);
12308 }
12309
12310 system_schedule_assign_phase_orders(world, phases, scratch);
12311 for (auto& item: items) {
12312 if (!item.hasPhase)
12313 continue;
12314 const auto phaseIdx = system_schedule_find_phase(phases, item.phase);
12315 if (phaseIdx == UINT32_MAX)
12316 continue;
12317 item.phaseOrder = phases[phaseIdx].order;
12318 item.phaseDepth = phases[phaseIdx].depth;
12319 }
12320
12321 groupIndices.clear();
12322 groupIndices.reserve(items.size());
12323 for (uint32_t i = 0; i < items.size(); ++i) {
12324 if (!items[i].hasPhase)
12325 groupIndices.push_back(i);
12326 }
12327 if (!groupIndices.empty())
12328 system_schedule_assign_group_orders(world, items, groupIndices, entityIndices, scratch);
12329
12330 for (auto& phase: phases) {
12331 groupIndices.clear();
12332 for (uint32_t i = 0; i < items.size(); ++i) {
12333 if (items[i].phase == phase.phase)
12334 groupIndices.push_back(i);
12335 }
12336 if (!groupIndices.empty())
12337 system_schedule_assign_group_orders(world, items, groupIndices, entityIndices, scratch);
12338 }
12339 }
12340
12350 GAIA_NODISCARD inline bool system_schedule_less(const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
12351 if (lhs.hasPhase != rhs.hasPhase)
12352 return lhs.hasPhase;
12353
12354 if (lhs.hasPhase && lhs.phase != rhs.phase) {
12355 if (lhs.phaseOrder != rhs.phaseOrder)
12356 return lhs.phaseOrder < rhs.phaseOrder;
12357 if (lhs.phaseDepth != rhs.phaseDepth)
12358 return lhs.phaseDepth > rhs.phaseDepth;
12359 return entity_schedule_less(lhs.phase, rhs.phase);
12360 }
12361
12362 if (lhs.systemOrder != rhs.systemOrder)
12363 return lhs.systemOrder < rhs.systemOrder;
12364 if (lhs.systemDepth != rhs.systemDepth)
12365 return lhs.systemDepth > rhs.systemDepth;
12366 return entity_schedule_less(lhs.entity, rhs.entity);
12367 }
12368
12374 inline void system_schedule_add_edge(
12375 cnt::darray<SystemScheduleEdge>& edges, cnt::darray<uint32_t>& childCounts, uint32_t childIdx,
12376 uint32_t targetIdx) {
12377 SystemScheduleEdge edge{};
12378 edge.child = childIdx;
12379 edge.target = targetIdx;
12380 edges.push_back(edge);
12381 ++childCounts[targetIdx];
12382 }
12383
12390 inline void system_schedule_build_edges(
12391 World& world, const cnt::darray<SystemScheduleItem>& items, const cnt::darray<uint32_t>& entityIndices,
12392 cnt::darray<SystemScheduleEdge>& edges, cnt::darray<uint32_t>& childCounts) {
12393 childCounts.resize(items.size(), 0);
12394 for (uint32_t childIdx = 0; childIdx < items.size(); ++childIdx) {
12395 const auto& child = items[childIdx];
12396 world.targets(child.entity, DependsOn, [&](Entity target) {
12397 if (target == child.phase)
12398 return;
12399 const auto targetIdx = system_schedule_find_item_by_entity(items, entityIndices, target);
12400 if (targetIdx == UINT32_MAX || targetIdx == childIdx)
12401 return;
12402 if (!system_schedule_same_group(child, items[targetIdx]))
12403 return;
12404
12405 system_schedule_add_edge(edges, childCounts, childIdx, targetIdx);
12406 });
12407 }
12408 }
12409
12415 inline void system_schedule_ready_insert(
12416 const cnt::darray<SystemScheduleItem>& items, cnt::darray<uint32_t>& readyNext, uint32_t& readyHead,
12417 uint32_t itemIdx) {
12418 uint32_t* ppCurr = &readyHead;
12419 while (*ppCurr != UINT32_MAX && system_schedule_less(items[*ppCurr], items[itemIdx]))
12420 ppCurr = &readyNext[*ppCurr];
12421 readyNext[itemIdx] = *ppCurr;
12422 *ppCurr = itemIdx;
12423 }
12424
12432 inline void order_system_schedule_items(
12433 World& world, cnt::darray<SystemScheduleItem>& items, SystemScheduleScratch& scratch) {
12434 auto& entityIndices = scratch.entityIndices;
12435 auto& edges = scratch.edges;
12436 auto& childCounts = scratch.childCounts;
12437 auto& sortedIndices = scratch.sortedIndices;
12438 auto& firstEdges = scratch.firstEdges;
12439 auto& ordered = scratch.ordered;
12440 auto& visited = scratch.visited;
12441 auto& readyNext = scratch.readyNext;
12442
12443 system_schedule_entity_indices(items, entityIndices);
12444 system_schedule_assign_order_keys(world, items, entityIndices, scratch);
12445
12446 edges.clear();
12447 childCounts.clear();
12448 edges.reserve(items.size());
12449 system_schedule_build_edges(world, items, entityIndices, edges, childCounts);
12450 if (edges.empty()) {
12451 core::sort(items, [](const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
12452 return system_schedule_less(lhs, rhs);
12453 });
12454 return;
12455 }
12456
12457 sortedIndices.clear();
12458 sortedIndices.reserve(items.size());
12459 for (uint32_t i = 0; i < items.size(); ++i)
12460 sortedIndices.push_back(i);
12461 core::sort(sortedIndices, [&](uint32_t lhs, uint32_t rhs) {
12462 return system_schedule_less(items[lhs], items[rhs]);
12463 });
12464
12465 firstEdges.clear();
12466 firstEdges.resize(items.size(), UINT32_MAX);
12467 for (uint32_t i = edges.size(); i > 0; --i) {
12468 auto& edge = edges[i - 1];
12469 edge.next = firstEdges[edge.child];
12470 firstEdges[edge.child] = i - 1;
12471 }
12472
12473 ordered.clear();
12474 ordered.reserve(items.size());
12475
12476 visited.clear();
12477 visited.resize(items.size(), 0);
12478 readyNext.clear();
12479 readyNext.resize(items.size(), UINT32_MAX);
12480
12481 uint32_t readyHead = UINT32_MAX;
12482 for (uint32_t i = sortedIndices.size(); i > 0; --i) {
12483 const auto itemIdx = sortedIndices[i - 1];
12484 if (childCounts[itemIdx] == 0) {
12485 readyNext[itemIdx] = readyHead;
12486 readyHead = itemIdx;
12487 }
12488 }
12489
12490 while (ordered.size() < items.size()) {
12491 uint32_t bestIdx = readyHead;
12492 if (bestIdx != UINT32_MAX)
12493 readyHead = readyNext[bestIdx];
12494 if (bestIdx == UINT32_MAX) {
12495 for (auto itemIdx: sortedIndices) {
12496 if (visited[itemIdx] == 0) {
12497 bestIdx = itemIdx;
12498 break;
12499 }
12500 }
12501 }
12502
12503 GAIA_ASSERT(bestIdx != UINT32_MAX);
12504 if (bestIdx == UINT32_MAX)
12505 break;
12506
12507 visited[bestIdx] = 1;
12508 ordered.push_back(items[bestIdx]);
12509
12510 for (uint32_t edgeIdx = firstEdges[bestIdx]; edgeIdx != UINT32_MAX; edgeIdx = edges[edgeIdx].next) {
12511 const auto targetIdx = edges[edgeIdx].target;
12512 if (visited[targetIdx] != 0)
12513 continue;
12514 GAIA_ASSERT(childCounts[targetIdx] > 0);
12515 if (childCounts[targetIdx] > 0) {
12516 --childCounts[targetIdx];
12517 if (childCounts[targetIdx] == 0)
12518 system_schedule_ready_insert(items, readyNext, readyHead, targetIdx);
12519 }
12520 }
12521 }
12522
12523 items = ordered;
12524 }
12525
12530 GAIA_NODISCARD inline bool
12531 system_schedule_batch_changed(const SystemScheduleItem& lhs, const SystemScheduleItem& rhs) {
12532 if (lhs.hasPhase != rhs.hasPhase)
12533 return true;
12534 if (lhs.hasPhase)
12535 return lhs.phase != rhs.phase || lhs.systemDepth != rhs.systemDepth;
12536 return lhs.systemDepth != rhs.systemDepth;
12537 }
12538
12542 GAIA_NODISCARD inline bool system_exec_uses_scheduler(QueryExecType type) {
12543 return type == QueryExecType::Parallel || type == QueryExecType::ParallelPerf ||
12544 type == QueryExecType::ParallelEff;
12545 }
12546
12550 inline void run_system_entity_erased(void* pCtx, const SystemScheduleItem& item) {
12551 auto& ctx = *static_cast<SystemRunCtx*>(pCtx);
12552 GAIA_ASSERT(ctx.pWorld != nullptr);
12553 GAIA_ASSERT(ctx.pPending != nullptr);
12554 if (ctx.pWorld == nullptr || ctx.pPending == nullptr)
12555 return;
12556
12557 auto& world = *ctx.pWorld;
12558 auto& pending = *ctx.pPending;
12559 const auto systemEntity = item.entity;
12560 if (!world.valid(systemEntity) || !world.has(systemEntity, System))
12561 return;
12562 if (!world.enabled_hierarchy(systemEntity, ChildOf))
12563 return;
12564
12565 if (!ctx.hasCurrent) {
12566 ctx.current = item;
12567 ctx.hasCurrent = true;
12568 } else if (system_schedule_batch_changed(ctx.current, item)) {
12569 flush_pending_system_jobs(pending);
12570 ctx.current = item;
12571 }
12572
12573 auto ss = world.acc_mut(systemEntity);
12574 auto& sys = ss.smut<ecs::System_>();
12575 if (!ctx.canScheduleSystems || !system_exec_uses_scheduler(sys.execType) || sys.query.main_thread_required()) {
12576 flush_pending_system_jobs(pending);
12577 sys.exec(world);
12578 return;
12579 }
12580
12581 auto job = sys.job(world);
12582 if (!job.valid())
12583 return;
12584
12585 for (auto& pendingJob: pending) {
12586 if (!world.valid(pendingJob.entity) || !world.has(pendingJob.entity, System))
12587 continue;
12588
12589 auto prevSs = world.acc_mut(pendingJob.entity);
12590 auto& prevSys = prevSs.smut<ecs::System_>();
12591 if (!prevSys.query.can_run_parallel(sys.query))
12592 job.dep(pendingJob.job);
12593 }
12594
12595 pending.emplace_back(systemEntity, GAIA_MOV(job));
12596 }
12597
12601 inline void collect_system_schedule_item_erased(void* pCtx, Entity systemEntity) {
12602 auto& ctx = *static_cast<SystemCollectCtx*>(pCtx);
12603 GAIA_ASSERT(ctx.pWorld != nullptr);
12604 GAIA_ASSERT(ctx.pItems != nullptr);
12605 if (ctx.pWorld == nullptr || ctx.pItems == nullptr)
12606 return;
12607 ctx.pItems->push_back(system_schedule_item(*ctx.pWorld, systemEntity));
12608 }
12609
12611 inline void collect_system_entity_erased(void* pCtx, Entity systemEntity) {
12612 auto& out = *static_cast<cnt::darray<Entity>*>(pCtx);
12613 out.push_back(systemEntity);
12614 }
12615 } // namespace detail
12616
12617 inline void World::systems_init() {
12618 m_systemsQuery = query().all(System);
12619 }
12620
12621 inline void World::systems_run() {
12622 if GAIA_UNLIKELY (tearing_down())
12623 return;
12624
12625 auto& items = m_systemScheduleScratch.items;
12626 items.clear();
12627
12628 detail::SystemCollectCtx collectCtx{};
12629 collectCtx.pWorld = this;
12630 collectCtx.pItems = &items;
12631 m_systemsQuery.each_entity_enabled(&collectCtx, detail::collect_system_schedule_item_erased);
12632 detail::order_system_schedule_items(*this, items, m_systemScheduleScratch);
12633
12634 cnt::darray<detail::PendingSystemJob> pending;
12635 detail::SystemRunCtx ctx{};
12636 ctx.pWorld = this;
12637 ctx.pPending = &pending;
12638 ctx.canScheduleSystems = detail::sched_supports_deferred_system_jobs(world_sched(*this));
12639
12640 for (auto& item: items)
12641 detail::run_system_entity_erased(&ctx, item);
12642 detail::flush_pending_system_jobs(pending);
12643 }
12644
12645 inline void World::systems_done() {
12646 cnt::darray<Entity> tmpEntities;
12647 m_systemsQuery.each_entity_enabled(&tmpEntities, detail::collect_system_entity_erased);
12648
12649 // Wait for every outstanding system job before mutating any system runtime state.
12650 // This keeps dependency chains intact while jobs are still live.
12651 for (auto entity: tmpEntities) {
12652 if (!valid(entity) || !has(entity, System))
12653 continue;
12654
12655 auto ss = acc_mut(entity);
12656 auto& sys = ss.smut<ecs::System_>();
12657 if (sys.jobHandle != (mt::JobHandle)mt::JobNull_t{}) {
12658 auto& tp = mt::ThreadPool::get();
12659 tp.wait(sys.jobHandle);
12660 }
12661 }
12662
12663 // With all system jobs complete we can release their runtime state safely.
12664 for (auto entity: tmpEntities) {
12665 if (!valid(entity) || !has(entity, System))
12666 continue;
12667
12668 auto ss = acc_mut(entity);
12669 auto& sys = ss.smut<ecs::System_>();
12670 if (sys.jobHandle != (mt::JobHandle)mt::JobNull_t{}) {
12671 auto& tp = mt::ThreadPool::get();
12672 tp.del(sys.jobHandle);
12673 sys.jobHandle = mt::JobNull;
12674 }
12675 sys.query = {};
12676 }
12677
12678 m_systemsQuery = {};
12679 tmpEntities.clear();
12680 }
12681
12682 inline SystemBuilder World::system() {
12683 // Create the system
12684 auto e = add();
12685 EntityBuilder(*this, e) //
12686 .add<System_>();
12687
12688 auto ss = acc_mut(e);
12689 auto& sys = ss.smut<System_>();
12690 auto& sysRuntime = systems().data_add(e);
12691 {
12692 sys.entity = e;
12693 sys.query = query();
12694 sysRuntime.on_each_func = {};
12695 }
12696 return SystemBuilder(*this, e);
12697 }
12698 } // namespace ecs
12699} // namespace gaia
12700#endif
12701
12702namespace gaia {
12703 namespace ecs {
12704 inline uint32_t world_version(const World& world) {
12705 return world.m_worldVersion;
12706 }
12707
12711 inline const Sched& world_sched(const World& world) {
12712 return world.sched();
12713 }
12714
12721 inline void
12722 world_for_each_target(const World& world, Entity entity, Entity relation, void* ctx, void (*func)(void*, Entity)) {
12723 world.targets(entity, relation, [ctx, func](Entity target) {
12724 func(ctx, target);
12725 });
12726 }
12727
12731 inline QueryMatchScratch& query_match_scratch_acquire(World& world) {
12732 if (world.m_queryMatchScratchDepth == world.m_queryMatchScratchStack.size())
12733 world.m_queryMatchScratchStack.push_back(new QueryMatchScratch());
12734
12735 auto& scratch = *world.m_queryMatchScratchStack[world.m_queryMatchScratchDepth++];
12736 scratch.clear_temporary_matches();
12737 return scratch;
12738 }
12739
12743 inline void query_match_scratch_release(World& world, bool keepStamps) {
12744 GAIA_ASSERT(world.m_queryMatchScratchDepth > 0);
12745 auto& scratch = *world.m_queryMatchScratchStack[--world.m_queryMatchScratchDepth];
12746 if (keepStamps)
12747 scratch.clear_temporary_matches_keep_stamps();
12748 else
12749 scratch.clear_temporary_matches();
12750 }
12751
12755 inline void world_invalidate_sorted_queries_for_entity(World& world, Entity entity) {
12757 }
12758
12761 inline void world_invalidate_sorted_queries(World& world) {
12762 world.invalidate_sorted_queries();
12763 }
12764
12770 inline bool world_has_entity_term(const World& world, Entity entity, Entity term) {
12771 if (term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
12772 const auto target = world.get(term.gen());
12773 return world.valid(target) && world.is(entity, target);
12774 }
12775
12776 return world.has(entity, term);
12777 }
12778
12784 inline bool world_has_entity_term_in(const World& world, Entity entity, Entity term) {
12785 if (term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
12786 const auto target = world.get(term.gen());
12787 return world.valid(target) && world.in(entity, target);
12788 }
12789
12790 return false;
12791 }
12792
12797 inline bool world_term_uses_inherit_policy(const World& world, Entity term) {
12798 return !is_wildcard(term) && world.valid(term) && world.target(term, OnInstantiate) == Inherit;
12799 }
12800
12806 inline bool world_has_entity_term_direct(const World& world, Entity entity, Entity term) {
12807 return world.has_direct(entity, term);
12808 }
12809
12814 inline bool world_is_exclusive_dont_fragment_relation(const World& world, Entity relation) {
12815 return world.is_exclusive_dont_fragment_relation(relation);
12816 }
12817
12822 inline bool world_is_out_of_line_component(const World& world, Entity component) {
12823 return world.is_out_of_line_component(component);
12824 }
12825
12830 inline bool world_is_non_fragmenting_out_of_line_component(const World& world, Entity component) {
12831 return world.is_non_fragmenting_out_of_line_component(component);
12832 }
12833
12838 inline uint32_t world_count_direct_term_entities(const World& world, Entity term) {
12839 return world.count_direct_term_entities(term);
12840 }
12841
12846 inline uint32_t world_count_in_term_entities(const World& world, Entity term) {
12847 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
12848 return 0;
12849
12850 const auto target = world.get(term.gen());
12851 return world.valid(target) ? (uint32_t)world.as_relations_trav_cache(target).size() : 0U;
12852 }
12853
12858 inline uint32_t world_count_direct_term_entities_direct(const World& world, Entity term) {
12859 return world.count_direct_term_entities_direct(term);
12860 }
12861
12866 inline void world_collect_direct_term_entities(const World& world, Entity term, cnt::darray<Entity>& out) {
12867 world.collect_direct_term_entities(term, out);
12868 }
12869
12874 inline void world_collect_in_term_entities(const World& world, Entity term, cnt::darray<Entity>& out) {
12875 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
12876 return;
12877
12878 const auto target = world.get(term.gen());
12879 if (!world.valid(target))
12880 return;
12881
12882 const auto& relations = world.as_relations_trav_cache(target);
12883 out.reserve(out.size() + (uint32_t)relations.size());
12884 for (auto relation: relations)
12885 out.push_back(relation);
12886 }
12887
12892 inline void world_collect_direct_term_entities_direct(const World& world, Entity term, cnt::darray<Entity>& out) {
12893 world.collect_direct_term_entities_direct(term, out);
12894 }
12895
12902 inline bool
12903 world_for_each_direct_term_entity(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
12904 return world.for_each_direct_term_entity(term, ctx, func);
12905 }
12906
12913 inline bool world_for_each_in_term_entity(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
12914 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
12915 return true;
12916
12917 const auto target = world.get(term.gen());
12918 if (!world.valid(target))
12919 return true;
12920
12921 for (auto relation: world.as_relations_trav_cache(target)) {
12922 if (!func(ctx, relation))
12923 return false;
12924 }
12925
12926 return true;
12927 }
12928
12935 inline bool
12936 world_for_each_direct_term_entity_direct(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
12937 return world.for_each_direct_term_entity_direct(term, ctx, func);
12938 }
12939
12944 inline bool world_entity_enabled(const World& world, Entity entity) {
12945 return world.enabled(entity);
12946 }
12947
12952 inline Entity world_pair_target_if_alive(const World& world, Entity pair) {
12953 return world.pair_target_if_alive(pair);
12954 }
12955
12961 inline bool world_entity_enabled_hierarchy(const World& world, Entity entity, Entity relation) {
12962 return world.enabled_hierarchy(entity, relation);
12963 }
12964
12968 inline uint32_t world_enabled_hierarchy_version(const World& world) {
12969 return world.enabled_hierarchy_version();
12970 }
12971
12976 inline bool world_is_hierarchy_relation(const World& world, Entity relation) {
12977 return world.is_hierarchy_relation(relation);
12978 }
12979
12984 inline bool world_is_fragmenting_relation(const World& world, Entity relation) {
12985 return world.is_fragmenting_relation(relation);
12986 }
12987
12992 inline bool world_is_fragmenting_hierarchy_relation(const World& world, Entity relation) {
12993 return world.is_fragmenting_hierarchy_relation(relation);
12994 }
12995
13000 inline bool world_supports_depth_order(const World& world, Entity relation) {
13001 return world.supports_depth_order(relation);
13002 }
13003
13008 inline bool world_depth_order_prunes_disabled_subtrees(const World& world, Entity relation) {
13009 return world.depth_order_prunes_disabled_subtrees(relation);
13010 }
13011
13016 inline bool world_entity_prefab(const World& world, Entity entity) {
13017 const auto& ec = world.fetch(entity);
13018 return ec.pArchetype != nullptr && ec.pArchetype->has(Prefab);
13019 }
13020
13026 inline Entity world_query_first_inherited_owner(const World& world, const Archetype& archetype, Entity term) {
13027 const auto& chunks = archetype.chunks();
13028 const Chunk* pFirstChunk = nullptr;
13029 for (const auto* pChunk: chunks) {
13030 if (pChunk == nullptr || pChunk->size() == 0)
13031 continue;
13032 pFirstChunk = pChunk;
13033 break;
13034 }
13035
13036 if (pFirstChunk == nullptr)
13037 return EntityBad;
13038
13039 const auto firstEntity = pFirstChunk->entity_view()[0];
13040 for (const auto target: world.as_targets_trav_cache(firstEntity)) {
13041 if (!world.has_direct(target, term))
13042 continue;
13043 return target;
13044 }
13045
13046 return EntityBad;
13047 }
13048
13053 inline const Archetype* world_entity_archetype(const World& world, Entity entity) {
13054 return world.fetch(entity).pArchetype;
13055 }
13056
13061 inline uint32_t world_component_index_bucket_size(const World& world, Entity term) {
13062 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
13063 if (it == world.m_entityToArchetypeMap.end())
13064 return 0;
13065
13066 return (uint32_t)it->second.size();
13067 }
13068
13074 inline uint32_t world_component_index_comp_idx(const World& world, const Archetype& archetype, Entity term) {
13075 if (is_wildcard(term))
13076 return BadIndex;
13077
13078 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
13079 if (it == world.m_entityToArchetypeMap.end())
13080 return BadIndex;
13081
13082 const auto idx = core::get_index_if(it->second, [&](const auto& entry) {
13083 return entry.matches(&archetype);
13084 });
13085 if (idx == BadIndex)
13086 return BadIndex;
13087
13088 return it->second[idx].compIdx;
13089 }
13090
13096 inline uint32_t world_component_index_match_count(const World& world, const Archetype& archetype, Entity term) {
13097 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
13098 if (it == world.m_entityToArchetypeMap.end())
13099 return 0;
13100
13101 const auto idx = core::get_index_if(it->second, [&](const auto& entry) {
13102 return entry.matches(&archetype);
13103 });
13104 if (idx == BadIndex)
13105 return 0;
13106
13107 return it->second[idx].matchCount;
13108 }
13109
13116 template <typename T>
13117 inline const std::remove_cv_t<std::remove_reference_t<T>>*
13118 world_query_inherited_arg_data_const(World& world, Entity owner, Entity id) {
13119 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13120 return &world.template get<Arg>(owner, id);
13121 }
13122
13128 inline const void* world_query_inherited_arg_data_const_ptr(const World& world, Entity owner, Entity id) {
13129 const auto& ec = world.fetch(owner);
13130 const auto row = id.kind() == EntityKind::EK_Gen ? ec.row : 0;
13131 return ec.pChunk->comp_ptr(ec.pChunk->comp_idx(id), row);
13132 }
13133
13139 template <typename T>
13140 inline decltype(auto) world_direct_entity_arg(World& world, Entity entity) {
13141 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13142 if constexpr (std::is_same_v<Arg, Entity>)
13143 return entity;
13144 else if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>)
13145 return world.template mut_im<Arg>(entity);
13146 else
13147 return world.template get<Arg>(entity);
13148 }
13149
13155 template <typename T>
13156 inline decltype(auto) world_direct_entity_arg_raw(World& world, Entity entity) {
13157 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13158 if constexpr (std::is_same_v<Arg, Entity>)
13159 return entity;
13160 else if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>)
13161 return world.template mut<Arg>(entity);
13162 else
13163 return world.template get<Arg>(entity);
13164 }
13165
13170 template <typename T>
13171 inline Entity world_query_arg_id(World& world) {
13172 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13173 using FT = typename component_type_t<Arg>::TypeFull;
13174 if constexpr (is_pair<FT>::value) {
13175 const auto rel = comp_cache(world).template get<typename FT::rel>().entity;
13176 const auto tgt = comp_cache(world).template get<typename FT::tgt>().entity;
13177 return (Entity)Pair(rel, tgt);
13178 } else
13179 return comp_cache(world).template get<FT>().entity;
13180 }
13181
13187 template <typename T>
13188 inline decltype(auto) world_query_entity_arg(World& world, Entity entity) {
13189 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13190 if constexpr (std::is_same_v<Arg, Entity>)
13191 return entity;
13192 else {
13193 const auto id = world_query_arg_id<Arg>(world);
13194 return world_query_entity_arg_by_id<T>(world, entity, id);
13195 }
13196 }
13197
13205 template <typename T>
13206 inline decltype(auto) world_query_entity_arg_by_id(World& world, Entity entity, Entity id) {
13207 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13208 if constexpr (std::is_same_v<Arg, Entity>)
13209 return entity;
13210 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
13211 if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>) {
13212 if (!world.has_direct(entity, termId)) {
13213 if constexpr (is_pair<Arg>::value)
13214 (void)world.override(entity, termId);
13215 else
13216 (void)world.template override<Arg>(entity, termId);
13217 }
13218
13219 return world.template mut_im<Arg>(entity, termId);
13220 } else
13221 return world.template get<Arg>(entity, termId);
13222 }
13223
13231 template <typename T>
13232 inline decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id) {
13233 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13234 if constexpr (std::is_same_v<Arg, Entity>)
13235 return entity;
13236
13237 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
13238 if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>) {
13239 if (!world.has_direct(entity, termId)) {
13240 if constexpr (is_pair<Arg>::value)
13241 (void)world.override(entity, termId);
13242 else
13243 (void)world.template override<Arg>(entity, termId);
13244 }
13245
13246 return world.template mut<Arg>(entity, termId);
13247 } else
13248 return world.template get<Arg>(entity, termId);
13249 }
13250
13260 template <typename T>
13261 inline void world_init_query_entity_arg_by_id_chunk_stable_const(
13262 World& world, const Chunk& chunk, const Entity* pEntities, Entity id, bool& direct, uint32_t& compIdx,
13263 const std::remove_cv_t<std::remove_reference_t<T>>*& pDataInherited) {
13264 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13265 if constexpr (std::is_same_v<Arg, Entity>) {
13266 direct = false;
13267 compIdx = BadIndex;
13268 pDataInherited = nullptr;
13269 return;
13270 }
13271
13272 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
13273 direct = chunk.has(termId);
13274 compIdx = BadIndex;
13275 pDataInherited = nullptr;
13276
13277 if (direct) {
13278 compIdx = chunk.comp_idx(termId);
13279 GAIA_ASSERT(compIdx != BadIndex);
13280 return;
13281 }
13282
13283 auto owner = EntityBad;
13284 const auto firstEntity = pEntities[0];
13285 for (const auto target: world.as_targets_trav_cache(firstEntity)) {
13286 if (!world.has_direct(target, termId))
13287 continue;
13288
13289 owner = target;
13290 break;
13291 }
13292
13293 GAIA_ASSERT(owner != EntityBad);
13294 pDataInherited = &world.template get<Arg>(owner, termId);
13295 }
13296
13306 template <typename T>
13307 inline decltype(auto) world_query_entity_arg_by_id_cached_const(
13308 World& world, Entity entity, Entity id, const Archetype*& pLastArchetype, Entity& cachedOwner,
13309 bool& cachedDirect) {
13310 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13311 if constexpr (std::is_same_v<Arg, Entity>)
13312 return entity;
13313
13314 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
13315 const auto& ec = world.fetch(entity);
13316 if (ec.pArchetype != pLastArchetype) {
13317 pLastArchetype = ec.pArchetype;
13318 cachedDirect = ec.pArchetype->has(termId);
13319 cachedOwner = EntityBad;
13320
13321 if (!cachedDirect) {
13322 for (const auto target: world.as_targets_trav_cache(entity)) {
13323 if (!world.has_direct(target, termId))
13324 continue;
13325
13326 cachedOwner = target;
13327 break;
13328 }
13329
13330 GAIA_ASSERT(cachedOwner != EntityBad);
13331 }
13332 }
13333
13334 if (cachedDirect)
13335 return ComponentGetter{world, ec.pChunk, entity, ec.row}.template get<Arg>(termId);
13336
13337 return world.template get<Arg>(cachedOwner, termId);
13338 }
13339
13346 inline void world_notify_on_set(World& world, Entity term, Chunk& chunk, uint16_t from, uint16_t to) {
13347#if GAIA_OBSERVERS_ENABLED
13348 if (world.tearing_down())
13349 return;
13350 if (!world.observers().has_on_set_observers(term))
13351 return;
13352
13353 auto entities = chunk.entity_view();
13354 if (from >= entities.size())
13355 return;
13356 if (to > entities.size())
13357 to = (uint16_t)entities.size();
13358 if (from >= to)
13359 return;
13360
13361 world.observers().on_set(world, term, EntitySpan{entities.data() + from, uint32_t(to - from)});
13362#else
13363 (void)world;
13364 (void)term;
13365 (void)chunk;
13366 (void)from;
13367 (void)to;
13368#endif
13369 }
13370
13371 //----------------------------------------------------------------------
13372
13373 template <typename T>
13374 GAIA_NODISCARD decltype(auto) ComponentGetter::get(Entity type) const {
13375 GAIA_ASSERT(m_pWorld != nullptr);
13376 GAIA_ASSERT(m_entity != EntityBad);
13377
13378 using FT = typename component_type_t<T>::TypeFull;
13379 if constexpr (World::template supports_out_of_line_component<FT>()) {
13380 if (m_pWorld->template can_use_out_of_line_component<FT>(type)) {
13381 const auto* pStore = m_pWorld->template sparse_component_store<FT>(type);
13382 GAIA_ASSERT(pStore != nullptr);
13383 GAIA_ASSERT(pStore->has(m_entity));
13384 return pStore->get(m_entity);
13385 }
13386 }
13387
13388 return m_pChunk->template get<T>(m_row, type);
13389 }
13390
13391 template <typename T>
13392 decltype(auto) ComponentSetter::mut(Entity type) {
13393 return smut<T>(type);
13394 }
13395
13396 template <typename T>
13397 decltype(auto) ComponentSetter::smut(Entity type) {
13398 GAIA_ASSERT(m_pWorld != nullptr);
13399 GAIA_ASSERT(m_entity != EntityBad);
13400
13401 using FT = typename component_type_t<T>::TypeFull;
13402 if constexpr (World::template supports_out_of_line_component<FT>()) {
13403 auto& world = *const_cast<World*>(m_pWorld);
13404 if (world.template can_use_out_of_line_component<FT>(type))
13405 return world.template sparse_component_store_mut<FT>(type).mut(m_entity);
13406 }
13407
13408 return const_cast<Chunk*>(m_pChunk)->template sset<T>(m_row, type);
13409 }
13410
13411 template <typename T>
13413 smut<T>(type) = GAIA_FWD(value);
13414 return *this;
13415 }
13416
13417 template <typename T>
13419 GAIA_ASSERT(m_pWorld != nullptr);
13420 GAIA_ASSERT(m_entity != EntityBad);
13421
13422 smut<T>(type) = GAIA_FWD(value);
13423 using FT = typename component_type_t<T>::TypeFull;
13424 auto& world = *const_cast<World*>(m_pWorld);
13425
13426 if constexpr (World::template supports_out_of_line_component<FT>()) {
13427 if (world.template can_use_out_of_line_component<FT>(type))
13428 ::gaia::ecs::update_version(world.m_worldVersion);
13429 }
13430
13431 world.finish_write(m_entity, type);
13432 return *this;
13433 }
13434
13435 //----------------------------------------------------------------------
13436
13441 inline void world_notify_on_set_entity(World& world, Entity term, Entity entity) {
13442#if GAIA_OBSERVERS_ENABLED
13443 if (world.tearing_down())
13444 return;
13445 if (!world.valid(entity))
13446 return;
13447 if (!world.observers().has_on_set_observers(term))
13448 return;
13449
13450 world.observers().on_set(world, term, EntitySpan{&entity, 1});
13451#else
13452 (void)world;
13453 (void)term;
13454 (void)entity;
13455#endif
13456 }
13457
13462 inline void world_finish_write(World& world, Entity term, Entity entity) {
13463 world.finish_write(entity, term);
13464 }
13465
13466 } // namespace ecs
13467} // namespace gaia
13468
13469#if GAIA_OBSERVERS_ENABLED
13470namespace gaia {
13471 namespace ecs {
13472 inline uint32_t world_rel_version(const World& world, Entity relation) {
13473 return world.rel_version(relation);
13474 }
13475
13479 inline uint32_t world_entity_archetype_version(const World& world, Entity entity) {
13480 if (!world.valid(entity))
13481 return 0;
13482
13483 const auto key = EntityLookupKey(entity);
13484 auto it = world.m_srcEntityVersions.find(key);
13485 if (it != world.m_srcEntityVersions.end())
13486 return it->second;
13487
13488 it = world.m_srcEntityVersions.try_emplace(key, 1).first;
13489 return it->second;
13490 }
13491
13492 inline ObserverBuilder World::observer() {
13493 // Create the observer
13494 auto e = add();
13495 EntityBuilder(*this, e) //
13496 .add<Observer_>();
13497
13498 auto ss = acc_mut(e);
13499 auto& hdr = ss.smut<Observer_>();
13500 auto& obs = observers().data_add(e);
13501 {
13502 hdr.entity = e;
13503 obs.entity = e;
13504 obs.query = query();
13505 }
13506 return ObserverBuilder(*this, e);
13507 }
13508 } // namespace ecs
13509} // namespace gaia
13510#endif
Array with variable size of elements of type.
Definition darray_impl.h:25
iterator insert(iterator pos, const T &arg)
Insert the element to the position given by iterator pos.
Definition darray_impl.h:290
Definition span_impl.h:99
Definition archetype.h:83
void set_hashes(LookupHash hashLookup)
Sets hashes for each component type and lookup.
Definition archetype.h:662
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:839
GAIA_NODISCARD bool is_req_del() const
Returns true if this archetype is requested to be deleted.
Definition archetype.h:1158
Definition chunk.h:35
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:339
bool enabled(uint16_t row) const
Checks if the entity is enabled.
Definition chunk.h:1359
GAIA_NODISCARD uint32_t comp_idx(Entity entity) const
Returns the internal index of a component based on the provided entity.
Definition chunk.h:1694
GAIA_NODISCARD bool has(Entity entity) const
Checks if a component/entity entity is present in the chunk.
Definition chunk.h:1447
Cache for compile-time defined components.
Definition component_cache.h:24
GAIA_NODISCARD const ComponentCacheItem & get(Entity entity) const noexcept
Returns the component cache item.
Definition component_cache.h:408
GAIA_NODISCARD const ComponentCacheItem * find(Entity entity) const noexcept
Searches for the component cache item.
Definition component_cache.h:389
GAIA_NODISCARD GAIA_FORCEINLINE const ComponentCacheItem & add(Entity entity, util::str_view scopePath={})
Registers the component item for.
Definition component_cache.h:302
Definition world.h:174
GAIA_NODISCARD uint32_t & world_version()
Returns the current version of the world.
Definition world.h:7421
void as(Entity entity, Entity entityBase)
Shortcut for add(entity, Pair(Is, entityBase)
Definition world.h:4940
GAIA_NODISCARD const EntityContainer & fetch(Entity entity) const
Returns the internal record for entity.
Definition world.h:956
void invalidate_queries_for_structural_entity(EntityLookupKey entityKey)
Invalidates cached queries structurally affected by entityKey.
Definition world.h:11215
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:7069
GAIA_NODISCARD bool is_non_fragmenting_out_of_line_component(Entity component) const
Returns whether component is both out-of-line and non-fragmenting. Non-fragmenting out-of-line compon...
Definition world.h:1066
GAIA_NODISCARD uint32_t count_direct_term_entities_direct(Entity term) const
Counts entities directly matching term without semantic Is expansion.
Definition world.h:7026
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:6589
void scope(Entity scopeEntity, Func &&func)
Executes func with a temporary component scope and restores the previous scope afterwards....
Definition world.h:5629
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:1101
void defrag_entities_per_tick(uint32_t value)
Sets the maximum number of entities defragmented per world tick.
Definition world.h:7584
void set_max_lifespan(Entity entity, uint32_t lifespan=Archetype::MAX_ARCHETYPE_LIFESPAN)
Sets maximal lifespan of an archetype entity belongs to.
Definition world.h:7462
void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count, Func func)
Instantiates count copies of a prefab as normal entities parented under parentInstance....
Definition world.h:4768
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:6244
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:6317
GAIA_NODISCARD const cnt::set< EntityLookupKey > * targets(Entity relation) const
Returns targets for relation.
Definition world.h:6450
void update_src_entity_version(Entity entity)
Updates a tracked source-entity version after the entity changes archetype membership.
Definition world.h:7444
bool alias(Entity entity, const char *alias, uint32_t len=0)
Assigns an alias name to an entity.
Definition world.h:2899
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:1147
GAIA_NODISCARD bool children_bfs_if(Entity root, Func func) const
Traverses descendants in the ChildOf hierarchy in breadth-first order.
Definition world.h:7184
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:6174
ser::serializer get_serializer() const
Returns the currently bound runtime serializer handle.
Definition world.h:1587
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:6384
EntityBuilder build(Entity entity)
Starts a bulk add/remove operation on entity.
Definition world.h:3253
GAIA_NODISCARD Entity prefab(EntityKind kind=EntityKind::EK_Gen)
Creates a new prefab entity.
Definition world.h:3267
GAIA_NODISCARD util::str_view symbol(Entity component) const
Returns the registered symbol name for a component entity.
Definition world.h:2828
bool add_raw(Entity entity, Entity component, const void *data, uint32_t size)
Adds a chunk-backed AoS component and initializes its raw payload before OnAdd observers run.
Definition world.h:5295
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:5591
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:5210
Query uquery()
Provides an uncached query set up to work with the parent world. Uncached queries keep only a local i...
Definition world.h:895
void enable(Entity entity, bool enable)
Enables or disables an entire entity.
Definition world.h:7302
GAIA_NODISCARD const ComponentCache & comp_cache() const
Returns read-only access to the world component cache.
Definition world.h:2807
void children_bfs(Entity root, Func func) const
Traverses descendants in the ChildOf hierarchy in breadth-first order.
Definition world.h:7174
void children(Entity parent, Func func) const
Visits direct children in the ChildOf hierarchy.
Definition world.h:7157
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:11301
void set_sched(const Sched &sched)
Installs a custom scheduler used by ECS parallel execution paths.
Definition world.h:918
void child(Entity entity, Entity parent)
Shortcut for add(entity, Pair(ChildOf, parent)
Definition world.h:4977
GAIA_NODISCARD Entity symbol(const char *symbol, uint32_t len=0) const
Finds a component entity by its exact registered symbol.
Definition world.h:2817
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:4852
void set_serializer(ser::serializer serializer)
Binds a pre-built runtime serializer handle.
Definition world.h:1573
void clear(Entity entity)
Removes any component or entity attached to entity.
Definition world.h:3522
GAIA_NODISCARD bool override(Entity entity, Entity object)
Materializes an inherited id as directly owned storage on entity.
Definition world.h:3475
GAIA_NODISCARD const ComponentCacheItem & add()
Creates a new component if not found already.
Definition world.h:3300
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:2918
void add(Entity entity, Entity object)
Attaches entity object to entity entity.
Definition world.h:3348
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:1122
GAIA_NODISCARD util::str_view name(EntityId entityId) const
Returns the entity name assigned to entityId.
Definition world.h:5795
GAIA_NODISCARD const cnt::set< EntityLookupKey > * relations(Entity target) const
Returns relations for target.
Definition world.h:5964
Entity scope(Entity scope)
Sets the current component scope used for component registration and relative component lookup....
Definition world.h:5613
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:5409
GAIA_NODISCARD ComponentCursor cursor(Entity entity, Entity component) const
Creates a read-only cursor over a chunk-backed AoS runtime component on entity. Inherited ids resolve...
Definition world.h:11353
GAIA_NODISCARD Entity get() const
Returns the entity registered for component type T.
Definition world.h:3231
static GAIA_NODISCARD constexpr bool supports_out_of_line_component()
Out-of-line non-fragmenting storage currently supports only plain generic components....
Definition world.h:1179
GAIA_NODISCARD decltype(auto) sset(Entity entity)
Sets the value of the component T on entity without triggering a world version update....
Definition world.h:5168
GAIA_NODISCARD bool is_exclusive_dont_fragment_relation(Entity relation) const
Returns whether relation is both Exclusive and DontFragment. Such relations are stored in the adjunct...
Definition world.h:1002
GAIA_NODISCARD bool enabled(const EntityContainer &ec) const
Checks if an entity is enabled.
Definition world.h:7325
void add(Entity entity, U &&value)
Attaches a new component T to entity. Also sets its value.
Definition world.h:3439
void invalidate_sorted_queries_for_entity(Entity entity)
Invalidates cached sorted queries whose row ordering depends on entity.
Definition world.h:11227
GAIA_NODISCARD bool is(Entity entity, Entity entityBase) const
Checks if entity inherits from entityBase.
Definition world.h:4949
void sources(Entity relation, Entity target, Func func) const
Returns relationship sources for the relation and target.
Definition world.h:6608
void del_sparse_component_store(Entity component)
Deletes the sparse out-of-line component store associated with component.
Definition world.h:1284
GAIA_NODISCARD bool for_each_direct_term_entity_direct(Entity term, void *ctx, bool(*func)(void *, Entity)) const
Visits entities directly matching term without semantic Is expansion.
Definition world.h:7059
void modify(Entity entity)
Marks the component T as modified. Best used with acc_mut().sset() or set() to manually trigger an up...
Definition world.h:5017
void add(Entity entity)
Attaches a new component T to entity.
Definition world.h:3375
GAIA_NODISCARD EntityContainer & fetch(Entity entity)
Returns the internal record for entity.
Definition world.h:938
void del(Entity entity)
Removes an entity along with all data associated with it.
Definition world.h:4864
GAIA_NODISCARD const ComponentCacheItem & add(const ComponentCacheItem::ComponentCacheItemCtx &item, EntityKind kind=EntityKind::EK_Gen)
Creates a new runtime component if not found already.
Definition world.h:3329
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:5769
void del(Entity entity)
Removes a component T from entity.
Definition world.h:4921
void remove_src_entity_version(Entity entity)
Removes sparse source-version state for an entity that is being destroyed.
Definition world.h:7454
GAIA_NODISCARD ExclusiveAdjunctStore & exclusive_adjunct_store_mut(Entity relation)
Returns the exclusive adjunct store for relation, creating it if needed.
Definition world.h:1308
GAIA_NODISCARD bool locked() const
Checks if the chunk is locked for structural changes.
Definition world.h:7839
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:6100
GAIA_NODISCARD bool is_hierarchy_relation(Entity relation) const
Returns true for hierarchy-like relations whose targets form an exclusive traversable parent chain....
Definition world.h:1013
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:6199
friend uint32_t world_entity_archetype_version(const World &world, Entity entity)
Returns the per-entity archetype version used for targeted source-query freshness checks.
GAIA_NODISCARD ComponentCursor cursor_mut(Entity entity, Entity component)
Creates a mutable cursor over a directly owned chunk-backed AoS runtime component on entity....
Definition world.h:11357
void invalidate_queries_for_entity(Pair is_pair)
Invalidates semantic Is queries affected by removing or changing is_pair.
Definition world.h:11238
GAIA_NODISCARD const ComponentCacheItem & reg_comp()
Returns the registered component cache item for T, auto-registering it when enabled.
Definition world.h:3239
GAIA_NODISCARD ComponentRawView get_raw(Entity entity, Entity component) const
Returns raw read-only bytes for a chunk-backed AoS component on entity. Inherited ids resolve to the ...
Definition world.h:5220
void invalidate_queries_for_rel(Entity relation)
Invalidates cached queries whose dynamic result depends on relation.
Definition world.h:11221
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:4729
GAIA_NODISCARD Entity get(EntityId id) const
Returns the entity located at the index id.
Definition world.h:3212
GAIA_NODISCARD const Sched & sched() const
Returns the resolved scheduler used by this world.
Definition world.h:929
void update()
Runs systems and then finishes the current frame.
Definition world.h:7519
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:5830
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:5752
void add(Entity entity, Entity object, T &&value)
Attaches object to entity. Also sets its value.
Definition world.h:3398
ComponentGetter acc(Entity entity) const
Starts a bulk get operation on an entity.
Definition world.h:5365
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:6339
GAIA_NODISCARD bool is_out_of_line_component(Entity component) const
Returns whether component stores instance data out of line instead of in archetype chunks....
Definition world.h:1051
GAIA_NODISCARD bool enabled(Entity entity) const
Checks if an entity is enabled.
Definition world.h:7338
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:6137
GAIA_NODISCARD bool supports_depth_order(Entity relation) const
Returns true when the relation can drive cached depth-ordered iteration. This requires a fragmenting ...
Definition world.h:1036
void parent(Entity entity, Entity parentEntity)
Shortcut for add(entity, Pair(Parent, parent))
Definition world.h:4990
void diag_components() const
Performs diagnostics on registered components. Prints basic info about them and reports and detected ...
Definition world.h:7599
friend uint32_t world_rel_version(const World &world, Entity relation)
Returns the current version of relation-specific traversal metadata.
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:7033
void targets_if(Entity entity, Entity relation, Func func) const
Returns the relationship targets for the relation entity on entity.
Definition world.h:6550
GAIA_NODISCARD bool valid(Entity entity) const
Checks if entity is valid.
Definition world.h:3201
CommandBufferST & cmd_buffer_st() const
Returns the single-threaded deferred command buffer owned by the world.
Definition world.h:7226
void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count)
Instantiates count copies of a prefab as normal entities parented under parentInstance.
Definition world.h:4761
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity is currently used by the world.
Definition world.h:5431
GAIA_NODISCARD bool has_exclusive_adjunct_target_cond(Entity target, Pair cond) const
Checks whether any non-fragmenting exclusive relation targeting target uses the given OnDeleteTarget ...
Definition world.h:1551
GAIA_NODISCARD bool is_dont_fragment_relation(Entity relation) const
Returns whether relation is a valid non-fragmenting relation entity.
Definition world.h:994
bool path(Entity component, const char *path, uint32_t len=0)
Assigns a scoped path name to a component entity.
Definition world.h:2858
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:6296
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:5077
GAIA_NODISCARD Entity copy(Entity srcEntity)
Creates a new entity by cloning an already existing one. Does not trigger observers.
Definition world.h:3545
void diag() const
Performs all diagnostics.
Definition world.h:7628
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:7384
GAIA_NODISCARD bool has(Entity entity, Pair pair) const
Checks if entity contains pair.
Definition world.h:5698
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:5648
GAIA_NODISCARD SparseComponentStore< T > * sparse_component_store(Entity component)
Returns the sparse out-of-line component store for component, or nullptr if it does not exist.
Definition world.h:1211
GAIA_NODISCARD Entity add(EntityKind kind=EntityKind::EK_Gen)
Creates a new empty entity.
Definition world.h:3260
GAIA_NODISCARD bool has(Pair pair) const
Checks if pair is currently used by the world.
Definition world.h:5486
void save()
Saves contents of the world to a buffer. The buffer is reset, not appended. NOTE: In order for custom...
Definition world.h:8013
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:5584
GAIA_NODISCARD decltype(auto) get(Entity entity) const
Returns the value stored in the component T on entity.
Definition world.h:5380
void add(Entity entity, Pair pair)
Attaches a relationship pair to entity.
Definition world.h:3365
GAIA_NODISCARD uint32_t count_direct_term_entities(Entity term) const
Counts entities directly matching term, including semantic Is inheritance expansion.
Definition world.h:7019
void add_n(uint32_t count, Func func=func_void_with_entity)
Creates count new empty entities.
Definition world.h:3277
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:7435
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:7398
void set_component_dont_fragment(Entity component, EntityContainer &ec)
Latches DontFragment on a component entity record. This first moves the payload out of chunks,...
Definition world.h:1133
GAIA_NODISCARD bool for_each_direct_term_entity(Entity term, void *ctx, bool(*func)(void *, Entity)) const
Visits entities directly matching term, including semantic Is expansion.
Definition world.h:7049
void del(Entity entity, Entity object)
Removes an object from entity if possible.
Definition world.h:4878
GAIA_NODISCARD bool 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:1043
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:5504
void diag_entities() const
Performs diagnostics on entities of the world. Also performs validation of internal structures which ...
Definition world.h:7605
GAIA_NODISCARD bool is_dont_fragment(Entity entity) const
Returns whether entity is marked DontFragment.
Definition world.h:987
GAIA_NODISCARD util::str_view path(Entity component) const
Returns the scoped path name for a component entity.
Definition world.h:2848
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:5202
void reset_sched()
Resets the world back to the default Gaia scheduler.
Definition world.h:923
void instantiate_n(Entity prefabEntity, uint32_t count, Func func=func_void_with_entity)
Instantiates count copies of a prefab as normal entities. The instance copies the prefab's direct dat...
Definition world.h:4756
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:5809
GAIA_NODISCARD Entity path(const char *path, uint32_t len=0) const
Finds a component entity by its exact scoped path.
Definition world.h:2837
GAIA_NODISCARD const ExclusiveAdjunctStore * exclusive_adjunct_store(Entity relation) const
Returns the exclusive adjunct store for relation, or nullptr when absent.
Definition world.h:1297
void frame_cleanup()
Performs deferred cleanup for the current frame.
Definition world.h:7487
void diag_archetypes() const
Performs diagnostics on archetypes. Prints basic info about them and the chunks they contain.
Definition world.h:7591
GAIA_NODISCARD bool child(Entity entity, Entity parent) const
Checks whether entity has a ChildOf relationship to parent.
Definition world.h:4985
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:4959
void children_if(Entity parent, Func func) const
Visits direct children in the ChildOf hierarchy until func returns false.
Definition world.h:7166
GAIA_NODISCARD bool has_direct(Entity entity, Pair pair) const
Checks if entity directly contains pair, without semantic inheritance expansion.
Definition world.h:5509
void relations_if(Entity entity, Entity target, Func func) const
Returns the relationship relations for the target entity on entity.
Definition world.h:6060
void del_sparse_components(Entity entity)
Removes all sparse out-of-line component instances owned by entity.
Definition world.h:1275
void copy_n(Entity entity, uint32_t count, Func func=func_void_with_entity)
Creates count new entities by cloning an already existing one.
Definition world.h:3585
GAIA_NODISCARD util::str_view name(Entity entity) const
Returns the name assigned to entity.
Definition world.h:5777
void sources_if(Entity relation, Entity target, Func func) const
Returns relationship sources for the relation and target.
Definition world.h:6665
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:5135
GAIA_NODISCARD bool is_fragmenting_relation(Entity relation) const
Returns true when the relation still participates in archetype identity. Non-fragmenting relations su...
Definition world.h:1022
bool as_targets_trav_if(Entity relation, Func func) const
Traverses transitive Is targets of relation until func returns true.
Definition world.h:7209
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:7193
GAIA_NODISCARD uint32_t rel_version(Entity relation) const
Returns structural version for a given relation. Increments whenever any Pair(relation,...
Definition world.h:7427
Query query()
Provides a cached query set up to work with the parent world. Cached queries use local scope by defau...
Definition world.h:885
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:8058
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:6417
void teardown()
Performs world shutdown maintenance without running systems or observers. Runtime callbacks are shut ...
Definition world.h:7528
void del(Entity entity, Pair pair)
Removes an existing entity relationship pair.
Definition world.h:4911
static GAIA_NODISCARD bool is_req_del(const EntityContainer &ec)
Returns whether the record is already in delete-requested state. Covers both explicit per-entity dele...
Definition world.h:977
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:11263
GAIA_NODISCARD Entity scope() const
Returns the current component scope used for component registration and relative component lookup.
Definition world.h:5605
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:5155
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:5182
GAIA_NODISCARD Chunk * get_chunk(Entity entity) const
Returns a chunk containing the entity.
Definition world.h:7373
GAIA_NODISCARD SparseComponentStore< T > & sparse_component_store_mut(Entity component)
Returns the sparse out-of-line component store for component, creating it if needed.
Definition world.h:1237
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:7113
GAIA_NODISCARD bool parent(Entity entity, Entity parentEntity) const
Checks whether entity has a non-fragmenting Parent relationship to parentEntity.
Definition world.h:4998
GAIA_NODISCARD bool can_use_out_of_line_component(Entity object) const
Returns whether object is a usable out-of-line storage target for component type T.
Definition world.h:1189
GAIA_NODISCARD ComponentRawMutView mut_raw(Entity entity, Entity component)
Returns raw mutable bytes for a directly owned chunk-backed AoS component on entity....
Definition world.h:5250
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:5865
GAIA_NODISCARD util::str_view alias(Entity entity) const
Returns the alias assigned to an entity.
Definition world.h:2880
void targets(Entity entity, Entity relation, Func func) const
Returns the relationship targets for the relation entity on entity.
Definition world.h:6507
GAIA_NODISCARD bool has(Entity entity, Entity object) const
Checks if entity contains the entity object.
Definition world.h:5496
void set_serializer(TSerializer &serializer)
Binds a concrete serializer object through ser::make_serializer().
Definition world.h:1581
GAIA_NODISCARD uint32_t size() const
Returns the number of active entities.
Definition world.h:7393
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity contains the component T.
Definition world.h:5709
void set_serializer(std::nullptr_t)
Resets runtime serializer binding to the default internal bin_stream backend.
Definition world.h:1566
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:7040
GAIA_NODISCARD Entity instantiate(Entity prefabEntity)
Instantiates a prefab as a normal entity. The instance copies the prefab's direct data,...
Definition world.h:4715
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:2936
bool set_raw(Entity entity, Entity component, const void *data, uint32_t size)
Replaces raw bytes for a directly owned chunk-backed AoS component and finishes the write.
Definition world.h:5331
GAIA_NODISCARD ComponentSetter acc_mut(Entity entity)
Starts a bulk set operation on entity.
Definition world.h:5117
void add_n(Entity entity, uint32_t count, Func func=func_void_with_entity)
Creates count of entities of the same archetype as entity.
Definition world.h:3287
void modify_raw(Entity entity, Entity component)
Marks a raw payload returned by mut_raw() as modified and emits normal set side effects.
Definition world.h:5352
void frame_end()
Marks the end of the current frame.
Definition world.h:7504
GAIA_NODISCARD Entity relation(Entity entity, Entity target) const
Returns the first relationship relation for the target entity on entity.
Definition world.h:5977
GAIA_NODISCARD Entity alias(const char *alias, uint32_t len=0) const
Finds an entity by its exact alias.
Definition world.h:2867
QuerySerBuffer & query_buffer(QueryId &serId)
Returns the temporary serialization buffer used while building a query. A fresh query id is allocated...
Definition world.h:11170
void cleanup()
Clears the world so that all its entities and components are released.
Definition world.h:7568
GAIA_NODISCARD Entity target(Entity entity, Entity relation) const
Returns the first relationship target for the relation entity on entity.
Definition world.h:6463
GAIA_NODISCARD const SparseComponentStore< T > * sparse_component_store(Entity component) const
Returns the sparse out-of-line component store for component, or nullptr if it does not exist.
Definition world.h:1224
GAIA_NODISCARD ComponentCache & comp_cache_mut()
Returns mutable access to the world component cache.
Definition world.h:2801
void relations(Entity entity, Entity target, Func func) const
Returns the relationship relations for the target entity on entity.
Definition world.h:6018
void query_buffer_reset(QueryId &serId)
Releases the temporary serialization buffer associated with serId.
Definition world.h:11204
void invalidate_sorted_queries()
Invalidates all cached sorted queries after row-order changes.
Definition world.h:11232
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:7347
bool load(TSerializer &inputSerializer)
Loads a world state from a serializer-compatible stream wrapper.
Definition world.h:8360
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:7845
GAIA_NODISCARD bool is_fragmenting_hierarchy_relation(Entity relation) const
Returns true for hierarchy relations that still fragment archetypes. ChildOf satisfies this today,...
Definition world.h:1028
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:6432
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:3223
CommandBufferMT & cmd_buffer_mt() const
Returns the multi-thread-safe deferred command buffer owned by the world.
Definition world.h:7232
void finish_out_of_line_add_inter(Entity entity, Entity object, OutOfLineMode mode)
Finishes adding an out-of-line component after its sparse payload has been created....
Definition world.h:1253
Buffer for deferred execution of some operations on entities.
Definition command_buffer.h:45
Definition query.h:500
Wrapper for two Entities forming a relationship pair.
Definition id.h:529
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:224
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:157
Lightweight JSON serializer/deserializer.
Definition ser_json.h:101
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
constexpr uint32_t BadIndex
Sentinel index value returned by helpers when a lookup fails.
Definition utility.h:20
Definition sparse_storage.h:31
Definition hashing_string.h:12
Component item registration context.
Definition component_cache_item.h:51
util::str_view name
Registered component symbol.
Definition component_cache_item.h:53
Definition component_cache_item.h:26
Entity entity
Component entity.
Definition component_cache_item.h:84
Component comp
Unique component identifier.
Definition component_cache_item.h:86
SymbolLookupKey name
Component name.
Definition component_cache_item.h:93
Stack-only cursor over raw component bytes and runtime field metadata.
Definition component_cursor.h:81
static GAIA_NODISCARD ComponentCursor from_raw(const ComponentCache &components, Entity component, ComponentRawView view)
Creates a read-only cursor from a raw component view.
Definition component_cursor.h:94
Entity-scoped component accessor bound to a specific world, chunk and row. It is not a standalone chu...
Definition component_getter.h:15
GAIA_NODISCARD decltype(auto) get() const
Returns the value stored in the component T on entity.
Definition component_getter.h:28
Non-owning mutable view over raw component bytes on an entity.
Definition component_cursor.h:58
Non-owning read-only view over raw component bytes on an entity.
Definition component_cursor.h:34
Entity-scoped mutable component accessor bound to a specific world, chunk and row....
Definition component_setter.h:14
decltype(auto) mut()
Returns a mutable reference to component without triggering hooks, observers or world-version updates...
Definition component_setter.h:22
ComponentSetter & set(U &&value)
Sets the value of the component.
Definition component_setter.h:34
decltype(auto) smut()
Returns a mutable reference to component without triggering a world version update.
Definition component_setter.h:64
ComponentSetter & sset(U &&value)
Sets the value of the component without triggering a world version update.
Definition component_setter.h:76
Definition id.h:35
uint32_t dis
Disabled Entity does not use this bit (always zero) so we steal it for special purposes....
Definition entity_container.h:83
Definition entity_container.h:60
uint16_t row
Row at which the entity is stored in the chunk.
Definition entity_container.h:94
uint16_t flags
Flags.
Definition entity_container.h:96
Archetype * pArchetype
Archetype (stable address)
Definition entity_container.h:112
Chunk * pChunk
Chunk the entity currently resides in (stable address)
Definition entity_container.h:114
Component used to describe the entity name.
Definition id.h:516
Hashmap lookup structure used for Entity.
Definition id.h:468
Definition id.h:247
Temporary VM matching buffer meant to be owned by an ECS World. QueryInfo only acquires a frame while...
Definition query_info.h:41
Scheduler descriptor used by ECS runtime code. All callbacks may be null when the descriptor is only ...
Definition sched.h:71
Definition world.h:1593
EntityBuilder & as(Entity entityBase)
Shortcut for add(Pair(Is, entityBase)). Effectively makes an entity inherit from entityBase.
Definition world.h:1914
void commit()
Commits all gathered changes and performs an archetype movement.
Definition world.h:1663
void del_name()
Removes any name associated with the entity.
Definition world.h:1824
EntityBuilder & child(Entity parent)
Shortcut for add(Pair(ChildOf, parent))
Definition world.h:1932
Archetype * m_pArchetype
Target archetype we want to move to.
Definition world.h:1604
IdMode
Classifies how a builder operation treats an id.
Definition world.h:1619
GAIA_NODISCARD bool as(Entity entity, Entity entityBase) const
Check if entity inherits from entityBase.
Definition world.h:1927
EntityBuilder & add(Pair pair)
Prepares an archetype movement by following the "add" edge of the current archetype.
Definition world.h:1901
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:1803
EntityBuilder & add(Entity entity)
Prepares an archetype movement by following the "add" edge of the current archetype.
Definition world.h:1890
EntityBuilder & del(Pair pair)
Prepares an archetype movement by following the "del" edge of the current archetype.
Definition world.h:1970
Entity register_component()
Takes care of registering the component type used by T.
Definition world.h:1939
EntityBuilder & del(Entity entity)
Prepares an archetype movement by following the "del" edge of the current archetype.
Definition world.h:1960
Entity m_entity
Source entity.
Definition world.h:1610
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:1811
EntityNameLookupKey m_targetAliasKey
Target alias string pointer.
Definition world.h:1608
EntityBuilder & prefab()
Marks the entity as a prefab.
Definition world.h:1919
EntityNameLookupKey m_targetNameKey
Target name.
Definition world.h:1606
void del_alias()
Removes any alias associated with the entity.
Definition world.h:1857
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:1788
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:1819
Scheduling key for one explicit phase entity.
Definition world.h:112
Entity phase
Phase entity.
Definition world.h:114
uint32_t depth
Depth of phase in the phase DependsOn graph.
Definition world.h:116
uint32_t order
Deterministic child-before-target order of phase.
Definition world.h:118
Direct child-before-target scheduling edge between collected systems.
Definition world.h:122
uint32_t child
Item index that must run first.
Definition world.h:124
uint32_t next
Next edge index in the same child adjacency list.
Definition world.h:128
uint32_t target
Item index that must run after child.
Definition world.h:126
Scheduling key for one enabled system entity.
Definition world.h:94
uint32_t systemDepth
Depth of the system in the DependsOn graph, excluding the phase marker target.
Definition world.h:102
uint32_t systemOrder
Deterministic child-before-target order of entity inside its scheduling group.
Definition world.h:106
Entity entity
System entity to run.
Definition world.h:96
uint32_t phaseDepth
Depth of phase in the phase DependsOn graph.
Definition world.h:100
uint32_t phaseOrder
Deterministic child-before-target order of phase.
Definition world.h:104
bool hasPhase
True when phase is valid.
Definition world.h:108
Entity phase
Phase entity assigned with SystemBuilder::phase(), or EntityBad for unphased systems.
Definition world.h:98
Reusable scratch arrays for one system schedule ordering pass.
Definition world.h:132
cnt::darray< uint32_t > sortedPhases
Phase indices sorted by entity id.
Definition world.h:154
cnt::darray< uint32_t > nextSiblings
Next sibling index per child item.
Definition world.h:152
cnt::darray< SystemScheduleItem > items
Collected systems for the current run.
Definition world.h:134
cnt::darray< uint32_t > primaryTargets
Primary dependency target per item.
Definition world.h:148
cnt::darray< uint32_t > readyNext
Next item in the ready list.
Definition world.h:166
cnt::darray< uint32_t > childCounts
Direct child count per target item.
Definition world.h:160
cnt::darray< uint32_t > groupIndices
Reused group item indices.
Definition world.h:158
cnt::darray< Entity > entityStack
Shared entity traversal stack.
Definition world.h:142
cnt::darray< SystemScheduleItem > ordered
Ordered item output used when explicit edges exist.
Definition world.h:140
cnt::darray< uint32_t > entityIndices
Item indices sorted by entity id.
Definition world.h:144
cnt::darray< uint32_t > sortedIndices
Item indices sorted by final deterministic key.
Definition world.h:162
cnt::darray< uint8_t > visited
Visit states for the final topological pass.
Definition world.h:170
cnt::darray< uint32_t > firstEdges
First edge index per child item.
Definition world.h:164
cnt::darray< uint32_t > sortedGroupIndices
Item indices sorted inside the active group.
Definition world.h:146
cnt::darray< uint32_t > firstChildren
First child index per target item.
Definition world.h:150
cnt::darray< SystemScheduleEdge > edges
Explicit dependency edges for the current run.
Definition world.h:138
cnt::darray< uint32_t > primaryPhases
Primary dependency target per phase.
Definition world.h:156
cnt::darray< SystemPhaseScheduleItem > phases
Collected unique phases for the current run.
Definition world.h:136
cnt::darray< uint8_t > states
Visit states reused by group and phase traversal.
Definition world.h:168
Definition id.h:239
Definition ser_json.h:57
Runtime serializer type-erased handle. Traversal logic is shared with compile-time serialization,...
Definition ser_rt.h:79
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:41
GAIA_NODISCARD constexpr bool empty() const
Checks whether the view contains no characters.
Definition str.h:47
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:331
void append(const char *data, uint32_t size)
Appends size characters from data.
Definition str.h:389
void clear()
Removes all characters from the string.
Definition str.h:352
GAIA_NODISCARD bool empty() const
Checks whether the string contains no characters.
Definition str.h:437
GAIA_NODISCARD str_view view() const
Returns a non-owning view over the current contents.
Definition str.h:443
void reserve(uint32_t len)
Reserves capacity for at least len characters.
Definition str.h:358
Definition robin_hood.h:418