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#include "gaia/mem/smallblock_allocator.h"
20
21namespace gaia {
22 namespace ecs {
23 struct Entity;
24 class World;
25
26 uint32_t world_version(const World& world);
27 Entity world_query_first_inherited_owner(const World& world, const Archetype& archetype, Entity term);
28 const void* world_query_inherited_arg_data_const_ptr(const World& world, Entity owner, Entity id);
29
30 using EntityToArchetypeMap = cnt::map<EntityLookupKey, ComponentIndexEntryArray>;
32 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
33 };
34
36 const void* data[ChunkHeader::MAX_COMPONENTS];
37 };
38
42 GAIA_USE_SMALLBLOCK(QueryMatchScratch)
43
44
45 cnt::darr<const Archetype*> matchesArr;
51 cnt::darray<const Archetype*> matchesArrDynPrev;
54 uint32_t matchVersion = 0;
55
58 matchesArr.clear();
59 matchesArrDynPrev.clear();
61 matchVersion = 0;
62 }
63
71
73 void reset_stamps() {
75 matchVersion = 0;
76 }
77
80 GAIA_NODISCARD uint32_t next_match_version() {
82 if (matchVersion != 0)
83 return matchVersion;
84
86 matchVersion = 1;
87 return matchVersion;
88 }
89 };
90
91 GAIA_NODISCARD QueryMatchScratch& query_match_scratch_acquire(World& world);
92 void query_match_scratch_release(World& world, bool keepStamps);
93
95 QueryCtx* pQueryCtx;
96 const EntityToArchetypeMap* pEntityToArchetypeMap;
97 std::span<const Archetype*> allArchetypes;
98 };
99
100 class QueryInfo {
101 public:
104 uint32_t idx = 0;
106 uint32_t gen = 0;
107
109 enum class MatchArchetypeQueryRet : uint8_t { Fail, Ok, Skip };
110
111 private:
112 struct Instruction {
113 Entity id;
114 QueryOpKind op;
115 };
116
117 struct SortData {
118 Chunk* pChunk;
119 uint32_t archetypeIdx;
120 uint16_t startRow;
121 uint16_t count;
122 };
123
124 struct GroupData {
125 GroupId groupId;
126 uint32_t idxFirst;
127 uint32_t idxLast;
128 bool needsSorting;
129 };
130
131 struct SrcTravSnapshotItem {
132 Entity entity = EntityBad;
134 uint32_t sourceVersion = 0;
135
136 GAIA_NODISCARD bool operator==(const SrcTravSnapshotItem& other) const {
137 return entity == other.entity && sourceVersion == other.sourceVersion;
138 }
139
140 GAIA_NODISCARD bool operator!=(const SrcTravSnapshotItem& other) const {
141 return !operator==(other);
142 }
143 };
144
145 public:
146 enum class InvalidationKind : uint8_t {
148 Result,
150 Seed,
152 All
153 };
154
155 private:
156 struct QueryPlan {
157 QueryCtx ctx;
159 };
160
161 struct QueryState {
162 enum DirtyFlags : uint8_t { Clean = 0x00, Seed = 0x01, Result = 0x02, All = Seed | Result };
163
165 cnt::set<const Archetype*> seedArchetypeSet;
166 CArchetypeDArray seedArchetypeCache;
167
170 cnt::set<const Archetype*> archetypeSet;
172 CArchetypeDArray archetypeCache;
173
180 mutable GroupData selectedGroupData{};
182 mutable bool selectedGroupDataValid = false;
184 bool dataPending = false;
185
187 void clear() {
192 dataPending = false;
193 }
194
197 archetypeGroupIds.clear();
198 archetypeGroupData.clear();
201 dataPending = false;
202 }
203 };
204
233
241 uint32_t sortVersion{};
243 uint32_t barrierRelVersion = UINT32_MAX;
245 uint32_t barrierEnabledVersion = UINT32_MAX;
247 uint8_t barrierMayPrune = 0;
248
250 void clear() {
253 sortVersion = 0;
254 barrierRelVersion = UINT32_MAX;
255 barrierEnabledVersion = UINT32_MAX;
256 barrierMayPrune = 0;
257 }
258
261 archetypeSortData.clear();
263 sortVersion = 0;
264 barrierRelVersion = UINT32_MAX;
265 barrierEnabledVersion = UINT32_MAX;
266 barrierMayPrune = 0;
267 }
268 };
269
271 ExecPayload exec;
273 GroupedPayload grouped;
275 NonTrivialPayload nonTrivial;
276
282
284 GAIA_NODISCARD bool changed(const World& world, std::span<const Entity> relations) const {
285 const auto cnt = (uint32_t)relations.size();
286 GAIA_FOR(cnt) {
287 if (versions[i] != world_rel_version(world, relations[i]))
288 return true;
289 }
290
291 return false;
292 }
293
295 void snapshot(const World& world, std::span<const Entity> relations) {
296 const auto cnt = (uint32_t)relations.size();
297 GAIA_FOR(cnt)
298 versions[i] = world_rel_version(world, relations[i]);
299 }
300 };
301
306
308 GAIA_NODISCARD bool changed(const World& world, std::span<const Entity> sourceEntities) const {
309 const auto cnt = (uint32_t)sourceEntities.size();
310 GAIA_FOR(cnt) {
311 if (entityVersions[i] != world_entity_archetype_version(world, sourceEntities[i]))
312 return true;
313 }
314
315 return false;
316 }
317
319 void snapshot(const World& world, std::span<const Entity> sourceEntities) {
320 const auto cnt = (uint32_t)sourceEntities.size();
321 GAIA_FOR(cnt)
322 entityVersions[i] = world_entity_archetype_version(world, sourceEntities[i]);
323 }
324 };
325
331 bool overflowed = false;
332
334 void clear() {
335 snapshot.clear();
336 overflowed = false;
337 }
338
340 GAIA_NODISCARD bool is_overflowed() const {
341 return overflowed;
342 }
343
345 GAIA_NODISCARD bool versions_changed(const World& world) const {
346 const auto cnt = (uint32_t)snapshot.size();
347 GAIA_FOR(cnt) {
348 const auto& item = snapshot[i];
349 if (item.sourceVersion != world_entity_archetype_version(world, item.entity))
350 return true;
351 }
352
353 return false;
354 }
355
357 GAIA_NODISCARD bool changed(const cnt::darray<SrcTravSnapshotItem>& items) const {
358 if (items.size() != snapshot.size())
359 return true;
360
361 const auto cnt = (uint32_t)snapshot.size();
362 GAIA_FOR(cnt) {
363 if (items[i] != snapshot[i])
364 return true;
365 }
366
367 return false;
368 }
369
372 snapshot = items;
373 overflowed = false;
374 }
375
378 snapshot.clear();
379 overflowed = true;
380 }
381 };
382
388 uint8_t bindingMask = 0;
389
391 void clear() {
392 bindingMask = 0;
393 }
394
396 GAIA_NODISCARD bool
397 changed(const cnt::sarray<Entity, MaxVarCnt>& runtimeBindings, uint8_t runtimeBindingMask) const {
398 if (bindingMask != runtimeBindingMask)
399 return true;
400 if (runtimeBindingMask == 0)
401 return false;
402
403 GAIA_FOR(MaxVarCnt) {
404 const auto bit = (uint8_t(1) << i);
405 if ((runtimeBindingMask & bit) == 0)
406 continue;
407
408 if (bindings[i] != runtimeBindings[i])
409 return true;
410 }
411
412 return false;
413 }
414
416 void snapshot(const cnt::sarray<Entity, MaxVarCnt>& runtimeBindings, uint8_t runtimeBindingMask) {
417 bindings = runtimeBindings;
418 bindingMask = runtimeBindingMask;
419 }
420 };
421
430
436 };
437
439 DynamicCacheState dynamic;
440
442 ArchetypeId lastArchetypeId{};
445 uint32_t resultCacheRevision = 1;
447 uint8_t dirtyFlags = DirtyFlags::All;
448
450 void clear_seed_cache() {
451 seedArchetypeSet = {};
452 seedArchetypeCache = {};
453 }
454
456 void clear_result_cache() {
457 archetypeSet = {};
458 archetypeCache = {};
459 exec.clear();
460 grouped.clear();
461 nonTrivial.clear();
462 }
463
465 void clear_transient_result_cache() {
466 archetypeCache.clear();
467 exec.clear_transient();
468 grouped.clear_transient();
469 nonTrivial.clear_transient();
470 }
471
473 void clear_cache() {
474 clear_seed_cache();
475 clear_result_cache();
476 }
477
479 void reset() {
480 clear_cache();
481 dynamic.clear_input_snapshots();
482 lastArchetypeId = 0;
483 dirtyFlags = DirtyFlags::All;
484 }
485
487 void invalidate_seed() {
488 dirtyFlags = (uint8_t)(dirtyFlags | DirtyFlags::Seed | DirtyFlags::Result);
489 }
490
492 void invalidate_result() {
493 dirtyFlags = (uint8_t)(dirtyFlags | DirtyFlags::Result);
494 }
495
497 void invalidate_all() {
498 dirtyFlags = DirtyFlags::All;
499 }
500
502 GAIA_NODISCARD bool seed_dirty() const {
503 return (dirtyFlags & DirtyFlags::Seed) != 0;
504 }
505
507 GAIA_NODISCARD bool result_dirty() const {
508 return (dirtyFlags & DirtyFlags::Result) != 0;
509 }
510
512 GAIA_NODISCARD bool needs_refresh() const {
513 return seed_dirty() || result_dirty();
514 }
515
517 void clear_dirty() {
518 dirtyFlags = DirtyFlags::Clean;
519 }
520 };
521
522 uint32_t m_refs = 0;
523#if GAIA_ECS_TEST_HOOKS
525 uint32_t m_testMatchPassCount = 0;
526#endif
527
528 QueryPlan m_plan;
529 QueryState m_state;
530
531 enum QueryCmdType : uint8_t { ALL, OR, NOT };
532
535 void reset_matching_cache(bool trackMembershipChange) {
536 if (trackMembershipChange && !m_state.archetypeCache.empty())
537 mark_result_cache_membership_changed();
538
539 m_state.reset();
540
541 auto& ctxData = m_plan.ctx.data;
542 ctxData.lastMatchedArchetypeIdx_All = {};
543 ctxData.lastMatchedArchetypeIdx_Or = {};
544 ctxData.lastMatchedArchetypeIdx_Not = {};
545 }
546
548 void clear_result_cache() {
549 m_state.clear_result_cache();
550 }
551
554 void invalidate_result_barriers() {
555 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
556 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
557 }
558
561 void mark_result_cache_membership_changed() {
562 invalidate_result_barriers();
563 ++m_state.resultCacheRevision;
564 if (m_state.resultCacheRevision != 0)
565 return;
566
567 // Reserve 0 as the "never synced" value in QueryCache.
568 m_state.resultCacheRevision = 1;
569 }
570
572 GAIA_NODISCARD bool has_dyn_terms() const {
573 return m_plan.ctx.data.cachePolicy == QueryCtx::CachePolicy::Dynamic;
574 }
575
577 GAIA_NODISCARD bool can_reuse_dyn_cache() const {
578 return m_plan.ctx.data.canReuseDynamicCache;
579 }
580
582 GAIA_NODISCARD bool uses_direct_src_version_tracking() const {
583 return m_plan.ctx.data.uses_direct_src_version_tracking();
584 }
585
587 GAIA_NODISCARD bool uses_src_trav_snapshot() const {
588 return m_plan.ctx.data.uses_src_trav_snapshot();
589 }
590
592 GAIA_NODISCARD bool dyn_rel_versions_changed() const {
593 return m_state.dynamic.relation.changed(*world(), m_plan.ctx.data.deps.relations_view());
594 }
595
597 GAIA_NODISCARD bool dyn_var_bindings_changed(
598 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) const {
599 return m_state.dynamic.variable.changed(runtimeVarBindings, runtimeVarBindingMask);
600 }
601
603 GAIA_NODISCARD bool direct_src_versions_changed() const {
604 return m_state.dynamic.directSource.changed(*world(), m_plan.ctx.data.deps.src_entities_view());
605 }
606
610 template <typename Func>
611 void each_reusable_src_entity(Func&& func) const {
612 const auto terms = m_plan.ctx.data.terms_view();
613 const auto cnt = (uint32_t)terms.size();
614 GAIA_FOR(cnt) {
615 const auto& term = terms[i];
616 if (term.src == EntityBad || is_variable(term.src))
617 continue;
618
619 (void)vm::detail::each_lookup_src(*world(), term, term.src, [&](Entity source) {
620 return func(source);
621 });
622 }
623 }
624
627 GAIA_NODISCARD bool build_src_trav_snapshot(cnt::darray<SrcTravSnapshotItem>& items) const {
628 const auto maxItems = (uint32_t)m_plan.ctx.data.cacheSrcTrav;
629 items.clear();
630 if (maxItems == 0)
631 return false;
632
633 bool overflowed = false;
634 each_reusable_src_entity([&](Entity source) {
635 if (items.size() >= maxItems) {
636 overflowed = true;
637 return true;
638 }
639 items.push_back({source, world_entity_archetype_version(*world(), source)});
640 return false;
641 });
642
643 return !overflowed;
644 }
645
648 GAIA_NODISCARD bool traversed_src_versions_changed() const {
649 return m_state.dynamic.traversedSource.versions_changed(*world());
650 }
651
653 GAIA_NODISCARD bool traversed_src_inputs_changed(bool relationVersionsChanged) {
654 if (m_state.dynamic.traversedSource.is_overflowed())
655 return true;
656
657 if (!relationVersionsChanged)
658 return traversed_src_versions_changed();
659
660 cnt::darray<SrcTravSnapshotItem> scratch;
661 if (!build_src_trav_snapshot(scratch))
662 return true;
663
664 return m_state.dynamic.traversedSource.changed(scratch);
665 }
666
668 GAIA_NODISCARD bool mixed_dyn_inputs_changed(
669 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
670 const auto& deps = m_plan.ctx.data.deps;
671 if (deps.has_dep_flag(QueryCtx::DependencyHasVariableTerms) &&
672 dyn_var_bindings_changed(runtimeVarBindings, runtimeVarBindingMask))
673 return true;
674
675 const bool relationVersionsChanged = dyn_rel_versions_changed();
676 const bool hasSourceTerms = deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms);
677 const bool hasTraversalTerms = deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms);
678 if (relationVersionsChanged && !(hasSourceTerms && hasTraversalTerms))
679 return true;
680
681 if (!hasSourceTerms)
682 return relationVersionsChanged;
683
684 if (m_state.dynamic.traversedSource.is_overflowed())
685 return true;
686
687 if (!hasTraversalTerms)
688 return direct_src_versions_changed();
689
690 return traversed_src_inputs_changed(relationVersionsChanged);
691 }
692
694 GAIA_NODISCARD bool
695 dyn_inputs_changed(const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
696 if (!can_reuse_dyn_cache())
697 return false;
698
699 switch (m_plan.ctx.data.dynamicCacheKind) {
701 return false;
703 return dyn_rel_versions_changed();
705 return direct_src_versions_changed();
707 return traversed_src_inputs_changed(dyn_rel_versions_changed());
709 return dyn_var_bindings_changed(runtimeVarBindings, runtimeVarBindingMask);
711 return mixed_dyn_inputs_changed(runtimeVarBindings, runtimeVarBindingMask);
712 }
713
714 GAIA_ASSERT(false);
715 return true;
716 }
717
719 void snapshot_dyn_rel_inputs() {
720 m_state.dynamic.relation.snapshot(*world(), m_plan.ctx.data.deps.relations_view());
721 }
722
724 void snapshot_dyn_direct_src_inputs() {
725 m_state.dynamic.directSource.snapshot(*world(), m_plan.ctx.data.deps.src_entities_view());
726 }
727
729 void snapshot_dyn_traversed_src_inputs() {
730 cnt::darray<SrcTravSnapshotItem> scratch;
731 if (build_src_trav_snapshot(scratch))
732 m_state.dynamic.traversedSource.capture(scratch);
733 else
734 m_state.dynamic.traversedSource.mark_overflowed();
735 }
736
738 void
739 snapshot_dyn_var_inputs(const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
740 m_state.dynamic.variable.snapshot(runtimeVarBindings, runtimeVarBindingMask);
741 }
742
744 void
745 snapshot_dyn_inputs(const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
746 if (!can_reuse_dyn_cache())
747 return;
748
749 m_state.dynamic.clear_input_snapshots();
750
751 switch (m_plan.ctx.data.dynamicCacheKind) {
753 return;
755 snapshot_dyn_rel_inputs();
756 return;
758 snapshot_dyn_direct_src_inputs();
759 return;
761 snapshot_dyn_rel_inputs();
762 snapshot_dyn_traversed_src_inputs();
763 return;
765 snapshot_dyn_var_inputs(runtimeVarBindings, runtimeVarBindingMask);
766 return;
768 const auto& deps = m_plan.ctx.data.deps;
769 snapshot_dyn_rel_inputs();
770 if (deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms)) {
771 if (deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms))
772 snapshot_dyn_traversed_src_inputs();
773 else
774 snapshot_dyn_direct_src_inputs();
775 }
776 if (deps.has_dep_flag(QueryCtx::DependencyHasVariableTerms))
777 snapshot_dyn_var_inputs(runtimeVarBindings, runtimeVarBindingMask);
778 return;
779 }
780 }
781
782 GAIA_ASSERT(false);
783 }
788 template <typename TType>
789 GAIA_NODISCARD bool has_inter([[maybe_unused]] QueryOpKind op, bool isReadWrite) const {
790 using T = core::raw_t<TType>;
791
792 if constexpr (std::is_same_v<T, Entity>) {
793 // Entities are read-only.
794 GAIA_ASSERT(!isReadWrite);
795 // Skip Entity input args. Entities are always there.
796 return true;
797 } else {
798 Entity id;
799
800 if constexpr (is_pair<T>::value) {
801 const auto rel = m_plan.ctx.cc->get<typename T::rel>().entity;
802 const auto tgt = m_plan.ctx.cc->get<typename T::tgt>().entity;
803 id = (Entity)Pair(rel, tgt);
804 } else {
805 id = m_plan.ctx.cc->get<T>().entity;
806 }
807
808 const auto& ctxData = m_plan.ctx.data;
809 const auto compIdx = comp_idx<MAX_ITEMS_IN_QUERY>(ctxData.terms.data(), id, EntityBad);
810
811 if (op != ctxData.terms[compIdx].op)
812 return false;
813
814 // Read-write mask must match
815 const uint32_t maskRW = (uint32_t)ctxData.readWriteMask & (1U << compIdx);
816 const uint32_t maskXX = (uint32_t)isReadWrite << compIdx;
817 return maskRW == maskXX;
818 }
819 }
820
824 template <typename T>
825 GAIA_NODISCARD bool has_inter(QueryOpKind op) const {
826 // static_assert(is_raw_v<<T>, "has() must be used with raw types");
827 constexpr bool isReadWrite = core::is_mut_v<T>;
828 return has_inter<T>(op, isReadWrite);
829 }
830
831 public:
833 void add_ref() {
834 ++m_refs;
835 GAIA_ASSERT(m_refs != 0);
836 }
837
839 void dec_ref() {
840 GAIA_ASSERT(m_refs > 0);
841 --m_refs;
842 }
843
845 uint32_t refs() const {
846 return m_refs;
847 }
848
851 void init(World* world) {
852 m_plan.ctx.w = world;
853 }
854
856 void reset() {
857 reset_matching_cache(true);
858 }
859
863 switch (kind) {
865 m_state.invalidate_result();
866 invalidate_result_barriers();
867 break;
869 m_state.invalidate_seed();
870 break;
872 m_state.invalidate_all();
873 break;
874 }
875 }
876
881
886
889 if (m_plan.ctx.data.sortByFunc != nullptr)
890 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortEntities;
891 }
892
898 GAIA_NODISCARD static QueryInfo create(
899 QueryId id, QueryCtx&& ctx, const EntityToArchetypeMap& entityToArchetypeMap,
900 std::span<const Archetype*> allArchetypes) {
901 // Make sure query items are sorted
902 sort(ctx);
903
904 QueryInfo info;
905 info.idx = id;
906 info.gen = 0;
907
908 info.m_plan.ctx = GAIA_MOV(ctx);
909 info.m_plan.ctx.q.handle = {id, 0};
910
911 // Compile the query
912 info.compile(entityToArchetypeMap, allArchetypes);
913
914 return info;
915 }
916
921 GAIA_NODISCARD static QueryInfo create(uint32_t idx, uint32_t gen, void* pCtx) {
922 auto* pCreationCtx = (QueryInfoCreationCtx*)pCtx;
923 auto& queryCtx = *pCreationCtx->pQueryCtx;
924 auto& entityToArchetypeMap = (EntityToArchetypeMap&)*pCreationCtx->pEntityToArchetypeMap;
925
926 // Make sure query items are sorted
927 sort(queryCtx);
928
929 QueryInfo info;
930 info.idx = idx;
931 info.gen = gen;
932
933 info.m_plan.ctx = GAIA_MOV(queryCtx);
934 info.m_plan.ctx.q.handle = {idx, gen};
935
936 // Compile the query
937 info.compile(entityToArchetypeMap, pCreationCtx->allArchetypes);
938
939 return info;
940 }
941
943 GAIA_NODISCARD static QueryHandle handle(const QueryInfo& info) {
944 return QueryHandle(info.idx, info.gen);
945 }
946
948 void compile(const EntityToArchetypeMap& entityToArchetypeMap, std::span<const Archetype*> allArchetypes) {
949 GAIA_PROF_SCOPE(queryinfo::compile);
950
951 // Compile the opcodes
952 m_plan.vm.compile(entityToArchetypeMap, allArchetypes, m_plan.ctx);
953 }
954
956 void recompile() {
957 GAIA_PROF_SCOPE(queryinfo::recompile);
958
959 // Compile the opcodes
960 m_plan.vm.create_opcodes(m_plan.ctx);
961 }
962
964 GAIA_NODISCARD QueryCtx::CachePolicy cache_policy() const {
965 return m_plan.ctx.data.cachePolicy;
966 }
967
969 GAIA_NODISCARD bool has_grouped_payload() const {
970 return m_plan.ctx.data.groupBy != EntityBad;
971 }
972
974 GAIA_NODISCARD bool has_sorted_payload() const {
975 return m_plan.ctx.data.sortByFunc != nullptr;
976 }
977
979 GAIA_NODISCARD uint32_t result_cache_rev() const {
980 return m_state.resultCacheRevision;
981 }
982
984 GAIA_NODISCARD bool can_update_with_new_archetype() const {
985 // Only immediate structural queries participate in archetype-create propagation.
986 return m_plan.vm.is_compiled() && cache_policy() == QueryCtx::CachePolicy::Immediate &&
987 !m_state.needs_refresh();
988 }
989
991 GAIA_NODISCARD bool can_use_direct_create_archetype_match() const {
992 return m_plan.ctx.data.createArchetypeMatchKind == QueryCtx::CreateArchetypeMatchKind::DirectStructuralTerms;
993 }
994
996 GAIA_NODISCARD bool direct_create_archetype_match_uses_is() const {
997 const auto& ctxData = m_plan.ctx.data;
998 return (ctxData.as_mask_0 + ctxData.as_mask_1) != 0;
999 }
1000
1001 GAIA_NODISCARD bool operator==(const QueryCtx& other) const {
1002 return m_plan.ctx == other;
1003 }
1004
1005 GAIA_NODISCARD bool operator!=(const QueryCtx& other) const {
1006 return m_plan.ctx != other;
1007 }
1008
1010 World& world;
1011 bool keepStamps;
1012
1013 explicit CleanUpTmpArchetypeMatches(World& world, bool keepStamps): world(world), keepStamps(keepStamps) {}
1016 CleanUpTmpArchetypeMatches& operator=(const CleanUpTmpArchetypeMatches&) = delete;
1018
1020 query_match_scratch_release(world, keepStamps);
1021 }
1022 };
1023
1034 template <typename ArchetypeLookup>
1035 void match(
1036 const ArchetypeLookup& entityToArchetypeMap, std::span<const Archetype*> allArchetypes,
1037 const EntityToArchetypeVersionMap* pEntityToArchetypeMapVersions, ArchetypeId archetypeLastId,
1038 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1039 auto& ctxData = m_plan.ctx.data;
1040
1041 // Recompile if necessary
1042 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1043 recompile();
1044
1045 // Skip if nothing has been compiled.
1046 if (!m_plan.vm.is_compiled())
1047 return;
1048
1049 const bool hasDynamicTerms = has_dyn_terms();
1050 const bool canReuseDynamicCache = can_reuse_dyn_cache();
1051 const bool refreshDynamicCache =
1052 hasDynamicTerms && (!canReuseDynamicCache || m_state.needs_refresh() ||
1053 dyn_inputs_changed(runtimeVarBindings, runtimeVarBindingMask));
1054 bool compareDynamicMembership = false;
1055 QueryMatchScratch* pMatchScratch = nullptr;
1056
1057 if (refreshDynamicCache) {
1058 compareDynamicMembership = !m_state.archetypeCache.empty();
1059 if (compareDynamicMembership) {
1060 auto& w = *world();
1061 pMatchScratch = &query_match_scratch_acquire(w);
1062 pMatchScratch->matchesArrDynPrev = m_state.archetypeCache;
1063 }
1064 // Dynamic queries keep their cached result as long as tracked runtime inputs stay stable.
1065 reset_matching_cache(!compareDynamicMembership);
1066 } else if (m_state.seed_dirty()) {
1067 reset_matching_cache(true);
1068 } else if (m_state.result_dirty()) {
1070 if (m_state.lastArchetypeId == archetypeLastId) {
1071 sort_entities();
1073 m_state.clear_dirty();
1074 return;
1075 }
1076 }
1077
1078 // Skip if no new archetype appeared and the cached match set is still valid.
1079 GAIA_ASSERT(archetypeLastId >= m_state.lastArchetypeId);
1080 if (!m_state.needs_refresh() && m_state.lastArchetypeId == archetypeLastId &&
1081 (!hasDynamicTerms || canReuseDynamicCache)) {
1082 // Sort entities if necessary
1083 sort_entities();
1084 return;
1085 }
1086
1087 m_state.lastArchetypeId = archetypeLastId;
1088
1089 GAIA_PROF_SCOPE(queryinfo::match);
1090
1091 auto& w = *world();
1092 auto& matchScratch = pMatchScratch != nullptr ? *pMatchScratch : query_match_scratch_acquire(w);
1093 CleanUpTmpArchetypeMatches autoCleanup(w, true);
1094
1095 // Prepare the context
1097 ctx.pWorld = &w;
1098 // ctx.targetEntities = {};
1099 ctx.allArchetypes = allArchetypes;
1100 if constexpr (std::is_same_v<ArchetypeLookup, EntityToArchetypeMap>) {
1101 GAIA_ASSERT(pEntityToArchetypeMapVersions != nullptr);
1102 ctx.archetypeLookup = vm::make_archetype_lookup_view(entityToArchetypeMap, *pEntityToArchetypeMapVersions);
1103 } else {
1104 (void)pEntityToArchetypeMapVersions;
1105 ctx.archetypeLookup = vm::make_archetype_lookup_view(entityToArchetypeMap);
1106 }
1107
1108 ctx.pMatchesArr = &matchScratch.matchesArr;
1109 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
1110 ctx.matchesVersion = matchScratch.next_match_version();
1111 ctx.pLastMatchedArchetypeIdx_All = &ctxData.lastMatchedArchetypeIdx_All;
1112 ctx.pLastMatchedArchetypeIdx_Or = &ctxData.lastMatchedArchetypeIdx_Or;
1113 ctx.pLastMatchedArchetypeIdx_Not = &ctxData.lastMatchedArchetypeIdx_Not;
1114 ctx.queryMask = ctxData.queryMask;
1115 ctx.as_mask_0 = ctxData.as_mask_0;
1116 ctx.as_mask_1 = ctxData.as_mask_1;
1117 ctx.flags = ctxData.flags;
1118 ctx.varBindings = runtimeVarBindings;
1119 ctx.varBindingMask = runtimeVarBindingMask;
1120
1121 // Run the virtual machine
1122#if GAIA_ECS_TEST_HOOKS
1123 ++m_testMatchPassCount;
1124#endif
1125 m_plan.vm.exec(ctx);
1126
1127 // Write found matches to cache
1128 const bool trackMembershipChangeOnAdd = !compareDynamicMembership;
1129 for (const auto* pArchetype: *ctx.pMatchesArr) {
1130 if (hasDynamicTerms) {
1131 add_archetype_to_cache(pArchetype, trackMembershipChangeOnAdd, false);
1132 } else {
1133 add_archetype_to_seed_cache(pArchetype);
1134 add_archetype_to_cache(pArchetype, true, false);
1135 }
1136 }
1137
1138 if (compareDynamicMembership) {
1139 if (!has_same_result_membership_as(matchScratch.matchesArrDynPrev))
1140 mark_result_cache_membership_changed();
1141 }
1142
1143 // Sort entities if necessary
1144 sort_entities();
1145 // Sort cache groups if necessary
1147 snapshot_dyn_inputs(runtimeVarBindings, runtimeVarBindingMask);
1148 m_state.clear_dirty();
1149 }
1150
1159 const Archetype& archetype, EntitySpan targetEntities,
1160 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1161 auto& ctxData = m_plan.ctx.data;
1162
1163 // Recompile if necessary
1164 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1165 recompile();
1166
1167 // Skip if nothing has been compiled.
1168 if (!m_plan.vm.is_compiled())
1169 return false;
1170
1171 const bool hasDynamicTerms = has_dyn_terms();
1172 const bool canReuseDynamicCache = can_reuse_dyn_cache();
1173 if ((hasDynamicTerms && (!canReuseDynamicCache || m_state.needs_refresh() ||
1174 dyn_inputs_changed(runtimeVarBindings, runtimeVarBindingMask))) ||
1175 m_state.seed_dirty()) {
1176 // Dynamic queries keep their cached result as long as tracked runtime inputs stay stable.
1177 reset_matching_cache(true);
1178 } else if (m_state.result_dirty()) {
1180 }
1181
1182 GAIA_PROF_SCOPE(queryinfo::match1);
1183
1184 auto& w = *world();
1185 auto& matchScratch = query_match_scratch_acquire(w);
1186 CleanUpTmpArchetypeMatches autoCleanup(w, true);
1187
1188 // Prepare the context
1190 ctx.pWorld = &w;
1191 ctx.targetEntities = targetEntities;
1192 const auto* pArchetype = &archetype;
1193 ctx.allArchetypes = std::span((const Archetype**)&pArchetype, 1);
1194 ctx.archetypeLookup = {};
1195 ctx.pMatchesArr = &matchScratch.matchesArr;
1196 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
1197 ctx.matchesVersion = matchScratch.next_match_version();
1198 ctx.pLastMatchedArchetypeIdx_All = nullptr;
1199 ctx.pLastMatchedArchetypeIdx_Or = nullptr;
1200 ctx.pLastMatchedArchetypeIdx_Not = nullptr;
1201 ctx.queryMask = ctxData.queryMask;
1202 ctx.as_mask_0 = ctxData.as_mask_0;
1203 ctx.as_mask_1 = ctxData.as_mask_1;
1204 ctx.flags = ctxData.flags;
1205 ctx.varBindings = runtimeVarBindings;
1206 ctx.varBindingMask = runtimeVarBindingMask;
1207
1208 // Run the virtual machine
1209#if GAIA_ECS_TEST_HOOKS
1210 ++m_testMatchPassCount;
1211#endif
1212 m_plan.vm.exec(ctx);
1213 const bool matched = !ctx.pMatchesArr->empty();
1214
1215 // Write found matches to cache
1216 for (const auto* pArch: *ctx.pMatchesArr) {
1217 if (hasDynamicTerms) {
1218 add_archetype_to_cache(pArch, true, false);
1219 } else {
1221 add_archetype_to_cache(pArch, true, false);
1222 }
1223 }
1224 snapshot_dyn_inputs(runtimeVarBindings, runtimeVarBindingMask);
1225 m_state.clear_dirty();
1226 return matched;
1227 }
1228
1229 void ensure_matches(
1230 const EntityToArchetypeMap& entityToArchetypeMap, std::span<const Archetype*> allArchetypes,
1231 const EntityToArchetypeVersionMap& entityToArchetypeMapVersions, ArchetypeId archetypeLastId,
1232 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1233 match(
1234 entityToArchetypeMap, allArchetypes, &entityToArchetypeMapVersions, archetypeLastId, runtimeVarBindings,
1235 runtimeVarBindingMask);
1236 }
1237
1238 void ensure_matches_transient(
1239 const EntityToArchetypeMap& entityToArchetypeMap, std::span<const Archetype*> allArchetypes,
1240 const EntityToArchetypeVersionMap& entityToArchetypeMapVersions,
1241 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1242 auto& ctxData = m_plan.ctx.data;
1243
1244 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1245 recompile();
1246
1247 if (!m_plan.vm.is_compiled())
1248 return;
1249
1250 m_state.clear_transient_result_cache();
1251
1252 auto& w = *world();
1253 auto& matchScratch = query_match_scratch_acquire(w);
1254 CleanUpTmpArchetypeMatches autoCleanup(w, true);
1255
1256 vm::MatchingCtx ctx{};
1257 ctx.pWorld = &w;
1258 ctx.allArchetypes = allArchetypes;
1259 ctx.archetypeLookup = vm::make_archetype_lookup_view(entityToArchetypeMap, entityToArchetypeMapVersions);
1260 ctx.pMatchesArr = &matchScratch.matchesArr;
1261 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
1262 ctx.matchesVersion = matchScratch.next_match_version();
1263 ctx.pLastMatchedArchetypeIdx_All = nullptr;
1264 ctx.pLastMatchedArchetypeIdx_Or = nullptr;
1265 ctx.pLastMatchedArchetypeIdx_Not = nullptr;
1266 ctx.queryMask = ctxData.queryMask;
1267 ctx.as_mask_0 = ctxData.as_mask_0;
1268 ctx.as_mask_1 = ctxData.as_mask_1;
1269 ctx.flags = ctxData.flags;
1270 ctx.varBindings = runtimeVarBindings;
1271 ctx.varBindingMask = runtimeVarBindingMask;
1272
1273 m_plan.vm.exec(ctx);
1274
1275 m_state.archetypeCache.reserve(ctx.pMatchesArr->size());
1276 if (ctxData.groupBy != EntityBad)
1277 m_state.grouped.archetypeGroupIds.reserve(ctx.pMatchesArr->size());
1278 for (const auto* pArchetype: *ctx.pMatchesArr)
1280
1281 sort_entities();
1282 ensure_group_data(false);
1283 }
1284
1285 bool ensure_matches_one(
1286 const Archetype& archetype, EntitySpan targetEntities,
1287 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1288 return match_one(archetype, targetEntities, runtimeVarBindings, runtimeVarBindingMask);
1289 }
1290
1291 bool ensure_matches_one_transient(
1292 const Archetype& archetype, EntitySpan targetEntities,
1293 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1294 auto& ctxData = m_plan.ctx.data;
1295
1296 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1297 recompile();
1298
1299 if (!m_plan.vm.is_compiled())
1300 return false;
1301
1302 m_state.clear_transient_result_cache();
1303
1304 auto& w = *world();
1305 auto& matchScratch = query_match_scratch_acquire(w);
1306 CleanUpTmpArchetypeMatches autoCleanup(w, true);
1307
1308 vm::MatchingCtx ctx{};
1309 ctx.pWorld = &w;
1310 ctx.targetEntities = targetEntities;
1311 const auto* pArchetype = &archetype;
1312 ctx.allArchetypes = std::span((const Archetype**)&pArchetype, 1);
1313 ctx.archetypeLookup = {};
1314 ctx.pMatchesArr = &matchScratch.matchesArr;
1315 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
1316 ctx.matchesVersion = matchScratch.next_match_version();
1317 ctx.pLastMatchedArchetypeIdx_All = nullptr;
1318 ctx.pLastMatchedArchetypeIdx_Or = nullptr;
1319 ctx.pLastMatchedArchetypeIdx_Not = nullptr;
1320 ctx.queryMask = ctxData.queryMask;
1321 ctx.as_mask_0 = ctxData.as_mask_0;
1322 ctx.as_mask_1 = ctxData.as_mask_1;
1323 ctx.flags = ctxData.flags;
1324 ctx.varBindings = runtimeVarBindings;
1325 ctx.varBindingMask = runtimeVarBindingMask;
1326
1327 m_plan.vm.exec(ctx);
1328 const bool matched = !ctx.pMatchesArr->empty();
1329
1330 m_state.archetypeCache.reserve(ctx.pMatchesArr->size());
1331 if (ctxData.groupBy != EntityBad)
1332 m_state.grouped.archetypeGroupIds.reserve(ctx.pMatchesArr->size());
1333 for (const auto* pArch: *ctx.pMatchesArr)
1335
1336 ensure_group_data(false);
1337 return matched;
1338 }
1339
1345 bool register_archetype(const Archetype& archetype, Entity matchedSelector = EntityBad, bool assumeNew = false) {
1346 auto& ctxData = m_plan.ctx.data;
1347
1348 // Recompile if necessary.
1349 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1350 recompile();
1351
1353 return false;
1354
1355 const bool hadMatchBefore = !assumeNew && m_state.archetypeSet.contains(&archetype);
1357 const bool usesIs = direct_create_archetype_match_uses_is();
1358 bool hasOrTerms = false;
1359 bool matchedOrTerm = false;
1360 for (const auto& term: ctxData.terms_view()) {
1361 if (term.id == matchedSelector) {
1362 if (term.op == QueryOpKind::Or)
1363 matchedOrTerm = true;
1364 continue;
1365 }
1366
1367 const bool present = usesIs ? vm::detail::match_single_id_on_archetype(*world(), archetype, term.id)
1368 : world_component_index_match_count(*world(), archetype, term.id) != 0;
1369 if (term.op == QueryOpKind::Or) {
1370 hasOrTerms = true;
1371 matchedOrTerm |= present;
1372 continue;
1373 }
1374 if (term.op == QueryOpKind::Any)
1375 continue;
1376
1377 const bool matched = term.op == QueryOpKind::Not ? !present : present;
1378 if (!matched)
1379 return false;
1380 }
1381 if (hasOrTerms && !matchedOrTerm)
1382 return false;
1383 if (hadMatchBefore)
1384 return false;
1385
1386 if (assumeNew)
1387 add_new_archetype_to_immediate_caches(&archetype, true);
1388 else {
1389 add_archetype_to_seed_cache(&archetype, false);
1390 add_archetype_to_cache(&archetype, true, false);
1391 }
1392 return true;
1393 }
1394
1395 SingleArchetypeLookup singleArchetypeLookup;
1396 auto addLookupUnique = [&](Entity key, uint16_t compIdx, uint16_t matchCount) {
1397 const auto keyLookup = EntityLookupKey(key);
1398 const auto itLookup =
1399 core::find_if(singleArchetypeLookup.begin(), singleArchetypeLookup.end(), [&](const auto& item) {
1400 return item.matches(keyLookup);
1401 });
1402 if (itLookup != singleArchetypeLookup.end()) {
1403 auto& entry = itLookup->entry;
1404 entry.matchCount = (uint16_t)(entry.matchCount + matchCount);
1405 if (compIdx != ComponentIndexBad)
1406 entry.compIdx = compIdx;
1407 return;
1408 }
1409 singleArchetypeLookup.push_back(
1411 keyLookup, ComponentIndexEntry{const_cast<Archetype*>(&archetype), compIdx, matchCount}});
1412 };
1413 auto archetypeIds = archetype.ids_view();
1414 const auto cntIds = (uint32_t)archetypeIds.size();
1415 GAIA_FOR(cntIds) {
1416 const auto entity = archetypeIds[i];
1417 singleArchetypeLookup.push_back(
1419 EntityLookupKey(entity), ComponentIndexEntry{const_cast<Archetype*>(&archetype), (uint16_t)i, 1}});
1420
1421 if (!entity.pair())
1422 continue;
1423
1424 // Wildcard pair lookups use the same special records as the world-level
1425 // entity-to-archetype map, so incremental matching can reuse the normal VM path.
1426 const auto relKind = entity.entity() ? EntityKind::EK_Uni : EntityKind::EK_Gen;
1427 const auto rel = Entity((EntityId)entity.id(), 0, false, false, relKind);
1428 const auto tgt = Entity((EntityId)entity.gen(), 0, false, false, entity.kind());
1429 addLookupUnique(Pair(All, tgt), ComponentIndexBad, 1);
1430 addLookupUnique(Pair(rel, All), ComponentIndexBad, 1);
1431 addLookupUnique(Pair(All, All), ComponentIndexBad, 1);
1432 }
1433
1434 auto lastMatchedArchetypeIdx_All = GAIA_MOV(ctxData.lastMatchedArchetypeIdx_All);
1435 auto lastMatchedArchetypeIdx_Or = GAIA_MOV(ctxData.lastMatchedArchetypeIdx_Or);
1436 auto lastMatchedArchetypeIdx_Not = GAIA_MOV(ctxData.lastMatchedArchetypeIdx_Not);
1437 GAIA_ASSERT(ctxData.lastMatchedArchetypeIdx_All.empty());
1438 GAIA_ASSERT(ctxData.lastMatchedArchetypeIdx_Or.empty());
1439 GAIA_ASSERT(ctxData.lastMatchedArchetypeIdx_Not.empty());
1440
1441 const auto* pArchetype = &archetype;
1442 const cnt::sarray<Entity, MaxVarCnt> noRuntimeVarBindings{};
1443 match(
1444 singleArchetypeLookup, std::span((const Archetype**)&pArchetype, 1), nullptr, archetype.id(),
1445 noRuntimeVarBindings, 0);
1446 ctxData.lastMatchedArchetypeIdx_All = GAIA_MOV(lastMatchedArchetypeIdx_All);
1447 ctxData.lastMatchedArchetypeIdx_Or = GAIA_MOV(lastMatchedArchetypeIdx_Or);
1448 ctxData.lastMatchedArchetypeIdx_Not = GAIA_MOV(lastMatchedArchetypeIdx_Not);
1449 const bool matched = assumeNew ? m_state.archetypeSet.contains(&archetype)
1450 : !hadMatchBefore && m_state.archetypeSet.contains(&archetype);
1451
1452 if (!matched)
1453 return false;
1454
1455 sort_entities();
1457 return true;
1458 }
1459
1463 GAIA_PROF_SCOPE(queryinfo::calc_sort_data);
1464
1465 m_state.nonTrivial.archetypeSortData.clear();
1466
1467 // The function doesn't do any moves and expects that all chunks have their data sorted already.
1468 // We use a min-heap / priority queue - like structure during query iteration to merge sorted tables:
1469 // - we hold a cursor into each sorted chunk
1470 // - we compare the next entity from each chunk using your sorting function
1471 // - we then pick the smallest one (like k-way merge sort), and advance that cursor
1472 // This is esentially what this function does:
1473 // while (any_chunk_has_entities) {
1474 // find_chunk_with_smallest_next_element();
1475 // yield(entity_from_that_chunk);
1476 // adv_cursor_for_that_chunk();
1477 // }
1478 // This produces a globally sorted view without modifying actual data. It's a balance between
1479 // performance and memory usage. We could also sort the data in-place across all chunks, but that
1480 // would generated too many data moves (entities + all of their components).
1481
1482 struct Cursor {
1483 uint32_t chunkIdx = 0;
1484 uint16_t row = 0;
1485 };
1486
1487 auto& archetypes = m_state.archetypeCache;
1488
1489 // Initialize cursors. We will need as many as there are archetypes.
1490 cnt::sarray_ext<Cursor, 128> cursors(archetypes.size());
1491
1492 uint32_t currArchetypeIdx = (uint32_t)-1;
1493 Chunk* pCurrentChunk = nullptr;
1494 uint16_t currentStartRow = 0;
1495 uint16_t currentRow = 0;
1496
1497 const void* pDataMin = nullptr;
1498 const void* pDataCurr = nullptr;
1499
1500 while (true) {
1501 uint32_t minArchetypeIdx = (uint32_t)-1;
1502 Entity minEntity = EntityBad;
1503
1504 // Find the next entity across all tables/chunks
1505 for (uint32_t t = 0; t < archetypes.size(); ++t) {
1506 const auto* pArchetype = archetypes[t];
1507 const auto& chunks = pArchetype->chunks();
1508 auto& cur = cursors[t];
1509
1510 while (cur.chunkIdx < chunks.size() && cur.row >= chunks[cur.chunkIdx]->size()) {
1511 ++cur.chunkIdx;
1512 cur.row = 0;
1513 }
1514
1515 if (cur.chunkIdx >= chunks.size())
1516 continue;
1517
1518 const auto* pChunk = pArchetype->chunks()[cur.chunkIdx];
1519 auto entity = pChunk->entity_view()[cur.row];
1520
1521 if (m_plan.ctx.data.sortBy != ecs::EntityBad) {
1522 auto compIdx = world_component_index_comp_idx(*m_plan.ctx.w, *pArchetype, m_plan.ctx.data.sortBy);
1523 if (compIdx == BadIndex)
1524 compIdx = pChunk->comp_idx(m_plan.ctx.data.sortBy);
1525 pDataCurr = pChunk->comp_ptr(compIdx, cur.row);
1526 } else
1527 pDataCurr = &pChunk->entity_view()[cur.row];
1528
1529 if (minEntity == EntityBad) {
1530 minEntity = entity;
1531 minArchetypeIdx = t;
1532 pDataMin = pDataCurr;
1533 continue;
1534 }
1535
1536 if (m_plan.ctx.data.sortByFunc(*m_plan.ctx.w, pDataCurr, pDataMin) < 0) {
1537 minEntity = entity;
1538 minArchetypeIdx = t;
1539 }
1540 }
1541
1542 // No more results found, we can stop
1543 if (minArchetypeIdx == (uint32_t)-1)
1544 break;
1545
1546 auto& cur = cursors[minArchetypeIdx];
1547 const auto& chunks = archetypes[minArchetypeIdx]->chunks();
1548 Chunk* pChunk = chunks[cur.chunkIdx];
1549
1550 if (minArchetypeIdx == currArchetypeIdx && pChunk == pCurrentChunk) {
1551 // Current slice
1552 } else {
1553 // End previous slice
1554 if (pCurrentChunk != nullptr) {
1555 m_state.nonTrivial.archetypeSortData.push_back(
1556 {pCurrentChunk, currArchetypeIdx, currentStartRow, (uint16_t)(currentRow - currentStartRow)});
1557 }
1558
1559 // Start a new slice
1560 currArchetypeIdx = minArchetypeIdx;
1561 pCurrentChunk = pChunk;
1562 currentStartRow = cur.row;
1563 }
1564
1565 ++cur.row;
1566 currentRow = cur.row;
1567 }
1568
1569 if (pCurrentChunk != nullptr) {
1570 m_state.nonTrivial.archetypeSortData.push_back(
1571 {pCurrentChunk, currArchetypeIdx, currentStartRow, (uint16_t)(currentRow - currentStartRow)});
1572 }
1573 }
1574
1577 if (m_plan.ctx.data.sortByFunc == nullptr)
1578 return;
1579
1580 if ((m_plan.ctx.data.flags & QueryCtx::QueryFlags::SortEntities) == 0 && m_state.nonTrivial.sortVersion != 0)
1581 return;
1582 m_plan.ctx.data.flags &= ~QueryCtx::QueryFlags::SortEntities;
1583
1584 // First, sort entities in archetypes
1585 for (const auto* pArchetype: m_state.archetypeCache)
1586 const_cast<Archetype*>(pArchetype)->sort_entities(m_plan.ctx.data.sortBy, m_plan.ctx.data.sortByFunc);
1587
1588 // Now that entites are sorted, we can start creating slices
1590 m_state.nonTrivial.sortVersion = ::gaia::ecs::world_version(*world());
1591 }
1592
1595 if ((m_plan.ctx.data.flags & QueryCtx::QueryFlags::SortGroups) == 0)
1596 return;
1597 m_plan.ctx.data.flags &= ~QueryCtx::QueryFlags::SortGroups;
1598
1599 if ((m_plan.ctx.data.flags & QueryCtx::QueryFlags::OrderGroups) != 0)
1600 ensure_group_data(true);
1601 }
1602
1606 void swap_archetype_cache_entry(uint32_t left, uint32_t right) {
1607 auto* pTmpArchetype = m_state.archetypeCache[left];
1608 m_state.archetypeCache[left] = m_state.archetypeCache[right];
1609 m_state.archetypeCache[right] = pTmpArchetype;
1610
1611 if (left < m_state.grouped.archetypeGroupIds.size() && right < m_state.grouped.archetypeGroupIds.size()) {
1612 const auto tmp = m_state.grouped.archetypeGroupIds[left];
1613 m_state.grouped.archetypeGroupIds[left] = m_state.grouped.archetypeGroupIds[right];
1614 m_state.grouped.archetypeGroupIds[right] = tmp;
1615 }
1616
1617 if (left < m_state.exec.archetypeCompIndices.size() && right < m_state.exec.archetypeCompIndices.size()) {
1618 auto tmp = m_state.exec.archetypeCompIndices[left];
1619 m_state.exec.archetypeCompIndices[left] = m_state.exec.archetypeCompIndices[right];
1620 m_state.exec.archetypeCompIndices[right] = tmp;
1621 }
1622
1623 if (left < m_state.exec.archetypeInheritedData.size() && right < m_state.exec.archetypeInheritedData.size()) {
1624 auto tmp = m_state.exec.archetypeInheritedData[left];
1625 m_state.exec.archetypeInheritedData[left] = m_state.exec.archetypeInheritedData[right];
1626 m_state.exec.archetypeInheritedData[right] = tmp;
1627 }
1628
1629 if (left < m_state.nonTrivial.archetypeBarrierPasses.size() &&
1630 right < m_state.nonTrivial.archetypeBarrierPasses.size()) {
1631 const auto tmp = m_state.nonTrivial.archetypeBarrierPasses[left];
1632 m_state.nonTrivial.archetypeBarrierPasses[left] = m_state.nonTrivial.archetypeBarrierPasses[right];
1633 m_state.nonTrivial.archetypeBarrierPasses[right] = tmp;
1634 }
1635 }
1636
1639 if (!m_state.exec.compIndicesPending)
1640 return;
1641
1642 m_state.exec.archetypeCompIndices.clear();
1643 m_state.exec.archetypeCompIndices.reserve(m_state.archetypeCache.size());
1644 for (const auto* pArchetype: m_state.archetypeCache)
1645 m_state.exec.archetypeCompIndices.push_back(create_comp_indices(pArchetype));
1646
1647 m_state.exec.compIndicesPending = false;
1648 }
1649
1651 GAIA_NODISCARD bool has_inherited_data_payload() const {
1652 return ctx().data.deps.has_dep_flag(QueryCtx::DependencyHasInheritedDataTerms);
1653 }
1654
1657 if (!m_state.exec.inheritedDataPending)
1658 return;
1659
1661 m_state.exec.archetypeInheritedData.clear();
1662 m_state.exec.inheritedDataPending = false;
1663 return;
1664 }
1665
1666 m_state.exec.archetypeInheritedData.clear();
1667 m_state.exec.archetypeInheritedData.reserve(m_state.archetypeCache.size());
1668 for (const auto* pArchetype: m_state.archetypeCache)
1669 create_inherited_data(pArchetype);
1670
1671 m_state.exec.inheritedDataPending = false;
1672 }
1673
1677 void ensure_group_data(bool orderGroups) {
1678 if (m_plan.ctx.data.groupBy == EntityBad || !m_state.grouped.dataPending)
1679 return;
1680
1681 if (!orderGroups && (m_plan.ctx.data.flags & QueryCtx::QueryFlags::OrderGroups) == 0)
1682 return;
1683
1684 struct sort_cond {
1685 bool operator()(GroupId a, GroupId b) const {
1686 return a <= b;
1687 }
1688 };
1689
1690 core::sort(m_state.grouped.archetypeGroupIds, sort_cond{}, [&](uint32_t left, uint32_t right) {
1691 swap_archetype_cache_entry(left, right);
1692 });
1693
1694 m_state.grouped.archetypeGroupData.clear();
1695 m_state.grouped.selectedGroupDataValid = false;
1696
1697 if (m_state.grouped.archetypeGroupIds.empty()) {
1698 m_state.grouped.dataPending = false;
1699 return;
1700 }
1701
1702 GroupId groupId = m_state.grouped.archetypeGroupIds[0];
1703 uint32_t idxFirst = 0;
1704 const auto cnt = (uint32_t)m_state.grouped.archetypeGroupIds.size();
1705 for (uint32_t i = 1; i < cnt; ++i) {
1706 if (m_state.grouped.archetypeGroupIds[i] == groupId)
1707 continue;
1708
1709 m_state.grouped.archetypeGroupData.push_back({groupId, idxFirst, i - 1, false});
1710 groupId = m_state.grouped.archetypeGroupIds[i];
1711 idxFirst = i;
1712 }
1713
1714 m_state.grouped.archetypeGroupData.push_back({groupId, idxFirst, cnt - 1, false});
1715 m_state.grouped.dataPending = false;
1716 }
1717
1720 if (!world_depth_order_prunes_disabled_subtrees(*world(), m_plan.ctx.data.groupBy))
1721 return;
1722
1723 ensure_group_data(true);
1724
1725 const auto currRelationVersion = world_rel_version(*world(), m_plan.ctx.data.groupBy);
1726 const auto currEnabledVersion = world_enabled_hierarchy_version(*world());
1727 if (m_state.nonTrivial.barrierRelVersion == currRelationVersion &&
1728 m_state.nonTrivial.barrierEnabledVersion == currEnabledVersion)
1729 return;
1730
1731 m_state.nonTrivial.archetypeBarrierPasses.resize(m_state.archetypeCache.size(), 1);
1732 m_state.nonTrivial.barrierMayPrune = 0;
1733
1734 const auto relation = m_plan.ctx.data.groupBy;
1735 for (uint32_t i = 0; i < m_state.archetypeCache.size(); ++i) {
1736 const auto* pArchetype = m_state.archetypeCache[i];
1737 auto& barrierPasses = m_state.nonTrivial.archetypeBarrierPasses[i];
1738 barrierPasses = 1;
1739
1740 auto ids = pArchetype->ids_view();
1741 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
1742 const auto pair = ids[idsIdx];
1743 const auto parent = world_pair_target_if_alive(*world(), pair);
1744 if (parent == EntityBad || !world_entity_enabled_hierarchy(*world(), parent, relation)) {
1745 barrierPasses = 0;
1746 m_state.nonTrivial.barrierMayPrune = 1;
1747 break;
1748 }
1749 }
1750 }
1751
1752 m_state.nonTrivial.barrierRelVersion = currRelationVersion;
1753 m_state.nonTrivial.barrierEnabledVersion = currEnabledVersion;
1754 }
1755
1760 ArchetypeCompIndices cacheData{};
1761 core::fill(cacheData.indices, cacheData.indices + ChunkHeader::MAX_COMPONENTS, (uint8_t)0xFF);
1762 const auto terms = ctx().data.terms_view();
1763 const auto cnt = (uint32_t)terms.size();
1764 GAIA_FOR(cnt) {
1765 const auto& term = terms[i];
1766 const auto fieldIdx = term.fieldIndex;
1767 const auto queryId = term.id;
1768 if (!queryId.pair() && world_is_out_of_line_component(*world(), queryId)) {
1769#if GAIA_ASSERT_ENABLED
1770 // Verify that the component is indeed not present on the archetype, otherwise our matching logic is flawed.
1771 const auto compIdx = core::get_index_unsafe(pArchetype->ids_view(), queryId);
1772 GAIA_ASSERT(compIdx != BadIndex);
1773#endif
1774 cacheData.indices[fieldIdx] = 0xFF;
1775 continue;
1776 }
1777
1778 auto compIdx = world_component_index_comp_idx(*world(), *pArchetype, queryId);
1779 if (compIdx == BadIndex) {
1780 // Wildcard/semantic terms are not represented by an exact component index entry.
1781 // Fall back to the archetype-local scan in those cases.
1782 compIdx = core::get_index_unsafe(pArchetype->ids_view(), queryId);
1783 }
1784 GAIA_ASSERT(compIdx != BadIndex);
1785
1786 cacheData.indices[fieldIdx] = (uint8_t)compIdx;
1787 }
1788 return cacheData;
1789 }
1790
1793 void create_inherited_data(const Archetype* pArchetype) {
1794 ArchetypeInheritedData inheritedData{};
1795 core::fill(inheritedData.data, inheritedData.data + ChunkHeader::MAX_COMPONENTS, nullptr);
1796
1797 const auto terms = ctx().data.terms_view();
1798 const auto cnt = (uint32_t)terms.size();
1799 GAIA_FOR(cnt) {
1800 const auto& term = terms[i];
1801 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
1802 continue;
1803 if (term.matchKind != QueryMatchKind::Semantic)
1804 continue;
1805 const auto queryId = term.id;
1806 if (queryId == EntityBad || is_wildcard(queryId) || is_variable((EntityId)queryId.id()))
1807 continue;
1808 if (world_is_out_of_line_component(*world(), queryId))
1809 continue;
1810 if (!world_term_uses_inherit_policy(*world(), queryId))
1811 continue;
1812 if (pArchetype->has(queryId))
1813 continue;
1814
1815 const auto owner = world_query_first_inherited_owner(*world(), *pArchetype, queryId);
1816 GAIA_ASSERT(owner != EntityBad);
1817 inheritedData.data[term.fieldIndex] = world_query_inherited_arg_data_const_ptr(*world(), owner, queryId);
1818 }
1819
1820 m_state.exec.archetypeInheritedData.push_back(inheritedData);
1821 }
1822
1828 const Archetype* pArchetype, bool trackMembershipChange, bool assumeAbsent = false) {
1829 GAIA_PROF_SCOPE(queryinfo::add_cache_ng);
1830
1831 if (!assumeAbsent && m_state.archetypeSet.contains(pArchetype))
1832 return;
1833 GAIA_ASSERT(assumeAbsent || !m_state.archetypeSet.contains(pArchetype));
1834
1835 m_state.archetypeSet.emplace(pArchetype);
1836 m_state.archetypeCache.push_back(pArchetype);
1837 m_state.exec.compIndicesPending = true;
1838 m_state.exec.inheritedDataPending = true;
1839 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
1840 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
1841 if (trackMembershipChange)
1842 mark_result_cache_membership_changed();
1843 }
1844
1848 void add_archetype_to_seed_cache(const Archetype* pArchetype, bool assumeAbsent = false) {
1849 if (!assumeAbsent && m_state.seedArchetypeSet.contains(pArchetype))
1850 return;
1851 GAIA_ASSERT(assumeAbsent || !m_state.seedArchetypeSet.contains(pArchetype));
1852
1853 m_state.seedArchetypeSet.emplace(pArchetype);
1854 m_state.seedArchetypeCache.push_back(pArchetype);
1855 }
1856
1860 void add_new_archetype_to_immediate_caches(const Archetype* pArchetype, bool trackMembershipChange) {
1861 GAIA_ASSERT(m_plan.ctx.data.groupBy == EntityBad);
1862 GAIA_ASSERT(m_plan.ctx.data.sortByFunc == nullptr);
1863 GAIA_ASSERT(!m_state.seedArchetypeSet.contains(pArchetype));
1864 GAIA_ASSERT(!m_state.archetypeSet.contains(pArchetype));
1865
1866 m_state.seedArchetypeSet.emplace(pArchetype);
1867 m_state.seedArchetypeCache.push_back(pArchetype);
1868
1869 m_state.archetypeSet.emplace(pArchetype);
1870 m_state.archetypeCache.push_back(pArchetype);
1871 m_state.exec.compIndicesPending = true;
1872 m_state.exec.inheritedDataPending = true;
1873 if (trackMembershipChange)
1874 mark_result_cache_membership_changed();
1875 }
1876
1882 const Archetype* pArchetype, bool trackMembershipChange, bool assumeAbsent = false) {
1883 GAIA_PROF_SCOPE(queryinfo::add_cache_wg);
1884
1885 if (!assumeAbsent && m_state.archetypeSet.contains(pArchetype))
1886 return;
1887 GAIA_ASSERT(assumeAbsent || !m_state.archetypeSet.contains(pArchetype));
1888
1889 m_state.grouped.selectedGroupDataValid = false;
1890
1891 const GroupId groupId = m_plan.ctx.data.groupByFunc(*m_plan.ctx.w, *pArchetype, m_plan.ctx.data.groupBy);
1892
1893 m_state.archetypeSet.emplace(pArchetype);
1894 m_state.archetypeCache.push_back(pArchetype);
1895 m_state.grouped.archetypeGroupIds.push_back(groupId);
1896 m_state.grouped.dataPending = true;
1897 m_state.exec.compIndicesPending = true;
1898 m_state.exec.inheritedDataPending = true;
1899 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
1900 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
1901 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortGroups;
1902 if (trackMembershipChange)
1903 mark_result_cache_membership_changed();
1904 }
1905
1910 void add_archetype_to_cache(const Archetype* pArchetype, bool trackMembershipChange, bool assumeAbsent) {
1911 if (m_plan.ctx.data.sortByFunc != nullptr)
1912 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortEntities;
1913
1914 if (m_plan.ctx.data.groupBy != EntityBad)
1915 add_archetype_to_cache_w_grouping(pArchetype, trackMembershipChange, assumeAbsent);
1916 else
1917 add_archetype_to_cache_no_grouping(pArchetype, trackMembershipChange, assumeAbsent);
1918 }
1919
1923 m_state.archetypeCache.push_back(pArchetype);
1924 m_state.exec.compIndicesPending = true;
1925 m_state.exec.inheritedDataPending = true;
1926 if (m_plan.ctx.data.groupBy != EntityBad) {
1927 const auto groupId = m_plan.ctx.data.groupByFunc(*m_plan.ctx.w, *pArchetype, m_plan.ctx.data.groupBy);
1928 m_state.grouped.archetypeGroupIds.push_back(groupId);
1929 m_state.grouped.dataPending = true;
1930 }
1931 }
1932
1937 GAIA_NODISCARD const GroupData* selected_group_data(GroupId runtimeGroupId) const {
1938 const_cast<QueryInfo*>(this)->ensure_group_data(true);
1939 if (m_plan.ctx.data.groupBy == EntityBad || runtimeGroupId == 0)
1940 return nullptr;
1941
1942 if (!m_state.grouped.selectedGroupDataValid || m_state.grouped.selectedGroupData.groupId != runtimeGroupId) {
1943 uint32_t left = 0;
1944 uint32_t right = (uint32_t)m_state.grouped.archetypeGroupData.size();
1945 while (left < right) {
1946 const uint32_t mid = left + ((right - left) >> 1);
1947 const auto midGroupId = m_state.grouped.archetypeGroupData[mid].groupId;
1948 if (midGroupId < runtimeGroupId)
1949 left = mid + 1;
1950 else
1951 right = mid;
1952 }
1953
1954 if (left < m_state.grouped.archetypeGroupData.size() &&
1955 m_state.grouped.archetypeGroupData[left].groupId == runtimeGroupId) {
1956 m_state.grouped.selectedGroupData = m_state.grouped.archetypeGroupData[left];
1957 m_state.grouped.selectedGroupDataValid = true;
1958 return &m_state.grouped.selectedGroupData;
1959 }
1960
1961 m_state.grouped.selectedGroupData = {};
1962 m_state.grouped.selectedGroupDataValid = false;
1963 return nullptr;
1964 }
1965
1966 return &m_state.grouped.selectedGroupData;
1967 }
1968
1971 GAIA_NODISCARD bool has_same_result_membership_as_seed_cache() const {
1972 if (m_state.archetypeSet.size() != m_state.seedArchetypeSet.size())
1973 return false;
1974
1975 for (const auto* pArchetype: m_state.seedArchetypeCache) {
1976 if (!m_state.archetypeSet.contains(pArchetype))
1977 return false;
1978 }
1979
1980 return true;
1981 }
1982
1986 GAIA_NODISCARD bool has_same_result_membership_as(const CArchetypeDArray& archetypeCache) const {
1987 if (m_state.archetypeSet.size() != archetypeCache.size())
1988 return false;
1989
1990 for (const auto* pArchetype: archetypeCache) {
1991 if (!m_state.archetypeSet.contains(pArchetype))
1992 return false;
1993 }
1994
1995 return true;
1996 }
1997
2001 const bool membershipChanged = !has_same_result_membership_as_seed_cache();
2002 clear_result_cache();
2003 const auto cnt = (uint32_t)m_state.seedArchetypeCache.size();
2004 GAIA_FOR(cnt) {
2005 add_archetype_to_cache(m_state.seedArchetypeCache[i], false, false);
2006 }
2007 if (membershipChanged)
2008 mark_result_cache_membership_changed();
2009 }
2010
2014 bool del_archetype_from_cache(const Archetype* pArchetype) {
2015 const auto it = m_state.archetypeSet.find(pArchetype);
2016 if (it == m_state.archetypeSet.end())
2017 return false;
2018
2019 m_state.archetypeSet.erase(it);
2020
2021 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
2022 GAIA_ASSERT(archetypeIdx != BadIndex);
2023 if (archetypeIdx == BadIndex)
2024 return true;
2025
2026 if (m_plan.ctx.data.sortByFunc != nullptr)
2027 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortEntities;
2028
2029 core::swap_erase(m_state.archetypeCache, archetypeIdx);
2030 if (archetypeIdx < m_state.exec.archetypeCompIndices.size())
2031 core::swap_erase(m_state.exec.archetypeCompIndices, archetypeIdx);
2032 if (archetypeIdx < m_state.exec.archetypeInheritedData.size())
2033 core::swap_erase(m_state.exec.archetypeInheritedData, archetypeIdx);
2034 if (archetypeIdx < m_state.grouped.archetypeGroupIds.size())
2035 core::swap_erase(m_state.grouped.archetypeGroupIds, archetypeIdx);
2036 if (archetypeIdx < m_state.nonTrivial.archetypeBarrierPasses.size())
2037 core::swap_erase(m_state.nonTrivial.archetypeBarrierPasses, archetypeIdx);
2038
2039 if (m_plan.ctx.data.groupBy != EntityBad) {
2040 m_state.grouped.selectedGroupDataValid = false;
2041 m_state.grouped.archetypeGroupData.clear();
2042 m_state.grouped.dataPending = true;
2043 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortGroups;
2044 }
2045 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
2046 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
2047
2048 mark_result_cache_membership_changed();
2049 return true;
2050 }
2051
2056 const auto it = m_state.seedArchetypeSet.find(pArchetype);
2057 if (it == m_state.seedArchetypeSet.end())
2058 return false;
2059
2060 m_state.seedArchetypeSet.erase(it);
2061
2062 const auto archetypeIdx = core::get_index(m_state.seedArchetypeCache, pArchetype);
2063 GAIA_ASSERT(archetypeIdx != BadIndex);
2064 if (archetypeIdx == BadIndex)
2065 return true;
2066
2067 core::swap_erase(m_state.seedArchetypeCache, archetypeIdx);
2068 return true;
2069 }
2070
2072 GAIA_NODISCARD World* world() {
2073 GAIA_ASSERT(m_plan.ctx.w != nullptr);
2074 return const_cast<World*>(m_plan.ctx.w);
2075 }
2077 GAIA_NODISCARD const World* world() const {
2078 GAIA_ASSERT(m_plan.ctx.w != nullptr);
2079 return m_plan.ctx.w;
2080 }
2081
2083 GAIA_NODISCARD QuerySerBuffer& ser_buffer() {
2084 return m_plan.ctx.q.ser_buffer(world());
2085 }
2088 m_plan.ctx.q.ser_buffer_reset(world());
2089 }
2090
2092 GAIA_NODISCARD QueryCtx& ctx() {
2093 return m_plan.ctx;
2094 }
2096 GAIA_NODISCARD const QueryCtx& ctx() const {
2097 return m_plan.ctx;
2098 }
2099
2101 GAIA_NODISCARD util::str bytecode() const {
2102 return m_plan.vm.bytecode(*world());
2103 }
2104
2106 GAIA_NODISCARD uint32_t op_count() const {
2107 return m_plan.vm.op_count();
2108 }
2109
2111 GAIA_NODISCARD uint64_t op_signature() const {
2112 return m_plan.vm.op_signature();
2113 }
2114
2116 GAIA_NODISCARD bool has_filters() const {
2117 const auto& ctxData = m_plan.ctx.data;
2118 return ctxData.changedCnt > 0;
2119 }
2120
2122 GAIA_NODISCARD bool has_entity_filter_terms() const {
2123 const auto& ctxData = m_plan.ctx.data;
2124 return ctxData.deps.has_dep_flag(QueryCtx::DependencyHasEntityFilterTerms);
2125 }
2126
2128 GAIA_NODISCARD bool has_potential_inherited_id_terms() const {
2129 const auto& ctxData = m_plan.ctx.data;
2130 return ctxData.deps.has_dep_flag(QueryCtx::DependencyHasPotentialInheritedIdTerms);
2131 }
2132
2134 GAIA_NODISCARD QueryCtx::DirectTargetEvalKind direct_target_eval_kind() const {
2135 return m_plan.ctx.data.directTargetEvalKind;
2136 }
2137
2139 GAIA_NODISCARD Entity direct_target_eval_id() const {
2140 return m_plan.ctx.data.directTargetEvalId;
2141 }
2142
2144 GAIA_NODISCARD bool can_direct_target_eval() const {
2145 return m_plan.ctx.data.canDirectTargetEval;
2146 }
2147
2149 GAIA_NODISCARD bool can_direct_entity_seed_eval_shape() const {
2150 return m_plan.ctx.data.canDirectEntitySeedEvalShape;
2151 }
2152
2154 GAIA_NODISCARD bool has_only_direct_or_terms() const {
2155 return m_plan.ctx.data.hasOnlyDirectOrTerms;
2156 }
2157
2159 GAIA_NODISCARD bool matches_prefab_entities() const {
2160 const auto& ctxData = m_plan.ctx.data;
2161 return (ctxData.flags & QueryCtx::QueryFlags::MatchPrefab) != 0 ||
2162 (ctxData.flags & QueryCtx::QueryFlags::HasPrefabTerms) != 0;
2163 }
2164
2166 template <typename... T>
2167 GAIA_NODISCARD bool has_any() const {
2168 return (has_inter<T>(QueryOpKind::Any) || ...);
2169 }
2170
2172 template <typename... T>
2173 GAIA_NODISCARD bool has_or() const {
2174 return (has_inter<T>(QueryOpKind::Or) || ...);
2175 }
2176
2178 template <typename... T>
2179 GAIA_NODISCARD bool has_all() const {
2180 return (has_inter<T>(QueryOpKind::All) && ...);
2181 }
2182
2184 template <typename... T>
2185 GAIA_NODISCARD bool has_no() const {
2186 return (!has_inter<T>(QueryOpKind::Not) && ...);
2187 }
2188
2191 void remove(Archetype* pArchetype) {
2192 GAIA_PROF_SCOPE(queryinfo::remove);
2193
2194 (void)del_archetype_from_seed_cache(pArchetype);
2195 (void)del_archetype_from_cache(pArchetype);
2196 }
2197
2199 std::span<const uint8_t> indices_mapping_view(uint32_t archetypeIdx) const {
2200 const_cast<QueryInfo*>(this)->ensure_comp_indices();
2201 const auto& ctxData = m_state.exec.archetypeCompIndices[archetypeIdx];
2202 return {(const uint8_t*)&ctxData.indices[0], ChunkHeader::MAX_COMPONENTS};
2203 }
2204
2207 InheritedTermDataView inherited_data_view(uint32_t archetypeIdx) const {
2208 const_cast<QueryInfo*>(this)->ensure_inherited_data();
2209 if (archetypeIdx >= m_state.exec.archetypeInheritedData.size())
2210 return {};
2211 const auto& ctxData = m_state.exec.archetypeInheritedData[archetypeIdx];
2212 return {ctxData.data, ChunkHeader::MAX_COMPONENTS};
2213 }
2214
2219 return {};
2220 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
2221 if (archetypeIdx == BadIndex)
2222 return {};
2223 return inherited_data_view((uint32_t)archetypeIdx);
2224 }
2225
2230
2233 if (m_state.exec.compIndicesPending)
2234 return {};
2235 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
2236 if (archetypeIdx == BadIndex)
2237 return {};
2238 return indices_mapping_view(archetypeIdx);
2239 }
2240
2244 if (!has_inherited_data_payload() || m_state.exec.inheritedDataPending)
2245 return {};
2246 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
2247 if (archetypeIdx == BadIndex)
2248 return {};
2249 return inherited_data_view((uint32_t)archetypeIdx);
2250 }
2251
2254 GAIA_NODISCARD GroupId group_id(uint32_t archetypeIdx) const {
2255 const_cast<QueryInfo*>(this)->ensure_group_data(true);
2256 GAIA_ASSERT(archetypeIdx < m_state.grouped.archetypeGroupIds.size());
2257 return m_state.grouped.archetypeGroupIds[archetypeIdx];
2258 }
2259
2262 GAIA_NODISCARD bool barrier_passes(uint32_t archetypeIdx) const {
2264 if (m_state.nonTrivial.archetypeBarrierPasses.empty())
2265 return true;
2266 GAIA_ASSERT(archetypeIdx < m_state.nonTrivial.archetypeBarrierPasses.size());
2267 return m_state.nonTrivial.archetypeBarrierPasses[archetypeIdx] != 0;
2268 }
2269
2271 GAIA_NODISCARD bool barrier_may_prune() const {
2273 return m_state.nonTrivial.barrierMayPrune != 0;
2274 }
2275
2277 GAIA_NODISCARD CArchetypeDArray::iterator begin() {
2278 return m_state.archetypeCache.begin();
2279 }
2280
2282 GAIA_NODISCARD CArchetypeDArray::const_iterator begin() const {
2283 return m_state.archetypeCache.begin();
2284 }
2285
2287 GAIA_NODISCARD CArchetypeDArray::const_iterator cbegin() const {
2288 return m_state.archetypeCache.begin();
2289 }
2290
2292 GAIA_NODISCARD CArchetypeDArray::iterator end() {
2293 return m_state.archetypeCache.end();
2294 }
2295
2297 GAIA_NODISCARD CArchetypeDArray::const_iterator end() const {
2298 return m_state.archetypeCache.end();
2299 }
2300
2302 GAIA_NODISCARD CArchetypeDArray::const_iterator cend() const {
2303 return m_state.archetypeCache.end();
2304 }
2305
2308 return std::span{(const Archetype**)m_state.archetypeCache.data(), m_state.archetypeCache.size()};
2309 }
2310
2313 return std::span{m_state.nonTrivial.archetypeSortData.data(), m_state.nonTrivial.archetypeSortData.size()};
2314 }
2315
2318 const_cast<QueryInfo*>(this)->ensure_group_data(true);
2319 return std::span{m_state.grouped.archetypeGroupData.data(), m_state.grouped.archetypeGroupData.size()};
2320 }
2321
2322#if GAIA_ECS_TEST_HOOKS
2324 using TestDynamicCacheKind = QueryCtx::DynamicCacheKind;
2325
2328 GAIA_NODISCARD TestDynamicCacheKind test_dynamic_cache_kind() const {
2329 return m_plan.ctx.data.dynamicCacheKind;
2330 }
2331
2333 GAIA_NODISCARD bool test_uses_direct_src_version_tracking() const {
2334 return m_plan.ctx.data.uses_direct_src_version_tracking();
2335 }
2336
2338 GAIA_NODISCARD bool test_uses_src_trav_snapshot() const {
2339 return m_plan.ctx.data.uses_src_trav_snapshot();
2340 }
2341
2344 GAIA_NODISCARD uint32_t test_match_pass_count() const {
2345 return m_testMatchPassCount;
2346 }
2347
2349 GAIA_NODISCARD static constexpr uint32_t test_query_info_size() {
2350 return (uint32_t)sizeof(QueryInfo);
2351 }
2352
2354 GAIA_NODISCARD static constexpr uint32_t test_query_plan_size() {
2355 return (uint32_t)sizeof(QueryPlan);
2356 }
2357
2359 GAIA_NODISCARD static constexpr uint32_t test_query_state_size() {
2360 return (uint32_t)sizeof(QueryState);
2361 }
2362
2364 GAIA_NODISCARD static constexpr uint32_t test_dynamic_cache_state_size() {
2365 return (uint32_t)sizeof(QueryState::DynamicCacheState);
2366 }
2367
2369 GAIA_NODISCARD static constexpr uint32_t test_query_state_dynamic_offset() {
2370 return (uint32_t)offsetof(QueryState, dynamic);
2371 }
2372
2375 void test_add_transient_archetype(const Archetype* pArchetype) {
2376 GAIA_ASSERT(pArchetype != nullptr);
2377 if (pArchetype == nullptr)
2378 return;
2379
2380 m_state.clear_transient_result_cache();
2382 }
2383#endif
2384 };
2385 } // namespace ecs
2386} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
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:839
void sort_entities(Entity entity, TSortByFunc func)
Sorts all entities in the archetypes according to the given function.
Definition archetype.h:1022
Definition chunk.h:35
Definition query_info.h:100
void ensure_inherited_data()
Rebuilds inherited-data payloads for matched archetypes when query terms require them.
Definition query_info.h:1656
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:1860
GAIA_NODISCARD CArchetypeDArray::const_iterator begin() const
Returns an iterator to the first cached result archetype.
Definition query_info.h:2282
void swap_archetype_cache_entry(uint32_t left, uint32_t right)
Swaps two result-cache entries and every parallel payload array that mirrors cache order.
Definition query_info.h:1606
GAIA_NODISCARD std::span< const GroupData > group_data_view() const
Returns cached group ranges, rebuilding grouped data when needed.
Definition query_info.h:2317
GAIA_NODISCARD GroupId group_id(uint32_t archetypeIdx) const
Returns the cached group id for a matched archetype index.
Definition query_info.h:2254
GAIA_NODISCARD bool has_no() const
Returns true when none of the requested types is present as a Not term.
Definition query_info.h:2185
GAIA_NODISCARD bool can_direct_entity_seed_eval_shape() const
Returns true when the query shape is eligible for direct entity seed evaluation.
Definition query_info.h:2149
void sort_entities()
Applies query entity sorting and rebuilds sorted chunk slices when needed.
Definition query_info.h:1576
GAIA_NODISCARD const World * world() const
Returns the world owning this query.
Definition query_info.h:2077
void remove(Archetype *pArchetype)
Removes an archetype from cache.
Definition query_info.h:2191
void reset()
Resets cached query state for slot reuse while keeping the compiled context object.
Definition query_info.h:856
GAIA_NODISCARD bool has_only_direct_or_terms() const
Returns true when the query contains only direct OR/NOT terms and at least one OR term.
Definition query_info.h:2154
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:1462
GAIA_NODISCARD bool barrier_may_prune() const
Returns true when any cached archetype can be pruned by the hierarchy barrier.
Definition query_info.h:2271
GAIA_NODISCARD bool has_or() const
Returns true when any of the requested types is present as an Or term.
Definition query_info.h:2173
void recompile()
Recompile the query.
Definition query_info.h:956
GAIA_NODISCARD bool barrier_passes(uint32_t archetypeIdx) const
Returns true when the matched archetype passes the depth-order hierarchy barrier.
Definition query_info.h:2262
GAIA_NODISCARD util::str bytecode() const
Returns a textual dump of the compiled VM bytecode.
Definition query_info.h:2101
MatchArchetypeQueryRet
Query matching result.
Definition query_info.h:109
GAIA_NODISCARD std::span< const Archetype * > cache_archetype_view() const
Returns the cached result archetypes as a span.
Definition query_info.h:2307
GAIA_NODISCARD bool can_update_with_new_archetype() const
Returns true when a new archetype can be propagated into the current cache incrementally.
Definition query_info.h:984
GAIA_NODISCARD bool has_inherited_data_payload() const
Returns true when query iteration needs cached inherited data payloads.
Definition query_info.h:1651
GAIA_NODISCARD uint32_t result_cache_rev() const
Returns the result membership revision used by reverse-index cache users.
Definition query_info.h:979
static GAIA_NODISCARD QueryInfo create(uint32_t idx, uint32_t gen, void *pCtx)
Creates and compiles a query info object from slot creation context.
Definition query_info.h:921
void init(World *world)
Binds this query info to a world after slot allocation.
Definition query_info.h:851
bool del_archetype_from_cache(const Archetype *pArchetype)
Removes an archetype from the final result cache and mirrored payload arrays.
Definition query_info.h:2014
GAIA_NODISCARD QuerySerBuffer & ser_buffer()
Returns the query serialization buffer associated with the owning world.
Definition query_info.h:2083
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:2232
GAIA_NODISCARD bool matches_prefab_entities() const
Returns true when prefab-tagged entities should participate in query results.
Definition query_info.h:2159
void invalidate_sort()
Marks the cached sorted slices dirty without invalidating query membership.
Definition query_info.h:888
void create_inherited_data(const Archetype *pArchetype)
Builds inherited component data pointers for semantic self-source terms on one archetype.
Definition query_info.h:1793
bool del_archetype_from_seed_cache(const Archetype *pArchetype)
Removes an archetype from the structural seed cache.
Definition query_info.h:2055
void add_archetype_to_seed_cache(const Archetype *pArchetype, bool assumeAbsent=false)
Adds an archetype to the structural seed cache.
Definition query_info.h:1848
bool register_archetype(const Archetype &archetype, Entity matchedSelector=EntityBad, bool assumeNew=false)
Registers a new or updated archetype with query caches when it matches this query.
Definition query_info.h:1345
InvalidationKind
Definition query_info.h:146
@ 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.
GAIA_NODISCARD CArchetypeDArray::const_iterator cend() const
Returns a const iterator past the last cached result archetype.
Definition query_info.h:2302
InheritedTermDataView inherited_data_view(uint32_t archetypeIdx) const
Returns cached inherited-term data for a matched archetype index.
Definition query_info.h:2207
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:104
void ser_buffer_reset()
Resets the query serialization buffer associated with the owning world.
Definition query_info.h:2087
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:996
void ensure_comp_indices()
Rebuilds cached component-index payloads for matched archetypes when marked pending.
Definition query_info.h:1638
GAIA_NODISCARD QueryCtx::CachePolicy cache_policy() const
Returns the query cache policy selected during compilation.
Definition query_info.h:964
void match(const ArchetypeLookup &entityToArchetypeMap, std::span< const Archetype * > allArchetypes, const EntityToArchetypeVersionMap *pEntityToArchetypeMapVersions, 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:1035
GAIA_NODISCARD bool has_filters() const
Returns true when the query has per-entity changed/filter terms.
Definition query_info.h:2116
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:948
GAIA_NODISCARD std::span< const SortData > cache_sort_view() const
Returns cached sorted chunk slices.
Definition query_info.h:2312
GAIA_NODISCARD bool has_grouped_payload() const
Returns true when grouped-query payloads are active for this query.
Definition query_info.h:969
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:991
GAIA_NODISCARD bool has_potential_inherited_id_terms() const
Returns true when the query shape can resolve through inherited-id matching.
Definition query_info.h:2128
void ensure_depth_order_hierarchy_barrier_cache_inter()
Rebuilds cached depth-order hierarchy barrier results when relation or enabled-state versions changed...
Definition query_info.h:1719
GAIA_NODISCARD World * world()
Returns the mutable world owning this query.
Definition query_info.h:2072
void sync_result_cache_from_seed_cache()
Rebuilds the final result cache from structural seed matches. Bumps result membership revision only w...
Definition query_info.h:2000
GAIA_NODISCARD bool has_all() const
Returns true when all requested types are present as All terms.
Definition query_info.h:2179
GAIA_NODISCARD CArchetypeDArray::iterator end()
Returns a mutable iterator past the last cached result archetype.
Definition query_info.h:2292
GAIA_NODISCARD Entity direct_target_eval_id() const
Returns the concrete direct target id used by direct target evaluation.
Definition query_info.h:2139
void add_ref()
Adds one external reference to this query slot.
Definition query_info.h:833
void invalidate_result()
Marks final result matches stale while preserving structural seed matches.
Definition query_info.h:883
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:1158
GAIA_NODISCARD bool has_sorted_payload() const
Returns true when sorted-query payloads are active for this query.
Definition query_info.h:974
GAIA_NODISCARD bool has_entity_filter_terms() const
Returns true when direct non-fragmenting terms must be rechecked per entity.
Definition query_info.h:2122
void sort_cache_groups()
Sorts cached archetypes by group id when grouped iteration requested ordering.
Definition query_info.h:1594
GAIA_NODISCARD CArchetypeDArray::const_iterator end() const
Returns an iterator past the last cached result archetype.
Definition query_info.h:2297
uint32_t gen
Generation ID of the query slot.
Definition query_info.h:106
GAIA_NODISCARD bool has_any() const
Returns true when any of the requested types is present as an Any term.
Definition query_info.h:2167
GAIA_NODISCARD uint32_t op_count() const
Returns the number of VM operations in the compiled query.
Definition query_info.h:2106
GAIA_NODISCARD bool has_same_result_membership_as(const CArchetypeDArray &archetypeCache) const
Compares final result-cache membership with another archetype array.
Definition query_info.h:1986
GAIA_NODISCARD CArchetypeDArray::const_iterator cbegin() const
Returns a const iterator to the first cached result archetype.
Definition query_info.h:2287
uint32_t refs() const
Returns the current external reference count.
Definition query_info.h:845
void add_archetype_to_cache(const Archetype *pArchetype, bool trackMembershipChange, bool assumeAbsent)
Adds an archetype to the final result cache and updates derived-payload dirty flags.
Definition query_info.h:1910
GAIA_NODISCARD QueryCtx & ctx()
Returns the mutable compiled query context.
Definition query_info.h:2092
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:2199
static GAIA_NODISCARD QueryHandle handle(const QueryInfo &info)
Builds a stable query handle from query slot metadata.
Definition query_info.h:943
GAIA_NODISCARD CArchetypeDArray::iterator begin()
Returns a mutable iterator to the first cached result archetype.
Definition query_info.h:2277
void invalidate_seed()
Marks structural seed matches stale.
Definition query_info.h:878
GAIA_NODISCARD uint64_t op_signature() const
Returns a stable signature for the compiled VM operation stream.
Definition query_info.h:2111
GAIA_NODISCARD bool has_same_result_membership_as_seed_cache() const
Compares final result-cache membership with the structural seed cache.
Definition query_info.h:1971
void ensure_depth_order_hierarchy_barrier_cache()
Ensures depth-order hierarchy barrier results are current before public reads.
Definition query_info.h:2227
ArchetypeCompIndices create_comp_indices(const Archetype *pArchetype)
Builds field-to-archetype component indices for one matched archetype.
Definition query_info.h:1759
void invalidate(InvalidationKind kind=InvalidationKind::All)
Marks cached query results stale.
Definition query_info.h:862
void ensure_group_data(bool orderGroups)
Rebuilds grouped archetype ranges when a caller needs ordered group data.
Definition query_info.h:1677
GAIA_NODISCARD const QueryCtx & ctx() const
Returns the compiled query context.
Definition query_info.h:2096
void add_archetype_to_cache_w_grouping(const Archetype *pArchetype, bool trackMembershipChange, bool assumeAbsent=false)
Adds an archetype to the final result cache and records its group id.
Definition query_info.h:1881
GAIA_NODISCARD bool can_direct_target_eval() const
Returns true when the query can evaluate concrete target entities directly.
Definition query_info.h:2144
InheritedTermDataView try_inherited_data_view(const Archetype *pArchetype) const
Returns cached inherited-term data if it is already available for a matched archetype.
Definition query_info.h:2243
InheritedTermDataView inherited_data_view(const Archetype *pArchetype) const
Returns cached inherited-term data for a matched archetype pointer.
Definition query_info.h:2217
GAIA_NODISCARD QueryCtx::DirectTargetEvalKind direct_target_eval_kind() const
Returns the direct target evaluation mode selected during compilation.
Definition query_info.h:2134
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:1937
void add_archetype_to_transient_cache(const Archetype *pArchetype)
Adds an archetype to the transient result cache used by non-persistent matching paths.
Definition query_info.h:1922
void add_archetype_to_cache_no_grouping(const Archetype *pArchetype, bool trackMembershipChange, bool assumeAbsent=false)
Adds an archetype to the final result cache for ungrouped queries.
Definition query_info.h:1827
static GAIA_NODISCARD QueryInfo create(QueryId id, QueryCtx &&ctx, const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes)
Creates and compiles a query info object from a moved query context.
Definition query_info.h:898
void dec_ref()
Releases one external reference to this query slot.
Definition query_info.h:839
Definition world.h:174
Wrapper for two Entities forming a relationship pair.
Definition id.h:529
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:224
Compiles query terms into matching bytecode and evaluates that bytecode against archetypes....
Definition vm.h:2311
Same API as ser_buffer_binary, but backed by fully dynamic storage.
Definition ser_buffer_binary.h:157
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
constexpr uint32_t BadIndex
Sentinel index value returned by helpers when a lookup fails.
Definition utility.h:20
Definition query_info.h:31
Definition query_info.h:35
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 query_common.h:117
Hashmap lookup structure used for Entity.
Definition id.h:468
Definition id.h:247
Dependencies deps
Explicit dependency metadata derived from query shape.
Definition query_common.h:788
Definition query_common.h:564
DynamicCacheKind
Dynamic-cache dependency shape derived from compiled query metadata.
Definition query_common.h:613
@ Variable
Dynamic cache tracks runtime variable bindings only.
@ TraversedSource
Dynamic cache tracks a traversed source closure.
@ DirectSource
Dynamic cache tracks concrete source entity archetype versions.
@ Mixed
Dynamic cache tracks more than one dependency family.
@ None
Query does not use dynamic-cache validation.
@ RelationOnly
Dynamic cache is invalidated by relation version dependencies only.
Definition query_common.h:144
Definition query_info.h:94
Direct concrete-source payload for reusable dynamic caches.
Definition query_info.h:303
GAIA_NODISCARD bool changed(const World &world, std::span< const Entity > sourceEntities) const
Returns true when any tracked source entity changed archetype.
Definition query_info.h:308
cnt::sarray< uint32_t, MAX_ITEMS_IN_QUERY > entityVersions
Last seen archetype versions for tracked direct concrete source entities.
Definition query_info.h:305
void snapshot(const World &world, std::span< const Entity > sourceEntities)
Captures current source entity archetype versions.
Definition query_info.h:319
Relation-version payload for reusable dynamic caches with relation dependencies.
Definition query_info.h:279
GAIA_NODISCARD bool changed(const World &world, std::span< const Entity > relations) const
Returns true when any tracked relation version changed.
Definition query_info.h:284
cnt::sarray< uint32_t, MAX_ITEMS_IN_QUERY > versions
Last seen versions for tracked dynamic relation dependencies.
Definition query_info.h:281
void snapshot(const World &world, std::span< const Entity > relations)
Captures current relation versions.
Definition query_info.h:295
Traversed-source payload for reusable dynamic caches with source traversal.
Definition query_info.h:327
GAIA_NODISCARD bool changed(const cnt::darray< SrcTravSnapshotItem > &items) const
Returns true when a rebuilt traversed-source closure differs from the captured snapshot.
Definition query_info.h:357
void mark_overflowed()
Marks the traversed-source snapshot as overflowed and unusable for reuse.
Definition query_info.h:377
void clear()
Clears traversed-source snapshot state.
Definition query_info.h:334
GAIA_NODISCARD bool versions_changed(const World &world) const
Returns true when any tracked traversed source entity changed archetype.
Definition query_info.h:345
cnt::darray< SrcTravSnapshotItem > snapshot
Last seen traversed-source closure for reusable source queries.
Definition query_info.h:329
void capture(const cnt::darray< SrcTravSnapshotItem > &items)
Captures a newly built traversed-source closure snapshot.
Definition query_info.h:371
GAIA_NODISCARD bool is_overflowed() const
Returns true when the traversed-source snapshot is unusable for reuse.
Definition query_info.h:340
bool overflowed
True when the traversed source closure exceeded the configured snapshot cap.
Definition query_info.h:331
Variable-binding payload for reusable dynamic caches with runtime variables.
Definition query_info.h:384
cnt::sarray< Entity, MaxVarCnt > bindings
Snapshot of runtime variable bindings used to build the current dynamic cache.
Definition query_info.h:386
uint8_t bindingMask
Bitmask of the variable bindings captured in bindings.
Definition query_info.h:388
void clear()
Clears runtime variable binding snapshot state.
Definition query_info.h:391
GAIA_NODISCARD bool changed(const cnt::sarray< Entity, MaxVarCnt > &runtimeBindings, uint8_t runtimeBindingMask) const
Returns true when runtime variable bindings differ from the captured snapshot.
Definition query_info.h:397
void snapshot(const cnt::sarray< Entity, MaxVarCnt > &runtimeBindings, uint8_t runtimeBindingMask)
Captures runtime variable bindings.
Definition query_info.h:416
DirectSourcePayload directSource
Direct concrete-source payload.
Definition query_info.h:425
TraversedSourcePayload traversedSource
Traversed-source closure payload.
Definition query_info.h:427
VariablePayload variable
Runtime variable-binding payload.
Definition query_info.h:429
RelationPayload relation
Relation-version payload.
Definition query_info.h:423
void clear_input_snapshots()
Clears transient dynamic input snapshot state while preserving fixed-size version snapshots.
Definition query_info.h:432
void clear()
Releases execution payload storage and clears pending rebuild flags.
Definition query_info.h:218
bool inheritedDataPending
True when archetype membership is populated but inherited-term data still needs to be built on demand...
Definition query_info.h:215
void clear_transient()
Clears execution payload contents while preserving allocated storage.
Definition query_info.h:226
bool compIndicesPending
True when archetype membership is populated but component-index metadata still needs to be built on d...
Definition query_info.h:212
cnt::darray< ArchetypeCompIndices > archetypeCompIndices
Cached component-index mapping for each matched archetype.
Definition query_info.h:207
cnt::darray< ArchetypeInheritedData > archetypeInheritedData
Cached inherited component data pointer per query field for exact self-source semantic terms.
Definition query_info.h:209
cnt::darray< GroupData > archetypeGroupData
Group data used by cache.
Definition query_info.h:178
void clear_transient()
Clears grouped payload contents while preserving allocated storage.
Definition query_info.h:196
void clear()
Releases grouped payload storage and resets cached group selection state.
Definition query_info.h:187
bool selectedGroupDataValid
True when selectedGroupData matches the active group filter.
Definition query_info.h:182
GroupData selectedGroupData
Cached range for the currently selected group id.
Definition query_info.h:180
cnt::darray< GroupId > archetypeGroupIds
Group ids for grouped queries, aligned with archetypeCache.
Definition query_info.h:176
bool dataPending
True when grouped archetype order/ranges need to be rebuilt.
Definition query_info.h:184
uint8_t barrierMayPrune
True when at least one cached archetype fails the depth-order hierarchy barrier.
Definition query_info.h:247
uint32_t barrierEnabledVersion
Entity enable-state version at which the cached depth-order hierarchy barrier state was last rebuilt.
Definition query_info.h:245
cnt::darray< uint8_t > archetypeBarrierPasses
Cached depth-order hierarchy barrier result for each archetype.
Definition query_info.h:236
cnt::darray< SortData > archetypeSortData
Sort data used by cache.
Definition query_info.h:238
void clear_transient()
Clears nontrivial payload contents while preserving allocated storage.
Definition query_info.h:260
uint32_t barrierRelVersion
Relation topology version at which the cached depth-order hierarchy barrier state was last rebuilt.
Definition query_info.h:243
void clear()
Releases nontrivial execution payload storage and resets version stamps.
Definition query_info.h:250
uint32_t sortVersion
World version at which the sorted cache slices were last rebuilt. Unlike worldVersion,...
Definition query_info.h:241
Temporary VM matching buffer meant to be owned by an ECS World. QueryInfo only acquires a frame while...
Definition query_info.h:41
cnt::darr< const Archetype * > matchesArr
Ordered list of matched archetypes emitted by the VM for the current run.
Definition query_info.h:45
void clear_temporary_matches()
Clears all temporary match data, including dedup stamps.
Definition query_info.h:57
uint32_t matchVersion
Monotonic dedup stamp used when the same scratch frame is reused by later full match() calls without ...
Definition query_info.h:54
void clear_temporary_matches_keep_stamps()
Clears temporary match arrays while preserving allocated dedup stamp pages. Full match() can reuse pr...
Definition query_info.h:67
cnt::darray< const Archetype * > matchesArrDynPrev
Previous result membership used when a dynamic rebuild compares reverse-index changes.
Definition query_info.h:51
ArchetypeMatchStamps matchStamps
Paged O(1) dedup table keyed by world-local archetype ids. Pages stay allocated on the scratch frame ...
Definition query_info.h:49
GAIA_NODISCARD uint32_t next_match_version()
Advances and returns the dedup stamp for the next VM match pass.
Definition query_info.h:80
void reset_stamps()
Clears dedup stamps and resets the match-version counter.
Definition query_info.h:73
Definition query_common.h:129
Definition vm.h:65
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:331