Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
query_info.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include "gaia/cnt/darray.h"
5#include "gaia/cnt/ilist.h"
6#include "gaia/config/profiler.h"
7#include "gaia/core/hashing_policy.h"
8#include "gaia/core/utility.h"
9#include "gaia/ecs/api.h"
10#include "gaia/ecs/archetype.h"
11#include "gaia/ecs/archetype_common.h"
12#include "gaia/ecs/component.h"
13#include "gaia/ecs/component_cache.h"
14#include "gaia/ecs/id.h"
15#include "gaia/ecs/query_common.h"
16#include "gaia/ecs/query_match_stamps.h"
17#include "gaia/ecs/vm.h"
18#include "gaia/mem/mem_utils.h"
19
20namespace gaia {
21 namespace ecs {
22 struct Entity;
23 class World;
24
25 uint32_t world_version(const World& world);
26 Entity world_query_first_inherited_owner(const World& world, const Archetype& archetype, Entity term);
27 const void* world_query_inherited_arg_data_const_ptr(const World& world, Entity owner, Entity id);
28
29 using EntityToArchetypeMap = cnt::map<EntityLookupKey, ComponentIndexEntryArray>;
31 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
32 };
33
35 const void* data[ChunkHeader::MAX_COMPONENTS];
36 };
37
47 uint32_t matchVersion = 0;
48
49 void clear_temporary_matches() {
50 matchesArr.clear();
52 matchVersion = 0;
53 }
54
60
61 void reset_stamps() {
63 matchVersion = 0;
64 }
65
66 GAIA_NODISCARD uint32_t next_match_version() {
68 if (matchVersion != 0)
69 return matchVersion;
70
71 reset_stamps();
72 matchVersion = 1;
73 return matchVersion;
74 }
75 };
76
77 GAIA_NODISCARD QueryMatchScratch& query_match_scratch_acquire(World& world);
78 void query_match_scratch_release(World& world, bool keepStamps);
79
81 QueryCtx* pQueryCtx;
82 const EntityToArchetypeMap* pEntityToArchetypeMap;
83 std::span<const Archetype*> allArchetypes;
84 };
85
86 class QueryInfo {
87 public:
90 uint32_t idx = 0;
92 uint32_t gen = 0;
93
95 enum class MatchArchetypeQueryRet : uint8_t { Fail, Ok, Skip };
96
97 private:
98 struct Instruction {
99 Entity id;
100 QueryOpKind op;
101 };
102
103 struct SortData {
104 Chunk* pChunk;
105 uint32_t archetypeIdx;
106 uint16_t startRow;
107 uint16_t count;
108 };
109
110 struct GroupData {
111 GroupId groupId;
112 uint32_t idxFirst;
113 uint32_t idxLast;
114 bool needsSorting;
115 };
116
117 struct SrcTravSnapshotItem {
118 Entity entity = EntityBad;
120 uint32_t sourceVersion = 0;
121
122 GAIA_NODISCARD bool operator==(const SrcTravSnapshotItem& other) const {
123 return entity == other.entity && sourceVersion == other.sourceVersion;
124 }
125
126 GAIA_NODISCARD bool operator!=(const SrcTravSnapshotItem& other) const {
127 return !operator==(other);
128 }
129 };
130
131 public:
132 enum class InvalidationKind : uint8_t {
134 Result,
136 Seed,
138 All
139 };
140
141 private:
142 struct QueryPlan {
143 QueryCtx ctx;
145 };
146
147 struct QueryState {
148 enum DirtyFlags : uint8_t { Clean = 0x00, Seed = 0x01, Result = 0x02, All = Seed | Result };
149
151 cnt::set<const Archetype*> seedArchetypeSet;
152 CArchetypeDArray seedArchetypeCache;
153
156 cnt::set<const Archetype*> archetypeSet;
158 CArchetypeDArray archetypeCache;
159
166 mutable GroupData selectedGroupData{};
168 mutable bool selectedGroupDataValid = false;
170 bool dataPending = false;
171
172 void clear() {
177 dataPending = false;
178 }
179
180 void clear_transient() {
181 archetypeGroupIds.clear();
182 archetypeGroupData.clear();
185 dataPending = false;
186 }
187 };
188
189 struct ExecPayload {
196 bool compIndicesPending = false;
200
201 void clear() {
204 compIndicesPending = false;
205 inheritedDataPending = false;
206 }
207
208 void clear_transient() {
209 archetypeCompIndices.clear();
211 compIndicesPending = false;
212 inheritedDataPending = false;
213 }
214 };
215
223 uint32_t sortVersion{};
225 uint32_t barrierRelVersion = UINT32_MAX;
227 uint32_t barrierEnabledVersion = UINT32_MAX;
228
229 void clear() {
232 sortVersion = 0;
233 barrierRelVersion = UINT32_MAX;
234 barrierEnabledVersion = UINT32_MAX;
235 }
236
237 void clear_transient() {
238 archetypeSortData.clear();
240 sortVersion = 0;
241 barrierRelVersion = UINT32_MAX;
242 barrierEnabledVersion = UINT32_MAX;
243 }
244 };
245
247 ExecPayload exec;
249 GroupedPayload grouped;
251 NonTrivialPayload nonTrivial;
252
254 ArchetypeId lastArchetypeId{};
258 cnt::sarray<uint32_t, MAX_ITEMS_IN_QUERY> directSrcEntityVersions;
260 cnt::darray<SrcTravSnapshotItem> srcTravSnapshot;
262 bool srcTravSnapshotOverflowed = false;
266 uint8_t varBindingMask = 0;
269 uint32_t resultCacheRevision = 1;
271 uint8_t dirtyFlags = DirtyFlags::All;
272
273 void clear_seed_cache() {
274 seedArchetypeSet = {};
275 seedArchetypeCache = {};
276 }
277
278 void clear_result_cache() {
279 archetypeSet = {};
280 archetypeCache = {};
281 exec.clear();
282 grouped.clear();
283 nonTrivial.clear();
284 }
285
286 void clear_transient_result_cache() {
287 archetypeCache.clear();
288 exec.clear_transient();
289 grouped.clear_transient();
290 nonTrivial.clear_transient();
291 }
292
293 void clear_cache() {
294 clear_seed_cache();
295 clear_result_cache();
296 }
297
298 void reset() {
299 clear_cache();
300 srcTravSnapshot.clear();
301 srcTravSnapshotOverflowed = false;
302 lastArchetypeId = 0;
303 dirtyFlags = DirtyFlags::All;
304 }
305
306 void invalidate_seed() {
307 dirtyFlags = (uint8_t)(dirtyFlags | DirtyFlags::Seed | DirtyFlags::Result);
308 }
309
310 void invalidate_result() {
311 dirtyFlags = (uint8_t)(dirtyFlags | DirtyFlags::Result);
312 }
313
314 void invalidate_all() {
315 dirtyFlags = DirtyFlags::All;
316 }
317
318 GAIA_NODISCARD bool seed_dirty() const {
319 return (dirtyFlags & DirtyFlags::Seed) != 0;
320 }
321
322 GAIA_NODISCARD bool result_dirty() const {
323 return (dirtyFlags & DirtyFlags::Result) != 0;
324 }
325
326 GAIA_NODISCARD bool needs_refresh() const {
327 return seed_dirty() || result_dirty();
328 }
329
330 void clear_dirty() {
331 dirtyFlags = DirtyFlags::Clean;
332 }
333 };
334
335 uint32_t m_refs = 0;
336
337 QueryPlan m_plan;
338 QueryState m_state;
339
340 enum QueryCmdType : uint8_t { ALL, OR, NOT };
341
342 void reset_matching_cache() {
343 if (!m_state.archetypeCache.empty())
344 mark_result_cache_membership_changed();
345
346 m_state.reset();
347
348 auto& ctxData = m_plan.ctx.data;
349 ctxData.lastMatchedArchetypeIdx_All = {};
350 ctxData.lastMatchedArchetypeIdx_Or = {};
351 ctxData.lastMatchedArchetypeIdx_Not = {};
352 }
353
354 void clear_result_cache() {
355 m_state.clear_result_cache();
356 }
357
358 void mark_result_cache_membership_changed() {
359 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
360 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
361 ++m_state.resultCacheRevision;
362 if (m_state.resultCacheRevision != 0)
363 return;
364
365 // Reserve 0 as the "never synced" value in QueryCache.
366 m_state.resultCacheRevision = 1;
367 }
368
370 GAIA_NODISCARD bool has_dyn_terms() const {
371 return m_plan.ctx.data.cachePolicy == QueryCtx::CachePolicy::Dynamic;
372 }
373
375 GAIA_NODISCARD bool can_reuse_dyn_cache() const {
376 if (!has_dyn_terms())
377 return false;
378
379 const auto& deps = m_plan.ctx.data.deps;
380 if (!deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms))
381 return true;
382
383 if (!deps.can_reuse_src_cache())
384 return false;
385
386 // Direct concrete-source reuse is automatic. Traversed source reuse still needs explicit snapshots.
387 if (!deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms))
388 return true;
389
390 return m_plan.ctx.data.cacheSrcTrav != 0;
391 }
392
394 GAIA_NODISCARD bool uses_direct_src_version_tracking() const {
395 const auto& deps = m_plan.ctx.data.deps;
396 return !deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms) && //
397 can_reuse_dyn_cache();
398 }
399
401 GAIA_NODISCARD bool uses_src_trav_snapshot() const {
402 const auto& deps = m_plan.ctx.data.deps;
403 return deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms) && //
404 can_reuse_dyn_cache();
405 }
406
408 GAIA_NODISCARD bool dyn_rel_versions_changed() const {
409 const auto relations = m_plan.ctx.data.deps.relations_view();
410 const auto cnt = (uint32_t)relations.size();
411 GAIA_FOR(cnt) {
412 if (m_state.relationVersions[i] != world_rel_version(*world(), relations[i]))
413 return true;
414 }
415
416 return false;
417 }
418
420 GAIA_NODISCARD bool dyn_var_bindings_changed(
421 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) const {
422 if (m_state.varBindingMask != runtimeVarBindingMask)
423 return true;
424
425 GAIA_FOR(MaxVarCnt) {
426 const auto bit = (uint8_t(1) << i);
427 if ((runtimeVarBindingMask & bit) == 0)
428 continue;
429
430 if (m_state.varBindings[i] != runtimeVarBindings[i])
431 return true;
432 }
433
434 return false;
435 }
436
438 GAIA_NODISCARD bool direct_src_versions_changed() const {
439 if (!uses_direct_src_version_tracking())
440 return false;
441
442 const auto& deps = m_plan.ctx.data.deps;
443 const auto sourceEntities = deps.src_entities_view();
444 const auto cnt = (uint32_t)sourceEntities.size();
445 GAIA_FOR(cnt) {
446 if (m_state.directSrcEntityVersions[i] != world_entity_archetype_version(*world(), sourceEntities[i]))
447 return true;
448 }
449
450 return false;
451 }
452
454 template <typename Func>
455 void each_reusable_src_entity(Func&& func) const {
456 const auto terms = m_plan.ctx.data.terms_view();
457 const auto cnt = (uint32_t)terms.size();
458 GAIA_FOR(cnt) {
459 const auto& term = terms[i];
460 if (term.src == EntityBad || is_variable(term.src))
461 continue;
462
463 (void)vm::detail::each_lookup_src(*world(), term, term.src, [&](Entity source) {
464 return func(source);
465 });
466 }
467 }
468
470 GAIA_NODISCARD static cnt::darray<SrcTravSnapshotItem>& src_trav_snapshot_scratch() {
471 static thread_local cnt::darray<SrcTravSnapshotItem> scratch;
472 return scratch;
473 }
474
477 GAIA_NODISCARD bool build_src_trav_snapshot(cnt::darray<SrcTravSnapshotItem>& items) const {
478 const auto maxItems = (uint32_t)m_plan.ctx.data.cacheSrcTrav;
479 items.clear();
480 if (maxItems == 0)
481 return false;
482
483 bool overflowed = false;
484 each_reusable_src_entity([&](Entity source) {
485 if (items.size() >= maxItems) {
486 overflowed = true;
487 return true;
488 }
489 items.push_back({source, world_entity_archetype_version(*world(), source)});
490 return false;
491 });
492
493 return !overflowed;
494 }
495
498 GAIA_NODISCARD bool traversed_src_versions_changed() const {
499 const auto cnt = (uint32_t)m_state.srcTravSnapshot.size();
500 GAIA_FOR(cnt) {
501 const auto& item = m_state.srcTravSnapshot[i];
502 if (item.sourceVersion != world_entity_archetype_version(*world(), item.entity))
503 return true;
504 }
505
506 return false;
507 }
508
510 GAIA_NODISCARD bool dyn_src_inputs_changed(bool relationVersionsChanged) {
511 const auto& deps = m_plan.ctx.data.deps;
512 if (!deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms))
513 return false;
514
515 if (!deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms))
516 return direct_src_versions_changed();
517
518 if (m_state.srcTravSnapshotOverflowed)
519 return true;
520
521 if (!relationVersionsChanged)
522 return traversed_src_versions_changed();
523
524 auto& scratch = src_trav_snapshot_scratch();
525 if (!build_src_trav_snapshot(scratch))
526 return true;
527
528 if (scratch.size() != m_state.srcTravSnapshot.size())
529 return true;
530
531 const auto cnt = (uint32_t)m_state.srcTravSnapshot.size();
532 GAIA_FOR(cnt) {
533 if (scratch[i] != m_state.srcTravSnapshot[i])
534 return true;
535 }
536
537 return false;
538 }
539
541 GAIA_NODISCARD bool
542 dyn_inputs_changed(const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
543 if (!can_reuse_dyn_cache())
544 return false;
545
546 if (dyn_var_bindings_changed(runtimeVarBindings, runtimeVarBindingMask))
547 return true;
548
549 const bool relationVersionsChanged = dyn_rel_versions_changed();
550 if (relationVersionsChanged && !uses_src_trav_snapshot())
551 return true;
552
553 const auto& deps = m_plan.ctx.data.deps;
554 if (!deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms))
555 return relationVersionsChanged;
556
557 if (m_state.srcTravSnapshotOverflowed)
558 return true;
559
560 return dyn_src_inputs_changed(relationVersionsChanged);
561 }
562
564 void
565 snapshot_dyn_inputs(const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
566 if (!can_reuse_dyn_cache())
567 return;
568
569 const auto relations = m_plan.ctx.data.deps.relations_view();
570 const auto cnt = (uint32_t)relations.size();
571 GAIA_FOR(cnt)
572 m_state.relationVersions[i] = world_rel_version(*world(), relations[i]);
573
574 const auto& deps = m_plan.ctx.data.deps;
575 if (uses_direct_src_version_tracking()) {
576 const auto sourceEntities = deps.src_entities_view();
577 const auto sourceCnt = (uint32_t)sourceEntities.size();
578 GAIA_FOR(sourceCnt)
579 m_state.directSrcEntityVersions[i] = world_entity_archetype_version(*world(), sourceEntities[i]);
580 }
581
582 if (uses_src_trav_snapshot()) {
583 auto& scratch = src_trav_snapshot_scratch();
584 if (build_src_trav_snapshot(scratch)) {
585 m_state.srcTravSnapshot = scratch;
586 m_state.srcTravSnapshotOverflowed = false;
587 } else {
588 m_state.srcTravSnapshot.clear();
589 m_state.srcTravSnapshotOverflowed = true;
590 }
591 } else {
592 m_state.srcTravSnapshot.clear();
593 m_state.srcTravSnapshotOverflowed = false;
594 }
595
596 m_state.varBindings = runtimeVarBindings;
597 m_state.varBindingMask = runtimeVarBindingMask;
598 }
599
600 template <typename TType>
601 GAIA_NODISCARD bool has_inter([[maybe_unused]] QueryOpKind op, bool isReadWrite) const {
602 using T = core::raw_t<TType>;
603
604 if constexpr (std::is_same_v<T, Entity>) {
605 // Entities are read-only.
606 GAIA_ASSERT(!isReadWrite);
607 // Skip Entity input args. Entities are always there.
608 return true;
609 } else {
610 Entity id;
611
612 if constexpr (is_pair<T>::value) {
613 const auto rel = m_plan.ctx.cc->get<typename T::rel>().entity;
614 const auto tgt = m_plan.ctx.cc->get<typename T::tgt>().entity;
615 id = (Entity)Pair(rel, tgt);
616 } else {
617 id = m_plan.ctx.cc->get<T>().entity;
618 }
619
620 const auto& ctxData = m_plan.ctx.data;
621 const auto compIdx = comp_idx<MAX_ITEMS_IN_QUERY>(ctxData.terms.data(), id, EntityBad);
622
623 if (op != ctxData.terms[compIdx].op)
624 return false;
625
626 // Read-write mask must match
627 const uint32_t maskRW = (uint32_t)ctxData.readWriteMask & (1U << compIdx);
628 const uint32_t maskXX = (uint32_t)isReadWrite << compIdx;
629 return maskRW == maskXX;
630 }
631 }
632
633 template <typename T>
634 GAIA_NODISCARD bool has_inter(QueryOpKind op) const {
635 // static_assert(is_raw_v<<T>, "has() must be used with raw types");
636 constexpr bool isReadWrite = core::is_mut_v<T>;
637 return has_inter<T>(op, isReadWrite);
638 }
639
640 public:
641 void add_ref() {
642 ++m_refs;
643 GAIA_ASSERT(m_refs != 0);
644 }
645
646 void dec_ref() {
647 GAIA_ASSERT(m_refs > 0);
648 --m_refs;
649 }
650
651 uint32_t refs() const {
652 return m_refs;
653 }
654
655 void init(World* world) {
656 m_plan.ctx.w = world;
657 }
658
659 void reset() {
660 reset_matching_cache();
661 }
662
663 void invalidate(InvalidationKind kind = InvalidationKind::All) {
664 switch (kind) {
666 m_state.invalidate_result();
667 break;
669 m_state.invalidate_seed();
670 break;
672 m_state.invalidate_all();
673 break;
674 }
675 }
676
677 void invalidate_seed() {
678 invalidate(InvalidationKind::Seed);
679 }
680
681 void invalidate_result() {
682 invalidate(InvalidationKind::Result);
683 }
684
687 if (m_plan.ctx.data.sortByFunc != nullptr)
688 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortEntities;
689 }
690
691 GAIA_NODISCARD static QueryInfo create(
692 QueryId id, QueryCtx&& ctx, const EntityToArchetypeMap& entityToArchetypeMap,
693 std::span<const Archetype*> allArchetypes) {
694 // Make sure query items are sorted
695 sort(ctx);
696
697 QueryInfo info;
698 info.idx = id;
699 info.gen = 0;
700
701 info.m_plan.ctx = GAIA_MOV(ctx);
702 info.m_plan.ctx.q.handle = {id, 0};
703
704 // Compile the query
705 info.compile(entityToArchetypeMap, allArchetypes);
706
707 return info;
708 }
709
710 GAIA_NODISCARD static QueryInfo create(uint32_t idx, uint32_t gen, void* pCtx) {
711 auto* pCreationCtx = (QueryInfoCreationCtx*)pCtx;
712 auto& queryCtx = *pCreationCtx->pQueryCtx;
713 auto& entityToArchetypeMap = (EntityToArchetypeMap&)*pCreationCtx->pEntityToArchetypeMap;
714
715 // Make sure query items are sorted
716 sort(queryCtx);
717
718 QueryInfo info;
719 info.idx = idx;
720 info.gen = gen;
721
722 info.m_plan.ctx = GAIA_MOV(queryCtx);
723 info.m_plan.ctx.q.handle = {idx, gen};
724
725 // Compile the query
726 info.compile(entityToArchetypeMap, pCreationCtx->allArchetypes);
727
728 return info;
729 }
730
731 GAIA_NODISCARD static QueryHandle handle(const QueryInfo& info) {
732 return QueryHandle(info.idx, info.gen);
733 }
734
736 void compile(const EntityToArchetypeMap& entityToArchetypeMap, std::span<const Archetype*> allArchetypes) {
737 GAIA_PROF_SCOPE(queryinfo::compile);
738
739 // Compile the opcodes
740 m_plan.vm.compile(entityToArchetypeMap, allArchetypes, m_plan.ctx);
741 }
742
744 void recompile() {
745 GAIA_PROF_SCOPE(queryinfo::recompile);
746
747 // Compile the opcodes
748 m_plan.vm.create_opcodes(m_plan.ctx);
749 }
750
751 GAIA_NODISCARD QueryCtx::CachePolicy cache_policy() const {
752 return m_plan.ctx.data.cachePolicy;
753 }
754
755 GAIA_NODISCARD bool has_grouped_payload() const {
756 return m_plan.ctx.data.groupBy != EntityBad;
757 }
758
759 GAIA_NODISCARD bool has_sorted_payload() const {
760 return m_plan.ctx.data.sortByFunc != nullptr;
761 }
762
763 GAIA_NODISCARD uint32_t reverse_index_revision() const {
764 return m_state.resultCacheRevision;
765 }
766
767 GAIA_NODISCARD bool can_update_with_new_archetype() const {
768 // Only immediate structural queries participate in archetype-create propagation.
769 return m_plan.vm.is_compiled() && cache_policy() == QueryCtx::CachePolicy::Immediate &&
770 !m_state.needs_refresh();
771 }
772
774 GAIA_NODISCARD bool can_use_direct_create_archetype_match() const {
775 return m_plan.ctx.data.createArchetypeMatchKind == QueryCtx::CreateArchetypeMatchKind::DirectStructuralTerms;
776 }
777
779 GAIA_NODISCARD bool direct_create_archetype_match_uses_is() const {
780 const auto& ctxData = m_plan.ctx.data;
781 return (ctxData.as_mask_0 + ctxData.as_mask_1) != 0;
782 }
783
784 GAIA_NODISCARD bool operator==(const QueryCtx& other) const {
785 return m_plan.ctx == other;
786 }
787
788 GAIA_NODISCARD bool operator!=(const QueryCtx& other) const {
789 return m_plan.ctx != other;
790 }
791
793 World& world;
794 bool keepStamps;
795
796 explicit CleanUpTmpArchetypeMatches(World& world, bool keepStamps): world(world), keepStamps(keepStamps) {}
799 CleanUpTmpArchetypeMatches& operator=(const CleanUpTmpArchetypeMatches&) = delete;
801
803 query_match_scratch_release(world, keepStamps);
804 }
805 };
806
813 template <typename ArchetypeLookup>
814 void match(
815 // entity -> archetypes mapping
816 const ArchetypeLookup& entityToArchetypeMap,
817 // all archetypes in the world
818 std::span<const Archetype*> allArchetypes,
819 // last matched archetype id
820 ArchetypeId archetypeLastId, const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings,
821 uint8_t runtimeVarBindingMask) {
822 auto& ctxData = m_plan.ctx.data;
823
824 // Recompile if necessary
825 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
826 recompile();
827
828 // Skip if nothing has been compiled.
829 if (!m_plan.vm.is_compiled())
830 return;
831
832 const bool hasDynamicTerms = has_dyn_terms();
833 if (hasDynamicTerms && (!can_reuse_dyn_cache() || m_state.needs_refresh() ||
834 dyn_inputs_changed(runtimeVarBindings, runtimeVarBindingMask))) {
835 // Dynamic queries keep their cached result as long as tracked runtime inputs stay stable.
836 // Source-based queries still take the conservative rebuild path until their inputs
837 // are tracked with finer-grained version metadata.
838 reset_matching_cache();
839 } else if (m_state.seed_dirty()) {
840 reset_matching_cache();
841 } else if (m_state.result_dirty()) {
842 sync_result_cache_from_seed_cache();
843 if (m_state.lastArchetypeId == archetypeLastId) {
844 sort_entities();
845 sort_cache_groups();
846 m_state.clear_dirty();
847 return;
848 }
849 }
850
851 // Skip if no new archetype appeared
852 GAIA_ASSERT(archetypeLastId >= m_state.lastArchetypeId);
853 if (!hasDynamicTerms && !m_state.needs_refresh() && m_state.lastArchetypeId == archetypeLastId) {
854 // Sort entities if necessary
855 sort_entities();
856 return;
857 }
858
859 m_state.lastArchetypeId = archetypeLastId;
860
861 GAIA_PROF_SCOPE(queryinfo::match);
862
863 auto& w = *world();
864 auto& matchScratch = query_match_scratch_acquire(w);
865 CleanUpTmpArchetypeMatches autoCleanup(w, true);
866
867 // Prepare the context
868 vm::MatchingCtx ctx{};
869 ctx.pWorld = world();
870 // ctx.targetEntities = {};
871 ctx.allArchetypes = allArchetypes;
872 ctx.archetypeLookup = vm::make_archetype_lookup_view(entityToArchetypeMap);
873 ctx.pMatchesArr = &matchScratch.matchesArr;
874 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
875 ctx.matchesVersion = matchScratch.next_match_version();
876 ctx.pLastMatchedArchetypeIdx_All = &ctxData.lastMatchedArchetypeIdx_All;
877 ctx.pLastMatchedArchetypeIdx_Or = &ctxData.lastMatchedArchetypeIdx_Or;
878 ctx.pLastMatchedArchetypeIdx_Not = &ctxData.lastMatchedArchetypeIdx_Not;
879 ctx.queryMask = ctxData.queryMask;
880 ctx.as_mask_0 = ctxData.as_mask_0;
881 ctx.as_mask_1 = ctxData.as_mask_1;
882 ctx.flags = ctxData.flags;
883 ctx.varBindings = runtimeVarBindings;
884 ctx.varBindingMask = runtimeVarBindingMask;
885
886 // Run the virtual machine
887 m_plan.vm.exec(ctx);
888
889 // Write found matches to cache
890 for (const auto* pArchetype: *ctx.pMatchesArr) {
891 if (hasDynamicTerms) {
892 add_archetype_to_cache(pArchetype, true);
893 } else {
894 add_archetype_to_seed_cache(pArchetype);
895 add_archetype_to_cache(pArchetype, true);
896 }
897 }
898
899 // Sort entities if necessary
900 sort_entities();
901 // Sort cache groups if necessary
902 sort_cache_groups();
903 snapshot_dyn_inputs(runtimeVarBindings, runtimeVarBindingMask);
904 m_state.clear_dirty();
905 }
906
913 const Archetype& archetype, EntitySpan targetEntities,
914 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
915 auto& ctxData = m_plan.ctx.data;
916
917 // Recompile if necessary
918 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
919 recompile();
920
921 // Skip if nothing has been compiled.
922 if (!m_plan.vm.is_compiled())
923 return false;
924
925 const bool hasDynamicTerms = has_dyn_terms();
926 if ((hasDynamicTerms && (!can_reuse_dyn_cache() || m_state.needs_refresh() ||
927 dyn_inputs_changed(runtimeVarBindings, runtimeVarBindingMask))) ||
928 m_state.seed_dirty()) {
929 // Dynamic queries keep their cached result as long as tracked runtime inputs stay stable.
930 // Source-based queries still take the conservative rebuild path until their inputs
931 // are tracked with finer-grained version metadata.
932 reset_matching_cache();
933 } else if (m_state.result_dirty()) {
934 sync_result_cache_from_seed_cache();
935 }
936
937 GAIA_PROF_SCOPE(queryinfo::match1);
938
939 auto& w = *world();
940 auto& matchScratch = query_match_scratch_acquire(w);
941 CleanUpTmpArchetypeMatches autoCleanup(w, true);
942
943 // Prepare the context
944 vm::MatchingCtx ctx{};
945 ctx.pWorld = world();
946 ctx.targetEntities = targetEntities;
947 const auto* pArchetype = &archetype;
948 ctx.allArchetypes = std::span((const Archetype**)&pArchetype, 1);
949 ctx.archetypeLookup = {};
950 ctx.pMatchesArr = &matchScratch.matchesArr;
951 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
952 ctx.matchesVersion = matchScratch.next_match_version();
953 ctx.pLastMatchedArchetypeIdx_All = nullptr;
954 ctx.pLastMatchedArchetypeIdx_Or = nullptr;
955 ctx.pLastMatchedArchetypeIdx_Not = nullptr;
956 ctx.queryMask = ctxData.queryMask;
957 ctx.as_mask_0 = ctxData.as_mask_0;
958 ctx.as_mask_1 = ctxData.as_mask_1;
959 ctx.flags = ctxData.flags;
960 ctx.varBindings = runtimeVarBindings;
961 ctx.varBindingMask = runtimeVarBindingMask;
962
963 // Run the virtual machine
964 m_plan.vm.exec(ctx);
965 const bool matched = !ctx.pMatchesArr->empty();
966
967 // Write found matches to cache
968 for (const auto* pArch: *ctx.pMatchesArr) {
969 if (hasDynamicTerms) {
970 add_archetype_to_cache(pArch, true);
971 } else {
972 add_archetype_to_seed_cache(pArch);
973 add_archetype_to_cache(pArch, true);
974 }
975 }
976 snapshot_dyn_inputs(runtimeVarBindings, runtimeVarBindingMask);
977 m_state.clear_dirty();
978 return matched;
979 }
980
981 void ensure_matches(
982 const EntityToArchetypeMap& entityToArchetypeMap, std::span<const Archetype*> allArchetypes,
983 ArchetypeId archetypeLastId, const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings,
984 uint8_t runtimeVarBindingMask) {
985 match(entityToArchetypeMap, allArchetypes, archetypeLastId, runtimeVarBindings, runtimeVarBindingMask);
986 }
987
988 void ensure_matches_transient(
989 const EntityToArchetypeMap& entityToArchetypeMap, std::span<const Archetype*> allArchetypes,
990 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
991 auto& ctxData = m_plan.ctx.data;
992
993 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
994 recompile();
995
996 if (!m_plan.vm.is_compiled())
997 return;
998
999 m_state.clear_transient_result_cache();
1000
1001 auto& w = *world();
1002 auto& matchScratch = query_match_scratch_acquire(w);
1003 CleanUpTmpArchetypeMatches autoCleanup(w, true);
1004
1005 vm::MatchingCtx ctx{};
1006 ctx.pWorld = world();
1007 ctx.allArchetypes = allArchetypes;
1008 ctx.archetypeLookup = vm::make_archetype_lookup_view(entityToArchetypeMap);
1009 ctx.pMatchesArr = &matchScratch.matchesArr;
1010 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
1011 ctx.matchesVersion = matchScratch.next_match_version();
1012 ctx.pLastMatchedArchetypeIdx_All = nullptr;
1013 ctx.pLastMatchedArchetypeIdx_Or = nullptr;
1014 ctx.pLastMatchedArchetypeIdx_Not = nullptr;
1015 ctx.queryMask = ctxData.queryMask;
1016 ctx.as_mask_0 = ctxData.as_mask_0;
1017 ctx.as_mask_1 = ctxData.as_mask_1;
1018 ctx.flags = ctxData.flags;
1019 ctx.varBindings = runtimeVarBindings;
1020 ctx.varBindingMask = runtimeVarBindingMask;
1021
1022 m_plan.vm.exec(ctx);
1023
1024 m_state.archetypeCache.reserve(ctx.pMatchesArr->size());
1025 if (ctxData.groupBy != EntityBad)
1026 m_state.grouped.archetypeGroupIds.reserve(ctx.pMatchesArr->size());
1027 for (const auto* pArchetype: *ctx.pMatchesArr)
1028 add_archetype_to_transient_cache(pArchetype);
1029
1030 sort_entities();
1031 ensure_group_data();
1032 }
1033
1034 bool ensure_matches_one(
1035 const Archetype& archetype, EntitySpan targetEntities,
1036 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1037 return match_one(archetype, targetEntities, runtimeVarBindings, runtimeVarBindingMask);
1038 }
1039
1040 bool ensure_matches_one_transient(
1041 const Archetype& archetype, EntitySpan targetEntities,
1042 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1043 auto& ctxData = m_plan.ctx.data;
1044
1045 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1046 recompile();
1047
1048 if (!m_plan.vm.is_compiled())
1049 return false;
1050
1051 m_state.clear_transient_result_cache();
1052
1053 auto& w = *world();
1054 auto& matchScratch = query_match_scratch_acquire(w);
1055 CleanUpTmpArchetypeMatches autoCleanup(w, true);
1056
1057 vm::MatchingCtx ctx{};
1058 ctx.pWorld = world();
1059 ctx.targetEntities = targetEntities;
1060 const auto* pArchetype = &archetype;
1061 ctx.allArchetypes = std::span((const Archetype**)&pArchetype, 1);
1062 ctx.archetypeLookup = {};
1063 ctx.pMatchesArr = &matchScratch.matchesArr;
1064 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
1065 ctx.matchesVersion = matchScratch.next_match_version();
1066 ctx.pLastMatchedArchetypeIdx_All = nullptr;
1067 ctx.pLastMatchedArchetypeIdx_Or = nullptr;
1068 ctx.pLastMatchedArchetypeIdx_Not = nullptr;
1069 ctx.queryMask = ctxData.queryMask;
1070 ctx.as_mask_0 = ctxData.as_mask_0;
1071 ctx.as_mask_1 = ctxData.as_mask_1;
1072 ctx.flags = ctxData.flags;
1073 ctx.varBindings = runtimeVarBindings;
1074 ctx.varBindingMask = runtimeVarBindingMask;
1075
1076 m_plan.vm.exec(ctx);
1077 const bool matched = !ctx.pMatchesArr->empty();
1078
1079 m_state.archetypeCache.reserve(ctx.pMatchesArr->size());
1080 if (ctxData.groupBy != EntityBad)
1081 m_state.grouped.archetypeGroupIds.reserve(ctx.pMatchesArr->size());
1082 for (const auto* pArch: *ctx.pMatchesArr)
1083 add_archetype_to_transient_cache(pArch);
1084
1085 ensure_group_data();
1086 return matched;
1087 }
1088
1089 bool register_archetype(const Archetype& archetype, Entity matchedSelector = EntityBad, bool assumeNew = false) {
1090 auto& ctxData = m_plan.ctx.data;
1091
1092 // Recompile if necessary.
1093 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1094 recompile();
1095
1096 if (!can_update_with_new_archetype())
1097 return false;
1098
1099 const bool hadMatchBefore = !assumeNew && m_state.archetypeSet.contains(&archetype);
1101 const bool usesIs = direct_create_archetype_match_uses_is();
1102 bool hasOrTerms = false;
1103 bool matchedOrTerm = false;
1104 for (const auto& term: ctxData.terms_view()) {
1105 if (term.id == matchedSelector) {
1106 if (term.op == QueryOpKind::Or)
1107 matchedOrTerm = true;
1108 continue;
1109 }
1110
1111 const bool present = usesIs ? vm::detail::match_single_id_on_archetype(*world(), archetype, term.id)
1112 : world_component_index_match_count(*world(), archetype, term.id) != 0;
1113 if (term.op == QueryOpKind::Or) {
1114 hasOrTerms = true;
1115 matchedOrTerm |= present;
1116 continue;
1117 }
1118 if (term.op == QueryOpKind::Any)
1119 continue;
1120
1121 const bool matched = term.op == QueryOpKind::Not ? !present : present;
1122 if (!matched)
1123 return false;
1124 }
1125 if (hasOrTerms && !matchedOrTerm)
1126 return false;
1127 if (hadMatchBefore)
1128 return false;
1129
1130 if (assumeNew)
1131 add_new_archetype_to_immediate_caches(&archetype, true);
1132 else {
1133 add_archetype_to_seed_cache(&archetype, false);
1134 add_archetype_to_cache(&archetype, true, false);
1135 }
1136 return true;
1137 }
1138
1139 SingleArchetypeLookup singleArchetypeLookup;
1140 auto addLookupUnique = [&](Entity key, uint16_t compIdx, uint16_t matchCount) {
1141 const auto keyLookup = EntityLookupKey(key);
1142 const auto itLookup =
1143 core::find_if(singleArchetypeLookup.begin(), singleArchetypeLookup.end(), [&](const auto& item) {
1144 return item.matches(keyLookup);
1145 });
1146 if (itLookup != singleArchetypeLookup.end()) {
1147 auto& entry = itLookup->entry;
1148 entry.matchCount = (uint16_t)(entry.matchCount + matchCount);
1149 if (compIdx != ComponentIndexBad)
1150 entry.compIdx = compIdx;
1151 return;
1152 }
1153 singleArchetypeLookup.push_back(
1154 SingleArchetypeLookupItem{
1155 keyLookup, ComponentIndexEntry{const_cast<Archetype*>(&archetype), compIdx, matchCount}});
1156 };
1157 auto archetypeIds = archetype.ids_view();
1158 const auto cntIds = (uint32_t)archetypeIds.size();
1159 GAIA_FOR(cntIds) {
1160 const auto entity = archetypeIds[i];
1161 singleArchetypeLookup.push_back(
1162 SingleArchetypeLookupItem{
1163 EntityLookupKey(entity), ComponentIndexEntry{const_cast<Archetype*>(&archetype), (uint16_t)i, 1}});
1164
1165 if (!entity.pair())
1166 continue;
1167
1168 // Wildcard pair lookups use the same special records as the world-level
1169 // entity-to-archetype map, so incremental matching can reuse the normal VM path.
1170 const auto relKind = entity.entity() ? EntityKind::EK_Uni : EntityKind::EK_Gen;
1171 const auto rel = Entity((EntityId)entity.id(), 0, false, false, relKind);
1172 const auto tgt = Entity((EntityId)entity.gen(), 0, false, false, entity.kind());
1173 addLookupUnique(Pair(All, tgt), ComponentIndexBad, 1);
1174 addLookupUnique(Pair(rel, All), ComponentIndexBad, 1);
1175 addLookupUnique(Pair(All, All), ComponentIndexBad, 1);
1176 }
1177
1178 auto lastMatchedArchetypeIdx_All = GAIA_MOV(ctxData.lastMatchedArchetypeIdx_All);
1179 auto lastMatchedArchetypeIdx_Or = GAIA_MOV(ctxData.lastMatchedArchetypeIdx_Or);
1180 auto lastMatchedArchetypeIdx_Not = GAIA_MOV(ctxData.lastMatchedArchetypeIdx_Not);
1181 GAIA_ASSERT(ctxData.lastMatchedArchetypeIdx_All.empty());
1182 GAIA_ASSERT(ctxData.lastMatchedArchetypeIdx_Or.empty());
1183 GAIA_ASSERT(ctxData.lastMatchedArchetypeIdx_Not.empty());
1184
1185 const auto* pArchetype = &archetype;
1186 const cnt::sarray<Entity, MaxVarCnt> noRuntimeVarBindings{};
1187 match(
1188 singleArchetypeLookup, std::span((const Archetype**)&pArchetype, 1), archetype.id(), noRuntimeVarBindings,
1189 0);
1190 ctxData.lastMatchedArchetypeIdx_All = GAIA_MOV(lastMatchedArchetypeIdx_All);
1191 ctxData.lastMatchedArchetypeIdx_Or = GAIA_MOV(lastMatchedArchetypeIdx_Or);
1192 ctxData.lastMatchedArchetypeIdx_Not = GAIA_MOV(lastMatchedArchetypeIdx_Not);
1193 const bool matched = assumeNew ? m_state.archetypeSet.contains(&archetype)
1194 : !hadMatchBefore && m_state.archetypeSet.contains(&archetype);
1195
1196 if (!matched)
1197 return false;
1198
1199 sort_entities();
1200 sort_cache_groups();
1201 return true;
1202 }
1203
1207 GAIA_PROF_SCOPE(queryinfo::calc_sort_data);
1208
1209 m_state.nonTrivial.archetypeSortData.clear();
1210
1211 // The function doesn't do any moves and expects that all chunks have their data sorted already.
1212 // We use a min-heap / priority queue - like structure during query iteration to merge sorted tables:
1213 // - we hold a cursor into each sorted chunk
1214 // - we compare the next entity from each chunk using your sorting function
1215 // - we then pick the smallest one (like k-way merge sort), and advance that cursor
1216 // This is esentially what this function does:
1217 // while (any_chunk_has_entities) {
1218 // find_chunk_with_smallest_next_element();
1219 // yield(entity_from_that_chunk);
1220 // adv_cursor_for_that_chunk();
1221 // }
1222 // This produces a globally sorted view without modifying actual data. It's a balance between
1223 // performance and memory usage. We could also sort the data in-place across all chunks, but that
1224 // would generated too many data moves (entities + all of their components).
1225
1226 struct Cursor {
1227 uint32_t chunkIdx = 0;
1228 uint16_t row = 0;
1229 };
1230
1231 auto& archetypes = m_state.archetypeCache;
1232
1233 // Initialize cursors. We will need as many as there are archetypes.
1234 cnt::sarray_ext<Cursor, 128> cursors(archetypes.size());
1235
1236 uint32_t currArchetypeIdx = (uint32_t)-1;
1237 Chunk* pCurrentChunk = nullptr;
1238 uint16_t currentStartRow = 0;
1239 uint16_t currentRow = 0;
1240
1241 const void* pDataMin = nullptr;
1242 const void* pDataCurr = nullptr;
1243
1244 while (true) {
1245 uint32_t minArchetypeIdx = (uint32_t)-1;
1246 Entity minEntity = EntityBad;
1247
1248 // Find the next entity across all tables/chunks
1249 for (uint32_t t = 0; t < archetypes.size(); ++t) {
1250 const auto* pArchetype = archetypes[t];
1251 const auto& chunks = pArchetype->chunks();
1252 auto& cur = cursors[t];
1253
1254 while (cur.chunkIdx < chunks.size() && cur.row >= chunks[cur.chunkIdx]->size()) {
1255 ++cur.chunkIdx;
1256 cur.row = 0;
1257 }
1258
1259 if (cur.chunkIdx >= chunks.size())
1260 continue;
1261
1262 const auto* pChunk = pArchetype->chunks()[cur.chunkIdx];
1263 auto entity = pChunk->entity_view()[cur.row];
1264
1265 if (m_plan.ctx.data.sortBy != ecs::EntityBad) {
1266 auto compIdx = world_component_index_comp_idx(*m_plan.ctx.w, *pArchetype, m_plan.ctx.data.sortBy);
1267 if (compIdx == BadIndex)
1268 compIdx = pChunk->comp_idx(m_plan.ctx.data.sortBy);
1269 pDataCurr = pChunk->comp_ptr(compIdx, cur.row);
1270 } else
1271 pDataCurr = &pChunk->entity_view()[cur.row];
1272
1273 if (minEntity == EntityBad) {
1274 minEntity = entity;
1275 minArchetypeIdx = t;
1276 pDataMin = pDataCurr;
1277 continue;
1278 }
1279
1280 if (m_plan.ctx.data.sortByFunc(*m_plan.ctx.w, pDataCurr, pDataMin) < 0) {
1281 minEntity = entity;
1282 minArchetypeIdx = t;
1283 }
1284 }
1285
1286 // No more results found, we can stop
1287 if (minArchetypeIdx == (uint32_t)-1)
1288 break;
1289
1290 auto& cur = cursors[minArchetypeIdx];
1291 const auto& chunks = archetypes[minArchetypeIdx]->chunks();
1292 Chunk* pChunk = chunks[cur.chunkIdx];
1293
1294 if (minArchetypeIdx == currArchetypeIdx && pChunk == pCurrentChunk) {
1295 // Current slice
1296 } else {
1297 // End previous slice
1298 if (pCurrentChunk != nullptr) {
1299 m_state.nonTrivial.archetypeSortData.push_back(
1300 {pCurrentChunk, currArchetypeIdx, currentStartRow, (uint16_t)(currentRow - currentStartRow)});
1301 }
1302
1303 // Start a new slice
1304 currArchetypeIdx = minArchetypeIdx;
1305 pCurrentChunk = pChunk;
1306 currentStartRow = cur.row;
1307 }
1308
1309 ++cur.row;
1310 currentRow = cur.row;
1311 }
1312
1313 if (pCurrentChunk != nullptr) {
1314 m_state.nonTrivial.archetypeSortData.push_back(
1315 {pCurrentChunk, currArchetypeIdx, currentStartRow, (uint16_t)(currentRow - currentStartRow)});
1316 }
1317 }
1318
1319 void sort_entities() {
1320 if (m_plan.ctx.data.sortByFunc == nullptr)
1321 return;
1322
1323 if ((m_plan.ctx.data.flags & QueryCtx::QueryFlags::SortEntities) == 0 && m_state.nonTrivial.sortVersion != 0)
1324 return;
1325 m_plan.ctx.data.flags &= ~QueryCtx::QueryFlags::SortEntities;
1326
1327 // First, sort entities in archetypes
1328 for (const auto* pArchetype: m_state.archetypeCache)
1329 const_cast<Archetype*>(pArchetype)->sort_entities(m_plan.ctx.data.sortBy, m_plan.ctx.data.sortByFunc);
1330
1331 // Now that entites are sorted, we can start creating slices
1333 m_state.nonTrivial.sortVersion = ::gaia::ecs::world_version(*world());
1334 }
1335
1336 void sort_cache_groups() {
1337 if ((m_plan.ctx.data.flags & QueryCtx::QueryFlags::SortGroups) == 0)
1338 return;
1339 m_plan.ctx.data.flags &= ~QueryCtx::QueryFlags::SortGroups;
1340
1341 ensure_group_data();
1342 }
1343
1344 void swap_archetype_cache_entry(uint32_t left, uint32_t right) {
1345 auto* pTmpArchetype = m_state.archetypeCache[left];
1346 m_state.archetypeCache[left] = m_state.archetypeCache[right];
1347 m_state.archetypeCache[right] = pTmpArchetype;
1348
1349 if (left < m_state.grouped.archetypeGroupIds.size() && right < m_state.grouped.archetypeGroupIds.size()) {
1350 const auto tmp = m_state.grouped.archetypeGroupIds[left];
1351 m_state.grouped.archetypeGroupIds[left] = m_state.grouped.archetypeGroupIds[right];
1352 m_state.grouped.archetypeGroupIds[right] = tmp;
1353 }
1354
1355 if (left < m_state.exec.archetypeCompIndices.size() && right < m_state.exec.archetypeCompIndices.size()) {
1356 auto tmp = m_state.exec.archetypeCompIndices[left];
1357 m_state.exec.archetypeCompIndices[left] = m_state.exec.archetypeCompIndices[right];
1358 m_state.exec.archetypeCompIndices[right] = tmp;
1359 }
1360
1361 if (left < m_state.exec.archetypeInheritedData.size() && right < m_state.exec.archetypeInheritedData.size()) {
1362 auto tmp = m_state.exec.archetypeInheritedData[left];
1363 m_state.exec.archetypeInheritedData[left] = m_state.exec.archetypeInheritedData[right];
1364 m_state.exec.archetypeInheritedData[right] = tmp;
1365 }
1366
1367 if (left < m_state.nonTrivial.archetypeBarrierPasses.size() &&
1368 right < m_state.nonTrivial.archetypeBarrierPasses.size()) {
1369 const auto tmp = m_state.nonTrivial.archetypeBarrierPasses[left];
1370 m_state.nonTrivial.archetypeBarrierPasses[left] = m_state.nonTrivial.archetypeBarrierPasses[right];
1371 m_state.nonTrivial.archetypeBarrierPasses[right] = tmp;
1372 }
1373 }
1374
1375 void ensure_comp_indices() {
1376 if (!m_state.exec.compIndicesPending)
1377 return;
1378
1379 m_state.exec.archetypeCompIndices.clear();
1380 m_state.exec.archetypeCompIndices.reserve(m_state.archetypeCache.size());
1381 for (const auto* pArchetype: m_state.archetypeCache)
1382 m_state.exec.archetypeCompIndices.push_back(create_comp_indices(pArchetype));
1383
1384 m_state.exec.compIndicesPending = false;
1385 }
1386
1387 GAIA_NODISCARD bool has_inherited_data_payload() const {
1388 return ctx().data.deps.has_dep_flag(QueryCtx::DependencyHasInheritedDataTerms);
1389 }
1390
1391 void ensure_inherited_data() {
1392 if (!m_state.exec.inheritedDataPending)
1393 return;
1394
1395 if (!has_inherited_data_payload()) {
1396 m_state.exec.archetypeInheritedData.clear();
1397 m_state.exec.inheritedDataPending = false;
1398 return;
1399 }
1400
1401 m_state.exec.archetypeInheritedData.clear();
1402 m_state.exec.archetypeInheritedData.reserve(m_state.archetypeCache.size());
1403 for (const auto* pArchetype: m_state.archetypeCache)
1404 create_inherited_data(pArchetype);
1405
1406 m_state.exec.inheritedDataPending = false;
1407 }
1408
1409 void ensure_group_data() {
1410 if (m_plan.ctx.data.groupBy == EntityBad || !m_state.grouped.dataPending)
1411 return;
1412
1413 struct sort_cond {
1414 bool operator()(GroupId a, GroupId b) const {
1415 return a <= b;
1416 }
1417 };
1418
1419 core::sort(m_state.grouped.archetypeGroupIds, sort_cond{}, [&](uint32_t left, uint32_t right) {
1420 swap_archetype_cache_entry(left, right);
1421 });
1422
1423 m_state.grouped.archetypeGroupData.clear();
1424 m_state.grouped.selectedGroupDataValid = false;
1425
1426 if (m_state.grouped.archetypeGroupIds.empty()) {
1427 m_state.grouped.dataPending = false;
1428 return;
1429 }
1430
1431 GroupId groupId = m_state.grouped.archetypeGroupIds[0];
1432 uint32_t idxFirst = 0;
1433 const auto cnt = (uint32_t)m_state.grouped.archetypeGroupIds.size();
1434 for (uint32_t i = 1; i < cnt; ++i) {
1435 if (m_state.grouped.archetypeGroupIds[i] == groupId)
1436 continue;
1437
1438 m_state.grouped.archetypeGroupData.push_back({groupId, idxFirst, i - 1, false});
1439 groupId = m_state.grouped.archetypeGroupIds[i];
1440 idxFirst = i;
1441 }
1442
1443 m_state.grouped.archetypeGroupData.push_back({groupId, idxFirst, cnt - 1, false});
1444 m_state.grouped.dataPending = false;
1445 }
1446
1447 void ensure_depth_order_hierarchy_barrier_cache_inter() {
1448 if (!world_depth_order_prunes_disabled_subtrees(*world(), m_plan.ctx.data.groupBy))
1449 return;
1450
1451 ensure_group_data();
1452
1453 const auto currRelationVersion = world_rel_version(*world(), m_plan.ctx.data.groupBy);
1454 const auto currEnabledVersion = world_enabled_hierarchy_version(*world());
1455 if (m_state.nonTrivial.barrierRelVersion == currRelationVersion &&
1456 m_state.nonTrivial.barrierEnabledVersion == currEnabledVersion)
1457 return;
1458
1459 m_state.nonTrivial.archetypeBarrierPasses.resize(m_state.archetypeCache.size(), 1);
1460
1461 const auto relation = m_plan.ctx.data.groupBy;
1462 for (uint32_t i = 0; i < m_state.archetypeCache.size(); ++i) {
1463 const auto* pArchetype = m_state.archetypeCache[i];
1464 auto& barrierPasses = m_state.nonTrivial.archetypeBarrierPasses[i];
1465 barrierPasses = 1;
1466
1467 auto ids = pArchetype->ids_view();
1468 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
1469 const auto pair = ids[idsIdx];
1470 const auto parent = world_pair_target_if_alive(*world(), pair);
1471 if (parent == EntityBad || !world_entity_enabled_hierarchy(*world(), parent, relation)) {
1472 barrierPasses = 0;
1473 break;
1474 }
1475 }
1476 }
1477
1478 m_state.nonTrivial.barrierRelVersion = currRelationVersion;
1479 m_state.nonTrivial.barrierEnabledVersion = currEnabledVersion;
1480 }
1481
1482 ArchetypeCompIndices create_comp_indices(const Archetype* pArchetype) {
1483 ArchetypeCompIndices cacheData{};
1484 core::fill(cacheData.indices, cacheData.indices + ChunkHeader::MAX_COMPONENTS, (uint8_t)0xFF);
1485 const auto terms = ctx().data.terms_view();
1486 const auto cnt = (uint32_t)terms.size();
1487 GAIA_FOR(cnt) {
1488 const auto& term = terms[i];
1489 const auto fieldIdx = term.fieldIndex;
1490 const auto queryId = term.id;
1491 if (!queryId.pair() && world_is_out_of_line_component(*world(), queryId)) {
1492 const auto compIdx = core::get_index_unsafe(pArchetype->ids_view(), queryId);
1493 GAIA_ASSERT(compIdx != BadIndex);
1494 cacheData.indices[fieldIdx] = 0xFF;
1495 continue;
1496 }
1497
1498 auto compIdx = world_component_index_comp_idx(*world(), *pArchetype, queryId);
1499 if (compIdx == BadIndex) {
1500 // Wildcard/semantic terms are not represented by an exact component index entry.
1501 // Fall back to the archetype-local scan in those cases.
1502 compIdx = core::get_index_unsafe(pArchetype->ids_view(), queryId);
1503 }
1504 GAIA_ASSERT(compIdx != BadIndex);
1505
1506 cacheData.indices[fieldIdx] = (uint8_t)compIdx;
1507 }
1508 return cacheData;
1509 }
1510
1511 void create_inherited_data(const Archetype* pArchetype) {
1512 ArchetypeInheritedData inheritedData{};
1513 core::fill(inheritedData.data, inheritedData.data + ChunkHeader::MAX_COMPONENTS, nullptr);
1514
1515 const auto terms = ctx().data.terms_view();
1516 const auto cnt = (uint32_t)terms.size();
1517 GAIA_FOR(cnt) {
1518 const auto& term = terms[i];
1519 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
1520 continue;
1521 if (term.matchKind != QueryMatchKind::Semantic)
1522 continue;
1523 const auto queryId = term.id;
1524 if (queryId == EntityBad || is_wildcard(queryId) || is_variable((EntityId)queryId.id()))
1525 continue;
1526 if (world_is_out_of_line_component(*world(), queryId))
1527 continue;
1528 if (!world_term_uses_inherit_policy(*world(), queryId))
1529 continue;
1530 if (pArchetype->has(queryId))
1531 continue;
1532
1533 const auto owner = world_query_first_inherited_owner(*world(), *pArchetype, queryId);
1534 GAIA_ASSERT(owner != EntityBad);
1535 inheritedData.data[term.fieldIndex] = world_query_inherited_arg_data_const_ptr(*world(), owner, queryId);
1536 }
1537
1538 m_state.exec.archetypeInheritedData.push_back(inheritedData);
1539 }
1540
1541 void add_archetype_to_cache_no_grouping(
1542 const Archetype* pArchetype, bool trackMembershipChange, bool assumeAbsent = false) {
1543 GAIA_PROF_SCOPE(queryinfo::add_cache_ng);
1544
1545 if (!assumeAbsent && m_state.archetypeSet.contains(pArchetype))
1546 return;
1547 GAIA_ASSERT(assumeAbsent || !m_state.archetypeSet.contains(pArchetype));
1548
1549 m_state.archetypeSet.emplace(pArchetype);
1550 m_state.archetypeCache.push_back(pArchetype);
1551 m_state.exec.compIndicesPending = true;
1552 m_state.exec.inheritedDataPending = true;
1553 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
1554 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
1555 if (trackMembershipChange)
1556 mark_result_cache_membership_changed();
1557 }
1558
1559 void add_archetype_to_seed_cache(const Archetype* pArchetype, bool assumeAbsent = false) {
1560 if (!assumeAbsent && m_state.seedArchetypeSet.contains(pArchetype))
1561 return;
1562 GAIA_ASSERT(assumeAbsent || !m_state.seedArchetypeSet.contains(pArchetype));
1563
1564 m_state.seedArchetypeSet.emplace(pArchetype);
1565 m_state.seedArchetypeCache.push_back(pArchetype);
1566 }
1567
1569 void add_new_archetype_to_immediate_caches(const Archetype* pArchetype, bool trackMembershipChange) {
1570 GAIA_ASSERT(m_plan.ctx.data.groupBy == EntityBad);
1571 GAIA_ASSERT(m_plan.ctx.data.sortByFunc == nullptr);
1572 GAIA_ASSERT(!m_state.seedArchetypeSet.contains(pArchetype));
1573 GAIA_ASSERT(!m_state.archetypeSet.contains(pArchetype));
1574
1575 m_state.seedArchetypeSet.emplace(pArchetype);
1576 m_state.seedArchetypeCache.push_back(pArchetype);
1577
1578 m_state.archetypeSet.emplace(pArchetype);
1579 m_state.archetypeCache.push_back(pArchetype);
1580 m_state.exec.compIndicesPending = true;
1581 m_state.exec.inheritedDataPending = true;
1582 if (trackMembershipChange)
1583 mark_result_cache_membership_changed();
1584 }
1585
1586 void add_archetype_to_cache_w_grouping(
1587 const Archetype* pArchetype, bool trackMembershipChange, bool assumeAbsent = false) {
1588 GAIA_PROF_SCOPE(queryinfo::add_cache_wg);
1589
1590 if (!assumeAbsent && m_state.archetypeSet.contains(pArchetype))
1591 return;
1592 GAIA_ASSERT(assumeAbsent || !m_state.archetypeSet.contains(pArchetype));
1593
1594 m_state.grouped.selectedGroupDataValid = false;
1595
1596 const GroupId groupId = m_plan.ctx.data.groupByFunc(*m_plan.ctx.w, *pArchetype, m_plan.ctx.data.groupBy);
1597
1598 m_state.archetypeSet.emplace(pArchetype);
1599 m_state.archetypeCache.push_back(pArchetype);
1600 m_state.grouped.archetypeGroupIds.push_back(groupId);
1601 m_state.grouped.dataPending = true;
1602 m_state.exec.compIndicesPending = true;
1603 m_state.exec.inheritedDataPending = true;
1604 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
1605 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
1606 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortGroups;
1607 if (trackMembershipChange)
1608 mark_result_cache_membership_changed();
1609 }
1610
1611 void add_archetype_to_cache(const Archetype* pArchetype, bool trackMembershipChange, bool assumeAbsent = false) {
1612 if (m_plan.ctx.data.sortByFunc != nullptr)
1613 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortEntities;
1614
1615 if (m_plan.ctx.data.groupBy != EntityBad)
1616 add_archetype_to_cache_w_grouping(pArchetype, trackMembershipChange, assumeAbsent);
1617 else
1618 add_archetype_to_cache_no_grouping(pArchetype, trackMembershipChange, assumeAbsent);
1619 }
1620
1621 void add_archetype_to_transient_cache(const Archetype* pArchetype) {
1622 m_state.archetypeCache.push_back(pArchetype);
1623 m_state.exec.compIndicesPending = true;
1624 m_state.exec.inheritedDataPending = true;
1625 if (m_plan.ctx.data.groupBy != EntityBad) {
1626 const auto groupId = m_plan.ctx.data.groupByFunc(*m_plan.ctx.w, *pArchetype, m_plan.ctx.data.groupBy);
1627 m_state.grouped.archetypeGroupIds.push_back(groupId);
1628 m_state.grouped.dataPending = true;
1629 }
1630 }
1631
1634 GAIA_NODISCARD const GroupData* selected_group_data(GroupId runtimeGroupId) const {
1635 const_cast<QueryInfo*>(this)->ensure_group_data();
1636 if (m_plan.ctx.data.groupBy == EntityBad || runtimeGroupId == 0)
1637 return nullptr;
1638
1639 if (!m_state.grouped.selectedGroupDataValid || m_state.grouped.selectedGroupData.groupId != runtimeGroupId) {
1640 uint32_t left = 0;
1641 uint32_t right = (uint32_t)m_state.grouped.archetypeGroupData.size();
1642 while (left < right) {
1643 const uint32_t mid = left + ((right - left) >> 1);
1644 const auto midGroupId = m_state.grouped.archetypeGroupData[mid].groupId;
1645 if (midGroupId < runtimeGroupId)
1646 left = mid + 1;
1647 else
1648 right = mid;
1649 }
1650
1651 if (left < m_state.grouped.archetypeGroupData.size() &&
1652 m_state.grouped.archetypeGroupData[left].groupId == runtimeGroupId) {
1653 m_state.grouped.selectedGroupData = m_state.grouped.archetypeGroupData[left];
1654 m_state.grouped.selectedGroupDataValid = true;
1655 return &m_state.grouped.selectedGroupData;
1656 }
1657
1658 m_state.grouped.selectedGroupData = {};
1659 m_state.grouped.selectedGroupDataValid = false;
1660 return nullptr;
1661 }
1662
1663 return &m_state.grouped.selectedGroupData;
1664 }
1665
1666 GAIA_NODISCARD bool has_same_result_membership_as_seed_cache() const {
1667 if (m_state.archetypeSet.size() != m_state.seedArchetypeSet.size())
1668 return false;
1669
1670 for (const auto* pArchetype: m_state.seedArchetypeCache) {
1671 if (!m_state.archetypeSet.contains(pArchetype))
1672 return false;
1673 }
1674
1675 return true;
1676 }
1677
1678 void sync_result_cache_from_seed_cache() {
1679 const bool membershipChanged = !has_same_result_membership_as_seed_cache();
1680 clear_result_cache();
1681 const auto cnt = (uint32_t)m_state.seedArchetypeCache.size();
1682 GAIA_FOR(cnt) {
1683 add_archetype_to_cache(m_state.seedArchetypeCache[i], false);
1684 }
1685 if (membershipChanged)
1686 mark_result_cache_membership_changed();
1687 }
1688
1689 bool del_archetype_from_cache(const Archetype* pArchetype) {
1690 const auto it = m_state.archetypeSet.find(pArchetype);
1691 if (it == m_state.archetypeSet.end())
1692 return false;
1693
1694 m_state.archetypeSet.erase(it);
1695
1696 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
1697 GAIA_ASSERT(archetypeIdx != BadIndex);
1698 if (archetypeIdx == BadIndex)
1699 return true;
1700
1701 if (m_plan.ctx.data.sortByFunc != nullptr)
1702 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortEntities;
1703
1704 core::swap_erase(m_state.archetypeCache, archetypeIdx);
1705 if (archetypeIdx < m_state.exec.archetypeCompIndices.size())
1706 core::swap_erase(m_state.exec.archetypeCompIndices, archetypeIdx);
1707 if (archetypeIdx < m_state.exec.archetypeInheritedData.size())
1708 core::swap_erase(m_state.exec.archetypeInheritedData, archetypeIdx);
1709 if (archetypeIdx < m_state.grouped.archetypeGroupIds.size())
1710 core::swap_erase(m_state.grouped.archetypeGroupIds, archetypeIdx);
1711 if (archetypeIdx < m_state.nonTrivial.archetypeBarrierPasses.size())
1712 core::swap_erase(m_state.nonTrivial.archetypeBarrierPasses, archetypeIdx);
1713
1714 if (m_plan.ctx.data.groupBy != EntityBad) {
1715 m_state.grouped.selectedGroupDataValid = false;
1716 m_state.grouped.archetypeGroupData.clear();
1717 m_state.grouped.dataPending = true;
1718 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortGroups;
1719 }
1720 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
1721 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
1722
1723 mark_result_cache_membership_changed();
1724 return true;
1725 }
1726
1727 bool del_archetype_from_seed_cache(const Archetype* pArchetype) {
1728 const auto it = m_state.seedArchetypeSet.find(pArchetype);
1729 if (it == m_state.seedArchetypeSet.end())
1730 return false;
1731
1732 m_state.seedArchetypeSet.erase(it);
1733
1734 const auto archetypeIdx = core::get_index(m_state.seedArchetypeCache, pArchetype);
1735 GAIA_ASSERT(archetypeIdx != BadIndex);
1736 if (archetypeIdx == BadIndex)
1737 return true;
1738
1739 core::swap_erase(m_state.seedArchetypeCache, archetypeIdx);
1740 return true;
1741 }
1742
1743 GAIA_NODISCARD World* world() {
1744 GAIA_ASSERT(m_plan.ctx.w != nullptr);
1745 return const_cast<World*>(m_plan.ctx.w);
1746 }
1747 GAIA_NODISCARD const World* world() const {
1748 GAIA_ASSERT(m_plan.ctx.w != nullptr);
1749 return m_plan.ctx.w;
1750 }
1751
1752 GAIA_NODISCARD QuerySerBuffer& ser_buffer() {
1753 return m_plan.ctx.q.ser_buffer(world());
1754 }
1755 void ser_buffer_reset() {
1756 m_plan.ctx.q.ser_buffer_reset(world());
1757 }
1758
1759 GAIA_NODISCARD QueryCtx& ctx() {
1760 return m_plan.ctx;
1761 }
1762 GAIA_NODISCARD const QueryCtx& ctx() const {
1763 return m_plan.ctx;
1764 }
1765
1766 GAIA_NODISCARD util::str bytecode() const {
1767 return m_plan.vm.bytecode(*world());
1768 }
1769
1770 GAIA_NODISCARD uint32_t op_count() const {
1771 return m_plan.vm.op_count();
1772 }
1773
1774 GAIA_NODISCARD uint64_t op_signature() const {
1775 return m_plan.vm.op_signature();
1776 }
1777
1778 GAIA_NODISCARD bool has_filters() const {
1779 const auto& ctxData = m_plan.ctx.data;
1780 return ctxData.changedCnt > 0;
1781 }
1782
1784 GAIA_NODISCARD bool has_entity_filter_terms() const {
1785 const auto& ctxData = m_plan.ctx.data;
1786 return ctxData.deps.has_dep_flag(QueryCtx::DependencyHasEntityFilterTerms);
1787 }
1788
1789 GAIA_NODISCARD QueryCtx::DirectTargetEvalKind direct_target_eval_kind() const {
1790 return m_plan.ctx.data.directTargetEvalKind;
1791 }
1792
1793 GAIA_NODISCARD Entity direct_target_eval_id() const {
1794 return m_plan.ctx.data.directTargetEvalId;
1795 }
1796
1798 GAIA_NODISCARD bool matches_prefab_entities() const {
1799 const auto& ctxData = m_plan.ctx.data;
1800 return (ctxData.flags & QueryCtx::QueryFlags::MatchPrefab) != 0 ||
1801 (ctxData.flags & QueryCtx::QueryFlags::HasPrefabTerms) != 0;
1802 }
1803
1804 template <typename... T>
1805 GAIA_NODISCARD bool has_any() const {
1806 return (has_inter<T>(QueryOpKind::Any) || ...);
1807 }
1808
1809 template <typename... T>
1810 GAIA_NODISCARD bool has_or() const {
1811 return (has_inter<T>(QueryOpKind::Or) || ...);
1812 }
1813
1814 template <typename... T>
1815 GAIA_NODISCARD bool has_all() const {
1816 return (has_inter<T>(QueryOpKind::All) && ...);
1817 }
1818
1819 template <typename... T>
1820 GAIA_NODISCARD bool has_no() const {
1821 return (!has_inter<T>(QueryOpKind::Not) && ...);
1822 }
1823
1826 void remove(Archetype* pArchetype) {
1827 GAIA_PROF_SCOPE(queryinfo::remove);
1828
1829 const bool removedFromSeed = del_archetype_from_seed_cache(pArchetype);
1830 const bool removedFromResult = del_archetype_from_cache(pArchetype);
1831 if (!removedFromSeed && !removedFromResult)
1832 return;
1833
1834 // An archetype was removed from the world so the last matching archetype index needs to be
1835 // lowered by one for every component context.
1836 auto clearMatches = [](QueryArchetypeCacheIndexMap& matches) {
1837 for (auto& pair: matches) {
1838 auto& lastMatchedArchetypeIdx = pair.second;
1839 if (lastMatchedArchetypeIdx > 0)
1840 --lastMatchedArchetypeIdx;
1841 }
1842 };
1843 clearMatches(m_plan.ctx.data.lastMatchedArchetypeIdx_All);
1844 clearMatches(m_plan.ctx.data.lastMatchedArchetypeIdx_Or);
1845 clearMatches(m_plan.ctx.data.lastMatchedArchetypeIdx_Not);
1846 }
1847
1849 std::span<const uint8_t> indices_mapping_view(uint32_t archetypeIdx) const {
1850 const_cast<QueryInfo*>(this)->ensure_comp_indices();
1851 const auto& ctxData = m_state.exec.archetypeCompIndices[archetypeIdx];
1852 return {(const uint8_t*)&ctxData.indices[0], ChunkHeader::MAX_COMPONENTS};
1853 }
1854
1855 InheritedTermDataView inherited_data_view(uint32_t archetypeIdx) const {
1856 const_cast<QueryInfo*>(this)->ensure_inherited_data();
1857 if (archetypeIdx >= m_state.exec.archetypeInheritedData.size())
1858 return {};
1859 const auto& ctxData = m_state.exec.archetypeInheritedData[archetypeIdx];
1860 return {ctxData.data, ChunkHeader::MAX_COMPONENTS};
1861 }
1862
1863 InheritedTermDataView inherited_data_view(const Archetype* pArchetype) const {
1864 if (!has_inherited_data_payload())
1865 return {};
1866 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
1867 if (archetypeIdx == BadIndex)
1868 return {};
1869 return inherited_data_view((uint32_t)archetypeIdx);
1870 }
1871
1872 void ensure_depth_order_hierarchy_barrier_cache() {
1873 ensure_depth_order_hierarchy_barrier_cache_inter();
1874 }
1875
1878 if (m_state.exec.compIndicesPending)
1879 return {};
1880 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
1881 if (archetypeIdx == BadIndex)
1882 return {};
1883 return indices_mapping_view(archetypeIdx);
1884 }
1885
1886 InheritedTermDataView try_inherited_data_view(const Archetype* pArchetype) const {
1887 if (!has_inherited_data_payload() || m_state.exec.inheritedDataPending)
1888 return {};
1889 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
1890 if (archetypeIdx == BadIndex)
1891 return {};
1892 return inherited_data_view((uint32_t)archetypeIdx);
1893 }
1894
1895 GAIA_NODISCARD GroupId group_id(uint32_t archetypeIdx) const {
1896 const_cast<QueryInfo*>(this)->ensure_group_data();
1897 GAIA_ASSERT(archetypeIdx < m_state.grouped.archetypeGroupIds.size());
1898 return m_state.grouped.archetypeGroupIds[archetypeIdx];
1899 }
1900
1901 GAIA_NODISCARD bool barrier_passes(uint32_t archetypeIdx) const {
1902 const_cast<QueryInfo*>(this)->ensure_depth_order_hierarchy_barrier_cache();
1903 if (m_state.nonTrivial.archetypeBarrierPasses.empty())
1904 return true;
1905 GAIA_ASSERT(archetypeIdx < m_state.nonTrivial.archetypeBarrierPasses.size());
1906 return m_state.nonTrivial.archetypeBarrierPasses[archetypeIdx] != 0;
1907 }
1908
1909 GAIA_NODISCARD CArchetypeDArray::iterator begin() {
1910 return m_state.archetypeCache.begin();
1911 }
1912
1913 GAIA_NODISCARD CArchetypeDArray::const_iterator begin() const {
1914 return m_state.archetypeCache.begin();
1915 }
1916
1917 GAIA_NODISCARD CArchetypeDArray::const_iterator cbegin() const {
1918 return m_state.archetypeCache.begin();
1919 }
1920
1921 GAIA_NODISCARD CArchetypeDArray::iterator end() {
1922 return m_state.archetypeCache.end();
1923 }
1924
1925 GAIA_NODISCARD CArchetypeDArray::const_iterator end() const {
1926 return m_state.archetypeCache.end();
1927 }
1928
1929 GAIA_NODISCARD CArchetypeDArray::const_iterator cend() const {
1930 return m_state.archetypeCache.end();
1931 }
1932
1933 GAIA_NODISCARD std::span<const Archetype*> cache_archetype_view() const {
1934 return std::span{(const Archetype**)m_state.archetypeCache.data(), m_state.archetypeCache.size()};
1935 }
1936
1937 GAIA_NODISCARD std::span<const SortData> cache_sort_view() const {
1938 return std::span{m_state.nonTrivial.archetypeSortData.data(), m_state.nonTrivial.archetypeSortData.size()};
1939 }
1940
1941 GAIA_NODISCARD std::span<const GroupData> group_data_view() const {
1942 const_cast<QueryInfo*>(this)->ensure_group_data();
1943 return std::span{m_state.grouped.archetypeGroupData.data(), m_state.grouped.archetypeGroupData.size()};
1944 }
1945 };
1946 } // namespace ecs
1947} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Array of elements of type.
Definition sarray_ext_impl.h:27
Array of elements of type.
Definition sarray_impl.h:26
Definition span_impl.h:99
Definition archetype.h:83
Definition chunk.h:35
Definition query_info.h:86
void add_new_archetype_to_immediate_caches(const Archetype *pArchetype, bool trackMembershipChange)
Adds a newly matched archetype to both immediate caches while reusing one computed index mapping.
Definition query_info.h:1569
void remove(Archetype *pArchetype)
Removes an archetype from cache.
Definition query_info.h:1826
void calculate_sort_data()
Calculates the sort data for the archetypes in the cache. This allows us to iterate entites in the or...
Definition query_info.h:1206
void recompile()
Recompile the query.
Definition query_info.h:744
MatchArchetypeQueryRet
Query matching result.
Definition query_info.h:95
void match(const ArchetypeLookup &entityToArchetypeMap, std::span< const Archetype * > allArchetypes, ArchetypeId archetypeLastId, const cnt::sarray< Entity, MaxVarCnt > &runtimeVarBindings, uint8_t runtimeVarBindingMask)
Tries to match the query against archetypes in entityToArchetypeMap. This is necessary so we do not i...
Definition query_info.h:814
std::span< const uint8_t > try_indices_mapping_view(const Archetype *pArchetype) const
Returns a cached indices mapping view for an exact archetype match, or an empty span when absent.
Definition query_info.h:1877
GAIA_NODISCARD bool matches_prefab_entities() const
Returns true when prefab-tagged entities should participate in query results.
Definition query_info.h:1798
void invalidate_sort()
Marks the cached sorted slices dirty without invalidating query membership.
Definition query_info.h:686
InvalidationKind
Definition query_info.h:132
@ Result
Only the final result cache is stale. Structural seed matches remain valid and can be reused.
@ All
Full invalidation of all query cache state.
@ Seed
Structural seed matches are stale. This also implies the final result cache must be rebuilt.
uint32_t idx
Allocated items: index in the query slot list. Deleted items: index of the next deleted item in the s...
Definition query_info.h:90
GAIA_NODISCARD bool direct_create_archetype_match_uses_is() const
Returns whether direct create-time matching needs Is-aware id checks.
Definition query_info.h:779
void compile(const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes)
Compile the query terms into a form we can easily process.
Definition query_info.h:736
GAIA_NODISCARD bool can_use_direct_create_archetype_match() const
Returns whether create-time matching should bypass the temporary one-archetype VM path.
Definition query_info.h:774
bool match_one(const Archetype &archetype, EntitySpan targetEntities, const cnt::sarray< Entity, MaxVarCnt > &runtimeVarBindings, uint8_t runtimeVarBindingMask)
Tries to match the query against the provided archetype. This is necessary so we do not iterate all c...
Definition query_info.h:912
GAIA_NODISCARD bool has_entity_filter_terms() const
Returns true when direct non-fragmenting terms must be rechecked per entity.
Definition query_info.h:1784
uint32_t gen
Generation ID of the query slot.
Definition query_info.h:92
std::span< const uint8_t > indices_mapping_view(uint32_t archetypeIdx) const
Returns a view of indices mapping for component entities in a given archetype.
Definition query_info.h:1849
GAIA_NODISCARD const GroupData * selected_group_data(GroupId runtimeGroupId) const
Returns cached group bounds for the currently selected group filter. The cached range is invalidated ...
Definition query_info.h:1634
Definition world.h:88
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:218
Definition vm.h:2284
Definition robin_hood.h:720
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition query_info.h:30
Definition query_info.h:34
Definition query_match_stamps.h:13
void clear()
Definition query_match_stamps.h:64
static constexpr uint32_t MAX_COMPONENTS
Maximum number of components on archetype.
Definition chunk_header.h:53
Definition id.h:241
Dependencies deps
Explicit dependency metadata derived from query shape.
Definition query_common.h:663
Definition query_common.h:471
Definition query_info.h:80
bool inheritedDataPending
True when archetype membership is populated but inherited-term data still needs to be built on demand...
Definition query_info.h:199
bool compIndicesPending
True when archetype membership is populated but component-index metadata still needs to be built on d...
Definition query_info.h:196
cnt::darray< ArchetypeCompIndices > archetypeCompIndices
Cached component-index mapping for each matched archetype.
Definition query_info.h:191
cnt::darray< ArchetypeInheritedData > archetypeInheritedData
Cached inherited component data pointer per query field for exact self-source semantic terms.
Definition query_info.h:193
cnt::darray< GroupData > archetypeGroupData
Group data used by cache.
Definition query_info.h:164
bool selectedGroupDataValid
True when selectedGroupData matches the active group filter.
Definition query_info.h:168
GroupData selectedGroupData
Cached range for the currently selected group id.
Definition query_info.h:166
cnt::darray< GroupId > archetypeGroupIds
Group ids for grouped queries, aligned with archetypeCache.
Definition query_info.h:162
bool dataPending
True when grouped archetype order/ranges need to be rebuilt.
Definition query_info.h:170
uint32_t barrierEnabledVersion
Entity enable-state version at which the cached depth-order hierarchy barrier state was last rebuilt.
Definition query_info.h:227
cnt::darray< uint8_t > archetypeBarrierPasses
Cached depth-order hierarchy barrier result for each archetype.
Definition query_info.h:218
cnt::darray< SortData > archetypeSortData
Sort data used by cache.
Definition query_info.h:220
uint32_t barrierRelVersion
Relation topology version at which the cached depth-order hierarchy barrier state was last rebuilt.
Definition query_info.h:225
uint32_t sortVersion
World version at which the sorted cache slices were last rebuilt. Unlike worldVersion,...
Definition query_info.h:223
Definition query_info.h:38
cnt::darr< const Archetype * > matchesArr
Ordered list of matched archetypes emitted by the VM for the current run.
Definition query_info.h:40
uint32_t matchVersion
Monotonic dedup stamp used when the same scratch frame is reused by later full match() calls without ...
Definition query_info.h:47
void clear_temporary_matches_keep_stamps()
Definition query_info.h:55
ArchetypeMatchStamps matchStamps
Paged O(1) dedup table keyed by world-local archetype ids. Pages stay allocated on the scratch frame ...
Definition query_info.h:44
Definition vm.h:55