Gaia-ECS v1.0.0
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/darray_ext.h"
6#include "gaia/cnt/ilist.h"
7#include "gaia/config/profiler.h"
8#include "gaia/core/hashing_policy.h"
9#include "gaia/core/utility.h"
10#include "gaia/ecs/api.h"
11#include "gaia/ecs/archetype.h"
12#include "gaia/ecs/archetype_common.h"
13#include "gaia/ecs/chunk_iterator.h"
14#include "gaia/ecs/component.h"
15#include "gaia/ecs/component_cache.h"
16#include "gaia/ecs/id.h"
17#include "gaia/ecs/query_common.h"
18#include "gaia/ecs/query_match_stamps.h"
19#include "gaia/ecs/vm.h"
20#include "gaia/mem/mem_utils.h"
21#include "gaia/mem/smallblock_allocator.h"
22
23namespace gaia {
24 namespace ecs {
25 struct Entity;
26 class World;
27
28 uint32_t world_version(const World& world);
29 uint32_t world_archetype_delete_version(const World& world);
30 Entity world_query_first_inherited_owner(const World& world, const Archetype& archetype, Entity term);
31 const void* world_query_inherited_arg_data_const_ptr(const World& world, Entity owner, Entity id);
32
34 using EntityToArchetypeMap = cnt::map<EntityLookupKey, ComponentIndexEntryArray>;
38 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
39 };
40
44 const void* data[ChunkHeader::MAX_COMPONENTS];
45 };
46
50 const Archetype* pArchetype = nullptr;
52 Chunk* pChunk = nullptr;
54 const uint8_t* pCompIndices = nullptr;
56 const void* pData = nullptr;
58 uint32_t dataOffset = UINT32_MAX;
60 uint16_t rowFrom = 0;
62 uint16_t rowTo = 0;
63 };
64
68 GAIA_USE_SMALLBLOCK(QueryMatchScratch)
69
70
71 cnt::darr<const Archetype*> matchesArr;
75 ArchetypeMatchStamps matchStamps;
77 cnt::darray<const Archetype*> matchesArrDynPrev;
80 uint32_t matchVersion = 0;
81
89
97
99 void reset_stamps() {
100 matchStamps.clear();
101 matchVersion = 0;
102 }
103
106 GAIA_NODISCARD uint32_t next_match_version() {
107 ++matchVersion;
108 if (matchVersion != 0)
109 return matchVersion;
110
111 reset_stamps();
112 matchVersion = 1;
113 return matchVersion;
114 }
115 };
116
117 GAIA_NODISCARD QueryMatchScratch& query_match_scratch_acquire(World& world);
118 void query_match_scratch_release(World& world, bool keepStamps);
119
125 const EntityToArchetypeMap* pEntityToArchetypeMap;
127 std::span<const Archetype*> allArchetypes;
128 };
129
131 class QueryInfo {
132 public:
135 uint32_t idx = 0;
137 uint32_t gen = 0;
138
140 enum class MatchArchetypeQueryRet : uint8_t {
142 Fail,
144 Ok,
146 Skip
147 };
148
149 private:
150 struct Instruction {
151 Entity id;
152 QueryOpKind op;
153 };
154
155 struct SortData {
156 Chunk* pChunk;
157 uint32_t archetypeIdx;
158 uint16_t startRow;
159 uint16_t count;
160 };
161
162 struct GroupData {
163 GroupId groupId;
164 uint32_t idxFirst;
165 uint32_t idxLast;
166 bool needsSorting;
167 };
168
169 struct SrcTravSnapshotItem {
170 Entity entity = EntityBad;
172 uint32_t sourceVersion = 0;
173
174 GAIA_NODISCARD bool operator==(const SrcTravSnapshotItem& other) const {
175 return entity == other.entity && sourceVersion == other.sourceVersion;
176 }
177
178 GAIA_NODISCARD bool operator!=(const SrcTravSnapshotItem& other) const {
179 return !operator==(other);
180 }
181 };
182
183 public:
185 enum class InvalidationKind : uint8_t {
187 Result,
189 Seed,
191 All
192 };
193
194 private:
195 struct QueryPlan {
196 QueryCtx ctx;
198 };
199
200 struct QueryState {
201 enum DirtyFlags : uint8_t { Clean = 0x00, Seed = 0x01, Result = 0x02, AllDirty = Seed | Result };
202
204 cnt::set<const Archetype*> seedArchetypeSet;
205 CArchetypeDArray seedArchetypeCache;
206
209 cnt::set<const Archetype*> archetypeSet;
211 CArchetypeDArray archetypeCache;
212
244
246 struct ExecPayload {
256 uint32_t directChunksWorldVersion = UINT32_MAX;
258 uint32_t directChunksEnabledVersion = UINT32_MAX;
260 uint32_t directChunksDeleteVersion = UINT32_MAX;
264 uint32_t directChunksIdxFrom = UINT32_MAX;
266 uint32_t directChunksIdxTo = UINT32_MAX;
268 uint32_t directChunksDataFields[MAX_ITEMS_IN_QUERY]{};
272 const void* directChunksCompIndicesData = nullptr;
275 bool compIndicesPending = false;
279
281 void clear() {
284 directChunks = {};
285 directChunkData = {};
286 directChunksWorldVersion = UINT32_MAX;
287 directChunksEnabledVersion = UINT32_MAX;
288 directChunksDeleteVersion = UINT32_MAX;
290 directChunksIdxFrom = UINT32_MAX;
291 directChunksIdxTo = UINT32_MAX;
294 compIndicesPending = false;
295 inheritedDataPending = false;
296 }
297
315 };
316
325 uint32_t sortVersion{};
327 uint32_t barrierRelVersion = UINT32_MAX;
329 uint32_t barrierEnabledVersion = UINT32_MAX;
331 uint8_t barrierMayPrune = 0;
332
334 void clear() {
337 sortVersion = 0;
338 barrierRelVersion = UINT32_MAX;
339 barrierEnabledVersion = UINT32_MAX;
340 barrierMayPrune = 0;
341 }
342
347 sortVersion = 0;
348 barrierRelVersion = UINT32_MAX;
349 barrierEnabledVersion = UINT32_MAX;
350 barrierMayPrune = 0;
351 }
352 };
353
355 ExecPayload exec;
357 GroupedPayload grouped;
359 NonTrivialPayload nonTrivial;
360
367
372 GAIA_NODISCARD bool changed(const World& world, std::span<const Entity> relations) const {
373 const auto cnt = (uint32_t)relations.size();
374 GAIA_FOR(cnt) {
375 if (versions[i] != world_rel_version(world, relations[i]))
376 return true;
377 }
378
379 return false;
380 }
381
385 void snapshot(const World& world, std::span<const Entity> relations) {
386 const auto cnt = (uint32_t)relations.size();
387 GAIA_FOR(cnt)
388 versions[i] = world_rel_version(world, relations[i]);
389 }
390 };
391
396
401 GAIA_NODISCARD bool changed(const World& world, std::span<const Entity> sourceEntities) const {
402 const auto cnt = (uint32_t)sourceEntities.size();
403 GAIA_FOR(cnt) {
404 if (entityVersions[i] != world_entity_archetype_version(world, sourceEntities[i]))
405 return true;
406 }
407
408 return false;
409 }
410
414 void snapshot(const World& world, std::span<const Entity> sourceEntities) {
415 const auto cnt = (uint32_t)sourceEntities.size();
416 GAIA_FOR(cnt)
417 entityVersions[i] = world_entity_archetype_version(world, sourceEntities[i]);
418 }
419 };
420
426 uint32_t enabledVersion = UINT32_MAX;
428 bool overflowed = false;
429
431 void clear() {
432 snapshot.clear();
433 enabledVersion = UINT32_MAX;
434 overflowed = false;
435 }
436
439 GAIA_NODISCARD bool is_overflowed() const {
440 return overflowed;
441 }
442
446 GAIA_NODISCARD bool versions_changed(const World& world) const {
447 if (enabledVersion != world_enabled_hierarchy_version(world))
448 return true;
449
450 const auto cnt = (uint32_t)snapshot.size();
451 GAIA_FOR(cnt) {
452 const auto& item = snapshot[i];
453 if (item.sourceVersion != world_entity_archetype_version(world, item.entity))
454 return true;
455 }
456
457 return false;
458 }
459
464 GAIA_NODISCARD bool changed(const World& world, const cnt::darray<SrcTravSnapshotItem>& items) const {
465 if (enabledVersion != world_enabled_hierarchy_version(world))
466 return true;
467
468 if (items.size() != snapshot.size())
469 return true;
470
471 const auto cnt = (uint32_t)snapshot.size();
472 GAIA_FOR(cnt) {
473 if (items[i] != snapshot[i])
474 return true;
475 }
476
477 return false;
478 }
479
484 snapshot = items;
485 enabledVersion = world_enabled_hierarchy_version(world);
486 overflowed = false;
487 }
488
491 snapshot.clear();
492 enabledVersion = UINT32_MAX;
493 overflowed = true;
494 }
495 };
496
502 uint8_t bindingMask = 0;
503
505 void clear() {
506 bindingMask = 0;
507 }
508
513 GAIA_NODISCARD bool
514 changed(const cnt::sarray<Entity, MaxVarCnt>& runtimeBindings, uint8_t runtimeBindingMask) const {
515 if (bindingMask != runtimeBindingMask)
516 return true;
517 if (runtimeBindingMask == 0)
518 return false;
519
520 GAIA_FOR(MaxVarCnt) {
521 const auto bit = (uint8_t(1) << i);
522 if ((runtimeBindingMask & bit) == 0)
523 continue;
524
525 if (bindings[i] != runtimeBindings[i])
526 return true;
527 }
528
529 return false;
530 }
531
535 void snapshot(const cnt::sarray<Entity, MaxVarCnt>& runtimeBindings, uint8_t runtimeBindingMask) {
536 bindings = runtimeBindings;
537 bindingMask = runtimeBindingMask;
538 }
539 };
540
549
555 };
556
558 DynamicCacheState dynamic;
559
561 ArchetypeId lastArchetypeId{};
564 uint32_t resultCacheRevision = 1;
566 uint8_t resultCacheMayNeedPrefabFilter = 0;
568 uint8_t dirtyFlags = DirtyFlags::AllDirty;
569
571 void clear_seed_cache() {
572 seedArchetypeSet = {};
573 seedArchetypeCache = {};
574 }
575
577 void clear_result_cache() {
578 archetypeSet = {};
579 archetypeCache = {};
580 resultCacheMayNeedPrefabFilter = 0;
581 exec.clear();
582 grouped.clear();
583 nonTrivial.clear();
584 }
585
587 void clear_transient_result_cache() {
588 archetypeCache.clear();
589 resultCacheMayNeedPrefabFilter = 0;
590 exec.clear_transient();
591 grouped.clear_transient();
592 nonTrivial.clear_transient();
593 }
594
596 void clear_cache() {
597 clear_seed_cache();
598 clear_result_cache();
599 }
600
602 void reset() {
603 clear_cache();
604 dynamic.clear_input_snapshots();
605 lastArchetypeId = 0;
606 dirtyFlags = DirtyFlags::AllDirty;
607 }
608
610 void invalidate_seed() {
611 dirtyFlags = (uint8_t)(dirtyFlags | DirtyFlags::Seed | DirtyFlags::Result);
612 }
613
615 void invalidate_result() {
616 dirtyFlags = (uint8_t)(dirtyFlags | DirtyFlags::Result);
617 }
618
620 void invalidate_all() {
621 dirtyFlags = DirtyFlags::AllDirty;
622 }
623
625 GAIA_NODISCARD bool seed_dirty() const {
626 return (dirtyFlags & DirtyFlags::Seed) != 0;
627 }
628
630 GAIA_NODISCARD bool result_dirty() const {
631 return (dirtyFlags & DirtyFlags::Result) != 0;
632 }
633
635 GAIA_NODISCARD bool needs_refresh() const {
636 return seed_dirty() || result_dirty();
637 }
638
640 void clear_dirty() {
641 dirtyFlags = DirtyFlags::Clean;
642 }
643 };
644
645 uint32_t m_refs = 0;
646#if GAIA_ECS_TEST_HOOKS
648 uint32_t m_testMatchPassCount = 0;
649#endif
650
651 QueryPlan m_plan;
652 QueryState m_state;
653
654 enum QueryCmdType : uint8_t { ALL, OR, NOT };
655
658 void reset_matching_cache(bool trackMembershipChange) {
659 if (trackMembershipChange && !m_state.archetypeCache.empty())
660 mark_result_cache_membership_changed();
661
662 m_state.reset();
663
664 auto& ctxData = m_plan.ctx.data;
665 ctxData.lastMatchedArchetypeIdx_All = {};
666 ctxData.lastMatchedArchetypeIdx_Or = {};
667 ctxData.lastMatchedArchetypeIdx_Not = {};
668 }
669
671 void clear_result_cache() {
672 m_state.clear_result_cache();
673 }
674
677 void invalidate_result_barriers() {
678 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
679 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
680 }
681
684 void mark_result_cache_membership_changed() {
685 invalidate_result_barriers();
686 ++m_state.resultCacheRevision;
687 if (m_state.resultCacheRevision != 0)
688 return;
689
690 // Reserve 0 as the "never synced" value in QueryCache.
691 m_state.resultCacheRevision = 1;
692 }
693
695 GAIA_NODISCARD bool has_dyn_terms() const {
696 return m_plan.ctx.data.cachePolicy == QueryCtx::CachePolicy::Dynamic;
697 }
698
700 GAIA_NODISCARD bool can_reuse_dyn_cache() const {
701 return m_plan.ctx.data.canReuseDynamicCache;
702 }
703
705 GAIA_NODISCARD bool uses_direct_src_version_tracking() const {
706 return m_plan.ctx.data.uses_direct_src_version_tracking();
707 }
708
710 GAIA_NODISCARD bool uses_src_trav_snapshot() const {
711 return m_plan.ctx.data.uses_src_trav_snapshot();
712 }
713
715 GAIA_NODISCARD bool dyn_rel_versions_changed() const {
716 return m_state.dynamic.relation.changed(*world(), m_plan.ctx.data.deps.relations_view());
717 }
718
720 GAIA_NODISCARD bool dyn_var_bindings_changed(
721 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) const {
722 return m_state.dynamic.variable.changed(runtimeVarBindings, runtimeVarBindingMask);
723 }
724
726 GAIA_NODISCARD bool direct_src_versions_changed() const {
727 return m_state.dynamic.directSource.changed(*world(), m_plan.ctx.data.deps.src_entities_view());
728 }
729
733 template <typename Func>
734 void each_reusable_src_entity(Func&& func) const {
735 const auto terms = m_plan.ctx.data.terms_view();
736 const auto cnt = (uint32_t)terms.size();
737 GAIA_FOR(cnt) {
738 const auto& term = terms[i];
739 if (term.src == EntityBad || is_variable(term.src))
740 continue;
741
742 (void)vm::detail::each_lookup_src(*world(), term, term.src, [&](Entity source) {
743 return func(source);
744 });
745 }
746 }
747
750 GAIA_NODISCARD bool build_src_trav_snapshot(cnt::darray<SrcTravSnapshotItem>& items) const {
751 const auto maxItems = (uint32_t)m_plan.ctx.data.cacheSrcTrav;
752 items.clear();
753 if (maxItems == 0)
754 return false;
755
756 bool overflowed = false;
757 each_reusable_src_entity([&](Entity source) {
758 if (items.size() >= maxItems) {
759 overflowed = true;
760 return true;
761 }
762 items.push_back({source, world_entity_archetype_version(*world(), source)});
763 return false;
764 });
765
766 return !overflowed;
767 }
768
771 GAIA_NODISCARD bool traversed_src_versions_changed() const {
772 return m_state.dynamic.traversedSource.versions_changed(*world());
773 }
774
776 GAIA_NODISCARD bool traversed_src_inputs_changed(bool relationVersionsChanged) {
777 if (m_state.dynamic.traversedSource.is_overflowed())
778 return true;
779
780 if (!relationVersionsChanged)
781 return traversed_src_versions_changed();
782
783 cnt::darray<SrcTravSnapshotItem> scratch;
784 if (!build_src_trav_snapshot(scratch))
785 return true;
786
787 return m_state.dynamic.traversedSource.changed(*world(), scratch);
788 }
789
791 GAIA_NODISCARD bool mixed_dyn_inputs_changed(
792 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
793 const auto& deps = m_plan.ctx.data.deps;
794 if (deps.has_dep_flag(QueryCtx::DependencyHasVariableTerms) &&
795 dyn_var_bindings_changed(runtimeVarBindings, runtimeVarBindingMask))
796 return true;
797
798 const bool relationVersionsChanged = dyn_rel_versions_changed();
799 const bool hasSourceTerms = deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms);
800 const bool hasTraversalTerms = deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms);
801 if (relationVersionsChanged && !(hasSourceTerms && hasTraversalTerms))
802 return true;
803
804 if (!hasSourceTerms)
805 return relationVersionsChanged;
806
807 if (m_state.dynamic.traversedSource.is_overflowed())
808 return true;
809
810 if (!hasTraversalTerms)
811 return direct_src_versions_changed();
812
813 return traversed_src_inputs_changed(relationVersionsChanged);
814 }
815
817 GAIA_NODISCARD bool
818 dyn_inputs_changed(const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
819 if (!can_reuse_dyn_cache())
820 return false;
821
822 switch (m_plan.ctx.data.dynamicCacheKind) {
824 return false;
826 return dyn_rel_versions_changed();
828 return direct_src_versions_changed();
830 return traversed_src_inputs_changed(dyn_rel_versions_changed());
832 return dyn_var_bindings_changed(runtimeVarBindings, runtimeVarBindingMask);
834 return mixed_dyn_inputs_changed(runtimeVarBindings, runtimeVarBindingMask);
835 }
836
837 GAIA_ASSERT(false);
838 return true;
839 }
840
842 void snapshot_dyn_rel_inputs() {
843 m_state.dynamic.relation.snapshot(*world(), m_plan.ctx.data.deps.relations_view());
844 }
845
847 void snapshot_dyn_direct_src_inputs() {
848 m_state.dynamic.directSource.snapshot(*world(), m_plan.ctx.data.deps.src_entities_view());
849 }
850
852 void snapshot_dyn_traversed_src_inputs() {
853 cnt::darray<SrcTravSnapshotItem> scratch;
854 if (build_src_trav_snapshot(scratch))
855 m_state.dynamic.traversedSource.capture(*world(), scratch);
856 else
857 m_state.dynamic.traversedSource.mark_overflowed();
858 }
859
861 void
862 snapshot_dyn_var_inputs(const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
863 m_state.dynamic.variable.snapshot(runtimeVarBindings, runtimeVarBindingMask);
864 }
865
867 void
868 snapshot_dyn_inputs(const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
869 if (!can_reuse_dyn_cache())
870 return;
871
872 m_state.dynamic.clear_input_snapshots();
873
874 switch (m_plan.ctx.data.dynamicCacheKind) {
876 return;
878 snapshot_dyn_rel_inputs();
879 return;
881 snapshot_dyn_direct_src_inputs();
882 return;
884 snapshot_dyn_rel_inputs();
885 snapshot_dyn_traversed_src_inputs();
886 return;
888 snapshot_dyn_var_inputs(runtimeVarBindings, runtimeVarBindingMask);
889 return;
891 const auto& deps = m_plan.ctx.data.deps;
892 snapshot_dyn_rel_inputs();
893 if (deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms)) {
894 if (deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms))
895 snapshot_dyn_traversed_src_inputs();
896 else
897 snapshot_dyn_direct_src_inputs();
898 }
899 if (deps.has_dep_flag(QueryCtx::DependencyHasVariableTerms))
900 snapshot_dyn_var_inputs(runtimeVarBindings, runtimeVarBindingMask);
901 return;
902 }
903 }
904
905 GAIA_ASSERT(false);
906 }
911 template <typename TType>
912 GAIA_NODISCARD bool has_inter([[maybe_unused]] QueryOpKind op, bool isReadWrite) const {
913 using T = core::raw_t<TType>;
914
915 if constexpr (std::is_same_v<T, Entity>) {
916 // Entities are read-only.
917 GAIA_ASSERT(!isReadWrite);
918 // Skip Entity input args. Entities are always there.
919 return true;
920 } else {
921 Entity id;
922
923 if constexpr (is_pair<T>::value) {
924 const auto rel = m_plan.ctx.cc->get<typename T::rel>().entity;
925 const auto tgt = m_plan.ctx.cc->get<typename T::tgt>().entity;
926 id = (Entity)Pair(rel, tgt);
927 } else {
928 id = m_plan.ctx.cc->get<T>().entity;
929 }
930
931 const auto& ctxData = m_plan.ctx.data;
932 const auto compIdx = comp_idx<MAX_ITEMS_IN_QUERY>(ctxData.terms.data(), id, EntityBad);
933
934 if (op != ctxData.terms[compIdx].op)
935 return false;
936
937 // Read-write mask must match
938 const uint32_t maskRW = (uint32_t)ctxData.readWriteMask & (1U << compIdx);
939 const uint32_t maskXX = (uint32_t)isReadWrite << compIdx;
940 return maskRW == maskXX;
941 }
942 }
943
947 template <typename T>
948 GAIA_NODISCARD bool has_inter(QueryOpKind op) const {
949 // static_assert(is_raw_v<<T>, "has() must be used with raw types");
950 constexpr bool isReadWrite = core::is_mut_v<T>;
951 return has_inter<T>(op, isReadWrite);
952 }
953
954 public:
956 void add_ref() {
957 ++m_refs;
958 GAIA_ASSERT(m_refs != 0);
959 }
960
962 void dec_ref() {
963 GAIA_ASSERT(m_refs > 0);
964 --m_refs;
965 }
966
969 uint32_t refs() const {
970 return m_refs;
971 }
972
975 void init(World* world) {
976 m_plan.ctx.w = world;
977 }
978
980 void reset() {
981 reset_matching_cache(true);
982 }
983
987 switch (kind) {
989 m_state.invalidate_result();
990 invalidate_result_barriers();
991 break;
993 m_state.invalidate_seed();
994 break;
996 m_state.invalidate_all();
997 break;
998 }
999 }
1000
1005
1010
1013 if (m_plan.ctx.data.sortByFunc != nullptr)
1014 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortEntities;
1015 }
1016
1023 GAIA_NODISCARD static QueryInfo create(
1024 QueryId id, QueryCtx&& ctx, const EntityToArchetypeMap& entityToArchetypeMap,
1025 std::span<const Archetype*> allArchetypes) {
1026 // Make sure query items are sorted
1027 sort(ctx);
1028
1029 QueryInfo info;
1030 info.idx = id;
1031 info.gen = 0;
1032
1033 info.m_plan.ctx = GAIA_MOV(ctx);
1034 info.m_plan.ctx.q.handle = {id, 0};
1035
1036 // Compile the query
1037 info.compile(entityToArchetypeMap, allArchetypes);
1038
1039 return info;
1040 }
1041
1047 GAIA_NODISCARD static QueryInfo create(uint32_t idx, uint32_t gen, void* pCtx) {
1048 auto* pCreationCtx = (QueryInfoCreationCtx*)pCtx;
1049 auto& queryCtx = *pCreationCtx->pQueryCtx;
1050 auto& entityToArchetypeMap = (EntityToArchetypeMap&)*pCreationCtx->pEntityToArchetypeMap;
1051
1052 // Make sure query items are sorted
1053 sort(queryCtx);
1054
1055 QueryInfo info;
1056 info.idx = idx;
1057 info.gen = gen;
1058
1059 info.m_plan.ctx = GAIA_MOV(queryCtx);
1060 info.m_plan.ctx.q.handle = {idx, gen};
1061
1062 // Compile the query
1063 info.compile(entityToArchetypeMap, pCreationCtx->allArchetypes);
1064
1065 return info;
1066 }
1067
1071 GAIA_NODISCARD static QueryHandle handle(const QueryInfo& info) {
1072 return QueryHandle(info.idx, info.gen);
1073 }
1074
1078 void compile(const EntityToArchetypeMap& entityToArchetypeMap, std::span<const Archetype*> allArchetypes) {
1079 GAIA_PROF_SCOPE(queryinfo::compile);
1080
1081 // Compile the opcodes
1082 m_plan.vm.compile(entityToArchetypeMap, allArchetypes, m_plan.ctx);
1083 }
1084
1086 void recompile() {
1087 GAIA_PROF_SCOPE(queryinfo::recompile);
1088
1089 // Compile the opcodes
1090 m_plan.vm.create_opcodes(m_plan.ctx);
1091 }
1092
1095 GAIA_NODISCARD QueryCtx::CachePolicy cache_policy() const {
1096 return m_plan.ctx.data.cachePolicy;
1097 }
1098
1101 GAIA_NODISCARD bool has_grouped_payload() const {
1102 return m_plan.ctx.data.groupBy != EntityBad;
1103 }
1104
1109 template <typename Container>
1110 void group_ids(Container& out, bool sortGroups) {
1111 out.clear();
1112 ensure_group_data(sortGroups);
1113
1114 const auto cnt = (uint32_t)m_state.grouped.archetypeGroupData.size();
1115 out.reserve(cnt);
1116 GAIA_FOR(cnt) {
1117 const auto groupId = m_state.grouped.archetypeGroupData[i].groupId;
1118 if (groupId != 0)
1119 out.push_back(groupId);
1120 }
1121 }
1122
1125 GAIA_NODISCARD bool has_sorted_payload() const {
1126 return m_plan.ctx.data.sortByFunc != nullptr;
1127 }
1128
1131 GAIA_NODISCARD uint32_t result_cache_rev() const {
1132 return m_state.resultCacheRevision;
1133 }
1134
1137 GAIA_NODISCARD bool result_cache_may_need_prefab_filter() const {
1138 return m_state.resultCacheMayNeedPrefabFilter != 0;
1139 }
1140
1143 GAIA_NODISCARD bool can_update_with_new_archetype() const {
1144 // Only immediate structural queries participate in archetype-create propagation.
1145 return m_plan.vm.is_compiled() && cache_policy() == QueryCtx::CachePolicy::Immediate &&
1146 !m_state.needs_refresh();
1147 }
1148
1151 GAIA_NODISCARD bool can_use_direct_create_archetype_match() const {
1152 return m_plan.ctx.data.createArchetypeMatchKind == QueryCtx::CreateArchetypeMatchKind::DirectStructuralTerms;
1153 }
1154
1157 GAIA_NODISCARD bool direct_create_archetype_match_uses_is() const {
1158 const auto& ctxData = m_plan.ctx.data;
1159 return (ctxData.as_mask_0 + ctxData.as_mask_1) != 0;
1160 }
1161
1165 GAIA_NODISCARD bool operator==(const QueryCtx& other) const {
1166 return m_plan.ctx == other;
1167 }
1168
1172 GAIA_NODISCARD bool operator!=(const QueryCtx& other) const {
1173 return m_plan.ctx != other;
1174 }
1175
1182
1186 explicit CleanUpTmpArchetypeMatches(World& w, bool keep): world(w), keepStamps(keep) {}
1189 CleanUpTmpArchetypeMatches& operator=(const CleanUpTmpArchetypeMatches&) = delete;
1191
1193 query_match_scratch_release(world, keepStamps);
1194 }
1195 };
1196
1207 template <typename ArchetypeLookup>
1208 void match(
1209 const ArchetypeLookup& entityToArchetypeMap, std::span<const Archetype*> allArchetypes,
1210 const EntityToArchetypeVersionMap* pEntityToArchetypeMapVersions, ArchetypeId archetypeLastId,
1211 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1212 auto& ctxData = m_plan.ctx.data;
1213
1214 // Recompile if necessary
1215 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1216 recompile();
1217
1218 // Skip if nothing has been compiled.
1219 if (!m_plan.vm.is_compiled())
1220 return;
1221
1222 const bool hasDynamicTerms = has_dyn_terms();
1223 const bool canReuseDynamicCache = can_reuse_dyn_cache();
1224 const bool refreshDynamicCache =
1225 hasDynamicTerms && (!canReuseDynamicCache || m_state.needs_refresh() ||
1226 dyn_inputs_changed(runtimeVarBindings, runtimeVarBindingMask));
1227 bool compareDynamicMembership = false;
1228 QueryMatchScratch* pMatchScratch = nullptr;
1229
1230 if (refreshDynamicCache) {
1231 compareDynamicMembership = !m_state.archetypeCache.empty();
1232 if (compareDynamicMembership) {
1233 auto& w = *world();
1234 pMatchScratch = &query_match_scratch_acquire(w);
1235 pMatchScratch->matchesArrDynPrev = m_state.archetypeCache;
1236 }
1237 // Dynamic queries keep their cached result as long as tracked runtime inputs stay stable.
1238 reset_matching_cache(!compareDynamicMembership);
1239 } else if (m_state.seed_dirty()) {
1240 reset_matching_cache(true);
1241 } else if (m_state.result_dirty()) {
1243 if (m_state.lastArchetypeId == archetypeLastId) {
1244 sort_entities();
1246 m_state.clear_dirty();
1247 return;
1248 }
1249 }
1250
1251 // Skip if no new archetype appeared and the cached match set is still valid.
1252 GAIA_ASSERT(archetypeLastId >= m_state.lastArchetypeId);
1253 if (!m_state.needs_refresh() && m_state.lastArchetypeId == archetypeLastId &&
1254 (!hasDynamicTerms || canReuseDynamicCache)) {
1255 // Sort entities if necessary
1256 sort_entities();
1257 return;
1258 }
1259
1260 m_state.lastArchetypeId = archetypeLastId;
1261
1262 GAIA_PROF_SCOPE(queryinfo::match);
1263
1264 auto& w = *world();
1265 auto& matchScratch = pMatchScratch != nullptr ? *pMatchScratch : query_match_scratch_acquire(w);
1266 CleanUpTmpArchetypeMatches autoCleanup(w, true);
1267
1268 // Prepare the context
1270 ctx.pWorld = &w;
1271 // ctx.targetEntities = {};
1272 ctx.allArchetypes = allArchetypes;
1273 if constexpr (std::is_same_v<ArchetypeLookup, EntityToArchetypeMap>) {
1274 GAIA_ASSERT(pEntityToArchetypeMapVersions != nullptr);
1275 ctx.archetypeLookup = vm::make_archetype_lookup_view(entityToArchetypeMap, *pEntityToArchetypeMapVersions);
1276 } else {
1277 (void)pEntityToArchetypeMapVersions;
1278 ctx.archetypeLookup = vm::make_archetype_lookup_view(entityToArchetypeMap);
1279 }
1280
1281 ctx.pMatchesArr = &matchScratch.matchesArr;
1282 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
1283 ctx.matchesVersion = matchScratch.next_match_version();
1284 ctx.pLastMatchedArchetypeIdx_All = &ctxData.lastMatchedArchetypeIdx_All;
1285 ctx.pLastMatchedArchetypeIdx_Or = &ctxData.lastMatchedArchetypeIdx_Or;
1286 ctx.pLastMatchedArchetypeIdx_Not = &ctxData.lastMatchedArchetypeIdx_Not;
1287 ctx.queryMask = ctxData.queryMask;
1288 ctx.as_mask_0 = ctxData.as_mask_0;
1289 ctx.as_mask_1 = ctxData.as_mask_1;
1290 ctx.flags = ctxData.flags;
1291 ctx.varBindings = runtimeVarBindings;
1292 ctx.varBindingMask = runtimeVarBindingMask;
1293
1294 // Run the virtual machine
1295#if GAIA_ECS_TEST_HOOKS
1296 ++m_testMatchPassCount;
1297#endif
1298 m_plan.vm.exec(ctx);
1299
1300 // Write found matches to cache
1301 const bool trackMembershipChangeOnAdd = !compareDynamicMembership;
1302 for (const auto* pArchetype: *ctx.pMatchesArr) {
1303 if (hasDynamicTerms) {
1304 add_archetype_to_cache(pArchetype, trackMembershipChangeOnAdd, false);
1305 } else {
1306 add_archetype_to_seed_cache(pArchetype);
1307 add_archetype_to_cache(pArchetype, true, false);
1308 }
1309 }
1310
1311 if (compareDynamicMembership) {
1312 if (!has_same_result_membership_as(matchScratch.matchesArrDynPrev))
1313 mark_result_cache_membership_changed();
1314 }
1315
1316 // Sort entities if necessary
1317 sort_entities();
1318 // Sort cache groups if necessary
1320 snapshot_dyn_inputs(runtimeVarBindings, runtimeVarBindingMask);
1321 m_state.clear_dirty();
1322 }
1323
1333 const Archetype& archetype, EntitySpan targetEntities,
1334 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1335 auto& ctxData = m_plan.ctx.data;
1336
1337 // Recompile if necessary
1338 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1339 recompile();
1340
1341 // Skip if nothing has been compiled.
1342 if (!m_plan.vm.is_compiled())
1343 return false;
1344
1345 const bool hasDynamicTerms = has_dyn_terms();
1346 const bool canReuseDynamicCache = can_reuse_dyn_cache();
1347 if ((hasDynamicTerms && (!canReuseDynamicCache || m_state.needs_refresh() ||
1348 dyn_inputs_changed(runtimeVarBindings, runtimeVarBindingMask))) ||
1349 m_state.seed_dirty()) {
1350 // Dynamic queries keep their cached result as long as tracked runtime inputs stay stable.
1351 reset_matching_cache(true);
1352 } else if (m_state.result_dirty()) {
1354 }
1355
1356 GAIA_PROF_SCOPE(queryinfo::match1);
1357
1358 auto& w = *world();
1359 auto& matchScratch = query_match_scratch_acquire(w);
1360 CleanUpTmpArchetypeMatches autoCleanup(w, true);
1361
1362 // Prepare the context
1364 ctx.pWorld = &w;
1365 ctx.targetEntities = targetEntities;
1366 const auto* pArchetype = &archetype;
1367 ctx.allArchetypes = std::span((const Archetype**)&pArchetype, 1);
1368 ctx.archetypeLookup = {};
1369 ctx.pMatchesArr = &matchScratch.matchesArr;
1370 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
1371 ctx.matchesVersion = matchScratch.next_match_version();
1372 ctx.pLastMatchedArchetypeIdx_All = nullptr;
1373 ctx.pLastMatchedArchetypeIdx_Or = nullptr;
1374 ctx.pLastMatchedArchetypeIdx_Not = nullptr;
1375 ctx.queryMask = ctxData.queryMask;
1376 ctx.as_mask_0 = ctxData.as_mask_0;
1377 ctx.as_mask_1 = ctxData.as_mask_1;
1378 ctx.flags = ctxData.flags;
1379 ctx.varBindings = runtimeVarBindings;
1380 ctx.varBindingMask = runtimeVarBindingMask;
1381
1382 // Run the virtual machine
1383#if GAIA_ECS_TEST_HOOKS
1384 ++m_testMatchPassCount;
1385#endif
1386 m_plan.vm.exec(ctx);
1387 const bool matched = !ctx.pMatchesArr->empty();
1388
1389 // Write found matches to cache
1390 for (const auto* pArch: *ctx.pMatchesArr) {
1391 if (hasDynamicTerms) {
1392 add_archetype_to_cache(pArch, true, false);
1393 } else {
1395 add_archetype_to_cache(pArch, true, false);
1396 }
1397 }
1398 snapshot_dyn_inputs(runtimeVarBindings, runtimeVarBindingMask);
1399 m_state.clear_dirty();
1400 return matched;
1401 }
1402
1411 const EntityToArchetypeMap& entityToArchetypeMap, std::span<const Archetype*> allArchetypes,
1412 const EntityToArchetypeVersionMap& entityToArchetypeMapVersions, ArchetypeId archetypeLastId,
1413 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1414 match(
1415 entityToArchetypeMap, allArchetypes, &entityToArchetypeMapVersions, archetypeLastId, runtimeVarBindings,
1416 runtimeVarBindingMask);
1417 }
1418
1426 const EntityToArchetypeMap& entityToArchetypeMap, std::span<const Archetype*> allArchetypes,
1427 const EntityToArchetypeVersionMap& entityToArchetypeMapVersions,
1428 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1429 auto& ctxData = m_plan.ctx.data;
1430
1431 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1432 recompile();
1433
1434 if (!m_plan.vm.is_compiled())
1435 return;
1436
1437 m_state.clear_transient_result_cache();
1438
1439 auto& w = *world();
1440 auto& matchScratch = query_match_scratch_acquire(w);
1441 CleanUpTmpArchetypeMatches autoCleanup(w, true);
1442
1444 ctx.pWorld = &w;
1445 ctx.allArchetypes = allArchetypes;
1446 ctx.archetypeLookup = vm::make_archetype_lookup_view(entityToArchetypeMap, entityToArchetypeMapVersions);
1447 ctx.pMatchesArr = &matchScratch.matchesArr;
1448 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
1449 ctx.matchesVersion = matchScratch.next_match_version();
1450 ctx.pLastMatchedArchetypeIdx_All = nullptr;
1451 ctx.pLastMatchedArchetypeIdx_Or = nullptr;
1452 ctx.pLastMatchedArchetypeIdx_Not = nullptr;
1453 ctx.queryMask = ctxData.queryMask;
1454 ctx.as_mask_0 = ctxData.as_mask_0;
1455 ctx.as_mask_1 = ctxData.as_mask_1;
1456 ctx.flags = ctxData.flags;
1457 ctx.varBindings = runtimeVarBindings;
1458 ctx.varBindingMask = runtimeVarBindingMask;
1459
1460 m_plan.vm.exec(ctx);
1461
1462 m_state.archetypeCache.reserve(ctx.pMatchesArr->size());
1463 if (ctxData.groupBy != EntityBad)
1464 m_state.grouped.archetypeGroupIds.reserve(ctx.pMatchesArr->size());
1465 for (const auto* pArchetype: *ctx.pMatchesArr)
1467
1468 sort_entities();
1469 ensure_group_data(false);
1470 }
1471
1479 const Archetype& archetype, EntitySpan targetEntities,
1480 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1481 return match_one(archetype, targetEntities, runtimeVarBindings, runtimeVarBindingMask);
1482 }
1483
1491 const Archetype& archetype, EntitySpan targetEntities,
1492 const cnt::sarray<Entity, MaxVarCnt>& runtimeVarBindings, uint8_t runtimeVarBindingMask) {
1493 auto& ctxData = m_plan.ctx.data;
1494
1495 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1496 recompile();
1497
1498 if (!m_plan.vm.is_compiled())
1499 return false;
1500
1501 m_state.clear_transient_result_cache();
1502
1503 auto& w = *world();
1504 auto& matchScratch = query_match_scratch_acquire(w);
1505 CleanUpTmpArchetypeMatches autoCleanup(w, true);
1506
1508 ctx.pWorld = &w;
1509 ctx.targetEntities = targetEntities;
1510 const auto* pArchetype = &archetype;
1511 ctx.allArchetypes = std::span((const Archetype**)&pArchetype, 1);
1512 ctx.archetypeLookup = {};
1513 ctx.pMatchesArr = &matchScratch.matchesArr;
1514 ctx.pMatchesStampByArchetypeId = &matchScratch.matchStamps;
1515 ctx.matchesVersion = matchScratch.next_match_version();
1516 ctx.pLastMatchedArchetypeIdx_All = nullptr;
1517 ctx.pLastMatchedArchetypeIdx_Or = nullptr;
1518 ctx.pLastMatchedArchetypeIdx_Not = nullptr;
1519 ctx.queryMask = ctxData.queryMask;
1520 ctx.as_mask_0 = ctxData.as_mask_0;
1521 ctx.as_mask_1 = ctxData.as_mask_1;
1522 ctx.flags = ctxData.flags;
1523 ctx.varBindings = runtimeVarBindings;
1524 ctx.varBindingMask = runtimeVarBindingMask;
1525
1526 m_plan.vm.exec(ctx);
1527 const bool matched = !ctx.pMatchesArr->empty();
1528
1529 m_state.archetypeCache.reserve(ctx.pMatchesArr->size());
1530 if (ctxData.groupBy != EntityBad)
1531 m_state.grouped.archetypeGroupIds.reserve(ctx.pMatchesArr->size());
1532 for (const auto* pArch: *ctx.pMatchesArr)
1534
1535 ensure_group_data(false);
1536 return matched;
1537 }
1538
1544 bool register_archetype(const Archetype& archetype, Entity matchedSelector = EntityBad, bool assumeNew = false) {
1545 auto& ctxData = m_plan.ctx.data;
1546
1547 // Recompile if necessary.
1548 if ((ctxData.flags & QueryCtx::QueryFlags::Recompile) != 0)
1549 recompile();
1550
1552 return false;
1553
1554 const bool hadMatchBefore = !assumeNew && m_state.archetypeSet.contains(&archetype);
1556 const bool usesIs = direct_create_archetype_match_uses_is();
1557 bool hasOrTerms = false;
1558 bool matchedOrTerm = false;
1559 for (const auto& term: ctxData.terms_view()) {
1560 if (term.id == matchedSelector) {
1561 if (term.op == QueryOpKind::Or)
1562 matchedOrTerm = true;
1563 continue;
1564 }
1565
1566 const bool present = usesIs ? vm::detail::match_single_id_on_archetype(*world(), archetype, term.id)
1567 : world_component_index_match_count(*world(), archetype, term.id) != 0;
1568 if (term.op == QueryOpKind::Or) {
1569 hasOrTerms = true;
1570 matchedOrTerm |= present;
1571 continue;
1572 }
1573 if (term.op == QueryOpKind::Any)
1574 continue;
1575
1576 const bool matched = term.op == QueryOpKind::Not ? !present : present;
1577 if (!matched)
1578 return false;
1579 }
1580 if (hasOrTerms && !matchedOrTerm)
1581 return false;
1582 if (hadMatchBefore)
1583 return false;
1584
1585 if (assumeNew)
1586 add_new_archetype_to_immediate_caches(&archetype, true);
1587 else {
1588 add_archetype_to_seed_cache(&archetype, false);
1589 add_archetype_to_cache(&archetype, true, false);
1590 }
1591 return true;
1592 }
1593
1594 SingleArchetypeLookup singleArchetypeLookup;
1595 auto addLookupUnique = [&](Entity key, uint16_t compIdx, uint16_t matchCount) {
1596 const auto keyLookup = EntityLookupKey(key);
1597 const auto itLookup =
1598 core::find_if(singleArchetypeLookup.begin(), singleArchetypeLookup.end(), [&](const auto& item) {
1599 return item.matches(keyLookup);
1600 });
1601 if (itLookup != singleArchetypeLookup.end()) {
1602 auto& entry = itLookup->entry;
1603 entry.matchCount = (uint16_t)(entry.matchCount + matchCount);
1604 if (compIdx != ComponentIndexBad)
1605 entry.compIdx = compIdx;
1606 return;
1607 }
1608 singleArchetypeLookup.push_back(
1610 keyLookup, ComponentIndexEntry{const_cast<Archetype*>(&archetype), compIdx, matchCount}});
1611 };
1612 auto archetypeIds = archetype.ids_view();
1613 const auto cntIds = (uint32_t)archetypeIds.size();
1614 GAIA_FOR(cntIds) {
1615 const auto entity = archetypeIds[i];
1616 singleArchetypeLookup.push_back(
1618 EntityLookupKey(entity), ComponentIndexEntry{const_cast<Archetype*>(&archetype), (uint16_t)i, 1}});
1619
1620 if (!entity.pair())
1621 continue;
1622
1623 // Wildcard pair lookups use the same special records as the world-level
1624 // entity-to-archetype map, so incremental matching can reuse the normal VM path.
1625 const auto rel = Entity((EntityId)entity.id(), 0, false, false);
1626 const auto tgt = Entity((EntityId)entity.gen(), 0, false, false);
1627 addLookupUnique(Pair(All, tgt), ComponentIndexBad, 1);
1628 addLookupUnique(Pair(rel, All), ComponentIndexBad, 1);
1629 addLookupUnique(Pair(All, All), ComponentIndexBad, 1);
1630 }
1631
1632 auto lastMatchedArchetypeIdx_All = GAIA_MOV(ctxData.lastMatchedArchetypeIdx_All);
1633 auto lastMatchedArchetypeIdx_Or = GAIA_MOV(ctxData.lastMatchedArchetypeIdx_Or);
1634 auto lastMatchedArchetypeIdx_Not = GAIA_MOV(ctxData.lastMatchedArchetypeIdx_Not);
1635 GAIA_ASSERT(ctxData.lastMatchedArchetypeIdx_All.empty());
1636 GAIA_ASSERT(ctxData.lastMatchedArchetypeIdx_Or.empty());
1637 GAIA_ASSERT(ctxData.lastMatchedArchetypeIdx_Not.empty());
1638
1639 const auto* pArchetype = &archetype;
1640 const cnt::sarray<Entity, MaxVarCnt> noRuntimeVarBindings{};
1641 match(
1642 singleArchetypeLookup, std::span((const Archetype**)&pArchetype, 1), nullptr, archetype.id(),
1643 noRuntimeVarBindings, 0);
1644 ctxData.lastMatchedArchetypeIdx_All = GAIA_MOV(lastMatchedArchetypeIdx_All);
1645 ctxData.lastMatchedArchetypeIdx_Or = GAIA_MOV(lastMatchedArchetypeIdx_Or);
1646 ctxData.lastMatchedArchetypeIdx_Not = GAIA_MOV(lastMatchedArchetypeIdx_Not);
1647 const bool matched = assumeNew ? m_state.archetypeSet.contains(&archetype)
1648 : !hadMatchBefore && m_state.archetypeSet.contains(&archetype);
1649
1650 if (!matched)
1651 return false;
1652
1653 sort_entities();
1655 return true;
1656 }
1657
1661 GAIA_PROF_SCOPE(queryinfo::calc_sort_data);
1662
1663 m_state.nonTrivial.archetypeSortData.clear();
1664
1665 // The function doesn't do any moves and expects that all chunks have their data sorted already.
1666 // We use a min-heap / priority queue - like structure during query iteration to merge sorted tables:
1667 // - we hold a cursor into each sorted chunk
1668 // - we compare the next entity from each chunk using your sorting function
1669 // - we then pick the smallest one (like k-way merge sort), and advance that cursor
1670 // This is esentially what this function does:
1671 // while (any_chunk_has_entities) {
1672 // find_chunk_with_smallest_next_element();
1673 // yield(entity_from_that_chunk);
1674 // adv_cursor_for_that_chunk();
1675 // }
1676 // This produces a globally sorted view without modifying actual data. It's a balance between
1677 // performance and memory usage. We could also sort the data in-place across all chunks, but that
1678 // would generated too many data moves (entities + all of their components).
1679
1680 struct Cursor {
1681 uint32_t chunkIdx = 0;
1682 uint16_t row = 0;
1683 };
1684
1685 auto& archetypes = m_state.archetypeCache;
1686
1687 // Initialize cursors. We will need as many as there are archetypes.
1688 cnt::sarray_ext<Cursor, 128> cursors(archetypes.size());
1689
1690 uint32_t currArchetypeIdx = (uint32_t)-1;
1691 Chunk* pCurrentChunk = nullptr;
1692 uint16_t currentStartRow = 0;
1693 uint16_t currentRow = 0;
1694
1695 const void* pDataMin = nullptr;
1696 const void* pDataCurr = nullptr;
1697
1698 while (true) {
1699 uint32_t minArchetypeIdx = (uint32_t)-1;
1700 Entity minEntity = EntityBad;
1701
1702 // Find the next entity across all tables/chunks
1703 for (uint32_t t = 0; t < archetypes.size(); ++t) {
1704 const auto* pArchetype = archetypes[t];
1705 const auto& chunks = pArchetype->chunks();
1706 auto& cur = cursors[t];
1707
1708 while (cur.chunkIdx < chunks.size() && cur.row >= chunks[cur.chunkIdx]->size()) {
1709 ++cur.chunkIdx;
1710 cur.row = 0;
1711 }
1712
1713 if (cur.chunkIdx >= chunks.size())
1714 continue;
1715
1716 const auto* pChunk = pArchetype->chunks()[cur.chunkIdx];
1717 auto entity = pChunk->entity_view()[cur.row];
1718
1719 if (m_plan.ctx.data.sortBy != ecs::EntityBad) {
1720 auto compIdx = world_component_index_comp_idx(*m_plan.ctx.w, *pArchetype, m_plan.ctx.data.sortBy);
1721 if (compIdx == BadIndex)
1722 compIdx = pChunk->comp_idx(m_plan.ctx.data.sortBy);
1723 pDataCurr = pChunk->comp_ptr(compIdx, cur.row);
1724 } else
1725 pDataCurr = &pChunk->entity_view()[cur.row];
1726
1727 if (minEntity == EntityBad) {
1728 minEntity = entity;
1729 minArchetypeIdx = t;
1730 pDataMin = pDataCurr;
1731 continue;
1732 }
1733
1734 if (m_plan.ctx.data.sortByFunc(*m_plan.ctx.w, pDataCurr, pDataMin) < 0) {
1735 minEntity = entity;
1736 minArchetypeIdx = t;
1737 }
1738 }
1739
1740 // No more results found, we can stop
1741 if (minArchetypeIdx == (uint32_t)-1)
1742 break;
1743
1744 auto& cur = cursors[minArchetypeIdx];
1745 const auto& chunks = archetypes[minArchetypeIdx]->chunks();
1746 Chunk* pChunk = chunks[cur.chunkIdx];
1747
1748 if (minArchetypeIdx == currArchetypeIdx && pChunk == pCurrentChunk) {
1749 // Current slice
1750 } else {
1751 // End previous slice
1752 if (pCurrentChunk != nullptr) {
1753 m_state.nonTrivial.archetypeSortData.push_back(
1754 {pCurrentChunk, currArchetypeIdx, currentStartRow, (uint16_t)(currentRow - currentStartRow)});
1755 }
1756
1757 // Start a new slice
1758 currArchetypeIdx = minArchetypeIdx;
1759 pCurrentChunk = pChunk;
1760 currentStartRow = cur.row;
1761 }
1762
1763 ++cur.row;
1764 currentRow = cur.row;
1765 }
1766
1767 if (pCurrentChunk != nullptr) {
1768 m_state.nonTrivial.archetypeSortData.push_back(
1769 {pCurrentChunk, currArchetypeIdx, currentStartRow, (uint16_t)(currentRow - currentStartRow)});
1770 }
1771 }
1772
1775 if (m_plan.ctx.data.sortByFunc == nullptr)
1776 return;
1777
1778 if ((m_plan.ctx.data.flags & QueryCtx::QueryFlags::SortEntities) == 0 && m_state.nonTrivial.sortVersion != 0)
1779 return;
1780 m_plan.ctx.data.flags &= (QueryCtx::QueryFlags)(~(uint16_t)QueryCtx::QueryFlags::SortEntities);
1781
1782 // First, sort entities in archetypes
1783 for (const auto* pArchetype: m_state.archetypeCache)
1784 const_cast<Archetype*>(pArchetype)->sort_entities(m_plan.ctx.data.sortBy, m_plan.ctx.data.sortByFunc);
1785
1786 // Now that entites are sorted, we can start creating slices
1788 m_state.nonTrivial.sortVersion = ::gaia::ecs::world_version(*world());
1789 }
1790
1793 if ((m_plan.ctx.data.flags & QueryCtx::QueryFlags::SortGroups) == 0)
1794 return;
1795 m_plan.ctx.data.flags &= (QueryCtx::QueryFlags)(~(uint16_t)QueryCtx::QueryFlags::SortGroups);
1796
1797 if ((m_plan.ctx.data.flags & QueryCtx::QueryFlags::OrderGroups) != 0)
1798 ensure_group_data(true);
1799 }
1800
1804 void swap_archetype_cache_entry(uint32_t left, uint32_t right) {
1805 auto* pTmpArchetype = m_state.archetypeCache[left];
1806 m_state.archetypeCache[left] = m_state.archetypeCache[right];
1807 m_state.archetypeCache[right] = pTmpArchetype;
1808
1809 if (left < m_state.grouped.archetypeGroupIds.size() && right < m_state.grouped.archetypeGroupIds.size()) {
1810 const auto tmp = m_state.grouped.archetypeGroupIds[left];
1811 m_state.grouped.archetypeGroupIds[left] = m_state.grouped.archetypeGroupIds[right];
1812 m_state.grouped.archetypeGroupIds[right] = tmp;
1813 }
1814
1815 if (left < m_state.exec.archetypeCompIndices.size() && right < m_state.exec.archetypeCompIndices.size()) {
1816 auto tmp = m_state.exec.archetypeCompIndices[left];
1817 m_state.exec.archetypeCompIndices[left] = m_state.exec.archetypeCompIndices[right];
1818 m_state.exec.archetypeCompIndices[right] = tmp;
1819 }
1820
1821 if (left < m_state.exec.archetypeInheritedData.size() && right < m_state.exec.archetypeInheritedData.size()) {
1822 auto tmp = m_state.exec.archetypeInheritedData[left];
1823 m_state.exec.archetypeInheritedData[left] = m_state.exec.archetypeInheritedData[right];
1824 m_state.exec.archetypeInheritedData[right] = tmp;
1825 }
1826
1827 if (left < m_state.nonTrivial.archetypeBarrierPasses.size() &&
1828 right < m_state.nonTrivial.archetypeBarrierPasses.size()) {
1829 const auto tmp = m_state.nonTrivial.archetypeBarrierPasses[left];
1830 m_state.nonTrivial.archetypeBarrierPasses[left] = m_state.nonTrivial.archetypeBarrierPasses[right];
1831 m_state.nonTrivial.archetypeBarrierPasses[right] = tmp;
1832 }
1833 }
1834
1837 if (!m_state.exec.compIndicesPending)
1838 return;
1839
1840 m_state.exec.archetypeCompIndices.clear();
1841 m_state.exec.archetypeCompIndices.reserve(m_state.archetypeCache.size());
1842 for (const auto* pArchetype: m_state.archetypeCache)
1843 m_state.exec.archetypeCompIndices.push_back(create_comp_indices(pArchetype));
1844
1845 m_state.exec.compIndicesPending = false;
1846 }
1847
1867 void
1868 ensure_direct_chunks(uint32_t idxFrom, uint32_t idxTo, const uint32_t* pDataFields, uint32_t dataFieldCount) {
1870 const auto* pCompIndicesData = m_state.exec.archetypeCompIndices.data();
1871 const auto dataFieldsMatch = [&]() {
1872 if (m_state.exec.directChunksDataFieldCount != dataFieldCount)
1873 return false;
1874 GAIA_FOR(dataFieldCount) {
1875 if (m_state.exec.directChunksDataFields[i] != pDataFields[i])
1876 return false;
1877 }
1878 return true;
1879 };
1880
1881 const auto currWorldVersion = ::gaia::ecs::world_version(*world());
1882 const auto currEnabledVersion = world_enabled_hierarchy_version(*world());
1883 const auto currDeleteVersion = world_archetype_delete_version(*world());
1884 if (m_state.exec.directChunksWorldVersion == currWorldVersion &&
1885 m_state.exec.directChunksEnabledVersion == currEnabledVersion &&
1886 m_state.exec.directChunksDeleteVersion == currDeleteVersion &&
1887 m_state.exec.directChunksResultRevision == m_state.resultCacheRevision &&
1888 m_state.exec.directChunksIdxFrom == idxFrom && m_state.exec.directChunksIdxTo == idxTo &&
1889 dataFieldsMatch() && m_state.exec.directChunksCompIndicesData == pCompIndicesData)
1890 return;
1891
1892 m_state.exec.directChunks.clear();
1893 m_state.exec.directChunkData.clear();
1894 m_state.exec.directChunksIdxFrom = idxFrom;
1895 m_state.exec.directChunksIdxTo = idxTo;
1896 m_state.exec.directChunksDataFieldCount = dataFieldCount;
1897 GAIA_FOR(dataFieldCount) {
1898 m_state.exec.directChunksDataFields[i] = pDataFields[i];
1899 }
1900 m_state.exec.directChunksResultRevision = m_state.resultCacheRevision;
1901 m_state.exec.directChunksWorldVersion = currWorldVersion;
1902 m_state.exec.directChunksEnabledVersion = currEnabledVersion;
1903 m_state.exec.directChunksDeleteVersion = currDeleteVersion;
1904 m_state.exec.directChunksCompIndicesData = pCompIndicesData;
1905
1906 uint32_t chunkCnt = 0;
1907 for (uint32_t i = idxFrom; i < idxTo; ++i)
1908 chunkCnt += (uint32_t)m_state.archetypeCache[i]->chunks().size();
1909 m_state.exec.directChunks.reserve(chunkCnt);
1910 m_state.exec.directChunkData.reserve(chunkCnt * dataFieldCount);
1911
1912 for (uint32_t i = idxFrom; i < idxTo; ++i) {
1913 const auto* pArchetype = m_state.archetypeCache[i];
1914 if (pArchetype->is_req_del())
1915 continue;
1916 const auto* pCompIndices = m_state.exec.archetypeCompIndices[i].indices;
1917 for (auto* pChunk: pArchetype->chunks()) {
1918 const auto from = detail::ChunkIterImpl::start_index(pChunk, Constraints::EnabledOnly);
1919 const auto to = detail::ChunkIterImpl::end_index(pChunk, Constraints::EnabledOnly);
1920 if (from != to) {
1921 const void* pData = nullptr;
1922 uint32_t dataOffset = UINT32_MAX;
1923 if (dataFieldCount == 1) {
1928 const auto dataFieldIdx = pDataFields[0];
1929 const auto compIdx =
1930 dataFieldIdx < ChunkHeader::MAX_COMPONENTS ? pCompIndices[dataFieldIdx] : uint8_t(0xFF);
1931 if (compIdx != 0xFF)
1932 pData = pChunk->comp_rec_view()[compIdx].pData;
1933 } else if (dataFieldCount > 1) {
1934 dataOffset = (uint32_t)m_state.exec.directChunkData.size();
1935 const auto recs = pChunk->comp_rec_view();
1936 GAIA_FOR_(dataFieldCount, fieldIdx) {
1937 const auto dataFieldIdx = pDataFields[fieldIdx];
1938 const auto compIdx =
1939 dataFieldIdx < ChunkHeader::MAX_COMPONENTS ? pCompIndices[dataFieldIdx] : uint8_t(0xFF);
1940 m_state.exec.directChunkData.push_back(compIdx != 0xFF ? recs[compIdx].pData : nullptr);
1941 }
1942 }
1943 m_state.exec.directChunks.push_back({pArchetype, pChunk, pCompIndices, pData, dataOffset, from, to});
1944 }
1945 }
1946 }
1947 }
1948
1954
1957 if (!m_state.exec.inheritedDataPending)
1958 return;
1959
1961 m_state.exec.archetypeInheritedData.clear();
1962 m_state.exec.inheritedDataPending = false;
1963 return;
1964 }
1965
1966 m_state.exec.archetypeInheritedData.clear();
1967 m_state.exec.archetypeInheritedData.reserve(m_state.archetypeCache.size());
1968 for (const auto* pArchetype: m_state.archetypeCache)
1969 create_inherited_data(pArchetype);
1970
1971 m_state.exec.inheritedDataPending = false;
1972 }
1973
1977 void apply_archetype_cache_order(std::span<const uint64_t> sortedKeys) {
1978 const auto cnt = (uint32_t)sortedKeys.size();
1979 // pos[e] is where the entry with initial index e is now. at[p] is the initial index of the entry at p.
1982 pos.resize(cnt);
1983 at.resize(cnt);
1984 GAIA_FOR(cnt) {
1985 pos[i] = i;
1986 at[i] = i;
1987 }
1988
1989 GAIA_FOR(cnt) {
1990 const auto want = (uint32_t)sortedKeys[i];
1991 const auto cur = pos[want];
1992 if (cur == i)
1993 continue;
1994
1996 const auto displaced = at[i];
1997 at[cur] = displaced;
1998 pos[displaced] = cur;
1999 at[i] = want;
2000 pos[want] = i;
2001 }
2002 }
2003
2007 void ensure_group_data(bool orderGroups) {
2008 if (m_plan.ctx.data.groupBy == EntityBad || !m_state.grouped.dataPending)
2009 return;
2010
2011 if (!orderGroups && (m_plan.ctx.data.flags & QueryCtx::QueryFlags::OrderGroups) == 0)
2012 return;
2013
2014 const auto groupCnt = (uint32_t)m_state.grouped.archetypeGroupIds.size();
2015 // Include the initial cache index so equal groups keep deterministic order under a strict comparator.
2017 sortKeys.resize(groupCnt);
2018 GAIA_FOR(groupCnt) {
2019 sortKeys[i] = ((uint64_t)m_state.grouped.archetypeGroupIds[i] << 32U) | i;
2020 }
2021
2022 // The keys are unique, so sort them alone and then move each cache entry once. Dragging the cache entries
2023 // through every swap of the sort would cost more swaps and inline the heavy swap into the sorting networks.
2024 core::sort(sortKeys, core::is_smaller<uint64_t>{});
2026
2027 m_state.grouped.archetypeGroupData.clear();
2028 m_state.grouped.selectedGroupDataValid = false;
2029
2030 if (m_state.grouped.archetypeGroupIds.empty()) {
2031 m_state.grouped.dataPending = false;
2032 return;
2033 }
2034
2035 GroupId groupId = m_state.grouped.archetypeGroupIds[0];
2036 uint32_t idxFirst = 0;
2037 const auto cnt = (uint32_t)m_state.grouped.archetypeGroupIds.size();
2038 for (uint32_t i = 1; i < cnt; ++i) {
2039 if (m_state.grouped.archetypeGroupIds[i] == groupId)
2040 continue;
2041
2042 m_state.grouped.archetypeGroupData.push_back({groupId, idxFirst, i - 1, false});
2043 groupId = m_state.grouped.archetypeGroupIds[i];
2044 idxFirst = i;
2045 }
2046
2047 m_state.grouped.archetypeGroupData.push_back({groupId, idxFirst, cnt - 1, false});
2048 m_state.grouped.dataPending = false;
2049 }
2050
2053 if (!world_relation_depth_order_prunes_disabled_subtrees(*world(), m_plan.ctx.data.groupBy))
2054 return;
2055
2056 ensure_group_data(true);
2057
2058 const auto currRelationVersion = world_rel_version(*world(), m_plan.ctx.data.groupBy);
2059 const auto currEnabledVersion = world_enabled_hierarchy_version(*world());
2060 if (m_state.nonTrivial.barrierRelVersion == currRelationVersion &&
2061 m_state.nonTrivial.barrierEnabledVersion == currEnabledVersion)
2062 return;
2063
2064 m_state.nonTrivial.archetypeBarrierPasses.resize(m_state.archetypeCache.size(), 1);
2065 m_state.nonTrivial.barrierMayPrune = 0;
2066
2067 const auto relation = m_plan.ctx.data.groupBy;
2068 for (uint32_t i = 0; i < m_state.archetypeCache.size(); ++i) {
2069 const auto* pArchetype = m_state.archetypeCache[i];
2070 auto& barrierPasses = m_state.nonTrivial.archetypeBarrierPasses[i];
2071 barrierPasses = 1;
2072
2073 auto ids = pArchetype->ids_view();
2074 for (auto idsIdx: pArchetype->pair_rel_indices(relation)) {
2075 const auto pair = ids[idsIdx];
2076 const auto parent = world_pair_target_if_alive(*world(), pair);
2077 if (parent == EntityBad || !world_entity_enabled_hierarchy(*world(), parent, relation)) {
2078 barrierPasses = 0;
2079 m_state.nonTrivial.barrierMayPrune = 1;
2080 break;
2081 }
2082 }
2083 }
2084
2085 m_state.nonTrivial.barrierRelVersion = currRelationVersion;
2086 m_state.nonTrivial.barrierEnabledVersion = currEnabledVersion;
2087 }
2088
2093 ArchetypeCompIndices cacheData{};
2094 core::fill(cacheData.indices, cacheData.indices + ChunkHeader::MAX_COMPONENTS, (uint8_t)0xFF);
2095 const auto terms = ctx().data.terms_view();
2096 const auto cnt = (uint32_t)terms.size();
2097 GAIA_FOR(cnt) {
2098 const auto& term = terms[i];
2099 const auto fieldIdx = term.fieldIndex;
2100 const auto queryId = term.id;
2104 if (!query_term_maps_to_current_archetype(term))
2105 continue;
2106
2107 if (world_component_uses_sparse_storage(*world(), queryId)) {
2108 cacheData.indices[fieldIdx] = 0xFF;
2109 continue;
2110 }
2111
2112 auto compIdx = world_component_index_comp_idx(*world(), *pArchetype, queryId);
2113 if (compIdx == BadIndex || compIdx == ComponentIndexBad) {
2114 // Wildcard/semantic terms are not represented by an exact component index entry.
2115 // OR terms may also be absent from this matching archetype. Use a safe lookup so
2116 // absent terms stay unmapped instead of becoming a past-the-end chunk column.
2117 compIdx = core::get_index(pArchetype->ids_view(), queryId);
2118 }
2119
2120 cacheData.indices[fieldIdx] =
2121 (compIdx != BadIndex && compIdx != ComponentIndexBad && compIdx < pArchetype->ids_view().size())
2122 ? (uint8_t)compIdx
2123 : (uint8_t)0xFF;
2124 }
2125 return cacheData;
2126 }
2127
2130 void create_inherited_data(const Archetype* pArchetype) {
2131 ArchetypeInheritedData inheritedData{};
2132 core::fill(inheritedData.data, inheritedData.data + ChunkHeader::MAX_COMPONENTS, nullptr);
2133
2134 const auto terms = ctx().data.terms_view();
2135 const auto cnt = (uint32_t)terms.size();
2136 GAIA_FOR(cnt) {
2137 const auto& term = terms[i];
2138 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
2139 continue;
2140 if (term.matchKind != QueryMatchKind::Semantic)
2141 continue;
2142 const auto queryId = term.id;
2143 if (queryId == EntityBad || is_wildcard(queryId) || is_variable((EntityId)queryId.id()))
2144 continue;
2145 if (world_component_uses_sparse_storage(*world(), queryId))
2146 continue;
2147 if (!world_term_uses_inherit_policy(*world(), queryId))
2148 continue;
2149 if (pArchetype->has(queryId))
2150 continue;
2151
2152 const auto owner = world_query_first_inherited_owner(*world(), *pArchetype, queryId);
2153 GAIA_ASSERT(owner != EntityBad);
2154 inheritedData.data[term.fieldIndex] = world_query_inherited_arg_data_const_ptr(*world(), owner, queryId);
2155 }
2156
2157 m_state.exec.archetypeInheritedData.push_back(inheritedData);
2158 }
2159
2163 if (!matches_prefab_entities() && pArchetype->has(Prefab))
2164 m_state.resultCacheMayNeedPrefabFilter = 1;
2165 }
2166
2172 const Archetype* pArchetype, bool trackMembershipChange, bool assumeAbsent = false) {
2173 GAIA_PROF_SCOPE(queryinfo::add_cache_ng);
2174
2175 if (!assumeAbsent && m_state.archetypeSet.contains(pArchetype))
2176 return;
2177 GAIA_ASSERT(assumeAbsent || !m_state.archetypeSet.contains(pArchetype));
2178
2179 m_state.archetypeSet.emplace(pArchetype);
2180 m_state.archetypeCache.push_back(pArchetype);
2182 m_state.exec.compIndicesPending = true;
2183 m_state.exec.inheritedDataPending = true;
2184 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
2185 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
2186 if (trackMembershipChange)
2187 mark_result_cache_membership_changed();
2188 }
2189
2193 void add_archetype_to_seed_cache(const Archetype* pArchetype, bool assumeAbsent = false) {
2194 if (!assumeAbsent && m_state.seedArchetypeSet.contains(pArchetype))
2195 return;
2196 GAIA_ASSERT(assumeAbsent || !m_state.seedArchetypeSet.contains(pArchetype));
2197
2198 m_state.seedArchetypeSet.emplace(pArchetype);
2199 m_state.seedArchetypeCache.push_back(pArchetype);
2200 }
2201
2205 void add_new_archetype_to_immediate_caches(const Archetype* pArchetype, bool trackMembershipChange) {
2206 GAIA_ASSERT(m_plan.ctx.data.groupBy == EntityBad);
2207 GAIA_ASSERT(m_plan.ctx.data.sortByFunc == nullptr);
2208 GAIA_ASSERT(!m_state.seedArchetypeSet.contains(pArchetype));
2209 GAIA_ASSERT(!m_state.archetypeSet.contains(pArchetype));
2210
2211 m_state.seedArchetypeSet.emplace(pArchetype);
2212 m_state.seedArchetypeCache.push_back(pArchetype);
2213
2214 m_state.archetypeSet.emplace(pArchetype);
2215 m_state.archetypeCache.push_back(pArchetype);
2217 m_state.exec.compIndicesPending = true;
2218 m_state.exec.inheritedDataPending = true;
2219 if (trackMembershipChange)
2220 mark_result_cache_membership_changed();
2221 }
2222
2228 const Archetype* pArchetype, bool trackMembershipChange, bool assumeAbsent = false) {
2229 GAIA_PROF_SCOPE(queryinfo::add_cache_wg);
2230
2231 if (!assumeAbsent && m_state.archetypeSet.contains(pArchetype))
2232 return;
2233 GAIA_ASSERT(assumeAbsent || !m_state.archetypeSet.contains(pArchetype));
2234
2235 m_state.grouped.selectedGroupDataValid = false;
2236
2237 const GroupId groupId = m_plan.ctx.data.groupByFunc(*m_plan.ctx.w, *pArchetype, m_plan.ctx.data.groupBy);
2238
2239 m_state.archetypeSet.emplace(pArchetype);
2240 m_state.archetypeCache.push_back(pArchetype);
2242 m_state.grouped.archetypeGroupIds.push_back(groupId);
2243 m_state.grouped.dataPending = true;
2244 m_state.exec.compIndicesPending = true;
2245 m_state.exec.inheritedDataPending = true;
2246 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
2247 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
2248 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortGroups;
2249 if (trackMembershipChange)
2250 mark_result_cache_membership_changed();
2251 }
2252
2257 void add_archetype_to_cache(const Archetype* pArchetype, bool trackMembershipChange, bool assumeAbsent) {
2258 if (m_plan.ctx.data.sortByFunc != nullptr)
2259 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortEntities;
2260
2261 if (m_plan.ctx.data.groupBy != EntityBad)
2262 add_archetype_to_cache_w_grouping(pArchetype, trackMembershipChange, assumeAbsent);
2263 else
2264 add_archetype_to_cache_no_grouping(pArchetype, trackMembershipChange, assumeAbsent);
2265 }
2266
2270 m_state.archetypeCache.push_back(pArchetype);
2272 m_state.exec.compIndicesPending = true;
2273 m_state.exec.inheritedDataPending = true;
2274 if (m_plan.ctx.data.groupBy != EntityBad) {
2275 const auto groupId = m_plan.ctx.data.groupByFunc(*m_plan.ctx.w, *pArchetype, m_plan.ctx.data.groupBy);
2276 m_state.grouped.archetypeGroupIds.push_back(groupId);
2277 m_state.grouped.dataPending = true;
2278 }
2279 }
2280
2285 GAIA_NODISCARD const GroupData* selected_group_data(GroupId runtimeGroupId) const {
2286 const_cast<QueryInfo*>(this)->ensure_group_data(true);
2287 if (m_plan.ctx.data.groupBy == EntityBad || runtimeGroupId == 0)
2288 return nullptr;
2289
2290 if (!m_state.grouped.selectedGroupDataValid || m_state.grouped.selectedGroupData.groupId != runtimeGroupId) {
2291 uint32_t left = 0;
2292 uint32_t right = (uint32_t)m_state.grouped.archetypeGroupData.size();
2293 while (left < right) {
2294 const uint32_t mid = left + ((right - left) >> 1);
2295 const auto midGroupId = m_state.grouped.archetypeGroupData[mid].groupId;
2296 if (midGroupId < runtimeGroupId)
2297 left = mid + 1;
2298 else
2299 right = mid;
2300 }
2301
2302 if (left < m_state.grouped.archetypeGroupData.size() &&
2303 m_state.grouped.archetypeGroupData[left].groupId == runtimeGroupId) {
2304 m_state.grouped.selectedGroupData = m_state.grouped.archetypeGroupData[left];
2305 m_state.grouped.selectedGroupDataValid = true;
2306 return &m_state.grouped.selectedGroupData;
2307 }
2308
2309 m_state.grouped.selectedGroupData = {};
2310 m_state.grouped.selectedGroupDataValid = false;
2311 return nullptr;
2312 }
2313
2314 return &m_state.grouped.selectedGroupData;
2315 }
2316
2319 GAIA_NODISCARD bool has_same_result_membership_as_seed_cache() const {
2320 if (m_state.archetypeSet.size() != m_state.seedArchetypeSet.size())
2321 return false;
2322
2323 for (const auto* pArchetype: m_state.seedArchetypeCache) {
2324 if (!m_state.archetypeSet.contains(pArchetype))
2325 return false;
2326 }
2327
2328 return true;
2329 }
2330
2334 GAIA_NODISCARD bool has_same_result_membership_as(const CArchetypeDArray& archetypeCache) const {
2335 if (m_state.archetypeSet.size() != archetypeCache.size())
2336 return false;
2337
2338 for (const auto* pArchetype: archetypeCache) {
2339 if (!m_state.archetypeSet.contains(pArchetype))
2340 return false;
2341 }
2342
2343 return true;
2344 }
2345
2349 const bool membershipChanged = !has_same_result_membership_as_seed_cache();
2350 clear_result_cache();
2351 const auto cnt = (uint32_t)m_state.seedArchetypeCache.size();
2352 GAIA_FOR(cnt) {
2353 add_archetype_to_cache(m_state.seedArchetypeCache[i], false, false);
2354 }
2355 if (membershipChanged)
2356 mark_result_cache_membership_changed();
2357 }
2358
2362 bool del_archetype_from_cache(const Archetype* pArchetype) {
2363 const auto it = m_state.archetypeSet.find(pArchetype);
2364 if (it == m_state.archetypeSet.end())
2365 return false;
2366
2367 m_state.archetypeSet.erase(it);
2368
2369 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
2370 GAIA_ASSERT(archetypeIdx != BadIndex);
2371 if (archetypeIdx == BadIndex)
2372 return true;
2373
2374 if (m_plan.ctx.data.sortByFunc != nullptr)
2375 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortEntities;
2376
2377 core::swap_erase(m_state.archetypeCache, archetypeIdx);
2378 if (archetypeIdx < m_state.exec.archetypeCompIndices.size())
2379 core::swap_erase(m_state.exec.archetypeCompIndices, archetypeIdx);
2380 if (archetypeIdx < m_state.exec.archetypeInheritedData.size())
2381 core::swap_erase(m_state.exec.archetypeInheritedData, archetypeIdx);
2382 if (archetypeIdx < m_state.grouped.archetypeGroupIds.size())
2383 core::swap_erase(m_state.grouped.archetypeGroupIds, archetypeIdx);
2384 if (archetypeIdx < m_state.nonTrivial.archetypeBarrierPasses.size())
2385 core::swap_erase(m_state.nonTrivial.archetypeBarrierPasses, archetypeIdx);
2386
2387 if (m_plan.ctx.data.groupBy != EntityBad) {
2388 m_state.grouped.selectedGroupDataValid = false;
2389 m_state.grouped.archetypeGroupData.clear();
2390 m_state.grouped.dataPending = true;
2391 m_plan.ctx.data.flags |= QueryCtx::QueryFlags::SortGroups;
2392 }
2393 m_state.nonTrivial.barrierRelVersion = UINT32_MAX;
2394 m_state.nonTrivial.barrierEnabledVersion = UINT32_MAX;
2395
2396 mark_result_cache_membership_changed();
2397 return true;
2398 }
2399
2404 const auto it = m_state.seedArchetypeSet.find(pArchetype);
2405 if (it == m_state.seedArchetypeSet.end())
2406 return false;
2407
2408 m_state.seedArchetypeSet.erase(it);
2409
2410 const auto archetypeIdx = core::get_index(m_state.seedArchetypeCache, pArchetype);
2411 GAIA_ASSERT(archetypeIdx != BadIndex);
2412 if (archetypeIdx == BadIndex)
2413 return true;
2414
2415 core::swap_erase(m_state.seedArchetypeCache, archetypeIdx);
2416 return true;
2417 }
2418
2421 GAIA_NODISCARD World* world() {
2422 GAIA_ASSERT(m_plan.ctx.w != nullptr);
2423 return const_cast<World*>(m_plan.ctx.w);
2424 }
2427 GAIA_NODISCARD const World* world() const {
2428 GAIA_ASSERT(m_plan.ctx.w != nullptr);
2429 return m_plan.ctx.w;
2430 }
2431
2434 GAIA_NODISCARD QuerySerBuffer& ser_buffer() {
2435 return m_plan.ctx.q.ser_buffer(world());
2436 }
2439 m_plan.ctx.q.ser_buffer_reset(world());
2440 }
2441
2444 GAIA_NODISCARD QueryCtx& ctx() {
2445 return m_plan.ctx;
2446 }
2449 GAIA_NODISCARD const QueryCtx& ctx() const {
2450 return m_plan.ctx;
2451 }
2452
2455 GAIA_NODISCARD util::str bytecode() const {
2456 return m_plan.vm.bytecode(*world());
2457 }
2458
2461 GAIA_NODISCARD uint32_t op_count() const {
2462 return m_plan.vm.op_count();
2463 }
2464
2467 GAIA_NODISCARD uint64_t op_signature() const {
2468 return m_plan.vm.op_signature();
2469 }
2470
2473 GAIA_NODISCARD bool has_filters() const {
2474 const auto& ctxData = m_plan.ctx.data;
2475 return ctxData.changedCnt > 0;
2476 }
2477
2480 GAIA_NODISCARD bool has_entity_filter_terms() const {
2481 const auto& ctxData = m_plan.ctx.data;
2482 return ctxData.deps.has_dep_flag(QueryCtx::DependencyHasEntityFilterTerms);
2483 }
2484
2487 GAIA_NODISCARD bool has_potential_inherited_id_terms() const {
2488 const auto& ctxData = m_plan.ctx.data;
2489 return ctxData.deps.has_dep_flag(QueryCtx::DependencyHasPotentialInheritedIdTerms);
2490 }
2491
2495 return m_plan.ctx.data.directTargetEvalKind;
2496 }
2497
2500 GAIA_NODISCARD Entity direct_target_eval_id() const {
2501 return m_plan.ctx.data.directTargetEvalId;
2502 }
2503
2506 GAIA_NODISCARD bool can_direct_target_eval() const {
2507 return m_plan.ctx.data.canDirectTargetEval;
2508 }
2509
2512 GAIA_NODISCARD bool can_direct_entity_seed_eval_shape() const {
2513 return m_plan.ctx.data.canDirectEntitySeedEvalShape;
2514 }
2515
2518 GAIA_NODISCARD bool has_only_direct_or_terms() const {
2519 return m_plan.ctx.data.hasOnlyDirectOrTerms;
2520 }
2521
2524 GAIA_NODISCARD bool matches_prefab_entities() const {
2525 const auto& ctxData = m_plan.ctx.data;
2526 return (ctxData.flags & QueryCtx::QueryFlags::MatchPrefab) != 0 ||
2527 (ctxData.flags & QueryCtx::QueryFlags::HasPrefabTerms) != 0;
2528 }
2529
2533 template <typename... T>
2534 GAIA_NODISCARD bool has_any() const {
2535 return (has_inter<T>(QueryOpKind::Any) || ...);
2536 }
2537
2541 template <typename... T>
2542 GAIA_NODISCARD bool has_or() const {
2543 return (has_inter<T>(QueryOpKind::Or) || ...);
2544 }
2545
2549 template <typename... T>
2550 GAIA_NODISCARD bool has_all() const {
2551 return (has_inter<T>(QueryOpKind::All) && ...);
2552 }
2553
2557 template <typename... T>
2558 GAIA_NODISCARD bool has_no() const {
2559 return (!has_inter<T>(QueryOpKind::Not) && ...);
2560 }
2561
2564 void remove(Archetype* pArchetype) {
2565 GAIA_PROF_SCOPE(queryinfo::remove);
2566
2567 (void)del_archetype_from_seed_cache(pArchetype);
2568 (void)del_archetype_from_cache(pArchetype);
2569 }
2570
2573 std::span<const uint8_t> indices_mapping_view(uint32_t archetypeIdx) const {
2574 const_cast<QueryInfo*>(this)->ensure_comp_indices();
2575 const auto& ctxData = m_state.exec.archetypeCompIndices[archetypeIdx];
2576 return {ctxData.indices, ChunkHeader::MAX_COMPONENTS};
2577 }
2578
2585 std::span<const DirectChunkEntry>
2586 direct_chunk_view(uint32_t idxFrom, uint32_t idxTo, const uint32_t* pDataFields, uint32_t dataFieldCount) const {
2587 const_cast<QueryInfo*>(this)->ensure_direct_chunks(idxFrom, idxTo, pDataFields, dataFieldCount);
2588 return {m_state.exec.directChunks.data(), m_state.exec.directChunks.size()};
2589 }
2590
2593 std::span<const void* const> direct_chunk_data_view() const {
2594 return {m_state.exec.directChunkData.data(), m_state.exec.directChunkData.size()};
2595 }
2596
2600 InheritedTermDataView inherited_data_view(uint32_t archetypeIdx) const {
2601 const_cast<QueryInfo*>(this)->ensure_inherited_data();
2602 if (archetypeIdx >= m_state.exec.archetypeInheritedData.size())
2603 return {};
2604 const auto& ctxData = m_state.exec.archetypeInheritedData[archetypeIdx];
2605 return {ctxData.data, ChunkHeader::MAX_COMPONENTS};
2606 }
2607
2611 InheritedTermDataView inherited_data_view(const Archetype* pArchetype) const {
2613 return {};
2614 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
2615 if (archetypeIdx == BadIndex)
2616 return {};
2617 return inherited_data_view((uint32_t)archetypeIdx);
2618 }
2619
2624
2627 std::span<const uint8_t> try_indices_mapping_view(const Archetype* pArchetype) const {
2628 if (m_state.exec.compIndicesPending)
2629 return {};
2630 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
2631 if (archetypeIdx == BadIndex)
2632 return {};
2633 return indices_mapping_view(archetypeIdx);
2634 }
2635
2639 InheritedTermDataView try_inherited_data_view(const Archetype* pArchetype) const {
2640 if (!has_inherited_data_payload() || m_state.exec.inheritedDataPending)
2641 return {};
2642 const auto archetypeIdx = core::get_index(m_state.archetypeCache, pArchetype);
2643 if (archetypeIdx == BadIndex)
2644 return {};
2645 return inherited_data_view((uint32_t)archetypeIdx);
2646 }
2647
2651 GAIA_NODISCARD GroupId group_id(uint32_t archetypeIdx) const {
2652 const_cast<QueryInfo*>(this)->ensure_group_data(true);
2653 GAIA_ASSERT(archetypeIdx < m_state.grouped.archetypeGroupIds.size());
2654 return m_state.grouped.archetypeGroupIds[archetypeIdx];
2655 }
2656
2660 GAIA_NODISCARD bool barrier_passes(uint32_t archetypeIdx) const {
2662 if (m_state.nonTrivial.archetypeBarrierPasses.empty())
2663 return true;
2664 GAIA_ASSERT(archetypeIdx < m_state.nonTrivial.archetypeBarrierPasses.size());
2665 return m_state.nonTrivial.archetypeBarrierPasses[archetypeIdx] != 0;
2666 }
2667
2670 GAIA_NODISCARD bool barrier_may_prune() const {
2672 return m_state.nonTrivial.barrierMayPrune != 0;
2673 }
2674
2677 GAIA_NODISCARD CArchetypeDArray::iterator begin() {
2678 return m_state.archetypeCache.begin();
2679 }
2680
2683 GAIA_NODISCARD CArchetypeDArray::const_iterator begin() const {
2684 return m_state.archetypeCache.begin();
2685 }
2686
2689 GAIA_NODISCARD CArchetypeDArray::const_iterator cbegin() const {
2690 return m_state.archetypeCache.begin();
2691 }
2692
2695 GAIA_NODISCARD CArchetypeDArray::iterator end() {
2696 return m_state.archetypeCache.end();
2697 }
2698
2701 GAIA_NODISCARD CArchetypeDArray::const_iterator end() const {
2702 return m_state.archetypeCache.end();
2703 }
2704
2707 GAIA_NODISCARD CArchetypeDArray::const_iterator cend() const {
2708 return m_state.archetypeCache.end();
2709 }
2710
2713 GAIA_NODISCARD std::span<const Archetype*> cache_archetype_view() const {
2714 return std::span{(const Archetype**)m_state.archetypeCache.data(), m_state.archetypeCache.size()};
2715 }
2716
2719 GAIA_NODISCARD std::span<const SortData> cache_sort_view() const {
2720 return std::span{m_state.nonTrivial.archetypeSortData.data(), m_state.nonTrivial.archetypeSortData.size()};
2721 }
2722
2725 GAIA_NODISCARD std::span<const GroupData> group_data_view() const {
2726 const_cast<QueryInfo*>(this)->ensure_group_data(true);
2727 return std::span{m_state.grouped.archetypeGroupData.data(), m_state.grouped.archetypeGroupData.size()};
2728 }
2729
2730#if GAIA_ECS_TEST_HOOKS
2732 using TestDynamicCacheKind = QueryCtx::DynamicCacheKind;
2733
2736 GAIA_NODISCARD TestDynamicCacheKind test_dynamic_cache_kind() const {
2737 return m_plan.ctx.data.dynamicCacheKind;
2738 }
2739
2741 GAIA_NODISCARD bool test_uses_direct_src_version_tracking() const {
2742 return m_plan.ctx.data.uses_direct_src_version_tracking();
2743 }
2744
2746 GAIA_NODISCARD bool test_uses_src_trav_snapshot() const {
2747 return m_plan.ctx.data.uses_src_trav_snapshot();
2748 }
2749
2752 GAIA_NODISCARD uint32_t test_match_pass_count() const {
2753 return m_testMatchPassCount;
2754 }
2755
2757 GAIA_NODISCARD static constexpr uint32_t test_query_info_size() {
2758 return (uint32_t)sizeof(QueryInfo);
2759 }
2760
2762 GAIA_NODISCARD static constexpr uint32_t test_query_plan_size() {
2763 return (uint32_t)sizeof(QueryPlan);
2764 }
2765
2767 GAIA_NODISCARD static constexpr uint32_t test_query_state_size() {
2768 return (uint32_t)sizeof(QueryState);
2769 }
2770
2772 GAIA_NODISCARD static constexpr uint32_t test_dynamic_cache_state_size() {
2773 return (uint32_t)sizeof(QueryState::DynamicCacheState);
2774 }
2775
2777 GAIA_NODISCARD static constexpr uint32_t test_query_state_dynamic_offset() {
2778 return (uint32_t)offsetof(QueryState, dynamic);
2779 }
2780
2783 void test_add_transient_archetype(const Archetype* pArchetype) {
2784 GAIA_ASSERT(pArchetype != nullptr);
2785 if (pArchetype == nullptr)
2786 return;
2787
2788 m_state.clear_transient_result_cache();
2790 }
2791#endif
2792 };
2793 } // namespace ecs
2794} // namespace gaia
Array of elements of type.
Definition darray_ext_impl.h:31
GAIA_NODISCARD size_type size() const noexcept
Returns the number of elements.
Definition darray_ext_impl.h:561
GAIA_NODISCARD auto begin() noexcept
Returns an iterator to the first element.
Definition darray_ext_impl.h:613
void push_back(const T &arg)
Appends an element.
Definition darray_ext_impl.h:360
GAIA_NODISCARD auto end() noexcept
Returns an iterator one past the last element.
Definition darray_ext_impl.h:649
void resize(size_type count, ResizePolicy policy=ResizePolicy::Exact)
Changes the number of elements.
Definition darray_ext_impl.h:289
void clear() noexcept
Removes all elements.
Definition darray_ext_impl.h:518
GAIA_NODISCARD ArchetypeId id() const
Archetype id used to address the archetype in the world list.
Definition archetype.h:72
Fixed-shape group of chunks storing entities that share the same component layout....
Definition archetype.h:97
GAIA_NODISCARD EntitySpan ids_view() const
Span over the component and entity identifiers defining the archetype shape.
Definition archetype.h:800
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:861
void sort_entities(Entity entity, TSortByFunc func)
Sorts all entities in the archetypes according to the given function.
Definition archetype.h:1076
Fixed-capacity archetype storage unit holding entities and their component columns.
Definition chunk.h:36
Compiled query plan and its incrementally maintained result caches.
Definition query_info.h:131
void ensure_inherited_data()
Rebuilds inherited-data payloads for matched archetypes when query terms require them.
Definition query_info.h:1956
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:2205
GAIA_NODISCARD CArchetypeDArray::const_iterator begin() const
Returns an iterator to the first cached result archetype.
Definition query_info.h:2683
void apply_archetype_cache_order(std::span< const uint64_t > sortedKeys)
Reorders the archetype cache so position i holds the entry whose initial cache index is stored in the...
Definition query_info.h:1977
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:1804
bool ensure_matches_one_transient(const Archetype &archetype, EntitySpan targetEntities, const cnt::sarray< Entity, MaxVarCnt > &runtimeVarBindings, uint8_t runtimeVarBindingMask)
Rebuilds transient result membership by evaluating one target archetype.
Definition query_info.h:1490
GAIA_NODISCARD std::span< const GroupData > group_data_view() const
Returns cached group ranges, rebuilding grouped data when needed.
Definition query_info.h:2725
GAIA_NODISCARD GroupId group_id(uint32_t archetypeIdx) const
Returns the cached group id for a matched archetype index.
Definition query_info.h:2651
GAIA_NODISCARD bool has_no() const
Returns true when none of the requested types is present as a Not term.
Definition query_info.h:2558
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:2512
void sort_entities()
Applies query entity sorting and rebuilds sorted chunk slices when needed.
Definition query_info.h:1774
bool ensure_matches_one(const Archetype &archetype, EntitySpan targetEntities, const cnt::sarray< Entity, MaxVarCnt > &runtimeVarBindings, uint8_t runtimeVarBindingMask)
Ensures persistent result membership for one target archetype.
Definition query_info.h:1478
GAIA_NODISCARD const World * world() const
Returns the world owning this query.
Definition query_info.h:2427
void ensure_matches(const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes, const EntityToArchetypeVersionMap &entityToArchetypeMapVersions, ArchetypeId archetypeLastId, const cnt::sarray< Entity, MaxVarCnt > &runtimeVarBindings, uint8_t runtimeVarBindingMask)
Refreshes persistent query matches when cache state or world archetypes changed.
Definition query_info.h:1410
void remove(Archetype *pArchetype)
Removes an archetype from cache.
Definition query_info.h:2564
void reset()
Resets cached query state for slot reuse while keeping the compiled context object.
Definition query_info.h:980
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:2518
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:1660
GAIA_NODISCARD bool barrier_may_prune() const
Returns true when any cached archetype can be pruned by the hierarchy barrier.
Definition query_info.h:2670
GAIA_NODISCARD bool has_or() const
Returns true when any of the requested types is present as an Or term.
Definition query_info.h:2542
void recompile()
Recompile the query.
Definition query_info.h:1086
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:2660
GAIA_NODISCARD util::str bytecode() const
Returns a textual dump of the compiled VM bytecode.
Definition query_info.h:2455
MatchArchetypeQueryRet
Result of evaluating one archetype for cache insertion.
Definition query_info.h:140
@ Skip
Archetype requires no cache action for this evaluation path.
@ Ok
Archetype satisfies the query and should be cached.
@ Fail
Archetype does not satisfy the query.
GAIA_NODISCARD std::span< const Archetype * > cache_archetype_view() const
Returns the cached result archetypes as a span.
Definition query_info.h:2713
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:1143
GAIA_NODISCARD bool has_inherited_data_payload() const
Returns true when query iteration needs cached inherited data payloads.
Definition query_info.h:1951
GAIA_NODISCARD bool operator!=(const QueryCtx &other) const
Compares this compiled plan with an authored query context for inequality.
Definition query_info.h:1172
GAIA_NODISCARD uint32_t result_cache_rev() const
Returns the result membership revision used by reverse-index cache users.
Definition query_info.h:1131
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:1047
void group_ids(Container &out, bool sortGroups)
Collects the query's active non-zero group ids.
Definition query_info.h:1110
void init(World *world)
Binds this query info to a world after slot allocation.
Definition query_info.h:975
GAIA_NODISCARD bool result_cache_may_need_prefab_filter() const
Returns true when the result cache contains archetypes that need default prefab filtering.
Definition query_info.h:1137
bool del_archetype_from_cache(const Archetype *pArchetype)
Removes an archetype from the final result cache and mirrored payload arrays.
Definition query_info.h:2362
GAIA_NODISCARD QuerySerBuffer & ser_buffer()
Returns the query serialization buffer associated with the owning world.
Definition query_info.h:2434
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:2627
GAIA_NODISCARD bool matches_prefab_entities() const
Returns true when prefab-tagged entities should participate in query results.
Definition query_info.h:2524
void invalidate_sort()
Marks the cached sorted slices dirty without invalidating query membership.
Definition query_info.h:1012
void create_inherited_data(const Archetype *pArchetype)
Builds inherited component data pointers for semantic self-source terms on one archetype.
Definition query_info.h:2130
bool del_archetype_from_seed_cache(const Archetype *pArchetype)
Removes an archetype from the structural seed cache.
Definition query_info.h:2403
void add_archetype_to_seed_cache(const Archetype *pArchetype, bool assumeAbsent=false)
Adds an archetype to the structural seed cache.
Definition query_info.h:2193
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:1544
InvalidationKind
Cache layer invalidated after query inputs or world state change.
Definition query_info.h:185
@ 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:2707
std::span< const void *const > direct_chunk_data_view() const
Returns cached flattened direct chunk data pointers.
Definition query_info.h:2593
InheritedTermDataView inherited_data_view(uint32_t archetypeIdx) const
Returns cached inherited-term data for a matched archetype index.
Definition query_info.h:2600
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:135
void ser_buffer_reset()
Resets the query serialization buffer associated with the owning world.
Definition query_info.h:2438
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:1157
void ensure_comp_indices()
Rebuilds cached component-index payloads for matched archetypes when marked pending.
Definition query_info.h:1836
GAIA_NODISCARD QueryCtx::CachePolicy cache_policy() const
Returns the query cache policy selected during compilation.
Definition query_info.h:1095
void ensure_direct_chunks(uint32_t idxFrom, uint32_t idxTo, const uint32_t *pDataFields, uint32_t dataFieldCount)
Rebuilds flattened direct chunk cache when result membership, world structure, selected rows,...
Definition query_info.h:1868
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:1208
GAIA_NODISCARD bool has_filters() const
Returns true when the query has per-entity changed/filter terms.
Definition query_info.h:2473
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:1078
GAIA_NODISCARD std::span< const SortData > cache_sort_view() const
Returns cached sorted chunk slices.
Definition query_info.h:2719
GAIA_NODISCARD bool has_grouped_payload() const
Returns true when grouped-query payloads are active for this query.
Definition query_info.h:1101
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:1151
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:2487
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:2052
GAIA_NODISCARD World * world()
Returns the mutable world owning this query.
Definition query_info.h:2421
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:2348
GAIA_NODISCARD bool has_all() const
Returns true when all requested types are present as All terms.
Definition query_info.h:2550
GAIA_NODISCARD bool operator==(const QueryCtx &other) const
Compares this compiled plan with an authored query context.
Definition query_info.h:1165
GAIA_NODISCARD CArchetypeDArray::iterator end()
Returns a mutable iterator past the last cached result archetype.
Definition query_info.h:2695
GAIA_NODISCARD Entity direct_target_eval_id() const
Returns the concrete direct target id used by direct target evaluation.
Definition query_info.h:2500
void add_ref()
Adds one external reference to this query slot.
Definition query_info.h:956
void invalidate_result()
Marks final result matches stale while preserving structural seed matches.
Definition query_info.h:1007
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:1332
GAIA_NODISCARD bool has_sorted_payload() const
Returns true when sorted-query payloads are active for this query.
Definition query_info.h:1125
GAIA_NODISCARD bool has_entity_filter_terms() const
Returns true when direct non-fragmenting terms must be rechecked per entity.
Definition query_info.h:2480
void sort_cache_groups()
Sorts cached archetypes by group id when grouped iteration requested ordering.
Definition query_info.h:1792
GAIA_NODISCARD CArchetypeDArray::const_iterator end() const
Returns an iterator past the last cached result archetype.
Definition query_info.h:2701
uint32_t gen
Generation ID of the query slot.
Definition query_info.h:137
std::span< const DirectChunkEntry > direct_chunk_view(uint32_t idxFrom, uint32_t idxTo, const uint32_t *pDataFields, uint32_t dataFieldCount) const
Returns a flattened chunk view for a direct-dense result-cache range.
Definition query_info.h:2586
GAIA_NODISCARD bool has_any() const
Returns true when any of the requested types is present as an Any term.
Definition query_info.h:2534
GAIA_NODISCARD uint32_t op_count() const
Returns the number of VM operations in the compiled query.
Definition query_info.h:2461
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:2334
GAIA_NODISCARD CArchetypeDArray::const_iterator cbegin() const
Returns a const iterator to the first cached result archetype.
Definition query_info.h:2689
uint32_t refs() const
Returns the current external reference count.
Definition query_info.h:969
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:2257
GAIA_NODISCARD QueryCtx & ctx()
Returns the mutable compiled query context.
Definition query_info.h:2444
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:2573
static GAIA_NODISCARD QueryHandle handle(const QueryInfo &info)
Builds a stable query handle from query slot metadata.
Definition query_info.h:1071
GAIA_NODISCARD CArchetypeDArray::iterator begin()
Returns a mutable iterator to the first cached result archetype.
Definition query_info.h:2677
void ensure_matches_transient(const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes, const EntityToArchetypeVersionMap &entityToArchetypeMapVersions, const cnt::sarray< Entity, MaxVarCnt > &runtimeVarBindings, uint8_t runtimeVarBindingMask)
Rebuilds transient result membership without retaining persistent seed-cache state.
Definition query_info.h:1425
void invalidate_seed()
Marks structural seed matches stale.
Definition query_info.h:1002
GAIA_NODISCARD uint64_t op_signature() const
Returns a stable signature for the compiled VM operation stream.
Definition query_info.h:2467
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:2319
void ensure_depth_order_hierarchy_barrier_cache()
Ensures depth-order hierarchy barrier results are current before public reads.
Definition query_info.h:2621
ArchetypeCompIndices create_comp_indices(const Archetype *pArchetype)
Builds field-to-archetype component indices for one matched archetype.
Definition query_info.h:2092
void invalidate(InvalidationKind kind=InvalidationKind::All)
Marks cached query results stale.
Definition query_info.h:986
void ensure_group_data(bool orderGroups)
Rebuilds grouped archetype ranges when a caller needs ordered group data.
Definition query_info.h:2007
GAIA_NODISCARD const QueryCtx & ctx() const
Returns the compiled query context.
Definition query_info.h:2449
void update_result_cache_prefab_filter_state(const Archetype *pArchetype)
Records whether pArchetype requires the default prefab filter during result-cache iteration.
Definition query_info.h:2162
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:2227
GAIA_NODISCARD bool can_direct_target_eval() const
Returns true when the query can evaluate concrete target entities directly.
Definition query_info.h:2506
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:2639
InheritedTermDataView inherited_data_view(const Archetype *pArchetype) const
Returns cached inherited-term data for a matched archetype pointer.
Definition query_info.h:2611
GAIA_NODISCARD QueryCtx::DirectTargetEvalKind direct_target_eval_kind() const
Returns the direct target evaluation mode selected during compilation.
Definition query_info.h:2494
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:2285
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:2269
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:2171
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:1023
void dec_ref()
Releases one external reference to this query slot.
Definition query_info.h:962
Owns entities, components, archetypes, queries, observers, and systems.
Definition world.h:80
Wrapper for two Entities forming a relationship pair. Conversion to Entity stores only id() of each e...
Definition id.h:559
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:197
Compiles query terms into matching bytecode and evaluates that bytecode against archetypes....
Definition vm.h:2343
Same API as ser_buffer_binary, but backed by fully dynamic storage.
Definition ser_buffer_binary.h:164
Strict weak ordering functor using operator<.
Definition utility.h:1385
Query-field to archetype-component column mapping for one matched archetype.
Definition query_info.h:36
uint8_t indices[ChunkHeader::MAX_COMPONENTS]
Component column per query field, with invalid entries encoded by the mapping builder.
Definition query_info.h:38
Cached inherited component data pointers for one matched archetype.
Definition query_info.h:42
const void * data[ChunkHeader::MAX_COMPONENTS]
Component data pointer per query field, or null when the field is not inherited.
Definition query_info.h:44
One archetype record stored in the entity-to-archetype reverse index.
Definition query_common.h:168
Cached archetype/chunk pair used by repeated direct-dense scans over highly fragmented result caches.
Definition query_info.h:48
const uint8_t * pCompIndices
Prepared query-field to chunk-column mapping for pArchetype, or null when not available.
Definition query_info.h:54
const void * pData
Cached base data pointer for the common single selected field, or null when unavailable/not single-fi...
Definition query_info.h:56
uint32_t dataOffset
Offset into ExecPayload::directChunkData for this entry, or UINT32_MAX when no pointers are cached.
Definition query_info.h:58
Chunk * pChunk
Chunk supplying entity and component rows.
Definition query_info.h:52
uint16_t rowFrom
First enabled row in pChunk processed by direct-dense iteration.
Definition query_info.h:60
const Archetype * pArchetype
Archetype owning the chunk.
Definition query_info.h:50
uint16_t rowTo
One-past-the-end enabled row in pChunk processed by direct-dense iteration.
Definition query_info.h:62
Hashmap lookup structure used for Entity.
Definition id.h:486
Identifier of an entity or component instance in the world. Packs the entity index,...
Definition id.h:231
GAIA_NODISCARD bool has_dep_flag(DependencyFlags dependency) const
Tests whether a dependency fact was recorded.
Definition query_common.h:1091
GAIA_NODISCARD std::span< const QueryTerm > terms_view() const
Returns compiled terms in execution order.
Definition query_common.h:1312
Dependencies deps
Explicit dependency metadata derived from query shape.
Definition query_common.h:1196
Authored and compiled state defining query identity and execution behavior.
Definition query_common.h:897
struct gaia::ecs::QueryCtx::Data data
Compiled query payload.
QueryFlags
Query maintenance and execution flags derived during compilation.
Definition query_common.h:908
@ HasPrefabTerms
Query explicitly mentions Prefab and therefore bypasses automatic exclusion.
Definition query_common.h:926
@ SortGroups
Cached group ranges require sorting.
Definition query_common.h:914
@ MatchPrefab
Includes prefab entities without requiring an explicit Prefab term.
Definition query_common.h:924
@ SortEntities
Cached entity slices require sorting.
Definition query_common.h:912
@ OrderGroups
Grouped archetypes are ordered by group identifier during cache refresh.
Definition query_common.h:928
@ Recompile
VM opcode recompilation is pending.
Definition query_common.h:918
@ DependencyHasSourceTerms
At least one term uses a fixed source entity.
Definition query_common.h:998
@ DependencyHasEntityFilterTerms
At least one term requires per-entity filtering.
Definition query_common.h:1016
@ DependencyHasVariableTerms
At least one term contains a runtime variable.
Definition query_common.h:1000
@ DependencyHasPotentialInheritedIdTerms
A term shape may resolve through inherited-id matching.
Definition query_common.h:1020
@ DependencyHasInheritedDataTerms
Iteration requires cached inherited component pointers.
Definition query_common.h:1018
@ DependencyHasTraversalTerms
At least one source term traverses a relation.
Definition query_common.h:1014
@ DirectStructuralTerms
Evaluates a small immediate ALL, OR, and NOT query directly on the archetype.
DirectTargetEvalKind
Specialized evaluation shape for concrete target entities.
Definition query_common.h:970
DynamicCacheKind
Dynamic-cache dependency shape derived from compiled query metadata.
Definition query_common.h:954
@ 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.
CachePolicy
Strategy used to maintain cached archetype matches.
Definition query_common.h:936
@ Immediate
Updates a structural query immediately when an archetype is created.
@ Dynamic
Repairs source- or variable-dependent cached state on demand.
Stable query-slot identifier combining a slot index and generation.
Definition query_common.h:349
Inputs passed through query-slot allocation to construct a QueryInfo instance.
Definition query_info.h:121
const EntityToArchetypeMap * pEntityToArchetypeMap
World reverse index used to compile initial archetype matching operations.
Definition query_info.h:125
std::span< const Archetype * > allArchetypes
Archetypes present in the world when the query is compiled.
Definition query_info.h:127
QueryCtx * pQueryCtx
Authored query context transferred into the allocated slot.
Definition query_info.h:123
Scope guard that releases the world-owned temporary VM matching frame.
Definition query_info.h:1177
bool keepStamps
True to retain allocated dedup-stamp pages while releasing the frame.
Definition query_info.h:1181
CleanUpTmpArchetypeMatches(World &w, bool keep)
Creates a guard for an already acquired matching frame.
Definition query_info.h:1186
World & world
World owning the acquired scratch frame.
Definition query_info.h:1179
Direct concrete-source payload for reusable dynamic caches.
Definition query_info.h:393
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:401
cnt::sarray< uint32_t, MAX_ITEMS_IN_QUERY > entityVersions
Last seen archetype versions for tracked direct concrete source entities.
Definition query_info.h:395
void snapshot(const World &world, std::span< const Entity > sourceEntities)
Captures current source entity archetype versions.
Definition query_info.h:414
Relation-version payload for reusable dynamic caches with relation dependencies.
Definition query_info.h:364
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:372
cnt::sarray< uint32_t, MAX_ITEMS_IN_QUERY > versions
Last seen versions for tracked dynamic relation dependencies.
Definition query_info.h:366
void snapshot(const World &world, std::span< const Entity > relations)
Captures current relation versions.
Definition query_info.h:385
Traversed-source payload for reusable dynamic caches with source traversal.
Definition query_info.h:422
void mark_overflowed()
Marks the traversed-source snapshot as overflowed and unusable for reuse.
Definition query_info.h:490
uint32_t enabledVersion
Entity enable-state version captured with the traversed-source closure.
Definition query_info.h:426
void clear()
Clears traversed-source snapshot state.
Definition query_info.h:431
GAIA_NODISCARD bool changed(const World &world, const cnt::darray< SrcTravSnapshotItem > &items) const
Returns true when a rebuilt traversed-source closure differs from the captured snapshot.
Definition query_info.h:464
GAIA_NODISCARD bool versions_changed(const World &world) const
Returns true when any tracked traversed source entity changed archetype.
Definition query_info.h:446
cnt::darray< SrcTravSnapshotItem > snapshot
Last seen traversed-source closure for reusable source queries.
Definition query_info.h:424
GAIA_NODISCARD bool is_overflowed() const
Returns true when the traversed-source snapshot is unusable for reuse.
Definition query_info.h:439
void capture(const World &world, const cnt::darray< SrcTravSnapshotItem > &items)
Captures a newly built traversed-source closure snapshot.
Definition query_info.h:483
bool overflowed
True when the traversed source closure exceeded the configured snapshot cap.
Definition query_info.h:428
Variable-binding payload for reusable dynamic caches with runtime variables.
Definition query_info.h:498
cnt::sarray< Entity, MaxVarCnt > bindings
Snapshot of runtime variable bindings used to build the current dynamic cache.
Definition query_info.h:500
uint8_t bindingMask
Bitmask of the variable bindings captured in bindings.
Definition query_info.h:502
void clear()
Clears runtime variable binding snapshot state.
Definition query_info.h:505
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:514
void snapshot(const cnt::sarray< Entity, MaxVarCnt > &runtimeBindings, uint8_t runtimeBindingMask)
Captures runtime variable bindings.
Definition query_info.h:535
Runtime dependency snapshots used to validate reusable dynamic results.
Definition query_info.h:362
DirectSourcePayload directSource
Direct concrete-source payload.
Definition query_info.h:544
TraversedSourcePayload traversedSource
Traversed-source closure payload.
Definition query_info.h:546
VariablePayload variable
Runtime variable-binding payload.
Definition query_info.h:548
RelationPayload relation
Relation-version payload.
Definition query_info.h:542
void clear_input_snapshots()
Clears transient dynamic input snapshot state while preserving fixed-size version snapshots.
Definition query_info.h:551
Component mappings and flattened chunks prepared for query iteration.
Definition query_info.h:246
void clear()
Releases execution payload storage and clears pending rebuild flags.
Definition query_info.h:281
bool inheritedDataPending
True when archetype membership is populated but inherited-term data still needs to be built on demand...
Definition query_info.h:278
cnt::darray< DirectChunkEntry > directChunks
Flattened chunk cache for read-only direct dense iteration over highly fragmented result caches.
Definition query_info.h:252
void clear_transient()
Clears execution payload contents while preserving allocated storage.
Definition query_info.h:299
bool compIndicesPending
True when archetype membership is populated but component-index metadata still needs to be built on d...
Definition query_info.h:275
uint32_t directChunksIdxTo
One-past-the-end result-cache archetype index captured when directChunks was built.
Definition query_info.h:266
uint32_t directChunksDataFieldCount
Number of valid entries in directChunksDataFields.
Definition query_info.h:270
uint32_t directChunksResultRevision
Result membership revision captured when directChunks was built.
Definition query_info.h:262
cnt::darray< ArchetypeCompIndices > archetypeCompIndices
Cached component-index mapping for each matched archetype.
Definition query_info.h:248
uint32_t directChunksDeleteVersion
Archetype deletion-request version captured when directChunks was built.
Definition query_info.h:260
const void * directChunksCompIndicesData
Component-index mapping storage captured by DirectChunkEntry::pCompIndices pointers.
Definition query_info.h:272
cnt::darray< ArchetypeInheritedData > archetypeInheritedData
Cached inherited component data pointer per query field for exact self-source semantic terms.
Definition query_info.h:250
uint32_t directChunksEnabledVersion
Enabled hierarchy version captured when directChunks was built.
Definition query_info.h:258
uint32_t directChunksWorldVersion
World version captured when directChunks was built.
Definition query_info.h:256
uint32_t directChunksIdxFrom
First result-cache archetype index captured when directChunks was built.
Definition query_info.h:264
cnt::darray< const void * > directChunkData
Contiguous cached component data pointers for DirectChunkEntry pointer blocks.
Definition query_info.h:254
uint32_t directChunksDataFields[MAX_ITEMS_IN_QUERY]
Query fields whose component data pointers were captured in DirectChunkEntry pointer blocks.
Definition query_info.h:268
Storage used only by grouped query result caches.
Definition query_info.h:214
cnt::darray< GroupData > archetypeGroupData
Group data used by cache.
Definition query_info.h:218
void clear_transient()
Clears grouped payload contents while preserving allocated storage.
Definition query_info.h:236
void clear()
Releases grouped payload storage and resets cached group selection state.
Definition query_info.h:227
bool selectedGroupDataValid
True when selectedGroupData matches the active group filter.
Definition query_info.h:222
GroupData selectedGroupData
Cached range for the currently selected group id.
Definition query_info.h:220
cnt::darray< GroupId > archetypeGroupIds
Group ids for grouped queries, aligned with archetypeCache.
Definition query_info.h:216
bool dataPending
True when grouped archetype order/ranges need to be rebuilt.
Definition query_info.h:224
Sorting and hierarchy-barrier state for nontrivial iteration paths.
Definition query_info.h:318
uint8_t barrierMayPrune
True when at least one cached archetype fails the depth-order hierarchy barrier.
Definition query_info.h:331
uint32_t barrierEnabledVersion
Entity enable-state version at which the cached depth-order hierarchy barrier state was last rebuilt.
Definition query_info.h:329
cnt::darray< uint8_t > archetypeBarrierPasses
Cached depth-order hierarchy barrier result for each archetype.
Definition query_info.h:320
cnt::darray< SortData > archetypeSortData
Sort data used by cache.
Definition query_info.h:322
void clear_transient()
Clears nontrivial payload contents while preserving allocated storage.
Definition query_info.h:344
uint32_t barrierRelVersion
Relation topology version at which the cached depth-order hierarchy barrier state was last rebuilt.
Definition query_info.h:327
void clear()
Releases nontrivial execution payload storage and resets version stamps.
Definition query_info.h:334
uint32_t sortVersion
World version at which the sorted cache slices were last rebuilt. Unlike worldVersion,...
Definition query_info.h:325
Temporary VM matching buffer meant to be owned by an ECS World. QueryInfo only acquires a frame while...
Definition query_info.h:67
cnt::darr< const Archetype * > matchesArr
Ordered list of matched archetypes emitted by the VM for the current run.
Definition query_info.h:71
void clear_temporary_matches()
Clears all temporary match data, including dedup stamps.
Definition query_info.h:83
uint32_t matchVersion
Monotonic dedup stamp used when the same scratch frame is reused by later full match() calls without ...
Definition query_info.h:80
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:93
cnt::darray< const Archetype * > matchesArrDynPrev
Previous result membership used when a dynamic rebuild compares reverse-index changes.
Definition query_info.h:77
ArchetypeMatchStamps matchStamps
Paged O(1) dedup table keyed by world-local archetype ids. Pages stay allocated on the scratch frame ...
Definition query_info.h:75
GAIA_NODISCARD uint32_t next_match_version()
Advances and returns the dedup stamp for the next VM match pass.
Definition query_info.h:106
void reset_stamps()
Clears dedup stamps and resets the match-version counter.
Definition query_info.h:99
One lookup-key record collected while indexing a single archetype.
Definition query_common.h:326
static constexpr bool value
True when the type derives from the pair base.
Definition id.h:222
Mutable execution context used while matching query bytecode.
Definition vm.h:88
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:332