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_getter.h"
36#include "gaia/ecs/component_setter.h"
37#include "gaia/ecs/entity_container.h"
38#include "gaia/ecs/id.h"
39#include "gaia/ecs/observer.h"
40#include "gaia/ecs/query.h"
41#include "gaia/ecs/query_cache.h"
42#include "gaia/ecs/query_common.h"
43#include "gaia/ecs/query_info.h"
44#include "gaia/mem/mem_alloc.h"
45#include "gaia/ser/ser_binary.h"
46#include "gaia/ser/ser_json.h"
47#include "gaia/ser/ser_rt.h"
48#include "gaia/util/logging.h"
49#include "gaia/util/str.h"
50
51namespace gaia {
52 namespace ecs {
53 template <typename T>
54 struct SparseComponentRecord;
55 }
56
57 namespace cnt {
58 template <typename T>
59 struct to_sparse_id<ecs::SparseComponentRecord<T>> {
60 static sparse_id get(const ecs::SparseComponentRecord<T>& item) noexcept {
61 return (sparse_id)item.entity.id();
62 }
63 };
64 } // namespace cnt
65
66 namespace ecs {
67#if GAIA_SYSTEMS_ENABLED
68 class SystemBuilder;
69#endif
70#if GAIA_OBSERVERS_ENABLED
71 class ObserverBuilder;
72#endif
73 class World;
74
75 void world_notify_on_set_entity(World& world, Entity term, Entity entity);
76 void world_finish_write(World& world, Entity term, Entity entity);
77 template <typename T>
78 decltype(auto) world_direct_entity_arg_raw(World& world, Entity entity);
79 template <typename T>
80 decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id);
81
82 template <typename T>
84 Entity entity;
85 T value{};
86 };
87
88 class GAIA_API World final {
89 public:
91 inline static bool s_enableUniqueNameDuplicateAssert = true;
92
93 private:
94 friend CommandBufferST;
95 friend CommandBufferMT;
96 friend struct ComponentGetter;
97 friend struct ComponentSetter;
98 friend void lock(World&);
99 friend void unlock(World&);
100 friend QueryMatchScratch& query_match_scratch_acquire(World&);
101 friend void query_match_scratch_release(World&, bool);
102 friend uint32_t world_component_index_bucket_size(const World&, Entity);
103 friend uint32_t world_component_index_comp_idx(const World&, const Archetype&, Entity);
104 friend uint32_t world_component_index_match_count(const World&, const Archetype&, Entity);
105 template <typename T>
106 friend decltype(auto) world_direct_entity_arg(World& world, Entity entity);
107 template <typename T>
108 friend decltype(auto) world_direct_entity_arg_raw(World& world, Entity entity);
109 template <typename T>
110 friend decltype(auto) world_query_entity_arg_by_id(World& world, Entity entity, Entity id);
111 template <typename T>
112 friend decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id);
113 friend void world_finish_write(World& world, Entity term, Entity entity);
114
115 ser::bin_stream m_stream;
116 ser::serializer m_serializer{};
117
118 using TFunc_Void_With_Entity = void(Entity);
119 static void func_void_with_entity([[maybe_unused]] Entity entity) {}
120
121 using EntityNameLookupKey = core::StringLookupKey<256>;
123 using EntityArrayMap = cnt::map<EntityLookupKey, cnt::darray<Entity>>;
124
125 struct ExclusiveAdjunctStore {
127 cnt::darray<Entity> srcToTgt;
129 cnt::darray<uint32_t> srcToTgtIdx;
131 uint32_t srcToTgtCnt = 0;
134 };
135
136 struct SparseComponentStoreErased {
137 void* pStore = nullptr;
138 void (*func_del)(void*, Entity) = nullptr;
139 bool (*func_has)(const void*, Entity) = nullptr;
140 bool (*func_copy_entity)(void*, Entity, Entity) = nullptr;
141 uint32_t (*func_count)(const void*) = nullptr;
142 void (*func_collect_entities)(const void*, cnt::darray<Entity>&) = nullptr;
143 bool (*func_for_each_entity)(const void*, void*, bool (*)(void*, Entity)) = nullptr;
144 void (*func_clear_store)(void*) = nullptr;
145 void (*func_del_store)(void*) = nullptr;
146 };
147
148 template <typename T>
149 struct SparseComponentStore final {
150 cnt::sparse_storage<SparseComponentRecord<T>> data;
151
152 static cnt::sparse_id sid(Entity entity) {
153 return (cnt::sparse_id)entity.id();
154 }
155
156 T& add(Entity entity) {
157 const auto sparseId = sid(entity);
158 if (data.has(sparseId))
159 return data[sparseId].value;
160
161 auto& item = data.add(SparseComponentRecord<T>{entity});
162 return item.value;
163 }
164
165 T& mut(Entity entity) {
166 GAIA_ASSERT(data.has(sid(entity)));
167 return data[sid(entity)].value;
168 }
169
170 const T& get(Entity entity) const {
171 GAIA_ASSERT(data.has(sid(entity)));
172 return data[sid(entity)].value;
173 }
174
175 void del_entity(Entity entity) {
176 const auto sparseId = sid(entity);
177 if (!data.empty() && data.has(sparseId))
178 data.del(sparseId);
179 }
180
181 bool has(Entity entity) const {
182 if (data.empty())
183 return false;
184 return data.has(sid(entity));
185 }
186
187 uint32_t count() const {
188 return (uint32_t)data.size();
189 }
190
191 void collect_entities(cnt::darray<Entity>& out) const {
192 out.reserve(out.size() + (uint32_t)data.size());
193 for (const auto& item: data)
194 out.push_back(item.entity);
195 }
196
197 void clear_store() {
198 data.clear();
199 }
200 };
201
202 template <typename T>
203 static SparseComponentStoreErased make_sparse_component_store_erased(SparseComponentStore<T>* pStore) {
204 SparseComponentStoreErased store{};
205 store.pStore = pStore;
206 store.func_del = [](void* pStoreRaw, Entity entity) {
207 static_cast<SparseComponentStore<T>*>(pStoreRaw)->del_entity(entity);
208 };
209 store.func_has = [](const void* pStoreRaw, Entity entity) {
210 return static_cast<const SparseComponentStore<T>*>(pStoreRaw)->has(entity);
211 };
212 store.func_copy_entity = [](void* pStoreRaw, Entity dstEntity, Entity srcEntity) {
213 auto* pStore = static_cast<SparseComponentStore<T>*>(pStoreRaw);
214 if (!pStore->has(srcEntity))
215 return false;
216
217 auto& dst = pStore->add(dstEntity);
218 dst = pStore->get(srcEntity);
219 return true;
220 };
221 store.func_count = [](const void* pStoreRaw) {
222 return static_cast<const SparseComponentStore<T>*>(pStoreRaw)->count();
223 };
224 store.func_collect_entities = [](const void* pStoreRaw, cnt::darray<Entity>& out) {
225 static_cast<const SparseComponentStore<T>*>(pStoreRaw)->collect_entities(out);
226 };
227 store.func_for_each_entity = [](const void* pStoreRaw, void* pCtx, bool (*func)(void*, Entity)) {
228 for (const auto& item: static_cast<const SparseComponentStore<T>*>(pStoreRaw)->data) {
229 if (!func(pCtx, item.entity))
230 return false;
231 }
232 return true;
233 };
234 store.func_clear_store = [](void* pStoreRaw) {
235 static_cast<SparseComponentStore<T>*>(pStoreRaw)->clear_store();
236 };
237 store.func_del_store = [](void* pStoreRaw) {
238 delete static_cast<SparseComponentStore<T>*>(pStoreRaw);
239 };
240 return store;
241 }
242
243 template <
244 typename TApi, typename TValue, bool DeriveFromValue = std::is_class_v<TValue> && !std::is_final_v<TValue>>
245 class SetWriteProxyTyped;
246
247 template <typename TApi, typename TValue>
248 class SetWriteProxyTyped<TApi, TValue, true>: public TValue {
249 World* m_pWorld = nullptr;
250 Entity m_entity = EntityBad;
251 Entity m_term = EntityBad;
252
253 void commit() {
254 if (m_pWorld == nullptr)
255 return;
256
257 m_pWorld->template write_back_set_typed<TApi, TValue>(m_entity, m_term, static_cast<const TValue&>(*this));
258 m_pWorld = nullptr;
259 }
260
261 public:
262 SetWriteProxyTyped(World& world, Entity entity, Entity term, const TValue& value):
263 TValue(value), m_pWorld(&world), m_entity(entity), m_term(term) {}
264
265 SetWriteProxyTyped(World& world, Entity entity, Entity term, TValue&& value):
266 TValue(GAIA_MOV(value)), m_pWorld(&world), m_entity(entity), m_term(term) {}
267
268 SetWriteProxyTyped(const SetWriteProxyTyped&) = delete;
269 SetWriteProxyTyped& operator=(const SetWriteProxyTyped&) = delete;
270
271 SetWriteProxyTyped(SetWriteProxyTyped&& other) noexcept:
272 TValue(static_cast<TValue&&>(other)), m_pWorld(other.m_pWorld), m_entity(other.m_entity),
273 m_term(other.m_term) {
274 other.m_pWorld = nullptr;
275 }
276
277 ~SetWriteProxyTyped() {
278 commit();
279 }
280
281 SetWriteProxyTyped& operator=(const TValue& value) {
282 static_cast<TValue&>(*this) = value;
283 return *this;
284 }
285
286 SetWriteProxyTyped& operator=(TValue&& value) {
287 static_cast<TValue&>(*this) = GAIA_MOV(value);
288 return *this;
289 }
290
291 GAIA_NODISCARD operator TValue&() {
292 return *this;
293 }
294
295 GAIA_NODISCARD operator const TValue&() const {
296 return *this;
297 }
298 };
299
300 template <typename TApi, typename TValue>
301 class SetWriteProxyTyped<TApi, TValue, false> {
302 World* m_pWorld = nullptr;
303 Entity m_entity = EntityBad;
304 Entity m_term = EntityBad;
305 TValue m_value{};
306
307 void commit() {
308 if (m_pWorld == nullptr)
309 return;
310
311 m_pWorld->template write_back_set_typed<TApi, TValue>(m_entity, m_term, m_value);
312 m_pWorld = nullptr;
313 }
314
315 public:
316 SetWriteProxyTyped(World& world, Entity entity, Entity term, const TValue& value):
317 m_pWorld(&world), m_entity(entity), m_term(term), m_value(value) {}
318
319 SetWriteProxyTyped(World& world, Entity entity, Entity term, TValue&& value):
320 m_pWorld(&world), m_entity(entity), m_term(term), m_value(GAIA_MOV(value)) {}
321
322 SetWriteProxyTyped(const SetWriteProxyTyped&) = delete;
323 SetWriteProxyTyped& operator=(const SetWriteProxyTyped&) = delete;
324
325 SetWriteProxyTyped(SetWriteProxyTyped&& other) noexcept:
326 m_pWorld(other.m_pWorld), m_entity(other.m_entity), m_term(other.m_term), m_value(GAIA_MOV(other.m_value)) {
327 other.m_pWorld = nullptr;
328 }
329
330 ~SetWriteProxyTyped() {
331 commit();
332 }
333
334 SetWriteProxyTyped& operator=(const TValue& value) {
335 m_value = value;
336 return *this;
337 }
338
339 SetWriteProxyTyped& operator=(TValue&& value) {
340 m_value = GAIA_MOV(value);
341 return *this;
342 }
343
344 GAIA_NODISCARD operator TValue&() {
345 return m_value;
346 }
347
348 GAIA_NODISCARD operator const TValue&() const {
349 return m_value;
350 }
351
352 GAIA_NODISCARD TValue* operator->() {
353 return &m_value;
354 }
355
356 GAIA_NODISCARD const TValue* operator->() const {
357 return &m_value;
358 }
359 };
360
361 template <typename TValue, bool DeriveFromValue = std::is_class_v<TValue> && !std::is_final_v<TValue>>
362 class SetWriteProxyObject;
363
364 template <typename TValue>
365 class SetWriteProxyObject<TValue, true>: public TValue {
366 World* m_pWorld = nullptr;
367 Entity m_entity = EntityBad;
368 Entity m_term = EntityBad;
369
370 void commit() {
371 if (m_pWorld == nullptr)
372 return;
373
374 m_pWorld->template write_back_set_object<TValue>(m_entity, m_term, static_cast<const TValue&>(*this));
375 m_pWorld = nullptr;
376 }
377
378 public:
379 SetWriteProxyObject(World& world, Entity entity, Entity term, const TValue& value):
380 TValue(value), m_pWorld(&world), m_entity(entity), m_term(term) {}
381
382 SetWriteProxyObject(World& world, Entity entity, Entity term, TValue&& value):
383 TValue(GAIA_MOV(value)), m_pWorld(&world), m_entity(entity), m_term(term) {}
384
385 SetWriteProxyObject(const SetWriteProxyObject&) = delete;
386 SetWriteProxyObject& operator=(const SetWriteProxyObject&) = delete;
387
388 SetWriteProxyObject(SetWriteProxyObject&& other) noexcept:
389 TValue(static_cast<TValue&&>(other)), m_pWorld(other.m_pWorld), m_entity(other.m_entity),
390 m_term(other.m_term) {
391 other.m_pWorld = nullptr;
392 }
393
394 ~SetWriteProxyObject() {
395 commit();
396 }
397
398 SetWriteProxyObject& operator=(const TValue& value) {
399 static_cast<TValue&>(*this) = value;
400 return *this;
401 }
402
403 SetWriteProxyObject& operator=(TValue&& value) {
404 static_cast<TValue&>(*this) = GAIA_MOV(value);
405 return *this;
406 }
407
408 GAIA_NODISCARD operator TValue&() {
409 return *this;
410 }
411
412 GAIA_NODISCARD operator const TValue&() const {
413 return *this;
414 }
415 };
416
417 template <typename TValue>
418 class SetWriteProxyObject<TValue, false> {
419 World* m_pWorld = nullptr;
420 Entity m_entity = EntityBad;
421 Entity m_term = EntityBad;
422 TValue m_value{};
423
424 void commit() {
425 if (m_pWorld == nullptr)
426 return;
427
428 m_pWorld->template write_back_set_object<TValue>(m_entity, m_term, m_value);
429 m_pWorld = nullptr;
430 }
431
432 public:
433 SetWriteProxyObject(World& world, Entity entity, Entity term, const TValue& value):
434 m_pWorld(&world), m_entity(entity), m_term(term), m_value(value) {}
435
436 SetWriteProxyObject(World& world, Entity entity, Entity term, TValue&& value):
437 m_pWorld(&world), m_entity(entity), m_term(term), m_value(GAIA_MOV(value)) {}
438
439 SetWriteProxyObject(const SetWriteProxyObject&) = delete;
440 SetWriteProxyObject& operator=(const SetWriteProxyObject&) = delete;
441
442 SetWriteProxyObject(SetWriteProxyObject&& other) noexcept:
443 m_pWorld(other.m_pWorld), m_entity(other.m_entity), m_term(other.m_term), m_value(GAIA_MOV(other.m_value)) {
444 other.m_pWorld = nullptr;
445 }
446
447 ~SetWriteProxyObject() {
448 commit();
449 }
450
451 SetWriteProxyObject& operator=(const TValue& value) {
452 m_value = value;
453 return *this;
454 }
455
456 SetWriteProxyObject& operator=(TValue&& value) {
457 m_value = GAIA_MOV(value);
458 return *this;
459 }
460
461 GAIA_NODISCARD operator TValue&() {
462 return m_value;
463 }
464
465 GAIA_NODISCARD operator const TValue&() const {
466 return m_value;
467 }
468
469 GAIA_NODISCARD TValue* operator->() {
470 return &m_value;
471 }
472
473 GAIA_NODISCARD const TValue* operator->() const {
474 return &m_value;
475 }
476 };
477
478 template <typename T>
479 GAIA_NODISCARD decltype(auto) mut_im(Entity entity) {
480 static_assert(!is_pair<T>::value);
481 using FT = typename component_type_t<T>::TypeFull;
482 const auto& item = add<FT>();
483 if constexpr (supports_out_of_line_component<FT>()) {
484 if (is_out_of_line_component(item.entity))
485 return sparse_component_store_mut<FT>(item.entity).mut(entity);
486 }
487
488 const auto& ec = m_recs.entities[entity.id()];
489 if constexpr (entity_kind_v<T> == EntityKind::EK_Gen)
490 return ec.pChunk->template set<T>(ec.row);
491 else
492 return ec.pChunk->template set<T>();
493 }
494
495 template <typename T>
496 GAIA_NODISCARD decltype(auto) mut_im(Entity entity, Entity object) {
497 static_assert(!is_pair<T>::value);
498 using FT = typename component_type_t<T>::TypeFull;
499 if constexpr (supports_out_of_line_component<FT>()) {
500 if (can_use_out_of_line_component<FT>(object))
501 return sparse_component_store_mut<FT>(object).mut(entity);
502 }
503
504 const auto& ec = m_recs.entities[entity.id()];
505 return ec.pChunk->template set<T>(ec.row, object);
506 }
507
513 void finish_write(Entity entity, Entity term) {
514 if (tearing_down() || !valid(entity))
515 return;
516
517 if (is_out_of_line_component(term)) {
518 world_notify_on_set_entity(*this, term, entity);
519 return;
520 }
521
522 auto compIdx = uint32_t(BadIndex);
523 {
524 const auto& ec = fetch(entity);
525 compIdx = world_component_index_comp_idx(*this, *ec.pArchetype, term);
526 }
527
528 if (compIdx == BadIndex) {
529 (void) override(entity, term);
530 const auto& ec = fetch(entity);
531 compIdx = world_component_index_comp_idx(*this, *ec.pArchetype, term);
532 if (compIdx == BadIndex)
533 return;
534 }
535
536 const auto& ec = fetch(entity);
537 const auto row = uint16_t(ec.row * (1U - (uint32_t)term.kind()));
538 (void)ec.pChunk->comp_ptr_mut_gen<true>(compIdx, row);
539 world_notify_on_set_entity(*this, term, entity);
540 }
541
542 template <typename TApi, typename TValue>
543 void write_back_set_typed(Entity entity, Entity term, const TValue& value) {
544 using FT = typename component_type_t<TApi>::TypeFull;
545 ::gaia::ecs::update_version(m_worldVersion);
546
547 if constexpr (supports_out_of_line_component<FT>()) {
548 if (is_out_of_line_component(term)) {
549 sparse_component_store_mut<FT>(term).add(entity) = value;
550 finish_write(entity, term);
551 return;
552 }
553 }
554
555 const auto& ec = fetch(entity);
556 const auto row = uint16_t(ec.row * (1U - (uint32_t)term.kind()));
557 ComponentSetter{*this, ec.pChunk, entity, row}.sset<TApi>(value);
558 finish_write(entity, term);
559 }
560
561 template <typename TValue>
562 void write_back_set_object(Entity entity, Entity term, const TValue& value) {
563 using FT = typename component_type_t<TValue>::TypeFull;
564 ::gaia::ecs::update_version(m_worldVersion);
565 if constexpr (supports_out_of_line_component<FT>()) {
566 if (can_use_out_of_line_component<FT>(term)) {
567 sparse_component_store_mut<FT>(term).add(entity) = value;
568 finish_write(entity, term);
569 return;
570 }
571 }
572
573 const auto& ec = fetch(entity);
574 const auto row = uint16_t(ec.row * (1U - (uint32_t)term.kind()));
575 ComponentSetter{*this, ec.pChunk, entity, row}.template smut<TValue>(term) = value;
576 finish_write(entity, term);
577 }
578
579 //----------------------------------------------------------------------
580
581#if GAIA_OBSERVERS_ENABLED
582 class ObserverRegistry {
583 struct DiffObserverIndex {
585 cnt::map<EntityLookupKey, cnt::darray<Entity>> direct;
587 cnt::map<EntityLookupKey, cnt::darray<Entity>> sourceTerm;
589 cnt::map<EntityLookupKey, cnt::darray<Entity>> traversalRelation;
591 cnt::map<EntityLookupKey, cnt::darray<Entity>> pairRelation;
593 cnt::map<EntityLookupKey, cnt::darray<Entity>> pairTarget;
595 cnt::darray<Entity> all;
597 cnt::darray<Entity> global;
598 };
599
600 struct PropagatedTargetCacheKey {
601 Entity bindingRelation = EntityBad;
602 Entity traversalRelation = EntityBad;
603 Entity rootTarget = EntityBad;
604 QueryTravKind travKind = QueryTravKind::None;
605 uint8_t travDepth = QueryTermOptions::TravDepthUnlimited;
606
607 size_t hash() const {
608 size_t seed = EntityLookupKey(bindingRelation).hash();
609 seed ^= EntityLookupKey(traversalRelation).hash() + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
610 seed ^= EntityLookupKey(rootTarget).hash() + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
611 seed ^= size_t(travKind) + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
612 seed ^= size_t(travDepth) + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
613 return seed;
614 }
615
616 bool operator==(const PropagatedTargetCacheKey& other) const {
617 return bindingRelation == other.bindingRelation && traversalRelation == other.traversalRelation &&
618 rootTarget == other.rootTarget && travKind == other.travKind && travDepth == other.travDepth;
619 }
620 };
621
622 struct PropagatedTargetCacheEntry {
623 uint32_t bindingRelationVersion = 0;
624 uint32_t traversalRelationVersion = 0;
625 cnt::darray<Entity> targets;
626 };
627
628 struct DiffDispatcher {
629 struct Snapshot {
630 ObserverRuntimeData* pObs = nullptr;
631 uint32_t matchesBeforeIdx = UINT32_MAX;
632 };
633
634 struct MatchCacheEntry {
635 ObserverRuntimeData* pObsRepresentative = nullptr;
636 QueryInfo* pQueryInfoRepresentative = nullptr;
637 uint64_t queryHash = 0;
638 cnt::darray<Entity> matches;
639 };
640
641 struct TargetNarrowCacheEntry {
642 ObserverPlan::DiffPlan::DispatchKind kind = ObserverPlan::DiffPlan::DispatchKind::LocalTargets;
643 Entity bindingRelation = EntityBad;
644 Entity traversalRelation = EntityBad;
645 QueryTravKind travKind = QueryTravKind::None;
646 uint8_t travDepth = QueryTermOptions::TravDepthUnlimited;
647 QueryEntityArray triggerTerms{};
648 uint8_t triggerTermCount = 0;
649 cnt::darray<Entity> targets;
650 };
651
652 struct Context {
653 ObserverEvent event = ObserverEvent::OnAdd;
654 cnt::darray<Snapshot> observers;
655 cnt::darray<MatchCacheEntry> matchesBeforeCache;
656 cnt::darray<Entity> targets;
657 bool active = false;
658 bool targeted = false;
659 bool targetsAddedAfterPrepare = false;
660 bool resetTraversalCaches = false;
661 };
662
663 static void collect_query_matches(World& world, ObserverRuntimeData& obs, cnt::darray<Entity>& out) {
664 out.clear();
665 if (!world.valid(obs.entity))
666 return;
667
668 const auto& ec = world.fetch(obs.entity);
669 if (!world.enabled(ec))
670 return;
671
672 obs.query.reset();
673 obs.query.each([&](Entity entity) {
674 out.push_back(entity);
675 });
676
677 core::sort(out, [](Entity left, Entity right) {
678 return left.value() < right.value();
679 });
680 }
681
682 static void collect_query_target_matches(
683 World& world, ObserverRuntimeData& obs, EntitySpan targets, cnt::darray<Entity>& out) {
684 out.clear();
685 if (!world.valid(obs.entity))
686 return;
687
688 const auto& ec = world.fetch(obs.entity);
689 if (!world.enabled(ec))
690 return;
691
692 auto& queryInfo = obs.query.fetch();
693 for (auto entity: targets) {
694 if (!world.valid(entity))
695 continue;
696
697 const auto& ecTarget = world.fetch(entity);
698 if (ecTarget.pArchetype == nullptr)
699 continue;
700
701 if (obs.query.matches_any(queryInfo, *ecTarget.pArchetype, EntitySpan{&entity, 1}))
702 out.push_back(entity);
703 }
704 }
705
706 static void append_valid_targets(World& world, cnt::darray<Entity>& out, EntitySpan targets) {
707 for (auto entity: targets) {
708 if (world.valid(entity))
709 out.push_back(entity);
710 }
711 }
712
713 static void copy_target_narrow_plan(const ObserverRuntimeData& obs, TargetNarrowCacheEntry& entry) {
714 entry.kind = obs.plan.diff.dispatchKind;
715 entry.bindingRelation = obs.plan.diff.bindingRelation;
716 entry.traversalRelation = obs.plan.diff.traversalRelation;
717 entry.travKind = obs.plan.diff.travKind;
718 entry.travDepth = obs.plan.diff.travDepth;
719 entry.triggerTermCount = obs.plan.diff.traversalTriggerTermCount;
720 GAIA_FOR(obs.plan.diff.traversalTriggerTermCount) {
721 entry.triggerTerms[i] = obs.plan.diff.traversalTriggerTerms[i];
722 }
723 }
724
725 static bool same_target_narrow_plan(const ObserverRuntimeData& obs, const TargetNarrowCacheEntry& entry) {
726 if (obs.plan.diff.dispatchKind != entry.kind || obs.plan.diff.bindingRelation != entry.bindingRelation ||
727 obs.plan.diff.traversalRelation != entry.traversalRelation ||
728 obs.plan.diff.travKind != entry.travKind || obs.plan.diff.travDepth != entry.travDepth ||
729 obs.plan.diff.traversalTriggerTermCount != entry.triggerTermCount)
730 return false;
731
732 GAIA_FOR(obs.plan.diff.traversalTriggerTermCount) {
733 if (obs.plan.diff.traversalTriggerTerms[i] != entry.triggerTerms[i])
734 return false;
735 }
736
737 return true;
738 }
739
740 static void normalize_targets(cnt::darray<Entity>& targets) {
741 if (targets.empty())
742 return;
743
744 core::sort(targets, [](Entity left, Entity right) {
745 return left.value() < right.value();
746 });
747
748 uint32_t outIdx = 0;
749 for (uint32_t i = 0; i < targets.size(); ++i) {
750 if (outIdx != 0 && targets[i] == targets[outIdx - 1])
751 continue;
752 targets[outIdx++] = targets[i];
753 }
754 targets.resize(outIdx);
755 }
756
757 static uint64_t query_hash(ObserverRuntimeData& obs) {
758 auto& queryInfo = obs.query.fetch();
759 return queryInfo.ctx().hashLookup.hash;
760 }
761
762 static bool same_query_ctx(const QueryCtx& left, const QueryCtx& right) {
763 if (left.hashLookup != right.hashLookup)
764 return false;
765
766 const auto& leftData = left.data;
767 const auto& rightData = right.data;
768 if (leftData.idsCnt != rightData.idsCnt || leftData.changedCnt != rightData.changedCnt ||
769 leftData.readWriteMask != rightData.readWriteMask || leftData.cacheSrcTrav != rightData.cacheSrcTrav ||
770 leftData.sortBy != rightData.sortBy || leftData.sortByFunc != rightData.sortByFunc ||
771 leftData.groupBy != rightData.groupBy || leftData.groupByFunc != rightData.groupByFunc)
772 return false;
773
774 GAIA_FOR(leftData.idsCnt) {
775 if (leftData.terms[i] != rightData.terms[i])
776 return false;
777 }
778
779 GAIA_FOR(leftData.changedCnt) {
780 if (leftData.changed[i] != rightData.changed[i])
781 return false;
782 }
783
784 return true;
785 }
786
787 static int32_t find_match_cache_entry(cnt::darray<MatchCacheEntry>& cache, ObserverRuntimeData& obs) {
788 auto& queryInfo = obs.query.fetch();
789 auto& queryCtx = queryInfo.ctx();
790 const auto queryHash = queryCtx.hashLookup.hash;
791
792 GAIA_FOR((uint32_t)cache.size()) {
793 auto& entry = cache[i];
794 if (entry.pQueryInfoRepresentative == &queryInfo)
795 return (int32_t)i;
796 if (entry.queryHash != queryHash || entry.pObsRepresentative == nullptr)
797 continue;
798
799 auto& repQueryInfo = entry.pObsRepresentative->query.fetch();
800 if (same_query_ctx(queryCtx, repQueryInfo.ctx()))
801 return (int32_t)i;
802 }
803
804 return -1;
805 }
806
807 static Context prepare(
808 ObserverRegistry& registry, World& world, ObserverEvent event, EntitySpan terms,
809 EntitySpan targetEntities = {}) {
810 Context ctx{};
811 ctx.event = event;
812 const auto& index = registry.diff_index(event);
813
814 if (terms.empty() && index.all.empty() && index.global.empty())
815 return ctx;
816
817 bool hasEntityLifecycleTerm = false;
818 if (!targetEntities.empty() && !terms.empty()) {
819 for (auto term: terms) {
820 if (term.pair())
821 continue;
822
823 for (auto target: targetEntities) {
824 if (term == target) {
825 hasEntityLifecycleTerm = true;
826 break;
827 }
828 }
829
830 if (hasEntityLifecycleTerm)
831 break;
832 }
833 }
834
835 if (!hasEntityLifecycleTerm && !targetEntities.empty() && !terms.empty() &&
836 !SharedDispatch::has_terms(index.sourceTerm, terms) &&
837 !SharedDispatch::has_pair_relations(world, index.traversalRelation, terms)) {
838 ctx.targeted = true;
839 ctx.targets.reserve((uint32_t)targetEntities.size());
840 append_valid_targets(world, ctx.targets, targetEntities);
841 normalize_targets(ctx.targets);
842 }
843
844 registry.m_relevant_observers_tmp.clear();
845 const auto matchStamp = ++registry.m_current_match_stamp;
846 if (terms.empty()) {
847 SharedDispatch::collect_diff_from_list(registry, world, index.all, matchStamp);
848 } else {
849 for (auto term: terms) {
850 SharedDispatch::collect_from_map<true>(registry, world, index.direct, term, matchStamp);
851
852 if (!term.pair())
853 continue;
854
855 if (!is_wildcard(term.id()) && world.valid_entity_id((EntityId)term.id())) {
856 const auto relation = entity_from_id(world, term.id());
857 if (world.valid(relation)) {
858 SharedDispatch::collect_from_map<true>(
859 registry, world, index.traversalRelation, relation, matchStamp);
860 SharedDispatch::collect_from_map<true>(registry, world, index.pairRelation, relation, matchStamp);
861 }
862 }
863
864 if (!is_wildcard(term.gen()) && world.valid_entity_id((EntityId)term.gen())) {
865 const auto target = world.get(term.gen());
866 if (world.valid(target))
867 SharedDispatch::collect_from_map<true>(registry, world, index.pairTarget, target, matchStamp);
868 }
869 }
870 }
871
872 if (hasEntityLifecycleTerm)
873 SharedDispatch::collect_diff_from_list(registry, world, index.all, matchStamp);
874 if (!terms.empty() && !hasEntityLifecycleTerm)
875 SharedDispatch::collect_diff_from_list(registry, world, index.global, matchStamp);
876
877 if (!ctx.targeted && !targetEntities.empty() && !registry.m_relevant_observers_tmp.empty()) {
878 cnt::darray<Entity> narrowedTargets;
879 cnt::darray<TargetNarrowCacheEntry> narrowCache;
880 bool canNarrow = true;
881
882 for (auto* pObs: registry.m_relevant_observers_tmp) {
883 if (pObs == nullptr)
884 continue;
885
886 const TargetNarrowCacheEntry* pEntry = nullptr;
887 for (const auto& entry: narrowCache) {
888 if (same_target_narrow_plan(*pObs, entry)) {
889 pEntry = &entry;
890 break;
891 }
892 }
893
894 if (pEntry == nullptr) {
895 narrowCache.push_back({});
896 auto& entry = narrowCache.back();
897 copy_target_narrow_plan(*pObs, entry);
898
899 if (!collect_diff_targets_for_observer(
900 registry, world, *pObs, terms, targetEntities, entry.targets)) {
901 canNarrow = false;
902 break;
903 }
904
905 pEntry = &entry;
906 }
907
908 for (auto entity: pEntry->targets)
909 narrowedTargets.push_back(entity);
910 }
911
912 if (canNarrow) {
913 normalize_targets(narrowedTargets);
914 ctx.targeted = true;
915 ctx.targets = GAIA_MOV(narrowedTargets);
916 }
917 }
918
919 if (registry.m_relevant_observers_tmp.empty())
920 return ctx;
921
922 ctx.active = true;
923 for (auto* pObs: registry.m_relevant_observers_tmp) {
924 ctx.observers.push_back({});
925 auto& snapshot = ctx.observers.back();
926 snapshot.pObs = pObs;
927 if (!ctx.resetTraversalCaches && observer_uses_changed_traversal_relation(world, *pObs, terms))
928 ctx.resetTraversalCaches = true;
929
930 auto cacheIdx = find_match_cache_entry(ctx.matchesBeforeCache, *pObs);
931 if (cacheIdx == -1) {
932 ctx.matchesBeforeCache.push_back({});
933 auto& entry = ctx.matchesBeforeCache.back();
934 entry.pObsRepresentative = pObs;
935 entry.pQueryInfoRepresentative = &pObs->query.fetch();
936 entry.queryHash = query_hash(*pObs);
937 if (ctx.targeted)
938 collect_query_target_matches(
939 world, *pObs, EntitySpan{ctx.targets.data(), ctx.targets.size()}, entry.matches);
940 else
941 collect_query_matches(world, *pObs, entry.matches);
942 cacheIdx = (int32_t)ctx.matchesBeforeCache.size() - 1;
943 }
944
945 snapshot.matchesBeforeIdx = (uint32_t)cacheIdx;
946 }
947
948 return ctx;
949 }
950
951 static Context prepare_add_new(ObserverRegistry& registry, World& world, EntitySpan terms) {
952 Context ctx{};
953 ctx.event = ObserverEvent::OnAdd;
954 const auto& index = registry.diff_index(ObserverEvent::OnAdd);
955
956 if (terms.empty() && index.all.empty() && index.global.empty())
957 return ctx;
958
959 if (terms.empty() || SharedDispatch::has_terms(index.sourceTerm, terms) ||
960 SharedDispatch::has_pair_relations(world, index.traversalRelation, terms)) {
961 return prepare(registry, world, ObserverEvent::OnAdd, terms);
962 }
963
964 registry.m_relevant_observers_tmp.clear();
965 const auto matchStamp = ++registry.m_current_match_stamp;
966 for (auto term: terms) {
967 SharedDispatch::collect_from_map<true>(registry, world, index.direct, term, matchStamp);
968
969 if (!term.pair())
970 continue;
971
972 if (!is_wildcard(term.id()) && world.valid_entity_id((EntityId)term.id())) {
973 const auto relation = entity_from_id(world, term.id());
974 if (world.valid(relation))
975 SharedDispatch::collect_from_map<true>(registry, world, index.pairRelation, relation, matchStamp);
976 }
977
978 if (!is_wildcard(term.gen()) && world.valid_entity_id((EntityId)term.gen())) {
979 const auto target = world.get(term.gen());
980 if (world.valid(target))
981 SharedDispatch::collect_from_map<true>(registry, world, index.pairTarget, target, matchStamp);
982 }
983 }
984 SharedDispatch::collect_diff_from_list(registry, world, index.global, matchStamp);
985
986 if (registry.m_relevant_observers_tmp.empty())
987 return ctx;
988
989 ctx.active = true;
990 ctx.targeted = true;
991 ctx.targetsAddedAfterPrepare = true;
992 for (auto* pObs: registry.m_relevant_observers_tmp) {
993 if (!ctx.resetTraversalCaches && observer_uses_changed_traversal_relation(world, *pObs, terms))
994 ctx.resetTraversalCaches = true;
995 ctx.observers.push_back({});
996 ctx.observers.back().pObs = pObs;
997 }
998
999 return ctx;
1000 }
1001
1002 static void append_targets(World& world, Context& ctx, EntitySpan targets) {
1003 if (!ctx.active || !ctx.targeted || targets.empty())
1004 return;
1005
1006 append_valid_targets(world, ctx.targets, targets);
1007 }
1008
1009 static void finish(World& world, Context&& ctx) {
1010 if (!ctx.active)
1011 return;
1012
1013 if (ctx.resetTraversalCaches) {
1014 world.m_targetsTravCache = {};
1015 world.m_srcBfsTravCache = {};
1016 world.m_depthOrderCache = {};
1017 world.m_sourcesAllCache = {};
1018 world.m_targetsAllCache = {};
1019 world.m_entityToAsTargetsTravCache = {};
1020 world.m_entityToAsRelationsTravCache = {};
1021 }
1022
1023 if (ctx.targeted)
1024 normalize_targets(ctx.targets);
1025
1026 cnt::darray<MatchCacheEntry> matchesAfterCache;
1027 cnt::darray<Entity> delta;
1028
1029 for (auto& snapshot: ctx.observers) {
1030 auto* pObs = snapshot.pObs;
1031 if (pObs == nullptr || !world.valid(pObs->entity))
1032 continue;
1033
1034 auto afterCacheIdx = find_match_cache_entry(matchesAfterCache, *pObs);
1035 if (afterCacheIdx == -1) {
1036 matchesAfterCache.push_back({});
1037 auto& entry = matchesAfterCache.back();
1038 entry.pObsRepresentative = pObs;
1039 entry.pQueryInfoRepresentative = &pObs->query.fetch();
1040 entry.queryHash = query_hash(*pObs);
1041 if (ctx.targeted)
1042 collect_query_target_matches(
1043 world, *pObs, EntitySpan{ctx.targets.data(), ctx.targets.size()}, entry.matches);
1044 else
1045 collect_query_matches(world, *pObs, entry.matches);
1046 afterCacheIdx = (int32_t)matchesAfterCache.size() - 1;
1047 }
1048
1049 const auto& matchesAfter = matchesAfterCache[(uint32_t)afterCacheIdx].matches;
1050
1051 if (ctx.targetsAddedAfterPrepare && ctx.event == ObserverEvent::OnAdd) {
1052 SharedDispatch::execute_targets(world, *pObs, EntitySpan{matchesAfter});
1053 continue;
1054 }
1055
1056 GAIA_ASSERT(snapshot.matchesBeforeIdx < ctx.matchesBeforeCache.size());
1057 const auto& before = ctx.matchesBeforeCache[snapshot.matchesBeforeIdx].matches;
1058 delta.clear();
1059 uint32_t beforeIdx = 0;
1060 uint32_t afterMatchIdx = 0;
1061 while (beforeIdx < before.size() || afterMatchIdx < matchesAfter.size()) {
1062 if (beforeIdx == before.size()) {
1063 if (ctx.event == ObserverEvent::OnAdd)
1064 delta.push_back(matchesAfter[afterMatchIdx]);
1065 ++afterMatchIdx;
1066 continue;
1067 }
1068
1069 if (afterMatchIdx == matchesAfter.size()) {
1070 if (ctx.event == ObserverEvent::OnDel)
1071 delta.push_back(before[beforeIdx]);
1072 ++beforeIdx;
1073 continue;
1074 }
1075
1076 const auto beforeEntity = before[beforeIdx];
1077 const auto afterEntity = matchesAfter[afterMatchIdx];
1078 if (beforeEntity == afterEntity) {
1079 ++beforeIdx;
1080 ++afterMatchIdx;
1081 continue;
1082 }
1083
1084 if (beforeEntity < afterEntity) {
1085 if (ctx.event == ObserverEvent::OnDel)
1086 delta.push_back(beforeEntity);
1087 ++beforeIdx;
1088 } else {
1089 if (ctx.event == ObserverEvent::OnAdd)
1090 delta.push_back(afterEntity);
1091 ++afterMatchIdx;
1092 }
1093 }
1094
1095 SharedDispatch::execute_targets(world, *pObs, EntitySpan{delta.data(), delta.size()});
1096 }
1097 }
1098 };
1099
1100 struct DirectDispatcher {
1101 static void on_add(
1102 ObserverRegistry& registry, World& world, const Archetype& archetype, EntitySpan entsAdded,
1103 EntitySpan targets) {
1104 if GAIA_UNLIKELY (world.tearing_down())
1105 return;
1106
1107 if (!archetype.has_observed_terms() && !SharedDispatch::has_terms(registry.m_observer_map_add, entsAdded) &&
1108 !SharedDispatch::has_semantic_is_terms(world, registry.m_observer_map_add_is, entsAdded) &&
1109 !SharedDispatch::has_inherited_terms(world, registry.m_observer_map_add, entsAdded))
1110 return;
1111
1112 const bool archetypeIsPrefab = archetype.has(Prefab);
1113 const auto matchStamp = ++registry.m_current_match_stamp;
1114 for (auto comp: entsAdded) {
1115 SharedDispatch::collect_for_event_term(registry, world, registry.m_observer_map_add, comp, matchStamp);
1116 if (!is_semantic_is_term(comp))
1117 continue;
1118
1119 const auto target = world.get(comp.gen());
1120 if (!world.valid(target))
1121 continue;
1122
1123 SharedDispatch::collect_for_is_target(
1124 registry, world, registry.m_observer_map_add_is, target, matchStamp);
1125 for (auto inheritedTarget: world.as_targets_trav_cache(target))
1126 SharedDispatch::collect_for_is_target(
1127 registry, world, registry.m_observer_map_add_is, inheritedTarget, matchStamp);
1128 SharedDispatch::collect_for_inherited_terms(
1129 registry, world, registry.m_observer_map_add, target, matchStamp);
1130 }
1131
1132 for (auto* pObs: registry.m_relevant_observers_tmp) {
1133 auto& obs = *pObs;
1134 if (!obs.plan.uses_direct_dispatch())
1135 continue;
1136 QueryInfo* pQueryInfo = nullptr;
1137 if (archetypeIsPrefab) {
1138 pQueryInfo = &obs.query.fetch();
1139 if (!pQueryInfo->matches_prefab_entities())
1140 continue;
1141 }
1142
1143 if (SharedDispatch::matches_direct_targets(obs, archetype, targets, pQueryInfo)) {
1144 SharedDispatch::execute_targets(world, obs, targets);
1145 }
1146 }
1147
1148 registry.m_relevant_observers_tmp.clear();
1149 }
1150
1151 static void on_del(
1152 ObserverRegistry& registry, World& world, const Archetype& archetype, EntitySpan entsRemoved,
1153 EntitySpan targets) {
1154 if GAIA_UNLIKELY (world.tearing_down())
1155 return;
1156
1157 const bool archetypeIsPrefab = archetype.has(Prefab);
1158 if (!archetype.has_observed_terms() &&
1159 !SharedDispatch::has_terms(registry.m_observer_map_del, entsRemoved) &&
1160 !SharedDispatch::has_semantic_is_terms(world, registry.m_observer_map_del_is, entsRemoved) &&
1161 !SharedDispatch::has_inherited_terms(world, registry.m_observer_map_del, entsRemoved))
1162 return;
1163
1164 const auto matchStamp = ++registry.m_current_match_stamp;
1165 for (auto comp: entsRemoved) {
1166 SharedDispatch::collect_for_event_term(registry, world, registry.m_observer_map_del, comp, matchStamp);
1167 if (!is_semantic_is_term(comp))
1168 continue;
1169
1170 const auto target = world.get(comp.gen());
1171 if (!world.valid(target))
1172 continue;
1173
1174 SharedDispatch::collect_for_is_target(
1175 registry, world, registry.m_observer_map_del_is, target, matchStamp);
1176 for (auto inheritedTarget: world.as_targets_trav_cache(target))
1177 SharedDispatch::collect_for_is_target(
1178 registry, world, registry.m_observer_map_del_is, inheritedTarget, matchStamp);
1179 SharedDispatch::collect_for_inherited_terms(
1180 registry, world, registry.m_observer_map_del, target, matchStamp);
1181 }
1182
1183 for (auto* pObs: registry.m_relevant_observers_tmp) {
1184 auto& obs = *pObs;
1185 if (!obs.plan.uses_direct_dispatch())
1186 continue;
1187 QueryInfo* pQueryInfo = nullptr;
1188 if (archetypeIsPrefab) {
1189 pQueryInfo = &obs.query.fetch();
1190 if (!pQueryInfo->matches_prefab_entities())
1191 continue;
1192 }
1193
1194 bool matches = SharedDispatch::matches_direct_targets(obs, archetype, targets, pQueryInfo);
1195 if (obs.plan.exec_kind() == ObserverPlan::ExecKind::DirectFast && obs.plan.is_fast_negative())
1196 matches = true;
1197
1198 if (matches) {
1199 SharedDispatch::execute_targets(world, obs, targets);
1200 }
1201 }
1202
1203 registry.m_relevant_observers_tmp.clear();
1204 }
1205
1206 static void on_set(ObserverRegistry& registry, World& world, Entity term, EntitySpan targets) {
1207 if GAIA_UNLIKELY (world.tearing_down())
1208 return;
1209 if (targets.empty())
1210 return;
1211
1212 const auto itObservers = registry.m_observer_map_set.find(EntityLookupKey(term));
1213 if (itObservers == registry.m_observer_map_set.end() || itObservers->second.empty())
1214 return;
1215
1216 registry.m_relevant_observers_tmp.clear();
1217 const auto matchStamp = ++registry.m_current_match_stamp;
1218 SharedDispatch::collect_from_map<false>(registry, world, registry.m_observer_map_set, term, matchStamp);
1219
1220 for (auto* pObs: registry.m_relevant_observers_tmp) {
1221 auto& obs = *pObs;
1222 for (auto entity: targets) {
1223 if (!world.valid(entity))
1224 continue;
1225
1226 auto& queryInfo = obs.query.fetch();
1227 const auto& ec = world.fetch(entity);
1228 if (ec.pArchetype == nullptr)
1229 continue;
1230 if (ec.pArchetype->has(Prefab) && !queryInfo.matches_prefab_entities())
1231 continue;
1232 if (!obs.query.matches_any(queryInfo, *ec.pArchetype, EntitySpan{&entity, 1}))
1233 continue;
1234
1235 SharedDispatch::execute_targets(world, obs, EntitySpan{&entity, 1});
1236 }
1237 }
1238
1239 registry.m_relevant_observers_tmp.clear();
1240 }
1241 };
1242
1243 struct SharedDispatch {
1244 template <bool DiffOnly, typename TObserverMap>
1245 static void collect_from_map(
1246 ObserverRegistry& registry, World& world, const TObserverMap& map, Entity term, uint64_t matchStamp) {
1247 const auto it = map.find(EntityLookupKey(term));
1248 if (it == map.end())
1249 return;
1250
1251 for (auto observer: it->second) {
1252 auto* pObs = registry.data_try(observer);
1253 GAIA_ASSERT(pObs != nullptr);
1254 if (pObs == nullptr)
1255 continue;
1256 if (pObs->lastMatchStamp == matchStamp)
1257 continue;
1258
1259 const auto& ec = world.fetch(observer);
1260 if (!world.enabled(ec))
1261 continue;
1262
1263 if constexpr (DiffOnly) {
1264 if (!pObs->plan.uses_diff_dispatch())
1265 continue;
1266 }
1267
1268 pObs->lastMatchStamp = matchStamp;
1269 registry.m_relevant_observers_tmp.push_back(pObs);
1270 }
1271 }
1272
1273 static void collect_diff_from_list(
1274 ObserverRegistry& registry, World& world, const cnt::darray<Entity>& observers, uint64_t matchStamp) {
1275 for (auto observer: observers) {
1276 auto* pObs = registry.data_try(observer);
1277 GAIA_ASSERT(pObs != nullptr);
1278 if (pObs == nullptr || !pObs->plan.uses_diff_dispatch())
1279 continue;
1280 if (pObs->lastMatchStamp == matchStamp)
1281 continue;
1282
1283 const auto& ec = world.fetch(observer);
1284 if (!world.enabled(ec))
1285 continue;
1286
1287 pObs->lastMatchStamp = matchStamp;
1288 registry.m_relevant_observers_tmp.push_back(pObs);
1289 }
1290 }
1291
1292 template <typename TObserverMap>
1293 GAIA_NODISCARD static bool has_terms(const TObserverMap& map, EntitySpan terms) {
1294 for (auto term: terms) {
1295 const auto it = map.find(EntityLookupKey(term));
1296 if (it != map.end() && !it->second.empty())
1297 return true;
1298 }
1299
1300 return false;
1301 }
1302
1303 template <typename TObserverMap>
1304 GAIA_NODISCARD static bool has_pair_relations(World& world, const TObserverMap& map, EntitySpan terms) {
1305 for (auto term: terms) {
1306 if (!term.pair())
1307 continue;
1308
1309 const auto relation = entity_from_id(world, term.id());
1310 if (!world.valid(relation))
1311 continue;
1312
1313 const auto it = map.find(EntityLookupKey(relation));
1314 if (it != map.end() && !it->second.empty())
1315 return true;
1316 }
1317
1318 return false;
1319 }
1320
1321 template <typename TObserverMap>
1322 static void collect_for_event_term(
1323 ObserverRegistry& registry, World& world, const TObserverMap& map, Entity term, uint64_t matchStamp) {
1324 if (!world.valid(term))
1325 return;
1326
1327 if (!is_semantic_is_term(term)) {
1328 if ((world.fetch(term).flags & EntityContainerFlags::IsObserved) == 0)
1329 return;
1330 }
1331
1332 collect_from_map<false>(registry, world, map, term, matchStamp);
1333 }
1334
1335 template <typename TObserverMap>
1336 static void collect_for_is_target(
1337 ObserverRegistry& registry, World& world, const TObserverMap& map, Entity target, uint64_t matchStamp) {
1338 collect_from_map<false>(registry, world, map, target, matchStamp);
1339 }
1340
1341 template <typename Func>
1342 static void for_each_inherited_term(World& world, Entity baseEntity, Func&& func) {
1343 auto collectTerms = [&](Entity entity) {
1344 if (!world.valid(entity))
1345 return;
1346
1347 const auto& ec = world.fetch(entity);
1348 if (ec.pArchetype == nullptr)
1349 return;
1350
1351 for (const auto id: ec.pArchetype->ids_view()) {
1352 if (id.pair() || is_wildcard(id) || !world.valid(id))
1353 continue;
1354 if (world.target(id, OnInstantiate) != Inherit)
1355 continue;
1356
1357 func(id);
1358 }
1359 };
1360
1361 collectTerms(baseEntity);
1362 for (const auto inheritedBase: world.as_targets_trav_cache(baseEntity))
1363 collectTerms(inheritedBase);
1364 }
1365
1366 template <typename TObserverMap>
1367 GAIA_NODISCARD static bool has_semantic_is_terms(World& world, const TObserverMap& map, EntitySpan terms) {
1368 for (auto term: terms) {
1369 if (!is_semantic_is_term(term))
1370 continue;
1371
1372 const auto target = world.get(term.gen());
1373 if (!world.valid(target))
1374 continue;
1375
1376 if (map.find(EntityLookupKey(target)) != map.end())
1377 return true;
1378
1379 for (auto inheritedTarget: world.as_targets_trav_cache(target)) {
1380 if (map.find(EntityLookupKey(inheritedTarget)) != map.end())
1381 return true;
1382 }
1383 }
1384
1385 return false;
1386 }
1387
1388 template <typename TObserverMap>
1389 GAIA_NODISCARD static bool has_inherited_terms(World& world, const TObserverMap& map, EntitySpan terms) {
1390 for (auto term: terms) {
1391 if (!is_semantic_is_term(term))
1392 continue;
1393
1394 const auto target = world.get(term.gen());
1395 if (!world.valid(target))
1396 continue;
1397
1398 bool found = false;
1399 for_each_inherited_term(world, target, [&](Entity inheritedId) {
1400 if (found)
1401 return;
1402 const auto it = map.find(EntityLookupKey(inheritedId));
1403 found = it != map.end() && !it->second.empty();
1404 });
1405
1406 if (found)
1407 return true;
1408 }
1409
1410 return false;
1411 }
1412
1413 template <typename TObserverMap>
1414 static void collect_for_inherited_terms(
1415 ObserverRegistry& registry, World& world, const TObserverMap& map, Entity baseEntity,
1416 uint64_t matchStamp) {
1417 for_each_inherited_term(world, baseEntity, [&](Entity inheritedId) {
1418 collect_for_event_term(registry, world, map, inheritedId, matchStamp);
1419 });
1420 }
1421
1422 static void execute_targets(World& world, ObserverRuntimeData& obs, EntitySpan targets) {
1423 if (targets.empty())
1424 return;
1425
1426 Iter it;
1427 it.set_world(&world);
1428 it.set_group_id(0);
1429 it.set_comp_indices(0);
1430 obs.exec(it, targets);
1431 }
1432
1433 static bool matches_direct_targets(
1434 ObserverRuntimeData& obs, const Archetype& archetype, EntitySpan targets,
1435 QueryInfo* pQueryInfo = nullptr) {
1436 switch (obs.plan.exec_kind()) {
1437 case ObserverPlan::ExecKind::DirectFast:
1438 if (obs.plan.is_fast_positive())
1439 return true;
1440 if (obs.plan.is_fast_negative())
1441 return false;
1442 break;
1443 case ObserverPlan::ExecKind::DirectQuery:
1444 break;
1445 case ObserverPlan::ExecKind::DiffLocal:
1446 case ObserverPlan::ExecKind::DiffPropagated:
1447 case ObserverPlan::ExecKind::DiffFallback:
1448 return false;
1449 }
1450
1451 auto& queryInfo = pQueryInfo != nullptr ? *pQueryInfo : obs.query.fetch();
1452 return obs.query.matches_any(queryInfo, archetype, targets);
1453 }
1454 };
1455
1456 public:
1457 using DiffDispatchCtx = DiffDispatcher::Context;
1458
1459 private:
1461 cnt::darray<ObserverRuntimeData*> m_relevant_observers_tmp;
1463 cnt::map<EntityLookupKey, ObserverRuntimeData> m_observer_data;
1465 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_add;
1467 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_del;
1469 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_set;
1471 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_add_is;
1473 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_del_is;
1475 DiffObserverIndex m_diff_index_add;
1477 DiffObserverIndex m_diff_index_del;
1479 cnt::map<PropagatedTargetCacheKey, PropagatedTargetCacheEntry> m_propagated_target_cache;
1481 uint64_t m_current_match_stamp = 0;
1482
1483 GAIA_NODISCARD DiffObserverIndex& diff_index(ObserverEvent event) {
1484 GAIA_ASSERT(event == ObserverEvent::OnAdd || event == ObserverEvent::OnDel);
1485 return event == ObserverEvent::OnAdd ? m_diff_index_add : m_diff_index_del;
1486 }
1487
1488 GAIA_NODISCARD const DiffObserverIndex& diff_index(ObserverEvent event) const {
1489 GAIA_ASSERT(event == ObserverEvent::OnAdd || event == ObserverEvent::OnDel);
1490 return event == ObserverEvent::OnAdd ? m_diff_index_add : m_diff_index_del;
1491 }
1492
1493 GAIA_NODISCARD bool has_observers_for_term(Entity term) const {
1494 const auto termKey = EntityLookupKey(term);
1495 return m_observer_map_add.find(termKey) != m_observer_map_add.end() ||
1496 m_observer_map_del.find(termKey) != m_observer_map_del.end() ||
1497 m_observer_map_set.find(termKey) != m_observer_map_set.end();
1498 }
1499
1500 GAIA_NODISCARD static bool can_mark_term_observed(World& world, Entity term) {
1501 if (!term.pair())
1502 return world.valid(term);
1503
1504 if (is_wildcard(term))
1505 return false;
1506
1507 const auto it = world.m_recs.pairs.find(EntityLookupKey(term));
1508 return it != world.m_recs.pairs.end() && world.valid(it->second, term);
1509 }
1510
1511 GAIA_NODISCARD static bool
1512 is_semantic_is_term(Entity term, QueryMatchKind matchKind = QueryMatchKind::Semantic) {
1513 return matchKind != QueryMatchKind::Direct && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen());
1514 }
1515
1516 void mark_term_observed(World& world, Entity term, bool observed) {
1517 auto& ec = world.fetch(term);
1518 const bool wasObserved = (ec.flags & EntityContainerFlags::IsObserved) != 0;
1519 if (wasObserved == observed)
1520 return;
1521
1522 if (observed)
1523 ec.flags |= EntityContainerFlags::IsObserved;
1524 else
1525 ec.flags &= ~EntityContainerFlags::IsObserved;
1526
1527 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
1528 if (it == world.m_entityToArchetypeMap.end())
1529 return;
1530
1531 for (const auto& record: it->second) {
1532 auto* pArchetype = record.pArchetype;
1533 if (observed)
1534 pArchetype->observed_terms_inc();
1535 else
1536 pArchetype->observed_terms_dec();
1537 }
1538 }
1539
1540 template <typename TObserverMap>
1541 static void add_observer_to_map(TObserverMap& map, Entity term, Entity observer) {
1542 const auto entityKey = EntityLookupKey(term);
1543 const auto it = map.find(entityKey);
1544 if (it == map.end())
1545 map.emplace(entityKey, cnt::darray<Entity>{observer});
1546 else
1547 it->second.push_back(observer);
1548 }
1549
1550 template <typename TObserverMap>
1551 static void add_observer_to_map_unique(TObserverMap& map, Entity term, Entity observer) {
1552 const auto entityKey = EntityLookupKey(term);
1553 const auto it = map.find(entityKey);
1554 if (it == map.end())
1555 map.emplace(entityKey, cnt::darray<Entity>{observer});
1556 else
1557 add_observer_to_list(it->second, observer);
1558 }
1559
1560 static void add_observer_to_list(cnt::darray<Entity>& list, Entity observer) {
1561 if (core::has(list, observer))
1562 return;
1563 list.push_back(observer);
1564 }
1565
1566 static void remove_observer_from_list(cnt::darray<Entity>& list, Entity observer) {
1567 for (uint32_t i = 0; i < list.size();) {
1568 if (list[i] == observer)
1569 core::swap_erase_unsafe(list, i);
1570 else
1571 ++i;
1572 }
1573 }
1574
1575 static void collect_traversal_descendants(
1576 World& world, Entity relation, Entity root, QueryTravKind travKind, uint8_t travDepth, uint64_t visitStamp,
1577 cnt::darray<Entity>& outTargets) {
1578 cnt::set<EntityLookupKey> visitedPairs;
1579 auto try_mark_visited = [&](Entity entity) {
1580 if (entity.pair())
1581 return visitedPairs.insert(EntityLookupKey(entity)).second;
1582 return world.try_mark_entity_visited(entity, visitStamp);
1583 };
1584
1585 if (query_trav_has(travKind, QueryTravKind::Self)) {
1586 if (try_mark_visited(root))
1587 outTargets.push_back(root);
1588 }
1589
1590 if (!query_trav_has(travKind, QueryTravKind::Up))
1591 return;
1592
1593 if (travDepth == QueryTermOptions::TravDepthUnlimited && !query_trav_has(travKind, QueryTravKind::Down)) {
1594 world.sources_bfs(relation, root, [&](Entity source) {
1595 if (try_mark_visited(source))
1596 outTargets.push_back(source);
1597 });
1598 return;
1599 }
1600
1601 if (travDepth == 1) {
1602 world.sources(relation, root, [&](Entity source) {
1603 if (try_mark_visited(source))
1604 outTargets.push_back(source);
1605 });
1606 return;
1607 }
1608
1609 cnt::darray_ext<Entity, 32> queue;
1610 cnt::darray_ext<uint8_t, 32> depths;
1611 queue.push_back(root);
1612 depths.push_back(0);
1613
1614 for (uint32_t i = 0; i < queue.size(); ++i) {
1615 const auto curr = queue[i];
1616 const auto currDepth = depths[i];
1617 if (travDepth != QueryTermOptions::TravDepthUnlimited && currDepth >= travDepth)
1618 continue;
1619
1620 world.sources(relation, curr, [&](Entity source) {
1621 if (!try_mark_visited(source))
1622 return;
1623
1624 outTargets.push_back(source);
1625 queue.push_back(source);
1626 depths.push_back((uint8_t)(currDepth + 1));
1627 });
1628 }
1629 }
1630
1631 static PropagatedTargetCacheEntry& ensure_propagated_targets_cached(
1632 ObserverRegistry& registry, World& world, const ObserverRuntimeData& obs, Entity changedSource) {
1633 const PropagatedTargetCacheKey key{
1634 obs.plan.diff.bindingRelation, obs.plan.diff.traversalRelation, changedSource, obs.plan.diff.travKind,
1635 obs.plan.diff.travDepth};
1636
1637 auto& entry = registry.m_propagated_target_cache[key];
1638 const auto bindingRelationVersion = world.rel_version(obs.plan.diff.bindingRelation);
1639 const auto traversalRelationVersion = world.rel_version(obs.plan.diff.traversalRelation);
1640 const bool cacheValid = entry.bindingRelationVersion == bindingRelationVersion &&
1641 entry.traversalRelationVersion == traversalRelationVersion;
1642
1643 if (!cacheValid) {
1644 entry.bindingRelationVersion = bindingRelationVersion;
1645 entry.traversalRelationVersion = traversalRelationVersion;
1646 entry.targets.clear();
1647
1648 const auto visitStamp = world.next_entity_visit_stamp();
1649 cnt::darray<Entity> bindingTargets;
1650 collect_traversal_descendants(
1651 world, obs.plan.diff.traversalRelation, changedSource, obs.plan.diff.travKind, obs.plan.diff.travDepth,
1652 visitStamp, bindingTargets);
1653 for (auto bindingTarget: bindingTargets) {
1654 world.sources(obs.plan.diff.bindingRelation, bindingTarget, [&](Entity source) {
1655 entry.targets.push_back(source);
1656 });
1657 }
1658
1659 DiffDispatcher::normalize_targets(entry.targets);
1660 }
1661
1662 return entry;
1663 }
1664
1665 static void collect_propagated_targets_cached(
1666 ObserverRegistry& registry, World& world, const ObserverRuntimeData& obs, Entity changedSource,
1667 uint64_t visitStamp, cnt::set<EntityLookupKey>& visitedPairs, cnt::darray<Entity>& outTargets) {
1668 auto& entry = ensure_propagated_targets_cached(registry, world, obs, changedSource);
1669
1670 for (auto source: entry.targets) {
1671 const bool isNew = source.pair() ? visitedPairs.insert(EntityLookupKey(source)).second
1672 : world.try_mark_entity_visited(source, visitStamp);
1673 if (isNew)
1674 outTargets.push_back(source);
1675 }
1676 }
1677
1678 static void append_propagated_targets_cached(
1679 ObserverRegistry& registry, World& world, const ObserverRuntimeData& obs, Entity changedSource,
1680 cnt::darray<Entity>& outTargets) {
1681 auto& entry = ensure_propagated_targets_cached(registry, world, obs, changedSource);
1682
1683 for (auto source: entry.targets)
1684 outTargets.push_back(source);
1685 }
1686
1687 static bool collect_source_traversal_diff_targets(
1688 ObserverRegistry& registry, World& world, ObserverRuntimeData& obs, EntitySpan changedTerms,
1689 EntitySpan changedSources, cnt::darray<Entity>& outTargets) {
1690 if (changedSources.empty())
1691 return false;
1692 if (!obs.plan.uses_propagated_diff_targets())
1693 return false;
1694 if (obs.plan.diff.bindingRelation == EntityBad || obs.plan.diff.traversalRelation == EntityBad ||
1695 obs.plan.diff.traversalTriggerTermCount == 0)
1696 return false;
1697
1698 bool termTriggered = false;
1699 for (auto changedTerm: changedTerms) {
1700 for (auto changedSource: changedSources) {
1701 if (!changedTerm.pair() && changedTerm == changedSource) {
1702 termTriggered = true;
1703 break;
1704 }
1705 }
1706 if (termTriggered)
1707 break;
1708
1709 if (changedTerm.pair() && entity_from_id(world, changedTerm.id()) == obs.plan.diff.traversalRelation) {
1710 termTriggered = true;
1711 break;
1712 }
1713
1714 GAIA_FOR(obs.plan.diff.traversalTriggerTermCount) {
1715 if (obs.plan.diff.traversalTriggerTerms[i] == changedTerm) {
1716 termTriggered = true;
1717 break;
1718 }
1719 }
1720
1721 if (termTriggered)
1722 break;
1723 }
1724 if (!termTriggered)
1725 return false;
1726
1727 if (changedSources.size() == 1) {
1728 append_propagated_targets_cached(registry, world, obs, changedSources[0], outTargets);
1729 return true;
1730 }
1731
1732 const auto visitStamp = world.next_entity_visit_stamp();
1733 cnt::set<EntityLookupKey> visitedPairs;
1734 for (auto changedSource: changedSources)
1735 collect_propagated_targets_cached(
1736 registry, world, obs, changedSource, visitStamp, visitedPairs, outTargets);
1737
1738 return true;
1739 }
1740
1741 static bool collect_diff_targets_for_observer(
1742 ObserverRegistry& registry, World& world, ObserverRuntimeData& obs, EntitySpan changedTerms,
1743 EntitySpan changedTargets, cnt::darray<Entity>& outTargets) {
1744 switch (obs.plan.exec_kind()) {
1745 case ObserverPlan::ExecKind::DiffLocal:
1746 DiffDispatcher::append_valid_targets(world, outTargets, changedTargets);
1747 return true;
1748 case ObserverPlan::ExecKind::DiffPropagated:
1749 return collect_source_traversal_diff_targets(
1750 registry, world, obs, changedTerms, changedTargets, outTargets);
1751 case ObserverPlan::ExecKind::DiffFallback:
1752 return false;
1753 case ObserverPlan::ExecKind::DirectQuery:
1754 case ObserverPlan::ExecKind::DirectFast:
1755 return false;
1756 }
1757
1758 return false;
1759 }
1760
1761 static bool observer_uses_changed_traversal_relation(
1762 World& world, const ObserverRuntimeData& obs, EntitySpan changedTerms) {
1763 if (obs.plan.diff.traversalRelationCount == 0 || changedTerms.empty())
1764 return false;
1765
1766 for (auto changedTerm: changedTerms) {
1767 if (!changedTerm.pair())
1768 continue;
1769
1770 const auto relation = entity_from_id(world, changedTerm.id());
1771 if (!world.valid(relation))
1772 continue;
1773
1774 GAIA_FOR(obs.plan.diff.traversalRelationCount) {
1775 if (obs.plan.diff.traversalRelations[i] == relation)
1776 return true;
1777 }
1778 }
1779
1780 return false;
1781 }
1782
1783 static bool is_dynamic_pair_endpoint(EntityId endpoint) {
1784 return is_wildcard(endpoint) || is_variable(endpoint);
1785 }
1786
1787 static bool is_observer_term_globally_dynamic(Entity term) {
1788 if (term == EntityBad || term == All)
1789 return true;
1790
1791 if (!term.pair())
1792 return is_variable((EntityId)term.id());
1793
1794 const bool relDynamic = is_dynamic_pair_endpoint(term.id());
1795 const bool tgtDynamic = is_dynamic_pair_endpoint(term.gen());
1796 return relDynamic && tgtDynamic;
1797 }
1798
1799 public:
1800 GAIA_NODISCARD bool has_observers(Entity term) const {
1801 return has_observers_for_term(term);
1802 }
1803
1804 GAIA_NODISCARD bool has_on_set_observers(Entity term) const {
1805 return m_observer_map_set.find(EntityLookupKey(term)) != m_observer_map_set.end();
1806 }
1807
1808 void add_diff_observer_term(World& world, Entity observer, Entity term, const QueryTermOptions& options) {
1809 GAIA_ASSERT(world.valid(observer));
1810
1811 const auto& ec = world.fetch(observer);
1812 const auto compIdx = ec.pChunk->comp_idx(Observer);
1813 const auto& obs = *reinterpret_cast<const Observer_*>(ec.pChunk->comp_ptr(compIdx, ec.row));
1814 auto& index = diff_index(obs.event);
1815
1816 switch (obs.event) {
1817 case ObserverEvent::OnAdd:
1818 case ObserverEvent::OnDel:
1819 break;
1820 case ObserverEvent::OnSet:
1821 return;
1822 }
1823
1824 add_observer_to_list(index.all, observer);
1825
1826 bool registered = false;
1827
1828 if (term != EntityBad && term != All) {
1829 add_observer_to_map_unique(index.direct, term, observer);
1830 registered = true;
1831 }
1832
1833 if (term != EntityBad && term != All && options.entSrc != EntityBad) {
1834 add_observer_to_map_unique(index.sourceTerm, term, observer);
1835 registered = true;
1836 }
1837
1838 if (options.entTrav != EntityBad) {
1839 if (term != EntityBad && term != All)
1840 add_observer_to_map_unique(index.sourceTerm, term, observer);
1841 add_observer_to_map_unique(index.traversalRelation, options.entTrav, observer);
1842 registered = true;
1843 }
1844
1845 if (term.pair()) {
1846 const bool relDynamic = is_dynamic_pair_endpoint(term.id());
1847 const bool tgtDynamic = is_dynamic_pair_endpoint(term.gen());
1848
1849 if (relDynamic && !tgtDynamic) {
1850 add_observer_to_map_unique(index.pairTarget, world.get(term.gen()), observer);
1851 registered = true;
1852 }
1853
1854 if (tgtDynamic && !relDynamic) {
1855 add_observer_to_map_unique(index.pairRelation, entity_from_id(world, term.id()), observer);
1856 registered = true;
1857 }
1858 }
1859
1860 if (!registered || is_observer_term_globally_dynamic(term))
1861 add_observer_to_list(index.global, observer);
1862 }
1863
1864 GAIA_NODISCARD DiffDispatchCtx
1865 prepare_diff(World& world, ObserverEvent event, EntitySpan terms, EntitySpan targetEntities = {}) {
1866 if GAIA_UNLIKELY (world.tearing_down())
1867 return {};
1868 return DiffDispatcher::prepare(*this, world, event, terms, targetEntities);
1869 }
1870
1871 GAIA_NODISCARD DiffDispatchCtx prepare_diff_add_new(World& world, EntitySpan terms) {
1872 if GAIA_UNLIKELY (world.tearing_down())
1873 return {};
1874 return DiffDispatcher::prepare_add_new(*this, world, terms);
1875 }
1876
1877 void append_diff_targets(World& world, DiffDispatchCtx& ctx, EntitySpan targets) {
1878 DiffDispatcher::append_targets(world, ctx, targets);
1879 }
1880
1881 void finish_diff(World& world, DiffDispatchCtx&& ctx) {
1882 if GAIA_UNLIKELY (world.tearing_down())
1883 return;
1884 DiffDispatcher::finish(world, GAIA_MOV(ctx));
1885 }
1886
1887 void teardown() {
1888 for (auto& it: m_observer_data) {
1889 auto& obs = it.second;
1890 obs.on_each_func = {};
1891 obs.query = {};
1892 obs.plan = {};
1893 obs.lastMatchStamp = 0;
1894 }
1895
1896 m_relevant_observers_tmp = {};
1897 m_observer_data = {};
1898 m_observer_map_add = {};
1899 m_observer_map_del = {};
1900 m_observer_map_set = {};
1901 m_observer_map_add_is = {};
1902 m_observer_map_del_is = {};
1903 m_diff_index_add = {};
1904 m_diff_index_del = {};
1905 m_propagated_target_cache = {};
1906 }
1907
1908 ObserverRuntimeData& data_add(Entity observer) {
1909 return m_observer_data[EntityLookupKey(observer)];
1910 }
1911
1912 GAIA_NODISCARD ObserverRuntimeData* data_try(Entity observer) {
1913 const auto it = m_observer_data.find(EntityLookupKey(observer));
1914 if (it == m_observer_data.end())
1915 return nullptr;
1916 return &it->second;
1917 }
1918
1919 GAIA_NODISCARD const ObserverRuntimeData* data_try(Entity observer) const {
1920 const auto it = m_observer_data.find(EntityLookupKey(observer));
1921 if (it == m_observer_data.end())
1922 return nullptr;
1923 return &it->second;
1924 }
1925
1926 GAIA_NODISCARD ObserverRuntimeData& data(Entity observer) {
1927 auto* pData = data_try(observer);
1928 GAIA_ASSERT(pData != nullptr);
1929 return *pData;
1930 }
1931
1932 GAIA_NODISCARD const ObserverRuntimeData& data(Entity observer) const {
1933 const auto* pData = data_try(observer);
1934 GAIA_ASSERT(pData != nullptr);
1935 return *pData;
1936 }
1937
1938 void try_mark_term_observed(World& world, Entity term) {
1939 if (!can_mark_term_observed(world, term))
1940 return;
1941 if (!has_observers_for_term(term))
1942 return;
1943 if ((world.fetch(term).flags & EntityContainerFlags::IsObserved) != 0)
1944 return;
1945
1946 mark_term_observed(world, term, true);
1947 }
1948
1953 void add(World& world, Entity term, Entity observer, QueryMatchKind matchKind = QueryMatchKind::Semantic) {
1954 GAIA_ASSERT(!observer.pair());
1955 GAIA_ASSERT(world.valid(observer));
1956 // For a pair term, valid(pair) is true only if that exact pair already exists
1957 // in m_recs.pairs (exists in-world). Observers are allowed to register pair terms that
1958 // may appear later, so asserting just world.valid(term) for pairs when adding is wrong.
1959 GAIA_ASSERT(term.pair() || world.valid(term));
1960
1961 const auto wasObserved = has_observers_for_term(term);
1962 const auto canMarkObserved = can_mark_term_observed(world, term);
1963 const auto& ec = world.fetch(observer);
1964 const auto compIdx = ec.pChunk->comp_idx(Observer);
1965 const auto& obs = *reinterpret_cast<const Observer_*>(ec.pChunk->comp_ptr(compIdx, ec.row));
1966 switch (obs.event) {
1967 case ObserverEvent::OnAdd:
1968 add_observer_to_map(m_observer_map_add, term, observer);
1969 if (is_semantic_is_term(term, matchKind))
1970 add_observer_to_map(m_observer_map_add_is, world.get(term.gen()), observer);
1971 break;
1972 case ObserverEvent::OnDel:
1973 add_observer_to_map(m_observer_map_del, term, observer);
1974 if (is_semantic_is_term(term, matchKind))
1975 add_observer_to_map(m_observer_map_del_is, world.get(term.gen()), observer);
1976 break;
1977 case ObserverEvent::OnSet:
1978 add_observer_to_map(m_observer_map_set, term, observer);
1979 break;
1980 }
1981 if (!wasObserved && canMarkObserved)
1982 mark_term_observed(world, term, true);
1983 }
1984
1988 void del(World& w, Entity term) {
1989 GAIA_ASSERT(w.valid(term));
1990
1991 const auto termKey = EntityLookupKey(term);
1992 const auto erasedData = m_observer_data.erase(termKey);
1993 const auto erasedOnAdd = m_observer_map_add.erase(termKey);
1994 const auto erasedOnDel = m_observer_map_del.erase(termKey);
1995 const auto erasedOnSet = m_observer_map_set.erase(termKey);
1996 if (is_semantic_is_term(term)) {
1997 const auto isKey = EntityLookupKey(w.get(term.gen()));
1998 m_observer_map_add_is.erase(isKey);
1999 m_observer_map_del_is.erase(isKey);
2000 }
2001 if ((erasedOnAdd != 0 || erasedOnDel != 0 || erasedOnSet != 0) && can_mark_term_observed(w, term))
2002 mark_term_observed(w, term, false);
2003
2004 // A regular term deletion has nothing else to clean up.
2005 // If an observer gets deleted, remove it from all term mappings.
2006 if (erasedData == 0)
2007 return;
2008
2009 auto remove_observer_from_map = [&](auto& map) {
2010 for (auto it = map.begin(); it != map.end();) {
2011 auto& observers = it->second;
2012 for (uint32_t i = 0; i < observers.size();) {
2013 if (observers[i] == term)
2014 core::swap_erase_unsafe(observers, i);
2015 else
2016 ++i;
2017 }
2018
2019 if (observers.empty()) {
2020 const auto mappedTerm = it->first.entity();
2021 auto itToErase = it++;
2022 map.erase(itToErase);
2023
2024 if (can_mark_term_observed(w, mappedTerm) && !has_observers_for_term(mappedTerm))
2025 mark_term_observed(w, mappedTerm, false);
2026 } else
2027 ++it;
2028 }
2029 };
2030 remove_observer_from_map(m_observer_map_add);
2031 remove_observer_from_map(m_observer_map_del);
2032 remove_observer_from_map(m_observer_map_set);
2033 remove_observer_from_map(m_observer_map_add_is);
2034 remove_observer_from_map(m_observer_map_del_is);
2035 auto remove_observer_from_diff_index = [&](auto& index) {
2036 remove_observer_from_map(index.direct);
2037 remove_observer_from_map(index.sourceTerm);
2038 remove_observer_from_map(index.traversalRelation);
2039 remove_observer_from_map(index.pairRelation);
2040 remove_observer_from_map(index.pairTarget);
2041 remove_observer_from_list(index.all, term);
2042 remove_observer_from_list(index.global, term);
2043 };
2044 remove_observer_from_diff_index(m_diff_index_add);
2045 remove_observer_from_diff_index(m_diff_index_del);
2046 }
2047
2053 void on_add(World& world, const Archetype& archetype, EntitySpan ents_added, EntitySpan targets) {
2054 DirectDispatcher::on_add(*this, world, archetype, ents_added, targets);
2055 }
2056
2062 void on_del(World& world, const Archetype& archetype, EntitySpan ents_removed, EntitySpan targets) {
2063 DirectDispatcher::on_del(*this, world, archetype, ents_removed, targets);
2064 }
2065
2066 void on_set(World& world, Entity term, EntitySpan targets) {
2067 DirectDispatcher::on_set(*this, world, term, targets);
2068 }
2069 };
2070#endif
2071
2072 //----------------------------------------------------------------------
2073
2075 ComponentCache m_compCache;
2077 QueryCache m_queryCache;
2080 cnt::darray<QueryMatchScratch*> m_queryMatchScratchStack;
2082 uint32_t m_queryMatchScratchDepth = 0;
2088 QuerySerMap m_querySerMap;
2089 uint32_t m_nextQuerySerId = 0;
2090
2092 uint32_t m_emptySpace0 = 0;
2093
2095 EntityToArchetypeMap m_entityToArchetypeMap;
2104 PairMap m_entityToAsTargets;
2107 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_entityToAsTargetsTravCache;
2115 PairMap m_entityToAsRelations;
2118 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_entityToAsRelationsTravCache;
2121 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_targetsTravCache;
2124 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_srcBfsTravCache;
2127 mutable cnt::map<EntityLookupKey, uint32_t> m_depthOrderCache;
2130 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_sourcesAllCache;
2133 mutable cnt::map<EntityLookupKey, cnt::darray<Entity>> m_targetsAllCache;
2135 PairMap m_relToTgt;
2137 PairMap m_tgtToRel;
2139 cnt::map<EntityLookupKey, ExclusiveAdjunctStore> m_exclusiveAdjunctByRel;
2141 EntityArrayMap m_srcToExclusiveAdjunctRel;
2143 cnt::map<EntityLookupKey, SparseComponentStoreErased> m_sparseComponentsByComp;
2145 cnt::map<EntityLookupKey, uint32_t> m_relationVersions;
2148 mutable cnt::map<EntityLookupKey, uint32_t> m_srcEntityVersions;
2149
2151 ArchetypeDArray m_archetypes;
2153 ArchetypeMapByHash m_archetypesByHash;
2155 ArchetypeMapById m_archetypesById;
2156
2158 Archetype* m_pRootArchetype = nullptr;
2160 Archetype* m_pEntityArchetype = nullptr;
2162 Archetype* m_pCompArchetype = nullptr;
2164 ArchetypeId m_nextArchetypeId = 0;
2165
2167 uint32_t m_emptySpace1 = 0;
2168
2170 EntityContainers m_recs;
2171
2173 cnt::map<EntityNameLookupKey, Entity> m_nameToEntity;
2175 cnt::map<EntityNameLookupKey, Entity> m_aliasToEntity;
2177 Entity m_componentScope = EntityBad;
2179 cnt::darray<Entity> m_componentLookupPath;
2181 mutable util::str m_componentScopePathCache;
2183 mutable Entity m_componentScopePathCacheEntity = EntityBad;
2185 mutable bool m_componentScopePathCacheValid = false;
2186
2188 cnt::set<ArchetypeLookupKey> m_reqArchetypesToDel;
2190 cnt::set<EntityLookupKey> m_reqEntitiesToDel;
2191
2192#if GAIA_OBSERVERS_ENABLED
2194 ObserverRegistry m_observers;
2195#endif
2196
2198 CommandBufferST* m_pCmdBufferST;
2200 CommandBufferMT* m_pCmdBufferMT;
2202 bool m_teardownActive = false;
2204 ecs::Query m_systemsQuery;
2206 mutable cnt::darray<uint64_t> m_entityVisitStamps;
2208 mutable uint64_t m_entityVisitStamp = 0;
2209
2211 cnt::set<EntityLookupKey> m_entitiesToDel;
2213 cnt::darray<ArchetypeChunkPair> m_chunksToDel;
2215 ArchetypeDArray m_archetypesToDel;
2217 uint32_t m_defragLastArchetypeIdx = 0;
2219 uint32_t m_defragEntitiesPerTick = 100;
2220
2222 uint32_t m_worldVersion = 0;
2224 uint32_t m_enabledHierarchyVersion = 0;
2225
2226 uint32_t m_structuralChangesLocked = 0;
2227
2228 public:
2229 World():
2230 // Command buffer for the main thread
2231 m_pCmdBufferST(cmd_buffer_st_create(*this)),
2232 // Command buffer safe for concurrent access
2233 m_pCmdBufferMT(cmd_buffer_mt_create(*this)) {
2234 init();
2235 }
2236
2237 ~World() {
2238 teardown();
2239 done();
2240 cmd_buffer_destroy(*m_pCmdBufferST);
2241 cmd_buffer_destroy(*m_pCmdBufferMT);
2242 }
2243
2244 World(World&&) = delete;
2245 World(const World&) = delete;
2246 World& operator=(World&&) = delete;
2247 World& operator=(const World&) = delete;
2248
2249 //----------------------------------------------------------------------
2250
2255 return Query(
2256 *const_cast<World*>(this), m_queryCache,
2257 //
2258 m_nextArchetypeId, m_worldVersion, m_entityToArchetypeMap, m_archetypes);
2259 }
2260
2265 auto q = query();
2266 q.kind(QueryCacheKind::None);
2267 return q;
2268 }
2269
2270 //----------------------------------------------------------------------
2271
2275 GAIA_NODISCARD EntityContainer& fetch(Entity entity) {
2276#if GAIA_ASSERT_ENABLED
2277 // Wildcard pairs are not a real entity so we can't accept them
2278 GAIA_ASSERT(!entity.pair() || !is_wildcard(entity));
2279 if (!valid(entity)) {
2280 // Delete-time cleanup can still reference an exact pair record after one endpoint
2281 // has already become invalid. As long as the pair record itself still exists,
2282 // allow fetch() so cleanup can finish removing it.
2283 const bool allowStaleExactPair = entity.pair() && m_recs.pairs.contains(EntityLookupKey(entity));
2284 GAIA_ASSERT(allowStaleExactPair);
2285 }
2286#endif
2287 return m_recs[entity];
2288 }
2289
2293 GAIA_NODISCARD const EntityContainer& fetch(Entity entity) const {
2294#if GAIA_ASSERT_ENABLED
2295 // Wildcard pairs are not a real entity so we can't accept them
2296 GAIA_ASSERT(!entity.pair() || !is_wildcard(entity));
2297 if (!valid(entity)) {
2298 // Delete-time cleanup can still reference an exact pair record after one endpoint
2299 // has already become invalid. As long as the pair record itself still exists,
2300 // allow fetch() so cleanup can finish removing it.
2301 const bool allowStaleExactPair = entity.pair() && m_recs.pairs.contains(EntityLookupKey(entity));
2302 GAIA_ASSERT(allowStaleExactPair);
2303 }
2304#endif
2305 return m_recs[entity];
2306 }
2307
2308 //----------------------------------------------------------------------
2309
2314 GAIA_NODISCARD static bool is_req_del(const EntityContainer& ec) {
2315 if ((ec.flags & EntityContainerFlags::DeleteRequested) != 0)
2316 return true;
2317 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) == 0);
2318 return ec.pArchetype != nullptr && ec.pArchetype->is_req_del();
2319 }
2320
2324 GAIA_NODISCARD bool is_dont_fragment(Entity entity) const {
2325 return (fetch(entity).flags & EntityContainerFlags::IsDontFragment) != 0;
2326 }
2327
2331 GAIA_NODISCARD bool is_dont_fragment_relation(Entity relation) const {
2332 return valid(relation) && !relation.pair() && is_dont_fragment(relation);
2333 }
2334
2339 GAIA_NODISCARD bool is_exclusive_dont_fragment_relation(Entity relation) const {
2340 if (!valid(relation) || relation.pair())
2341 return false;
2342
2343 const auto& ec = fetch(relation);
2344 return (ec.flags & EntityContainerFlags::IsExclusive) != 0 &&
2345 (ec.flags & EntityContainerFlags::IsDontFragment) != 0;
2346 }
2347
2350 GAIA_NODISCARD bool is_hierarchy_relation(Entity relation) const {
2351 if (!valid(relation) || relation.pair())
2352 return false;
2353
2354 return has(relation, Exclusive) && has(relation, Traversable);
2355 }
2356
2359 GAIA_NODISCARD bool is_fragmenting_relation(Entity relation) const {
2360 return valid(relation) && !relation.pair() && !is_dont_fragment(relation);
2361 }
2362
2365 GAIA_NODISCARD bool is_fragmenting_hierarchy_relation(Entity relation) const {
2366 return is_hierarchy_relation(relation) && is_fragmenting_relation(relation);
2367 }
2368
2373 GAIA_NODISCARD bool supports_depth_order(Entity relation) const {
2374 return is_fragmenting_relation(relation);
2375 }
2376
2380 GAIA_NODISCARD bool depth_order_prunes_disabled_subtrees(Entity relation) const {
2381 return is_fragmenting_hierarchy_relation(relation);
2382 }
2383
2388 GAIA_NODISCARD bool is_out_of_line_component(Entity component) const {
2389 if (!valid(component) || component.pair() || component.entity())
2390 return false;
2391
2392 const auto* pItem = comp_cache().find(component);
2393 if (pItem == nullptr || component.kind() != EntityKind::EK_Gen || pItem->comp.soa() != 0)
2394 return false;
2395
2396 return component_has_out_of_line_data(pItem->comp);
2397 }
2398
2403 GAIA_NODISCARD bool is_non_fragmenting_out_of_line_component(Entity component) const {
2404 if (!is_out_of_line_component(component))
2405 return false;
2406
2407 return (fetch(component).flags & EntityContainerFlags::IsDontFragment) != 0;
2408 }
2409
2410 //----------------------------------------------------------------------
2411
2416 auto& item = comp_cache_mut().get(component);
2417 item.comp = comp;
2418
2419 auto& ec = m_recs.entities[component.id()];
2420 if (ec.pArchetype == nullptr || ec.pChunk == nullptr)
2421 return;
2422
2423 const auto compIdx = core::get_index(ec.pArchetype->ids_view(), GAIA_ID(Component));
2424 if (compIdx == BadIndex)
2425 return;
2426
2427 auto* pComp = reinterpret_cast<Component*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
2428 *pComp = comp;
2429 }
2430
2436 if (component.comp())
2437 set_component_sparse_storage(component);
2438
2439 if ((ec.flags & EntityContainerFlags::IsDontFragment) != 0)
2440 return;
2441
2442 ec.flags |= EntityContainerFlags::IsDontFragment;
2443 }
2444
2450 GAIA_ASSERT(valid(component));
2451 GAIA_ASSERT(component.comp());
2452 GAIA_ASSERT(!component.pair());
2453 GAIA_ASSERT(!component.entity());
2454 GAIA_ASSERT(component.kind() == EntityKind::EK_Gen);
2455
2456 const auto& item = comp_cache().get(component);
2457 GAIA_ASSERT(item.entity == component);
2458
2459 if (item.comp.storage_type() == DataStorageType::Sparse)
2460 return;
2461
2462 GAIA_ASSERT(item.comp.soa() == 0);
2463 if (item.comp.soa() != 0)
2464 return;
2465
2466 const auto directTermEntityCnt = count_direct_term_entities_direct(component);
2467 GAIA_ASSERT(directTermEntityCnt == 0);
2468 if (directTermEntityCnt != 0)
2469 return;
2470
2471 auto comp = item.comp;
2472 comp.data.storage = (IdentifierData)DataStorageType::Sparse;
2473 sync_component_record(component, comp);
2474 }
2475
2480 template <typename T>
2481 GAIA_NODISCARD static constexpr bool supports_out_of_line_component() {
2482 using U = typename actual_type_t<T>::Type;
2483 return !is_pair<T>::value && entity_kind_v<T> == EntityKind::EK_Gen && !mem::is_soa_layout_v<U>;
2484 }
2485
2490 template <typename T>
2491 GAIA_NODISCARD bool can_use_out_of_line_component(Entity object) const {
2492 if constexpr (!supports_out_of_line_component<T>())
2493 return false;
2494 else {
2495 if (object.pair())
2496 return false;
2497
2498 const auto* pItem = comp_cache().find(object);
2499 if (pItem == nullptr || pItem->entity != object || !is_out_of_line_component(object))
2500 return false;
2501
2502 using U = typename actual_type_t<T>::Type;
2503 return pItem->comp.size() == (uint32_t)sizeof(U) && pItem->comp.alig() == (uint32_t)alignof(U) &&
2504 pItem->comp.soa() == 0 && object.kind() == entity_kind_v<T>;
2505 }
2506 }
2507
2512 template <typename T>
2513 GAIA_NODISCARD SparseComponentStore<T>* sparse_component_store(Entity component) {
2514 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
2515 if (it == m_sparseComponentsByComp.end())
2516 return nullptr;
2517
2518 return static_cast<SparseComponentStore<T>*>(it->second.pStore);
2519 }
2520
2525 template <typename T>
2526 GAIA_NODISCARD const SparseComponentStore<T>* sparse_component_store(Entity component) const {
2527 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
2528 if (it == m_sparseComponentsByComp.end())
2529 return nullptr;
2530
2531 return static_cast<const SparseComponentStore<T>*>(it->second.pStore);
2532 }
2533
2538 template <typename T>
2539 GAIA_NODISCARD SparseComponentStore<T>& sparse_component_store_mut(Entity component) {
2540 const auto key = EntityLookupKey(component);
2541 const auto it = m_sparseComponentsByComp.find(key);
2542 if (it != m_sparseComponentsByComp.end())
2543 return *static_cast<SparseComponentStore<T>*>(it->second.pStore);
2544
2545 auto* pStore = new SparseComponentStore<T>();
2546 m_sparseComponentsByComp.emplace(key, make_sparse_component_store_erased(pStore));
2547 return *pStore;
2548 }
2549
2553 for (auto& [compKey, store]: m_sparseComponentsByComp) {
2554 (void)compKey;
2555 store.func_del(store.pStore, entity);
2556 }
2557 }
2558
2562 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(component));
2563 if (it == m_sparseComponentsByComp.end())
2564 return;
2565
2566 it->second.func_clear_store(it->second.pStore);
2567 it->second.func_del_store(it->second.pStore);
2568 m_sparseComponentsByComp.erase(it);
2569 }
2570
2574 GAIA_NODISCARD const ExclusiveAdjunctStore* exclusive_adjunct_store(Entity relation) const {
2575 const auto it = m_exclusiveAdjunctByRel.find(EntityLookupKey(relation));
2576 if (it == m_exclusiveAdjunctByRel.end())
2577 return nullptr;
2578
2579 return &it->second;
2580 }
2581
2585 GAIA_NODISCARD ExclusiveAdjunctStore& exclusive_adjunct_store_mut(Entity relation) {
2586 return m_exclusiveAdjunctByRel[EntityLookupKey(relation)];
2587 }
2588
2589 static void ensure_exclusive_adjunct_src_capacity(ExclusiveAdjunctStore& store, Entity source) {
2590 const auto required = (uint32_t)source.id() + 1;
2591 if (store.srcToTgt.size() >= required)
2592 return;
2593
2594 const auto oldSize = (uint32_t)store.srcToTgt.size();
2595 auto newSize = oldSize == 0 ? 16U : oldSize;
2596 while (newSize < required)
2597 newSize *= 2U;
2598
2599 store.srcToTgt.resize(newSize, EntityBad);
2600 store.srcToTgtIdx.resize(newSize, BadIndex);
2601 }
2602
2603 static void ensure_exclusive_adjunct_tgt_capacity(ExclusiveAdjunctStore& store, Entity target) {
2604 const auto required = target.id() + 1;
2605 if (store.tgtToSrc.size() >= required)
2606 return;
2607
2608 const auto oldSize = (uint32_t)store.tgtToSrc.size();
2609 auto newSize = oldSize == 0 ? 16U : oldSize;
2610 while (newSize < required)
2611 newSize *= 2U;
2612
2613 store.tgtToSrc.resize(newSize);
2614 }
2615
2616 GAIA_NODISCARD static Entity exclusive_adjunct_target(const ExclusiveAdjunctStore& store, Entity source) {
2617 if (source.id() >= store.srcToTgt.size())
2618 return EntityBad;
2619
2620 return store.srcToTgt[source.id()];
2621 }
2622
2623 GAIA_NODISCARD static const cnt::darray<Entity>*
2624 exclusive_adjunct_sources(const ExclusiveAdjunctStore& store, Entity target) {
2625 if (target.id() >= store.tgtToSrc.size())
2626 return nullptr;
2627
2628 const auto& sources = store.tgtToSrc[target.id()];
2629 return sources.empty() ? nullptr : &sources;
2630 }
2631
2632 static void del_exclusive_adjunct_target_source(ExclusiveAdjunctStore& store, Entity target, Entity source) {
2633 GAIA_ASSERT(target.id() < store.tgtToSrc.size());
2634 if (target.id() >= store.tgtToSrc.size())
2635 return;
2636
2637 auto& sources = store.tgtToSrc[target.id()];
2638 const auto idx = source.id() < store.srcToTgtIdx.size() ? store.srcToTgtIdx[source.id()] : BadIndex;
2639 GAIA_ASSERT(idx != BadIndex && idx < sources.size());
2640 if (idx == BadIndex || idx >= sources.size())
2641 return;
2642
2643 const auto lastIdx = (uint32_t)sources.size() - 1;
2644 if (idx != lastIdx) {
2645 const auto movedSource = sources[lastIdx];
2646 sources[idx] = movedSource;
2647 GAIA_ASSERT(movedSource.id() < store.srcToTgtIdx.size());
2648 store.srcToTgtIdx[movedSource.id()] = idx;
2649 }
2650
2651 sources.pop_back();
2652 }
2653
2654 void exclusive_adjunct_track_src_relation(Entity source, Entity relation) {
2655 auto& rels = m_srcToExclusiveAdjunctRel[EntityLookupKey(source)];
2656 if (!core::has(rels, relation))
2657 rels.push_back(relation);
2658 }
2659
2660 void exclusive_adjunct_untrack_src_relation(Entity source, Entity relation) {
2661 const auto it = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(source));
2662 if (it == m_srcToExclusiveAdjunctRel.end())
2663 return;
2664
2665 auto& rels = it->second;
2666 const auto idx = core::get_index(rels, relation);
2667 if (idx != BadIndex)
2668 core::swap_erase_unsafe(rels, idx);
2669 if (rels.empty())
2670 m_srcToExclusiveAdjunctRel.erase(it);
2671 }
2672
2673 void exclusive_adjunct_set(Entity source, Entity relation, Entity target) {
2674 GAIA_ASSERT(is_exclusive_dont_fragment_relation(relation));
2675
2676 auto& store = exclusive_adjunct_store_mut(relation);
2677 ensure_exclusive_adjunct_src_capacity(store, source);
2678 const auto oldTarget = store.srcToTgt[source.id()];
2679 if (oldTarget != EntityBad) {
2680 if (oldTarget == target)
2681 return;
2682
2683 del_exclusive_adjunct_target_source(store, oldTarget, source);
2684 } else {
2685 ++store.srcToTgtCnt;
2686 exclusive_adjunct_track_src_relation(source, relation);
2687 }
2688
2689 ensure_exclusive_adjunct_tgt_capacity(store, target);
2690 auto& sources = store.tgtToSrc[target.id()];
2691 store.srcToTgt[source.id()] = target;
2692 store.srcToTgtIdx[source.id()] = (uint32_t)sources.size();
2693 sources.push_back(source);
2694
2695 touch_rel_version(relation);
2696 invalidate_queries_for_rel(relation);
2697 m_targetsTravCache = {};
2698 m_srcBfsTravCache = {};
2699 m_depthOrderCache = {};
2700 m_sourcesAllCache = {};
2701 m_targetsAllCache = {};
2702 }
2703
2704 bool exclusive_adjunct_del(Entity source, Entity relation, Entity target) {
2705 const auto itStore = m_exclusiveAdjunctByRel.find(EntityLookupKey(relation));
2706 if (itStore == m_exclusiveAdjunctByRel.end())
2707 return false;
2708
2709 auto& store = itStore->second;
2710 const auto oldTarget = exclusive_adjunct_target(store, source);
2711 if (oldTarget == EntityBad)
2712 return false;
2713 if (target != EntityBad && oldTarget != target)
2714 return false;
2715
2716 del_exclusive_adjunct_target_source(store, oldTarget, source);
2717 store.srcToTgt[source.id()] = EntityBad;
2718 store.srcToTgtIdx[source.id()] = BadIndex;
2719 GAIA_ASSERT(store.srcToTgtCnt > 0);
2720 --store.srcToTgtCnt;
2721
2722 exclusive_adjunct_untrack_src_relation(source, relation);
2723 if (store.srcToTgtCnt == 0)
2724 m_exclusiveAdjunctByRel.erase(itStore);
2725
2726 touch_rel_version(relation);
2727 invalidate_queries_for_rel(relation);
2728 m_targetsTravCache = {};
2729 m_srcBfsTravCache = {};
2730 m_depthOrderCache = {};
2731 m_sourcesAllCache = {};
2732 m_targetsAllCache = {};
2733
2734 return true;
2735 }
2736
2737 GAIA_NODISCARD bool has_exclusive_adjunct_pair(Entity source, Entity object) const {
2738 if (!object.pair())
2739 return false;
2740
2741 const auto srcRelsIt = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(source));
2742 if (srcRelsIt == m_srcToExclusiveAdjunctRel.end())
2743 return false;
2744
2745 const auto relId = object.id();
2746 const auto tgtId = object.gen();
2747
2748 if (relId != All.id()) {
2749 const auto relation = get(relId);
2750 if (!is_exclusive_dont_fragment_relation(relation))
2751 return false;
2752
2753 const auto* pStore = exclusive_adjunct_store(relation);
2754 if (pStore == nullptr)
2755 return false;
2756
2757 const auto target = exclusive_adjunct_target(*pStore, source);
2758 if (target == EntityBad)
2759 return false;
2760
2761 return tgtId == All.id() || target.id() == tgtId;
2762 }
2763
2764 if (tgtId == All.id())
2765 return !srcRelsIt->second.empty();
2766
2767 const auto target = get(tgtId);
2768 for (auto relKey: srcRelsIt->second) {
2769 const auto relation = relKey;
2770 const auto* pStore = exclusive_adjunct_store(relation);
2771 if (pStore == nullptr)
2772 continue;
2773
2774 if (exclusive_adjunct_target(*pStore, source) == target)
2775 return true;
2776 }
2777
2778 return false;
2779 }
2780
2781 void del_exclusive_adjunct_source(Entity source) {
2782 const auto it = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(source));
2783 if (it == m_srcToExclusiveAdjunctRel.end())
2784 return;
2785
2786 cnt::darray<Entity> relations;
2787 for (auto relKey: it->second)
2788 relations.push_back(relKey);
2789
2790 for (auto relation: relations) {
2791 touch_rel_version(relation);
2792 invalidate_queries_for_rel(relation);
2793 (void)exclusive_adjunct_del(source, relation, EntityBad);
2794 }
2795 m_targetsTravCache = {};
2796 m_srcBfsTravCache = {};
2797 m_depthOrderCache = {};
2798 m_sourcesAllCache = {};
2799 m_targetsAllCache = {};
2800 }
2801
2802 void del_exclusive_adjunct_relation(Entity relation) {
2803 const auto itStore = m_exclusiveAdjunctByRel.find(EntityLookupKey(relation));
2804 if (itStore == m_exclusiveAdjunctByRel.end())
2805 return;
2806
2807 cnt::darray<Entity> sources;
2808 const auto& srcToTgt = itStore->second.srcToTgt;
2809 GAIA_FOR((uint32_t)srcToTgt.size()) {
2810 if (srcToTgt[i] == EntityBad)
2811 continue;
2812
2813 sources.push_back(get((EntityId)i));
2814 }
2815
2816 touch_rel_version(relation);
2817 invalidate_queries_for_rel(relation);
2818 for (auto source: sources)
2819 (void)exclusive_adjunct_del(source, relation, EntityBad);
2820 m_targetsTravCache = {};
2821 m_srcBfsTravCache = {};
2822 m_depthOrderCache = {};
2823 m_sourcesAllCache = {};
2824 m_targetsAllCache = {};
2825 }
2826
2828 GAIA_NODISCARD bool has_exclusive_adjunct_target_cond(Entity target, Pair cond) const {
2829 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
2830 if (exclusive_adjunct_sources(store, target) == nullptr)
2831 continue;
2832
2833 if (has(relKey.entity(), cond))
2834 return true;
2835 }
2836
2837 return false;
2838 }
2839
2840 //----------------------------------------------------------------------
2841
2843 void set_serializer(std::nullptr_t) {
2844 // Always use the binary serializer as the default.
2845 m_serializer = ser::make_serializer(m_stream);
2846 }
2847
2851 GAIA_ASSERT(serializer.valid());
2852 m_serializer = serializer;
2853 }
2854
2857 template <typename TSerializer>
2858 void set_serializer(TSerializer& serializer) {
2859 set_serializer(ser::make_serializer(serializer));
2860 }
2861
2865 return m_serializer;
2866 }
2867
2868 //----------------------------------------------------------------------
2869
2870 struct EntityBuilder final {
2871 friend class World;
2872
2873 World& m_world;
2875 Archetype* m_pArchetypeSrc = nullptr;
2877 Chunk* m_pChunkSrc = nullptr;
2879 uint32_t m_rowSrc = 0;
2881 Archetype* m_pArchetype = nullptr;
2889 Entity m_graphEdgeEntity = EntityBad;
2891 uint8_t m_graphEdgeOpCount = 0;
2893 bool m_graphEdgeIsAdd = false;
2894
2895#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
2896 static constexpr uint32_t MAX_TERMS = 32;
2897 static_assert(MAX_TERMS <= ChunkHeader::MAX_COMPONENTS);
2898
2901#endif
2902
2903 EntityBuilder(World& world, Entity entity, EntityContainer& ec):
2904 m_world(world), m_pArchetypeSrc(ec.pArchetype), m_pChunkSrc(ec.pChunk), m_rowSrc(ec.row),
2905 m_pArchetype(ec.pArchetype), m_entity(entity) {
2906 // Make sure entity matches the provided entity container record
2907 GAIA_ASSERT(ec.pChunk->entity_view()[ec.row] == entity);
2908 }
2909
2910 EntityBuilder(World& world, Entity entity): m_world(world), m_entity(entity) {
2911 const auto& ec = world.fetch(entity);
2912 m_pArchetypeSrc = ec.pArchetype;
2913 m_pChunkSrc = ec.pChunk;
2914 m_rowSrc = ec.row;
2915
2916 m_pArchetype = ec.pArchetype;
2917 }
2918
2919 EntityBuilder(const EntityBuilder&) = default;
2920 EntityBuilder(EntityBuilder&&) = delete;
2921 EntityBuilder& operator=(const EntityBuilder&) = delete;
2922 EntityBuilder& operator=(EntityBuilder&&) = delete;
2923
2924 ~EntityBuilder() {
2925 commit();
2926 }
2927
2930 void commit() {
2931 // No requests to change the archetype were made
2932 if (m_pArchetype == nullptr) {
2933 reset_graph_edge_tracking();
2934 return;
2935 }
2936
2937 // Change in archetype detected
2938 if (m_pArchetypeSrc != m_pArchetype) {
2939 auto& ec = m_world.fetch(m_entity);
2940 GAIA_ASSERT(ec.pArchetype == m_pArchetypeSrc);
2941#if GAIA_OBSERVERS_ENABLED
2942 auto delDiffCtx = tl_del_comps.empty() ? ObserverRegistry::DiffDispatchCtx{}
2943 : m_world.m_observers.prepare_diff(
2944 m_world, ObserverEvent::OnDel, EntitySpan{tl_del_comps},
2945 EntitySpan{&m_entity, 1});
2946 auto addDiffCtx = tl_new_comps.empty() ? ObserverRegistry::DiffDispatchCtx{}
2947 : m_world.m_observers.prepare_diff(
2948 m_world, ObserverEvent::OnAdd, EntitySpan{tl_new_comps},
2949 EntitySpan{&m_entity, 1});
2950#endif
2951
2952 // Trigger remove hooks if there are any
2953 trigger_del_hooks(*m_pArchetype);
2954
2955 // Now that we have the final archetype move the entity to it
2956 m_world.move_entity_raw(m_entity, ec, *m_pArchetype);
2957
2958 // Batched builder operations resolve intermediate archetypes without touching the
2959 // graph. Recreate the cached edge only for the single-step case.
2960 if (m_graphEdgeOpCount == 1 && m_graphEdgeEntity != EntityBad) {
2961 if (m_graphEdgeIsAdd)
2962 rebuild_graph_edge(m_pArchetypeSrc, m_pArchetype, m_graphEdgeEntity);
2963 else
2964 rebuild_graph_edge(m_pArchetype, m_pArchetypeSrc, m_graphEdgeEntity);
2965 }
2966
2967 if (m_targetNameKey.str() != nullptr || m_targetAliasKey.str() != nullptr) {
2968 const auto compIdx = ec.pChunk->comp_idx(GAIA_ID(EntityDesc));
2969 // No need to update version, entity move did it already.
2970 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut_gen<false>(compIdx, ec.row));
2971 GAIA_ASSERT(core::check_alignment(pDesc));
2972
2973 // Update the entity name string pointers if necessary
2974 if (m_targetNameKey.str() != nullptr) {
2975 pDesc->name = m_targetNameKey.str();
2976 pDesc->name_len = m_targetNameKey.len();
2977 }
2978
2979 // Update the entity alias string pointers if necessary
2980 if (m_targetAliasKey.str() != nullptr) {
2981 pDesc->alias = m_targetAliasKey.str();
2982 pDesc->alias_len = m_targetAliasKey.len();
2983 }
2984 }
2985
2986 // Trigger add hooks if there are any
2987 trigger_add_hooks(*m_pArchetype);
2988#if GAIA_OBSERVERS_ENABLED
2989 m_world.m_observers.finish_diff(m_world, GAIA_MOV(delDiffCtx));
2990 m_world.m_observers.finish_diff(m_world, GAIA_MOV(addDiffCtx));
2991#endif
2992 cleanup_deleted_out_of_line_components();
2993
2994 m_pArchetypeSrc = ec.pArchetype;
2995 m_pChunkSrc = ec.pChunk;
2996 m_rowSrc = ec.row;
2997 }
2998 // Archetype is still the same. Make sure no chunk movement has happened.
2999 else {
3000#if GAIA_ASSERT_ENABLED
3001 auto& ec = m_world.fetch(m_entity);
3002 GAIA_ASSERT(ec.pChunk == m_pChunkSrc);
3003#endif
3004
3005#if GAIA_OBSERVERS_ENABLED
3006 auto delDiffCtx = tl_del_comps.empty() ? ObserverRegistry::DiffDispatchCtx{}
3007 : m_world.m_observers.prepare_diff(
3008 m_world, ObserverEvent::OnDel, EntitySpan{tl_del_comps},
3009 EntitySpan{&m_entity, 1});
3010 auto addDiffCtx = tl_new_comps.empty() ? ObserverRegistry::DiffDispatchCtx{}
3011 : m_world.m_observers.prepare_diff(
3012 m_world, ObserverEvent::OnAdd, EntitySpan{tl_new_comps},
3013 EntitySpan{&m_entity, 1});
3014#endif
3015
3016 if (m_targetNameKey.str() != nullptr || m_targetAliasKey.str() != nullptr) {
3017 const auto compIdx = m_pChunkSrc->comp_idx(GAIA_ID(EntityDesc));
3018 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<true>(compIdx, m_rowSrc));
3019 GAIA_ASSERT(core::check_alignment(pDesc));
3020
3021 // Update the entity name string pointers if necessary
3022 if (m_targetNameKey.str() != nullptr) {
3023 pDesc->name = m_targetNameKey.str();
3024 pDesc->name_len = m_targetNameKey.len();
3025 }
3026
3027 // Update the entity alias string pointers if necessary
3028 if (m_targetAliasKey.str() != nullptr) {
3029 pDesc->alias = m_targetAliasKey.str();
3030 pDesc->alias_len = m_targetAliasKey.len();
3031 }
3032 }
3033
3034#if GAIA_OBSERVERS_ENABLED
3035 m_world.m_observers.finish_diff(m_world, GAIA_MOV(delDiffCtx));
3036 m_world.m_observers.finish_diff(m_world, GAIA_MOV(addDiffCtx));
3037#endif
3038 cleanup_deleted_out_of_line_components();
3039 }
3040
3041 // Finalize the builder by reseting the archetype pointer
3042 m_pArchetype = nullptr;
3043 m_targetNameKey = {};
3044 m_targetAliasKey = {};
3045 reset_graph_edge_tracking();
3046 }
3047
3055 void name(const char* name, uint32_t len = 0) {
3056 name_inter<true>(name, len);
3057 }
3058
3070 void name_raw(const char* name, uint32_t len = 0) {
3071 name_inter<false>(name, len);
3072 }
3073
3078 void alias(const char* alias, uint32_t len = 0) {
3079 alias_inter<true>(alias, len);
3080 }
3081
3086 void alias_raw(const char* alias, uint32_t len = 0) {
3087 alias_inter<false>(alias, len);
3088 }
3089
3091 void del_name() {
3092 if (m_entity.pair())
3093 return;
3094
3095 // The following block is essentially the same as this but without the archetype pointer access:
3096 // const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
3097 const auto compIdx = core::get_index(m_pChunkSrc->ids_view(), GAIA_ID(EntityDesc));
3098 if (compIdx == BadIndex)
3099 return;
3100
3101 {
3102 const auto* pDesc = reinterpret_cast<const EntityDesc*>(m_pChunkSrc->comp_ptr(compIdx, m_rowSrc));
3103 GAIA_ASSERT(core::check_alignment(pDesc));
3104 if (pDesc->name == nullptr)
3105 return;
3106 }
3107
3108 // TODO: Trigger the hooks/observers manually here? I do not like essentially calling comp_ptr twice here.
3109 // The second one could be replaced with const cast + emit.
3110 // No need to update version, commit() will do it
3111 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<false>(compIdx, m_rowSrc));
3112 del_name_inter(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0));
3113 m_world.invalidate_scope_path_cache();
3114
3115 pDesc->name = nullptr;
3116 pDesc->name_len = 0;
3117 if (pDesc->alias == nullptr)
3118 del_inter(GAIA_ID(EntityDesc));
3119
3120 m_targetNameKey = {};
3121 }
3122
3124 void del_alias() {
3125 if (m_entity.pair())
3126 return;
3127
3128 // The following block is essentially the same as this but without the archetype pointer access:
3129 // const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
3130 const auto compIdx = core::get_index(m_pChunkSrc->ids_view(), GAIA_ID(EntityDesc));
3131 if (compIdx == BadIndex)
3132 return;
3133
3134 {
3135 const auto* pDesc = reinterpret_cast<const EntityDesc*>(m_pChunkSrc->comp_ptr(compIdx, m_rowSrc));
3136 GAIA_ASSERT(core::check_alignment(pDesc));
3137 if (pDesc->alias == nullptr)
3138 return;
3139 }
3140
3141 // TODO: Trigger the hooks/observers manually here? I do not like essentially calling comp_ptr twice here.
3142 // The second one could be replaced with const cast + emit.
3143 // No need to update version, commit() will do it
3144 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut_gen<false>(compIdx, m_rowSrc));
3145 del_alias_inter(EntityNameLookupKey(pDesc->alias, pDesc->alias_len, 0));
3146
3147 pDesc->alias = nullptr;
3148 pDesc->alias_len = 0;
3149 if (pDesc->name == nullptr)
3150 del_inter(GAIA_ID(EntityDesc));
3151
3152 m_targetAliasKey = {};
3153 }
3154
3158 GAIA_PROF_SCOPE(EntityBuilder::add);
3159 GAIA_ASSERT(m_world.valid(m_entity));
3160 GAIA_ASSERT(m_world.valid(entity));
3161
3162 add_inter(entity);
3163 return *this;
3164 }
3165
3169 GAIA_PROF_SCOPE(EntityBuilder::add);
3170 GAIA_ASSERT(m_world.valid(m_entity));
3171 GAIA_ASSERT(m_world.valid(pair.first()));
3172 GAIA_ASSERT(m_world.valid(pair.second()));
3173
3174 add_inter(pair);
3175 return *this;
3176 }
3177
3181 EntityBuilder& as(Entity entityBase) {
3182 return add(Pair(Is, entityBase));
3183 }
3184
3187 return add(Prefab);
3188 }
3189
3194 GAIA_NODISCARD bool as(Entity entity, Entity entityBase) const {
3195 return static_cast<const World&>(m_world).is(entity, entityBase);
3196 }
3197
3200 return add(Pair(ChildOf, parent));
3201 }
3202
3204 template <typename T>
3206 if constexpr (is_pair<T>::value) {
3207 const auto rel = m_world.template reg_comp<typename T::rel>().entity;
3208 const auto tgt = m_world.template reg_comp<typename T::tgt>().entity;
3209 const Entity ent = Pair(rel, tgt);
3210 add_inter(ent);
3211 return ent;
3212 } else {
3213 return m_world.template reg_comp<T>().entity;
3214 }
3215 }
3216
3217 template <typename T>
3218 EntityBuilder& add() {
3219 verify_comp<T>();
3220 add(register_component<T>());
3221 return *this;
3222 }
3223
3227 GAIA_PROF_SCOPE(EntityBuilder::del);
3228 GAIA_ASSERT(m_world.valid(m_entity));
3229 GAIA_ASSERT(m_world.valid(entity));
3230 del_inter(entity);
3231 return *this;
3232 }
3233
3237 GAIA_PROF_SCOPE(EntityBuilder::add);
3238 GAIA_ASSERT(m_world.valid(m_entity));
3239 GAIA_ASSERT(m_world.valid(pair.first()));
3240 GAIA_ASSERT(m_world.valid(pair.second()));
3241 del_inter(pair);
3242 return *this;
3243 }
3244
3245 template <typename T>
3246 EntityBuilder& del() {
3247 verify_comp<T>();
3248 del(register_component<T>());
3249 return *this;
3250 }
3251
3252 private:
3255 void trigger_add_hooks(const Archetype& newArchetype) {
3256#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
3257 if GAIA_UNLIKELY (m_world.tearing_down()) {
3258 tl_new_comps.clear();
3259 (void)newArchetype;
3260 return;
3261 }
3262
3263 m_world.lock();
3264
3265 #if GAIA_ENABLE_ADD_DEL_HOOKS
3266 // if (hookCnt > 0)
3267 {
3268 // Trigger component hooks first
3269 for (auto entity: tl_new_comps) {
3270 if (!entity.comp())
3271 continue;
3272
3273 const auto& item = m_world.comp_cache().get(entity);
3274 const auto& hooks = ComponentCache::hooks(item);
3275 if (hooks.func_add != nullptr)
3276 hooks.func_add(m_world, item, m_entity);
3277 }
3278 }
3279 #endif
3280
3281 #if GAIA_OBSERVERS_ENABLED
3282 // Trigger observers second
3283 m_world.m_observers.on_add(m_world, newArchetype, std::span<Entity>{tl_new_comps}, {&m_entity, 1});
3284 #else
3285 (void)newArchetype;
3286 #endif
3287
3288 tl_new_comps.clear();
3289
3290 m_world.unlock();
3291#endif
3292 }
3293
3296 void trigger_del_hooks(const Archetype& newArchetype) {
3297#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
3298 if GAIA_UNLIKELY (m_world.tearing_down()) {
3299 tl_del_comps.clear();
3300 (void)newArchetype;
3301 return;
3302 }
3303
3304 m_world.notify_inherited_del_dependents(m_entity, std::span<Entity>{tl_del_comps});
3305 m_world.lock();
3306
3307 #if GAIA_OBSERVERS_ENABLED
3308 // Trigger observers first
3309 m_world.m_observers.on_del(m_world, newArchetype, std::span<Entity>{tl_del_comps}, {&m_entity, 1});
3310 #else
3311 (void)newArchetype;
3312 #endif
3313
3314 #if GAIA_ENABLE_ADD_DEL_HOOKS
3315 // if (hookCnt > 0)
3316 {
3317 // Trigger component hooks second
3318 for (auto entity: tl_del_comps) {
3319 if (!entity.comp())
3320 continue;
3321
3322 const auto& item = m_world.comp_cache().get(entity);
3323 const auto& hooks = ComponentCache::hooks(item);
3324 if (hooks.func_del != nullptr)
3325 hooks.func_del(m_world, item, m_entity);
3326 }
3327 }
3328 #endif
3329
3330 m_world.unlock();
3331#endif
3332 }
3333
3334 void cleanup_deleted_out_of_line_components() {
3335 for (auto entity: tl_del_comps) {
3336 if (entity.pair() || !m_world.is_out_of_line_component(entity) ||
3337 m_world.is_non_fragmenting_out_of_line_component(entity))
3338 continue;
3339
3340 const auto it = m_world.m_sparseComponentsByComp.find(EntityLookupKey(entity));
3341 if (it != m_world.m_sparseComponentsByComp.end())
3342 it->second.func_del(it->second.pStore, m_entity);
3343 }
3344
3345 tl_del_comps.clear();
3346 }
3347
3348 bool handle_add_entity(Entity entity) {
3349 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> targets;
3350
3351 const auto& ecMain = m_world.fetch(entity);
3352 if (entity.pair())
3353 m_world.invalidate_scope_path_cache();
3354
3355 // Handle entity combinations that can't be together
3356 if ((ecMain.flags & EntityContainerFlags::HasCantCombine) != 0) {
3357 m_world.targets(entity, CantCombine, [&targets](Entity target) {
3358 targets.push_back(target);
3359 });
3360 for (auto e: targets) {
3361 if (m_pArchetype->has(e)) {
3362#if GAIA_ASSERT_ENABLED
3363 GAIA_ASSERT2(false, "Trying to add an entity which can't be combined with the source");
3364 print_archetype_entities(m_world, *m_pArchetype, entity, true);
3365#endif
3366 return false;
3367 }
3368 }
3369 }
3370
3371 // Handle exclusivity
3372 if (entity.pair()) {
3373 // Check if (rel, tgt)'s rel part is exclusive
3374 const auto& ecRel = m_world.m_recs.entities[entity.id()];
3375 if ((ecRel.flags & EntityContainerFlags::IsExclusive) != 0) {
3376 auto rel = Entity(
3377 entity.id(), ecRel.data.gen, (bool)ecRel.data.ent, (bool)ecRel.data.pair,
3378 (EntityKind)ecRel.data.kind);
3379 auto tgt = m_world.try_get(entity.gen());
3380 if (tgt == EntityBad)
3381 return false;
3382
3383 // Make sure to remove the (rel, tgt0) so only the new (rel, tgt1) remains.
3384 // However, before that we need to make sure there only exists one target at most.
3385 targets.clear();
3386 m_world.targets_if(m_entity, rel, [&targets](Entity target) {
3387 targets.push_back(target);
3388 // Stop the moment we have more than 1 target. This kind of scenario is not supported
3389 // and can happen only if Exclusive is added after multiple relationships already exist.
3390 return targets.size() < 2;
3391 });
3392
3393 const auto targetsCnt = targets.size();
3394 if (targetsCnt > 1) {
3395#if GAIA_ASSERT_ENABLED
3396 GAIA_ASSERT2(
3397 false, "Trying to add a pair with exclusive relationship but there are multiple targets present. "
3398 "Make sure to add the Exclusive property before any relationships with it are created.");
3399 print_archetype_entities(m_world, *m_pArchetype, entity, true);
3400#endif
3401 return false;
3402 }
3403
3404 // Remove the previous relationship if possible.
3405 // We avoid self-removal.
3406 const auto tgtNew = *targets.begin();
3407 if (targetsCnt == 1 && tgt != tgtNew) {
3408 // Exclusive relationship replaces the previous one.
3409 // We need to check if the old one can be removed.
3410 // This is what del_inter does on the inside.
3411 // It first checks if entity can be deleted and calls handle_del afterwards.
3412 if (!can_del(entity)) {
3413#if GAIA_ASSERT_ENABLED
3414 GAIA_ASSERT2(
3415 false, "Trying to replace an exclusive relationship but the entity which is getting removed has "
3416 "dependencies.");
3417 print_archetype_entities(m_world, *m_pArchetype, entity, true);
3418#endif
3419 return false;
3420 }
3421
3422 handle_del(ecs::Pair(rel, tgtNew));
3423 }
3424 }
3425 }
3426
3427 // Handle requirements
3428 {
3429 targets.clear();
3430 m_world.targets(entity, Requires, [&targets](Entity target) {
3431 targets.push_back(target);
3432 });
3433
3434 for (auto e: targets) {
3435 auto* pArchetype = m_pArchetype;
3436 handle_add<false>(e);
3437 if (m_pArchetype != pArchetype)
3438 handle_add_entity(e);
3439 }
3440 }
3441
3442 return true;
3443 }
3444
3445 GAIA_NODISCARD bool has_Requires_tgt(Entity entity) const {
3446 // Don't allow to delete entity if something in the archetype requires it
3447 auto ids = m_pArchetype->ids_view();
3448 for (auto e: ids) {
3449 if (m_world.has(e, Pair(Requires, entity)))
3450 return true;
3451 }
3452
3453 return false;
3454 }
3455
3456 static void set_flag(EntityContainerFlagsType& flags, EntityContainerFlags flag, bool enable) {
3457 if (enable)
3458 flags |= flag;
3459 else
3460 flags &= ~flag;
3461 }
3462
3463 void set_flag(Entity entity, EntityContainerFlags flag, bool enable) {
3464 auto& ec = m_world.fetch(entity);
3465 set_flag(ec.flags, flag, enable);
3466 }
3467
3468 void try_set_flags(Entity entity, bool enable) {
3469 auto& ecMain = m_world.fetch(m_entity);
3470 try_set_CantCombine(ecMain, entity, enable);
3471
3472 auto& ec = m_world.fetch(entity);
3473 try_set_Is(ec, entity, enable);
3474 try_set_IsExclusive(ecMain, entity, enable);
3475 if (enable)
3476 try_set_sticky_component_traits(ecMain, entity);
3477 try_set_IsSingleton(ecMain, entity, enable);
3478 try_set_OnDelete(ecMain, entity, enable);
3479 try_set_OnDeleteTarget(entity, enable);
3480 }
3481
3482 void try_set_Is(EntityContainer& ec, Entity entity, bool enable) {
3483 if (!entity.pair() || entity.id() != Is.id())
3484 return;
3485
3486 set_flag(ec.flags, EntityContainerFlags::HasAliasOf, enable);
3487 }
3488
3489 void try_set_CantCombine(EntityContainer& ec, Entity entity, bool enable) {
3490 if (!entity.pair() || entity.id() != CantCombine.id())
3491 return;
3492
3493 GAIA_ASSERT(entity != m_entity);
3494
3495 // Setting the flag can be done right away.
3496 // One bit can only contain information about one pair but there
3497 // can be any amount of CanCombine pairs formed with an entity.
3498 // Therefore, when resetting the flag, we first need to check if there
3499 // are any other targets with this flag set and only reset the flag
3500 // if there is only one present.
3501 if (enable)
3502 set_flag(ec.flags, EntityContainerFlags::HasCantCombine, true);
3503 else if ((ec.flags & EntityContainerFlags::HasCantCombine) != 0) {
3504 uint32_t targets = 0;
3505 m_world.targets(m_entity, CantCombine, [&targets]([[maybe_unused]] Entity entity) {
3506 ++targets;
3507 });
3508 if (targets == 1)
3509 set_flag(ec.flags, EntityContainerFlags::HasCantCombine, false);
3510 }
3511 }
3512
3513 void try_set_IsExclusive(EntityContainer& ec, Entity entity, bool enable) {
3514 if (entity.pair() || entity.id() != Exclusive.id())
3515 return;
3516
3517 set_flag(ec.flags, EntityContainerFlags::IsExclusive, enable);
3518 }
3519
3520 void try_set_sticky_component_traits(EntityContainer& ecMain, Entity entity) {
3521 if (entity.pair())
3522 return;
3523
3524 if (entity.id() == DontFragment.id()) {
3525 m_world.set_component_dont_fragment(m_entity, ecMain);
3526 return;
3527 }
3528
3529 if (entity.id() == Sparse.id())
3530 m_world.set_component_sparse_storage(m_entity);
3531 }
3532
3533 void try_set_OnDeleteTarget(Entity entity, bool enable) {
3534 if (!entity.pair())
3535 return;
3536
3537 const auto rel = m_world.try_get(entity.id());
3538 const auto tgt = m_world.try_get(entity.gen());
3539 if (rel == EntityBad || tgt == EntityBad)
3540 return;
3541
3542 // Adding a pair to an entity with OnDeleteTarget relationship.
3543 // We need to update the target entity's flags.
3544 if (m_world.has(rel, Pair(OnDeleteTarget, Delete)))
3545 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Delete, enable);
3546 else if (m_world.has(rel, Pair(OnDeleteTarget, Remove)))
3547 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Remove, enable);
3548 else if (m_world.has(rel, Pair(OnDeleteTarget, Error)))
3549 set_flag(tgt, EntityContainerFlags::OnDeleteTarget_Error, enable);
3550 }
3551
3552 void try_set_OnDelete(EntityContainer& ec, Entity entity, bool enable) {
3553 if (entity == Pair(OnDelete, Delete))
3554 set_flag(ec.flags, EntityContainerFlags::OnDelete_Delete, enable);
3555 else if (entity == Pair(OnDelete, Remove))
3556 set_flag(ec.flags, EntityContainerFlags::OnDelete_Remove, enable);
3557 else if (entity == Pair(OnDelete, Error))
3558 set_flag(ec.flags, EntityContainerFlags::OnDelete_Error, enable);
3559 }
3560
3561 void try_set_IsSingleton(EntityContainer& ec, Entity entity, bool enable) {
3562 const bool isSingleton = enable && m_entity == entity;
3563 set_flag(ec.flags, EntityContainerFlags::IsSingleton, isSingleton);
3564 }
3565
3566 void handle_DependsOn(Entity entity, bool enable) {
3567 (void)entity;
3568 (void)enable;
3569 // auto& ec = m_world.fetch(entity);
3570 // if (enable) {
3571 // // Calculate the depth in the dependency tree
3572 // uint32_t depth = 1;
3573
3574 // auto e = entity;
3575 // if (m_world.valid(e)) {
3576 // while (true) {
3577 // auto tgt = m_world.target(e, DependsOn);
3578 // if (tgt == EntityBad)
3579 // break;
3580
3581 // ++depth;
3582 // e = tgt;
3583 // }
3584 // }
3585 // ec.depthDependsOn = (uint8_t)depth;
3586
3587 // // Update depth for all entities depending on this one
3588 // auto q = m_world.uquery();
3589 // q.all(ecs::Pair(DependsOn, m_entity)) //
3590 // .each([&](Entity dependingEntity) {
3591 // auto& ecDependingEntity = m_world.fetch(dependingEntity);
3592 // ecDependingEntity.depthDependsOn += (uint8_t)depth;
3593 // });
3594 // } else {
3595 // // Update depth for all entities depending on this one
3596 // auto q = m_world.uquery();
3597 // q.all(ecs::Pair(DependsOn, m_entity)) //
3598 // .each([&](Entity dependingEntity) {
3599 // auto& ecDependingEntity = m_world.fetch(dependingEntity);
3600 // ecDependingEntity.depthDependsOn -= ec.depthDependsOn;
3601 // });
3602
3603 // // Reset the depth
3604 // ec.depthDependsOn = 0;
3605 // }
3606 }
3607
3608 template <bool IsBootstrap>
3609 bool handle_add(Entity entity) {
3610 const bool isDontFragmentPair =
3611 entity.pair() && m_world.is_exclusive_dont_fragment_relation(m_world.get(entity.id()));
3612#if GAIA_ASSERT_ENABLED
3613 if (!isDontFragmentPair)
3614 World::verify_add(m_world, *m_pArchetype, m_entity, entity);
3615#endif
3616
3617 // Don't add the same entity twice
3618 if (isDontFragmentPair ? m_world.has(m_entity, entity) : m_pArchetype->has(entity))
3619 return false;
3620
3621 if (entity.pair()) {
3622 auto relation = m_world.get(entity.id());
3623 m_world.touch_rel_version(relation);
3624 m_world.invalidate_queries_for_rel(relation);
3625 m_world.m_targetsTravCache = {};
3626 m_world.m_srcBfsTravCache = {};
3627 m_world.m_depthOrderCache = {};
3628 m_world.m_sourcesAllCache = {};
3629 m_world.m_targetsAllCache = {};
3630 }
3631
3632 try_set_flags(entity, true);
3633
3634 // Update the Is relationship base counter if necessary
3635 if (entity.pair() && entity.id() == Is.id()) {
3636 auto e = m_world.try_get(entity.gen());
3637 if (e == EntityBad)
3638 return false;
3639
3640 EntityLookupKey entityKey(m_entity);
3641 EntityLookupKey eKey(e);
3642
3643 // m_entity -> {..., e}
3644 auto& entity_to_e = m_world.m_entityToAsTargets[entityKey];
3645 entity_to_e.insert(eKey);
3646 m_world.m_entityToAsTargetsTravCache = {};
3647 // e -> {..., m_entity}
3648 auto& e_to_entity = m_world.m_entityToAsRelations[eKey];
3649 e_to_entity.insert(entityKey);
3650 m_world.m_entityToAsRelationsTravCache = {};
3651
3652 // Make sure the relation entity is registered as archetype so queries can find it
3653 // auto& ec = m_world.fetch(tgt);
3654 // m_world.add_entity_archetype_pair(m_entity, ec.pArchetype);
3655
3656 // Cached queries might need to be invalidated.
3657 m_world.invalidate_queries_for_entity({Is, e});
3658 }
3659
3660 if (isDontFragmentPair) {
3661 const auto relation = m_world.try_get(entity.id());
3662 const auto target = m_world.try_get(entity.gen());
3663 if (relation == EntityBad || target == EntityBad)
3664 return false;
3665
3666 m_world.exclusive_adjunct_set(m_entity, relation, target);
3667 } else {
3668 m_pArchetype = m_world.foc_archetype_add_no_graph(m_pArchetype, entity);
3669 note_graph_edge(entity, true);
3670 }
3671
3672 if constexpr (!IsBootstrap) {
3673 handle_DependsOn(entity, true);
3674
3675#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
3676 tl_new_comps.push_back(entity);
3677#endif
3678 }
3679
3680 return true;
3681 }
3682
3683 void handle_del(Entity entity) {
3684 if (entity.pair() && !m_world.valid(entity)) {
3685 if (m_pArchetype->has(entity)) {
3686 const auto relation = m_world.try_get(entity.id());
3687 if (relation != EntityBad) {
3688 m_world.touch_rel_version(relation);
3689 m_world.invalidate_queries_for_rel(relation);
3690 m_world.m_targetsTravCache = {};
3691 m_world.m_srcBfsTravCache = {};
3692 m_world.m_depthOrderCache = {};
3693 m_world.m_sourcesAllCache = {};
3694 m_world.m_targetsAllCache = {};
3695 }
3696
3697 if (entity.id() == Is.id())
3698 m_world.unlink_stale_is_relations_by_target_id(m_entity, entity.gen());
3699
3700 m_pArchetype = m_world.foc_archetype_del_no_graph(m_pArchetype, entity);
3701 note_graph_edge(entity, false);
3702 }
3703 return;
3704 }
3705
3706 const bool isDontFragmentPair =
3707 entity.pair() && m_world.is_exclusive_dont_fragment_relation(m_world.get(entity.id()));
3708 if (entity.pair())
3709 m_world.invalidate_scope_path_cache();
3710
3711#if GAIA_ASSERT_ENABLED
3712 if (!isDontFragmentPair)
3713 World::verify_del(m_world, *m_pArchetype, m_entity, entity);
3714#endif
3715
3716 // Don't delete what has not beed added
3717 if (!(isDontFragmentPair ? m_world.has(m_entity, entity) : m_pArchetype->has(entity)))
3718 return;
3719
3720 if (entity.pair()) {
3721 auto relation = m_world.get(entity.id());
3722 m_world.touch_rel_version(relation);
3723 m_world.invalidate_queries_for_rel(relation);
3724 m_world.m_targetsTravCache = {};
3725 m_world.m_srcBfsTravCache = {};
3726 m_world.m_depthOrderCache = {};
3727 m_world.m_sourcesAllCache = {};
3728 m_world.m_targetsAllCache = {};
3729 }
3730
3731 try_set_flags(entity, false);
3732 handle_DependsOn(entity, false);
3733
3734 // Update the Is relationship base counter if necessary
3735 if (entity.pair() && entity.id() == Is.id()) {
3736 auto e = m_world.try_get(entity.gen());
3737 if (e != EntityBad) {
3738 m_world.unlink_live_is_relation(m_entity, e);
3739 }
3740 }
3741
3742 if (isDontFragmentPair) {
3743 const auto relation = m_world.try_get(entity.id());
3744 const auto target = m_world.try_get(entity.gen());
3745 if (relation != EntityBad && target != EntityBad)
3746 (void)m_world.exclusive_adjunct_del(m_entity, relation, target);
3747 } else {
3748 m_pArchetype = m_world.foc_archetype_del_no_graph(m_pArchetype, entity);
3749 note_graph_edge(entity, false);
3750 }
3751
3752#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
3753 tl_del_comps.push_back(entity);
3754#endif
3755 }
3756
3757 void add_inter(Entity entity) {
3758 GAIA_ASSERT(!is_wildcard(entity));
3759
3760 if (entity.pair()) {
3761 // Make sure the entity container record exists if it is a pair
3762 m_world.assign_pair(entity, *m_world.m_pEntityArchetype);
3763 }
3764
3765 if (!handle_add_entity(entity))
3766 return;
3767
3768 handle_add<false>(entity);
3769 }
3770
3771 void note_graph_edge(Entity entity, bool isAdd) {
3772 ++m_graphEdgeOpCount;
3773 m_graphEdgeEntity = entity;
3774 m_graphEdgeIsAdd = isAdd;
3775 }
3776
3777 void reset_graph_edge_tracking() {
3778 m_graphEdgeEntity = EntityBad;
3779 m_graphEdgeOpCount = 0;
3780 m_graphEdgeIsAdd = false;
3781 }
3782
3785 static void rebuild_graph_edge(Archetype* pArchetypeLeft, Archetype* pArchetypeRight, Entity entity) {
3786 pArchetypeLeft->del_graph_edge_right_local(entity);
3787 pArchetypeRight->del_graph_edge_left_local(entity);
3788 pArchetypeLeft->build_graph_edges(pArchetypeRight, entity);
3789 }
3790
3791 void add_inter_init(Entity entity) {
3792 GAIA_ASSERT(!is_wildcard(entity));
3793
3794 if (entity.pair()) {
3795 // Make sure the entity container record exists if it is a pair
3796 m_world.assign_pair(entity, *m_world.m_pEntityArchetype);
3797 }
3798
3799 if (!handle_add_entity(entity))
3800 return;
3801
3802 handle_add<true>(entity);
3803 }
3804
3805 GAIA_NODISCARD bool can_del(Entity entity) const noexcept {
3806 if (has_Requires_tgt(entity))
3807 return false;
3808
3809 return true;
3810 }
3811
3812 GAIA_NODISCARD bool is_sticky_component_trait(Entity entity) const noexcept {
3813 if (entity.pair() || !m_entity.comp())
3814 return false;
3815
3816 return entity.id() == DontFragment.id() || entity.id() == Sparse.id();
3817 }
3818
3819 bool del_inter(Entity entity) {
3820 if (is_sticky_component_trait(entity))
3821 return true;
3822
3823 if (!can_del(entity))
3824 return false;
3825
3826 handle_del(entity);
3827 return true;
3828 }
3829
3830 void del_name_inter(EntityNameLookupKey key) {
3831 const auto it = m_world.m_nameToEntity.find(key);
3832 // If the assert is hit it means the pointer to the name string was invalidated or became dangling.
3833 // That should not be possible for strings managed internally so the only other option is user-managed
3834 // strings are broken.
3835 GAIA_ASSERT(it != m_world.m_nameToEntity.end());
3836 if (it != m_world.m_nameToEntity.end()) {
3837 // Release memory allocated for the string if we own it
3838 if (it->first.owned())
3839 mem::mem_free((void*)key.str());
3840
3841 m_world.m_nameToEntity.erase(it);
3842 }
3843 }
3844
3845 void del_alias_inter(EntityNameLookupKey key) {
3846 const auto it = m_world.m_aliasToEntity.find(key);
3847 // If the assert is hit it means the pointer to the name string was invalidated or became dangling.
3848 // That should not be possible for strings managed internally so the only other option is user-managed
3849 // strings are broken.
3850 GAIA_ASSERT(it != m_world.m_aliasToEntity.end());
3851 if (it != m_world.m_aliasToEntity.end()) {
3852 // Release memory allocated for the string if we own it
3853 if (it->first.owned())
3854 mem::mem_free((void*)key.str());
3855
3856 m_world.m_aliasToEntity.erase(it);
3857 }
3858 }
3859
3860 template <bool IsOwned>
3861 void name_inter(const char* name, uint32_t len) {
3863 GAIA_ASSERT(!m_entity.pair());
3864 if (m_entity.pair())
3865 return;
3866
3867 // When nullptr is passed for the name it means the user wants to delete the current one
3868 if (name == nullptr) {
3869 GAIA_ASSERT(len == 0);
3870 del_name();
3871 return;
3872 }
3873
3874 GAIA_ASSERT(len < ComponentCacheItem::MaxNameLength);
3875
3876 // Make sure the name does not contain a dot because this character is reserved for
3877 // hierarchical lookups, e.g. "parent.child.subchild".
3878 GAIA_FOR(len) {
3879 const bool hasInvalidCharacter = name[i] == '.';
3880 GAIA_ASSERT(!hasInvalidCharacter && "Character '.' can't be used in entity names");
3881 if (hasInvalidCharacter)
3882 return;
3883 }
3884
3885 EntityNameLookupKey key(
3886 name, len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len, IsOwned);
3887
3888 // Make sure the name is unique. Ignore setting the same name twice on the same entity.
3889 // If it is not, there is nothing to do.
3890 auto it = m_world.m_nameToEntity.find(key);
3891 if (it == m_world.m_nameToEntity.end()) {
3892 // If we already had some name, remove the pair from the map first.
3893 if (m_targetNameKey.str() != nullptr) {
3894 del_name_inter(m_targetNameKey);
3895 } else {
3896 const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
3897 if (compIdx != BadIndex) {
3898 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut(compIdx, m_rowSrc));
3899 GAIA_ASSERT(core::check_alignment(pDesc));
3900 if (pDesc->name != nullptr) {
3901 del_name_inter(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0));
3902 pDesc->name = nullptr;
3903 }
3904 } else {
3905 // Make sure EntityDesc is added to the entity.
3906 add_inter(GAIA_ID(EntityDesc));
3907 }
3908 }
3909
3910 // Insert the new pair
3911 it = m_world.m_nameToEntity.emplace(key, m_entity).first;
3912 } else {
3913#if GAIA_ASSERT_ENABLED
3914 if (it->second != m_entity && World::s_enableUniqueNameDuplicateAssert)
3915 GAIA_ASSERT(false && "Trying to set non-unique name for an entity");
3916#endif
3917
3918 // Attempts to set the same name again, or not a unique name, will be dropped.
3919 return;
3920 }
3921
3922 if constexpr (IsOwned) {
3923 // Allocate enough storage for the name
3924 char* entityStr = (char*)mem::mem_alloc(key.len() + 1);
3925 memcpy((void*)entityStr, (const void*)name, key.len());
3926 entityStr[key.len()] = 0;
3927
3928 m_targetNameKey = EntityNameLookupKey(entityStr, key.len(), 1, {key.hash()});
3929
3930 // Update the map so it points to the newly allocated string.
3931 // We replace the pointer we provided in try_emplace with an internally allocated string.
3932 auto p = robin_hood::pair(std::make_pair(m_targetNameKey, m_entity));
3933 it->swap(p);
3934 } else {
3935 m_targetNameKey = key;
3936
3937 // We tell the map the string is non-owned.
3938 auto p = robin_hood::pair(std::make_pair(key, m_entity));
3939 it->swap(p);
3940 }
3941
3942 m_world.invalidate_scope_path_cache();
3943 }
3944
3945 template <bool IsOwned>
3946 void alias_inter(const char* alias, uint32_t len) {
3948 GAIA_ASSERT(!m_entity.pair());
3949 if (m_entity.pair())
3950 return;
3951
3952 // When nullptr is passed for the alias it means the user wants to delete the current one
3953 if (alias == nullptr) {
3954 GAIA_ASSERT(len == 0);
3955 del_alias();
3956 return;
3957 }
3958
3959 GAIA_ASSERT(len < ComponentCacheItem::MaxNameLength);
3960
3961 // Make sure the name does not contain a dot because this character is reserved for
3962 // hierarchical lookups, e.g. "parent.child.subchild".
3963 GAIA_FOR(len) {
3964 const bool hasInvalidCharacter = alias[i] == '.';
3965 GAIA_ASSERT(!hasInvalidCharacter && "Character '.' can't be used in entity aliases");
3966 if (hasInvalidCharacter)
3967 return;
3968 }
3969
3970 EntityNameLookupKey key(
3971 alias, len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len, IsOwned);
3972
3973 auto it = m_world.m_aliasToEntity.find(key);
3974 if (it == m_world.m_aliasToEntity.end()) {
3975 // If we already had some alias, remove the pair from the map first.
3976 if (m_targetAliasKey.str() != nullptr) {
3977 del_alias_inter(m_targetAliasKey);
3978 } else {
3979 const auto compIdx = core::get_index(m_pArchetypeSrc->ids_view(), GAIA_ID(EntityDesc));
3980 if (compIdx != BadIndex) {
3981 auto* pDesc = reinterpret_cast<EntityDesc*>(m_pChunkSrc->comp_ptr_mut(compIdx, m_rowSrc));
3982 GAIA_ASSERT(core::check_alignment(pDesc));
3983 if (pDesc->alias != nullptr) {
3984 del_alias_inter(EntityNameLookupKey(pDesc->alias, pDesc->alias_len, 0));
3985 pDesc->alias = nullptr;
3986 }
3987 } else {
3988 // Make sure EntityDesc is added to the entity.
3989 add_inter(GAIA_ID(EntityDesc));
3990 }
3991 }
3992
3993 it = m_world.m_aliasToEntity.emplace(key, m_entity).first;
3994 } else {
3995#if GAIA_ASSERT_ENABLED
3996 if (it->second != m_entity && World::s_enableUniqueNameDuplicateAssert)
3997 GAIA_ASSERT(false && "Trying to set non-unique alias for an entity");
3998#endif
3999
4000 // Attempts to set the same alias again, or not a unique alias, will be dropped.
4001 return;
4002 }
4003
4004 if constexpr (IsOwned) {
4005 // Allocate enough storage for the alias
4006 char* aliasStr = (char*)mem::mem_alloc(key.len() + 1);
4007 memcpy((void*)aliasStr, (const void*)alias, key.len());
4008 aliasStr[key.len()] = 0;
4009
4010 m_targetAliasKey = EntityNameLookupKey(aliasStr, key.len(), 1, {key.hash()});
4011
4012 // Update the map so it points to the newly allocated string.
4013 // We replace the pointer we provided in try_emplace with an internally allocated string.
4014 auto p = robin_hood::pair(std::make_pair(m_targetAliasKey, m_entity));
4015 it->swap(p);
4016 } else {
4017 m_targetAliasKey = key;
4018
4019 // We tell the map the string is non-owned.
4020 auto p = robin_hood::pair(std::make_pair(key, m_entity));
4021 it->swap(p);
4022 }
4023 }
4024 };
4025
4026 //----------------------------------------------------------------------
4027
4030 GAIA_NODISCARD ComponentCache& comp_cache_mut() {
4031 return m_compCache;
4032 }
4033
4036 GAIA_NODISCARD const ComponentCache& comp_cache() const {
4037 return m_compCache;
4038 }
4039
4040 //----------------------------------------------------------------------
4041
4046 GAIA_NODISCARD Entity symbol(const char* symbol, uint32_t len = 0) const {
4047 if (symbol == nullptr || symbol[0] == 0)
4048 return EntityBad;
4049
4050 const auto* pItem = comp_cache().symbol(symbol, len);
4051 return pItem != nullptr ? pItem->entity : EntityBad;
4052 }
4053
4057 GAIA_NODISCARD util::str_view symbol(Entity component) const {
4058 const auto* pItem = comp_cache().find(component);
4059 return pItem != nullptr ? comp_cache().symbol_name(*pItem) : util::str_view{};
4060 }
4061
4066 GAIA_NODISCARD Entity path(const char* path, uint32_t len = 0) const {
4067 if (path == nullptr || path[0] == 0)
4068 return EntityBad;
4069
4070 const auto* pItem = comp_cache().path(path, len);
4071 return pItem != nullptr ? pItem->entity : EntityBad;
4072 }
4073
4077 GAIA_NODISCARD util::str_view path(Entity component) const {
4078 const auto* pItem = comp_cache().find(component);
4079 return pItem != nullptr ? comp_cache().path_name(*pItem) : util::str_view{};
4080 }
4081
4087 bool path(Entity component, const char* path, uint32_t len = 0) {
4088 auto* pItem = comp_cache_mut().find(component);
4089 return pItem != nullptr ? comp_cache_mut().path(*pItem, path, len) : false;
4090 }
4091
4096 GAIA_NODISCARD Entity alias(const char* alias, uint32_t len = 0) const {
4097 if (alias == nullptr || alias[0] == 0)
4098 return EntityBad;
4099
4100 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
4101 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
4102 const auto it = m_aliasToEntity.find(EntityNameLookupKey(alias, l, 0));
4103 return it != m_aliasToEntity.end() ? it->second : EntityBad;
4104 }
4105
4109 GAIA_NODISCARD util::str_view alias(Entity entity) const {
4110 if (entity.pair())
4111 return {};
4112
4113 const auto& ec = m_recs.entities[entity.id()];
4114 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
4115 if (compIdx == BadIndex)
4116 return {};
4117
4118 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
4119 GAIA_ASSERT(core::check_alignment(pDesc));
4120 return {pDesc->alias, pDesc->alias_len};
4121 }
4122
4128 bool alias(Entity entity, const char* alias, uint32_t len = 0) {
4129 if (!valid(entity) || entity.pair())
4130 return false;
4131
4132 const auto before = this->alias(entity);
4133 EntityBuilder(*this, entity).alias(alias, len);
4134 const auto after = this->alias(entity);
4135 if (alias == nullptr)
4136 return !before.empty() && after.empty();
4137
4138 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
4139 return after == util::str_view(alias, l);
4140 }
4141
4147 bool alias_raw(Entity entity, const char* alias, uint32_t len = 0) {
4148 if (!valid(entity) || entity.pair())
4149 return false;
4150
4151 const auto before = this->alias(entity);
4152 EntityBuilder(*this, entity).alias_raw(alias, len);
4153 const auto after = this->alias(entity);
4154 if (alias == nullptr)
4155 return !before.empty() && after.empty();
4156
4157 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(alias, ComponentCacheItem::MaxNameLength) : len;
4158 return after == util::str_view(alias, l);
4159 }
4160
4165 GAIA_NODISCARD util::str_view display_name(Entity entity) const {
4166 const auto* pItem = comp_cache().find(entity);
4167 if (pItem == nullptr)
4168 return {};
4169
4170 const auto aliasValue = alias(entity);
4171 if (!aliasValue.empty())
4172 return aliasValue;
4173
4174 const auto pathValue = path(entity);
4175 if (!pathValue.empty()) {
4176 const auto symbolEntity = symbol(pathValue.data(), pathValue.size());
4177 if (symbolEntity == EntityBad || symbolEntity == entity)
4178 return pathValue;
4179 }
4180
4181 return symbol(entity);
4182 }
4183
4184 //----------------------------------------------------------------------
4185
4186 private:
4187 void invalidate_scope_path_cache() const {
4188 m_componentScopePathCache.clear();
4189 m_componentScopePathCacheEntity = EntityBad;
4190 m_componentScopePathCacheValid = false;
4191 }
4192
4197 GAIA_NODISCARD bool build_scope_path(Entity scope, util::str& out) const {
4198 out.clear();
4199 if (!valid(scope) || scope.pair())
4200 return false;
4201
4203 auto curr = scope;
4204 while (curr != EntityBad) {
4205 const auto currName = name(curr);
4206 if (currName.empty()) {
4207 out.clear();
4208 return false;
4209 }
4210
4211 segments.push_back(currName);
4212 curr = target(curr, ChildOf);
4213 }
4214
4215 if (segments.empty())
4216 return false;
4217
4218 uint32_t totalLen = 0;
4219 for (auto segment: segments)
4220 totalLen += segment.size();
4221 totalLen += (uint32_t)segments.size() - 1;
4222
4223 out.reserve(totalLen);
4224 for (uint32_t i = (uint32_t)segments.size(); i > 0; --i) {
4225 if (!out.empty())
4226 out.append('.');
4227 out.append(segments[i - 1]);
4228 }
4229
4230 return true;
4231 }
4232
4236 GAIA_NODISCARD bool current_scope_path(util::str& out) const {
4237 if (m_componentScope == EntityBad) {
4238 invalidate_scope_path_cache();
4239 out.clear();
4240 return false;
4241 }
4242
4243 if (m_componentScopePathCacheValid && m_componentScopePathCacheEntity == m_componentScope) {
4244 out.assign(m_componentScopePathCache.view());
4245 return true;
4246 }
4247
4248 if (!build_scope_path(m_componentScope, out)) {
4249 invalidate_scope_path_cache();
4250 return false;
4251 }
4252
4253 m_componentScopePathCache.assign(out.view());
4254 m_componentScopePathCacheEntity = m_componentScope;
4255 m_componentScopePathCacheValid = true;
4256 return true;
4257 }
4258
4259 template <typename Func>
4260 bool find_component_in_scope_chain_inter(Entity scopeEntity, const char* name, uint32_t len, Func&& func) const {
4261 if (scopeEntity == EntityBad)
4262 return false;
4263
4264 util::str scopePath;
4265 if (!build_scope_path(scopeEntity, scopePath))
4266 return false;
4267
4268 util::str scopedName;
4269 scopedName.reserve(scopePath.size() + 1 + len);
4270
4271 while (!scopePath.empty()) {
4272 scopedName.clear();
4273 scopedName.append(scopePath.view());
4274 scopedName.append('.');
4275 scopedName.append(name, len);
4276
4277 if (const auto* pItem = m_compCache.path(scopedName.data(), (uint32_t)scopedName.size()); pItem != nullptr) {
4278 if (func(*pItem))
4279 return true;
4280 }
4281
4282 const auto parentSepIdx = scopePath.view().find_last_of('.');
4283 if (parentSepIdx == BadIndex)
4284 break;
4285
4286 scopePath.assign(util::str_view(scopePath.data(), parentSepIdx));
4287 }
4288
4289 return false;
4290 }
4291
4299 GAIA_NODISCARD const ComponentCacheItem* resolve_component_name_inter(const char* name, uint32_t len = 0) const {
4300 GAIA_ASSERT(name != nullptr);
4301
4302 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
4303 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
4304 const bool isPath = memchr(name, '.', l) != nullptr;
4305 const bool isSymbol = memchr(name, ':', l) != nullptr;
4306
4307 if (isPath) {
4308 if (const auto* pItem = m_compCache.path(name, l); pItem != nullptr)
4309 return pItem;
4310 }
4311
4312 if (!isPath && !isSymbol) {
4313 const auto* pFound = (const ComponentCacheItem*)nullptr;
4314 const auto findAndStore = [&](const ComponentCacheItem& item) {
4315 pFound = &item;
4316 return true;
4317 };
4318 if (find_component_in_scope_chain_inter(m_componentScope, name, l, findAndStore))
4319 return pFound;
4320
4321 for (const auto scopeEntity: m_componentLookupPath) {
4322 if (scopeEntity == m_componentScope)
4323 continue;
4324 if (find_component_in_scope_chain_inter(scopeEntity, name, l, findAndStore))
4325 return pFound;
4326 }
4327 }
4328
4329 if (const auto* pItem = m_compCache.symbol(name, l); pItem != nullptr)
4330 return pItem;
4331
4332 if (!isPath) {
4333 if (const auto* pItem = m_compCache.path(name, l); pItem != nullptr)
4334 return pItem;
4335 if (!isSymbol) {
4336 if (const auto* pItem = m_compCache.short_symbol(name, l); pItem != nullptr)
4337 return pItem;
4338 }
4339 }
4340
4341 const auto aliasEntity = alias(name, l);
4342 return aliasEntity != EntityBad ? m_compCache.find(aliasEntity) : nullptr;
4343 }
4344
4345 public:
4346 //----------------------------------------------------------------------
4347
4351 GAIA_NODISCARD bool valid(Entity entity) const {
4352 return entity.pair() //
4353 ? valid_pair(entity)
4354 : valid_entity(entity);
4355 }
4356
4357 //----------------------------------------------------------------------
4358
4362 GAIA_NODISCARD Entity get(EntityId id) const {
4363 // Cleanup, observer propagation, and wildcard expansion can briefly encounter stale ids.
4364 // Treat those as absent instead of crashing the world.
4365 if (!valid_entity_id(id))
4366 return EntityBad;
4367
4368 const auto& ec = m_recs.entities[id];
4369 return Entity(id, ec.data.gen, (bool)ec.data.ent, (bool)ec.data.pair, (EntityKind)ec.data.kind);
4370 }
4371
4373 GAIA_NODISCARD Entity try_get(EntityId id) const {
4374 return valid_entity_id(id) ? get(id) : EntityBad;
4375 }
4376
4380 template <typename T>
4381 GAIA_NODISCARD Entity get() const {
4382 return comp_cache().get<T>().entity;
4383 }
4384
4388 template <typename T>
4389 GAIA_NODISCARD const ComponentCacheItem& reg_comp() {
4390#if GAIA_ECS_AUTO_COMPONENT_REGISTRATION
4391 return add<T>();
4392#else
4393 return comp_cache().template get<T>();
4394#endif
4395 }
4396
4397 //----------------------------------------------------------------------
4398
4404 return EntityBuilder(*this, entity);
4405 }
4406
4410 GAIA_NODISCARD Entity add(EntityKind kind = EntityKind::EK_Gen) {
4411 return add(*m_pEntityArchetype, true, false, kind);
4412 }
4413
4417 GAIA_NODISCARD Entity prefab(EntityKind kind = EntityKind::EK_Gen) {
4418 const auto entity = add(kind);
4419 add(entity, Prefab);
4420 return entity;
4421 }
4422
4426 template <typename Func = TFunc_Void_With_Entity>
4427 void add_n(uint32_t count, Func func = func_void_with_entity) {
4428 add_entity_n(*m_pEntityArchetype, count, func);
4429 }
4430
4436 template <typename Func = TFunc_Void_With_Entity>
4437 void add_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
4438 auto& ec = m_recs.entities[entity.id()];
4439
4440 GAIA_ASSERT(ec.pArchetype != nullptr);
4441 GAIA_ASSERT(ec.pChunk != nullptr);
4442
4443 add_entity_n(*ec.pArchetype, count, func);
4444 }
4445
4449 template <typename T>
4450 GAIA_NODISCARD const ComponentCacheItem& add() {
4451 static_assert(!is_pair<T>::value, "Pairs can't be registered as components");
4452
4453 using CT = component_type_t<T>;
4454 using FT = typename CT::TypeFull;
4455 constexpr auto kind = CT::Kind;
4456
4457 const auto* pItem = comp_cache().find<FT>();
4458 if (pItem != nullptr)
4459 return *pItem;
4460
4461 const auto entity = add(*m_pCompArchetype, false, false, kind);
4462 util::str scopePath;
4463 (void)current_scope_path(scopePath);
4464
4465 const auto& item = comp_cache_mut().add<FT>(entity, scopePath.view());
4466 sset<Component>(item.entity) = item.comp;
4467 // Register the default component symbol through the normal entity naming path.
4468 name_raw(item.entity, item.name.str(), item.name.len());
4469#if GAIA_ECS_AUTO_COMPONENT_SCHEMA
4470 auto_populate_component_schema<FT>(comp_cache_mut().get(item.entity));
4471#endif
4472
4473 return item;
4474 }
4475
4486 GAIA_NODISCARD const ComponentCacheItem&
4487 add(const char* name, uint32_t size, DataStorageType storageType, uint32_t alig = 1, uint32_t soa = 0,
4488 const uint8_t* pSoaSizes = nullptr, ComponentLookupHash hashLookup = {},
4489 EntityKind kind = EntityKind::EK_Gen) {
4490 GAIA_ASSERT(name != nullptr);
4491
4492 const auto len = (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength);
4493 GAIA_ASSERT(len > 0 && len < ComponentCacheItem::MaxNameLength);
4494
4495 if (const auto* pItem = comp_cache().symbol(name, len); pItem != nullptr)
4496 return *pItem;
4497
4498 const auto entity = add(*m_pCompArchetype, false, false, kind);
4499 util::str scopePath;
4500 (void)current_scope_path(scopePath);
4501 const auto& item = comp_cache_mut().add(
4502 entity, name, len, size, storageType, alig, soa, pSoaSizes, hashLookup, scopePath.view());
4503 {
4504 auto& ec = m_recs.entities[item.entity.id()];
4505 const auto compIdx = core::get_index(ec.pArchetype->ids_view(), GAIA_ID(Component));
4506 GAIA_ASSERT(compIdx != BadIndex);
4507 auto* pComp = reinterpret_cast<Component*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
4508 *pComp = item.comp;
4509 }
4510 // Register the default component symbol through the normal entity naming path.
4511 name_raw(item.entity, item.name.str(), item.name.len());
4512 if (item.comp.storage_type() == DataStorageType::Sparse)
4513 add(item.entity, Sparse);
4514 return item;
4515 }
4516
4521 void add(Entity entity, Entity object) {
4522#if GAIA_ASSERT_ENABLED
4523 if (!object.pair()) {
4524 const auto* pItem = comp_cache().find(object);
4525 if (pItem != nullptr && pItem->entity == object && is_out_of_line_component(object))
4526 GAIA_ASSERT2(
4527 false, "Out-of-line runtime components require an explicit typed value when added by entity id");
4528 }
4529#endif
4530 EntityBuilder(*this, entity).add(object);
4531 }
4532
4538 void add(Entity entity, Pair pair) {
4539 EntityBuilder(*this, entity).add(pair);
4540 }
4541
4547 template <typename T>
4548 void add(Entity entity) {
4549 using FT = typename component_type_t<T>::TypeFull;
4550 const auto& item = add<FT>();
4551 if constexpr (supports_out_of_line_component<FT>()) {
4552 if (is_out_of_line_component(item.entity)) {
4553 if (!is_non_fragmenting_out_of_line_component(item.entity)) {
4554 (void)sparse_component_store_mut<FT>(item.entity).add(entity);
4555 EntityBuilder(*this, entity).add<T>();
4556 return;
4557 }
4558#if GAIA_OBSERVERS_ENABLED
4559 auto addDiffCtx = m_observers.prepare_diff(
4560 *this, ObserverEvent::OnAdd, EntitySpan{&item.entity, 1}, EntitySpan{&entity, 1});
4561#endif
4562 (void)sparse_component_store_mut<FT>(item.entity).add(entity);
4563 notify_add_single(entity, item.entity);
4564#if GAIA_OBSERVERS_ENABLED
4565 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4566#endif
4567 return;
4568 }
4569 }
4570
4571 EntityBuilder(*this, entity).add<T>();
4572 }
4573
4581 template <typename T>
4582 void add(Entity entity, Entity object, T&& value) {
4583 static_assert(core::is_raw_v<T>);
4584
4585 if constexpr (supports_out_of_line_component<typename component_type_t<T>::TypeFull>()) {
4586 using FT = typename component_type_t<T>::TypeFull;
4587 if (can_use_out_of_line_component<FT>(object)) {
4588 auto& data = sparse_component_store_mut<FT>(object).add(entity);
4589 data = GAIA_FWD(value);
4590
4591 if (!is_non_fragmenting_out_of_line_component(object)) {
4592#if GAIA_OBSERVERS_ENABLED
4593 auto addDiffCtx =
4594 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
4595#endif
4596 EntityBuilder eb(*this, entity);
4597 eb.add_inter_init(object);
4598 eb.commit();
4599 notify_add_single(entity, object);
4600#if GAIA_OBSERVERS_ENABLED
4601 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4602#endif
4603 return;
4604 }
4605#if GAIA_OBSERVERS_ENABLED
4606 auto addDiffCtx =
4607 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
4608#endif
4609 notify_add_single(entity, object);
4610#if GAIA_OBSERVERS_ENABLED
4611 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4612#endif
4613 return;
4614 }
4615 }
4616
4617 EntityBuilder eb(*this, entity);
4618 eb.add_inter_init(object);
4619 eb.commit();
4620
4621 const auto& ec = fetch(entity);
4622 // Make sure the idx is 0 for unique entities
4623 const auto idx = uint16_t(ec.row * (1U - (uint32_t)object.kind()));
4624 ComponentSetter{*this, ec.pChunk, entity, idx}.sset(object, GAIA_FWD(value));
4625 notify_add_single(entity, object);
4626 }
4627
4634 template <typename T, typename U = typename actual_type_t<T>::Type>
4635 void add(Entity entity, U&& value) {
4636 using FT = typename component_type_t<T>::TypeFull;
4637 if constexpr (!is_pair<FT>::value && supports_out_of_line_component<FT>()) {
4638 const auto& item = add<FT>();
4639 if (is_out_of_line_component(item.entity)) {
4640 auto& data = sparse_component_store_mut<FT>(item.entity).add(entity);
4641 data = GAIA_FWD(value);
4642
4643 if (!is_non_fragmenting_out_of_line_component(item.entity)) {
4644 EntityBuilder builder(*this, entity);
4645 builder.add(item.entity);
4646 builder.commit();
4647 return;
4648 }
4649#if GAIA_OBSERVERS_ENABLED
4650 auto addDiffCtx = m_observers.prepare_diff(
4651 *this, ObserverEvent::OnAdd, EntitySpan{&item.entity, 1}, EntitySpan{&entity, 1});
4652#endif
4653 notify_add_single(entity, item.entity);
4654#if GAIA_OBSERVERS_ENABLED
4655 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4656#endif
4657 return;
4658 }
4659 }
4660
4661 EntityBuilder builder(*this, entity);
4662 auto object = builder.register_component<T>();
4663 builder.add(object);
4664 builder.commit();
4665
4666 const auto& ec = m_recs.entities[entity.id()];
4667 // Make sure the idx is 0 for unique entities
4668 const auto idx = uint16_t(ec.row * (1U - (uint32_t)object.kind()));
4669 ComponentSetter{*this, ec.pChunk, entity, idx}.sset<T>(GAIA_FWD(value));
4670 }
4671
4674 GAIA_NODISCARD bool override(Entity entity, Entity object) {
4675 return override_inter(entity, object);
4676 }
4677
4680 GAIA_NODISCARD bool override(Entity entity, Pair pair) {
4681 return override_inter(entity, (Entity)pair);
4682 }
4683
4686 template <typename T>
4687 GAIA_NODISCARD bool override(Entity entity) {
4688 static_assert(!is_pair<T>::value);
4689 using FT = typename component_type_t<T>::TypeFull;
4690 const auto& item = add<FT>();
4691
4692 if constexpr (supports_out_of_line_component<FT>()) {
4693 if (is_out_of_line_component(item.entity)) {
4694 if (has_direct(entity, item.entity))
4695 return false;
4696
4697 const auto inheritedOwner = inherited_id_owner(entity, item.entity);
4698 if (inheritedOwner == EntityBad)
4699 return false;
4700
4701 auto* pStore = sparse_component_store<FT>(item.entity);
4702 GAIA_ASSERT(pStore != nullptr);
4703 auto& data = sparse_component_store_mut<FT>(item.entity).add(entity);
4704 data = pStore->get(inheritedOwner);
4705
4706 if (!is_non_fragmenting_out_of_line_component(item.entity)) {
4707 EntityBuilder eb(*this, entity);
4708 eb.add_inter_init(item.entity);
4709 eb.commit();
4710 }
4711 return true;
4712 }
4713 }
4714
4715 return override_inter(entity, item.entity);
4716 }
4717
4720 template <typename T>
4721 GAIA_NODISCARD bool override(Entity entity, Entity object) {
4722 static_assert(!is_pair<T>::value);
4723 using FT = typename component_type_t<T>::TypeFull;
4724
4725 if constexpr (supports_out_of_line_component<FT>()) {
4726 if (can_use_out_of_line_component<FT>(object)) {
4727 if (has_direct(entity, object))
4728 return false;
4729
4730 const auto inheritedOwner = inherited_id_owner(entity, object);
4731 if (inheritedOwner == EntityBad)
4732 return false;
4733
4734 auto* pStore = sparse_component_store<FT>(object);
4735 GAIA_ASSERT(pStore != nullptr);
4736 auto& data = sparse_component_store_mut<FT>(object).add(entity);
4737 data = pStore->get(inheritedOwner);
4738
4739 if (!is_non_fragmenting_out_of_line_component(object)) {
4740 EntityBuilder eb(*this, entity);
4741 eb.add_inter_init(object);
4742 eb.commit();
4743 }
4744 return true;
4745 }
4746 }
4747
4748 return override_inter(entity, object);
4749 }
4750
4751 //----------------------------------------------------------------------
4752
4757 void clear(Entity entity) {
4758 GAIA_ASSERT(!entity.pair());
4759 GAIA_ASSERT(valid(entity));
4760
4761 EntityBuilder eb(*this, entity);
4762
4763 // Remove back to front because it's better for the archetype graph
4764 auto ids = eb.m_pArchetype->ids_view();
4765 for (uint32_t i = (uint32_t)ids.size() - 1; i != (uint32_t)-1; --i)
4766 eb.del(ids[i]);
4767
4768 eb.commit();
4769 }
4770
4771 //----------------------------------------------------------------------
4772
4780 GAIA_NODISCARD Entity copy(Entity srcEntity) {
4781 GAIA_ASSERT(!srcEntity.pair());
4782 GAIA_ASSERT(valid(srcEntity));
4783
4784 auto& ec = m_recs.entities[srcEntity.id()];
4785 GAIA_ASSERT(ec.pArchetype != nullptr);
4786 GAIA_ASSERT(ec.pChunk != nullptr);
4787
4788 auto* pDstArchetype = ec.pArchetype;
4789 Entity dstEntity;
4790
4791 // Names have to be unique so if we see that EntityDesc is present during copy
4792 // we navigate towards a version of the archetype without the EntityDesc.
4793 if (pDstArchetype->has<EntityDesc>()) {
4794 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4795
4796 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
4797 auto& ecDst = m_recs.entities[dstEntity.id()];
4798 Chunk::copy_foreign_entity_data(ec.pChunk, ec.row, ecDst.pChunk, ecDst.row);
4799 } else {
4800 // No description associated with the entity, direct copy is possible
4801 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
4802 Chunk::copy_entity_data(srcEntity, dstEntity, m_recs);
4803 }
4804
4805 copy_sparse_entity_data(srcEntity, dstEntity, [](Entity) {
4806 return true;
4807 });
4808
4809 return dstEntity;
4810 }
4811
4821 template <typename Func = TFunc_Void_With_Entity>
4822 void copy_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
4823 copy_n_inter(entity, count, func, EntitySpan{});
4824 }
4825
4826#if GAIA_OBSERVERS_ENABLED
4834 GAIA_NODISCARD Entity copy_ext(Entity srcEntity) {
4835 GAIA_ASSERT(!srcEntity.pair());
4836 GAIA_ASSERT(valid(srcEntity));
4837
4838 auto& ec = m_recs.entities[srcEntity.id()];
4839 GAIA_ASSERT(ec.pArchetype != nullptr);
4840 GAIA_ASSERT(ec.pChunk != nullptr);
4841
4842 auto* pDstArchetype = ec.pArchetype;
4843 // Names have to be unique so if we see that EntityDesc is present during copy
4844 // we navigate towards a version of the archetype without the EntityDesc.
4845 const bool hasEntityDesc = pDstArchetype->has<EntityDesc>();
4846 if (hasEntityDesc)
4847 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4848
4849 const auto archetypeIdCount = (uint32_t)pDstArchetype->ids_view().size();
4850 const auto sparseIdCount = copied_sparse_id_count(srcEntity, [](Entity) {
4851 return true;
4852 });
4853 const auto addedIdCount = archetypeIdCount + sparseIdCount;
4854 auto* pAddedIds = addedIdCount != 0U ? (Entity*)alloca(sizeof(Entity) * addedIdCount) : nullptr;
4855 write_archetype_ids(*pDstArchetype, pAddedIds);
4856 write_copied_sparse_ids(
4857 srcEntity,
4858 [](Entity) {
4859 return true;
4860 },
4861 pAddedIds + archetypeIdCount);
4862 #if GAIA_OBSERVERS_ENABLED
4863 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{pAddedIds, addedIdCount});
4864 #endif
4865
4866 Entity dstEntity;
4867 if (hasEntityDesc) {
4868 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
4869 auto& ecDst = m_recs.entities[dstEntity.id()];
4870 Chunk::copy_foreign_entity_data(ec.pChunk, ec.row, ecDst.pChunk, ecDst.row);
4871 } else {
4872 // No description associated with the entity, direct copy is possible
4873 dstEntity = add(*pDstArchetype, srcEntity.entity(), srcEntity.pair(), srcEntity.kind());
4874 Chunk::copy_entity_data(srcEntity, dstEntity, m_recs);
4875 }
4876
4877 (void)copy_sparse_entity_data(srcEntity, dstEntity, [](Entity) {
4878 return true;
4879 });
4880 m_observers.append_diff_targets(*this, addDiffCtx, EntitySpan{&dstEntity, 1});
4881
4882 m_observers.on_add(*this, *pDstArchetype, EntitySpan{pAddedIds, addedIdCount}, EntitySpan{&dstEntity, 1});
4883 #if GAIA_OBSERVERS_ENABLED
4884 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4885 #endif
4886
4887 return dstEntity;
4888 }
4889
4899 template <typename Func = TFunc_Void_With_Entity>
4900 void copy_ext_n(Entity entity, uint32_t count, Func func = func_void_with_entity) {
4901 auto& ec = m_recs.entities[entity.id()];
4902 auto* pDstArchetype = ec.pArchetype;
4903 if (pDstArchetype->has<EntityDesc>())
4904 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
4905
4906 const auto archetypeIdCount = (uint32_t)pDstArchetype->ids_view().size();
4907 const auto sparseIdCount = copied_sparse_id_count(entity, [](Entity) {
4908 return true;
4909 });
4910 const auto addedIdCount = archetypeIdCount + sparseIdCount;
4911 auto* pAddedIds = addedIdCount != 0U ? (Entity*)alloca(sizeof(Entity) * addedIdCount) : nullptr;
4912 write_archetype_ids(*pDstArchetype, pAddedIds);
4913 write_copied_sparse_ids(
4914 entity,
4915 [](Entity) {
4916 return true;
4917 },
4918 pAddedIds + archetypeIdCount);
4919 #if GAIA_OBSERVERS_ENABLED
4920 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{pAddedIds, addedIdCount});
4921 #endif
4922 copy_n_inter(
4923 entity, count, func, EntitySpan{pAddedIds, addedIdCount}, EntityBad
4924 #if GAIA_OBSERVERS_ENABLED
4925 ,
4926 &addDiffCtx
4927 #endif
4928 );
4929 #if GAIA_OBSERVERS_ENABLED
4930 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
4931 #endif
4932 }
4933#endif
4934
4935 private:
4936 struct CopyIterGroupState {
4937 Archetype* pArchetype = nullptr;
4938 Chunk* pChunk = nullptr;
4939 uint16_t startRow = 0;
4940 uint16_t count = 0;
4941 };
4942
4943 struct PrefabInstantiatePlanNode {
4944 Entity prefab = EntityBad;
4945 uint32_t parentIdx = BadIndex;
4946 Archetype* pDstArchetype = nullptr;
4947 cnt::darray_ext<Entity, 16> copiedSparseIds;
4948 cnt::darray_ext<Entity, 16> addedIds;
4949 cnt::darray_ext<Entity, 16> addHookIds;
4950 };
4951
4952 template <typename Func>
4953 void invoke_copy_batch_callback(
4954 Func& func, Archetype* pDstArchetype, Chunk* pDstChunk, uint32_t originalChunkSize, uint32_t toCreate) {
4955 if constexpr (std::is_invocable_v<Func, CopyIter&>) {
4956 CopyIter it;
4957 it.set_world(this);
4958 it.set_archetype(pDstArchetype);
4959 it.set_chunk(pDstChunk);
4960 it.set_range((uint16_t)originalChunkSize, (uint16_t)toCreate);
4961 func(it);
4962 } else {
4963 auto entities = pDstChunk->entity_view();
4964 GAIA_FOR2(originalChunkSize, pDstChunk->size()) func(entities[i]);
4965 }
4966 }
4967
4968 template <typename Func>
4969 void flush_copy_iter_group(Func& func, CopyIterGroupState& group) {
4970 if (group.count == 0)
4971 return;
4972
4973 CopyIter it;
4974 it.set_world(this);
4975 it.set_archetype(group.pArchetype);
4976 it.set_chunk(group.pChunk);
4977 it.set_range(group.startRow, group.count);
4978 func(it);
4979 group.count = 0;
4980 }
4981
4982 template <typename Func>
4983 void push_copy_iter_group(Func& func, CopyIterGroupState& group, Entity instance) {
4984 const auto& ec = fetch(instance);
4985
4986 if (group.count != 0 && ec.pArchetype == group.pArchetype && ec.pChunk == group.pChunk &&
4987 ec.row == uint16_t(group.startRow + group.count)) {
4988 ++group.count;
4989 return;
4990 }
4991
4992 flush_copy_iter_group(func, group);
4993 group.pArchetype = ec.pArchetype;
4994 group.pChunk = ec.pChunk;
4995 group.startRow = ec.row;
4996 group.count = 1;
4997 }
4998
4999 void prepare_parent_batch(Entity parentEntity) {
5000 GAIA_ASSERT(valid(parentEntity));
5001
5002 const auto parentPair = Pair(Parent, parentEntity);
5003 assign_pair(parentPair, *m_pEntityArchetype);
5004
5005 touch_rel_version(Parent);
5006 invalidate_queries_for_rel(Parent);
5007 m_targetsTravCache = {};
5008 m_srcBfsTravCache = {};
5009 m_depthOrderCache = {};
5010 m_sourcesAllCache = {};
5011 m_targetsAllCache = {};
5012
5013 auto& ecParent = fetch(parentEntity);
5014 EntityBuilder::set_flag(ecParent.flags, EntityContainerFlags::OnDeleteTarget_Delete, true);
5015 }
5016
5017 void parent_batch(
5018 Entity parentEntity, Archetype& archetype, Chunk& chunk, uint32_t originalChunkSize, uint32_t toCreate) {
5019 GAIA_ASSERT(valid(parentEntity));
5020
5021 if (toCreate == 0)
5022 return;
5023
5024 auto entities = chunk.entity_view();
5025#if GAIA_OBSERVERS_ENABLED
5026 const Entity parentPair = Pair(Parent, parentEntity);
5027 auto addDiffCtx = m_observers.prepare_diff(
5028 *this, ObserverEvent::OnAdd, EntitySpan{&parentPair, 1},
5029 EntitySpan{entities.data() + originalChunkSize, toCreate});
5030#endif
5031 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
5032 exclusive_adjunct_set(entities[rowIdx], Parent, parentEntity);
5033 }
5034
5035#if GAIA_OBSERVERS_ENABLED
5036 m_observers.on_add(
5037 *this, archetype, EntitySpan{&parentPair, 1}, EntitySpan{entities.data() + originalChunkSize, toCreate});
5038 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5039#endif
5040 }
5041
5042 void parent_direct(Entity entity, Entity parentEntity) {
5043 GAIA_ASSERT(valid(entity));
5044 GAIA_ASSERT(valid(parentEntity));
5045
5046 prepare_parent_batch(parentEntity);
5047#if GAIA_OBSERVERS_ENABLED
5048 const Entity parentPair = Pair(Parent, parentEntity);
5049 auto addDiffCtx =
5050 m_observers.prepare_diff(*this, ObserverEvent::OnAdd, EntitySpan{&parentPair, 1}, EntitySpan{&entity, 1});
5051#endif
5052 exclusive_adjunct_set(entity, Parent, parentEntity);
5053
5054#if GAIA_OBSERVERS_ENABLED
5055 const auto& ec = fetch(entity);
5056 m_observers.on_add(*this, *ec.pArchetype, EntitySpan{&parentPair, 1}, EntitySpan{&entity, 1});
5057 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5058#endif
5059 }
5060
5061 void notify_add_single(Entity entity, Entity object) {
5062#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
5063 if GAIA_UNLIKELY (tearing_down())
5064 return;
5065
5066 const auto& ec = fetch(entity);
5067
5068 lock();
5069
5070 #if GAIA_ENABLE_ADD_DEL_HOOKS
5071 if (object.comp()) {
5072 const auto& item = comp_cache().get(object);
5073 const auto& hooks = ComponentCache::hooks(item);
5074 if (hooks.func_add != nullptr)
5075 hooks.func_add(*this, item, entity);
5076 }
5077 #endif
5078
5079 #if GAIA_OBSERVERS_ENABLED
5080 m_observers.on_add(*this, *ec.pArchetype, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
5081 #endif
5082
5083 unlock();
5084#else
5085 (void)entity;
5086 (void)object;
5087#endif
5088 }
5089
5090 void notify_del_single(Entity entity, Entity object) {
5091#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
5092 if GAIA_UNLIKELY (tearing_down())
5093 return;
5094
5095 const auto& ec = fetch(entity);
5096
5097 lock();
5098
5099 #if GAIA_OBSERVERS_ENABLED
5100 m_observers.on_del(*this, *ec.pArchetype, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
5101 #endif
5102
5103 #if GAIA_ENABLE_ADD_DEL_HOOKS
5104 if (object.comp()) {
5105 const auto& item = comp_cache().get(object);
5106 const auto& hooks = ComponentCache::hooks(item);
5107 if (hooks.func_del != nullptr)
5108 hooks.func_del(*this, item, entity);
5109 }
5110 #endif
5111
5112 unlock();
5113#else
5114 (void)entity;
5115 (void)object;
5116#endif
5117 }
5118
5119 GAIA_NODISCARD bool has_semantic_match_without_source(
5120 Entity entity, Entity object, Entity excludedSource, cnt::set<EntityLookupKey>& visited) const {
5121 const auto inserted = visited.insert(EntityLookupKey(entity));
5122 if (!inserted.second)
5123 return false;
5124
5125 if (entity != excludedSource && has_direct(entity, object))
5126 return true;
5127
5128 const auto it = m_entityToAsTargets.find(EntityLookupKey(entity));
5129 if (it == m_entityToAsTargets.end())
5130 return false;
5131
5132 for (const auto baseKey: it->second) {
5133 if (has_semantic_match_without_source(baseKey.entity(), object, excludedSource, visited))
5134 return true;
5135 }
5136
5137 return false;
5138 }
5139
5140 void notify_inherited_del_dependents(Entity source, Entity object) {
5141#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
5142 const auto& descendants = as_relations_trav_cache(source);
5143 for (const auto descendant: descendants) {
5144 if (descendant == source)
5145 continue;
5146 if (has_direct(descendant, object) || !has(descendant, object))
5147 continue;
5148
5149 cnt::set<EntityLookupKey> visited;
5150 if (has_semantic_match_without_source(descendant, object, source, visited))
5151 continue;
5152
5153 notify_del_single(descendant, object);
5154 }
5155#else
5156 (void)source;
5157 (void)object;
5158#endif
5159 }
5160
5161 void notify_inherited_del_dependents(Entity source, EntitySpan removedObjects) {
5162 for (const auto object: removedObjects)
5163 notify_inherited_del_dependents(source, object);
5164 }
5165
5166 template <typename Func>
5167 void copy_n_inter(
5168 Entity entity, uint32_t count, Func& func, EntitySpan addedIds, Entity parentInstance = EntityBad
5169#if GAIA_OBSERVERS_ENABLED
5170 ,
5171 ObserverRegistry::DiffDispatchCtx* pAddDiffCtx = nullptr
5172#endif
5173 ) {
5174 GAIA_ASSERT(!entity.pair());
5175 GAIA_ASSERT(valid(entity));
5176 GAIA_ASSERT(parentInstance == EntityBad || valid(parentInstance));
5177
5178 if (count == 0U)
5179 return;
5180
5181#if GAIA_OBSERVERS_ENABLED
5182 const bool useLocalAddDiff = !addedIds.empty() && pAddDiffCtx == nullptr;
5183 ObserverRegistry::DiffDispatchCtx addDiffCtx{};
5184 if (useLocalAddDiff)
5185 addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{addedIds});
5186#endif
5187
5188 auto& ec = m_recs.entities[entity.id()];
5189
5190 GAIA_ASSERT(ec.pChunk != nullptr);
5191 GAIA_ASSERT(ec.pArchetype != nullptr);
5192
5193 auto* pSrcChunk = ec.pChunk;
5194 auto* pDstArchetype = ec.pArchetype;
5195 const auto hasEntityDesc = pDstArchetype->has<EntityDesc>();
5196 if (hasEntityDesc)
5197 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
5198
5199 if (parentInstance != EntityBad)
5200 prepare_parent_batch(parentInstance);
5201
5202 // Entities array might get reallocated after m_recs.entities.alloc
5203 // so instead of fetching the container again we simply cache the row
5204 // of our source entity.
5205 const auto srcRow = ec.row;
5206
5207 EntityContainerCtx ctx{true, false, EntityKind::EK_Gen};
5208
5209 uint32_t left = count;
5210 do {
5211 auto* pDstChunk = pDstArchetype->foc_free_chunk();
5212 const uint32_t originalChunkSize = pDstChunk->size();
5213 const uint32_t freeSlotsInChunk = pDstChunk->capacity() - originalChunkSize;
5214 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
5215
5216 GAIA_FOR(toCreate) {
5217 const auto entityNew = m_recs.entities.alloc(&ctx);
5218 auto& ecNew = m_recs.entities[entityNew.id()];
5219 store_entity(ecNew, entityNew, pDstArchetype, pDstChunk);
5220
5221#if GAIA_ASSERT_ENABLED
5222 GAIA_ASSERT(ecNew.pChunk == pDstChunk);
5223 auto entityExpected = pDstChunk->entity_view()[ecNew.row];
5224 GAIA_ASSERT(entityExpected == entityNew);
5225#endif
5226
5227 if (hasEntityDesc) {
5228 Chunk::copy_foreign_entity_data(pSrcChunk, srcRow, pDstChunk, ecNew.row);
5229 }
5230
5231 copy_sparse_entity_data(entity, entityNew, [](Entity) {
5232 return true;
5233 });
5234 }
5235
5236 pDstArchetype->try_update_free_chunk_idx();
5237
5238 if (!hasEntityDesc) {
5239 pDstChunk->call_gen_ctors(originalChunkSize, toCreate);
5240
5241 {
5242 GAIA_PROF_SCOPE(World::copy_n_entity_data);
5243 Chunk::copy_entity_data_n_same_chunk(pSrcChunk, srcRow, pDstChunk, originalChunkSize, toCreate);
5244 }
5245 }
5246
5247 pDstChunk->update_versions();
5248
5249#if GAIA_OBSERVERS_ENABLED
5250 if (!addedIds.empty()) {
5251 auto entities = pDstChunk->entity_view();
5252 if (pAddDiffCtx != nullptr)
5253 m_observers.append_diff_targets(
5254 *this, *pAddDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
5255 else if (useLocalAddDiff)
5256 m_observers.append_diff_targets(
5257 *this, addDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
5258 m_observers.on_add(
5259 *this, *pDstArchetype, addedIds, EntitySpan{entities.data() + originalChunkSize, toCreate});
5260 }
5261#endif
5262
5263 if (parentInstance != EntityBad)
5264 parent_batch(parentInstance, *pDstArchetype, *pDstChunk, originalChunkSize, toCreate);
5265
5266 invoke_copy_batch_callback(func, pDstArchetype, pDstChunk, originalChunkSize, toCreate);
5267
5268 left -= toCreate;
5269 } while (left > 0);
5270#if GAIA_OBSERVERS_ENABLED
5271 if (useLocalAddDiff)
5272 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5273#endif
5274 }
5275
5276 GAIA_NODISCARD bool id_uses_inherit_policy(Entity id) const {
5277 return !is_wildcard(id) && valid(id) && target(id, OnInstantiate) == Inherit;
5278 }
5279
5280 GAIA_NODISCARD Entity inherited_id_owner(Entity entity, Entity id) const {
5281 if (!id_uses_inherit_policy(id))
5282 return EntityBad;
5283
5284 const auto& targets = as_targets_trav_cache(entity);
5285 for (const auto target: targets) {
5286 if (has_inter(target, id, false))
5287 return target;
5288 }
5289
5290 return EntityBad;
5291 }
5292
5293 GAIA_NODISCARD bool instantiate_copies_id(Entity id) const {
5294 const auto policy = target(id, OnInstantiate);
5295 if (policy == EntityBad || policy == Override)
5296 return true;
5297 if (policy == DontInherit || policy == Inherit)
5298 return false;
5299 return true;
5300 }
5301
5302 template <typename T>
5303 void gather_sorted_prefab_children(Entity prefabEntity, T& outChildren) {
5304 sources(Parent, prefabEntity, [&](Entity childPrefab) {
5305 if (!has_direct(childPrefab, Prefab))
5306 return;
5307 outChildren.push_back(childPrefab);
5308 });
5309
5310 core::sort(outChildren, [](Entity left, Entity right) {
5311 return left.id() < right.id();
5312 });
5313 }
5314
5315 template <typename Func>
5316 uint32_t
5317 copy_sparse_entity_data(Entity srcEntity, Entity dstEntity, Func&& shouldCopy, Entity* pCopiedIds = nullptr) {
5318 uint32_t copiedCnt = 0;
5319 for (auto& [compKey, store]: m_sparseComponentsByComp) {
5320 const auto comp = compKey.entity();
5321 if (!store.func_has(store.pStore, srcEntity) || !shouldCopy(comp))
5322 continue;
5323
5324 GAIA_ASSERT(store.func_copy_entity != nullptr);
5325 if (!store.func_copy_entity(store.pStore, dstEntity, srcEntity))
5326 continue;
5327
5328 if (pCopiedIds != nullptr)
5329 pCopiedIds[copiedCnt] = comp;
5330 ++copiedCnt;
5331 }
5332
5333 return copiedCnt;
5334 }
5335
5336 uint32_t copy_sparse_entity_data(
5337 Entity srcEntity, Entity dstEntity, EntitySpan copiedSparseIds, Entity* pCopiedIds = nullptr) {
5338 uint32_t copiedCnt = 0;
5339 for (const auto comp: copiedSparseIds) {
5340 auto it = m_sparseComponentsByComp.find(EntityLookupKey(comp));
5341 GAIA_ASSERT(it != m_sparseComponentsByComp.end());
5342
5343 auto& store = it->second;
5344 if (!store.func_has(store.pStore, srcEntity))
5345 continue;
5346
5347 GAIA_ASSERT(store.func_copy_entity != nullptr);
5348 if (!store.func_copy_entity(store.pStore, dstEntity, srcEntity))
5349 continue;
5350
5351 if (pCopiedIds != nullptr)
5352 pCopiedIds[copiedCnt] = comp;
5353 ++copiedCnt;
5354 }
5355
5356 return copiedCnt;
5357 }
5358
5359 void write_archetype_ids(const Archetype& dstArchetype, Entity* pDst) const {
5360 for (const auto id: dstArchetype.ids_view())
5361 *pDst++ = id;
5362 }
5363
5364 template <typename Func>
5365 GAIA_NODISCARD uint32_t copied_sparse_id_count(Entity srcEntity, Func&& shouldCopySparse) const {
5366 uint32_t count = 0;
5367 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
5368 const auto comp = compKey.entity();
5369 if (!store.func_has(store.pStore, srcEntity) || !shouldCopySparse(comp) ||
5370 !is_non_fragmenting_out_of_line_component(comp))
5371 continue;
5372 ++count;
5373 }
5374
5375 return count;
5376 }
5377
5378 template <typename Func>
5379 void write_copied_sparse_ids(Entity srcEntity, Func&& shouldCopySparse, Entity* pDst) const {
5380 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
5381 const auto comp = compKey.entity();
5382 if (!store.func_has(store.pStore, srcEntity) || !shouldCopySparse(comp) ||
5383 !is_non_fragmenting_out_of_line_component(comp))
5384 continue;
5385 *pDst++ = comp;
5386 }
5387 }
5388
5389 GAIA_NODISCARD bool override_inter(Entity entity, Entity object) {
5390 GAIA_ASSERT(valid(entity));
5391 GAIA_ASSERT(object.pair() || valid(object));
5392
5393 if (has_direct(entity, object))
5394 return false;
5395
5396 const auto inheritedOwner = inherited_id_owner(entity, object);
5397 if (inheritedOwner == EntityBad)
5398 return false;
5399
5400 if (!object.pair()) {
5401 const auto* pItem = comp_cache().find(object);
5402 if (pItem != nullptr && pItem->entity == object) {
5403 if (is_out_of_line_component(object)) {
5404 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
5405 if (itSparseStore == m_sparseComponentsByComp.end())
5406 return false;
5407
5408 GAIA_ASSERT(itSparseStore->second.func_copy_entity != nullptr);
5409 if (!itSparseStore->second.func_copy_entity(itSparseStore->second.pStore, entity, inheritedOwner))
5410 return false;
5411
5412 if (!is_non_fragmenting_out_of_line_component(object)) {
5413 EntityBuilder eb(*this, entity);
5414 eb.add_inter_init(object);
5415 eb.commit();
5416 }
5417 return true;
5418 }
5419
5420 if (pItem->comp.size() != 0U) {
5421 add(entity, object);
5422
5423 const auto& ecDst = fetch(entity);
5424 const auto& ecSrc = fetch(inheritedOwner);
5425 const auto compIdxDst = ecDst.pChunk->comp_idx(object);
5426 const auto compIdxSrc = ecSrc.pChunk->comp_idx(object);
5427 GAIA_ASSERT(compIdxDst != BadIndex && compIdxSrc != BadIndex);
5428
5429 const auto idxDst = uint16_t(ecDst.row * (1U - (uint32_t)object.kind()));
5430 const auto idxSrc = uint16_t(ecSrc.row * (1U - (uint32_t)object.kind()));
5431 void* pDst = ecDst.pChunk->comp_ptr_mut(compIdxDst);
5432 const void* pSrc = ecSrc.pChunk->comp_ptr(compIdxSrc);
5433 pItem->copy(pDst, pSrc, idxDst, idxSrc, ecDst.pChunk->capacity(), ecSrc.pChunk->capacity());
5434 return true;
5435 }
5436 }
5437 }
5438
5439 add(entity, object);
5440 return true;
5441 }
5442
5443 GAIA_NODISCARD bool copy_owned_id_from_entity(Entity srcEntity, Entity dstEntity, Entity object) {
5444 GAIA_ASSERT(valid(srcEntity));
5445 GAIA_ASSERT(valid(dstEntity));
5446 GAIA_ASSERT(object.pair() || valid(object));
5447
5448 if (has_direct(dstEntity, object))
5449 return false;
5450
5451 if (!object.pair()) {
5452 const auto* pItem = comp_cache().find(object);
5453 if (pItem != nullptr && pItem->entity == object) {
5454 if (is_out_of_line_component(object)) {
5455 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
5456 if (itSparseStore == m_sparseComponentsByComp.end())
5457 return false;
5458
5459 GAIA_ASSERT(itSparseStore->second.func_copy_entity != nullptr);
5460 if (!is_non_fragmenting_out_of_line_component(object)) {
5461 if (!itSparseStore->second.func_copy_entity(itSparseStore->second.pStore, dstEntity, srcEntity))
5462 return false;
5463
5464 EntityBuilder eb(*this, dstEntity);
5465 eb.add_inter_init(object);
5466 eb.commit();
5467 notify_add_single(dstEntity, object);
5468 return true;
5469 }
5470#if GAIA_OBSERVERS_ENABLED
5471 auto addDiffCtx = m_observers.prepare_diff(
5472 *this, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&dstEntity, 1});
5473#endif
5474 if (!itSparseStore->second.func_copy_entity(itSparseStore->second.pStore, dstEntity, srcEntity))
5475 return false;
5476 notify_add_single(dstEntity, object);
5477#if GAIA_OBSERVERS_ENABLED
5478 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5479#endif
5480 return true;
5481 }
5482
5483 if (pItem->comp.size() != 0U) {
5484 EntityBuilder eb(*this, dstEntity);
5485 eb.add_inter_init(object);
5486 eb.commit();
5487
5488 const auto& ecDst = fetch(dstEntity);
5489 const auto& ecSrc = fetch(srcEntity);
5490 const auto compIdxDst = ecDst.pChunk->comp_idx(object);
5491 const auto compIdxSrc = ecSrc.pChunk->comp_idx(object);
5492 GAIA_ASSERT(compIdxDst != BadIndex && compIdxSrc != BadIndex);
5493
5494 const auto idxDst = uint16_t(ecDst.row * (1U - (uint32_t)object.kind()));
5495 const auto idxSrc = uint16_t(ecSrc.row * (1U - (uint32_t)object.kind()));
5496 void* pDst = ecDst.pChunk->comp_ptr_mut(compIdxDst);
5497 const void* pSrc = ecSrc.pChunk->comp_ptr(compIdxSrc);
5498 pItem->copy(pDst, pSrc, idxDst, idxSrc, ecDst.pChunk->capacity(), ecSrc.pChunk->capacity());
5499 notify_add_single(dstEntity, object);
5500 return true;
5501 }
5502 }
5503 }
5504
5505 add(dstEntity, object);
5506 return true;
5507 }
5508
5509 GAIA_NODISCARD Archetype* instantiate_prefab_dst_archetype(Entity prefabEntity) {
5510 GAIA_ASSERT(!prefabEntity.pair());
5511 GAIA_ASSERT(valid(prefabEntity));
5512 GAIA_ASSERT(has_direct(prefabEntity, Prefab));
5513
5514 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab))
5515 return fetch(prefabEntity).pArchetype;
5516
5517 auto& ecSrc = m_recs.entities[prefabEntity.id()];
5518 GAIA_ASSERT(ecSrc.pArchetype != nullptr);
5519
5520 auto* pDstArchetype = ecSrc.pArchetype;
5521 if (pDstArchetype->has<EntityDesc>())
5522 pDstArchetype = foc_archetype_del(pDstArchetype, GAIA_ID(EntityDesc));
5523 if (pDstArchetype->has(Prefab))
5524 pDstArchetype = foc_archetype_del(pDstArchetype, Prefab);
5525
5526 for (const auto id: ecSrc.pArchetype->ids_view()) {
5527 if (id.pair() && id.id() == Is.id()) {
5528 pDstArchetype = foc_archetype_del(pDstArchetype, id);
5529 continue;
5530 }
5531
5532 if (!instantiate_copies_id(id))
5533 pDstArchetype = foc_archetype_del(pDstArchetype, id);
5534 }
5535
5536 const auto isPair = Pair(Is, prefabEntity);
5537 assign_pair(isPair, *m_pEntityArchetype);
5538 pDstArchetype = foc_archetype_add(pDstArchetype, isPair);
5539
5540 return pDstArchetype;
5541 }
5542
5543 template <typename T>
5544 void collect_prefab_copied_sparse_ids(Entity prefabEntity, T& outCopiedSparseIds) {
5545 outCopiedSparseIds.clear();
5546 if (m_sparseComponentsByComp.empty())
5547 return;
5548
5549 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
5550 const auto comp = compKey.entity();
5551 if (!store.func_has(store.pStore, prefabEntity) || !instantiate_copies_id(comp) ||
5552 !is_out_of_line_component(comp))
5553 continue;
5554 outCopiedSparseIds.push_back(comp);
5555 }
5556 }
5557
5558 template <typename T>
5559 void collect_prefab_added_ids(Archetype* pDstArchetype, EntitySpan copiedSparseIds, T& outAddedIds) {
5560 outAddedIds.clear();
5561 for (const auto id: pDstArchetype->ids_view())
5562 outAddedIds.push_back(id);
5563
5564 for (const auto comp: copiedSparseIds) {
5565 if (is_non_fragmenting_out_of_line_component(comp))
5566 outAddedIds.push_back(comp);
5567 }
5568 }
5569
5570 template <typename T>
5571 void collect_prefab_add_hook_ids(EntitySpan addedIds, T& outHookIds) {
5572 outHookIds.clear();
5573 for (const auto id: addedIds) {
5574 if (!id.comp())
5575 continue;
5576
5577 const auto& item = comp_cache().get(id);
5578 if (ComponentCache::hooks(item).func_add != nullptr)
5579 outHookIds.push_back(id);
5580 }
5581 }
5582
5583 GAIA_NODISCARD Entity instantiate_prefab_node_inter(
5584 Entity prefabEntity, Archetype* pDstArchetype, Entity parentInstance, EntitySpan copiedSparseIds,
5585 EntitySpan addedIds, EntitySpan addHookIds) {
5586 GAIA_ASSERT(!prefabEntity.pair());
5587 GAIA_ASSERT(valid(prefabEntity));
5588 GAIA_ASSERT(has_direct(prefabEntity, Prefab));
5589 GAIA_ASSERT(pDstArchetype != nullptr);
5590#if GAIA_OBSERVERS_ENABLED
5591 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{addedIds});
5592#endif
5593
5594 auto& ecSrc = m_recs.entities[prefabEntity.id()];
5595 GAIA_ASSERT(ecSrc.pArchetype != nullptr);
5596 GAIA_ASSERT(ecSrc.pChunk != nullptr);
5597
5598 EntityContainerCtx ctx{true, false, prefabEntity.kind()};
5599 const auto instance = m_recs.entities.alloc(&ctx);
5600 auto& ecDst = m_recs.entities[instance.id()];
5601 auto* pDstChunk = pDstArchetype->foc_free_chunk();
5602 store_entity(ecDst, instance, pDstArchetype, pDstChunk);
5603 pDstArchetype->try_update_free_chunk_idx();
5604 Chunk::copy_foreign_entity_data(ecSrc.pChunk, ecSrc.row, pDstChunk, ecDst.row);
5605 pDstChunk->update_versions();
5606
5607 ecDst.flags |= EntityContainerFlags::HasAliasOf;
5608
5609 // Keep payload copy and observer/add-id reporting separate:
5610 // fragmenting sparse payloads must still be copied here even though their id is
5611 // already present in the destination archetype and therefore absent from addedIds tail.
5612 (void)copy_sparse_entity_data(prefabEntity, instance, copiedSparseIds);
5613#if GAIA_OBSERVERS_ENABLED
5614 m_observers.append_diff_targets(*this, addDiffCtx, EntitySpan{&instance, 1});
5615#endif
5616
5617 touch_rel_version(Is);
5618 invalidate_queries_for_rel(Is);
5619 m_targetsTravCache = {};
5620 m_srcBfsTravCache = {};
5621 m_depthOrderCache = {};
5622 m_sourcesAllCache = {};
5623 m_targetsAllCache = {};
5624
5625 const auto instanceKey = EntityLookupKey(instance);
5626 const auto prefabKey = EntityLookupKey(prefabEntity);
5627 m_entityToAsTargets[instanceKey].insert(prefabKey);
5628 m_entityToAsTargetsTravCache = {};
5629 m_entityToAsRelations[prefabKey].insert(instanceKey);
5630 m_entityToAsRelationsTravCache = {};
5631 invalidate_queries_for_entity({Is, prefabEntity});
5632
5633#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
5634 if GAIA_UNLIKELY (tearing_down()) {
5635 (void)pDstArchetype;
5636 (void)addedIds;
5637 } else {
5638 lock();
5639
5640 #if GAIA_ENABLE_ADD_DEL_HOOKS
5641 for (const auto id: addHookIds) {
5642 const auto& item = comp_cache().get(id);
5643 const auto& hooks = ComponentCache::hooks(item);
5644 GAIA_ASSERT(hooks.func_add != nullptr);
5645 hooks.func_add(*this, item, instance);
5646 }
5647 #endif
5648
5649 #if GAIA_OBSERVERS_ENABLED
5650 m_observers.on_add(*this, *pDstArchetype, addedIds, EntitySpan{&instance, 1});
5651 #endif
5652
5653 unlock();
5654 }
5655#endif
5656
5657#if GAIA_OBSERVERS_ENABLED
5658 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5659#endif
5660
5661 if (parentInstance != EntityBad)
5662 parent_direct(instance, parentInstance);
5663
5664 return instance;
5665 }
5666
5667 GAIA_NODISCARD Entity instantiate_prefab_node_inter(Entity prefabEntity, Entity parentInstance) {
5668 auto* pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
5669 cnt::darray_ext<Entity, 16> copiedSparseIds;
5670 cnt::darray_ext<Entity, 16> addedIds;
5671 cnt::darray_ext<Entity, 16> addHookIds;
5672 collect_prefab_copied_sparse_ids(prefabEntity, copiedSparseIds);
5673 collect_prefab_added_ids(pDstArchetype, EntitySpan{copiedSparseIds}, addedIds);
5674 collect_prefab_add_hook_ids(EntitySpan{addedIds}, addHookIds);
5675 return instantiate_prefab_node_inter(
5676 prefabEntity, pDstArchetype, parentInstance, EntitySpan{copiedSparseIds}, EntitySpan{addedIds},
5677 EntitySpan{addHookIds});
5678 }
5679
5680 template <typename Func>
5681 void instantiate_prefab_n_inter(
5682 const PrefabInstantiatePlanNode& node, Entity parentInstance, uint32_t count, Func& func) {
5683 GAIA_ASSERT(node.prefab != EntityBad);
5684 GAIA_ASSERT(node.pDstArchetype != nullptr);
5685
5686 if (count == 0U)
5687 return;
5688#if GAIA_OBSERVERS_ENABLED
5689 auto addDiffCtx = m_observers.prepare_diff_add_new(*this, EntitySpan{node.addedIds});
5690#endif
5691
5692 auto& ecSrc = m_recs.entities[node.prefab.id()];
5693 GAIA_ASSERT(ecSrc.pChunk != nullptr);
5694
5695 if (parentInstance != EntityBad)
5696 prepare_parent_batch(parentInstance);
5697
5698 const auto srcRow = ecSrc.row;
5699 auto* pSrcChunk = ecSrc.pChunk;
5700 auto* pDstArchetype = node.pDstArchetype;
5701 const auto prefabKey = EntityLookupKey(node.prefab);
5702 EntityContainerCtx ctx{true, false, node.prefab.kind()};
5703
5704 uint32_t left = count;
5705 do {
5706 auto* pDstChunk = pDstArchetype->foc_free_chunk();
5707 const uint32_t originalChunkSize = pDstChunk->size();
5708 const uint32_t freeSlotsInChunk = pDstChunk->capacity() - originalChunkSize;
5709 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
5710
5711 GAIA_FOR_(toCreate, rowOffset) {
5712 const auto instance = m_recs.entities.alloc(&ctx);
5713 auto& ecDst = m_recs.entities[instance.id()];
5714 store_entity(ecDst, instance, pDstArchetype, pDstChunk);
5715 ecDst.flags |= EntityContainerFlags::HasAliasOf;
5716
5717 (void)copy_sparse_entity_data(node.prefab, instance, EntitySpan{node.copiedSparseIds});
5718 }
5719
5720 pDstArchetype->try_update_free_chunk_idx();
5721 Chunk::copy_foreign_entity_data_n(pSrcChunk, srcRow, pDstChunk, originalChunkSize, toCreate);
5722 pDstChunk->update_versions();
5723
5724 touch_rel_version(Is);
5725 invalidate_queries_for_rel(Is);
5726 m_targetsTravCache = {};
5727 m_srcBfsTravCache = {};
5728 m_depthOrderCache = {};
5729 m_sourcesAllCache = {};
5730 m_targetsAllCache = {};
5731
5732 auto entities = pDstChunk->entity_view();
5733 auto& asRelations = m_entityToAsRelations[prefabKey];
5734 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
5735 const auto instance = entities[rowIdx];
5736 m_entityToAsTargets[EntityLookupKey(instance)].insert(prefabKey);
5737 asRelations.insert(EntityLookupKey(instance));
5738 }
5739 m_entityToAsTargetsTravCache = {};
5740 m_entityToAsRelationsTravCache = {};
5741 invalidate_queries_for_entity({Is, node.prefab});
5742
5743#if GAIA_ENABLE_ADD_DEL_HOOKS || GAIA_OBSERVERS_ENABLED
5744 if GAIA_UNLIKELY (tearing_down()) {
5745 (void)entities;
5746 (void)originalChunkSize;
5747 (void)toCreate;
5748 } else {
5749 lock();
5750
5751 #if GAIA_ENABLE_ADD_DEL_HOOKS
5752 for (const auto id: node.addHookIds) {
5753 const auto& item = comp_cache().get(id);
5754 const auto& hooks = ComponentCache::hooks(item);
5755 GAIA_ASSERT(hooks.func_add != nullptr);
5756
5757 GAIA_FOR2_(originalChunkSize, originalChunkSize + toCreate, rowIdx) {
5758 hooks.func_add(*this, item, entities[rowIdx]);
5759 }
5760 }
5761 #endif
5762
5763 #if GAIA_OBSERVERS_ENABLED
5764 m_observers.append_diff_targets(
5765 *this, addDiffCtx, EntitySpan{entities.data() + originalChunkSize, toCreate});
5766 m_observers.on_add(
5767 *this, *pDstArchetype, EntitySpan{node.addedIds},
5768 EntitySpan{entities.data() + originalChunkSize, toCreate});
5769 #endif
5770
5771 unlock();
5772 }
5773#endif
5774
5775 if (parentInstance != EntityBad)
5776 parent_batch(parentInstance, *pDstArchetype, *pDstChunk, originalChunkSize, toCreate);
5777
5778 invoke_copy_batch_callback(func, pDstArchetype, pDstChunk, originalChunkSize, toCreate);
5779
5780 left -= toCreate;
5781 } while (left > 0);
5782#if GAIA_OBSERVERS_ENABLED
5783 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
5784#endif
5785 }
5786
5787 template <typename T>
5788 void build_prefab_instantiate_plan(Entity prefabEntity, uint32_t parentIdx, T& plan) {
5789 PrefabInstantiatePlanNode node{};
5790 node.prefab = prefabEntity;
5791 node.parentIdx = parentIdx;
5792 node.pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
5793 collect_prefab_copied_sparse_ids(prefabEntity, node.copiedSparseIds);
5794 collect_prefab_added_ids(node.pDstArchetype, EntitySpan{node.copiedSparseIds}, node.addedIds);
5795 collect_prefab_add_hook_ids(EntitySpan{node.addedIds}, node.addHookIds);
5796
5797 const auto nodeIdx = (uint32_t)plan.size();
5798 plan.push_back(GAIA_MOV(node));
5799
5800 cnt::darray_ext<Entity, 16> prefabChildren;
5801 gather_sorted_prefab_children(prefabEntity, prefabChildren);
5802
5803 for (const auto childPrefab: prefabChildren)
5804 build_prefab_instantiate_plan(childPrefab, nodeIdx, plan);
5805 }
5806
5807 GAIA_NODISCARD bool instance_has_prefab_child(Entity parentInstance, Entity childPrefab) const {
5808 bool found = false;
5809 sources(Parent, parentInstance, [&](Entity child) {
5810 if (found)
5811 return;
5812 if (has_direct(child, Pair(Is, childPrefab)))
5813 found = true;
5814 });
5815 return found;
5816 }
5817
5818 uint32_t sync_prefab_instance(
5819 Entity prefabEntity, Entity instance, const PrefabInstantiatePlanNode& node, EntitySpan prefabChildren) {
5820 uint32_t changes = 0;
5821
5822 const auto isPair = Pair(Is, prefabEntity);
5823 for (const auto id: node.pDstArchetype->ids_view()) {
5824 if (id == isPair || has_direct(instance, id))
5825 continue;
5826 if (copy_owned_id_from_entity(prefabEntity, instance, id))
5827 ++changes;
5828 }
5829
5830 for (const auto comp: node.copiedSparseIds) {
5831 if (has_direct(instance, comp))
5832 continue;
5833 if (copy_owned_id_from_entity(prefabEntity, instance, comp))
5834 ++changes;
5835 }
5836
5837 for (const auto childPrefab: prefabChildren) {
5838 if (instance_has_prefab_child(instance, childPrefab))
5839 continue;
5840 (void)instantiate(childPrefab, instance);
5841 ++changes;
5842 }
5843
5844 return changes;
5845 }
5846
5847 uint32_t sync_prefab_inter(Entity prefabEntity, cnt::set<EntityLookupKey>& visited) {
5848 GAIA_ASSERT(!prefabEntity.pair());
5849 GAIA_ASSERT(valid(prefabEntity));
5850
5851 if (!has_direct(prefabEntity, Prefab))
5852 return 0;
5853
5854 const auto ins = visited.insert(EntityLookupKey(prefabEntity));
5855 if (!ins.second)
5856 return 0;
5857
5858 PrefabInstantiatePlanNode node{};
5859 node.prefab = prefabEntity;
5860 node.pDstArchetype = instantiate_prefab_dst_archetype(prefabEntity);
5861 collect_prefab_copied_sparse_ids(prefabEntity, node.copiedSparseIds);
5862 collect_prefab_added_ids(node.pDstArchetype, EntitySpan{node.copiedSparseIds}, node.addedIds);
5863 collect_prefab_add_hook_ids(EntitySpan{node.addedIds}, node.addHookIds);
5864
5865 cnt::darray_ext<Entity, 16> prefabChildren;
5866 gather_sorted_prefab_children(prefabEntity, prefabChildren);
5867
5868 uint32_t changes = 0;
5869 const auto& descendants = as_relations_trav_cache(prefabEntity);
5870 for (const auto entity: descendants) {
5871 if (has_direct(entity, Prefab))
5872 continue;
5873 changes += sync_prefab_instance(prefabEntity, entity, node, EntitySpan{prefabChildren});
5874 }
5875
5876 for (const auto childPrefab: prefabChildren)
5877 changes += sync_prefab_inter(childPrefab, visited);
5878
5879 return changes;
5880 }
5881
5883 GAIA_NODISCARD Entity instantiate_inter(Entity prefabEntity, Entity parentInstance) {
5884 const auto instance = instantiate_prefab_node_inter(prefabEntity, parentInstance);
5885
5886 cnt::darray_ext<Entity, 16> prefabChildren;
5887 gather_sorted_prefab_children(prefabEntity, prefabChildren);
5888
5889 for (const auto childPrefab: prefabChildren)
5890 (void)instantiate_inter(childPrefab, instance);
5891
5892 return instance;
5893 }
5894
5895 public:
5899 GAIA_NODISCARD Entity instantiate(Entity prefabEntity) {
5900 GAIA_ASSERT(!prefabEntity.pair());
5901 GAIA_ASSERT(valid(prefabEntity));
5902
5903 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab))
5904 return copy(prefabEntity);
5905
5906 return instantiate_inter(prefabEntity, EntityBad);
5907 }
5908
5913 GAIA_NODISCARD Entity instantiate(Entity prefabEntity, Entity parentInstance) {
5914 GAIA_ASSERT(!prefabEntity.pair());
5915 GAIA_ASSERT(valid(prefabEntity));
5916 GAIA_ASSERT(valid(parentInstance));
5917
5918 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab)) {
5919 const auto instance = copy(prefabEntity);
5920 parent_direct(instance, parentInstance);
5921 return instance;
5922 }
5923
5924 return instantiate_inter(prefabEntity, parentInstance);
5925 }
5926
5939 template <typename Func = TFunc_Void_With_Entity>
5940 void instantiate_n(Entity prefabEntity, uint32_t count, Func func = func_void_with_entity) {
5941 instantiate_n(prefabEntity, EntityBad, count, func);
5942 }
5943
5945 void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count) {
5946 instantiate_n(prefabEntity, parentInstance, count, func_void_with_entity);
5947 }
5948
5951 template <typename Func>
5952 void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count, Func func) {
5953 GAIA_ASSERT(!prefabEntity.pair());
5954 GAIA_ASSERT(valid(prefabEntity));
5955 GAIA_ASSERT(parentInstance == EntityBad || valid(parentInstance));
5956
5957 if (count == 0U)
5958 return;
5959
5960 if GAIA_UNLIKELY (!has_direct(prefabEntity, Prefab)) {
5961 if (parentInstance == EntityBad) {
5962 copy_n(prefabEntity, count, func);
5963 return;
5964 }
5965
5966 copy_n_inter(prefabEntity, count, func, EntitySpan{}, parentInstance);
5967 return;
5968 }
5969
5972
5973 if constexpr (std::is_invocable_v<Func, CopyIter&>) {
5974 build_prefab_instantiate_plan(prefabEntity, BadIndex, plan);
5975 if (plan.size() == 1) {
5976 instantiate_prefab_n_inter(plan[0], parentInstance, count, func);
5977 return;
5978 }
5979
5980 CopyIterGroupState group;
5981 cnt::darray<Entity> roots;
5982 roots.reserve(count);
5983 auto collectRoot = [&](Entity instance) {
5984 roots.push_back(instance);
5985 };
5986 instantiate_prefab_n_inter(plan[0], parentInstance, count, collectRoot);
5987
5988 spawned.resize((uint32_t)plan.size());
5989
5990 GAIA_FOR_(count, rootIdx) {
5991 spawned[0] = roots[rootIdx];
5992 GAIA_FOR2_(1, (uint32_t)plan.size(), planIdx) {
5993 const auto parent = spawned[plan[planIdx].parentIdx];
5994 spawned[planIdx] = instantiate_prefab_node_inter(
5995 plan[planIdx].prefab, plan[planIdx].pDstArchetype, parent, EntitySpan{plan[planIdx].copiedSparseIds},
5996 EntitySpan{plan[planIdx].addedIds}, EntitySpan{plan[planIdx].addHookIds});
5997 }
5998
5999 push_copy_iter_group(func, group, roots[rootIdx]);
6000 }
6001
6002 flush_copy_iter_group(func, group);
6003 } else {
6004 build_prefab_instantiate_plan(prefabEntity, BadIndex, plan);
6005 if (plan.size() == 1) {
6006 instantiate_prefab_n_inter(plan[0], parentInstance, count, func);
6007 return;
6008 }
6009
6010 cnt::darray<Entity> roots;
6011 roots.reserve(count);
6012 auto collectRoot = [&](Entity instance) {
6013 roots.push_back(instance);
6014 };
6015 instantiate_prefab_n_inter(plan[0], parentInstance, count, collectRoot);
6016
6017 spawned.resize((uint32_t)plan.size());
6018
6019 GAIA_FOR_(count, rootIdx) {
6020 spawned[0] = roots[rootIdx];
6021 GAIA_FOR2_(1, (uint32_t)plan.size(), planIdx) {
6022 const auto parent = spawned[plan[planIdx].parentIdx];
6023 spawned[planIdx] = instantiate_prefab_node_inter(
6024 plan[planIdx].prefab, plan[planIdx].pDstArchetype, parent, EntitySpan{plan[planIdx].copiedSparseIds},
6025 EntitySpan{plan[planIdx].addedIds}, EntitySpan{plan[planIdx].addHookIds});
6026 }
6027
6028 func(roots[rootIdx]);
6029 }
6030 }
6031 }
6032
6036 GAIA_NODISCARD uint32_t sync(Entity prefabEntity) {
6037 GAIA_ASSERT(!prefabEntity.pair());
6038 GAIA_ASSERT(valid(prefabEntity));
6039
6041 return sync_prefab_inter(prefabEntity, visited);
6042 }
6043
6044 //----------------------------------------------------------------------
6045
6048 void del(Entity entity) {
6049 if (!entity.pair()) {
6050 // Delete all relationships associated with this entity (if any)
6051 del_inter(Pair(entity, All));
6052 del_inter(Pair(All, entity));
6053 }
6054
6055 del_inter(entity);
6056 }
6057
6062 void del(Entity entity, Entity object) {
6063 if (!object.pair()) {
6064 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
6065 if (itSparseStore != m_sparseComponentsByComp.end()) {
6066 if (!is_non_fragmenting_out_of_line_component(object)) {
6067 {
6068 EntityBuilder eb(*this, entity);
6069 eb.del(object);
6070 }
6071 itSparseStore->second.func_del(itSparseStore->second.pStore, entity);
6072 return;
6073 }
6074#if GAIA_OBSERVERS_ENABLED
6075 auto delDiffCtx =
6076 m_observers.prepare_diff(*this, ObserverEvent::OnDel, EntitySpan{&object, 1}, EntitySpan{&entity, 1});
6077#endif
6078 notify_inherited_del_dependents(entity, object);
6079 notify_del_single(entity, object);
6080 itSparseStore->second.func_del(itSparseStore->second.pStore, entity);
6081#if GAIA_OBSERVERS_ENABLED
6082 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
6083#endif
6084 return;
6085 }
6086 }
6087 EntityBuilder(*this, entity).del(object);
6088 }
6089
6095 void del(Entity entity, Pair pair) {
6096 EntityBuilder(*this, entity).del(pair);
6097 }
6098
6104 template <typename T>
6105 void del(Entity entity) {
6106 using CT = component_type_t<T>;
6107 using FT = typename CT::TypeFull;
6108
6109 if constexpr (supports_out_of_line_component<FT>()) {
6110 if (const auto* pItem = comp_cache().template find<FT>();
6111 pItem != nullptr && is_out_of_line_component(pItem->entity)) {
6112 if (sparse_component_store<FT>(pItem->entity) != nullptr)
6113 del(entity, pItem->entity);
6114 return;
6115 }
6116 }
6117
6118 EntityBuilder(*this, entity).del<FT>();
6119 }
6120
6121 //----------------------------------------------------------------------
6122
6124 void as(Entity entity, Entity entityBase) {
6125 // Form the relationship
6126 add(entity, Pair(Is, entityBase));
6127 }
6128
6133 GAIA_NODISCARD bool is(Entity entity, Entity entityBase) const {
6134 return is_inter<false>(entity, entityBase);
6135 }
6136
6143 GAIA_NODISCARD bool in(Entity entity, Entity entityBase) const {
6144 return is_inter<true>(entity, entityBase);
6145 }
6146
6147 GAIA_NODISCARD bool is_base(Entity target) const {
6148 GAIA_ASSERT(valid_entity(target));
6149
6150 // Pairs are not supported
6151 if (target.pair())
6152 return false;
6153
6154 const auto it = m_entityToAsRelations.find(EntityLookupKey(target));
6155 return it != m_entityToAsRelations.end();
6156 }
6157
6158 //----------------------------------------------------------------------
6159
6161 void child(Entity entity, Entity parent) {
6162 add(entity, Pair(ChildOf, parent));
6163 }
6164
6167 GAIA_NODISCARD bool child(Entity entity, Entity parent) const {
6168 return has(entity, Pair(ChildOf, parent));
6169 }
6170
6172 void parent(Entity entity, Entity parentEntity) {
6173 add(entity, Pair(Parent, parentEntity));
6174 }
6175
6177 GAIA_NODISCARD bool parent(Entity entity, Entity parentEntity) const {
6178 return has(entity, Pair(Parent, parentEntity));
6179 }
6180
6181 //----------------------------------------------------------------------
6182
6189 template <
6190 typename T
6191#if GAIA_ENABLE_HOOKS
6192 ,
6193 bool TriggerSetEffects
6194#endif
6195 >
6196 void modify(Entity entity) {
6197 GAIA_ASSERT(valid(entity));
6198
6199 if constexpr (supports_out_of_line_component<T>()) {
6200 const auto* pItem = comp_cache().template find<T>();
6201 if (pItem != nullptr && is_out_of_line_component(pItem->entity)) {
6202 auto* pStore = sparse_component_store<typename component_type_t<T>::TypeFull>(pItem->entity);
6203 GAIA_ASSERT(pStore != nullptr);
6204 GAIA_ASSERT(pStore->has(entity));
6205
6206 ::gaia::ecs::update_version(m_worldVersion);
6207
6208#if GAIA_OBSERVERS_ENABLED
6209 if constexpr (TriggerSetEffects)
6210 world_notify_on_set_entity(*this, pItem->entity, entity);
6211#endif
6212 return;
6213 }
6214 }
6215
6216 auto& ec = m_recs.entities[entity.id()];
6217 ec.pChunk->template modify<
6218 T
6219#if GAIA_ENABLE_HOOKS
6220 ,
6221 TriggerSetEffects
6222#endif
6223 >();
6224
6225#if GAIA_OBSERVERS_ENABLED
6226 if constexpr (TriggerSetEffects) {
6227 Entity term = EntityBad;
6228 if constexpr (is_pair<T>::value) {
6229 const auto rel = comp_cache().template get<typename T::rel>().entity;
6230 const auto tgt = comp_cache().template get<typename T::tgt>().entity;
6231 term = (Entity)Pair(rel, tgt);
6232 } else
6233 term = comp_cache().template get<T>().entity;
6234
6235 world_notify_on_set(*this, term, *ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
6236 }
6237#endif
6238 }
6239
6247 template <
6248 typename T
6249#if GAIA_ENABLE_HOOKS
6250 ,
6251 bool TriggerSetEffects
6252#endif
6253 >
6254 void modify(Entity entity, Entity object) {
6255 GAIA_ASSERT(valid(entity));
6256 GAIA_ASSERT(valid(object));
6257
6258 using FT = typename component_type_t<T>::TypeFull;
6259 if constexpr (supports_out_of_line_component<FT>()) {
6260 if (can_use_out_of_line_component<FT>(object)) {
6261 auto* pStore = sparse_component_store<FT>(object);
6262 GAIA_ASSERT(pStore != nullptr);
6263 GAIA_ASSERT(pStore->has(entity));
6264
6265 ::gaia::ecs::update_version(m_worldVersion);
6266
6267#if GAIA_OBSERVERS_ENABLED
6268 if constexpr (TriggerSetEffects)
6269 world_notify_on_set_entity(*this, object, entity);
6270#endif
6271 return;
6272 }
6273 }
6274
6275 auto& ec = m_recs.entities[entity.id()];
6276 const auto compIdx = ec.pChunk->comp_idx(object);
6277 GAIA_ASSERT(compIdx != ComponentIndexBad);
6278
6279 if constexpr (TriggerSetEffects)
6280 ec.pChunk->finish_write(compIdx, ec.row, (uint16_t)(ec.row + 1));
6281 else
6282 ec.pChunk->update_world_version(compIdx);
6283 }
6284
6285 //----------------------------------------------------------------------
6286
6292 GAIA_NODISCARD ComponentSetter acc_mut(Entity entity) {
6293 GAIA_ASSERT(valid(entity));
6294
6295 const auto& ec = m_recs.entities[entity.id()];
6296 return ComponentSetter{*this, ec.pChunk, entity, ec.row};
6297 }
6298
6309 template <typename T>
6310 GAIA_NODISCARD auto set(Entity entity) {
6311 static_assert(!is_pair<T>::value);
6312 using FT = typename component_type_t<T>::TypeFull;
6313 using ValueType = typename actual_type_t<T>::Type;
6314 const auto& item = add<FT>();
6315 return SetWriteProxyTyped<T, ValueType>{*this, entity, item.entity, get<T>(entity)};
6316 }
6317
6329 template <typename T>
6330 GAIA_NODISCARD auto set(Entity entity, Entity object) {
6331 static_assert(!is_pair<T>::value);
6332 return SetWriteProxyObject<typename actual_type_t<T>::Type>{*this, entity, object, get<T>(entity, object)};
6333 }
6334
6342 template <typename T>
6343 GAIA_NODISCARD decltype(auto) sset(Entity entity) {
6344 static_assert(!is_pair<T>::value);
6345 using FT = typename component_type_t<T>::TypeFull;
6346 const auto& item = add<FT>();
6347 if constexpr (supports_out_of_line_component<FT>()) {
6348 if (is_out_of_line_component(item.entity))
6349 return sparse_component_store_mut<FT>(item.entity).mut(entity);
6350 }
6351 return acc_mut(entity).smut<T>();
6352 }
6353
6356 template <typename T>
6357 GAIA_NODISCARD decltype(auto) sset(Entity entity, Entity object) {
6358 static_assert(!is_pair<T>::value);
6359 using FT = typename component_type_t<T>::TypeFull;
6360 if constexpr (supports_out_of_line_component<FT>()) {
6361 if (can_use_out_of_line_component<FT>(object))
6362 return sparse_component_store_mut<FT>(object).mut(entity);
6363 }
6364 return acc_mut(entity).smut<T>(object);
6365 }
6366
6367 //----------------------------------------------------------------------
6368
6376 template <typename T>
6377 GAIA_NODISCARD decltype(auto) mut(Entity entity) {
6378 static_assert(!is_pair<T>::value);
6379 return sset<T>(entity);
6380 }
6381
6384 template <typename T>
6385 GAIA_NODISCARD decltype(auto) mut(Entity entity, Entity object) {
6386 static_assert(!is_pair<T>::value);
6387 return sset<T>(entity, object);
6388 }
6389
6390 //----------------------------------------------------------------------
6391
6398 GAIA_ASSERT(valid(entity));
6399
6400 const auto& ec = m_recs.entities[entity.id()];
6401 return ComponentGetter{*this, ec.pChunk, entity, ec.row};
6402 }
6403
6411 template <typename T>
6412 GAIA_NODISCARD decltype(auto) get(Entity entity) const {
6413 using FT = typename component_type_t<T>::TypeFull;
6414 const auto compEntity = [&]() {
6415 if constexpr (is_pair<FT>::value) {
6416 const auto rel = comp_cache().template get<typename FT::rel>().entity;
6417 const auto tgt = comp_cache().template get<typename FT::tgt>().entity;
6418 return (Entity)Pair(rel, tgt);
6419 } else {
6420 return comp_cache().template get<FT>().entity;
6421 }
6422 }();
6423 if constexpr (supports_out_of_line_component<FT>()) {
6424 const auto* pItem = comp_cache().template find<FT>();
6425 if (pItem != nullptr && is_out_of_line_component(pItem->entity)) {
6426 const auto* pStore = sparse_component_store<FT>(pItem->entity);
6427 GAIA_ASSERT(pStore != nullptr);
6428 if (pStore->has(entity))
6429 return pStore->get(entity);
6430
6431 const auto inheritedOwner = inherited_id_owner(entity, compEntity);
6432 GAIA_ASSERT(inheritedOwner != EntityBad);
6433 return pStore->get(inheritedOwner);
6434 }
6435 }
6436
6437 const auto& ec = m_recs.entities[entity.id()];
6438 if (ec.pArchetype->has(compEntity))
6439 return acc(entity).template get<T>();
6440
6441 const auto inheritedOwner = inherited_id_owner(entity, compEntity);
6442 GAIA_ASSERT(inheritedOwner != EntityBad);
6443 return acc(inheritedOwner).template get<T>();
6444 }
6445
6447 template <typename T>
6448 GAIA_NODISCARD decltype(auto) get(Entity entity, Entity object) const {
6449 using FT = typename component_type_t<T>::TypeFull;
6450 if constexpr (supports_out_of_line_component<FT>()) {
6451 if (can_use_out_of_line_component<FT>(object)) {
6452 const auto* pStore = sparse_component_store<FT>(object);
6453 GAIA_ASSERT(pStore != nullptr);
6454 if (pStore->has(entity))
6455 return pStore->get(entity);
6456
6457 const auto inheritedOwner = inherited_id_owner(entity, object);
6458 GAIA_ASSERT(inheritedOwner != EntityBad);
6459 return pStore->get(inheritedOwner);
6460 }
6461 }
6462
6463 const auto& ec = m_recs.entities[entity.id()];
6464 if (ec.pArchetype->has(object))
6465 return acc(entity).template get<T>(object);
6466
6467 const auto inheritedOwner = inherited_id_owner(entity, object);
6468 GAIA_ASSERT(inheritedOwner != EntityBad);
6469 return acc(inheritedOwner).template get<T>(object);
6470 }
6471
6472 //----------------------------------------------------------------------
6473
6477 GAIA_NODISCARD bool has(Entity entity) const {
6478 // Pair
6479 if (entity.pair()) {
6480 if (entity == Pair(All, All))
6481 return true;
6482
6483 if (is_wildcard(entity)) {
6484 if (!m_entityToArchetypeMap.contains(EntityLookupKey(entity)))
6485 return false;
6486
6487 // If the pair is found, both entities forming it need to be found as well
6488 GAIA_ASSERT(has(get(entity.id())) && has(get(entity.gen())));
6489
6490 return true;
6491 }
6492
6493 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
6494 if (it == m_recs.pairs.end())
6495 return false;
6496
6497 const auto& ec = it->second;
6498 if (is_req_del(ec))
6499 return false;
6500
6501#if GAIA_ASSERT_ENABLED
6502 // If the pair is found, both entities forming it need to be found as well
6503 GAIA_ASSERT(has(get(entity.id())) && has(get(entity.gen())));
6504
6505 // Index of the entity must fit inside the chunk
6506 auto* pChunk = ec.pChunk;
6507 GAIA_ASSERT(pChunk != nullptr && ec.row < pChunk->size());
6508#endif
6509
6510 return true;
6511 }
6512
6513 // Regular entity
6514 {
6515 // Entity ID has to fit inside the entity array
6516 if (entity.id() >= m_recs.entities.size() || !m_recs.entities.has(entity.id()))
6517 return false;
6518
6519 // Index of the entity must fit inside the chunk
6520 const auto& ec = m_recs.entities[entity.id()];
6521 if (is_req_del(ec))
6522 return false;
6523
6524 auto* pChunk = ec.pChunk;
6525 return pChunk != nullptr && ec.row < pChunk->size();
6526 }
6527 }
6528
6532 GAIA_NODISCARD bool has(Pair pair) const {
6533 return has((Entity)pair);
6534 }
6535
6542 GAIA_NODISCARD bool has(Entity entity, Entity object) const {
6543 return has_inter(entity, object, true);
6544 }
6545
6550 GAIA_NODISCARD bool has_direct(Entity entity, Entity object) const {
6551 return has_inter(entity, object, false);
6552 }
6553
6555 GAIA_NODISCARD bool has_direct(Entity entity, Pair pair) const {
6556 return has_inter(entity, (Entity)pair, false);
6557 }
6558
6559 private:
6560 GAIA_NODISCARD bool has_inter(Entity entity, Entity object, bool allowSemanticIs) const {
6561 const auto& ec = fetch(entity);
6562 if (is_req_del(ec))
6563 return false;
6564
6565 if (object.pair() && has_exclusive_adjunct_pair(entity, object))
6566 return true;
6567 if (!object.pair()) {
6568 const auto itSparseStore = m_sparseComponentsByComp.find(EntityLookupKey(object));
6569 if (itSparseStore != m_sparseComponentsByComp.end())
6570 return itSparseStore->second.func_has(itSparseStore->second.pStore, entity) ||
6571 (allowSemanticIs && inherited_id_owner(entity, object) != EntityBad);
6572 }
6573
6574 const auto* pArchetype = ec.pArchetype;
6575
6576 if (object.pair()) {
6577 if (allowSemanticIs && object.id() == Is.id() && !is_wildcard(object.gen())) {
6578 const auto target = get(object.gen());
6579 return valid(target) && is(entity, target);
6580 }
6581
6582 // Early exit if there are no pairs on the archetype
6583 if (pArchetype->pairs() == 0)
6584 return false;
6585
6586 EntityId rel = object.id();
6587 EntityId tgt = object.gen();
6588
6589 // (*,*)
6590 if (rel == All.id() && tgt == All.id())
6591 return true;
6592
6593 // (X,*)
6594 if (rel != All.id() && tgt == All.id()) {
6595 auto ids = pArchetype->ids_view();
6596 for (auto id: ids) {
6597 if (!id.pair())
6598 continue;
6599 if (id.id() == rel)
6600 return true;
6601 }
6602
6603 return false;
6604 }
6605
6606 // (*,X)
6607 if (rel == All.id() && tgt != All.id()) {
6608 auto ids = pArchetype->ids_view();
6609 for (auto id: ids) {
6610 if (!id.pair())
6611 continue;
6612 if (id.gen() == tgt)
6613 return true;
6614 }
6615
6616 return false;
6617 }
6618 }
6619
6620 if (pArchetype->has(object))
6621 return true;
6622
6623 return allowSemanticIs && inherited_id_owner(entity, object) != EntityBad;
6624 }
6625
6626 public:
6630 GAIA_NODISCARD std::span<const Entity> lookup_path() const {
6631 return {m_componentLookupPath.data(), m_componentLookupPath.size()};
6632 }
6633
6638 m_componentLookupPath.clear();
6639 m_componentLookupPath.reserve((uint32_t)scopes.size());
6640 for (const auto scopeEntity: scopes) {
6641 GAIA_ASSERT(scopeEntity != EntityBad && valid(scopeEntity) && !scopeEntity.pair());
6642 if (scopeEntity == EntityBad || !valid(scopeEntity) || scopeEntity.pair())
6643 continue;
6644
6645 m_componentLookupPath.push_back(scopeEntity);
6646 }
6647 }
6648
6651 GAIA_NODISCARD Entity scope() const {
6652 return m_componentScope;
6653 }
6654
6660 GAIA_ASSERT(scope == EntityBad || (valid(scope) && !scope.pair()));
6661 const auto prev = m_componentScope;
6662 if (scope == EntityBad || (valid(scope) && !scope.pair())) {
6663 m_componentScope = scope;
6664 invalidate_scope_path_cache();
6665 }
6666 return prev;
6667 }
6668
6674 template <typename Func>
6675 void scope(Entity scopeEntity, Func&& func) {
6676 struct ComponentScopeRestore final {
6677 World& world;
6678 Entity prevScope;
6679 ~ComponentScopeRestore() {
6680 world.scope(prevScope);
6681 }
6682 };
6683
6684 ComponentScopeRestore restore{*this, scope(scopeEntity)};
6685 func();
6686 }
6687
6694 Entity module(const char* path, uint32_t len = 0) {
6695 if (path == nullptr || path[0] == 0)
6696 return EntityBad;
6697
6698 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(path, ComponentCacheItem::MaxNameLength) : len;
6699 if (l == 0 || l >= ComponentCacheItem::MaxNameLength)
6700 return EntityBad;
6701 if (path[l - 1] == '.')
6702 return EntityBad;
6703
6704 Entity parent = EntityBad;
6705 uint32_t partBeg = 0;
6706 while (partBeg < l) {
6707 uint32_t partEnd = partBeg;
6708 while (partEnd < l && path[partEnd] != '.')
6709 ++partEnd;
6710 if (partEnd == partBeg)
6711 return EntityBad;
6712
6713 const auto partLen = partEnd - partBeg;
6714 const auto key = EntityNameLookupKey(path + partBeg, partLen, 0);
6715 const auto it = m_nameToEntity.find(key);
6716 Entity curr = EntityBad;
6717
6718 if (it != m_nameToEntity.end()) {
6719 curr = it->second;
6720 if (parent != EntityBad && !static_cast<const World&>(*this).child(curr, parent)) {
6721 GAIA_ASSERT2(false, "Module path collides with an existing entity name outside the requested scope");
6722 return EntityBad;
6723 }
6724 } else {
6725 curr = add();
6726 name(curr, path + partBeg, partLen);
6727 if (parent != EntityBad)
6728 child(curr, parent);
6729 }
6730
6731 parent = curr;
6732 partBeg = partEnd + 1;
6733 }
6734
6735 return parent;
6736 }
6737
6744 GAIA_NODISCARD bool has(Entity entity, Pair pair) const {
6745 return has(entity, (Entity)pair);
6746 }
6747
6754 template <typename T>
6755 GAIA_NODISCARD bool has(Entity entity) const {
6756 GAIA_ASSERT(valid(entity));
6757
6758 using FT = typename component_type_t<T>::TypeFull;
6759 const auto compEntity = [&]() {
6760 if constexpr (is_pair<FT>::value) {
6761 const auto* pRel = comp_cache().template find<typename FT::rel>();
6762 const auto* pTgt = comp_cache().template find<typename FT::tgt>();
6763 if (pRel == nullptr || pTgt == nullptr)
6764 return EntityBad;
6765
6766 const auto rel = pRel->entity;
6767 const auto tgt = pTgt->entity;
6768 return (Entity)Pair(rel, tgt);
6769 } else {
6770 const auto* pItem = comp_cache().template find<FT>();
6771 return pItem != nullptr ? pItem->entity : EntityBad;
6772 }
6773 }();
6774 if (compEntity == EntityBad)
6775 return false;
6776
6777 if constexpr (supports_out_of_line_component<FT>()) {
6778 const auto* pItem = comp_cache().template find<FT>();
6779 if (pItem != nullptr && is_out_of_line_component(pItem->entity)) {
6780 const auto* pStore = sparse_component_store<FT>(pItem->entity);
6781 return pStore != nullptr && (pStore->has(entity) || inherited_id_owner(entity, compEntity) != EntityBad);
6782 }
6783 }
6784
6785 const auto& ec = m_recs.entities[entity.id()];
6786 if (is_req_del(ec))
6787 return false;
6788
6789 return ec.pArchetype->has(compEntity) || inherited_id_owner(entity, compEntity) != EntityBad;
6790 }
6791
6792 //----------------------------------------------------------------------
6793
6803 void name(Entity entity, const char* name, uint32_t len = 0) {
6804 EntityBuilder(*this, entity).name(name, len);
6805 }
6806
6820 void name_raw(Entity entity, const char* name, uint32_t len = 0) {
6821 EntityBuilder(*this, entity).name_raw(name, len);
6822 }
6823
6828 GAIA_NODISCARD util::str_view name(Entity entity) const {
6829 if (entity.pair())
6830 return {};
6831
6832 const auto& ec = m_recs.entities[entity.id()];
6833 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
6834 if (compIdx == BadIndex)
6835 return {};
6836
6837 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
6838 GAIA_ASSERT(core::check_alignment(pDesc));
6839 return {pDesc->name, pDesc->name_len};
6840 }
6841
6846 GAIA_NODISCARD util::str_view name(EntityId entityId) const {
6847 auto entity = get(entityId);
6848 return name(entity);
6849 }
6850
6851 //----------------------------------------------------------------------
6852
6860 GAIA_NODISCARD Entity resolve(const char* name, uint32_t len = 0) const {
6861 if (name == nullptr || name[0] == 0)
6862 return EntityBad;
6863
6864 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
6865 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
6866
6867 if (memchr(name, '.', l) != nullptr) {
6868 const auto entity = get_entity_inter(name, l);
6869 if (entity != EntityBad)
6870 return entity;
6871 }
6872
6873 return get_inter(name, l);
6874 }
6875
6881 void resolve(cnt::darray<Entity>& out, const char* name, uint32_t len = 0) const {
6882 out.clear();
6883 if (name == nullptr || name[0] == 0)
6884 return;
6885
6886 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
6887 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
6888
6889 auto push_unique = [&](Entity entity) {
6890 if (entity == EntityBad)
6891 return;
6892 for (const auto existing: out) {
6893 if (existing == entity)
6894 return;
6895 }
6896 out.push_back(entity);
6897 };
6898
6899 push_unique(get_entity_inter(name, l));
6900
6901 if (memchr(name, '.', l) == nullptr && memchr(name, ':', l) == nullptr) {
6902 const auto pushLookupHit = [&](const ComponentCacheItem& item) {
6903 push_unique(item.entity);
6904 return false;
6905 };
6906
6907 (void)find_component_in_scope_chain_inter(m_componentScope, name, l, pushLookupHit);
6908 for (const auto scopeEntity: m_componentLookupPath) {
6909 if (scopeEntity == m_componentScope)
6910 continue;
6911
6912 (void)find_component_in_scope_chain_inter(scopeEntity, name, l, pushLookupHit);
6913 }
6914 }
6915
6916 if (const auto* pItem = m_compCache.symbol(name, l); pItem != nullptr)
6917 push_unique(pItem->entity);
6918
6919 if (const auto* pItem = m_compCache.path(name, l); pItem != nullptr) {
6920 push_unique(pItem->entity);
6921 } else {
6922 const auto needle = util::str_view(name, l);
6923 m_compCache.for_each_item([&](const ComponentCacheItem& item) {
6924 if (item.path.view() == needle)
6925 push_unique(item.entity);
6926 });
6927 }
6928
6929 if (out.empty() && memchr(name, '.', l) == nullptr && memchr(name, ':', l) == nullptr) {
6930 if (const auto* pItem = m_compCache.short_symbol(name, l); pItem != nullptr)
6931 push_unique(pItem->entity);
6932 }
6933
6934 push_unique(alias(name, l));
6935 }
6936
6942 GAIA_NODISCARD Entity get(const char* name, uint32_t len = 0) const {
6943 return resolve(name, len);
6944 }
6945
6946 private:
6947 GAIA_NODISCARD Entity find_named_entity_inter(const char* name, uint32_t len = 0) const {
6948 if (name == nullptr || name[0] == 0)
6949 return EntityBad;
6950
6951 const auto key = EntityNameLookupKey(name, len, 0);
6952 const auto it = m_nameToEntity.find(key);
6953 return it != m_nameToEntity.end() ? it->second : EntityBad;
6954 }
6955
6956 GAIA_NODISCARD Entity get_entity_inter(const char* name, uint32_t len = 0) const {
6957 if (name == nullptr || name[0] == 0)
6958 return EntityBad;
6959
6960 if (len == 0) {
6961 while (name[len] != '\0')
6962 ++len;
6963 }
6964
6965 Entity parent = EntityBad;
6966 Entity child = EntityBad;
6967 uint32_t posDot = 0;
6968 std::span<const char> str(name, len);
6969
6970 posDot = core::get_index(str, '.');
6971 if (posDot == BadIndex)
6972 return find_named_entity_inter(str.data(), (uint32_t)str.size());
6973
6974 if (posDot == 0)
6975 return EntityBad;
6976
6977 parent = find_named_entity_inter(str.data(), posDot);
6978 if (parent == EntityBad)
6979 return EntityBad;
6980
6981 str = str.subspan(posDot + 1);
6982 while (!str.empty()) {
6983 posDot = core::get_index(str, '.');
6984
6985 if (posDot == BadIndex) {
6986 child = find_named_entity_inter(str.data(), (uint32_t)str.size());
6987 if (child == EntityBad || !this->child(child, parent))
6988 return EntityBad;
6989
6990 return child;
6991 }
6992
6993 if (posDot == 0)
6994 return EntityBad;
6995
6996 child = find_named_entity_inter(str.data(), posDot);
6997 if (child == EntityBad || !this->child(child, parent))
6998 return EntityBad;
6999
7000 parent = child;
7001 str = str.subspan(posDot + 1);
7002 }
7003
7004 return parent;
7005 }
7006
7007 GAIA_NODISCARD Entity get_inter(const char* name, uint32_t len = 0) const {
7008 if (name == nullptr || name[0] == 0)
7009 return EntityBad;
7010
7011 auto key = EntityNameLookupKey(name, len, 0);
7012 const auto l = key.len();
7013
7014 if ((m_componentScope != EntityBad || !m_componentLookupPath.empty()) && memchr(name, '.', l) == nullptr &&
7015 memchr(name, ':', l) == nullptr) {
7016 const auto* pScopedItem = resolve_component_name_inter(name, l);
7017 if (pScopedItem != nullptr) {
7018 const auto it = m_nameToEntity.find(key);
7019 if (it == m_nameToEntity.end())
7020 return pScopedItem->entity;
7021
7022 if (m_compCache.find(it->second) != nullptr)
7023 return pScopedItem->entity;
7024 }
7025 }
7026
7027 const auto it = m_nameToEntity.find(key);
7028 if (it != m_nameToEntity.end())
7029 return it->second;
7030
7031 // Name not found. This might be a component so check the component cache
7032 const auto* pItem = resolve_component_name_inter(name, l);
7033 if (pItem != nullptr)
7034 return pItem->entity;
7035
7036 const auto aliasEntity = alias(name, l);
7037 if (aliasEntity != EntityBad)
7038 return aliasEntity;
7039
7040 // No entity with the given name exists. Return a bad entity
7041 return EntityBad;
7042 }
7043
7044 public:
7045 //----------------------------------------------------------------------
7046
7050 GAIA_NODISCARD const cnt::set<EntityLookupKey>* relations(Entity target) const {
7051 const auto it = m_tgtToRel.find(EntityLookupKey(target));
7052 if (it == m_tgtToRel.end())
7053 return nullptr;
7054
7055 return &it->second;
7056 }
7057
7063 GAIA_NODISCARD Entity relation(Entity entity, Entity target) const {
7064 GAIA_ASSERT(valid(entity));
7065 if (!valid(target))
7066 return EntityBad;
7067
7068 const auto itAdjunctRels = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(entity));
7069 if (itAdjunctRels != m_srcToExclusiveAdjunctRel.end()) {
7070 for (auto relKey: itAdjunctRels->second) {
7071 const auto relation = relKey;
7072 const auto* pStore = exclusive_adjunct_store(relation);
7073 if (pStore == nullptr)
7074 continue;
7075
7076 if (exclusive_adjunct_target(*pStore, entity) == target)
7077 return relation;
7078 }
7079 }
7080
7081 const auto& ec = fetch(entity);
7082 const auto* pArchetype = ec.pArchetype;
7083
7084 // Early exit if there are no pairs on the archetype
7085 if (pArchetype->pairs() == 0)
7086 return EntityBad;
7087
7088 const auto indices = pArchetype->pair_tgt_indices(target);
7089 if (indices.empty())
7090 return EntityBad;
7091
7092 const auto ids = pArchetype->ids_view();
7093 const auto e = ids[indices[0]];
7094 const auto& ecRel = m_recs.entities[e.id()];
7095 return *ecRel.pEntity;
7096 }
7097
7103 template <typename Func>
7104 void relations(Entity entity, Entity target, Func func) const {
7105 GAIA_ASSERT(valid(entity));
7106 if (!valid(target))
7107 return;
7108
7109 const auto itAdjunctRels = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(entity));
7110 if (itAdjunctRels != m_srcToExclusiveAdjunctRel.end()) {
7111 for (auto relKey: itAdjunctRels->second) {
7112 const auto relation = relKey;
7113 const auto* pStore = exclusive_adjunct_store(relation);
7114 if (pStore == nullptr)
7115 continue;
7116
7117 if (exclusive_adjunct_target(*pStore, entity) == target)
7118 func(relation);
7119 }
7120 }
7121
7122 const auto& ec = fetch(entity);
7123 const auto* pArchetype = ec.pArchetype;
7124
7125 // Early exit if there are no pairs on the archetype
7126 if (pArchetype->pairs() == 0)
7127 return;
7128
7129 const auto ids = pArchetype->ids_view();
7130 for (auto idsIdx: pArchetype->pair_tgt_indices(target)) {
7131 const auto e = ids[idsIdx];
7132
7133 const auto& ecRel = m_recs.entities[e.id()];
7134 auto relation = *ecRel.pEntity;
7135 func(relation);
7136 }
7137 }
7138
7145 template <typename Func>
7146 void relations_if(Entity entity, Entity target, Func func) const {
7147 GAIA_ASSERT(valid(entity));
7148 if (!valid(target))
7149 return;
7150
7151 const auto itAdjunctRels = m_srcToExclusiveAdjunctRel.find(EntityLookupKey(entity));
7152 if (itAdjunctRels != m_srcToExclusiveAdjunctRel.end()) {
7153 for (auto relKey: itAdjunctRels->second) {
7154 const auto relation = relKey;
7155 const auto* pStore = exclusive_adjunct_store(relation);
7156 if (pStore == nullptr)
7157 continue;
7158
7159 if (exclusive_adjunct_target(*pStore, entity) == target) {
7160 if (!func(relation))
7161 return;
7162 }
7163 }
7164 }
7165
7166 const auto& ec = fetch(entity);
7167 const auto* pArchetype = ec.pArchetype;
7168
7169 // Early exit if there are no pairs on the archetype
7170 if (pArchetype->pairs() == 0)
7171 return;
7172
7173 const auto ids = pArchetype->ids_view();
7174 for (auto idsIdx: pArchetype->pair_tgt_indices(target)) {
7175 const auto e = ids[idsIdx];
7176
7177 const auto& ecRel = m_recs.entities[e.id()];
7178 auto relation = *ecRel.pEntity;
7179 if (!func(relation))
7180 return;
7181 }
7182 }
7183
7186 GAIA_NODISCARD const cnt::darray<Entity>& as_relations_trav_cache(Entity target) const {
7187 const auto key = EntityLookupKey(target);
7188 const auto itCache = m_entityToAsRelationsTravCache.find(key);
7189 if (itCache != m_entityToAsRelationsTravCache.end())
7190 return itCache->second;
7191
7192 auto& cache = m_entityToAsRelationsTravCache[key];
7193 const auto it = m_entityToAsRelations.find(key);
7194 if (it == m_entityToAsRelations.end())
7195 return cache;
7196
7199 stack.reserve((uint32_t)it->second.size());
7200 for (auto relation: it->second)
7201 stack.push_back(relation);
7202
7203 while (!stack.empty()) {
7204 const auto relationKey = stack.back();
7205 stack.pop_back();
7206
7207 const auto relation = relationKey.entity();
7208 cache.push_back(relation);
7209
7210 const auto itChild = m_entityToAsRelations.find(relationKey);
7211 if (itChild == m_entityToAsRelations.end())
7212 continue;
7213
7214 for (auto childRelation: itChild->second)
7215 stack.push_back(childRelation);
7216 }
7217
7218 return cache;
7219 }
7220
7223 GAIA_NODISCARD const cnt::darray<Entity>& as_targets_trav_cache(Entity relation) const {
7224 const auto key = EntityLookupKey(relation);
7225 const auto itCache = m_entityToAsTargetsTravCache.find(key);
7226 if (itCache != m_entityToAsTargetsTravCache.end())
7227 return itCache->second;
7228
7229 auto& cache = m_entityToAsTargetsTravCache[key];
7230 const auto it = m_entityToAsTargets.find(key);
7231 if (it == m_entityToAsTargets.end())
7232 return cache;
7233
7236 stack.reserve((uint32_t)it->second.size());
7237 for (auto target: it->second)
7238 stack.push_back(target);
7239
7240 while (!stack.empty()) {
7241 const auto targetKey = stack.back();
7242 stack.pop_back();
7243
7244 const auto target = targetKey.entity();
7245 cache.push_back(target);
7246
7247 const auto itChild = m_entityToAsTargets.find(targetKey);
7248 if (itChild == m_entityToAsTargets.end())
7249 continue;
7250
7251 for (auto childTarget: itChild->second)
7252 stack.push_back(childTarget);
7253 }
7254
7255 return cache;
7256 }
7257
7260 GAIA_NODISCARD const cnt::darray<Entity>& targets_trav_cache(Entity relation, Entity source) const {
7261 const auto key = EntityLookupKey(Pair(relation, source));
7262 const auto itCache = m_targetsTravCache.find(key);
7263 if (itCache != m_targetsTravCache.end())
7264 return itCache->second;
7265
7266 auto& cache = m_targetsTravCache[key];
7267 if (!valid(relation) || !valid(source))
7268 return cache;
7269
7270 auto curr = source;
7271 GAIA_FOR(MAX_TRAV_DEPTH) {
7272 const auto next = target(curr, relation);
7273 if (next == EntityBad || next == curr)
7274 break;
7275
7276 cache.push_back(next);
7277 curr = next;
7278 }
7279
7280 return cache;
7281 }
7282
7285 GAIA_NODISCARD const cnt::darray<Entity>& targets_all_cache(Entity source) const {
7286 const auto key = EntityLookupKey(source);
7287 const auto itCache = m_targetsAllCache.find(key);
7288 if (itCache != m_targetsAllCache.end())
7289 return itCache->second;
7290
7291 auto& cache = m_targetsAllCache[key];
7292 if (!valid(source))
7293 return cache;
7294
7295 const auto visitStamp = next_entity_visit_stamp();
7296
7297 const auto itAdjunctRels = m_srcToExclusiveAdjunctRel.find(key);
7298 if (itAdjunctRels != m_srcToExclusiveAdjunctRel.end()) {
7299 for (auto rel: itAdjunctRels->second) {
7300 const auto* pStore = exclusive_adjunct_store(rel);
7301 if (pStore == nullptr)
7302 continue;
7303
7304 const auto target = exclusive_adjunct_target(*pStore, source);
7305 if (target != EntityBad && try_mark_entity_visited(target, visitStamp))
7306 cache.push_back(target);
7307 }
7308 }
7309
7310 const auto& ec = fetch(source);
7311 const auto* pArchetype = ec.pArchetype;
7312 if (pArchetype->pairs() == 0)
7313 return cache;
7314
7315 const auto ids = pArchetype->ids_view();
7316 for (auto idsIdx: pArchetype->pair_indices()) {
7317 const auto id = ids[idsIdx];
7318 const auto target = pair_target_if_alive(id);
7319 if (target == EntityBad)
7320 continue;
7321 if (try_mark_entity_visited(target, visitStamp))
7322 cache.push_back(target);
7323 }
7324
7325 return cache;
7326 }
7327
7330 GAIA_NODISCARD const cnt::darray<Entity>& sources_all_cache(Entity target) const {
7331 const auto key = EntityLookupKey(target);
7332 const auto itCache = m_sourcesAllCache.find(key);
7333 if (itCache != m_sourcesAllCache.end())
7334 return itCache->second;
7335
7336 auto& cache = m_sourcesAllCache[key];
7337 if (!valid(target))
7338 return cache;
7339
7340 const auto visitStamp = next_entity_visit_stamp();
7341
7342 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
7343 (void)relKey;
7344 const auto* pSources = exclusive_adjunct_sources(store, target);
7345 if (pSources == nullptr)
7346 continue;
7347
7348 for (auto source: *pSources) {
7349 if (valid(source) && try_mark_entity_visited(source, visitStamp))
7350 cache.push_back(source);
7351 }
7352 }
7353
7354 const auto pair = Pair(All, target);
7355 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
7356 if (it == m_entityToArchetypeMap.end())
7357 return cache;
7358
7359 for (const auto& record: it->second) {
7360 const auto* pArchetype = record.pArchetype;
7361 if (pArchetype->is_req_del())
7362 continue;
7363
7364 for (const auto* pChunk: pArchetype->chunks()) {
7365 const auto entities = pChunk->entity_view();
7366 GAIA_EACH(entities) {
7367 const auto source = entities[i];
7368 if (!valid(source))
7369 continue;
7370 if (try_mark_entity_visited(source, visitStamp))
7371 cache.push_back(source);
7372 }
7373 }
7374 }
7375
7376 return cache;
7377 }
7378
7381 template <typename Func>
7382 void targets_trav(Entity relation, Entity source, Func func) const {
7383 if (!valid(relation) || !valid(source))
7384 return;
7385
7386 auto curr = source;
7387 GAIA_FOR(MAX_TRAV_DEPTH) {
7388 const auto next = target(curr, relation);
7389 if (next == EntityBad || next == curr)
7390 break;
7391 if (!enabled(next))
7392 break;
7393
7394 func(next);
7395 curr = next;
7396 }
7397 }
7398
7402 template <typename Func>
7403 GAIA_NODISCARD bool targets_trav_if(Entity relation, Entity source, Func func) const {
7404 if (!valid(relation) || !valid(source))
7405 return false;
7406
7407 auto curr = source;
7408 GAIA_FOR(MAX_TRAV_DEPTH) {
7409 const auto next = target(curr, relation);
7410 if (next == EntityBad || next == curr)
7411 break;
7412 if (!enabled(next))
7413 break;
7414
7415 if (!func(next))
7416 return true;
7417 curr = next;
7418 }
7419
7420 return false;
7421 }
7422
7425 GAIA_NODISCARD const cnt::darray<Entity>& sources_bfs_trav_cache(Entity relation, Entity rootTarget) const {
7426 const auto key = EntityLookupKey(Pair(relation, rootTarget));
7427 const auto itCache = m_srcBfsTravCache.find(key);
7428 if (itCache != m_srcBfsTravCache.end())
7429 return itCache->second;
7430
7431 auto& cache = m_srcBfsTravCache[key];
7432 if (!valid(relation) || !valid(rootTarget))
7433 return cache;
7434
7436 queue.push_back(rootTarget);
7437
7439 visited.insert(EntityLookupKey(rootTarget));
7440
7441 for (uint32_t i = 0; i < queue.size(); ++i) {
7442 const auto currTarget = queue[i];
7443
7444 cnt::darray<Entity> children;
7445 sources(relation, currTarget, [&](Entity source) {
7446 const auto keySource = EntityLookupKey(source);
7447 const auto ins = visited.insert(keySource);
7448 if (!ins.second)
7449 return;
7450
7451 children.push_back(source);
7452 });
7453
7454 core::sort(children, [](Entity left, Entity right) {
7455 return left.id() < right.id();
7456 });
7457
7458 for (auto child: children) {
7459 cache.push_back(child);
7460 queue.push_back(child);
7461 }
7462 }
7463
7464 return cache;
7465 }
7466
7470 GAIA_NODISCARD uint32_t depth_order_cache(Entity relation, Entity sourceTarget) const {
7471 const auto key = EntityLookupKey(Pair(relation, sourceTarget));
7472 const auto itCache = m_depthOrderCache.find(key);
7473 if (itCache != m_depthOrderCache.end()) {
7474 GAIA_ASSERT(itCache->second != GroupIdMax && "depth_order requires an acyclic relation graph");
7475 return itCache->second;
7476 }
7477
7478 if (!valid(relation) || !valid(sourceTarget))
7479 return 0;
7480
7481 // Mark this node as in-flight so cycles trip a debug assert instead of recursing forever.
7482 m_depthOrderCache[key] = GroupIdMax;
7483
7484 uint32_t depth = 1;
7485 targets(sourceTarget, relation, [&](Entity next) {
7486 const auto nextDepth = depth_order_cache(relation, next);
7487 if (nextDepth == 0)
7488 return;
7489 const auto candidate = nextDepth + 1;
7490 if (candidate > depth)
7491 depth = candidate;
7492 });
7493
7494 m_depthOrderCache[key] = depth;
7495 return depth;
7496 }
7497
7502 template <typename Func>
7503 void as_relations_trav(Entity target, Func func) const {
7504 if (!valid(target))
7505 return;
7506
7507 const auto& relations = as_relations_trav_cache(target);
7508 for (auto relation: relations)
7509 func(relation);
7510 }
7511
7517 template <typename Func>
7518 GAIA_NODISCARD bool as_relations_trav_if(Entity target, Func func) const {
7519 if (!valid(target))
7520 return false;
7521
7522 const auto& relations = as_relations_trav_cache(target);
7523 for (auto relation: relations) {
7524 if (func(relation))
7525 return true;
7526 }
7527
7528 return false;
7529 }
7530
7531 //----------------------------------------------------------------------
7532
7536 GAIA_NODISCARD const cnt::set<EntityLookupKey>* targets(Entity relation) const {
7537 const auto it = m_relToTgt.find(EntityLookupKey(relation));
7538 if (it == m_relToTgt.end())
7539 return nullptr;
7540
7541 return &it->second;
7542 }
7543
7549 GAIA_NODISCARD Entity target(Entity entity, Entity relation) const {
7550 if (!valid(entity))
7551 return EntityBad;
7552 if (relation != All && !valid(relation))
7553 return EntityBad;
7554
7555 if (relation == All) {
7556 const auto& targets = targets_all_cache(entity);
7557 return targets.empty() ? EntityBad : targets[0];
7558 }
7559
7560 if (is_exclusive_dont_fragment_relation(relation)) {
7561 const auto* pStore = exclusive_adjunct_store(relation);
7562 if (pStore == nullptr)
7563 return EntityBad;
7564
7565 return exclusive_adjunct_target(*pStore, entity);
7566 }
7567
7568 const auto& ec = fetch(entity);
7569 const auto* pArchetype = ec.pArchetype;
7570
7571 // Early exit if there are no pairs on the archetype
7572 if (pArchetype->pairs() == 0)
7573 return EntityBad;
7574
7575 const auto ids = pArchetype->ids_view();
7576 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
7577 const auto e = ids[idsIdx];
7578 const auto target = pair_target_if_alive(e);
7579 if (target == EntityBad)
7580 continue;
7581 return target;
7582 }
7583
7584 return EntityBad;
7585 }
7586
7592 template <typename Func>
7593 void targets(Entity entity, Entity relation, Func func) const {
7594 if (!valid(entity))
7595 return;
7596 if (relation != All && !valid(relation))
7597 return;
7598
7599 if (relation == All) {
7600 for (auto target: targets_all_cache(entity))
7601 func(target);
7602 return;
7603 }
7604
7605 if (is_exclusive_dont_fragment_relation(relation)) {
7606 const auto target = this->target(entity, relation);
7607 if (target != EntityBad)
7608 func(target);
7609 return;
7610 }
7611
7612 const auto& ec = fetch(entity);
7613 const auto* pArchetype = ec.pArchetype;
7614
7615 // Early exit if there are no pairs on the archetype
7616 if (pArchetype->pairs() == 0)
7617 return;
7618
7619 const auto ids = pArchetype->ids_view();
7620 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
7621 const auto e = ids[idsIdx];
7622 const auto target = pair_target_if_alive(e);
7623 if (target == EntityBad)
7624 continue;
7625 func(target);
7626 }
7627 }
7628
7635 template <typename Func>
7636 void targets_if(Entity entity, Entity relation, Func func) const {
7637 GAIA_ASSERT(valid(entity));
7638 if (relation != All && !valid(relation))
7639 return;
7640
7641 if (relation == All) {
7642 for (auto target: targets_all_cache(entity)) {
7643 if (!func(target))
7644 return;
7645 }
7646 return;
7647 }
7648
7649 if (is_exclusive_dont_fragment_relation(relation)) {
7650 const auto target = this->target(entity, relation);
7651 if (target != EntityBad)
7652 (void)func(target);
7653 return;
7654 }
7655
7656 const auto& ec = fetch(entity);
7657 const auto* pArchetype = ec.pArchetype;
7658
7659 // Early exit if there are no pairs on the archetype
7660 if (pArchetype->pairs() == 0)
7661 return;
7662
7663 const auto ids = pArchetype->ids_view();
7664 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
7665 const auto e = ids[idsIdx];
7666 const auto target = pair_target_if_alive(e);
7667 if (target == EntityBad)
7668 continue;
7669 if (!func(target))
7670 return;
7671 }
7672 }
7673
7675 GAIA_NODISCARD Entity pair_target_if_alive(Entity pair) const {
7676 GAIA_ASSERT(pair.pair());
7677 if (!valid_entity_id((EntityId)pair.gen()))
7678 return EntityBad;
7679
7680 const auto& ecTarget = m_recs.entities[pair.gen()];
7681 if (ecTarget.pEntity == nullptr)
7682 return EntityBad;
7683
7684 const auto target = *ecTarget.pEntity;
7685 return valid(target) ? target : EntityBad;
7686 }
7687
7693 template <typename Func>
7694 void sources(Entity relation, Entity target, Func func) const {
7695 if ((relation != All && !valid(relation)) || !valid(target))
7696 return;
7697
7698 if (relation == All) {
7699 for (auto source: sources_all_cache(target))
7700 func(source);
7701 return;
7702 }
7703
7704 if (is_exclusive_dont_fragment_relation(relation)) {
7705 const auto* pStore = exclusive_adjunct_store(relation);
7706 if (pStore == nullptr)
7707 return;
7708
7709 const auto* pSources = exclusive_adjunct_sources(*pStore, target);
7710 if (pSources == nullptr)
7711 return;
7712
7713 for (auto source: *pSources) {
7714 if (!valid(source))
7715 continue;
7716
7717 func(source);
7718 }
7719 return;
7720 }
7721
7722 const auto pair = Pair(relation, target);
7723 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
7724 if (it == m_entityToArchetypeMap.end())
7725 return;
7726
7727 for (const auto& record: it->second) {
7728 const auto* pArchetype = record.pArchetype;
7729 if (pArchetype->is_req_del())
7730 continue;
7731
7732 for (const auto* pChunk: pArchetype->chunks()) {
7733 auto entities = pChunk->entity_view();
7734 GAIA_EACH(entities) {
7735 const auto source = entities[i];
7736 if (!valid(source))
7737 continue;
7738 func(source);
7739 }
7740 }
7741 }
7742 }
7743
7750 template <typename Func>
7751 void sources_if(Entity relation, Entity target, Func func) const {
7752 if ((relation != All && !valid(relation)) || !valid(target))
7753 return;
7754
7755 if (relation == All) {
7756 for (auto source: sources_all_cache(target)) {
7757 if (!func(source))
7758 return;
7759 }
7760 return;
7761 }
7762
7763 if (is_exclusive_dont_fragment_relation(relation)) {
7764 const auto* pStore = exclusive_adjunct_store(relation);
7765 if (pStore == nullptr)
7766 return;
7767
7768 const auto* pSources = exclusive_adjunct_sources(*pStore, target);
7769 if (pSources == nullptr)
7770 return;
7771
7772 for (auto source: *pSources) {
7773 if (!valid(source))
7774 continue;
7775
7776 if (!func(source))
7777 return;
7778 }
7779 return;
7780 }
7781
7782 const auto pair = Pair(relation, target);
7783 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
7784 if (it == m_entityToArchetypeMap.end())
7785 return;
7786
7787 for (const auto& record: it->second) {
7788 const auto* pArchetype = record.pArchetype;
7789 if (pArchetype->is_req_del())
7790 continue;
7791
7792 for (const auto* pChunk: pArchetype->chunks()) {
7793 auto entities = pChunk->entity_view();
7794 GAIA_EACH(entities) {
7795 const auto source = entities[i];
7796 if (!valid(source))
7797 continue;
7798 if (!func(source))
7799 return;
7800 }
7801 }
7802 }
7803 }
7804
7805 private:
7806 GAIA_NODISCARD uint64_t next_entity_visit_stamp() const {
7807 ++m_entityVisitStamp;
7808 if (m_entityVisitStamp != 0)
7809 return m_entityVisitStamp;
7810
7811 const auto cnt = (uint32_t)m_entityVisitStamps.size();
7812 GAIA_FOR(cnt) {
7813 m_entityVisitStamps[i] = 0;
7814 }
7815
7816 m_entityVisitStamp = 1;
7817 return m_entityVisitStamp;
7818 }
7819
7820 GAIA_NODISCARD bool try_mark_entity_visited(Entity entity, uint64_t stamp) const {
7821 GAIA_ASSERT(!entity.pair());
7822 if (entity.id() >= m_entityVisitStamps.size())
7823 m_entityVisitStamps.resize(m_recs.entities.size(), 0);
7824
7825 auto& slot = m_entityVisitStamps[entity.id()];
7826 if (slot == stamp)
7827 return false;
7828
7829 slot = stamp;
7830 return true;
7831 }
7832
7833 template <typename Func>
7834 GAIA_NODISCARD bool for_each_inherited_term_entity(Entity term, Func&& func) const {
7835 cnt::set<EntityLookupKey> seen;
7836 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
7837 if (it == m_entityToArchetypeMap.end())
7838 return true;
7839
7840 for (const auto& record: it->second) {
7841 const auto* pArchetype = record.pArchetype;
7842 if (pArchetype->is_req_del())
7843 continue;
7844
7845 for (const auto* pChunk: pArchetype->chunks()) {
7846 const auto entities = pChunk->entity_view();
7847 GAIA_EACH(entities) {
7848 const auto entity = entities[i];
7849 GAIA_ASSERT(valid(entity));
7850 const auto entityKey = EntityLookupKey(entity);
7851 if (seen.contains(entityKey))
7852 continue;
7853 seen.insert(entityKey);
7854
7855 if (!func(entity))
7856 return false;
7857
7858 const auto& descendants = as_relations_trav_cache(entity);
7859 for (const auto descendant: descendants) {
7860 GAIA_ASSERT(valid(descendant));
7861 const auto descendantKey = EntityLookupKey(descendant);
7862 if (seen.contains(descendantKey))
7863 continue;
7864 seen.insert(descendantKey);
7865
7866 if (!func(descendant))
7867 return false;
7868 }
7869 }
7870 }
7871 }
7872
7873 return true;
7874 }
7875
7878 GAIA_NODISCARD uint32_t count_direct_term_entities_inter(Entity term, bool allowSemanticIs) const {
7879 if (term == EntityBad)
7880 return 0;
7881
7882 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
7883 const auto target = get(term.gen());
7884 if (!valid(target))
7885 return 0;
7886
7887 return (uint32_t)as_relations_trav_cache(target).size() + 1;
7888 }
7889
7890 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
7891 uint32_t cnt = 0;
7892 (void)for_each_inherited_term_entity(term, [&](Entity) {
7893 ++cnt;
7894 return true;
7895 });
7896 return cnt;
7897 }
7898
7899 if (term.pair() && is_exclusive_dont_fragment_relation(entity_from_id(*this, term.id()))) {
7900 const auto relation = entity_from_id(*this, term.id());
7901 const auto* pStore = exclusive_adjunct_store(relation);
7902 if (pStore == nullptr)
7903 return 0;
7904
7905 if (is_wildcard(term.gen()))
7906 return pStore->srcToTgtCnt;
7907
7908 const auto* pSources = exclusive_adjunct_sources(*pStore, entity_from_id(*this, term.gen()));
7909 return pSources != nullptr ? (uint32_t)pSources->size() : 0;
7910 }
7911
7912 if (!term.pair() && is_non_fragmenting_out_of_line_component(term)) {
7913 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
7914 return it != m_sparseComponentsByComp.end() ? it->second.func_count(it->second.pStore) : 0;
7915 }
7916
7917 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
7918 if (it == m_entityToArchetypeMap.end())
7919 return 0;
7920
7921 uint32_t cnt = 0;
7922 for (const auto& record: it->second) {
7923 const auto* pArchetype = record.pArchetype;
7924 if (pArchetype->is_req_del())
7925 continue;
7926 for (const auto* pChunk: pArchetype->chunks())
7927 cnt += pChunk->size();
7928 }
7929
7930 return cnt;
7931 }
7932
7934 void collect_direct_term_entities_inter(Entity term, cnt::darray<Entity>& out, bool allowSemanticIs) const {
7935 if (term == EntityBad)
7936 return;
7937
7938 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
7939 const auto target = get(term.gen());
7940 if (!valid(target))
7941 return;
7942
7943 out.push_back(target);
7944 const auto& relations = as_relations_trav_cache(target);
7945 out.reserve(out.size() + (uint32_t)relations.size());
7946 for (auto relation: relations)
7947 out.push_back(relation);
7948 return;
7949 }
7950
7951 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
7952 (void)for_each_inherited_term_entity(term, [&](Entity entity) {
7953 out.push_back(entity);
7954 return true;
7955 });
7956 return;
7957 }
7958
7959 if (term.pair() && is_exclusive_dont_fragment_relation(entity_from_id(*this, term.id()))) {
7960 const auto relation = entity_from_id(*this, term.id());
7961 const auto* pStore = exclusive_adjunct_store(relation);
7962 if (pStore == nullptr)
7963 return;
7964
7965 if (is_wildcard(term.gen())) {
7966 out.reserve(out.size() + pStore->srcToTgtCnt);
7967 const auto cnt = (uint32_t)pStore->srcToTgt.size();
7968 GAIA_FOR(cnt) {
7969 const auto target = pStore->srcToTgt[i];
7970 if (target == EntityBad)
7971 continue;
7972 if (!m_recs.entities.has(i))
7973 continue;
7974 out.push_back(EntityContainer::handle(m_recs.entities[i]));
7975 }
7976 return;
7977 }
7978
7979 const auto* pSources = exclusive_adjunct_sources(*pStore, entity_from_id(*this, term.gen()));
7980 if (pSources == nullptr)
7981 return;
7982
7983 out.reserve(out.size() + (uint32_t)pSources->size());
7984 for (auto source: *pSources)
7985 out.push_back(source);
7986 return;
7987 }
7988
7989 if (!term.pair() && is_non_fragmenting_out_of_line_component(term)) {
7990 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
7991 if (it != m_sparseComponentsByComp.end())
7992 it->second.func_collect_entities(it->second.pStore, out);
7993 return;
7994 }
7995
7996 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
7997 if (it == m_entityToArchetypeMap.end())
7998 return;
7999
8000 for (const auto& record: it->second) {
8001 const auto* pArchetype = record.pArchetype;
8002 if (pArchetype->is_req_del())
8003 continue;
8004
8005 for (const auto* pChunk: pArchetype->chunks()) {
8006 const auto entities = pChunk->entity_view();
8007 out.reserve(out.size() + (uint32_t)entities.size());
8008 GAIA_EACH(entities)
8009 out.push_back(entities[i]);
8010 }
8011 }
8012 }
8013
8015 GAIA_NODISCARD bool for_each_direct_term_entity_inter(
8016 Entity term, void* ctx, bool (*func)(void*, Entity), bool allowSemanticIs) const {
8017 if (term == EntityBad)
8018 return true;
8019
8020 if (allowSemanticIs && term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
8021 const auto target = get(term.gen());
8022 if (!valid(target))
8023 return true;
8024
8025 if (!func(ctx, target))
8026 return false;
8027
8028 const auto& relations = as_relations_trav_cache(target);
8029 for (auto relation: relations) {
8030 if (!func(ctx, relation))
8031 return false;
8032 }
8033 return true;
8034 }
8035
8036 if (allowSemanticIs && !is_wildcard(term) && valid(term) && target(term, OnInstantiate) == Inherit) {
8037 return for_each_inherited_term_entity(term, [&](Entity entity) {
8038 return func(ctx, entity);
8039 });
8040 }
8041
8042 if (term.pair() && is_exclusive_dont_fragment_relation(entity_from_id(*this, term.id()))) {
8043 const auto relation = entity_from_id(*this, term.id());
8044 const auto* pStore = exclusive_adjunct_store(relation);
8045 if (pStore == nullptr)
8046 return true;
8047
8048 if (is_wildcard(term.gen())) {
8049 const auto cnt = (uint32_t)pStore->srcToTgt.size();
8050 GAIA_FOR(cnt) {
8051 const auto target = pStore->srcToTgt[i];
8052 if (target == EntityBad)
8053 continue;
8054 if (!m_recs.entities.has(i))
8055 continue;
8056 if (!func(ctx, EntityContainer::handle(m_recs.entities[i])))
8057 return false;
8058 }
8059 return true;
8060 }
8061
8062 const auto* pSources = exclusive_adjunct_sources(*pStore, entity_from_id(*this, term.gen()));
8063 if (pSources == nullptr)
8064 return true;
8065
8066 for (auto source: *pSources) {
8067 if (!func(ctx, source))
8068 return false;
8069 }
8070 return true;
8071 }
8072
8073 if (!term.pair() && is_non_fragmenting_out_of_line_component(term)) {
8074 const auto it = m_sparseComponentsByComp.find(EntityLookupKey(term));
8075 if (it == m_sparseComponentsByComp.end())
8076 return true;
8077 return it->second.func_for_each_entity(it->second.pStore, ctx, func);
8078 }
8079
8080 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(term));
8081 if (it == m_entityToArchetypeMap.end())
8082 return true;
8083
8084 for (const auto& record: it->second) {
8085 const auto* pArchetype = record.pArchetype;
8086 if (pArchetype->is_req_del())
8087 continue;
8088
8089 for (const auto* pChunk: pArchetype->chunks()) {
8090 const auto entities = pChunk->entity_view();
8091 GAIA_EACH(entities) {
8092 if (!func(ctx, entities[i]))
8093 return false;
8094 }
8095 }
8096 }
8097
8098 return true;
8099 }
8100
8101 public:
8105 GAIA_NODISCARD uint32_t count_direct_term_entities(Entity term) const {
8106 return count_direct_term_entities_inter(term, true);
8107 }
8108
8112 GAIA_NODISCARD uint32_t count_direct_term_entities_direct(Entity term) const {
8113 return count_direct_term_entities_inter(term, false);
8114 }
8115
8120 collect_direct_term_entities_inter(term, out, true);
8121 }
8122
8127 collect_direct_term_entities_inter(term, out, false);
8128 }
8129
8135 GAIA_NODISCARD bool for_each_direct_term_entity(Entity term, void* ctx, bool (*func)(void*, Entity)) const {
8136 return for_each_direct_term_entity_inter(term, ctx, func, true);
8137 }
8138
8144 GAIA_NODISCARD bool
8145 for_each_direct_term_entity_direct(Entity term, void* ctx, bool (*func)(void*, Entity)) const {
8146 return for_each_direct_term_entity_inter(term, ctx, func, false);
8147 }
8148
8154 template <typename Func>
8155 void sources_bfs(Entity relation, Entity rootTarget, Func func) const {
8156 if (!valid(relation) || !valid(rootTarget))
8157 return;
8158
8159 cnt::darray<Entity> queue;
8160 queue.push_back(rootTarget);
8161
8163 visited.insert(EntityLookupKey(rootTarget));
8164
8165 for (uint32_t i = 0; i < queue.size(); ++i) {
8166 const auto currTarget = queue[i];
8167
8168 cnt::darray<Entity> children;
8169 sources(relation, currTarget, [&](Entity source) {
8170 const auto key = EntityLookupKey(source);
8171 const auto ins = visited.insert(key);
8172 if (!ins.second)
8173 return;
8174
8175 children.push_back(source);
8176 });
8177
8178 core::sort(children, [](Entity left, Entity right) {
8179 return left.id() < right.id();
8180 });
8181
8182 for (auto child: children) {
8183 if (!enabled(child))
8184 continue;
8185 func(child);
8186 queue.push_back(child);
8187 }
8188 }
8189 }
8190
8198 template <typename Func>
8199 GAIA_NODISCARD bool sources_bfs_if(Entity relation, Entity rootTarget, Func func) const {
8200 if (!valid(relation) || !valid(rootTarget))
8201 return false;
8202
8203 cnt::darray<Entity> queue;
8204 queue.push_back(rootTarget);
8205
8207 visited.insert(EntityLookupKey(rootTarget));
8208
8209 for (uint32_t i = 0; i < queue.size(); ++i) {
8210 const auto currTarget = queue[i];
8211
8212 cnt::darray<Entity> children;
8213 sources(relation, currTarget, [&](Entity source) {
8214 const auto key = EntityLookupKey(source);
8215 const auto ins = visited.insert(key);
8216 if (!ins.second)
8217 return;
8218
8219 children.push_back(source);
8220 });
8221
8222 core::sort(children, [](Entity left, Entity right) {
8223 return left.id() < right.id();
8224 });
8225
8226 for (auto child: children) {
8227 if (!enabled(child))
8228 continue;
8229 if (func(child))
8230 return true;
8231
8232 queue.push_back(child);
8233 }
8234 }
8235
8236 return false;
8237 }
8238
8242 template <typename Func>
8243 void children(Entity parent, Func func) const {
8244 sources(ChildOf, parent, func);
8245 }
8246
8251 template <typename Func>
8252 void children_if(Entity parent, Func func) const {
8253 sources_if(ChildOf, parent, func);
8254 }
8255
8259 template <typename Func>
8260 void children_bfs(Entity root, Func func) const {
8261 sources_bfs(ChildOf, root, func);
8262 }
8263
8269 template <typename Func>
8270 GAIA_NODISCARD bool children_bfs_if(Entity root, Func func) const {
8271 return sources_bfs_if(ChildOf, root, func);
8272 }
8273
8278 template <typename Func>
8279 void as_targets_trav(Entity relation, Func func) const {
8280 GAIA_ASSERT(valid(relation));
8281 if (!valid(relation))
8282 return;
8283
8284 const auto& targets = as_targets_trav_cache(relation);
8285 for (auto target: targets)
8286 func(target);
8287 }
8288
8294 template <typename Func>
8295 bool as_targets_trav_if(Entity relation, Func func) const {
8296 GAIA_ASSERT(valid(relation));
8297 if (!valid(relation))
8298 return false;
8299
8300 const auto& targets = as_targets_trav_cache(relation);
8301 for (auto target: targets)
8302 if (func(target))
8303 return true;
8304
8305 return false;
8306 }
8307
8308 //----------------------------------------------------------------------
8309
8313 return *m_pCmdBufferST;
8314 }
8315
8319 return *m_pCmdBufferMT;
8320 }
8321
8322 //----------------------------------------------------------------------
8323
8324#if GAIA_SYSTEMS_ENABLED
8325
8327 void systems_init();
8328
8330 void systems_run();
8331
8334 SystemBuilder system();
8335
8336#endif
8337
8338#if GAIA_OBSERVERS_ENABLED
8339
8342 ObserverBuilder observer();
8343
8345 ObserverRegistry& observers() {
8346 return m_observers;
8347 }
8348
8350 const ObserverRegistry& observers() const {
8351 return m_observers;
8352 }
8353
8354#endif
8355
8356 //----------------------------------------------------------------------
8357
8362 void enable(Entity entity, bool enable) {
8363 GAIA_ASSERT(valid(entity));
8364
8365 auto& ec = m_recs.entities[entity.id()];
8366 auto& archetype = *ec.pArchetype;
8367 auto* pChunk = ec.pChunk;
8368 const bool wasEnabled = !ec.data.dis;
8369#if GAIA_ASSERT_ENABLED
8370 verify_enable(*this, archetype, entity);
8371#endif
8372 archetype.enable_entity(ec.pChunk, ec.row, enable, m_recs);
8373
8374 if (wasEnabled != enable) {
8375 pChunk->update_world_version();
8376 pChunk->update_entity_order_version();
8377 update_version(m_enabledHierarchyVersion);
8378 update_version(m_worldVersion);
8379 }
8380 }
8381
8385 GAIA_NODISCARD bool enabled(const EntityContainer& ec) const {
8386 const bool entityStateInContainer = !ec.data.dis;
8387#if GAIA_ASSERT_ENABLED
8388 const bool entityStateInChunk = ec.pChunk->enabled(ec.row);
8389 GAIA_ASSERT(entityStateInChunk == entityStateInContainer);
8390#endif
8391 return entityStateInContainer;
8392 }
8393
8398 GAIA_NODISCARD bool enabled(Entity entity) const {
8399 GAIA_ASSERT(valid(entity));
8400
8401 const auto& ec = m_recs.entities[entity.id()];
8402 return enabled(ec);
8403 }
8404
8407 GAIA_NODISCARD bool enabled_hierarchy(Entity entity, Entity relation) const {
8408 GAIA_ASSERT(valid(entity));
8409 GAIA_ASSERT(valid(relation));
8410 if (!valid(entity) || !valid(relation))
8411 return false;
8412 if (!enabled(entity))
8413 return false;
8414
8415 auto curr = entity;
8416 GAIA_FOR(MAX_TRAV_DEPTH) {
8417 const auto next = target(curr, relation);
8418 if (next == EntityBad || next == curr)
8419 break;
8420 if (!enabled(next))
8421 return false;
8422 curr = next;
8423 }
8424
8425 return true;
8426 }
8427
8428 //----------------------------------------------------------------------
8429
8433 GAIA_NODISCARD Chunk* get_chunk(Entity entity) const {
8434 GAIA_ASSERT(entity.id() < m_recs.entities.size());
8435 const auto& ec = m_recs.entities[entity.id()];
8436 return ec.pChunk;
8437 }
8438
8444 GAIA_NODISCARD Chunk* get_chunk(Entity entity, uint32_t& row) const {
8445 GAIA_ASSERT(entity.id() < m_recs.entities.size());
8446 const auto& ec = m_recs.entities[entity.id()];
8447 row = ec.row;
8448 return ec.pChunk;
8449 }
8450
8453 GAIA_NODISCARD uint32_t size() const {
8454 return m_recs.entities.item_count();
8455 }
8456
8459 uint32_t& outArchetypes, uint32_t& outChunks, uint32_t& outEntitiesTotal, uint32_t& outEntitiesActive) const {
8460 outArchetypes = (uint32_t)m_archetypes.size();
8461 outChunks = 0;
8462 outEntitiesTotal = 0;
8463 outEntitiesActive = 0;
8464
8465 for (const auto* pArchetype: m_archetypes) {
8466 if (pArchetype == nullptr)
8467 continue;
8468 const auto& chunks = pArchetype->chunks();
8469 outChunks += (uint32_t)chunks.size();
8470 for (const auto* pChunk: chunks) {
8471 if (pChunk == nullptr)
8472 continue;
8473 outEntitiesTotal += pChunk->size();
8474 outEntitiesActive += pChunk->size_enabled();
8475 }
8476 }
8477 }
8478
8481 GAIA_NODISCARD uint32_t& world_version() {
8482 return m_worldVersion;
8483 }
8484
8487 GAIA_NODISCARD uint32_t rel_version(Entity relation) const {
8488 const auto it = m_relationVersions.find(EntityLookupKey(relation));
8489 return it != m_relationVersions.end() ? it->second : 0;
8490 }
8491
8495 GAIA_NODISCARD uint32_t enabled_hierarchy_version() const {
8496 return m_enabledHierarchyVersion;
8497 }
8498
8499 friend uint32_t world_rel_version(const World& world, Entity relation);
8500 friend uint32_t world_version(const World& world);
8501 friend uint32_t world_entity_archetype_version(const World& world, Entity entity);
8502
8505 const auto key = EntityLookupKey(entity);
8506 const auto it = m_srcEntityVersions.find(key);
8507 if (it == m_srcEntityVersions.end())
8508 return;
8509
8510 update_version(it->second);
8511 }
8512
8515 m_srcEntityVersions.erase(EntityLookupKey(entity));
8516 }
8517
8522 void set_max_lifespan(Entity entity, uint32_t lifespan = Archetype::MAX_ARCHETYPE_LIFESPAN) {
8523 if (!valid(entity))
8524 return;
8525
8526 auto& ec = fetch(entity);
8527 const auto prevLifespan = ec.pArchetype->max_lifespan();
8528 ec.pArchetype->set_max_lifespan(lifespan);
8529
8530 if (prevLifespan == 0) {
8531 // The archetype used to be immortal but not anymore
8532 try_enqueue_archetype_for_deletion(*ec.pArchetype);
8533 }
8534 }
8535
8536 //----------------------------------------------------------------------
8537
8540 void update() {
8541 systems_run();
8542
8543 // Finish deleting entities
8544 del_finalize();
8545
8546 // Run garbage collector
8547 gc();
8548
8549 util::log_flush();
8550
8551 // Signal the end of the frame
8552 GAIA_PROF_FRAME();
8553 }
8554
8558 void teardown() {
8559 if GAIA_UNLIKELY (m_teardownActive)
8560 return;
8561 m_teardownActive = true;
8562
8563 GAIA_PROF_SCOPE(World::teardown);
8564
8565#if GAIA_SYSTEMS_ENABLED
8566 systems_done();
8567#endif
8568
8569#if GAIA_OBSERVERS_ENABLED
8570 m_observers.teardown();
8571#endif
8572
8573 for (;;) {
8574 const auto prevReqArchetypes = m_reqArchetypesToDel.size();
8575 const auto prevReqEntities = m_reqEntitiesToDel.size();
8576 const auto prevChunks = m_chunksToDel.size();
8577 const auto prevArchetypes = m_archetypesToDel.size();
8578
8579 del_finalize();
8580 gc();
8581
8582 if (m_reqArchetypesToDel.empty() && m_reqEntitiesToDel.empty() && m_chunksToDel.empty() &&
8583 m_archetypesToDel.empty())
8584 break;
8585
8586 const bool madeProgress = m_reqArchetypesToDel.size() != prevReqArchetypes ||
8587 m_reqEntitiesToDel.size() != prevReqEntities ||
8588 m_chunksToDel.size() != prevChunks || m_archetypesToDel.size() != prevArchetypes;
8589 if (!madeProgress)
8590 break;
8591 }
8592
8593 util::log_flush();
8594 }
8595
8597 void cleanup() {
8598 cleanup_inter();
8599
8600 // Reinit
8601 m_pRootArchetype = nullptr;
8602 m_pEntityArchetype = nullptr;
8603 m_pCompArchetype = nullptr;
8604 m_nextArchetypeId = 0;
8605 m_defragLastArchetypeIdx = 0;
8606 m_worldVersion = 0;
8607 m_enabledHierarchyVersion = 0;
8608 init();
8609 }
8610
8613 void defrag_entities_per_tick(uint32_t value) {
8614 m_defragEntitiesPerTick = value;
8615 }
8616
8617 //--------------------------------------------------------------------------------
8618
8620 void diag_archetypes() const {
8621 GAIA_LOG_N("Archetypes:%u", (uint32_t)m_archetypes.size());
8622 for (auto* pArchetype: m_archetypes)
8623 Archetype::diag(*this, *pArchetype);
8624 }
8625
8628 void diag_components() const {
8629 comp_cache().diag();
8630 }
8631
8634 void diag_entities() const {
8635 validate_entities();
8636
8637 GAIA_LOG_N("Deleted entities: %u", (uint32_t)m_recs.entities.get_free_items());
8638 if (m_recs.entities.get_free_items() != 0U) {
8639 GAIA_LOG_N(" --> %u", (uint32_t)m_recs.entities.get_next_free_item());
8640
8641 uint32_t iters = 0;
8642 auto fe = m_recs.entities.next_free(m_recs.entities.get_next_free_item());
8643 while (fe != IdentifierIdBad) {
8644 GAIA_LOG_N(" --> %u", m_recs.entities.next_free(fe));
8645 fe = m_recs.entities.next_free(fe);
8646 ++iters;
8647 if (iters > m_recs.entities.get_free_items())
8648 break;
8649 }
8650
8651 if ((iters == 0U) || iters > m_recs.entities.get_free_items())
8652 GAIA_LOG_E(" Entities recycle list contains inconsistent data!");
8653 }
8654 }
8655
8657 void diag() const {
8658 diag_archetypes();
8659 diag_components();
8660 diag_entities();
8661 }
8662
8663 private:
8665 void cleanup_inter() {
8666 GAIA_PROF_SCOPE(World::cleanup_inter);
8667
8668 // Shutdown bypasses the regular GC path, so clear raw-pointer tracking first.
8669 // Chunk/component dtors that run while archetypes are freed can still drop cached queries,
8670 // but after this point they must not touch stale archetype/chunk reverse indices.
8671 {
8672 m_queryCache.clear_archetype_tracking();
8673 m_reqArchetypesToDel = {};
8674 m_reqEntitiesToDel = {};
8675 m_entitiesToDel = {};
8676 m_chunksToDel = {};
8677 m_archetypesToDel = {};
8678 }
8679
8680 // Clear entities
8681 m_recs.entities = {};
8682 m_recs.pairs = {};
8683
8684 // Clear archetypes
8685 {
8686 // Delete all allocated chunks and their parent archetypes
8687 for (auto* pArchetype: m_archetypes)
8688 Archetype::destroy(pArchetype);
8689
8690 m_entityToAsRelations = {};
8691 m_entityToAsRelationsTravCache = {};
8692 m_entityToAsTargets = {};
8693 m_entityToAsTargetsTravCache = {};
8694 m_targetsTravCache = {};
8695 m_srcBfsTravCache = {};
8696 m_depthOrderCache = {};
8697 m_sourcesAllCache = {};
8698 m_targetsAllCache = {};
8699 m_tgtToRel = {};
8700 m_relToTgt = {};
8701 m_exclusiveAdjunctByRel = {};
8702 m_srcToExclusiveAdjunctRel = {};
8703 for (auto& [compKey, store]: m_sparseComponentsByComp) {
8704 (void)compKey;
8705 store.func_clear_store(store.pStore);
8706 store.func_del_store(store.pStore);
8707 }
8708 m_sparseComponentsByComp = {};
8709 m_relationVersions = {};
8710 m_srcEntityVersions = {};
8711
8712 m_archetypes = {};
8713 m_archetypesById = {};
8714 m_archetypesByHash = {};
8715 }
8716
8717 // Clear caches
8718 {
8719 m_entityToArchetypeMap = {};
8720 m_queryCache.clear();
8721 for (auto* pScratch: m_queryMatchScratchStack)
8722 delete pScratch;
8723 m_queryMatchScratchStack = {};
8724 m_queryMatchScratchDepth = 0;
8725 m_querySerMap = {};
8726 m_nextQuerySerId = 0;
8727 }
8728
8729 // Clear entity aliases
8730 {
8731 for (auto& pair: m_aliasToEntity) {
8732 if (!pair.first.owned())
8733 continue;
8734 // Release any memory allocated for owned names
8735 mem::mem_free((void*)pair.first.str());
8736 }
8737 m_aliasToEntity = {};
8738 }
8739
8740 // Clear entity names
8741 {
8742 for (auto& pair: m_nameToEntity) {
8743 if (!pair.first.owned())
8744 continue;
8745 // Release any memory allocated for owned names
8746 mem::mem_free((void*)pair.first.str());
8747 }
8748 m_nameToEntity = {};
8749 }
8750
8751 // Clear component cache
8752 m_compCache.clear();
8753 }
8754
8755 GAIA_NODISCARD static bool valid(const EntityContainer& ec, [[maybe_unused]] Entity entityExpected) {
8756 if ((ec.flags & EntityContainerFlags::Load) != 0) {
8757 return entityExpected.id() == ec.idx && entityExpected.gen() == ec.data.gen &&
8758 entityExpected.entity() == (bool)ec.data.ent && entityExpected.pair() == (bool)ec.data.pair &&
8759 entityExpected.kind() == (EntityKind)ec.data.kind;
8760 }
8761
8762 if (is_req_del(ec))
8763 return false;
8764
8765 // The entity in the chunk must match the index in the entity container
8766 const auto* pChunk = ec.pChunk;
8767 if (pChunk == nullptr || ec.row >= pChunk->size())
8768 return false;
8769
8770 const auto entityPresent = pChunk->entity_view()[ec.row];
8771 // Public validity checks can legitimately observe a recycled slot with a different generation.
8772 // Treat that as stale instead of aborting.
8773 return entityExpected == entityPresent;
8774 }
8775
8778 GAIA_NODISCARD bool valid_pair(Entity entity) const {
8779 if (entity == EntityBad)
8780 return false;
8781
8782 GAIA_ASSERT(entity.pair());
8783 if (!entity.pair())
8784 return false;
8785
8786 // Ignore wildcards because they can't be attached to entities
8787 if (is_wildcard(entity))
8788 return true;
8789
8790 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
8791 if (it == m_recs.pairs.end())
8792 return false;
8793
8794 const auto& ec = it->second;
8795 return valid(ec, entity);
8796 }
8797
8800 GAIA_NODISCARD bool valid_entity(Entity entity) const {
8801 if (entity == EntityBad)
8802 return false;
8803
8804 GAIA_ASSERT(!entity.pair());
8805 if (entity.pair())
8806 return false;
8807
8808 // Entity ID has to fit inside the entity array
8809 if (entity.id() >= m_recs.entities.size())
8810 return false;
8811
8812 const auto* pEc = m_recs.entities.try_get(entity.id());
8813 if (pEc == nullptr)
8814 return false;
8815
8816 return valid(*pEc, entity);
8817 }
8818
8822 GAIA_NODISCARD bool valid_entity_id(EntityId entityId) const {
8823 if (entityId == EntityBad.id())
8824 return false;
8825
8826 // Entity ID has to fit inside the entity array
8827 if (entityId >= m_recs.entities.size())
8828 return false;
8829
8830 const auto* pEc = m_recs.entities.try_get(entityId);
8831 if (pEc == nullptr)
8832 return false;
8833
8834 const auto& ec = *pEc;
8835 if (ec.data.pair != 0)
8836 return false;
8837
8838 return valid(
8839 ec, Entity(entityId, ec.data.gen, (bool)ec.data.ent, (bool)ec.data.pair, (EntityKind)ec.data.kind));
8840 }
8841
8845 void lock() {
8846 GAIA_ASSERT(m_structuralChangesLocked != (uint32_t)-1);
8847 ++m_structuralChangesLocked;
8848 }
8849
8853 void unlock() {
8854 GAIA_ASSERT(m_structuralChangesLocked > 0);
8855 --m_structuralChangesLocked;
8856 }
8857
8858#if GAIA_SYSTEMS_ENABLED
8859 void systems_done();
8860#endif
8861
8862 public:
8864 GAIA_NODISCARD bool locked() const {
8865 return m_structuralChangesLocked != 0;
8866 }
8867
8870 GAIA_NODISCARD bool tearing_down() const {
8871 return m_teardownActive;
8872 }
8873
8874 private:
8875 static constexpr uint32_t WorldSerializerVersion = 2;
8876 static constexpr uint32_t WorldSerializerJSONVersion = 1;
8877
8878 void save_to(ser::serializer s) const {
8879 GAIA_ASSERT(s.valid());
8880
8881 // Version number, currently unused
8882 s.save((uint32_t)WorldSerializerVersion);
8883
8884 // Store the index of the last core component.
8885 // TODO: As this changes, we will have to modify entity ids accordingly.
8886 const auto lastCoreComponentId = GAIA_ID(LastCoreComponent).id();
8887 s.save(lastCoreComponentId);
8888
8889 // Entities
8890 {
8891 auto saveEntityContainer = [&](const EntityContainer& ec) {
8892 s.save(ec.idx);
8893 s.save(ec.dataRaw);
8894 s.save(ec.row);
8895 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) == 0);
8896 s.save(ec.flags); // ignore Load
8897
8898#if GAIA_USE_SAFE_ENTITY
8899 s.save(ec.refCnt);
8900#else
8901 s.save((uint32_t)0);
8902#endif
8903
8904 uint32_t archetypeIdx = ec.pArchetype->list_idx();
8905 s.save(archetypeIdx);
8906 uint32_t chunkIdx = ec.pChunk->idx();
8907 s.save(chunkIdx);
8908 };
8909
8910 const auto recEntities = (uint32_t)m_recs.entities.size();
8911 const auto newEntities = recEntities - lastCoreComponentId;
8912 s.save(newEntities);
8913 GAIA_FOR2(lastCoreComponentId, recEntities) {
8914 const bool isAlive = m_recs.entities.has(i);
8915 s.save(isAlive);
8916 if (isAlive)
8917 saveEntityContainer(m_recs.entities[i]);
8918 else {
8919 s.save(m_recs.entities.handle(i).val);
8920 s.save(m_recs.entities.next_free(i));
8921 }
8922 }
8923
8924 {
8925 uint32_t pairsCnt = 0;
8926 for (const auto& pair: m_recs.pairs) {
8927 // Skip core pairs
8928 if (pair.first.entity().id() < lastCoreComponentId && pair.first.entity().gen() < lastCoreComponentId)
8929 continue;
8930
8931 ++pairsCnt;
8932 }
8933 s.save(pairsCnt);
8934 }
8935 {
8936 for (const auto& pair: m_recs.pairs) {
8937 // Skip core pairs
8938 if (pair.first.entity().id() < lastCoreComponentId && pair.first.entity().gen() < lastCoreComponentId)
8939 continue;
8940
8941 saveEntityContainer(pair.second);
8942 }
8943 }
8944
8945 s.save(m_recs.entities.m_nextFreeIdx);
8946 s.save(m_recs.entities.m_freeItems);
8947 }
8948
8949 // World
8950 {
8951 s.save((uint32_t)m_archetypes.size());
8952 for (auto* pArchetype: m_archetypes) {
8953 s.save((uint32_t)pArchetype->ids_view().size());
8954 for (auto e: pArchetype->ids_view())
8955 s.save(e);
8956
8957 pArchetype->save(s);
8958 }
8959
8960 s.save(m_worldVersion);
8961 }
8962
8963 // Entity names
8964 {
8965 s.save((uint32_t)m_nameToEntity.size());
8966 for (const auto& pair: m_nameToEntity) {
8967 s.save(pair.second);
8968 const bool isOwnedStr = pair.first.owned();
8969 s.save(isOwnedStr);
8970
8971 // For owner string we copy the entire string into the buffer
8972 if (isOwnedStr) {
8973 const auto* str = pair.first.str();
8974 const uint32_t len = pair.first.len();
8975 s.save(len);
8976 s.save_raw(str, len, ser::serialization_type_id::c8);
8977 }
8978 // Non-owned strings will only store the pointer.
8979 // However, if it is a component, we do not store anything at all because we can reconstruct
8980 // the name from our component cache.
8981 else if (!pair.second.comp()) {
8982 const auto* str = pair.first.str();
8983 const uint32_t len = pair.first.len();
8984 s.save(len);
8985 const auto ptr_val = (uint64_t)str;
8986 s.save_raw(&ptr_val, sizeof(ptr_val), ser::serialization_type_id::u64);
8987 }
8988 }
8989 }
8990
8991 // Entity aliases
8992 {
8993 uint32_t aliasCnt = 0;
8994 GAIA_FOR((uint32_t)m_recs.entities.size()) {
8995 const auto entity = get((EntityId)i);
8996 if (!valid(entity) || entity.pair())
8997 continue;
8998
8999 const auto& ec = m_recs.entities[i];
9000 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
9001 if (compIdx == BadIndex)
9002 continue;
9003
9004 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
9005 if (pDesc->alias != nullptr)
9006 ++aliasCnt;
9007 }
9008
9009 s.save(aliasCnt);
9010 GAIA_FOR((uint32_t)m_recs.entities.size()) {
9011 const auto entity = get((EntityId)i);
9012 if (!valid(entity) || entity.pair())
9013 continue;
9014
9015 const auto& ec = m_recs.entities[i];
9016 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
9017 if (compIdx == BadIndex)
9018 continue;
9019
9020 const auto* pDesc = reinterpret_cast<const EntityDesc*>(ec.pChunk->comp_ptr(compIdx, ec.row));
9021 if (pDesc->alias == nullptr)
9022 continue;
9023
9024 s.save(entity);
9025 s.save(pDesc->alias_len);
9026 s.save_raw(pDesc->alias, pDesc->alias_len, ser::serialization_type_id::c8);
9027 }
9028 }
9029 }
9030
9031 public:
9038 void save() {
9039 auto s = m_serializer;
9040 GAIA_ASSERT(s.valid());
9041
9042 s.reset();
9043 save_to(s);
9044 }
9045
9050 bool save_json(ser::ser_json& writer, ser::JsonSaveFlags flags = ser::JsonSaveFlags::Default) const;
9051
9053 ser::json_str save_json(bool& ok, ser::JsonSaveFlags flags = ser::JsonSaveFlags::Default) const;
9054
9058 bool load_json(const char* json, uint32_t len, ser::JsonDiagnostics& diagnostics);
9059
9064 bool load_json(const char* json, uint32_t len);
9065
9070 bool load_json(ser::json_str_view json, ser::JsonDiagnostics& diagnostics);
9071
9075 bool load_json(ser::json_str_view json);
9076
9083 bool load(ser::serializer inputSerializer = {}) {
9084 auto s = inputSerializer.valid() ? inputSerializer : m_serializer;
9085 GAIA_ASSERT(s.valid());
9086
9087 // Move back to the beginning of the stream
9088 s.seek(0);
9089
9090 // Version number, currently unused
9091 uint32_t version = 0;
9092 s.load(version);
9093 if (version != WorldSerializerVersion) {
9094 GAIA_LOG_E("Unsupported world version %u. Expected %u.", version, WorldSerializerVersion);
9095 return false;
9096 }
9097
9098 // Store the index of the last core component. As they change, we will have to modify entity ids accordingly.
9099 uint32_t lastCoreComponentId = 0;
9100 s.load(lastCoreComponentId);
9101
9102 // Append-only core ids are handled via load-time entity remapping.
9103 // Snapshots from a runtime with a larger core-id boundary are not supported.
9104 const auto currLastCoreComponentId = GAIA_ID(LastCoreComponent).id();
9105 if (lastCoreComponentId > currLastCoreComponentId) {
9106 GAIA_LOG_E(
9107 "Unsupported world core boundary %u. Current runtime supports up to %u.", lastCoreComponentId,
9108 currLastCoreComponentId);
9109 return false;
9110 }
9111 // Install the append-only core-id remap for nested Entity::load() calls.
9112 // This keeps the serializer API unchanged, at the cost of relying on
9113 // scoped thread-local state instead of explicit serializer-local context.
9114 const detail::EntityLoadRemapGuard entityLoadRemapGuard(lastCoreComponentId, currLastCoreComponentId);
9115 auto remapLoadedEntityId = [&](uint32_t id) {
9116 return detail::remap_loaded_entity_id(id, lastCoreComponentId, currLastCoreComponentId);
9117 };
9118
9119 // Entities
9120 {
9121 auto loadEntityContainer = [&](EntityContainer& ec) {
9122 s.load(ec.idx);
9123 s.load(ec.dataRaw);
9124 s.load(ec.row);
9125 s.load(ec.flags);
9126 ec.flags |= EntityContainerFlags::Load;
9127
9128 ec.idx = remapLoadedEntityId(ec.idx);
9129 if (ec.data.pair != 0)
9130 ec.data.gen = remapLoadedEntityId(ec.data.gen);
9131
9132#if GAIA_USE_SAFE_ENTITY
9133 s.load(ec.refCnt);
9134#else
9135 s.load(ec.unused);
9136 // if this value is different from zero, it means we are trying to load data
9137 // that was previously saved with GAIA_USE_SAFE_ENTITY. It's probably not a good idea
9138 // because if your program used reference counting it probably won't work correctly.
9139 GAIA_ASSERT(ec.unused == 0);
9140#endif
9141 // Store the archetype idx inside the pointer. We will decode this once archetypes are created.
9142 uint32_t archetypeIdx = 0;
9143 s.load(archetypeIdx);
9144 ec.pArchetype = (Archetype*)((uintptr_t)archetypeIdx);
9145 // Store the chunk idx inside the pointer. We will decode this once chunks are created.
9146 uint32_t chunkIdx = 0;
9147 s.load(chunkIdx);
9148 ec.pChunk = (Chunk*)((uintptr_t)chunkIdx);
9149 };
9150
9151 uint32_t newEntities = 0;
9152 s.load(newEntities);
9153 GAIA_FOR(newEntities) {
9154 bool isAlive = false;
9155 s.load(isAlive);
9156 if (isAlive) {
9157 EntityContainer ec{};
9158 loadEntityContainer(ec);
9159 m_recs.entities.add_live(GAIA_MOV(ec));
9160 } else {
9161 Identifier id = IdentifierBad;
9162 uint32_t nextFreeIdx = Entity::IdMask;
9163 s.load(id);
9164 s.load(nextFreeIdx);
9165 auto entity = Entity(id);
9166 entity = detail::remap_loaded_entity(entity, lastCoreComponentId, currLastCoreComponentId);
9167 nextFreeIdx = remapLoadedEntityId(nextFreeIdx);
9168 GAIA_ASSERT(entity.id() == remapLoadedEntityId(lastCoreComponentId + i));
9169 m_recs.entities.add_free(entity, nextFreeIdx);
9170 }
9171 }
9172
9173 uint32_t pairsCnt = 0;
9174 s.load(pairsCnt);
9175 GAIA_FOR(pairsCnt) {
9176 EntityContainer ec{};
9177 loadEntityContainer(ec);
9178 Entity pair(ec.idx, ec.data.gen);
9179 m_recs.pairs.emplace(EntityLookupKey(pair), GAIA_MOV(ec));
9180 }
9181
9182 s.load(m_recs.entities.m_nextFreeIdx);
9183 m_recs.entities.m_nextFreeIdx = remapLoadedEntityId(m_recs.entities.m_nextFreeIdx);
9184 s.load(m_recs.entities.m_freeItems);
9185 }
9186
9187 // World
9188 {
9189 uint32_t archetypesSize = 0;
9190 s.load(archetypesSize);
9191 m_archetypes.reserve(archetypesSize);
9192 GAIA_FOR(archetypesSize) {
9193 uint32_t idsSize = 0;
9194 s.load(idsSize);
9195 Entity ids[ChunkHeader::MAX_COMPONENTS];
9196 GAIA_FOR_(idsSize, j) {
9197 s.load(ids[j]);
9198 }
9199
9200 // Calculate the lookup hash
9201 const auto hashLookup = calc_lookup_hash({&ids[0], idsSize}).hash;
9202
9203 auto* pArchetype = find_archetype({hashLookup}, {&ids[0], idsSize});
9204 if (pArchetype == nullptr) {
9205 // Create the archetype
9206 pArchetype = create_archetype({&ids[0], idsSize});
9207 pArchetype->set_hashes({hashLookup});
9208
9209 // No need to do anything with the archetype graph. It will build itself naturally.
9210 // pArchetype->build_graph_edges(pArchetypeRight, entity);
9211
9212 // Register the archetype in the world
9213 reg_archetype(pArchetype);
9214 }
9215
9216 // Load archetype data
9217 pArchetype->load(s);
9218 }
9219
9220 s.load(m_worldVersion);
9221 }
9222
9223 // Update entity records.
9224 // We previously encoded the archetype id into refCnt.
9225 // Now we need to convert it back to the pointer.
9226 {
9227 for (auto& ec: m_recs.entities) {
9228 if ((ec.flags & EntityContainerFlags::Load) == 0)
9229 continue;
9230 ec.flags &= ~EntityContainerFlags::Load; // Clear the load flag
9231
9232 const auto archetypeIdx = (ArchetypeId)((uintptr_t)ec.pArchetype); // Decode the archetype idx
9233 ec.pArchetype = m_archetypes[archetypeIdx];
9234 const uint32_t chunkIdx = (uint32_t)((uintptr_t)ec.pChunk); // Decode the chunk idx
9235 ec.pChunk = ec.pArchetype->chunks()[chunkIdx];
9236 ec.pEntity = &ec.pChunk->entity_view()[ec.row];
9237 }
9238
9239 for (auto& pair: m_recs.pairs) {
9240 auto& ec = pair.second;
9241
9242 // Core pairs remain in-world during load and were not serialized into the stream.
9243 if ((ec.flags & EntityContainerFlags::Load) == 0)
9244 continue;
9245
9246 GAIA_ASSERT((ec.flags & EntityContainerFlags::Load) != 0);
9247 ec.flags &= ~EntityContainerFlags::Load; // Clear the load flag
9248
9249 const auto archetypeIdx = (ArchetypeId)((uintptr_t)ec.pArchetype); // Decode the archetype idx
9250 ec.pArchetype = m_archetypes[archetypeIdx];
9251 const uint32_t chunkIdx = (uint32_t)((uintptr_t)ec.pChunk); // Decode the chunk idx
9252 ec.pChunk = ec.pArchetype->chunks()[chunkIdx];
9253 ec.pEntity = &ec.pChunk->entity_view()[ec.row];
9254 }
9255 }
9256
9257#if GAIA_ASSERT_ENABLED
9258 for (const auto& ec: m_recs.entities) {
9259 GAIA_ASSERT(ec.idx < m_recs.entities.size());
9260 GAIA_ASSERT(m_recs.entities.handle(ec.idx) == EntityContainer::handle(ec));
9261 GAIA_ASSERT(ec.pArchetype != nullptr);
9262 GAIA_ASSERT(ec.pChunk != nullptr);
9263 GAIA_ASSERT(ec.pEntity != nullptr);
9264 }
9265#endif
9266 // Entity names
9267 {
9268 m_nameToEntity = {};
9269 uint32_t cnt = 0;
9270 s.load(cnt);
9271 GAIA_FOR(cnt) {
9272 Entity entity;
9273 s.load(entity);
9274 // entity.data.gen = 0; // Reset generation to zero
9275
9276 const auto& ec = fetch(entity);
9277 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
9278 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
9279 GAIA_ASSERT(core::check_alignment(pDesc));
9280
9281 bool isOwned = false;
9282 s.load(isOwned);
9283 if (!isOwned) {
9284 if (entity.comp()) {
9285 // Make components point back to their component cache record because if we save the world and load
9286 // it back in runtime, EntityDesc would still point to the old pointers to component names.
9287 const auto& ci = comp_cache().get(entity);
9288 pDesc->name = ci.name.str();
9289 // Length should still be the same. Only the pointer has changed.
9290 GAIA_ASSERT(pDesc->name_len == ci.name.len());
9291 m_nameToEntity.try_emplace(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0), entity);
9292 } else {
9293 uint32_t len = 0;
9294 s.load(len);
9295 uint64_t ptr_val = 0;
9296 s.load_raw(&ptr_val, sizeof(ptr_val), ser::serialization_type_id::u64);
9297
9298 // Simply point to whereever the original pointer pointed to
9299 pDesc->name = (const char*)ptr_val;
9300 pDesc->name_len = len;
9301 m_nameToEntity.try_emplace(EntityNameLookupKey(pDesc->name, pDesc->name_len, 0), entity);
9302 }
9303
9304 continue;
9305 }
9306
9307 uint32_t len = 0;
9308 s.load(len);
9309
9310 // Get a pointer to where the string begins and seek to the end of the string
9311 const char* entityStr = (const char*)(s.data() + s.tell());
9312 s.seek(s.tell() + len);
9313
9314 // Make sure EntityDesc does not point anywhere right now.
9315 {
9316 pDesc->name = nullptr;
9317 pDesc->name_len = 0;
9318 }
9319
9320 // Name the entity using an owned string
9321 name(entity, entityStr, len);
9322 }
9323 }
9324
9325 // Entity aliases
9326 {
9327 m_aliasToEntity = {};
9328 for (auto& ec: m_recs.entities) {
9329 const auto entity = EntityContainer::handle(ec);
9330 if (entity.pair())
9331 continue;
9332
9333 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
9334 if (compIdx == BadIndex)
9335 continue;
9336
9337 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
9338 GAIA_ASSERT(core::check_alignment(pDesc));
9339 pDesc->alias = nullptr;
9340 pDesc->alias_len = 0;
9341 }
9342
9343 uint32_t cnt = 0;
9344 s.load(cnt);
9345 GAIA_FOR(cnt) {
9346 Entity entity;
9347 s.load(entity);
9348
9349 const auto& ec = fetch(entity);
9350 const auto compIdx = core::get_index(ec.pChunk->ids_view(), GAIA_ID(EntityDesc));
9351 auto* pDesc = reinterpret_cast<EntityDesc*>(ec.pChunk->comp_ptr_mut(compIdx, ec.row));
9352 GAIA_ASSERT(core::check_alignment(pDesc));
9353
9354 uint32_t len = 0;
9355 s.load(len);
9356
9357 // Get a pointer to where the string begins and seek to the end of the string
9358 const char* aliasStr = (const char*)(s.data() + s.tell());
9359 s.seek(s.tell() + len);
9360
9361 pDesc->alias = nullptr;
9362 pDesc->alias_len = 0;
9363 alias(entity, aliasStr, len);
9364 }
9365 }
9366
9367 return true;
9368 }
9369
9373 template <typename TSerializer>
9374 bool load(TSerializer& inputSerializer) {
9375 return load(ser::make_serializer(inputSerializer));
9376 }
9377
9378 private:
9380 void sort_archetypes() {
9381 struct sort_cond {
9382 bool operator()(const Archetype* a, const Archetype* b) const {
9383 return a->id() < b->id();
9384 }
9385 };
9386
9387 core::sort(m_archetypes, sort_cond{}, [&](uint32_t left, uint32_t right) {
9388 Archetype* tmp = m_archetypes[left];
9389
9390 m_archetypes[right]->list_idx(left);
9391 m_archetypes[left]->list_idx(right);
9392
9393 m_archetypes.data()[left] = (Archetype*)m_archetypes[right];
9394 m_archetypes.data()[right] = tmp;
9395 });
9396 }
9397
9401 void remove_chunk(Archetype& archetype, Chunk& chunk) {
9402 archetype.del(&chunk);
9403 try_enqueue_archetype_for_deletion(archetype);
9404 }
9405
9407 void remove_chunk_from_delete_queue(uint32_t idx) {
9408 GAIA_ASSERT(idx < m_chunksToDel.size());
9409
9410 auto* pChunk = m_chunksToDel[idx].pChunk;
9411 pChunk->clear_delete_queue_index();
9412
9413 const auto lastIdx = (uint32_t)m_chunksToDel.size() - 1;
9414 if (idx != lastIdx) {
9415 auto* pMovedChunk = m_chunksToDel[lastIdx].pChunk;
9416 pMovedChunk->delete_queue_index(idx);
9417 }
9418
9419 core::swap_erase(m_chunksToDel, idx);
9420 }
9421
9426 void remove_entity(Archetype& archetype, Chunk& chunk, uint16_t row) {
9427 archetype.remove_entity(chunk, row, m_recs);
9428 try_enqueue_chunk_for_deletion(archetype, chunk);
9429 }
9430
9432 void del_empty_chunks() {
9433 GAIA_PROF_SCOPE(World::del_empty_chunks);
9434
9435 for (uint32_t i = 0; i < m_chunksToDel.size();) {
9436 auto* pArchetype = m_chunksToDel[i].pArchetype;
9437 auto* pChunk = m_chunksToDel[i].pChunk;
9438
9439 // Revive reclaimed chunks
9440 if (!pChunk->empty()) {
9441 pChunk->revive();
9442 revive_archetype(*pArchetype);
9443 remove_chunk_from_delete_queue(i);
9444 continue;
9445 }
9446
9447 // Skip chunks which still have some lifespan left
9448 if (pChunk->progress_death()) {
9449 ++i;
9450 continue;
9451 }
9452
9453 // Delete unused chunks that are past their lifespan
9454 remove_chunk(*pArchetype, *pChunk);
9455 remove_chunk_from_delete_queue(i);
9456 }
9457 }
9458
9460 void del_empty_archetype(Archetype* pArchetype) {
9461 GAIA_PROF_SCOPE(World::del_empty_archetype);
9462
9463 GAIA_ASSERT(pArchetype != nullptr);
9464 GAIA_ASSERT(pArchetype->empty() || pArchetype->is_req_del());
9465 GAIA_ASSERT(!pArchetype->dying() || pArchetype->is_req_del());
9466
9467 unreg_archetype(pArchetype);
9468 for (auto& ec: m_recs.entities) {
9469 if (ec.pArchetype != pArchetype)
9470 continue;
9471
9472 ec.pArchetype = nullptr;
9473 ec.pChunk = nullptr;
9474 ec.pEntity = nullptr;
9475 }
9476 for (auto& [_, ec]: m_recs.pairs) {
9477 if (ec.pArchetype != pArchetype)
9478 continue;
9479
9480 ec.pArchetype = nullptr;
9481 ec.pChunk = nullptr;
9482 ec.pEntity = nullptr;
9483 }
9484 Archetype::destroy(pArchetype);
9485 }
9486
9488 void del_empty_archetypes() {
9489 GAIA_PROF_SCOPE(World::del_empty_archetypes);
9490
9491 cnt::sarray_ext<Archetype*, 512> tmp;
9492
9493 // Remove all dead archetypes from query caches.
9494 // Because the number of cached queries is way higher than the number of archetypes
9495 // we want to remove, we flip the logic around and iterate over all query caches
9496 // and match against our lists.
9497 // Note, all archetype pointers in the tmp array are invalid at this point and can
9498 // be used only for comparison. They can't be dereferenced.
9499 auto remove_from_queries = [&]() {
9500 if (tmp.empty())
9501 return;
9502
9503 for (auto* pArchetype: tmp) {
9504 m_queryCache.remove_archetype_from_queries(pArchetype);
9505 del_empty_archetype(pArchetype);
9506 }
9507 tmp.clear();
9508 };
9509
9510 for (uint32_t i = 0; i < m_archetypesToDel.size();) {
9511 auto* pArchetype = m_archetypesToDel[i];
9512
9513 // Skip reclaimed archetypes or archetypes that became immortal
9514 if (!pArchetype->empty() || pArchetype->max_lifespan() == 0) {
9515 revive_archetype(*pArchetype);
9516 core::swap_erase(m_archetypesToDel, i);
9517 continue;
9518 }
9519
9520 // Skip archetypes which still have some lifespan left unless
9521 // they are force-deleted.
9522 if (!pArchetype->is_req_del() && pArchetype->progress_death()) {
9523 ++i;
9524 continue;
9525 }
9526
9527 tmp.push_back(pArchetype);
9528
9529 // Remove the unused archetypes
9530 core::swap_erase(m_archetypesToDel, i);
9531
9532 // Clear what we have once the capacity is reached
9533 if (tmp.size() == tmp.max_size())
9534 remove_from_queries();
9535 }
9536
9537 remove_from_queries();
9538 }
9539
9540 void revive_archetype(Archetype& archetype) {
9541 archetype.revive();
9542 m_reqArchetypesToDel.erase(ArchetypeLookupKey(archetype.lookup_hash(), &archetype));
9543 }
9544
9545 void try_enqueue_chunk_for_deletion(Archetype& archetype, Chunk& chunk) {
9546 if (chunk.dying() || !chunk.empty())
9547 return;
9548
9549 // When the chunk is emptied we want it to be removed. We can't do it
9550 // rowB away and need to wait for world::gc() to be called.
9551 //
9552 // However, we need to prevent the following:
9553 // 1) chunk is emptied, add it to some removal list
9554 // 2) chunk is reclaimed
9555 // 3) chunk is emptied, add it to some removal list again
9556 //
9557 // Therefore, we have a flag telling us the chunk is already waiting to
9558 // be removed. The chunk might be reclaimed before garbage collection happens
9559 // but it simply ignores such requests. This way we always have at most one
9560 // record for removal for any given chunk.
9561 chunk.start_dying();
9562
9563 m_chunksToDel.push_back({&archetype, &chunk});
9564 chunk.delete_queue_index((uint32_t)m_chunksToDel.size() - 1);
9565 }
9566
9567 void try_enqueue_archetype_for_deletion(Archetype& archetype) {
9568 if (!archetype.ready_to_die())
9569 return;
9570
9571 // When the chunk is emptied we want it to be removed. We can't do it
9572 // rowB away and need to wait for world::gc() to be called.
9573 //
9574 // However, we need to prevent the following:
9575 // 1) archetype is emptied, add it to some removal list
9576 // 2) archetype is reclaimed
9577 // 3) archetype is emptied, add it to some removal list again
9578 //
9579 // Therefore, we have a flag telling us the chunk is already waiting to
9580 // be removed. The archetype might be reclaimed before garbage collection happens
9581 // but it simply ignores such requests. This way we always have at most one
9582 // record for removal for any given chunk.
9583 archetype.start_dying();
9584
9585 m_archetypesToDel.push_back(&archetype);
9586 }
9587
9590 void defrag_chunks(uint32_t maxEntities) {
9591 GAIA_PROF_SCOPE(World::defrag_chunks);
9592
9593 const auto maxIters = m_archetypes.size();
9594 // There has to be at least the root archetype present
9595 GAIA_ASSERT(maxIters > 0);
9596
9597 GAIA_FOR(maxIters) {
9598 const auto idx = (m_defragLastArchetypeIdx + 1) % maxIters;
9599 auto* pArchetype = m_archetypes[idx];
9600 defrag_archetype(*pArchetype, maxEntities);
9601 if (maxEntities == 0)
9602 return;
9603
9604 m_defragLastArchetypeIdx = idx;
9605 }
9606 }
9607
9612 void defrag_archetype(Archetype& archetype, uint32_t& maxEntities) {
9613 // Assuming the following chunk layout:
9614 // Chunk_1: 10/10
9615 // Chunk_2: 1/10
9616 // Chunk_3: 7/10
9617 // Chunk_4: 10/10
9618 // Chunk_5: 9/10
9619 // After full defragmentation we end up with:
9620 // Chunk_1: 10/10
9621 // Chunk_2: 10/10 (7 entities from Chunk_3 + 2 entities from Chunk_5)
9622 // Chunk_3: 0/10 (empty, ready for removal)
9623 // Chunk_4: 10/10
9624 // Chunk_5: 7/10
9625 // TODO: Implement mask of semi-full chunks so we can pick one easily when searching
9626 // for a chunk to fill with a new entity and when defragmenting.
9627 // NOTE 1:
9628 // Even though entity movement might be present during defragmentation, we do
9629 // not update the world version here because no real structural changes happen.
9630 // All entities and components remain intact, they just move to a different place.
9631 // NOTE 2:
9632 // Entities belonging to chunks with uni components are locked to their chunk.
9633 // Therefore, we won't defragment them unless their uni components contain matching
9634 // values.
9635
9636 if (maxEntities == 0)
9637 return;
9638
9639 const auto& chunks = archetype.chunks();
9640 if (chunks.size() < 2)
9641 return;
9642
9643 uint32_t front = 0;
9644 uint32_t back = chunks.size() - 1;
9645
9646 auto* pDstChunk = chunks[front];
9647 auto* pSrcChunk = chunks[back];
9648
9649 // Find the first semi-full chunk in the front
9650 while (front < back && (pDstChunk->full() || !pDstChunk->is_semi()))
9651 pDstChunk = chunks[++front];
9652 // Find the last semi-full chunk in the back
9653 while (front < back && (pSrcChunk->empty() || !pSrcChunk->is_semi()))
9654 pSrcChunk = chunks[--back];
9655
9656 const auto& props = archetype.props();
9657 const bool hasUniEnts =
9658 props.cntEntities > 0 && archetype.ids_view()[props.cntEntities - 1].kind() == EntityKind::EK_Uni;
9659
9660 // Find the first semi-empty chunk in the back
9661 while (front < back) {
9662 pDstChunk = chunks[front];
9663 pSrcChunk = chunks[back];
9664
9665 const uint32_t entitiesInSrcChunk = pSrcChunk->size();
9666 const uint32_t spaceInDstChunk = pDstChunk->capacity() - pDstChunk->size();
9667 const uint32_t entitiesToMoveSrc = core::get_min(entitiesInSrcChunk, maxEntities);
9668 const uint32_t entitiesToMove = core::get_min(entitiesToMoveSrc, spaceInDstChunk);
9669
9670 // Make sure uni components have matching values
9671 if (hasUniEnts) {
9672 auto rec = pSrcChunk->comp_rec_view();
9673 bool res = true;
9674 GAIA_FOR2(props.genEntities, props.cntEntities) {
9675 const auto* pSrcVal = (const void*)pSrcChunk->comp_ptr(i, 0);
9676 const auto* pDstVal = (const void*)pDstChunk->comp_ptr(i, 0);
9677 if (rec[i].pItem->cmp(pSrcVal, pDstVal)) {
9678 res = false;
9679 break;
9680 }
9681 }
9682
9683 // When there is not a match we move to the next chunk
9684 if (!res) {
9685 pDstChunk = chunks[++front];
9686 goto next_iteration;
9687 }
9688 }
9689
9690 GAIA_FOR(entitiesToMove) {
9691 const auto lastSrcEntityIdx = entitiesInSrcChunk - i - 1;
9692 const auto entity = pSrcChunk->entity_view()[lastSrcEntityIdx];
9693
9694 auto& ec = m_recs[entity];
9695
9696 const auto srcRow = ec.row;
9697 const auto dstRow = pDstChunk->add_entity(entity);
9698 const bool wasEnabled = !ec.data.dis;
9699
9700 // Make sure the old entity becomes enabled now
9701 archetype.enable_entity(pSrcChunk, srcRow, true, m_recs);
9702 // We go back-to-front in the chunk so enabling the entity is not expected to change its row
9703 GAIA_ASSERT(srcRow == ec.row);
9704
9705 // Move data from the old chunk to the new one
9706 pDstChunk->move_entity_data(entity, dstRow, m_recs);
9707
9708 // Remove the entity record from the old chunk.
9709 // Normally we'd call remove_entity but we don't want to trigger world
9710 // version updated all the time. It's enough to do it just once at the
9711 // end of defragmentation.
9712 // remove_entity(archetype, *pSrcChunk, srcRow);
9713 archetype.remove_entity_raw(*pSrcChunk, srcRow, m_recs);
9714 try_enqueue_chunk_for_deletion(archetype, *pSrcChunk);
9715
9716 // Bring the entity container record up-to-date
9717 ec.pChunk = pDstChunk;
9718 ec.row = (uint16_t)dstRow;
9719 ec.pEntity = &pDstChunk->entity_view()[dstRow];
9720
9721 // Transfer the original enabled state to the new chunk
9722 archetype.enable_entity(pDstChunk, dstRow, wasEnabled, m_recs);
9723 }
9724
9725 // Update world versions
9726 if (entitiesToMove > 0) {
9727 pSrcChunk->update_world_version();
9728 pDstChunk->update_world_version();
9729 pSrcChunk->update_entity_order_version();
9730 pDstChunk->update_entity_order_version();
9731 update_version(m_worldVersion);
9732 }
9733
9734 maxEntities -= entitiesToMove;
9735 if (maxEntities == 0)
9736 return;
9737
9738 // The source is empty, find another semi-empty source
9739 if (pSrcChunk->empty()) {
9740 while (front < back) {
9741 if (chunks[--back]->is_semi())
9742 break;
9743 }
9744 }
9745
9746 next_iteration:
9747 // The destination chunk is full, we need to move to the next one.
9748 // The idea is to fill the destination as much as possible.
9749 while (front < back && pDstChunk->full())
9750 pDstChunk = chunks[++front];
9751 }
9752 }
9753
9758 GAIA_NODISCARD Archetype* find_archetype(Archetype::LookupHash hashLookup, EntitySpan ids) {
9759 auto tmpArchetype = ArchetypeLookupChecker(ids);
9760 ArchetypeLookupKey key(hashLookup, &tmpArchetype);
9761
9762 // Search for the archetype in the map
9763 const auto it = m_archetypesByHash.find(key);
9764 if (it == m_archetypesByHash.end())
9765 return nullptr;
9766
9767 auto* pArchetype = it->second;
9768 return pArchetype;
9769 }
9770
9771 GAIA_NODISCARD static auto
9772 find_component_index_record(ComponentIndexEntryArray& records, const Archetype* pArchetype) {
9773 return core::get_index_if(records, [&](const auto& record) {
9774 return record.matches(pArchetype);
9775 });
9776 }
9777
9778 GAIA_NODISCARD static auto
9779 find_component_index_record(const ComponentIndexEntryArray& records, const Archetype* pArchetype) {
9780 return core::get_index_if(records, [&](const auto& record) {
9781 return record.matches(pArchetype);
9782 });
9783 }
9784
9787 void add_entity_archetype_pair(
9788 Entity entity, Archetype* pArchetype, uint16_t compIdx = ComponentIndexBad, uint16_t matchCount = 1) {
9789 GAIA_ASSERT(pArchetype != nullptr);
9790 GAIA_ASSERT(matchCount > 0);
9791
9792 EntityLookupKey entityKey(entity);
9793 const auto it = m_entityToArchetypeMap.find(entityKey);
9794 if (it == m_entityToArchetypeMap.end()) {
9795 ComponentIndexEntryArray records;
9796 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
9797 m_entityToArchetypeMap.try_emplace(entityKey, GAIA_MOV(records));
9798 return;
9799 }
9800
9801 auto& records = it->second;
9802 const auto idx = find_component_index_record(records, pArchetype);
9803 if (idx == BadIndex) {
9804 records.push_back(ComponentIndexEntry{pArchetype, compIdx, matchCount});
9805 return;
9806 }
9807
9808 auto& record = records[idx];
9809 record.matchCount = (uint16_t)(record.matchCount + matchCount);
9810 if (compIdx != ComponentIndexBad)
9811 record.compIdx = compIdx;
9812 }
9813
9814 void add_pair_archetype_query_pairs(Entity pair, Archetype* pArchetype, uint16_t matchCount = 1) {
9815 GAIA_ASSERT(pair.pair());
9816 GAIA_ASSERT(pArchetype != nullptr);
9817 GAIA_ASSERT(matchCount > 0);
9818
9819 const auto first = get(pair.id());
9820 const auto second = get(pair.gen());
9821
9822 add_entity_archetype_pair(Pair(All, second), pArchetype, ComponentIndexBad, matchCount);
9823 add_entity_archetype_pair(Pair(first, All), pArchetype, ComponentIndexBad, matchCount);
9824 add_entity_archetype_pair(Pair(All, All), pArchetype, ComponentIndexBad, matchCount);
9825 }
9826
9830 void del_entity_query_pair(Pair pair, Entity entityToRemove) {
9831 auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
9832 if (it == m_entityToArchetypeMap.end())
9833 return;
9834 auto& records = it->second;
9835
9836 // Remove any reference to the found archetype from the array.
9837 // We don't know the archetype so we remove/decrement any archetype record that contains our entity.
9838 for (uint32_t i = records.size() - 1; i != (uint32_t)-1; --i) {
9839 auto& record = records[i];
9840 const auto* pArchetype = record.pArchetype;
9841 if (!pArchetype->has(entityToRemove))
9842 continue;
9843
9844 if ((!is_wildcard(pair.first()) && !is_wildcard(pair.second())) || record.matchCount <= 1)
9845 core::swap_erase_unsafe(records, i);
9846 else
9847 --record.matchCount;
9848 }
9849
9850 if (records.empty())
9851 m_entityToArchetypeMap.erase(it);
9852 }
9853
9856 void del_entity_query_pair(Pair pair, Archetype* pArchetypeToRemove) {
9857 GAIA_ASSERT(pArchetypeToRemove != nullptr);
9858
9859 auto it = m_entityToArchetypeMap.find(EntityLookupKey(pair));
9860 if (it == m_entityToArchetypeMap.end())
9861 return;
9862
9863 auto& records = it->second;
9864 const auto idx = find_component_index_record(records, pArchetypeToRemove);
9865 if (idx != BadIndex)
9866 core::swap_erase_unsafe(records, idx);
9867
9868 if (records.empty())
9869 m_entityToArchetypeMap.erase(it);
9870 }
9871
9872 void del_pair_archetype_query_pairs(Entity pair, Archetype* pArchetypeToRemove) {
9873 GAIA_ASSERT(pair.pair());
9874 GAIA_ASSERT(pArchetypeToRemove != nullptr);
9875
9876 GAIA_ASSERT(pair.id() < m_recs.entities.size());
9877 GAIA_ASSERT(pair.gen() < m_recs.entities.size());
9878 const auto first = m_recs.entities.handle(pair.id());
9879 const auto second = m_recs.entities.handle(pair.gen());
9880
9881 del_entity_query_pair(Pair(All, second), pArchetypeToRemove);
9882 del_entity_query_pair(Pair(first, All), pArchetypeToRemove);
9883 del_entity_query_pair(Pair(All, All), pArchetypeToRemove);
9884 }
9885
9886 void del_pair_archetype_query_pairs(Entity pair, Entity entityToRemove) {
9887 GAIA_ASSERT(pair.pair());
9888
9889 GAIA_ASSERT(pair.id() < m_recs.entities.size());
9890 GAIA_ASSERT(pair.gen() < m_recs.entities.size());
9891 const auto first = m_recs.entities.handle(pair.id());
9892 const auto second = m_recs.entities.handle(pair.gen());
9893
9894 del_entity_query_pair(Pair(All, second), entityToRemove);
9895 del_entity_query_pair(Pair(first, All), entityToRemove);
9896 del_entity_query_pair(Pair(All, All), entityToRemove);
9897 }
9898
9901 void del_entity_archetype_pair(Entity entity, Archetype* pArchetypeToRemove) {
9902 GAIA_ASSERT(entity != Pair(All, All));
9903 GAIA_ASSERT(pArchetypeToRemove != nullptr);
9904
9905 auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
9906 if (it == m_entityToArchetypeMap.end())
9907 return;
9908
9909 auto& records = it->second;
9910 const auto idx = find_component_index_record(records, pArchetypeToRemove);
9911 if (idx != BadIndex)
9912 core::swap_erase_unsafe(records, idx);
9913
9914 if (records.empty())
9915 m_entityToArchetypeMap.erase(it);
9916 }
9917
9919 void del_archetype_entity_pairs(Archetype* pArchetype) {
9920 GAIA_ASSERT(pArchetype != nullptr);
9921
9922 for (const auto entity: pArchetype->ids_view()) {
9923 del_entity_archetype_pair(entity, pArchetype);
9924
9925 if (!entity.pair())
9926 continue;
9927
9928 // Archetype unregistration can run while the pair's relation or target entity is already
9929 // invalid. Rebuild wildcard pair lookup keys from the stored entity records instead of
9930 // calling get(), which asserts on invalidated ids.
9931 GAIA_ASSERT(entity.id() < m_recs.entities.size());
9932 GAIA_ASSERT(entity.gen() < m_recs.entities.size());
9933 del_pair_archetype_query_pairs(entity, pArchetype);
9934 }
9935 }
9936
9939 void del_entity_archetype_pairs(Entity entity, Archetype* pArchetype) {
9940 GAIA_ASSERT(entity != Pair(All, All));
9941
9942 m_entityToArchetypeMap.erase(EntityLookupKey(entity));
9943
9944 if (entity.pair()) {
9945 if (pArchetype != nullptr) {
9946 del_pair_archetype_query_pairs(entity, pArchetype);
9947 } else {
9948 del_pair_archetype_query_pairs(entity, entity);
9949 }
9950 }
9951 }
9952
9956 GAIA_NODISCARD Archetype* create_archetype(EntitySpan entities) {
9957 GAIA_ASSERT(m_nextArchetypeId < (decltype(m_nextArchetypeId))-1);
9958 auto* pArchetype = Archetype::create(*this, m_nextArchetypeId++, m_worldVersion, entities);
9959
9960 const auto entityCnt = (uint32_t)entities.size();
9961 GAIA_FOR(entityCnt) {
9962 auto entity = entities[i];
9963 add_entity_archetype_pair(entity, pArchetype, (uint16_t)i);
9964
9965#if GAIA_OBSERVERS_ENABLED
9966 auto& ec = fetch(entity);
9967 if ((ec.flags & EntityContainerFlags::IsObserved) != 0 || m_observers.has_observers(entity)) {
9968 ec.flags |= EntityContainerFlags::IsObserved;
9969 pArchetype->observed_terms_inc();
9970 }
9971#endif
9972
9973 // If the entity is a pair, make sure to create special wildcard records for it
9974 // as well so wildcard queries can find the archetype.
9975 if (entity.pair()) {
9976 add_pair_archetype_query_pairs(entity, pArchetype);
9977 }
9978 }
9979
9980 return pArchetype;
9981 }
9982
9985 void reg_archetype(Archetype* pArchetype) {
9986 GAIA_ASSERT(pArchetype != nullptr);
9987
9988 // // Make sure hashes were set already
9989 // GAIA_ASSERT(
9990 // (m_archetypesById.empty() || pArchetype == m_pRootArchetype) || (pArchetype->lookup_hash().hash != 0));
9991
9992 // Make sure the archetype is not registered yet
9993 GAIA_ASSERT(pArchetype->list_idx() == BadIndex);
9994
9995 // Register the archetype
9996 [[maybe_unused]] const auto it0 =
9997 m_archetypesById.emplace(ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash()), pArchetype);
9998 [[maybe_unused]] const auto it1 =
9999 m_archetypesByHash.emplace(ArchetypeLookupKey(pArchetype->lookup_hash(), pArchetype), pArchetype);
10000
10001 GAIA_ASSERT(it0.second);
10002 GAIA_ASSERT(it1.second);
10003
10004 pArchetype->list_idx(m_archetypes.size());
10005 m_archetypes.emplace_back(pArchetype);
10006
10007 m_queryCache.register_archetype_with_queries(pArchetype);
10008 }
10009
10012 void unreg_archetype(Archetype* pArchetype) {
10013 GAIA_ASSERT(pArchetype != nullptr);
10014
10015 // Make sure hashes were set already
10016 GAIA_ASSERT(
10017 (m_archetypesById.empty() || pArchetype == m_pRootArchetype) || (pArchetype->lookup_hash().hash != 0));
10018
10019 // Make sure the archetype was registered already
10020 GAIA_ASSERT(pArchetype->list_idx() != BadIndex);
10021
10022 // Query rematching uses the entity -> archetype lookup map as an input. Remove this
10023 // archetype from all of its lookup buckets before destroying it so dead archetype
10024 // pointers cannot be reintroduced into cached query state during the next rematch.
10025 del_archetype_entity_pairs(pArchetype);
10026
10027 // Break graph connections
10028 {
10029 auto& edgeLefts = pArchetype->left_edges();
10030 for (auto& itLeft: edgeLefts)
10031 remove_edge_from_archetype(pArchetype, itLeft.second, itLeft.first.entity());
10032 }
10033
10034 auto tmpArchetype = ArchetypeLookupChecker(pArchetype->ids_view());
10035 [[maybe_unused]] const auto res0 =
10036 m_archetypesById.erase(ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash()));
10037 [[maybe_unused]] const auto res1 =
10038 m_archetypesByHash.erase(ArchetypeLookupKey(pArchetype->lookup_hash(), &tmpArchetype));
10039 GAIA_ASSERT(res0 != 0);
10040 GAIA_ASSERT(res1 != 0);
10041
10042 const auto idx = pArchetype->list_idx();
10043 GAIA_ASSERT(idx == core::get_index(m_archetypes, pArchetype));
10044 core::swap_erase(m_archetypes, idx);
10045 if (!m_archetypes.empty() && idx != m_archetypes.size())
10046 m_archetypes[idx]->list_idx(idx);
10047 }
10048
10049#if GAIA_ASSERT_ENABLED
10050 static void print_archetype_entities(const World& world, const Archetype& archetype, Entity entity, bool adding) {
10051 auto ids = archetype.ids_view();
10052
10053 GAIA_LOG_W("Currently present:");
10054 GAIA_EACH(ids) {
10055 const auto name = entity_name(world, ids[i]);
10056 GAIA_LOG_W(
10057 "> [%u] %.*s [%s]", i, (int)name.size(), name.empty() ? "" : name.data(),
10058 EntityKindString[(uint32_t)ids[i].kind()]);
10059 }
10060
10061 GAIA_LOG_W("Trying to %s:", adding ? "add" : "del");
10062 const auto name = entity_name(world, entity);
10063 GAIA_LOG_W(
10064 "> %.*s [%s]", (int)name.size(), name.empty() ? "" : name.data(),
10065 EntityKindString[(uint32_t)entity.kind()]);
10066 }
10067
10068 static void verify_add(const World& world, Archetype& archetype, Entity entity, Entity addEntity) {
10069 // Make sure the world is not locked
10070 if (world.locked()) {
10071 GAIA_ASSERT2(false, "Trying to add an entity while the world is locked");
10072 GAIA_LOG_W("Trying to add an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
10073 print_archetype_entities(world, archetype, entity, false);
10074 return;
10075 }
10076
10077 // Makes sure no wildcard entities are added
10078 if (is_wildcard(addEntity)) {
10079 GAIA_ASSERT2(false, "Adding wildcard pairs is not supported");
10080 print_archetype_entities(world, archetype, addEntity, true);
10081 return;
10082 }
10083
10084 // Make sure not to add too many entities/components
10085 auto ids = archetype.ids_view();
10086 if GAIA_UNLIKELY (ids.size() + 1 >= ChunkHeader::MAX_COMPONENTS) {
10087 GAIA_ASSERT2(false, "Trying to add too many entities to entity!");
10088 GAIA_LOG_W("Trying to add an entity to entity [%u:%u] but there's no space left!", entity.id(), entity.gen());
10089 print_archetype_entities(world, archetype, addEntity, true);
10090 return;
10091 }
10092 }
10093
10094 static void verify_del(const World& world, Archetype& archetype, Entity entity, Entity func_del) {
10095 // Make sure the world is not locked
10096 if (world.locked()) {
10097 GAIA_ASSERT2(false, "Trying to delete an entity while the world is locked");
10098 GAIA_LOG_W("Trying to delete an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
10099 print_archetype_entities(world, archetype, entity, false);
10100 return;
10101 }
10102
10103 // Make sure the entity is present on the archetype
10104 if GAIA_UNLIKELY (!archetype.has(func_del)) {
10105 GAIA_ASSERT2(false, "Trying to remove an entity which wasn't added");
10106 GAIA_LOG_W("Trying to del an entity from entity [%u:%u] but it was never added", entity.id(), entity.gen());
10107 print_archetype_entities(world, archetype, func_del, false);
10108 return;
10109 }
10110 }
10111
10112 static void verify_enable(const World& world, Archetype& archetype, Entity entity) {
10113 if (world.locked()) {
10114 GAIA_ASSERT2(false, "Trying to enable/disable an entity while the world is locked");
10115 GAIA_LOG_W("Trying to enable/disable an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
10116 print_archetype_entities(world, archetype, entity, false);
10117 }
10118 }
10119
10120 static void verify_move(const World& world, Archetype& archetype, Entity entity) {
10121 if (world.locked()) {
10122 GAIA_ASSERT2(false, "Trying to move an entity while the world is locked");
10123 GAIA_LOG_W("Trying to move an entity [%u:%u] while the world is locked", entity.id(), entity.gen());
10124 print_archetype_entities(world, archetype, entity, false);
10125 }
10126 }
10127#endif
10128
10134 GAIA_NODISCARD Archetype* foc_archetype_add(Archetype* pArchetypeLeft, Entity entity) {
10135 // Check if the component is found when following the "add" edges
10136 bool edgeNeedsRebuild = false;
10137 {
10138 const auto edge = pArchetypeLeft->find_edge_right(entity);
10139 if (edge != ArchetypeIdHashPairBad) {
10140 auto it = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
10141 if (it != m_archetypesById.end() && it->second != nullptr)
10142 return it->second;
10143
10144 // Drop stale local cache edge and rebuild it below.
10145 pArchetypeLeft->del_graph_edge_right_local(entity);
10146 edgeNeedsRebuild = true;
10147 }
10148 }
10149
10150 // Prepare a joint array of components of old + the newly added component
10151 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
10152 {
10153 auto entsOld = pArchetypeLeft->ids_view();
10154 const auto entsOldCnt = entsOld.size();
10155 entsNew.resize((uint32_t)entsOld.size() + 1);
10156 GAIA_FOR(entsOldCnt) entsNew[i] = entsOld[i];
10157 entsNew[(uint32_t)entsOld.size()] = entity;
10158 }
10159
10160 // Make sure to sort the components so we receive the same hash no matter the order in which components
10161 // are provided Bubble sort is okay. We're dealing with at most ChunkHeader::MAX_COMPONENTS items.
10162 sort(entsNew, SortComponentCond{});
10163
10164 // Once sorted we can calculate the hashes
10165 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
10166 auto* pArchetypeRight = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
10167 if (pArchetypeRight == nullptr) {
10168 pArchetypeRight = create_archetype({entsNew.data(), entsNew.size()});
10169 pArchetypeRight->set_hashes({hashLookup});
10170 reg_archetype(pArchetypeRight);
10171 edgeNeedsRebuild = true;
10172 }
10173
10174 if (edgeNeedsRebuild)
10175 pArchetypeLeft->build_graph_edges(pArchetypeRight, entity);
10176
10177 return pArchetypeRight;
10178 }
10179
10182 GAIA_NODISCARD Archetype* foc_archetype_add_no_graph(Archetype* pArchetypeLeft, Entity entity) {
10183 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
10184 {
10185 auto entsOld = pArchetypeLeft->ids_view();
10186 const auto entsOldCnt = entsOld.size();
10187 entsNew.resize((uint32_t)entsOld.size() + 1);
10188 GAIA_FOR(entsOldCnt) entsNew[i] = entsOld[i];
10189 entsNew[(uint32_t)entsOld.size()] = entity;
10190 }
10191
10192 sort(entsNew, SortComponentCond{});
10193
10194 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
10195 auto* pArchetypeRight = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
10196 if (pArchetypeRight != nullptr)
10197 return pArchetypeRight;
10198
10199 pArchetypeRight = create_archetype({entsNew.data(), entsNew.size()});
10200 pArchetypeRight->set_hashes({hashLookup});
10201 reg_archetype(pArchetypeRight);
10202 return pArchetypeRight;
10203 }
10204
10210 GAIA_NODISCARD Archetype* foc_archetype_del(Archetype* pArchetypeRight, Entity entity) {
10211 // Check if the component is found when following the "del" edges
10212 bool edgeNeedsRebuild = false;
10213 {
10214 const auto edge = pArchetypeRight->find_edge_left(entity);
10215 if (edge != ArchetypeIdHashPairBad) {
10216 const auto it = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
10217 if (it != m_archetypesById.end()) {
10218 auto* pArchetypeLeft = it->second;
10219 if (pArchetypeLeft != nullptr)
10220 return pArchetypeLeft;
10221 }
10222
10223 // Drop stale local cache edge and rebuild it below.
10224 pArchetypeRight->del_graph_edge_left_local(entity);
10225 edgeNeedsRebuild = true;
10226 }
10227 }
10228
10229 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
10230 auto entsOld = pArchetypeRight->ids_view();
10231
10232 // Find the intersection
10233 for (const auto e: entsOld) {
10234 if (e == entity)
10235 continue;
10236
10237 entsNew.push_back(e);
10238 }
10239
10240 // Verify there was a change
10241 GAIA_ASSERT(entsNew.size() != entsOld.size());
10242
10243 // Calculate the hashes
10244 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
10245 auto* pArchetype = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
10246 if (pArchetype == nullptr) {
10247 pArchetype = create_archetype({entsNew.data(), entsNew.size()});
10248 pArchetype->set_hashes({hashLookup});
10249 reg_archetype(pArchetype);
10250 edgeNeedsRebuild = true;
10251 }
10252
10253 if (edgeNeedsRebuild)
10254 pArchetype->build_graph_edges(pArchetypeRight, entity);
10255
10256 return pArchetype;
10257 }
10258
10261 GAIA_NODISCARD Archetype* foc_archetype_del_no_graph(Archetype* pArchetypeRight, Entity entity) {
10262 cnt::sarray_ext<Entity, ChunkHeader::MAX_COMPONENTS> entsNew;
10263 auto entsOld = pArchetypeRight->ids_view();
10264
10265 for (const auto e: entsOld) {
10266 if (e == entity)
10267 continue;
10268
10269 entsNew.push_back(e);
10270 }
10271
10272 GAIA_ASSERT(entsNew.size() != entsOld.size());
10273
10274 const auto hashLookup = calc_lookup_hash({entsNew.data(), entsNew.size()}).hash;
10275 auto* pArchetype = find_archetype({hashLookup}, {entsNew.data(), entsNew.size()});
10276 if (pArchetype != nullptr)
10277 return pArchetype;
10278
10279 pArchetype = create_archetype({entsNew.data(), entsNew.size()});
10280 pArchetype->set_hashes({hashLookup});
10281 reg_archetype(pArchetype);
10282 return pArchetype;
10283 }
10284
10287 GAIA_NODISCARD const auto& archetypes() const {
10288 return m_archetypes;
10289 }
10290
10294 GAIA_NODISCARD Archetype& archetype(Entity entity) {
10295 const auto& ec = fetch(entity);
10296 return *ec.pArchetype;
10297 }
10298
10301 void del_name(EntityContainer& ec, Entity entity) {
10302 EntityBuilder(*this, entity, ec).del_name();
10303 }
10304
10307 void del_name(Entity entity) {
10308 EntityBuilder(*this, entity).del_name();
10309 }
10310
10315 void del_entity(Entity entity, bool invalidate) {
10316 if (entity.pair() || entity == EntityBad)
10317 return;
10318
10319 auto& ec = fetch(entity);
10320 del_entity_inter(ec, entity, invalidate);
10321 }
10322
10328 void del_entity(EntityContainer& ec, Entity entity, bool invalidate) {
10329 if (entity.pair() || entity == EntityBad)
10330 return;
10331
10332 del_entity_inter(ec, entity, invalidate);
10333 }
10334
10339 void del_entity_inter(EntityContainer& ec, Entity entity, bool invalidate) {
10340 GAIA_ASSERT(entity.id() > GAIA_ID(LastCoreComponent).id());
10341
10342 // if (!is_req_del(ec))
10343 {
10344 if (m_recs.entities.item_count() == 0)
10345 return;
10346
10347#if GAIA_ASSERT_ENABLED
10348 auto* pChunk = ec.pChunk;
10349 GAIA_ASSERT(pChunk != nullptr);
10350#endif
10351
10352 // Remove the entity from its chunk.
10353 // We call del_name first because remove_entity calls component destructors.
10354 // If the call was made inside invalidate_entity we would access a memory location
10355 // which has already been destructed which is not nice.
10356 del_name(ec, entity);
10357 remove_entity(*ec.pArchetype, *ec.pChunk, ec.row);
10358 remove_src_entity_version(entity);
10359 }
10360
10361 // Invalidate on-demand.
10362 // We delete as a separate step in the delayed deletion.
10363 if (invalidate)
10364 invalidate_entity(entity);
10365 }
10366
10371 void del_entities(Archetype& archetype) {
10372 for (auto* pChunk: archetype.chunks()) {
10373 auto ids = pChunk->entity_view();
10374 for (auto e: ids) {
10375 if (!valid(e))
10376 continue;
10377
10378#if GAIA_ASSERT_ENABLED
10379 const auto& ec = fetch(e);
10380
10381 // We should never end up trying to delete a forbidden-to-delete entity
10382 GAIA_ASSERT((ec.flags & EntityContainerFlags::OnDeleteTarget_Error) == 0);
10383#endif
10384
10385 del_entity(e, true);
10386 }
10387
10388 validate_chunk(pChunk);
10389
10390 // If the chunk was already dying we need to remove it from the delete list
10391 // because we can delete it right away.
10392 if (pChunk->queued_for_deletion())
10393 remove_chunk_from_delete_queue(pChunk->delete_queue_index());
10394
10395 remove_chunk(archetype, *pChunk);
10396 }
10397
10398 validate_entities();
10399 }
10400
10403 void del_inter(Entity entity) {
10404 auto on_delete = [this](Entity entityToDel) {
10405 auto& ec = fetch(entityToDel);
10406 handle_del_entity(ec, entityToDel);
10407 };
10408
10409 if (is_wildcard(entity)) {
10410 const auto rel = get(entity.id());
10411 const auto tgt = get(entity.gen());
10412
10413 // (*,*)
10414 if (rel == All && tgt == All) {
10415 GAIA_ASSERT2(false, "Not supported yet");
10416 }
10417 // (*,X)
10418 else if (rel == All) {
10419 if (const auto* pTargets = relations(tgt)) {
10420 // handle_del might invalidate the targets map so we need to make a copy
10421 // TODO: this is suboptimal at best, needs to be optimized
10422 cnt::darray_ext<Entity, 64> tmp;
10423 for (auto key: *pTargets)
10424 tmp.push_back(key.entity());
10425 for (auto e: tmp)
10426 on_delete(Pair(e, tgt));
10427 }
10428 }
10429 // (X,*)
10430 else if (tgt == All) {
10431 if (const auto* pRelations = targets(rel)) {
10432 // handle_del might invalidate the targets map so we need to make a copy
10433 // TODO: this is suboptimal at best, needs to be optimized
10434 cnt::darray_ext<Entity, 64> tmp;
10435 for (auto key: *pRelations)
10436 tmp.push_back(key.entity());
10437 for (auto e: tmp)
10438 on_delete(Pair(rel, e));
10439 }
10440 }
10441 } else {
10442 on_delete(entity);
10443 }
10444 }
10445
10446 // Force-delete all entities from the requested archetypes along with the archetype itself
10447 void del_finalize_archetypes() {
10448 GAIA_PROF_SCOPE(World::del_finalize_archetypes);
10449
10450 for (auto& key: m_reqArchetypesToDel) {
10451 auto* pArchetype = key.archetype();
10452 if (pArchetype == nullptr)
10453 continue;
10454
10455 del_entities(*pArchetype);
10456
10457 // Now that all entities are deleted, all their chunks are requested to get deleted
10458 // and in turn the archetype itself as well. Therefore, it is added to the archetype
10459 // delete list and picked up by del_empty_archetypes. No need to call deletion from here.
10460 // > del_empty_archetype(pArchetype);
10461 }
10462 m_reqArchetypesToDel.clear();
10463 }
10464
10466 void del_finalize_entities() {
10467 GAIA_PROF_SCOPE(World::del_finalize_entities);
10468
10469 for (auto it = m_reqEntitiesToDel.begin(); it != m_reqEntitiesToDel.end();) {
10470 const auto e = it->entity();
10471
10472 // Entities that form archetypes need to stay until the archetype itself is gone
10473 if (m_entityToArchetypeMap.contains(*it)) {
10474 ++it;
10475 continue;
10476 }
10477
10478 // Requested entities are partially deleted. We only need to invalidate them.
10479 invalidate_entity(e);
10480
10481 it = m_reqEntitiesToDel.erase(it);
10482 }
10483 }
10484
10486 void del_finalize() {
10487 GAIA_PROF_SCOPE(World::del_finalize);
10488
10489 del_finalize_archetypes();
10490 del_finalize_entities();
10491 }
10492
10493 GAIA_NODISCARD bool archetype_cond_match(Archetype& archetype, Pair cond, Entity target) const {
10494 // E.g.:
10495 // target = (All, entity)
10496 // cond = (OnDeleteTarget, delete)
10497 // Delete the entity if it matches the cond
10498 auto ids = archetype.ids_view();
10499
10500 if (target.pair()) {
10501 for (auto e: ids) {
10502 // Find the pair which matches (All, entity)
10503 if (!e.pair())
10504 continue;
10505 if (e.gen() != target.gen())
10506 continue;
10507
10508 const auto& ec = m_recs.entities[e.id()];
10509 const auto entity = ec.pChunk->entity_view()[ec.row];
10510 if (!has(entity, cond))
10511 continue;
10512
10513 return true;
10514 }
10515 } else {
10516 for (auto e: ids) {
10517 if (e.pair())
10518 continue;
10519 if (!has(e, cond))
10520 continue;
10521
10522 return true;
10523 }
10524 }
10525
10526 return false;
10527 }
10528
10532 void move_to_archetype(Archetype& srcArchetype, Archetype& dstArchetype) {
10533 GAIA_ASSERT(&srcArchetype != &dstArchetype);
10534
10535 bool updated = false;
10536
10537 for (auto* pSrcChunk: srcArchetype.chunks()) {
10538 auto srcEnts = pSrcChunk->entity_view();
10539 if (srcEnts.empty())
10540 continue;
10541
10542 // Copy entities back-to-front to avoid unnecessary data movements.
10543 // TODO: Handle disabled entities efficiently.
10544 // If there are disabled entities, we still do data movements if there already
10545 // are enabled entities in the chunk.
10546 // TODO: If the header was of some fixed size, e.g. if we always acted as if we had
10547 // ChunkHeader::MAX_COMPONENTS, certain data movements could be done pretty much instantly.
10548 // E.g. when removing tags or pairs, we would simply replace the chunk pointer
10549 // with a pointer to another one. The same goes for archetypes. Component data
10550 // would not have to move at all internal chunk header pointers would remain unchanged.
10551
10552 uint32_t i = (uint32_t)srcEnts.size();
10553 while (i != 0) {
10554 auto* pDstChunk = dstArchetype.foc_free_chunk();
10555 const uint32_t dstSpaceLeft = pDstChunk->capacity() - pDstChunk->size();
10556 const uint32_t cnt = core::get_min(dstSpaceLeft, i);
10557 for (uint32_t j = 0; j < cnt; ++j) {
10558 auto e = srcEnts[i - j - 1];
10559 move_entity(e, fetch(e), dstArchetype, *pDstChunk);
10560 }
10561
10562 pDstChunk->update_world_version();
10563 pDstChunk->update_entity_order_version();
10564
10565 GAIA_ASSERT(cnt <= i);
10566 i -= cnt;
10567 }
10568
10569 pSrcChunk->update_world_version();
10570 pSrcChunk->update_entity_order_version();
10571 updated = true;
10572 }
10573
10574 if (updated)
10575 update_version(m_worldVersion);
10576 }
10577
10580 GAIA_NODISCARD Archetype* calc_dst_archetype_ent(Archetype* pArchetype, Entity entity) {
10581 GAIA_ASSERT(!is_wildcard(entity));
10582
10583 auto ids = pArchetype->ids_view();
10584 for (auto id: ids) {
10585 if (id != entity)
10586 continue;
10587
10588 return foc_archetype_del(pArchetype, id);
10589 }
10590
10591 return nullptr;
10592 }
10593
10597 GAIA_NODISCARD Archetype* calc_dst_archetype_all_ent(Archetype* pArchetype, Entity entity) {
10598 GAIA_ASSERT(is_wildcard(entity));
10599
10600 Archetype* pDstArchetype = pArchetype;
10601
10602 auto ids = pArchetype->ids_view();
10603 for (auto id: ids) {
10604 if (!id.pair() || id.gen() != entity.gen())
10605 continue;
10606
10607 pDstArchetype = foc_archetype_del(pDstArchetype, id);
10608 }
10609
10610 return pArchetype != pDstArchetype ? pDstArchetype : nullptr;
10611 }
10612
10616 GAIA_NODISCARD Archetype* calc_dst_archetype_ent_all(Archetype* pArchetype, Entity entity) {
10617 GAIA_ASSERT(is_wildcard(entity));
10618
10619 Archetype* pDstArchetype = pArchetype;
10620
10621 auto ids = pArchetype->ids_view();
10622 for (auto id: ids) {
10623 if (!id.pair() || id.id() != entity.id())
10624 continue;
10625
10626 pDstArchetype = foc_archetype_del(pDstArchetype, id);
10627 }
10628
10629 return pArchetype != pDstArchetype ? pDstArchetype : nullptr;
10630 }
10631
10635 GAIA_NODISCARD Archetype* calc_dst_archetype_all_all(Archetype* pArchetype, [[maybe_unused]] Entity entity) {
10636 GAIA_ASSERT(is_wildcard(entity));
10637
10638 Archetype* pDstArchetype = pArchetype;
10639 bool found = false;
10640
10641 auto ids = pArchetype->ids_view();
10642 for (auto id: ids) {
10643 if (!id.pair())
10644 continue;
10645
10646 pDstArchetype = foc_archetype_del(pDstArchetype, id);
10647 found = true;
10648 }
10649
10650 return found ? pDstArchetype : nullptr;
10651 }
10652
10656 GAIA_NODISCARD Archetype* calc_dst_archetype(Archetype* pArchetype, Entity entity) {
10657 if (entity.pair()) {
10658 auto rel = entity.id();
10659 auto tgt = entity.gen();
10660
10661 // Removing a wildcard pair. We need to find all pairs matching it.
10662 if (rel == All.id() || tgt == All.id()) {
10663 // (first, All) means we need to match (first, A), (first, B), ...
10664 if (rel != All.id() && tgt == All.id())
10665 return calc_dst_archetype_ent_all(pArchetype, entity);
10666
10667 // (All, second) means we need to match (A, second), (B, second), ...
10668 if (rel == All.id() && tgt != All.id())
10669 return calc_dst_archetype_all_ent(pArchetype, entity);
10670
10671 // (All, All) means we need to match all relationships
10672 return calc_dst_archetype_all_all(pArchetype, EntityBad);
10673 }
10674 }
10675
10676 // Non-wildcard pair or entity
10677 return calc_dst_archetype_ent(pArchetype, entity);
10678 }
10679
10680 void req_del(Archetype& archetype) {
10681 if (archetype.is_req_del())
10682 return;
10683
10684 archetype.req_del();
10685 m_reqArchetypesToDel.insert(ArchetypeLookupKey(archetype.lookup_hash(), &archetype));
10686 }
10687
10688 void req_del(EntityContainer& ec, Entity entity) {
10689 if (is_req_del(ec))
10690 return;
10691
10692#if GAIA_OBSERVERS_ENABLED
10693 auto delDiffCtx =
10694 m_observers.prepare_diff(*this, ObserverEvent::OnDel, EntitySpan{&entity, 1}, EntitySpan{&entity, 1});
10695#endif
10696 del_entity(ec, entity, false);
10697#if GAIA_OBSERVERS_ENABLED
10698 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
10699#endif
10700
10701 ec.req_del();
10702 m_reqEntitiesToDel.insert(EntityLookupKey(entity));
10703 }
10704
10705 void invalidate_pair_removal_caches(Entity entity) {
10706 if (!entity.pair())
10707 return;
10708
10709 auto invalidate_relation = [this](Entity relation) {
10710 if (relation == EntityBad)
10711 return;
10712 touch_rel_version(relation);
10713 invalidate_queries_for_rel(relation);
10714 };
10715
10716 if (entity.id() != All.id()) {
10717 invalidate_relation(try_get(entity.id()));
10718 } else if (entity.gen() != All.id()) {
10719 const auto target = try_get(entity.gen());
10720 if (target != EntityBad) {
10721 if (const auto* pRelations = relations(target)) {
10722 for (auto relationKey: *pRelations)
10723 invalidate_relation(relationKey.entity());
10724 }
10725 }
10726 } else {
10727 for (const auto& [relationKey, _]: m_relToTgt)
10728 invalidate_relation(relationKey.entity());
10729 }
10730
10731 m_targetsTravCache = {};
10732 m_srcBfsTravCache = {};
10733 m_depthOrderCache = {};
10734 m_sourcesAllCache = {};
10735 m_targetsAllCache = {};
10736 }
10737
10738 void unlink_live_is_relation(Entity source, Entity target) {
10739 const auto sourceKey = EntityLookupKey(source);
10740 const auto targetKey = EntityLookupKey(target);
10741
10742 invalidate_queries_for_entity({Is, target});
10743
10744 if (const auto itTargets = m_entityToAsTargets.find(sourceKey); itTargets != m_entityToAsTargets.end()) {
10745 itTargets->second.erase(targetKey);
10746 if (itTargets->second.empty())
10747 m_entityToAsTargets.erase(itTargets);
10748 }
10749 m_entityToAsTargetsTravCache = {};
10750
10751 if (const auto itRelations = m_entityToAsRelations.find(targetKey);
10752 itRelations != m_entityToAsRelations.end()) {
10753 itRelations->second.erase(sourceKey);
10754 if (itRelations->second.empty())
10755 m_entityToAsRelations.erase(itRelations);
10756 }
10757 m_entityToAsRelationsTravCache = {};
10758 }
10759
10760 void unlink_stale_is_relations_by_target_id(Entity source, EntityId targetId) {
10761 const auto sourceKey = EntityLookupKey(source);
10762 const auto itTargets = m_entityToAsTargets.find(sourceKey);
10763 if (itTargets == m_entityToAsTargets.end())
10764 return;
10765
10766 cnt::darray_ext<EntityLookupKey, 4> removedTargets;
10767 for (auto targetKey: itTargets->second) {
10768 if (targetKey.entity().id() == targetId)
10769 removedTargets.push_back(targetKey);
10770 }
10771
10772 for (auto targetKey: removedTargets) {
10773 invalidate_queries_for_structural_entity(EntityLookupKey(Pair{Is, targetKey.entity()}));
10774 itTargets->second.erase(targetKey);
10775
10776 const auto itRelations = m_entityToAsRelations.find(targetKey);
10777 if (itRelations != m_entityToAsRelations.end()) {
10778 itRelations->second.erase(sourceKey);
10779 if (itRelations->second.empty())
10780 m_entityToAsRelations.erase(itRelations);
10781 }
10782 }
10783
10784 if (itTargets->second.empty())
10785 m_entityToAsTargets.erase(itTargets);
10786
10787 m_entityToAsTargetsTravCache = {};
10788 m_entityToAsRelationsTravCache = {};
10789 }
10790
10791 template <typename Func>
10792 void each_delete_cascade_direct_source(Entity target, Pair cond, Func&& func) {
10793 GAIA_ASSERT(!target.pair());
10794
10795 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
10796 const auto relation = relKey.entity();
10797 if (!has(relation, cond))
10798 continue;
10799
10800 const auto* pSources = exclusive_adjunct_sources(store, target);
10801 if (pSources == nullptr)
10802 continue;
10803
10804 for (auto source: *pSources)
10805 func(source);
10806 }
10807
10808 const auto pairEntity = Pair(All, target);
10809 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(pairEntity));
10810 if (it != m_entityToArchetypeMap.end()) {
10811 for (const auto& record: it->second) {
10812 auto* pArchetype = record.pArchetype;
10813 if (pArchetype == nullptr || pArchetype->is_req_del())
10814 continue;
10815 if (!archetype_cond_match(*pArchetype, cond, pairEntity))
10816 continue;
10817
10818 for (const auto* pChunk: pArchetype->chunks()) {
10819 const auto entities = pChunk->entity_view();
10820 GAIA_EACH(entities)
10821 func(entities[i]);
10822 }
10823 }
10824 }
10825 }
10826
10827 void collect_delete_cascade_direct_sources(Entity target, Pair cond, cnt::darray<Entity>& out) {
10828 GAIA_ASSERT(!target.pair());
10829 const auto visitStamp = next_entity_visit_stamp();
10830 each_delete_cascade_direct_source(target, cond, [&](Entity source) {
10831 if (!valid(source))
10832 return;
10833 if (!try_mark_entity_visited(source, visitStamp))
10834 return;
10835 out.push_back(source);
10836 });
10837 }
10838
10839 void collect_delete_cascade_sources(Entity target, Pair cond, cnt::darray<Entity>& out) {
10840 GAIA_ASSERT(!target.pair());
10841 const auto visitStamp = next_entity_visit_stamp();
10842 cnt::darray_ext<Entity, 32> targetsToVisit;
10843 (void)try_mark_entity_visited(target, visitStamp);
10844 targetsToVisit.push_back(target);
10845
10846 for (uint32_t i = 0; i < targetsToVisit.size(); ++i) {
10847 const auto currTarget = targetsToVisit[i];
10848 each_delete_cascade_direct_source(currTarget, cond, [&](Entity source) {
10849 if (!valid(source))
10850 return;
10851 if (!try_mark_entity_visited(source, visitStamp))
10852 return;
10853
10854 out.push_back(source);
10855 targetsToVisit.push_back(source);
10856 });
10857 }
10858 }
10859
10861 void req_del_entities_with(Entity entity) {
10862 GAIA_PROF_SCOPE(World::req_del_entities_with);
10863
10864 GAIA_ASSERT(entity != Pair(All, All));
10865
10866 auto req_del_adjunct_pair = [&](Entity relation, Entity target) {
10867 cnt::darray<Entity> sourcesToDel;
10868 sources(relation, target, [&](Entity source) {
10869 sourcesToDel.push_back(source);
10870 });
10871
10872 for (auto source: sourcesToDel)
10873 req_del(fetch(source), source);
10874 };
10875
10876 if (entity.pair()) {
10877 if (entity.id() != All.id()) {
10878 const auto relation = try_get(entity.id());
10879 const auto target = try_get(entity.gen());
10880 if (relation != EntityBad && target != EntityBad && is_exclusive_dont_fragment_relation(relation))
10881 req_del_adjunct_pair(relation, target);
10882 } else {
10883 const auto target = try_get(entity.gen());
10884 if (target == EntityBad)
10885 goto skip_req_del_all_target;
10886 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
10887 if (exclusive_adjunct_sources(store, target) == nullptr)
10888 continue;
10889
10890 req_del_adjunct_pair(relKey.entity(), target);
10891 }
10892 skip_req_del_all_target:;
10893 }
10894 } else if (is_exclusive_dont_fragment_relation(entity)) {
10895 if (const auto* pTargets = targets(entity)) {
10896 for (auto targetKey: *pTargets)
10897 req_del_adjunct_pair(entity, targetKey.entity());
10898 }
10899 }
10900
10901 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
10902 if (it == m_entityToArchetypeMap.end())
10903 return;
10904
10905 for (const auto& record: it->second)
10906 req_del(*record.pArchetype);
10907 }
10908
10911 void req_del_entities_with(Entity entity, Pair cond) {
10912 cnt::set<EntityLookupKey> visited;
10913 req_del_entities_with(entity, cond, visited);
10914 }
10915
10916 void req_del_entities_with(Entity entity, Pair cond, cnt::set<EntityLookupKey>& visited) {
10917 GAIA_PROF_SCOPE(World::req_del_entities_with);
10918
10919 GAIA_ASSERT(entity != Pair(All, All));
10920 if (!visited.insert(EntityLookupKey(entity)).second)
10921 return;
10922
10923 auto req_del_adjunct_pair = [&](Entity relation, Entity target) {
10924 if (!has(relation, cond))
10925 return;
10926
10927 cnt::darray<Entity> sourcesToDel;
10928 sources(relation, target, [&](Entity source) {
10929 sourcesToDel.push_back(source);
10930 });
10931
10932 for (auto source: sourcesToDel)
10933 req_del(fetch(source), source);
10934 };
10935
10936 if (entity.pair()) {
10937 if (entity.id() != All.id()) {
10938 const auto relation = try_get(entity.id());
10939 const auto target = try_get(entity.gen());
10940 if (relation != EntityBad && target != EntityBad && is_exclusive_dont_fragment_relation(relation))
10941 req_del_adjunct_pair(relation, target);
10942 } else {
10943 const auto target = try_get(entity.gen());
10944 if (target == EntityBad)
10945 goto skip_req_del_all_target_cond;
10946 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
10947 if (exclusive_adjunct_sources(store, target) == nullptr)
10948 continue;
10949
10950 req_del_adjunct_pair(relKey.entity(), target);
10951 }
10952 skip_req_del_all_target_cond:;
10953 }
10954 } else if (is_exclusive_dont_fragment_relation(entity)) {
10955 if (const auto* pTargets = targets(entity)) {
10956 for (auto targetKey: *pTargets)
10957 req_del_adjunct_pair(entity, targetKey.entity());
10958 }
10959 }
10960
10961 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
10962 if (it == m_entityToArchetypeMap.end())
10963 return;
10964
10965 cnt::darray<Entity> cascadeTargets;
10966 if (entity.pair() && entity.id() == All.id()) {
10967 const auto target = try_get(entity.gen());
10968 if (target != EntityBad)
10969 collect_delete_cascade_direct_sources(target, cond, cascadeTargets);
10970 }
10971
10972 for (auto source: cascadeTargets)
10973 req_del_entities_with(Pair(All, source), cond, visited);
10974
10975 for (const auto& record: it->second) {
10976 auto* pArchetype = record.pArchetype;
10977 // Evaluate the condition if a valid pair is given
10978 if (!archetype_cond_match(*pArchetype, cond, entity))
10979 continue;
10980
10981 req_del(*pArchetype);
10982 }
10983 }
10984
10986 void rem_from_entities(Entity entity) {
10987 GAIA_PROF_SCOPE(World::rem_from_entities);
10988
10989 invalidate_pair_removal_caches(entity);
10990
10991 auto rem_adjunct_pair = [&](Entity relation, Entity target) {
10992 cnt::darray<Entity> sourcesToRem;
10993 sources(relation, target, [&](Entity source) {
10994 sourcesToRem.push_back(source);
10995 });
10996
10997 for (auto source: sourcesToRem)
10998 del(source, Pair(relation, target));
10999 };
11000
11001 if (entity.pair()) {
11002 if (entity.id() != All.id()) {
11003 const auto relation = try_get(entity.id());
11004 const auto target = try_get(entity.gen());
11005 if (relation != EntityBad && target != EntityBad && is_exclusive_dont_fragment_relation(relation))
11006 rem_adjunct_pair(relation, target);
11007 } else {
11008 const auto target = try_get(entity.gen());
11009 if (target == EntityBad)
11010 goto skip_rem_all_target;
11011 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
11012 if (exclusive_adjunct_sources(store, target) == nullptr)
11013 continue;
11014
11015 rem_adjunct_pair(relKey.entity(), target);
11016 }
11017 skip_rem_all_target:;
11018 }
11019 } else if (is_exclusive_dont_fragment_relation(entity)) {
11020 if (const auto* pTargets = targets(entity)) {
11021 for (auto targetKey: *pTargets)
11022 rem_adjunct_pair(entity, targetKey.entity());
11023 }
11024 }
11025
11026 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
11027 if (it == m_entityToArchetypeMap.end())
11028 return;
11029
11030 // Invalidate the singleton status if necessary
11031 if (!entity.pair()) {
11032 auto& ec = fetch(entity);
11033 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0) {
11034 auto ids = ec.pArchetype->ids_view();
11035 const auto idx = core::get_index(ids, entity);
11036 if (idx != BadIndex)
11037 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::IsSingleton, false);
11038 }
11039 }
11040
11041#if GAIA_OBSERVERS_ENABLED
11042 cnt::set<EntityLookupKey> diffTermSet;
11043 cnt::darray<Entity> diffTerms;
11044 cnt::darray<Entity> diffTargets;
11045 for (const auto& record: it->second) {
11046 auto* pArchetype = record.pArchetype;
11047 if (pArchetype->is_req_del())
11048 continue;
11049
11050 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
11051 if (pDstArchetype == nullptr)
11052 continue;
11053
11054 for (auto id: pArchetype->ids_view()) {
11055 bool matches = false;
11056 if (entity.pair()) {
11057 if (entity.id() == All.id() && entity.gen() == All.id())
11058 matches = id.pair();
11059 else if (entity.id() == All.id())
11060 matches = id.pair() && id.gen() == entity.gen();
11061 else if (entity.gen() == All.id())
11062 matches = id.pair() && id.id() == entity.id();
11063 else
11064 matches = id == entity;
11065 } else
11066 matches = id == entity;
11067
11068 if (!matches)
11069 continue;
11070
11071 if (diffTermSet.insert(EntityLookupKey(id)).second)
11072 diffTerms.push_back(id);
11073 }
11074
11075 for (const auto* pChunk: pArchetype->chunks()) {
11076 const auto entities = pChunk->entity_view();
11077 GAIA_EACH(entities)
11078 diffTargets.push_back(entities[i]);
11079 }
11080 }
11081 auto delDiffCtx = diffTargets.empty() || diffTerms.empty()
11082 ? ObserverRegistry::DiffDispatchCtx{}
11083 : m_observers.prepare_diff(
11084 *this, ObserverEvent::OnDel, EntitySpan{diffTerms.data(), diffTerms.size()},
11085 EntitySpan{diffTargets.data(), diffTargets.size()});
11086#endif
11087
11088 // Update archetypes of all affected entities
11089 for (const auto& record: it->second) {
11090 auto* pArchetype = record.pArchetype;
11091 if (pArchetype->is_req_del())
11092 continue;
11093
11094 if (entity.pair()) {
11095 cnt::darray_ext<Entity, 16> removedIsTargets;
11096 for (auto id: pArchetype->ids_view()) {
11097 bool matches = false;
11098 if (entity.id() == All.id() && entity.gen() == All.id())
11099 matches = id.pair();
11100 else if (entity.id() == All.id())
11101 matches = id.pair() && id.gen() == entity.gen();
11102 else if (entity.gen() == All.id())
11103 matches = id.pair() && id.id() == entity.id();
11104 else
11105 matches = id == entity;
11106
11107 if (!matches || !id.pair() || id.id() != Is.id())
11108 continue;
11109
11110 const auto target = try_get(id.gen());
11111 if (target != EntityBad)
11112 removedIsTargets.push_back(target);
11113 }
11114
11115 if (!removedIsTargets.empty()) {
11116 for (const auto* pChunk: pArchetype->chunks()) {
11117 auto entities = pChunk->entity_view();
11118 GAIA_EACH(entities) {
11119 for (const auto target: removedIsTargets)
11120 unlink_live_is_relation(entities[i], target);
11121 }
11122 }
11123 }
11124 }
11125
11126 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
11127 if (pDstArchetype != nullptr)
11128 move_to_archetype(*pArchetype, *pDstArchetype);
11129 }
11130
11131#if GAIA_OBSERVERS_ENABLED
11132 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
11133#endif
11134 }
11135
11138 void rem_from_entities(Entity entity, Pair cond) {
11139 GAIA_PROF_SCOPE(World::rem_from_entities);
11140
11141 invalidate_pair_removal_caches(entity);
11142
11143 auto rem_adjunct_pair = [&](Entity relation, Entity target) {
11144 if (!has(relation, cond))
11145 return;
11146
11147 cnt::darray<Entity> sourcesToRem;
11148 sources(relation, target, [&](Entity source) {
11149 sourcesToRem.push_back(source);
11150 });
11151
11152 for (auto source: sourcesToRem)
11153 del(source, Pair(relation, target));
11154 };
11155
11156 if (entity.pair()) {
11157 if (entity.id() != All.id()) {
11158 const auto relation = try_get(entity.id());
11159 const auto target = try_get(entity.gen());
11160 if (relation != EntityBad && target != EntityBad && is_exclusive_dont_fragment_relation(relation))
11161 rem_adjunct_pair(relation, target);
11162 } else {
11163 const auto target = try_get(entity.gen());
11164 if (target == EntityBad)
11165 goto skip_rem_all_target_cond;
11166 for (const auto& [relKey, store]: m_exclusiveAdjunctByRel) {
11167 if (exclusive_adjunct_sources(store, target) == nullptr)
11168 continue;
11169
11170 rem_adjunct_pair(relKey.entity(), target);
11171 }
11172 skip_rem_all_target_cond:;
11173 }
11174 } else if (is_exclusive_dont_fragment_relation(entity)) {
11175 if (const auto* pTargets = targets(entity)) {
11176 for (auto targetKey: *pTargets)
11177 rem_adjunct_pair(entity, targetKey.entity());
11178 }
11179 }
11180
11181 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entity));
11182 if (it == m_entityToArchetypeMap.end())
11183 return;
11184
11185 // Invalidate the singleton status if necessary
11186 if (!entity.pair()) {
11187 auto& ec = fetch(entity);
11188 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0) {
11189 auto ids = ec.pArchetype->ids_view();
11190 const auto idx = core::get_index(ids, entity);
11191 if (idx != BadIndex)
11192 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::IsSingleton, false);
11193 }
11194 }
11195
11196#if GAIA_OBSERVERS_ENABLED
11197 cnt::set<EntityLookupKey> diffTermSet;
11198 cnt::darray<Entity> diffTerms;
11199 cnt::darray<Entity> diffTargets;
11200 for (const auto& record: it->second) {
11201 auto* pArchetype = record.pArchetype;
11202 if (pArchetype->is_req_del())
11203 continue;
11204
11205 if (!archetype_cond_match(*pArchetype, cond, entity))
11206 continue;
11207
11208 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
11209 if (pDstArchetype == nullptr)
11210 continue;
11211
11212 for (auto id: pArchetype->ids_view()) {
11213 bool matches = false;
11214 if (entity.pair()) {
11215 if (entity.id() == All.id() && entity.gen() == All.id())
11216 matches = id.pair();
11217 else if (entity.id() == All.id())
11218 matches = id.pair() && id.gen() == entity.gen();
11219 else if (entity.gen() == All.id())
11220 matches = id.pair() && id.id() == entity.id();
11221 else
11222 matches = id == entity;
11223 } else
11224 matches = id == entity;
11225
11226 if (!matches)
11227 continue;
11228
11229 if (diffTermSet.insert(EntityLookupKey(id)).second)
11230 diffTerms.push_back(id);
11231 }
11232
11233 for (const auto* pChunk: pArchetype->chunks()) {
11234 const auto entities = pChunk->entity_view();
11235 GAIA_EACH(entities)
11236 diffTargets.push_back(entities[i]);
11237 }
11238 }
11239 auto delDiffCtx = diffTargets.empty() || diffTerms.empty()
11240 ? ObserverRegistry::DiffDispatchCtx{}
11241 : m_observers.prepare_diff(
11242 *this, ObserverEvent::OnDel, EntitySpan{diffTerms.data(), diffTerms.size()},
11243 EntitySpan{diffTargets.data(), diffTargets.size()});
11244#endif
11245
11246 for (const auto& record: it->second) {
11247 auto* pArchetype = record.pArchetype;
11248 if (pArchetype->is_req_del())
11249 continue;
11250
11251 // Evaluate the condition if a valid pair is given
11252 if (!archetype_cond_match(*pArchetype, cond, entity))
11253 continue;
11254
11255 auto* pDstArchetype = calc_dst_archetype(pArchetype, entity);
11256 if (pDstArchetype != nullptr)
11257 move_to_archetype(*pArchetype, *pDstArchetype);
11258 }
11259
11260#if GAIA_OBSERVERS_ENABLED
11261 m_observers.finish_diff(*this, GAIA_MOV(delDiffCtx));
11262#endif
11263 }
11264
11275 void handle_del_entity(EntityContainer& ec, Entity entity) {
11276 GAIA_PROF_SCOPE(World::handle_del_entity);
11277
11278 GAIA_ASSERT(!is_wildcard(entity));
11279
11280 if (entity.pair()) {
11281 if ((ec.flags & EntityContainerFlags::OnDelete_Error) != 0) {
11282 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted");
11283 GAIA_LOG_E(
11284 "Trying to delete a pair [%u.%u] %s [%s] that is forbidden from being deleted", entity.id(),
11285 entity.gen(), name(entity), EntityKindString[entity.kind()]);
11286 return;
11287 }
11288
11289 const auto tgt = try_get(entity.gen());
11290 const bool hasLiveTarget = tgt != EntityBad;
11291 if (hasLiveTarget) {
11292 const auto& ecTgt = fetch(tgt);
11293 if ((ecTgt.flags & EntityContainerFlags::OnDeleteTarget_Error) != 0 ||
11294 has_exclusive_adjunct_target_cond(tgt, Pair(OnDeleteTarget, Error))) {
11295 GAIA_ASSERT2(
11296 false, "Trying to delete an entity that is forbidden from being deleted (target restriction)");
11297 GAIA_LOG_E(
11298 "Trying to delete a pair [%u.%u] %s [%s] that is forbidden from being deleted (target restriction)",
11299 entity.id(), entity.gen(), name(entity), EntityKindString[entity.kind()]);
11300 return;
11301 }
11302 }
11303
11304#if GAIA_USE_SAFE_ENTITY
11305 // Decrement the ref count at this point.
11306 if ((ec.flags & EntityContainerFlags::RefDecreased) == 0) {
11307 --ec.refCnt;
11308 ec.flags |= EntityContainerFlags::RefDecreased;
11309 }
11310
11311 // Don't delete so long something still references us
11312 if (ec.refCnt != 0)
11313 return;
11314#endif
11315
11316 if (hasLiveTarget) {
11317 const auto& ecTgt = fetch(tgt);
11318 if ((ecTgt.flags & EntityContainerFlags::OnDeleteTarget_Delete) != 0 ||
11319 has_exclusive_adjunct_target_cond(tgt, Pair(OnDeleteTarget, Delete))) {
11320#if GAIA_OBSERVERS_ENABLED
11321 cnt::darray<Entity> cascadeTargets;
11322 collect_delete_cascade_sources(tgt, Pair(OnDeleteTarget, Delete), cascadeTargets);
11323 auto cascadeDelDiffCtx =
11324 cascadeTargets.empty()
11325 ? ObserverRegistry::DiffDispatchCtx{}
11326 : m_observers.prepare_diff(
11327 *this, ObserverEvent::OnDel, EntitySpan{cascadeTargets.data(), cascadeTargets.size()},
11328 EntitySpan{cascadeTargets.data(), cascadeTargets.size()});
11329#endif
11330 // Delete all entities referencing this one as a relationship pair's target
11331 req_del_entities_with(Pair(All, tgt), Pair(OnDeleteTarget, Delete));
11332#if GAIA_OBSERVERS_ENABLED
11333 m_observers.finish_diff(*this, GAIA_MOV(cascadeDelDiffCtx));
11334#endif
11335 } else {
11336 // Remove from all entities referencing this one as a relationship pair's target
11337 rem_from_entities(Pair(All, tgt));
11338 }
11339 }
11340
11341 // This entity has been requested to be deleted already. Nothing more for us to do here
11342 if (is_req_del(ec))
11343 return;
11344
11345#if GAIA_OBSERVERS_ENABLED
11346 observers().del(*this, entity);
11347#endif
11348
11349 if ((ec.flags & EntityContainerFlags::OnDelete_Delete) != 0) {
11350 // Delete all references to the entity
11351 req_del_entities_with(entity);
11352 } else {
11353 // Entities are only removed by default
11354 rem_from_entities(entity);
11355 }
11356 } else {
11357 if ((ec.flags & EntityContainerFlags::OnDelete_Error) != 0) {
11358 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted");
11359 GAIA_LOG_E(
11360 "Trying to delete an entity [%u.%u] %s [%s] that is forbidden from being deleted", entity.id(),
11361 entity.gen(), name(entity), EntityKindString[entity.kind()]);
11362 return;
11363 }
11364
11365 if ((ec.flags & EntityContainerFlags::OnDeleteTarget_Error) != 0 ||
11366 has_exclusive_adjunct_target_cond(entity, Pair(OnDeleteTarget, Error))) {
11367 GAIA_ASSERT2(false, "Trying to delete an entity that is forbidden from being deleted (a pair's target)");
11368 GAIA_LOG_E(
11369 "Trying to delete an entity [%u.%u] %s [%s] that is forbidden from being deleted (a pair's target)",
11370 entity.id(), entity.gen(), name(entity), EntityKindString[entity.kind()]);
11371 return;
11372 }
11373
11374#if GAIA_USE_SAFE_ENTITY
11375 // Decrement the ref count at this point.
11376 if ((ec.flags & EntityContainerFlags::RefDecreased) == 0) {
11377 --ec.refCnt;
11378 ec.flags |= EntityContainerFlags::RefDecreased;
11379 }
11380
11381 // Don't delete so long something still references us
11382 if (ec.refCnt != 0)
11383 return;
11384#endif
11385
11386 const bool deleteTargets = (ec.flags & EntityContainerFlags::OnDeleteTarget_Delete) != 0 ||
11387 has_exclusive_adjunct_target_cond(entity, Pair(OnDeleteTarget, Delete));
11388 cnt::darray<Entity> cascadeTargets;
11389 if (deleteTargets)
11390 collect_delete_cascade_sources(entity, Pair(OnDeleteTarget, Delete), cascadeTargets);
11391#if GAIA_OBSERVERS_ENABLED
11392 auto cascadeDelDiffCtx = ObserverRegistry::DiffDispatchCtx{};
11393 if (!cascadeTargets.empty()) {
11394 cascadeDelDiffCtx = m_observers.prepare_diff(
11395 *this, ObserverEvent::OnDel, EntitySpan{cascadeTargets.data(), cascadeTargets.size()},
11396 EntitySpan{cascadeTargets.data(), cascadeTargets.size()});
11397 }
11398#endif
11399
11400 if (deleteTargets) {
11401 // Delete all entities referencing this one as a relationship pair's target
11402 req_del_entities_with(Pair(All, entity), Pair(OnDeleteTarget, Delete));
11403 } else {
11404 // Remove from all entities referencing this one as a relationship pair's target
11405 rem_from_entities(Pair(All, entity));
11406 }
11407
11408#if GAIA_OBSERVERS_ENABLED
11409 m_observers.finish_diff(*this, GAIA_MOV(cascadeDelDiffCtx));
11410#endif
11411
11412 // This entity is has been requested to be deleted already. Nothing more for us to do here
11413 if (is_req_del(ec))
11414 return;
11415
11416#if GAIA_OBSERVERS_ENABLED
11417 observers().del(*this, entity);
11418#endif
11419
11420 if ((ec.flags & EntityContainerFlags::OnDelete_Delete) != 0) {
11421 // Delete all references to the entity
11422 req_del_entities_with(entity);
11423 } else {
11424 // Entities are only removed by default
11425 rem_from_entities(entity);
11426 }
11427 }
11428
11429 // Mark the entity with the "delete requested" flag
11430 req_del(ec, entity);
11431
11432#if GAIA_USE_WEAK_ENTITY
11433 // Invalidate WeakEntities
11434 while (ec.pWeakTracker != nullptr) {
11435 auto* pTracker = ec.pWeakTracker;
11436 ec.pWeakTracker = pTracker->next;
11437 if (ec.pWeakTracker != nullptr)
11438 ec.pWeakTracker->prev = nullptr;
11439
11440 auto* pWeakEntity = pTracker->pWeakEntity;
11441 GAIA_ASSERT(pWeakEntity != nullptr);
11442 GAIA_ASSERT(pWeakEntity->m_pTracker == pTracker);
11443 pWeakEntity->m_pTracker = nullptr;
11444 pWeakEntity->m_entity = EntityBad;
11445 delete pTracker;
11446 }
11447#endif
11448 }
11449
11454 void remove_edge_from_archetype(Archetype* pArchetype, ArchetypeGraphEdge edgeLeft, Entity edgeEntity) {
11455 GAIA_ASSERT(pArchetype != nullptr);
11456
11457 const auto edgeLeftIt = m_archetypesById.find(ArchetypeIdLookupKey(edgeLeft.id, edgeLeft.hash));
11458 if (edgeLeftIt == m_archetypesById.end())
11459 return;
11460
11461 auto* pArchetypeLeft = edgeLeftIt->second;
11462 GAIA_ASSERT(pArchetypeLeft != nullptr);
11463
11464 // Remove the connection with the current archetype
11465 pArchetypeLeft->del_graph_edges(pArchetype, edgeEntity);
11466
11467 // Traverse all archetypes on the right
11468 auto& archetypesRight = pArchetype->right_edges();
11469 for (auto& it: archetypesRight) {
11470 const auto& edgeRight = it.second;
11471 const auto edgeRightIt = m_archetypesById.find(ArchetypeIdLookupKey(edgeRight.id, edgeRight.hash));
11472 if (edgeRightIt == m_archetypesById.end())
11473 continue;
11474
11475 auto* pArchetypeRight = edgeRightIt->second;
11476
11477 // Remove the connection with the current archetype
11478 pArchetype->del_graph_edges(pArchetypeRight, it.first.entity());
11479 }
11480 }
11481
11482 void remove_edges(Entity entityToRemove) {
11483 const auto it = m_entityToArchetypeMap.find(EntityLookupKey(entityToRemove));
11484 if (it == m_entityToArchetypeMap.end())
11485 return;
11486
11487 for (const auto& record: it->second) {
11488 auto* pArchetype = record.pArchetype;
11489 remove_edge_from_archetype(pArchetype, pArchetype->find_edge_left(entityToRemove), entityToRemove);
11490 }
11491 }
11492
11493 void remove_edges_from_pairs(Entity entity) {
11494 if (entity.pair())
11495 return;
11496
11497 // Make sure to remove all pairs containing the entity
11498 // (X, something)
11499 const auto* tgts = targets(entity);
11500 if (tgts != nullptr) {
11501 for (auto target: *tgts)
11502 remove_edges(Pair(entity, target.entity()));
11503 }
11504 // (something, X)
11505 const auto* rels = relations(entity);
11506 if (rels != nullptr) {
11507 for (auto relation: *rels)
11508 remove_edges(Pair(relation.entity(), entity));
11509 }
11510 }
11511
11514 void del_graph_edges(Entity entity) {
11515 remove_edges(entity);
11516 remove_edges_from_pairs(entity);
11517 }
11518
11519 void touch_rel_version(Entity relation) {
11520 const EntityLookupKey key(relation);
11521 auto it = m_relationVersions.find(key);
11522 if (it == m_relationVersions.end())
11523 m_relationVersions.emplace(key, 1);
11524 else {
11525 ++it->second;
11526 if (it->second == 0)
11527 it->second = 1;
11528 }
11529 }
11530
11531 void del_reltgt_tgtrel_pairs(Entity entity) {
11532 auto delPair = [](PairMap& map, Entity source, Entity remove) {
11533 auto itTargets = map.find(EntityLookupKey(source));
11534 if (itTargets != map.end()) {
11535 auto& targets = itTargets->second;
11536 targets.erase(EntityLookupKey(remove));
11537 }
11538 };
11539
11540 Archetype* pArchetype = nullptr;
11541
11542 if (entity.pair()) {
11543 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
11544 if (it != m_recs.pairs.end()) {
11545 pArchetype = it->second.pArchetype;
11546 // Delete the container record
11547 m_recs.pairs.erase(it);
11548
11549 // Update pairs
11550 // The relation or target entity may already be invalid at this point. Rebuild the
11551 // lookup keys from the stored entity records instead of calling get().
11552 GAIA_ASSERT(entity.id() < m_recs.entities.size());
11553 GAIA_ASSERT(entity.gen() < m_recs.entities.size());
11554 auto rel = m_recs.entities.handle(entity.id());
11555 auto tgt = m_recs.entities.handle(entity.gen());
11556
11557 delPair(m_relToTgt, rel, tgt);
11558 delPair(m_relToTgt, All, tgt);
11559 delPair(m_tgtToRel, tgt, rel);
11560 delPair(m_tgtToRel, All, rel);
11561 }
11562 } else {
11563 // Update the container record
11564 auto ec = m_recs.entities[entity.id()];
11565 m_recs.entities.free(entity);
11566
11567 // Remove all outgoing non-fragmenting sparse components from this entity.
11568 del_sparse_components(entity);
11569 // Remove all outgoing non-fragmenting exclusive relations from this source entity.
11570 del_exclusive_adjunct_source(entity);
11571 // If the deleted entity is itself a non-fragmenting exclusive relation, drop its store.
11572 del_exclusive_adjunct_relation(entity);
11573 // If the deleted entity is itself a non-fragmenting sparse component, drop its store.
11574 del_sparse_component_store(entity);
11575
11576 // If this is a singleton entity its archetype needs to be deleted
11577 if ((ec.flags & EntityContainerFlags::IsSingleton) != 0)
11578 req_del(*ec.pArchetype);
11579
11580 ec.pArchetype = nullptr;
11581 ec.pChunk = nullptr;
11582 ec.pEntity = nullptr;
11583 EntityBuilder::set_flag(ec.flags, EntityContainerFlags::DeleteRequested, false);
11584
11585 // Update pairs
11586 delPair(m_relToTgt, All, entity);
11587 delPair(m_tgtToRel, All, entity);
11588 m_relToTgt.erase(EntityLookupKey(entity));
11589 m_tgtToRel.erase(EntityLookupKey(entity));
11590 }
11591
11592 del_entity_archetype_pairs(entity, pArchetype);
11593 }
11594
11597 void invalidate_entity(Entity entity) {
11598 del_graph_edges(entity);
11599 del_reltgt_tgtrel_pairs(entity);
11600 }
11601
11606 void store_entity(EntityContainer& ec, Entity entity, Archetype* pArchetype, Chunk* pChunk) {
11607 GAIA_ASSERT(pArchetype != nullptr);
11608 GAIA_ASSERT(pChunk != nullptr);
11609 GAIA_ASSERT(
11610 !locked() && "Entities can't be stored while the world is locked "
11611 "(structural changes are forbidden during this time!)");
11612
11613 ec.pArchetype = pArchetype;
11614 ec.pChunk = pChunk;
11615 ec.row = pChunk->add_entity(entity);
11616 ec.pEntity = &pChunk->entity_view()[ec.row];
11617 GAIA_ASSERT(entity.pair() || ec.data.gen == entity.gen());
11618 ec.data.dis = 0;
11619 }
11620
11626 void move_entity(Entity entity, EntityContainer& ec, Archetype& dstArchetype, Chunk& dstChunk) {
11627 GAIA_PROF_SCOPE(World::move_entity);
11628
11629 auto* pDstChunk = &dstChunk;
11630 auto* pSrcChunk = ec.pChunk;
11631
11632 GAIA_ASSERT(pDstChunk != pSrcChunk);
11633
11634 const auto srcRow0 = ec.row;
11635 const auto dstRow = pDstChunk->add_entity(entity);
11636 const bool wasEnabled = !ec.data.dis;
11637
11638 auto& srcArchetype = *ec.pArchetype;
11639 const bool archetypeChanged = srcArchetype.id() != dstArchetype.id();
11640#if GAIA_ASSERT_ENABLED
11641 verify_move(*this, srcArchetype, entity);
11642#endif
11643
11644 // Make sure the old entity becomes enabled now
11645 srcArchetype.enable_entity(pSrcChunk, srcRow0, true, m_recs);
11646 // Enabling the entity might have changed its chunk index so fetch it again
11647 const auto srcRow = ec.row;
11648
11649 // Move data from the old chunk to the new one
11650 if (dstArchetype.id() == srcArchetype.id()) {
11651 pDstChunk->move_entity_data(entity, dstRow, m_recs);
11652 } else {
11653 pDstChunk->move_foreign_entity_data(pSrcChunk, srcRow, pDstChunk, dstRow);
11654 }
11655
11656 // Remove the entity record from the old chunk
11657 remove_entity(srcArchetype, *pSrcChunk, srcRow);
11658
11659 // An entity might have moved, try updating the free chunk index
11660 dstArchetype.try_update_free_chunk_idx();
11661
11662 // Bring the entity container record up-to-date
11663 ec.pArchetype = &dstArchetype;
11664 ec.pChunk = pDstChunk;
11665 ec.row = (uint16_t)dstRow;
11666 ec.pEntity = &pDstChunk->entity_view()[dstRow];
11667 if (archetypeChanged)
11668 update_src_entity_version(entity);
11669
11670 // Make the enabled state in the new chunk match the original state
11671 dstArchetype.enable_entity(pDstChunk, dstRow, wasEnabled, m_recs);
11672
11673 // End-state validation
11674 GAIA_ASSERT(valid(entity));
11675 validate_chunk(pSrcChunk);
11676 validate_chunk(pDstChunk);
11677 validate_entities();
11678 }
11679
11682 void move_entity_raw(Entity entity, EntityContainer& ec, Archetype& dstArchetype) {
11683 // Update the old chunk's world version first
11684 ec.pChunk->update_world_version();
11685 ec.pChunk->update_entity_order_version();
11686
11687 auto* pDstChunk = dstArchetype.foc_free_chunk();
11688 move_entity(entity, ec, dstArchetype, *pDstChunk);
11689
11690 // Update world versions
11691 pDstChunk->update_world_version();
11692 pDstChunk->update_entity_order_version();
11693 update_version(m_worldVersion);
11694 }
11695
11698 Chunk* move_entity(Entity entity, Archetype& dstArchetype) {
11699 // Archetypes need to be different
11700 auto& ec = fetch(entity);
11701 if (ec.pArchetype == &dstArchetype)
11702 return nullptr;
11703
11704 // Update the old chunk's world version first
11705 ec.pChunk->update_world_version();
11706 ec.pChunk->update_entity_order_version();
11707
11708 auto* pDstChunk = dstArchetype.foc_free_chunk();
11709 move_entity(entity, ec, dstArchetype, *pDstChunk);
11710
11711 // Update world versions
11712 pDstChunk->update_world_version();
11713 pDstChunk->update_entity_order_version();
11714 update_version(m_worldVersion);
11715
11716 return pDstChunk;
11717 }
11718
11719 void validate_archetype_edges([[maybe_unused]] const Archetype* pArchetype) const {
11720#if GAIA_ECS_VALIDATE_ARCHETYPE_GRAPH && GAIA_ASSERT_ENABLED
11721 GAIA_ASSERT(pArchetype != nullptr);
11722
11723 // Validate left edges
11724 const auto& archetypesLeft = pArchetype->left_edges();
11725 for (const auto& it: archetypesLeft) {
11726 const auto& edge = it.second;
11727 const auto edgeIt = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
11728 if (edgeIt == m_archetypesById.end())
11729 continue;
11730
11731 const auto entity = it.first.entity();
11732 const auto* pArchetypeRight = edgeIt->second;
11733
11734 // Edge must be found
11735 const auto edgeRight = pArchetypeRight->find_edge_right(entity);
11736 GAIA_ASSERT(edgeRight != ArchetypeIdHashPairBad);
11737
11738 // The edge must point to pArchetype
11739 const auto it2 = m_archetypesById.find(ArchetypeIdLookupKey(edgeRight.id, edgeRight.hash));
11740 GAIA_ASSERT(it2 != m_archetypesById.end());
11741 const auto* pArchetype2 = it2->second;
11742 GAIA_ASSERT(pArchetype2 == pArchetype);
11743 }
11744
11745 // Validate right edges
11746 const auto& archetypesRight = pArchetype->right_edges();
11747 for (const auto& it: archetypesRight) {
11748 const auto& edge = it.second;
11749 const auto edgeIt = m_archetypesById.find(ArchetypeIdLookupKey(edge.id, edge.hash));
11750 if (edgeIt == m_archetypesById.end())
11751 continue;
11752
11753 const auto entity = it.first.entity();
11754 const auto* pArchetypeRight = edgeIt->second;
11755
11756 // Edge must be found
11757 const auto edgeLeft = pArchetypeRight->find_edge_left(entity);
11758 GAIA_ASSERT(edgeLeft != ArchetypeIdHashPairBad);
11759
11760 // The edge must point to pArchetype
11761 const auto it2 = m_archetypesById.find(ArchetypeIdLookupKey(edgeLeft.id, edgeLeft.hash));
11762 GAIA_ASSERT(it2 != m_archetypesById.end());
11763 const auto* pArchetype2 = it2->second;
11764 GAIA_ASSERT(pArchetype2 == pArchetype);
11765 }
11766#endif
11767 }
11768
11770 void validate_entities() const {
11771#if GAIA_ECS_VALIDATE_ENTITY_LIST
11772 m_recs.entities.validate();
11773#endif
11774 }
11775
11777 void validate_chunk([[maybe_unused]] Chunk* pChunk) const {
11778#if GAIA_ECS_VALIDATE_CHUNKS && GAIA_ASSERT_ENABLED
11779 GAIA_ASSERT(pChunk != nullptr);
11780
11781 if (!pChunk->empty()) {
11782 // Make sure a proper amount of entities reference the chunk
11783 uint32_t cnt = 0;
11784 for (const auto& ec: m_recs.entities) {
11785 if (ec.pChunk != pChunk)
11786 continue;
11787 ++cnt;
11788 }
11789 for (const auto& pair: m_recs.pairs) {
11790 if (pair.second.pChunk != pChunk)
11791 continue;
11792 ++cnt;
11793 }
11794 GAIA_ASSERT(cnt == pChunk->size());
11795 } else {
11796 // Make sure no entities reference the chunk
11797 for (const auto& ec: m_recs.entities) {
11798 GAIA_ASSERT(ec.pChunk != pChunk);
11799 }
11800 for (const auto& pair: m_recs.pairs) {
11801 GAIA_ASSERT(pair.second.pChunk != pChunk);
11802 }
11803 }
11804#endif
11805 }
11806
11810 template <bool CheckIn>
11811 GAIA_NODISCARD bool is_inter(Entity entity, Entity entityBase) const {
11812 GAIA_ASSERT(valid_entity(entity));
11813 GAIA_ASSERT(valid_entity(entityBase));
11814
11815 // Pairs are not supported
11816 if (entity.pair() || entityBase.pair())
11817 return false;
11818
11819 if constexpr (!CheckIn) {
11820 if (entity == entityBase)
11821 return true;
11822 }
11823
11824 const auto& targets = as_targets_trav_cache(entity);
11825 for (auto target: targets) {
11826 if (target == entityBase)
11827 return true;
11828 }
11829
11830 return false;
11831 }
11832
11834 template <bool CheckIn, typename Func>
11835 void as_up_trav(Entity entity, Func func) {
11836 GAIA_ASSERT(valid_entity(entity));
11837
11838 // Pairs are not supported
11839 if (entity.pair())
11840 return;
11841
11842 if constexpr (!CheckIn) {
11843 func(entity);
11844 }
11845
11846 const auto& ec = m_recs.entities[entity.id()];
11847 const auto* pArchetype = ec.pArchetype;
11848
11849 // Early exit if there are no Is relationship pairs on the archetype
11850 if (pArchetype->pairs_is() == 0)
11851 return;
11852
11853 for (uint32_t i = 0; i < pArchetype->pairs_is(); ++i) {
11854 auto e = pArchetype->entity_from_pairs_as_idx(i);
11855 const auto& ecTarget = m_recs.entities[e.gen()];
11856 auto target = *ecTarget.pEntity;
11857 func(target);
11858
11859 as_up_trav<CheckIn>(target, func);
11860 }
11861 }
11862
11863 template <typename T>
11864 const ComponentCacheItem& reg_core_entity(Entity id, Archetype* pArchetype) {
11865 auto comp = add(*pArchetype, id.entity(), id.pair(), id.kind());
11866 const auto& ci = comp_cache_mut().add<T>(id);
11867 GAIA_ASSERT(ci.entity == id);
11868 GAIA_ASSERT(comp == id);
11869 (void)comp;
11870 return ci;
11871 }
11872
11873 template <typename T>
11874 const ComponentCacheItem& reg_core_entity(Entity id) {
11875 return reg_core_entity<T>(id, m_pRootArchetype);
11876 }
11877
11878#if GAIA_ECS_AUTO_COMPONENT_SCHEMA
11879 template <typename T>
11880 static void auto_populate_component_schema(ComponentCacheItem& item) {
11881 if (!item.fields_empty())
11882 return;
11883
11884 using U = core::raw_t<T>;
11885 if constexpr (std::is_empty_v<U>)
11886 return;
11887 if constexpr (mem::is_soa_layout_v<U>)
11888 return;
11889
11890 if constexpr (!std::is_class_v<U>) {
11891 (void)item.set_field("value", 5, ser::type_id<U>(), 0, (uint32_t)sizeof(U));
11892 return;
11893 } else if constexpr (!std::is_aggregate_v<U>) {
11894 // Non-aggregate classes are not safe for offset extraction via meta::each_member.
11895 // Keep these components on serializer-based fallback rendering.
11896 return;
11897 } else {
11898 U tmp{};
11899 const auto* pBase = reinterpret_cast<const uint8_t*>(&tmp);
11900 uint32_t fieldIdx = 0;
11901 meta::each_member(tmp, [&](auto&... fields) {
11902 auto add_field = [&](auto& field) {
11903 using F = core::raw_t<decltype(field)>;
11904 char fieldName[24]{};
11905 (void)GAIA_STRFMT(fieldName, sizeof(fieldName), "f%u", fieldIdx++);
11906 const auto* pField = reinterpret_cast<const uint8_t*>(&field);
11907 const auto offset = (uint32_t)(pField - pBase);
11908 (void)item.set_field(fieldName, 0, ser::type_id<F>(), offset, (uint32_t)sizeof(F));
11909 };
11910 (add(fields), ...);
11911 });
11912 }
11913 }
11914#endif
11915
11916 void init();
11917
11918 void done() {
11919 cleanup_inter();
11920
11921#if GAIA_ECS_CHUNK_ALLOCATOR
11922 ChunkAllocator::get().flush();
11923#endif
11924 }
11925
11929 void assign_entity(Entity entity, Archetype& archetype) {
11930 GAIA_ASSERT(!entity.pair());
11931
11932 auto* pChunk = archetype.foc_free_chunk();
11933 store_entity(m_recs.entities[entity.id()], entity, &archetype, pChunk);
11934 pChunk->update_versions();
11935 archetype.try_update_free_chunk_idx();
11936
11937 // Call constructors for the generic components on the newly added entity if necessary
11938 pChunk->call_gen_ctors(pChunk->size() - 1, 1);
11939
11940#if GAIA_ASSERT_ENABLED
11941 const auto& ec = m_recs.entities[entity.id()];
11942 GAIA_ASSERT(ec.pChunk == pChunk);
11943 auto entityExpected = pChunk->entity_view()[ec.row];
11944 GAIA_ASSERT(entityExpected == entity);
11945#endif
11946 }
11947
11951 void assign_pair(Entity entity, Archetype& archetype) {
11952 GAIA_ASSERT(entity.pair());
11953
11954 // Pairs are always added to m_pEntityArchetype initially and this can't change.
11955 GAIA_ASSERT(&archetype == m_pEntityArchetype);
11956
11957 const auto it = m_recs.pairs.find(EntityLookupKey(entity));
11958 if (it != m_recs.pairs.end())
11959 return;
11960
11961 // Update the container record
11962 EntityContainer ec{};
11963 ec.idx = entity.id();
11964 ec.data.gen = entity.gen();
11965 ec.data.pair = 1;
11966 ec.data.ent = 1;
11967 ec.data.kind = EntityKind::EK_Gen;
11968
11969 auto* pChunk = archetype.foc_free_chunk();
11970 store_entity(ec, entity, &archetype, pChunk);
11971 pChunk->update_versions();
11972 archetype.try_update_free_chunk_idx();
11973
11974 m_recs.pairs.emplace(EntityLookupKey(entity), GAIA_MOV(ec));
11975
11976 // Update pair mappings
11977 const auto rel = get(entity.id());
11978 const auto tgt = get(entity.gen());
11979
11980 auto addPair = [](PairMap& map, Entity source, Entity add) {
11981 auto& ents = map[EntityLookupKey(source)];
11982 ents.insert(EntityLookupKey(add));
11983 };
11984
11985 addPair(m_relToTgt, rel, tgt);
11986 addPair(m_relToTgt, All, tgt);
11987 addPair(m_tgtToRel, tgt, rel);
11988 addPair(m_tgtToRel, All, rel);
11989
11990#if GAIA_OBSERVERS_ENABLED
11991 m_observers.try_mark_term_observed(*this, entity);
11992#endif
11993 }
11994
12001 GAIA_NODISCARD Entity add(Archetype& archetype, bool isEntity, bool isPair, EntityKind kind) {
12002 EntityContainerCtx ctx{isEntity, isPair, kind};
12003 const auto entity = m_recs.entities.alloc(&ctx);
12004 assign_entity(entity, archetype);
12005 return entity;
12006 }
12007
12013 template <typename Func>
12014 void add_entity_n(Archetype& archetype, uint32_t count, Func func) {
12015 EntityContainerCtx ctx{true, false, EntityKind::EK_Gen};
12016#if GAIA_OBSERVERS_ENABLED
12017 const auto addedIds = EntitySpan{archetype.ids_view()};
12018 ObserverRegistry::DiffDispatchCtx addDiffCtx{};
12019 if (!addedIds.empty())
12020 addDiffCtx = m_observers.prepare_diff_add_new(*this, addedIds);
12021#endif
12022
12023 uint32_t left = count;
12024 do {
12025 auto* pChunk = archetype.foc_free_chunk();
12026 const uint32_t originalChunkSize = pChunk->size();
12027 const uint32_t freeSlotsInChunk = pChunk->capacity() - originalChunkSize;
12028 const uint32_t toCreate = core::get_min(freeSlotsInChunk, left);
12029
12030 GAIA_FOR(toCreate) {
12031 const auto entityNew = m_recs.entities.alloc(&ctx);
12032 auto& ecNew = m_recs.entities[entityNew.id()];
12033 store_entity(ecNew, entityNew, &archetype, pChunk);
12034
12035#if GAIA_ASSERT_ENABLED
12036 GAIA_ASSERT(ecNew.pChunk == pChunk);
12037 auto entityExpected = pChunk->entity_view()[ecNew.row];
12038 GAIA_ASSERT(entityExpected == entityNew);
12039#endif
12040 }
12041
12042 // New entities were added, try updating the free chunk index
12043 archetype.try_update_free_chunk_idx();
12044
12045 // Call constructors for the generic components on the newly added entity if necessary
12046 pChunk->call_gen_ctors(originalChunkSize, toCreate);
12047
12048 // Call functors
12049 {
12050 auto entities = pChunk->entity_view();
12051 GAIA_FOR2(originalChunkSize, pChunk->size()) func(entities[i]);
12052 }
12053
12054 pChunk->update_versions();
12055
12056#if GAIA_OBSERVERS_ENABLED
12057 if (!addedIds.empty()) {
12058 auto entities = pChunk->entity_view();
12059 const auto targets = EntitySpan{entities.data() + originalChunkSize, toCreate};
12060 m_observers.append_diff_targets(*this, addDiffCtx, targets);
12061 m_observers.on_add(*this, archetype, addedIds, targets);
12062 }
12063#endif
12064
12065 left -= toCreate;
12066 } while (left > 0);
12067
12068#if GAIA_OBSERVERS_ENABLED
12069 if (!addedIds.empty())
12070 m_observers.finish_diff(*this, GAIA_MOV(addDiffCtx));
12071#endif
12072 }
12073
12075 void gc() {
12076 GAIA_PROF_SCOPE(World::gc);
12077
12078 del_empty_chunks();
12079 defrag_chunks(m_defragEntitiesPerTick);
12080 del_empty_archetypes();
12081 }
12082
12083 public:
12088 QuerySerBuffer& query_buffer(QueryId& serId) {
12089 // No serialization id set on the query, try creating a new record
12090 if GAIA_UNLIKELY (serId == QueryIdBad) {
12091#if GAIA_ASSERT_ENABLED
12092 uint32_t safetyCounter = 0;
12093#endif
12094
12095 while (true) {
12096#if GAIA_ASSERT_ENABLED
12097 // Make sure we don't cross some safety threshold
12098 ++safetyCounter;
12099 GAIA_ASSERT(safetyCounter < 100000);
12100#endif
12101
12102 serId = ++m_nextQuerySerId;
12103 // Make sure we do not overflow
12104 GAIA_ASSERT(serId != 0);
12105
12106 // If the id is already found, try again.
12107 // Note, this is essentially never going to repeat. We would have to prepare millions if
12108 // not billions of queries for which we only added inputs but never queried them.
12109 auto ret = m_querySerMap.try_emplace(serId);
12110 if (!ret.second)
12111 continue;
12112
12113 return ret.first->second;
12114 };
12115 }
12116
12117 return m_querySerMap[serId];
12118 }
12119
12122 void query_buffer_reset(QueryId& serId) {
12123 auto it = m_querySerMap.find(serId);
12124 if (it == m_querySerMap.end())
12125 return;
12126
12127 m_querySerMap.erase(it);
12128 serId = QueryIdBad;
12129 }
12130
12134 m_queryCache.invalidate_queries_for_entity(entityKey, QueryCache::ChangeKind::Structural);
12135 }
12136
12140 m_queryCache.invalidate_queries_for_rel(relation, QueryCache::ChangeKind::DynamicResult);
12141 }
12142
12146 m_queryCache.invalidate_sorted_queries_for_entity(entity);
12147 }
12148
12151 m_queryCache.invalidate_sorted_queries();
12152 }
12153
12157 GAIA_ASSERT(is_pair.first() == Is);
12158
12159 // We still need to handle invalidation "down-the-tree".
12160 // E.g. following setup:
12161 // q = w.query().all({Is,animal});
12162 // w.as(wolf, carnivore);
12163 // w.as(carnivore, animal);
12164 // q.each() ...; // animal, carnivore, wolf
12165 // w.del(wolf, {Is,carnivore}) // wolf is no longer a carnivore and thus no longer an animal
12166 // After this deletion, we need to invalidate "q" because wolf is no longer an animal
12167 // and we don't want q to include it.
12168 // q.each() ...; // animal
12169
12170 auto e = is_pair.second();
12171 as_up_trav<false>(e, [&](Entity target) {
12172 // Invalidate all queries that contain everything in our path.
12173 invalidate_queries_for_structural_entity(EntityLookupKey(Pair{Is, target}));
12174 });
12175 }
12176
12182 auto expr = util::trim(exprRaw);
12183
12184 if (expr[0] == '(') {
12185 if (expr.back() != ')') {
12186 GAIA_ASSERT2(false, "Expression '(' not terminated");
12187 return EntityBad;
12188 }
12189
12190 const auto idStr = expr.subspan(1, expr.size() - 2);
12191 const auto commaIdx = core::get_index(idStr, ',');
12192
12193 const auto first = name_to_entity(idStr.subspan(0, commaIdx));
12194 if (first == EntityBad)
12195 return EntityBad;
12196 const auto second = name_to_entity(idStr.subspan(commaIdx + 1));
12197 if (second == EntityBad)
12198 return EntityBad;
12199
12200 return ecs::Pair(first, second);
12201 }
12202
12203 {
12204 auto idStr = util::trim(expr);
12205
12206 // Wildcard character
12207 if (idStr.size() == 1 && idStr[0] == '*')
12208 return All;
12209
12210 return get_inter(idStr.data(), (uint32_t)idStr.size());
12211 }
12212 }
12213
12219 Entity expr_to_entity(va_list& args, std::span<const char> exprRaw) const {
12220 auto expr = util::trim(exprRaw);
12221
12222 if (expr[0] == '%') {
12223 if (expr[1] != 'e') {
12224 GAIA_ASSERT2(false, "Expression '%' not terminated");
12225 return EntityBad;
12226 }
12227
12228 auto id = (Identifier)va_arg(args, unsigned long long);
12229 return Entity(id);
12230 }
12231
12232 if (expr[0] == '(') {
12233 if (expr.back() != ')') {
12234 GAIA_ASSERT2(false, "Expression '(' not terminated");
12235 return EntityBad;
12236 }
12237
12238 const auto idStr = expr.subspan(1, expr.size() - 2);
12239 const auto commaIdx = core::get_index(idStr, ',');
12240
12241 const auto first = expr_to_entity(args, idStr.subspan(0, commaIdx));
12242 if (first == EntityBad)
12243 return EntityBad;
12244 const auto second = expr_to_entity(args, idStr.subspan(commaIdx + 1));
12245 if (second == EntityBad)
12246 return EntityBad;
12247
12248 return ecs::Pair(first, second);
12249 }
12250
12251 {
12252 auto idStr = util::trim(expr);
12253
12254 // Wildcard character
12255 if (idStr.size() == 1 && idStr[0] == '*')
12256 return All;
12257
12258 // Anything else is a component name
12259 const auto* pItem = resolve_component_name_inter(idStr.data(), (uint32_t)idStr.size());
12260 if (pItem == nullptr) {
12261 GAIA_ASSERT2(false, "Component not found");
12262 GAIA_LOG_W("Component '%.*s' not found", (uint32_t)idStr.size(), idStr.data());
12263 return EntityBad;
12264 }
12265
12266 return pItem->entity;
12267 }
12268 }
12269 };
12270
12271 using EntityBuilder = World::EntityBuilder;
12272 } // namespace ecs
12273} // namespace gaia
12274
12275#include "api.inl"
12276#include "observer.inl"
12277#include "system.inl"
12278
12279namespace gaia {
12280 namespace ecs {
12281 inline void World::init() {
12282 // Use the default serializer
12283 set_serializer(nullptr);
12284
12285 // Register the root archetype
12286 {
12287 m_pRootArchetype = create_archetype({});
12288 m_pRootArchetype->set_hashes({calc_lookup_hash({})});
12289 reg_archetype(m_pRootArchetype);
12290 }
12291
12292 (void)reg_core_entity<Core_>(Core);
12293
12294 // Entity archetype matches the root archetype for now
12295 m_pEntityArchetype = m_pRootArchetype;
12296
12297 // Register the component archetype (entity + EntityDesc + Component)
12298 {
12299 Archetype* pCompArchetype{};
12300 {
12301 const auto id = GAIA_ID(EntityDesc);
12302 const auto& ci = reg_core_entity<EntityDesc>(id);
12303 EntityBuilder(*this, id).add_inter_init(ci.entity);
12304 sset<EntityDesc>(id) = {ci.name.str(), ci.name.len(), nullptr, 0};
12305 pCompArchetype = m_recs.entities[id.id()].pArchetype;
12306 }
12307 {
12308 const auto id = GAIA_ID(Component);
12309 const auto& ci = reg_core_entity<Component>(id, pCompArchetype);
12310 EntityBuilder(*this, id).add_inter_init(ci.entity);
12311 acc_mut(id)
12312 // Entity descriptor
12313 .sset<EntityDesc>({ci.name.str(), ci.name.len(), nullptr, 0})
12314 // Component
12315 .sset<Component>(ci.comp);
12316 m_pCompArchetype = m_recs.entities[id.id()].pArchetype;
12317 }
12318 }
12319
12320 // Core components.
12321 // Their order must correspond to the value sequence in id.h.
12322 {
12323 (void)reg_core_entity<OnDelete_>(OnDelete);
12324 (void)reg_core_entity<OnDeleteTarget_>(OnDeleteTarget);
12325 (void)reg_core_entity<Remove_>(Remove);
12326 (void)reg_core_entity<Delete_>(Delete);
12327 (void)reg_core_entity<Error_>(Error);
12328 (void)reg_core_entity<Requires_>(Requires);
12329 (void)reg_core_entity<CantCombine_>(CantCombine);
12330 (void)reg_core_entity<Exclusive_>(Exclusive);
12331 (void)reg_core_entity<DontFragment_>(DontFragment);
12332 (void)reg_core_entity<Sparse_>(Sparse);
12333 (void)reg_core_entity<Acyclic_>(Acyclic);
12334 (void)reg_core_entity<Traversable_>(Traversable);
12335 (void)reg_core_entity<All_>(All);
12336 (void)reg_core_entity<ChildOf_>(ChildOf);
12337 (void)reg_core_entity<Parent_>(Parent);
12338 (void)reg_core_entity<Is_>(Is);
12339 (void)reg_core_entity<Prefab_>(Prefab);
12340 (void)reg_core_entity<OnInstantiate_>(OnInstantiate);
12341 (void)reg_core_entity<Override_>(Override);
12342 (void)reg_core_entity<Inherit_>(Inherit);
12343 (void)reg_core_entity<DontInherit_>(DontInherit);
12344 (void)reg_core_entity<System_>(System);
12345 (void)reg_core_entity<DependsOn_>(DependsOn);
12346 (void)reg_core_entity<Observer_>(Observer);
12347
12348 (void)reg_core_entity<_Var0>(Var0);
12349 (void)reg_core_entity<_Var1>(Var1);
12350 (void)reg_core_entity<_Var2>(Var2);
12351 (void)reg_core_entity<_Var3>(Var3);
12352 (void)reg_core_entity<_Var4>(Var4);
12353 (void)reg_core_entity<_Var5>(Var5);
12354 (void)reg_core_entity<_Var6>(Var6);
12355 (void)reg_core_entity<_Var7>(Var7);
12356 }
12357
12358 // Add special properties for core components.
12359 // Their order must correspond to the value sequence in id.h.
12360 {
12361 EntityBuilder(*this, Core) //
12362 .add(Core)
12363 .add(Pair(OnDelete, Error));
12364 EntityBuilder(*this, GAIA_ID(EntityDesc)) //
12365 .add(Core)
12366 .add(Pair(OnDelete, Error));
12367 EntityBuilder(*this, GAIA_ID(Component)) //
12368 .add(Core)
12369 .add(Pair(OnDelete, Error));
12370 EntityBuilder(*this, OnDelete) //
12371 .add(Core)
12372 .add(Exclusive)
12373 .add(Pair(OnDelete, Error));
12374 EntityBuilder(*this, OnDeleteTarget) //
12375 .add(Core)
12376 .add(Exclusive)
12377 .add(Pair(OnDelete, Error));
12378 EntityBuilder(*this, Remove) //
12379 .add(Core)
12380 .add(Pair(OnDelete, Error));
12381 EntityBuilder(*this, Delete) //
12382 .add(Core)
12383 .add(Pair(OnDelete, Error));
12384 EntityBuilder(*this, Error) //
12385 .add(Core)
12386 .add(Pair(OnDelete, Error));
12387 EntityBuilder(*this, All) //
12388 .add(Core)
12389 .add(Pair(OnDelete, Error));
12390 EntityBuilder(*this, Requires) //
12391 .add(Core)
12392 .add(Acyclic)
12393 .add(Pair(OnDelete, Error));
12394 EntityBuilder(*this, CantCombine) //
12395 .add(Core)
12396 .add(Acyclic)
12397 .add(Pair(OnDelete, Error));
12398 EntityBuilder(*this, Exclusive) //
12399 .add(Core)
12400 .add(Pair(OnDelete, Error))
12401 .add(Acyclic);
12402 EntityBuilder(*this, DontFragment) //
12403 .add(Core)
12404 .add(Pair(OnDelete, Error))
12405 .add(Acyclic);
12406 EntityBuilder(*this, Sparse) //
12407 .add(Core)
12408 .add(Pair(OnDelete, Error))
12409 .add(Acyclic);
12410 EntityBuilder(*this, Acyclic) //
12411 .add(Core)
12412 .add(Pair(OnDelete, Error));
12413 EntityBuilder(*this, Traversable) //
12414 .add(Core)
12415 .add(Pair(OnDelete, Error));
12416
12417 EntityBuilder(*this, ChildOf) //
12418 .add(Core)
12419 .add(Acyclic)
12420 .add(Exclusive)
12421 .add(Traversable)
12422 .add(Pair(OnDelete, Error))
12423 .add(Pair(OnDeleteTarget, Delete));
12424 EntityBuilder(*this, Parent) //
12425 .add(Core)
12426 .add(Acyclic)
12427 .add(Exclusive)
12428 .add(DontFragment)
12429 .add(Traversable)
12430 .add(Pair(OnDelete, Error))
12431 .add(Pair(OnDeleteTarget, Delete));
12432 EntityBuilder(*this, Is) //
12433 .add(Core)
12434 .add(Acyclic)
12435 .add(Pair(OnDelete, Error));
12436 EntityBuilder(*this, Prefab) //
12437 .add(Core)
12438 .add(Pair(OnDelete, Error));
12439 EntityBuilder(*this, OnInstantiate) //
12440 .add(Core)
12441 .add(Acyclic)
12442 .add(Exclusive)
12443 .add(DontFragment)
12444 .add(Pair(OnDelete, Error));
12445 EntityBuilder(*this, Override) //
12446 .add(Core)
12447 .add(Pair(OnDelete, Error));
12448 EntityBuilder(*this, Inherit) //
12449 .add(Core)
12450 .add(Pair(OnDelete, Error));
12451 EntityBuilder(*this, DontInherit) //
12452 .add(Core)
12453 .add(Pair(OnDelete, Error));
12454
12455 EntityBuilder(*this, System) //
12456 .add(Core)
12457 .add(Acyclic)
12458 .add(Pair(OnDelete, Error));
12459 EntityBuilder(*this, DependsOn) //
12460 .add(Core)
12461 .add(Acyclic)
12462 .add(Pair(OnDelete, Error));
12463 EntityBuilder(*this, Observer) //
12464 .add(Core)
12465 .add(Acyclic)
12466 .add(Pair(OnDelete, Error));
12467
12468 EntityBuilder(*this, Var0) //
12469 .add(Core)
12470 .add(Pair(OnDelete, Error));
12471 EntityBuilder(*this, Var1) //
12472 .add(Core)
12473 .add(Pair(OnDelete, Error));
12474 EntityBuilder(*this, Var2) //
12475 .add(Core)
12476 .add(Pair(OnDelete, Error));
12477 EntityBuilder(*this, Var3) //
12478 .add(Core)
12479 .add(Pair(OnDelete, Error));
12480 EntityBuilder(*this, Var4) //
12481 .add(Core)
12482 .add(Pair(OnDelete, Error));
12483 EntityBuilder(*this, Var5) //
12484 .add(Core)
12485 .add(Pair(OnDelete, Error));
12486 EntityBuilder(*this, Var6) //
12487 .add(Core)
12488 .add(Pair(OnDelete, Error));
12489 EntityBuilder(*this, Var7) //
12490 .add(Core)
12491 .add(Pair(OnDelete, Error));
12492 }
12493
12494 // Remove all archetypes with no chunks. We don't want any leftovers after
12495 // archetype movements.
12496 {
12497 for (uint32_t i = 1; i < m_archetypes.size(); ++i) {
12498 auto* pArchetype = m_archetypes[i];
12499 if (!pArchetype->chunks().empty())
12500 continue;
12501
12502 // Request deletion the standard way.
12503 // We could simply add archetypes into m_archetypesToDel but this way
12504 // we can actually replicate what the system really does on the inside
12505 // and it will require more work at the cost of easier maintenance.
12506 // The amount of archetypes cleanup is very small after init and the code
12507 // only runs after the world is created so this is not a big deal.
12508 req_del(*pArchetype);
12509 }
12510
12511 // Cleanup
12512 {
12513 del_finalize();
12514 while (!m_chunksToDel.empty() || !m_archetypesToDel.empty())
12515 gc();
12516
12517 // Make sure everything has been cleared
12518 GAIA_ASSERT(m_reqArchetypesToDel.empty());
12519 GAIA_ASSERT(m_chunksToDel.empty());
12520 GAIA_ASSERT(m_archetypesToDel.empty());
12521 }
12522
12523 sort_archetypes();
12524
12525 // Make sure archetypes have valid graphs after the cleanup
12526 for (const auto* pArchetype: m_archetypes)
12527 validate_archetype_edges(pArchetype);
12528 }
12529
12530 // Make sure archetype pointers are up-to-date
12531 m_pCompArchetype = m_recs.entities[GAIA_ID(Component).id()].pArchetype;
12532
12533#if GAIA_SYSTEMS_ENABLED
12534 // Initialize the systems query
12535 systems_init();
12536#endif
12537 }
12538
12539 inline GroupId
12540 group_by_func_default([[maybe_unused]] const World& world, const Archetype& archetype, Entity groupBy) {
12541 if (archetype.pairs() > 0) {
12542 auto ids = archetype.ids_view();
12543 for (auto id: ids) {
12544 if (!id.pair() || id.id() != groupBy.id())
12545 continue;
12546
12547 // Consider the pair's target the groupId
12548 return id.gen();
12549 }
12550 }
12551
12552 // No group
12553 return 0;
12554 }
12555
12556 inline GroupId group_by_func_depth_order(const World& world, const Archetype& archetype, Entity relation) {
12557 GAIA_ASSERT(!relation.pair());
12558
12559 // Depth ordering only makes sense for fragmenting relations whose target participates in archetype identity.
12560 // Non-fragmenting relations such as Parent must stay on walk(...), because their targets vary per entity
12561 // and cannot be represented by one cached archetype depth.
12562 // The level is derived from the cached upward traversal chain so normal query iteration can stay cheap.
12563 if (!world.supports_depth_order(relation) || archetype.pairs() == 0)
12564 return 0;
12565
12566 auto ids = archetype.ids_view();
12567 GroupId maxDepth = 0;
12568 bool found = false;
12569
12570 for (auto idsIdx: archetype.pair_rel_indices(relation)) {
12571 const auto pair = ids[idsIdx];
12572 const auto target = world.pair_target_if_alive(pair);
12573 if (target == EntityBad)
12574 continue;
12575
12576 const GroupId depth = GroupId(world.depth_order_cache(relation, target));
12577
12578 if (!found || depth > maxDepth) {
12579 maxDepth = depth;
12580 found = true;
12581 }
12582 }
12583
12584 return found ? maxDepth : 0;
12585 }
12586 } // namespace ecs
12587} // namespace gaia
12588
12589#include "gaia/ecs/impl/world_json.h"
12590
12591#if GAIA_SYSTEMS_ENABLED
12592namespace gaia {
12593 namespace ecs {
12594 inline void World::systems_init() {
12595 m_systemsQuery = query().all(System).depth_order(DependsOn);
12596 }
12597
12598 inline void World::systems_run() {
12599 if GAIA_UNLIKELY (tearing_down())
12600 return;
12601
12602 m_systemsQuery.each([&](Entity systemEntity) {
12603 if (!valid(systemEntity) || !has(systemEntity, System))
12604 return;
12605 if (!enabled_hierarchy(systemEntity, ChildOf))
12606 return;
12607
12608 auto ss = acc_mut(systemEntity);
12609 auto& sys = ss.smut<ecs::System_>();
12610 sys.exec();
12611 });
12612 }
12613
12614 inline void World::systems_done() {
12615 cnt::darray<Entity> tmpEntities;
12616 m_systemsQuery.each([&](Entity systemEntity) {
12617 tmpEntities.push_back(systemEntity);
12618 });
12619
12620 // Wait for every outstanding system job before mutating any system runtime state.
12621 // This keeps dependency chains intact while jobs are still live.
12622 for (auto entity: tmpEntities) {
12623 if (!valid(entity) || !has(entity, System))
12624 continue;
12625
12626 auto ss = acc_mut(entity);
12627 auto& sys = ss.smut<ecs::System_>();
12628 if (sys.jobHandle != (mt::JobHandle)mt::JobNull_t{}) {
12629 auto& tp = mt::ThreadPool::get();
12630 tp.wait(sys.jobHandle);
12631 }
12632 }
12633
12634 // With all system jobs complete we can release their runtime state safely.
12635 for (auto entity: tmpEntities) {
12636 if (!valid(entity) || !has(entity, System))
12637 continue;
12638
12639 auto ss = acc_mut(entity);
12640 auto& sys = ss.smut<ecs::System_>();
12641 if (sys.jobHandle != (mt::JobHandle)mt::JobNull_t{}) {
12642 auto& tp = mt::ThreadPool::get();
12643 tp.del(sys.jobHandle);
12644 sys.jobHandle = mt::JobNull;
12645 }
12646 sys.on_each_func = {};
12647 sys.query = {};
12648 }
12649
12650 m_systemsQuery = {};
12651 tmpEntities.clear();
12652 }
12653
12654 inline SystemBuilder World::system() {
12655 // Create the system
12656 auto e = add();
12657 EntityBuilder(*this, e) //
12658 .add<System_>();
12659
12660 auto ss = acc_mut(e);
12661 auto& sys = ss.smut<System_>();
12662 {
12663 sys.entity = e;
12664 sys.query = query();
12665 }
12666 return SystemBuilder(*this, e);
12667 }
12668 } // namespace ecs
12669} // namespace gaia
12670#endif
12671
12672namespace gaia {
12673 namespace ecs {
12674 inline uint32_t world_version(const World& world) {
12675 return world.m_worldVersion;
12676 }
12677
12684 inline void
12685 world_for_each_target(const World& world, Entity entity, Entity relation, void* ctx, void (*func)(void*, Entity)) {
12686 world.targets(entity, relation, [ctx, func](Entity target) {
12687 func(ctx, target);
12688 });
12689 }
12690
12694 inline QueryMatchScratch& query_match_scratch_acquire(World& world) {
12695 if (world.m_queryMatchScratchDepth == world.m_queryMatchScratchStack.size())
12696 world.m_queryMatchScratchStack.push_back(new QueryMatchScratch());
12697
12698 auto& scratch = *world.m_queryMatchScratchStack[world.m_queryMatchScratchDepth++];
12699 scratch.clear_temporary_matches();
12700 return scratch;
12701 }
12702
12706 inline void query_match_scratch_release(World& world, bool keepStamps) {
12707 GAIA_ASSERT(world.m_queryMatchScratchDepth > 0);
12708 auto& scratch = *world.m_queryMatchScratchStack[--world.m_queryMatchScratchDepth];
12709 if (keepStamps)
12710 scratch.clear_temporary_matches_keep_stamps();
12711 else
12712 scratch.clear_temporary_matches();
12713 }
12714
12718 inline void world_invalidate_sorted_queries_for_entity(World& world, Entity entity) {
12720 }
12721
12724 inline void world_invalidate_sorted_queries(World& world) {
12725 world.invalidate_sorted_queries();
12726 }
12727
12733 inline bool world_has_entity_term(const World& world, Entity entity, Entity term) {
12734 if (term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
12735 const auto target = world.get(term.gen());
12736 return world.valid(target) && world.is(entity, target);
12737 }
12738
12739 return world.has(entity, term);
12740 }
12741
12747 inline bool world_has_entity_term_in(const World& world, Entity entity, Entity term) {
12748 if (term.pair() && term.id() == Is.id() && !is_wildcard(term.gen())) {
12749 const auto target = world.get(term.gen());
12750 return world.valid(target) && world.in(entity, target);
12751 }
12752
12753 return false;
12754 }
12755
12760 inline bool world_term_uses_inherit_policy(const World& world, Entity term) {
12761 return !is_wildcard(term) && world.valid(term) && world.target(term, OnInstantiate) == Inherit;
12762 }
12763
12769 inline bool world_has_entity_term_direct(const World& world, Entity entity, Entity term) {
12770 return world.has_direct(entity, term);
12771 }
12772
12777 inline bool world_is_exclusive_dont_fragment_relation(const World& world, Entity relation) {
12778 return world.is_exclusive_dont_fragment_relation(relation);
12779 }
12780
12785 inline bool world_is_out_of_line_component(const World& world, Entity component) {
12786 return world.is_out_of_line_component(component);
12787 }
12788
12793 inline bool world_is_non_fragmenting_out_of_line_component(const World& world, Entity component) {
12794 return world.is_non_fragmenting_out_of_line_component(component);
12795 }
12796
12801 inline uint32_t world_count_direct_term_entities(const World& world, Entity term) {
12802 return world.count_direct_term_entities(term);
12803 }
12804
12809 inline uint32_t world_count_in_term_entities(const World& world, Entity term) {
12810 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
12811 return 0;
12812
12813 const auto target = world.get(term.gen());
12814 return world.valid(target) ? (uint32_t)world.as_relations_trav_cache(target).size() : 0U;
12815 }
12816
12821 inline uint32_t world_count_direct_term_entities_direct(const World& world, Entity term) {
12822 return world.count_direct_term_entities_direct(term);
12823 }
12824
12829 inline void world_collect_direct_term_entities(const World& world, Entity term, cnt::darray<Entity>& out) {
12830 world.collect_direct_term_entities(term, out);
12831 }
12832
12837 inline void world_collect_in_term_entities(const World& world, Entity term, cnt::darray<Entity>& out) {
12838 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
12839 return;
12840
12841 const auto target = world.get(term.gen());
12842 if (!world.valid(target))
12843 return;
12844
12845 const auto& relations = world.as_relations_trav_cache(target);
12846 out.reserve(out.size() + (uint32_t)relations.size());
12847 for (auto relation: relations)
12848 out.push_back(relation);
12849 }
12850
12855 inline void world_collect_direct_term_entities_direct(const World& world, Entity term, cnt::darray<Entity>& out) {
12856 world.collect_direct_term_entities_direct(term, out);
12857 }
12858
12865 inline bool
12866 world_for_each_direct_term_entity(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
12867 return world.for_each_direct_term_entity(term, ctx, func);
12868 }
12869
12876 inline bool world_for_each_in_term_entity(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
12877 if (!term.pair() || term.id() != Is.id() || is_wildcard(term.gen()))
12878 return true;
12879
12880 const auto target = world.get(term.gen());
12881 if (!world.valid(target))
12882 return true;
12883
12884 for (auto relation: world.as_relations_trav_cache(target)) {
12885 if (!func(ctx, relation))
12886 return false;
12887 }
12888
12889 return true;
12890 }
12891
12898 inline bool
12899 world_for_each_direct_term_entity_direct(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity)) {
12900 return world.for_each_direct_term_entity_direct(term, ctx, func);
12901 }
12902
12907 inline bool world_entity_enabled(const World& world, Entity entity) {
12908 return world.enabled(entity);
12909 }
12910
12915 inline Entity world_pair_target_if_alive(const World& world, Entity pair) {
12916 return world.pair_target_if_alive(pair);
12917 }
12918
12924 inline bool world_entity_enabled_hierarchy(const World& world, Entity entity, Entity relation) {
12925 return world.enabled_hierarchy(entity, relation);
12926 }
12927
12931 inline uint32_t world_enabled_hierarchy_version(const World& world) {
12932 return world.enabled_hierarchy_version();
12933 }
12934
12939 inline bool world_is_hierarchy_relation(const World& world, Entity relation) {
12940 return world.is_hierarchy_relation(relation);
12941 }
12942
12947 inline bool world_is_fragmenting_relation(const World& world, Entity relation) {
12948 return world.is_fragmenting_relation(relation);
12949 }
12950
12955 inline bool world_is_fragmenting_hierarchy_relation(const World& world, Entity relation) {
12956 return world.is_fragmenting_hierarchy_relation(relation);
12957 }
12958
12963 inline bool world_supports_depth_order(const World& world, Entity relation) {
12964 return world.supports_depth_order(relation);
12965 }
12966
12971 inline bool world_depth_order_prunes_disabled_subtrees(const World& world, Entity relation) {
12972 return world.depth_order_prunes_disabled_subtrees(relation);
12973 }
12974
12979 inline bool world_entity_prefab(const World& world, Entity entity) {
12980 const auto& ec = world.fetch(entity);
12981 return ec.pArchetype != nullptr && ec.pArchetype->has(Prefab);
12982 }
12983
12989 inline Entity world_query_first_inherited_owner(const World& world, const Archetype& archetype, Entity term) {
12990 const auto& chunks = archetype.chunks();
12991 const Chunk* pFirstChunk = nullptr;
12992 for (const auto* pChunk: chunks) {
12993 if (pChunk == nullptr || pChunk->size() == 0)
12994 continue;
12995 pFirstChunk = pChunk;
12996 break;
12997 }
12998
12999 if (pFirstChunk == nullptr)
13000 return EntityBad;
13001
13002 const auto firstEntity = pFirstChunk->entity_view()[0];
13003 for (const auto target: world.as_targets_trav_cache(firstEntity)) {
13004 if (!world.has_direct(target, term))
13005 continue;
13006 return target;
13007 }
13008
13009 return EntityBad;
13010 }
13011
13016 inline const Archetype* world_entity_archetype(const World& world, Entity entity) {
13017 return world.fetch(entity).pArchetype;
13018 }
13019
13024 inline uint32_t world_component_index_bucket_size(const World& world, Entity term) {
13025 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
13026 if (it == world.m_entityToArchetypeMap.end())
13027 return 0;
13028
13029 return (uint32_t)it->second.size();
13030 }
13031
13037 inline uint32_t world_component_index_comp_idx(const World& world, const Archetype& archetype, Entity term) {
13038 if (is_wildcard(term))
13039 return BadIndex;
13040
13041 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
13042 if (it == world.m_entityToArchetypeMap.end())
13043 return BadIndex;
13044
13045 const auto idx = core::get_index_if(it->second, [&](const auto& entry) {
13046 return entry.matches(&archetype);
13047 });
13048 if (idx == BadIndex)
13049 return BadIndex;
13050
13051 return it->second[idx].compIdx;
13052 }
13053
13059 inline uint32_t world_component_index_match_count(const World& world, const Archetype& archetype, Entity term) {
13060 const auto it = world.m_entityToArchetypeMap.find(EntityLookupKey(term));
13061 if (it == world.m_entityToArchetypeMap.end())
13062 return 0;
13063
13064 const auto idx = core::get_index_if(it->second, [&](const auto& entry) {
13065 return entry.matches(&archetype);
13066 });
13067 if (idx == BadIndex)
13068 return 0;
13069
13070 return it->second[idx].matchCount;
13071 }
13072
13079 template <typename T>
13080 inline const std::remove_cv_t<std::remove_reference_t<T>>*
13081 world_query_inherited_arg_data_const(World& world, Entity owner, Entity id) {
13082 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13083 return &world.template get<Arg>(owner, id);
13084 }
13085
13091 inline const void* world_query_inherited_arg_data_const_ptr(const World& world, Entity owner, Entity id) {
13092 const auto& ec = world.fetch(owner);
13093 const auto row = id.kind() == EntityKind::EK_Gen ? ec.row : 0;
13094 return ec.pChunk->comp_ptr(ec.pChunk->comp_idx(id), row);
13095 }
13096
13102 template <typename T>
13103 inline decltype(auto) world_direct_entity_arg(World& world, Entity entity) {
13104 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13105 if constexpr (std::is_same_v<Arg, Entity>)
13106 return entity;
13107 else if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>)
13108 return world.template mut_im<Arg>(entity);
13109 else
13110 return world.template get<Arg>(entity);
13111 }
13112
13118 template <typename T>
13119 inline decltype(auto) world_direct_entity_arg_raw(World& world, Entity entity) {
13120 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13121 if constexpr (std::is_same_v<Arg, Entity>)
13122 return entity;
13123 else if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>)
13124 return world.template mut<Arg>(entity);
13125 else
13126 return world.template get<Arg>(entity);
13127 }
13128
13133 template <typename T>
13134 inline Entity world_query_arg_id(World& world) {
13135 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13136 using FT = typename component_type_t<Arg>::TypeFull;
13137 if constexpr (is_pair<FT>::value) {
13138 const auto rel = comp_cache(world).template get<typename FT::rel>().entity;
13139 const auto tgt = comp_cache(world).template get<typename FT::tgt>().entity;
13140 return (Entity)Pair(rel, tgt);
13141 } else
13142 return comp_cache(world).template get<FT>().entity;
13143 }
13144
13150 template <typename T>
13151 inline decltype(auto) world_query_entity_arg(World& world, Entity entity) {
13152 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13153 if constexpr (std::is_same_v<Arg, Entity>)
13154 return entity;
13155 else {
13156 const auto id = world_query_arg_id<Arg>(world);
13157 return world_query_entity_arg_by_id<T>(world, entity, id);
13158 }
13159 }
13160
13168 template <typename T>
13169 inline decltype(auto) world_query_entity_arg_by_id(World& world, Entity entity, Entity id) {
13170 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13171 if constexpr (std::is_same_v<Arg, Entity>)
13172 return entity;
13173 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
13174 if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>) {
13175 if (!world.has_direct(entity, termId)) {
13176 if constexpr (is_pair<Arg>::value)
13177 (void)world.override(entity, termId);
13178 else
13179 (void)world.template override<Arg>(entity, termId);
13180 }
13181
13182 return world.template mut_im<Arg>(entity, termId);
13183 } else
13184 return world.template get<Arg>(entity, termId);
13185 }
13186
13194 template <typename T>
13195 inline decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id) {
13196 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13197 if constexpr (std::is_same_v<Arg, Entity>)
13198 return entity;
13199
13200 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
13201 if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>) {
13202 if (!world.has_direct(entity, termId)) {
13203 if constexpr (is_pair<Arg>::value)
13204 (void)world.override(entity, termId);
13205 else
13206 (void)world.template override<Arg>(entity, termId);
13207 }
13208
13209 return world.template mut<Arg>(entity, termId);
13210 } else
13211 return world.template get<Arg>(entity, termId);
13212 }
13213
13223 template <typename T>
13224 inline void world_init_query_entity_arg_by_id_chunk_stable_const(
13225 World& world, const Chunk& chunk, const Entity* pEntities, Entity id, bool& direct, uint32_t& compIdx,
13226 const std::remove_cv_t<std::remove_reference_t<T>>*& pDataInherited) {
13227 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13228 if constexpr (std::is_same_v<Arg, Entity>) {
13229 direct = false;
13230 compIdx = BadIndex;
13231 pDataInherited = nullptr;
13232 return;
13233 }
13234
13235 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
13236 direct = chunk.has(termId);
13237 compIdx = BadIndex;
13238 pDataInherited = nullptr;
13239
13240 if (direct) {
13241 compIdx = chunk.comp_idx(termId);
13242 GAIA_ASSERT(compIdx != BadIndex);
13243 return;
13244 }
13245
13246 auto owner = EntityBad;
13247 const auto firstEntity = pEntities[0];
13248 for (const auto target: world.as_targets_trav_cache(firstEntity)) {
13249 if (!world.has_direct(target, termId))
13250 continue;
13251
13252 owner = target;
13253 break;
13254 }
13255
13256 GAIA_ASSERT(owner != EntityBad);
13257 pDataInherited = &world.template get<Arg>(owner, termId);
13258 }
13259
13269 template <typename T>
13270 inline decltype(auto) world_query_entity_arg_by_id_cached_const(
13271 World& world, Entity entity, Entity id, const Archetype*& pLastArchetype, Entity& cachedOwner,
13272 bool& cachedDirect) {
13273 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
13274 if constexpr (std::is_same_v<Arg, Entity>)
13275 return entity;
13276
13277 const auto termId = id != EntityBad ? id : world_query_arg_id<Arg>(world);
13278 const auto& ec = world.fetch(entity);
13279 if (ec.pArchetype != pLastArchetype) {
13280 pLastArchetype = ec.pArchetype;
13281 cachedDirect = ec.pArchetype->has(termId);
13282 cachedOwner = EntityBad;
13283
13284 if (!cachedDirect) {
13285 for (const auto target: world.as_targets_trav_cache(entity)) {
13286 if (!world.has_direct(target, termId))
13287 continue;
13288
13289 cachedOwner = target;
13290 break;
13291 }
13292
13293 GAIA_ASSERT(cachedOwner != EntityBad);
13294 }
13295 }
13296
13297 if (cachedDirect)
13298 return ComponentGetter{world, ec.pChunk, entity, ec.row}.template get<Arg>(termId);
13299
13300 return world.template get<Arg>(cachedOwner, termId);
13301 }
13302
13309 inline void world_notify_on_set(World& world, Entity term, Chunk& chunk, uint16_t from, uint16_t to) {
13310#if GAIA_OBSERVERS_ENABLED
13311 if (world.tearing_down())
13312 return;
13313 if (!world.observers().has_on_set_observers(term))
13314 return;
13315
13316 auto entities = chunk.entity_view();
13317 if (from >= entities.size())
13318 return;
13319 if (to > entities.size())
13320 to = (uint16_t)entities.size();
13321 if (from >= to)
13322 return;
13323
13324 world.observers().on_set(world, term, EntitySpan{entities.data() + from, uint32_t(to - from)});
13325#else
13326 (void)world;
13327 (void)term;
13328 (void)chunk;
13329 (void)from;
13330 (void)to;
13331#endif
13332 }
13333
13334 //----------------------------------------------------------------------
13335
13336 template <typename T>
13337 GAIA_NODISCARD decltype(auto) ComponentGetter::get(Entity type) const {
13338 GAIA_ASSERT(m_pWorld != nullptr);
13339 GAIA_ASSERT(m_entity != EntityBad);
13340
13341 using FT = typename component_type_t<T>::TypeFull;
13342 if constexpr (World::template supports_out_of_line_component<FT>()) {
13343 if (m_pWorld->template can_use_out_of_line_component<FT>(type)) {
13344 const auto* pStore = m_pWorld->template sparse_component_store<FT>(type);
13345 GAIA_ASSERT(pStore != nullptr);
13346 GAIA_ASSERT(pStore->has(m_entity));
13347 return pStore->get(m_entity);
13348 }
13349 }
13350
13351 return m_pChunk->template get<T>(m_row, type);
13352 }
13353
13354 template <typename T>
13355 decltype(auto) ComponentSetter::mut(Entity type) {
13356 return smut<T>(type);
13357 }
13358
13359 template <typename T>
13360 decltype(auto) ComponentSetter::smut(Entity type) {
13361 GAIA_ASSERT(m_pWorld != nullptr);
13362 GAIA_ASSERT(m_entity != EntityBad);
13363
13364 using FT = typename component_type_t<T>::TypeFull;
13365 if constexpr (World::template supports_out_of_line_component<FT>()) {
13366 auto& world = *const_cast<World*>(m_pWorld);
13367 if (world.template can_use_out_of_line_component<FT>(type))
13368 return world.template sparse_component_store_mut<FT>(type).mut(m_entity);
13369 }
13370
13371 return const_cast<Chunk*>(m_pChunk)->template sset<T>(m_row, type);
13372 }
13373
13374 template <typename T>
13375 ComponentSetter& ComponentSetter::sset(Entity type, T&& value) {
13376 smut<T>(type) = GAIA_FWD(value);
13377 return *this;
13378 }
13379
13380 template <typename T>
13381 ComponentSetter& ComponentSetter::set(Entity type, T&& value) {
13382 GAIA_ASSERT(m_pWorld != nullptr);
13383 GAIA_ASSERT(m_entity != EntityBad);
13384
13385 smut<T>(type) = GAIA_FWD(value);
13386 using FT = typename component_type_t<T>::TypeFull;
13387 auto& world = *const_cast<World*>(m_pWorld);
13388
13389 if constexpr (World::template supports_out_of_line_component<FT>()) {
13390 if (world.template can_use_out_of_line_component<FT>(type))
13391 ::gaia::ecs::update_version(world.m_worldVersion);
13392 }
13393
13394 world.finish_write(m_entity, type);
13395 return *this;
13396 }
13397
13398 //----------------------------------------------------------------------
13399
13404 inline void world_notify_on_set_entity(World& world, Entity term, Entity entity) {
13405#if GAIA_OBSERVERS_ENABLED
13406 if (world.tearing_down())
13407 return;
13408 if (!world.valid(entity))
13409 return;
13410 if (!world.observers().has_on_set_observers(term))
13411 return;
13412
13413 world.observers().on_set(world, term, EntitySpan{&entity, 1});
13414#else
13415 (void)world;
13416 (void)term;
13417 (void)entity;
13418#endif
13419 }
13420
13425 inline void world_finish_write(World& world, Entity term, Entity entity) {
13426 world.finish_write(entity, term);
13427 }
13428
13429 } // namespace ecs
13430} // namespace gaia
13431
13432#if GAIA_OBSERVERS_ENABLED
13433namespace gaia {
13434 namespace ecs {
13435 inline uint32_t world_rel_version(const World& world, Entity relation) {
13436 return world.rel_version(relation);
13437 }
13438
13442 inline uint32_t world_entity_archetype_version(const World& world, Entity entity) {
13443 if (!world.valid(entity))
13444 return 0;
13445
13446 const auto key = EntityLookupKey(entity);
13447 auto it = world.m_srcEntityVersions.find(key);
13448 if (it != world.m_srcEntityVersions.end())
13449 return it->second;
13450
13451 it = world.m_srcEntityVersions.try_emplace(key, 1).first;
13452 return it->second;
13453 }
13454
13455 inline ObserverBuilder World::observer() {
13456 // Create the observer
13457 auto e = add();
13458 EntityBuilder(*this, e) //
13459 .add<Observer_>();
13460
13461 auto ss = acc_mut(e);
13462 auto& hdr = ss.smut<Observer_>();
13463 auto& obs = observers().data_add(e);
13464 {
13465 hdr.entity = e;
13466 obs.entity = e;
13467 obs.query = query();
13468 }
13469 return ObserverBuilder(*this, e);
13470 }
13471 } // namespace ecs
13472} // namespace gaia
13473#endif
Array of elements of type.
Definition darray_ext_impl.h:28
Array with variable size of elements of type.
Definition darray_impl.h:25
Array of elements of type.
Definition sarray_ext_impl.h:27
Definition span_impl.h:99
Definition archetype.h:83
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:837
GAIA_NODISCARD bool is_req_del() const
Returns true if this archetype is requested to be deleted.
Definition archetype.h:1156
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:334
bool enabled(uint16_t row) const
Checks if the entity is enabled.
Definition chunk.h:1353
GAIA_NODISCARD uint32_t comp_idx(Entity entity) const
Returns the internal index of a component based on the provided entity.
Definition chunk.h:1688
GAIA_NODISCARD bool has(Entity entity) const
Checks if a component/entity entity is present in the chunk.
Definition chunk.h:1441
Cache for compile-time defined components.
Definition component_cache.h:25
GAIA_NODISCARD const ComponentCacheItem * find(detail::ComponentDescId compDescId) const noexcept
Searches for the component cache item given the compDescId.
Definition component_cache.h:355
GAIA_NODISCARD GAIA_FORCEINLINE const ComponentCacheItem & add(Entity entity, util::str_view scopePath={})
Registers the component item for.
Definition component_cache.h:236
GAIA_NODISCARD const ComponentCacheItem & get(detail::ComponentDescId compDescId) const noexcept
Returns the component cache item given the compDescId.
Definition component_cache.h:373
Definition world.h:88
GAIA_NODISCARD uint32_t & world_version()
Returns the current version of the world.
Definition world.h:8481
void as(Entity entity, Entity entityBase)
Shortcut for add(entity, Pair(Is, entityBase)
Definition world.h:6124
GAIA_NODISCARD const EntityContainer & fetch(Entity entity) const
Returns the internal record for entity.
Definition world.h:2293
void invalidate_queries_for_structural_entity(EntityLookupKey entityKey)
Invalidates cached queries structurally affected by entityKey.
Definition world.h:12133
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:8155
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:2403
GAIA_NODISCARD uint32_t count_direct_term_entities_direct(Entity term) const
Counts entities directly matching term without semantic Is expansion.
Definition world.h:8112
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:7675
void scope(Entity scopeEntity, Func &&func)
Executes func with a temporary component scope and restores the previous scope afterwards....
Definition world.h:6675
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:2415
void defrag_entities_per_tick(uint32_t value)
Sets the maximum number of entities defragmented per world tick.
Definition world.h:8613
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:8522
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:5952
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:7330
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:7403
GAIA_NODISCARD const cnt::set< EntityLookupKey > * targets(Entity relation) const
Returns targets for relation.
Definition world.h:7536
void update_src_entity_version(Entity entity)
Updates a tracked source-entity version after the entity changes archetype membership.
Definition world.h:8504
bool alias(Entity entity, const char *alias, uint32_t len=0)
Assigns an alias name to an entity.
Definition world.h:4128
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:2449
GAIA_NODISCARD bool children_bfs_if(Entity root, Func func) const
Traverses descendants in the ChildOf hierarchy in breadth-first order.
Definition world.h:8270
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:7260
ser::serializer get_serializer() const
Returns the currently bound runtime serializer handle.
Definition world.h:2864
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:7470
EntityBuilder build(Entity entity)
Starts a bulk add/remove operation on entity.
Definition world.h:4403
GAIA_NODISCARD Entity prefab(EntityKind kind=EntityKind::EK_Gen)
Creates a new prefab entity.
Definition world.h:4417
GAIA_NODISCARD util::str_view symbol(Entity component) const
Returns the registered symbol name for a component entity.
Definition world.h:4057
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:6637
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:6385
Query uquery()
Provides an uncached query set up to work with the parent world. Uncached queries keep only a local i...
Definition world.h:2264
void enable(Entity entity, bool enable)
Enables or disables an entire entity.
Definition world.h:8362
GAIA_NODISCARD const ComponentCache & comp_cache() const
Returns read-only access to the world component cache.
Definition world.h:4036
void children_bfs(Entity root, Func func) const
Traverses descendants in the ChildOf hierarchy in breadth-first order.
Definition world.h:8260
void children(Entity parent, Func func) const
Visits direct children in the ChildOf hierarchy.
Definition world.h:8243
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:12219
void child(Entity entity, Entity parent)
Shortcut for add(entity, Pair(ChildOf, parent)
Definition world.h:6161
GAIA_NODISCARD Entity symbol(const char *symbol, uint32_t len=0) const
Finds a component entity by its exact registered symbol.
Definition world.h:4046
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:6036
void set_serializer(ser::serializer serializer)
Binds a pre-built runtime serializer handle.
Definition world.h:2850
void clear(Entity entity)
Removes any component or entity attached to entity.
Definition world.h:4757
GAIA_NODISCARD bool override(Entity entity, Entity object)
Materializes an inherited id as directly owned storage on entity.
Definition world.h:4674
GAIA_NODISCARD const ComponentCacheItem & add()
Creates a new component if not found already.
Definition world.h:4450
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:4147
void add(Entity entity, Entity object)
Attaches entity object to entity entity.
Definition world.h:4521
GAIA_NODISCARD util::str_view name(EntityId entityId) const
Returns the entity name assigned to entityId.
Definition world.h:6846
GAIA_NODISCARD const cnt::set< EntityLookupKey > * relations(Entity target) const
Returns relations for target.
Definition world.h:7050
Entity scope(Entity scope)
Sets the current component scope used for component registration and relative component lookup....
Definition world.h:6659
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:6448
GAIA_NODISCARD Entity get() const
Returns the entity registered for component type T.
Definition world.h:4381
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:2481
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:6343
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:2339
GAIA_NODISCARD bool enabled(const EntityContainer &ec) const
Checks if an entity is enabled.
Definition world.h:8385
void add(Entity entity, U &&value)
Attaches a new component T to entity. Also sets its value.
Definition world.h:4635
void invalidate_sorted_queries_for_entity(Entity entity)
Invalidates cached sorted queries whose row ordering depends on entity.
Definition world.h:12145
GAIA_NODISCARD bool is(Entity entity, Entity entityBase) const
Checks if entity inherits from entityBase.
Definition world.h:6133
void sources(Entity relation, Entity target, Func func) const
Returns relationship sources for the relation and target.
Definition world.h:7694
void del_sparse_component_store(Entity component)
Deletes the sparse out-of-line component store associated with component.
Definition world.h:2561
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:8145
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:6196
void add(Entity entity)
Attaches a new component T to entity.
Definition world.h:4548
GAIA_NODISCARD EntityContainer & fetch(Entity entity)
Returns the internal record for entity.
Definition world.h:2275
void del(Entity entity)
Removes an entity along with all data associated with it.
Definition world.h:6048
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:6820
void del(Entity entity)
Removes a component T from entity.
Definition world.h:6105
void remove_src_entity_version(Entity entity)
Removes sparse source-version state for an entity that is being destroyed.
Definition world.h:8514
GAIA_NODISCARD ExclusiveAdjunctStore & exclusive_adjunct_store_mut(Entity relation)
Returns the exclusive adjunct store for relation, creating it if needed.
Definition world.h:2585
GAIA_NODISCARD bool locked() const
Checks if the chunk is locked for structural changes.
Definition world.h:8864
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:7186
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:2350
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:7285
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.
void invalidate_queries_for_entity(Pair is_pair)
Invalidates semantic Is queries affected by removing or changing is_pair.
Definition world.h:12156
GAIA_NODISCARD const ComponentCacheItem & reg_comp()
Returns the registered component cache item for T, auto-registering it when enabled.
Definition world.h:4389
void invalidate_queries_for_rel(Entity relation)
Invalidates cached queries whose dynamic result depends on relation.
Definition world.h:12139
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:5913
GAIA_NODISCARD Entity get(EntityId id) const
Returns the entity located at the index id.
Definition world.h:4362
void update()
Performs various internal operations related to the end of the frame such as memory cleanup and other...
Definition world.h:8540
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:6881
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:6803
void add(Entity entity, Entity object, T &&value)
Attaches object to entity. Also sets its value.
Definition world.h:4582
ComponentGetter acc(Entity entity) const
Starts a bulk get operation on an entity.
Definition world.h:6397
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:7425
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:2388
GAIA_NODISCARD bool enabled(Entity entity) const
Checks if an entity is enabled.
Definition world.h:8398
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:7223
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:2373
void parent(Entity entity, Entity parentEntity)
Shortcut for add(entity, Pair(Parent, parent))
Definition world.h:6172
void diag_components() const
Performs diagnostics on registered components. Prints basic info about them and reports and detected ...
Definition world.h:8628
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:8119
void targets_if(Entity entity, Entity relation, Func func) const
Returns the relationship targets for the relation entity on entity.
Definition world.h:7636
GAIA_NODISCARD bool valid(Entity entity) const
Checks if entity is valid.
Definition world.h:4351
CommandBufferST & cmd_buffer_st() const
Returns the single-threaded deferred command buffer owned by the world.
Definition world.h:8312
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:5945
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity is currently used by the world.
Definition world.h:6477
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:2828
GAIA_NODISCARD bool is_dont_fragment_relation(Entity relation) const
Returns whether relation is a valid non-fragmenting relation entity.
Definition world.h:2331
bool path(Entity component, const char *path, uint32_t len=0)
Assigns a scoped path name to a component entity.
Definition world.h:4087
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:7382
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:6254
GAIA_NODISCARD Entity copy(Entity srcEntity)
Creates a new entity by cloning an already existing one. Does not trigger observers.
Definition world.h:4780
void diag() const
Performs all diagnostics.
Definition world.h:8657
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:8444
GAIA_NODISCARD bool has(Entity entity, Pair pair) const
Checks if entity contains pair.
Definition world.h:6744
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:6694
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:2513
GAIA_NODISCARD Entity add(EntityKind kind=EntityKind::EK_Gen)
Creates a new empty entity.
Definition world.h:4410
GAIA_NODISCARD bool has(Pair pair) const
Checks if pair is currently used by the world.
Definition world.h:6532
void save()
Saves contents of the world to a buffer. The buffer is reset, not appended. NOTE: In order for custom...
Definition world.h:9038
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:6630
GAIA_NODISCARD decltype(auto) get(Entity entity) const
Returns the value stored in the component T on entity.
Definition world.h:6412
void add(Entity entity, Pair pair)
Attaches a relationship pair to entity.
Definition world.h:4538
GAIA_NODISCARD uint32_t count_direct_term_entities(Entity term) const
Counts entities directly matching term, including semantic Is inheritance expansion.
Definition world.h:8105
void add_n(uint32_t count, Func func=func_void_with_entity)
Creates count new empty entities.
Definition world.h:4427
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:8495
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:8458
GAIA_NODISCARD const ComponentCacheItem & add(const char *name, uint32_t size, DataStorageType storageType, uint32_t alig=1, uint32_t soa=0, const uint8_t *pSoaSizes=nullptr, ComponentLookupHash hashLookup={}, EntityKind kind=EntityKind::EK_Gen)
Creates a new runtime component if not found already.
Definition world.h:4487
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:2435
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:8135
void del(Entity entity, Entity object)
Removes an object from entity if possible.
Definition world.h:6062
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:2380
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:6550
void diag_entities() const
Performs diagnostics on entities of the world. Also performs validation of internal structures which ...
Definition world.h:8634
GAIA_NODISCARD bool is_dont_fragment(Entity entity) const
Returns whether entity is marked DontFragment.
Definition world.h:2324
GAIA_NODISCARD util::str_view path(Entity component) const
Returns the scoped path name for a component entity.
Definition world.h:4077
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:6377
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:5940
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:6860
GAIA_NODISCARD Entity path(const char *path, uint32_t len=0) const
Finds a component entity by its exact scoped path.
Definition world.h:4066
GAIA_NODISCARD const ExclusiveAdjunctStore * exclusive_adjunct_store(Entity relation) const
Returns the exclusive adjunct store for relation, or nullptr when absent.
Definition world.h:2574
void diag_archetypes() const
Performs diagnostics on archetypes. Prints basic info about them and the chunks they contain.
Definition world.h:8620
GAIA_NODISCARD bool child(Entity entity, Entity parent) const
Checks if.
Definition world.h:6167
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:6143
void children_if(Entity parent, Func func) const
Visits direct children in the ChildOf hierarchy until func returns false.
Definition world.h:8252
GAIA_NODISCARD bool has_direct(Entity entity, Pair pair) const
Checks if entity directly contains pair, without semantic inheritance expansion.
Definition world.h:6555
void relations_if(Entity entity, Entity target, Func func) const
Returns the relationship relations for the target entity on entity.
Definition world.h:7146
void del_sparse_components(Entity entity)
Removes all sparse out-of-line component instances owned by entity.
Definition world.h:2552
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:4822
GAIA_NODISCARD util::str_view name(Entity entity) const
Returns the name assigned to entity.
Definition world.h:6828
void sources_if(Entity relation, Entity target, Func func) const
Returns relationship sources for the relation and target.
Definition world.h:7751
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:6310
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:2359
bool as_targets_trav_if(Entity relation, Func func) const
Traverses transitive Is targets of relation until func returns true.
Definition world.h:8295
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:8279
GAIA_NODISCARD uint32_t rel_version(Entity relation) const
Returns structural version for a given relation. Increments whenever any Pair(relation,...
Definition world.h:8487
Query query()
Provides a cached query set up to work with the parent world. Cached queries use local scope by defau...
Definition world.h:2254
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:9083
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:7503
void teardown()
Performs world shutdown maintenance without running systems or observers. Runtime callbacks are shut ...
Definition world.h:8558
void del(Entity entity, Pair pair)
Removes an existing entity relationship pair.
Definition world.h:6095
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:2314
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:12181
GAIA_NODISCARD Entity scope() const
Returns the current component scope used for component registration and relative component lookup.
Definition world.h:6651
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:6330
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:6357
GAIA_NODISCARD Chunk * get_chunk(Entity entity) const
Returns a chunk containing the entity.
Definition world.h:8433
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:2539
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:8199
GAIA_NODISCARD bool parent(Entity entity, Entity parentEntity) const
Checks if.
Definition world.h:6177
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:2491
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:6942
GAIA_NODISCARD util::str_view alias(Entity entity) const
Returns the alias assigned to an entity.
Definition world.h:4109
void targets(Entity entity, Entity relation, Func func) const
Returns the relationship targets for the relation entity on entity.
Definition world.h:7593
GAIA_NODISCARD bool has(Entity entity, Entity object) const
Checks if entity contains the entity object.
Definition world.h:6542
void set_serializer(TSerializer &serializer)
Binds a concrete serializer object through ser::make_serializer().
Definition world.h:2858
GAIA_NODISCARD uint32_t size() const
Returns the number of active entities.
Definition world.h:8453
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity contains the component T.
Definition world.h:6755
void set_serializer(std::nullptr_t)
Resets runtime serializer binding to the default internal bin_stream backend.
Definition world.h:2843
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:8126
GAIA_NODISCARD Entity instantiate(Entity prefabEntity)
Instantiates a prefab as a normal entity. The instance copies the prefab's direct data,...
Definition world.h:5899
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:4165
GAIA_NODISCARD ComponentSetter acc_mut(Entity entity)
Starts a bulk set operation on entity.
Definition world.h:6292
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:4437
GAIA_NODISCARD Entity relation(Entity entity, Entity target) const
Returns the first relationship relation for the target entity on entity.
Definition world.h:7063
GAIA_NODISCARD Entity alias(const char *alias, uint32_t len=0) const
Finds an entity by its exact alias.
Definition world.h:4096
QuerySerBuffer & query_buffer(QueryId &serId)
Returns the temporary serialization buffer used while building a query. A fresh query id is allocated...
Definition world.h:12088
void cleanup()
Clears the world so that all its entities and components are released.
Definition world.h:8597
GAIA_NODISCARD Entity target(Entity entity, Entity relation) const
Returns the first relationship target for the relation entity on entity.
Definition world.h:7549
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:2526
GAIA_NODISCARD ComponentCache & comp_cache_mut()
Returns mutable access to the world component cache.
Definition world.h:4030
void relations(Entity entity, Entity target, Func func) const
Returns the relationship relations for the target entity on entity.
Definition world.h:7104
void query_buffer_reset(QueryId &serId)
Releases the temporary serialization buffer associated with serId.
Definition world.h:12122
void invalidate_sorted_queries()
Invalidates all cached sorted queries after row-order changes.
Definition world.h:12150
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:8407
bool load(TSerializer &inputSerializer)
Loads a world state from a serializer-compatible stream wrapper.
Definition world.h:9374
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:8870
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:2365
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:7518
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:4373
CommandBufferMT & cmd_buffer_mt() const
Returns the multi-thread-safe deferred command buffer owned by the world.
Definition world.h:8318
Buffer for deferred execution of some operations on entities.
Definition command_buffer.h:45
Definition query.h:459
Wrapper for two Entities forming a relationship pair.
Definition id.h:500
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:218
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
Definition robin_hood.h:720
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition sparse_storage.h:31
Definition hashing_string.h:12
Definition component_cache_item.h:25
Entity entity
Component entity.
Definition component_cache_item.h:75
Component comp
Unique component identifier.
Definition component_cache_item.h:77
SymbolLookupKey name
Component name.
Definition component_cache_item.h:84
util::str path
User-facing path name, e.g. "Gameplay.Device".
Definition component_cache_item.h:86
Entity-scoped component accessor bound to a specific world, chunk and row. It is not a standalone chu...
Definition component_getter.h:15
Entity-scoped mutable component accessor bound to a specific world, chunk and row....
Definition component_setter.h:14
Definition id.h:34
uint32_t dis
Disabled Entity does not use this bit (always zero) so we steal it for special purposes....
Definition entity_container.h:82
Definition entity_container.h:59
uint16_t row
Row at which the entity is stored in the chunk.
Definition entity_container.h:93
uint16_t flags
Flags.
Definition entity_container.h:95
Archetype * pArchetype
Archetype (stable address)
Definition entity_container.h:111
Chunk * pChunk
Chunk the entity currently resides in (stable address)
Definition entity_container.h:113
Component used to describe the entity name.
Definition id.h:487
Hashmap lookup structure used for Entity.
Definition id.h:439
Definition id.h:241
Definition query_info.h:38
Definition world.h:2870
EntityBuilder & as(Entity entityBase)
Shortcut for add(Pair(Is, entityBase)). Effectively makes an entity inherit from entityBase.
Definition world.h:3181
void commit()
Commits all gathered changes and performs an archetype movement.
Definition world.h:2930
void del_name()
Removes any name associated with the entity.
Definition world.h:3091
EntityBuilder & child(Entity parent)
Shortcut for add(Pair(ChildOf, parent))
Definition world.h:3199
Archetype * m_pArchetype
Target archetype we want to move to.
Definition world.h:2881
GAIA_NODISCARD bool as(Entity entity, Entity entityBase) const
Check if entity inherits from entityBase.
Definition world.h:3194
EntityBuilder & add(Pair pair)
Prepares an archetype movement by following the "add" edge of the current archetype.
Definition world.h:3168
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:3070
EntityBuilder & add(Entity entity)
Prepares an archetype movement by following the "add" edge of the current archetype.
Definition world.h:3157
EntityBuilder & del(Pair pair)
Prepares an archetype movement by following the "del" edge of the current archetype.
Definition world.h:3236
Entity register_component()
Takes care of registering the component.
Definition world.h:3205
EntityBuilder & del(Entity entity)
Prepares an archetype movement by following the "del" edge of the current archetype.
Definition world.h:3226
Entity m_entity
Source entity.
Definition world.h:2887
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:3078
EntityNameLookupKey m_targetAliasKey
Target alias string pointer.
Definition world.h:2885
EntityBuilder & prefab()
Marks the entity as a prefab.
Definition world.h:3186
EntityNameLookupKey m_targetNameKey
Target name.
Definition world.h:2883
void del_alias()
Removes any alias associated with the entity.
Definition world.h:3124
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:3055
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:3086
Definition id.h:233
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
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