Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
query_common.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <type_traits>
5
6#include "gaia/cnt/darray.h"
7#include "gaia/cnt/map.h"
8#include "gaia/cnt/sarray.h"
9#include "gaia/cnt/sarray_ext.h"
10#include "gaia/core/bit_utils.h"
11#include "gaia/core/hashing_policy.h"
12#include "gaia/core/span.h"
13#include "gaia/core/utility.h"
14#include "gaia/ecs/api.h"
15#include "gaia/ecs/component.h"
16#include "gaia/ecs/id.h"
17#include "gaia/ecs/query_fwd.h"
18#include "gaia/ecs/query_mask.h"
19
20namespace gaia {
21 namespace ecs {
22 class World;
23 class Archetype;
24 using InheritedTermDataView = std::span<const void* const>;
25 GAIA_NODISCARD uint32_t world_rel_version(const World& world, Entity relation);
26 GAIA_NODISCARD bool world_has_entity_term(const World& world, Entity entity, Entity term);
27 GAIA_NODISCARD bool world_has_entity_term_in(const World& world, Entity entity, Entity term);
28 GAIA_NODISCARD bool world_has_entity_term_direct(const World& world, Entity entity, Entity term);
29 GAIA_NODISCARD bool world_term_uses_inherit_policy(const World& world, Entity term);
30 GAIA_NODISCARD bool world_is_exclusive_dont_fragment_relation(const World& world, Entity relation);
31 GAIA_NODISCARD bool world_is_out_of_line_component(const World& world, Entity component);
32 GAIA_NODISCARD bool world_is_non_fragmenting_out_of_line_component(const World& world, Entity component);
33 GAIA_NODISCARD uint32_t world_count_direct_term_entities(const World& world, Entity term);
34 GAIA_NODISCARD uint32_t world_count_in_term_entities(const World& world, Entity term);
35 GAIA_NODISCARD uint32_t world_count_direct_term_entities_direct(const World& world, Entity term);
36 void world_collect_direct_term_entities(const World& world, Entity term, cnt::darray<Entity>& out);
37 void world_collect_in_term_entities(const World& world, Entity term, cnt::darray<Entity>& out);
38 void world_collect_direct_term_entities_direct(const World& world, Entity term, cnt::darray<Entity>& out);
39 GAIA_NODISCARD bool
40 world_for_each_direct_term_entity(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity));
41 GAIA_NODISCARD bool
42 world_for_each_in_term_entity(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity));
43 GAIA_NODISCARD bool
44 world_for_each_direct_term_entity_direct(const World& world, Entity term, void* ctx, bool (*func)(void*, Entity));
45 GAIA_NODISCARD bool world_entity_enabled(const World& world, Entity entity);
46 GAIA_NODISCARD bool world_entity_prefab(const World& world, Entity entity);
47 GAIA_NODISCARD const Archetype* world_entity_archetype(const World& world, Entity entity);
48 void world_finish_write(World& world, Entity term, Entity entity);
49 GAIA_NODISCARD uint32_t world_component_index_bucket_size(const World& world, Entity term);
50 GAIA_NODISCARD uint32_t world_component_index_comp_idx(const World& world, const Archetype& archetype, Entity term);
51 GAIA_NODISCARD uint32_t
52 world_component_index_match_count(const World& world, const Archetype& archetype, Entity term);
54 GAIA_NODISCARD GroupId group_by_func_depth_order(const World& world, const Archetype& archetype, Entity relation);
55 template <typename T>
56 GAIA_NODISCARD decltype(auto) world_direct_entity_arg(World& world, Entity entity);
57 template <typename T>
58 GAIA_NODISCARD decltype(auto) world_query_entity_arg(World& world, Entity entity);
59 template <typename T>
60 GAIA_NODISCARD Entity world_query_arg_id(World& world);
61 template <typename T>
62 GAIA_NODISCARD decltype(auto) world_query_entity_arg_by_id(World& world, Entity entity, Entity id);
63 template <typename T>
64 GAIA_NODISCARD decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id);
66 GAIA_NODISCARD uint32_t world_entity_archetype_version(const World& world, Entity entity);
67
69 static constexpr uint32_t MAX_ITEMS_IN_QUERY = 12U;
71 static constexpr uint32_t MAX_TRAV_DEPTH = 128U;
72
73 GAIA_GCC_WARNING_PUSH()
74 // GCC is unnecessarily too strict about shadowing.
75 // We have a globally defined entity All and thinks QueryOpKind::All shadows it.
76 GAIA_GCC_WARNING_DISABLE("-Wshadow")
77
79 enum class QueryOpKind : uint8_t { All, Or, Not, Any, Count };
81 enum class QueryAccess : uint8_t { None, Read, Write };
83 enum class QueryMatchKind : uint8_t { Semantic, In, Direct };
85 enum class QueryInputFlags : uint8_t { None, Variable };
87 enum class QueryTravKind : uint8_t { None = 0x00, Self = 0x01, Up = 0x02, Down = 0x04 };
88
89 GAIA_GCC_WARNING_POP()
90
91 GAIA_NODISCARD constexpr QueryTravKind operator|(QueryTravKind lhs, QueryTravKind rhs) {
92 return (QueryTravKind)((uint8_t)lhs | (uint8_t)rhs);
93 }
94
95 GAIA_NODISCARD constexpr bool query_trav_has(QueryTravKind value, QueryTravKind bit) {
96 return (((uint8_t)value) & ((uint8_t)bit)) != 0;
97 }
98
99 using QueryLookupHash = core::direct_hash_key<uint64_t>;
100 using QueryEntityArray = cnt::sarray<Entity, MAX_ITEMS_IN_QUERY>;
101 using QuerySerMap = cnt::map<QueryId, QuerySerBuffer>;
102
106 uint32_t index = 0;
108 uint32_t revision = 0;
109 };
110
114
115 static constexpr uint16_t ComponentIndexBad = (uint16_t)-1;
116
118 Archetype* pArchetype = nullptr;
119 uint16_t compIdx = ComponentIndexBad;
120 uint16_t matchCount = 0;
121
122 GAIA_NODISCARD bool matches(const Archetype* pOther) const {
123 return pArchetype == pOther;
124 }
125 };
126
128
130 EntityLookupKey key = EntityBadLookupKey;
131 ComponentIndexEntry entry{};
132
133 GAIA_NODISCARD bool matches(EntityLookupKey other) const {
134 return key == other;
135 }
136 };
137
138 // Exact id + (X, id) + (id, X) + (*, *) wildcard-derived records for a single archetype.
140
141 static constexpr QueryId QueryIdBad = (QueryId)-1;
142 static constexpr GroupId GroupIdMax = ((GroupId)-1) - 1;
143
144 struct QueryHandle {
145 static constexpr uint32_t IdMask = QueryIdBad;
146
147 private:
148 struct HandleData {
149 QueryId id;
150 uint32_t gen;
151 };
152
153 union {
154 HandleData data;
155 uint64_t val;
156 };
157
158 public:
159 constexpr QueryHandle() noexcept: val((uint64_t)-1) {};
160
161 QueryHandle(QueryId id, uint32_t gen) {
162 data.id = id;
163 data.gen = gen;
164 }
165 ~QueryHandle() = default;
166
167 QueryHandle(QueryHandle&&) noexcept = default;
168 QueryHandle(const QueryHandle&) = default;
169 QueryHandle& operator=(QueryHandle&&) noexcept = default;
170 QueryHandle& operator=(const QueryHandle&) = default;
171
172 GAIA_NODISCARD constexpr bool operator==(const QueryHandle& other) const noexcept {
173 return val == other.val;
174 }
175 GAIA_NODISCARD constexpr bool operator!=(const QueryHandle& other) const noexcept {
176 return val != other.val;
177 }
178
179 GAIA_NODISCARD auto id() const {
180 return data.id;
181 }
182 GAIA_NODISCARD auto gen() const {
183 return data.gen;
184 }
185 GAIA_NODISCARD auto value() const {
186 return val;
187 }
188 };
189
190 inline static const QueryHandle QueryHandleBad = QueryHandle();
191
195
196 private:
198 QueryHandle m_handle;
200 LookupHash m_hash;
201
202 static LookupHash calc(QueryHandle handle) {
203 return {core::calculate_hash64(handle.value())};
204 }
205
206 public:
207 static constexpr bool IsDirectHashKey = true;
208
209 QueryHandleLookupKey() = default;
210 explicit QueryHandleLookupKey(QueryHandle handle): m_handle(handle), m_hash(calc(handle)) {}
211 ~QueryHandleLookupKey() = default;
212
215 QueryHandleLookupKey& operator=(const QueryHandleLookupKey&) = default;
216 QueryHandleLookupKey& operator=(QueryHandleLookupKey&&) = default;
217
218 QueryHandle handle() const {
219 return m_handle;
220 }
221
222 size_t hash() const {
223 return (size_t)m_hash.hash;
224 }
225
226 bool operator==(const QueryHandleLookupKey& other) const {
227 if GAIA_LIKELY (m_hash != other.m_hash)
228 return false;
229
230 return m_handle == other.m_handle;
231 }
232
233 bool operator!=(const QueryHandleLookupKey& other) const {
234 return !operator==(other);
235 }
236 };
237
238 inline static const QueryHandleLookupKey QueryHandleBadLookupKey = QueryHandleLookupKey(QueryHandleBad);
239
241 struct QueryInput {
242 static constexpr uint8_t TravDepthUnlimited = 0;
243
245 QueryOpKind op = QueryOpKind::All;
247 QueryAccess access = QueryAccess::Read;
253 Entity entSrc = EntityBad;
256 Entity entTrav = EntityBad;
260 QueryTravKind travKind = QueryTravKind::Self | QueryTravKind::Up;
263 uint8_t travDepth = TravDepthUnlimited;
265 QueryMatchKind matchKind = QueryMatchKind::Semantic;
266 };
267
272 static constexpr uint8_t TravDepthUnlimited = QueryInput::TravDepthUnlimited;
273
275 Entity entSrc = EntityBad;
277 Entity entTrav = EntityBad;
279 QueryTravKind travKind = QueryTravKind::Self | QueryTravKind::Up;
282 uint8_t travDepth = TravDepthUnlimited;
285 QueryAccess access = QueryAccess::None;
287 QueryMatchKind matchKind = QueryMatchKind::Semantic;
288
289 QueryTermOptions& src(Entity source) {
290 entSrc = source;
291 return *this;
292 }
293
294 QueryTermOptions& trav(Entity relation = ChildOf) {
295 entTrav = relation;
296 travKind = QueryTravKind::Self | QueryTravKind::Up;
297 travDepth = TravDepthUnlimited;
298 return *this;
299 }
300
301 QueryTermOptions& trav_up(Entity relation = ChildOf) {
302 entTrav = relation;
303 travKind = QueryTravKind::Up;
304 travDepth = TravDepthUnlimited;
305 return *this;
306 }
307
308 QueryTermOptions& trav_parent(Entity relation = ChildOf) {
309 entTrav = relation;
310 travKind = QueryTravKind::Up;
311 travDepth = 1;
312 return *this;
313 }
314
315 QueryTermOptions& trav_self_parent(Entity relation = ChildOf) {
316 entTrav = relation;
317 travKind = QueryTravKind::Self | QueryTravKind::Up;
318 travDepth = 1;
319 return *this;
320 }
321
322 QueryTermOptions& trav_down(Entity relation = ChildOf) {
323 entTrav = relation;
324 travKind = QueryTravKind::Down;
325 travDepth = TravDepthUnlimited;
326 return *this;
327 }
328
329 QueryTermOptions& trav_self_down(Entity relation = ChildOf) {
330 entTrav = relation;
331 travKind = QueryTravKind::Self | QueryTravKind::Down;
332 travDepth = TravDepthUnlimited;
333 return *this;
334 }
335
336 QueryTermOptions& trav_child(Entity relation = ChildOf) {
337 entTrav = relation;
338 travKind = QueryTravKind::Down;
339 travDepth = 1;
340 return *this;
341 }
342
343 QueryTermOptions& trav_self_child(Entity relation = ChildOf) {
344 entTrav = relation;
345 travKind = QueryTravKind::Self | QueryTravKind::Down;
346 travDepth = 1;
347 return *this;
348 }
349
350 QueryTermOptions& trav_kind(QueryTravKind kind) {
351 travKind = kind;
352 return *this;
353 }
354
355 QueryTermOptions& trav_depth(uint8_t maxDepth) {
356 travDepth = maxDepth;
357 return *this;
358 }
359
360 QueryTermOptions& read() {
361 access = QueryAccess::Read;
362 return *this;
363 }
364
365 QueryTermOptions& write() {
366 access = QueryAccess::Write;
367 return *this;
368 }
369
370 QueryTermOptions& direct() {
371 matchKind = QueryMatchKind::Direct;
372 return *this;
373 }
374
375 QueryTermOptions& in() {
376 matchKind = QueryMatchKind::In;
377 return *this;
378 }
379 };
380
394 uint8_t readCnt = 0;
396 uint8_t writeCnt = 0;
397
400 GAIA_NODISCARD std::span<const Entity> reads_view() const {
401 return {reads.data(), readCnt};
402 }
403
406 GAIA_NODISCARD std::span<const Entity> writes_view() const {
407 return {writes.data(), writeCnt};
408 }
409
412 void add_read(Entity entity) {
413 if (entity == EntityBad || core::has(reads_view(), entity))
414 return;
415
416 GAIA_ASSERT(readCnt < MAX_ITEMS_IN_QUERY);
417 if (readCnt < MAX_ITEMS_IN_QUERY)
418 reads[readCnt++] = entity;
419 }
420
423 void add_write(Entity entity) {
424 if (entity == EntityBad || core::has(writes_view(), entity))
425 return;
426
427 GAIA_ASSERT(writeCnt < MAX_ITEMS_IN_QUERY);
428 if (writeCnt < MAX_ITEMS_IN_QUERY)
429 writes[writeCnt++] = entity;
430 }
431
435 GAIA_NODISCARD QueryAccess access(Entity entity) const {
436 if (core::has(writes_view(), entity))
437 return QueryAccess::Write;
438 if (core::has(reads_view(), entity))
439 return QueryAccess::Read;
440 return QueryAccess::None;
441 }
442 };
443
445 struct QueryTerm {
453 QueryTravKind travKind;
455 uint8_t travDepth;
457 QueryMatchKind matchKind;
461 QueryOpKind op;
463 uint8_t fieldIndex = 0;
464
465 bool operator==(const QueryTerm& other) const {
466 return id == other.id && src == other.src && entTrav == other.entTrav && travKind == other.travKind &&
467 travDepth == other.travDepth && matchKind == other.matchKind && op == other.op;
468 }
469 bool operator!=(const QueryTerm& other) const {
470 return !operator==(other);
471 }
472 };
473
474 constexpr bool query_term_less_for_lookup(const QueryTerm& lhs, const QueryTerm& rhs) {
475 if (lhs.op != rhs.op)
476 return lhs.op < rhs.op;
477
478 if (lhs.id != rhs.id)
479 return SortComponentCond()(lhs.id, rhs.id);
480
481 if (lhs.src != rhs.src)
482 return SortComponentCond()(lhs.src, rhs.src);
483
484 if (lhs.entTrav != rhs.entTrav)
485 return SortComponentCond()(lhs.entTrav, rhs.entTrav);
486
487 if (lhs.travKind != rhs.travKind)
488 return (uint8_t)lhs.travKind < (uint8_t)rhs.travKind;
489
490 if (lhs.travDepth != rhs.travDepth)
491 return lhs.travDepth < rhs.travDepth;
492
493 return (uint8_t)lhs.matchKind < (uint8_t)rhs.matchKind;
494 }
495
496 inline void canonicalize_lookup_terms(std::span<QueryTerm> terms) {
497 const auto idsCnt = (uint32_t)terms.size();
498 if (idsCnt > 0) {
499 uint32_t orCnt = 0;
500 uint32_t orIdx = BadIndex;
501 GAIA_FOR(idsCnt) {
502 if (terms[i].op != QueryOpKind::Or)
503 continue;
504 ++orCnt;
505 orIdx = i;
506 if (orCnt > 1)
507 break;
508 }
509
510 if (orCnt == 1)
511 terms[orIdx].op = QueryOpKind::All;
512 }
513
514 core::sort(terms.data(), terms.data() + idsCnt, [](const QueryTerm& left, const QueryTerm& right) {
515 return query_term_less_for_lookup(left, right);
516 });
517 }
518
519 inline void canonicalize_lookup_changed(std::span<Entity> changed) {
520 const auto changedCnt = (uint32_t)changed.size();
521 if (changedCnt > 1)
522 core::sort(changed.data(), changed.data() + changedCnt, SortComponentCond{});
523 }
524
525 GAIA_NODISCARD inline bool term_has_variables(const QueryTerm& term) {
526 if (is_variable(term.src))
527 return true;
528
529 if (term.id.pair())
530 return is_variable(EntityId(term.id.id())) || is_variable(EntityId(term.id.gen()));
531
532 return is_variable(EntityId(term.id.id()));
533 }
534
539 GAIA_NODISCARD inline bool query_term_uses_potential_inherited_id_matching(const QueryTerm& term) {
540 const auto id = term.id;
541 return term.matchKind == QueryMatchKind::Semantic && term.src == EntityBad && term.entTrav == EntityBad &&
542 !term_has_variables(term) && !is_wildcard(id) && !is_variable((EntityId)id.id()) &&
543 (!id.pair() || !is_variable((EntityId)id.gen()));
544 }
545
546 using QueryTermArray = cnt::sarray_ext<QueryTerm, MAX_ITEMS_IN_QUERY>;
547 using QueryTermSpan = std::span<QueryTerm>;
548 using QueryRemappingArray = cnt::sarray_ext<uint8_t, MAX_ITEMS_IN_QUERY>;
549
554 QueryId serId = QueryIdBad;
555
556 GAIA_NODISCARD QuerySerBuffer& ser_buffer(World* world) {
557 return query_buffer(*world, serId);
558 }
559 void ser_buffer_reset(World* world) {
560 query_buffer_reset(*world, serId);
561 }
562 };
563
564 struct QueryCtx {
565 // World
566 const World* w{};
573
574 enum QueryFlags : uint16_t {
575 Empty = 0x00,
576 // Entities need sorting
577 SortEntities = 0x01,
578 // Groups need sorting
579 SortGroups = 0x02,
580 // Complex query
581 Complex = 0x04,
582 // Recompilation requested
583 Recompile = 0x08,
584 // Query contains source-based lookup terms
585 HasSourceTerms = 0x10,
586 // Query contains variable-based lookup terms
587 HasVariableTerms = 0x20,
588 // Include entities tagged with Prefab even when the query does not mention Prefab explicitly.
589 MatchPrefab = 0x40,
590 // Query mentions Prefab explicitly and therefore must not auto-exclude it.
591 HasPrefabTerms = 0x80,
592 // Grouped archetypes should be ordered by group id during cache refresh.
593 OrderGroups = 0x100,
594 };
595
596 enum class CachePolicy : uint8_t {
597 // Structural query with a positive selector term. Safe to update immediately on archetype creation.
598 Immediate,
599 // Structural query that stays cached but refreshes lazily on the next read.
600 Lazy,
601 // Query with source or variable terms. Cached state is repaired on demand.
602 Dynamic,
603 };
604
605 enum class CreateArchetypeMatchKind : uint8_t {
606 // Use the normal one-archetype VM path.
607 Vm,
608 // Match a small immediate structural query with ALL/OR/NOT terms directly on the archetype.
609 DirectStructuralTerms,
610 };
611
613 enum class DynamicCacheKind : uint8_t {
615 None,
623 Variable,
625 Mixed
626 };
627
628 enum class DirectTargetEvalKind : uint8_t {
629 Generic,
630 SingleAllDirect,
631 SingleAllSemanticIs,
632 SingleAllInIs,
633 SingleAllInherited,
634 };
635
636 enum DependencyFlags : uint16_t {
637 DependencyNone = 0x00,
638 DependencyHasSourceTerms = 0x01,
639 DependencyHasVariableTerms = 0x02,
640 DependencyHasPositiveTerms = 0x04,
641 DependencyHasNegativeTerms = 0x08,
642 DependencyHasAnyTerms = 0x10,
643 DependencyHasWildcardTerms = 0x20,
644 DependencyHasSort = 0x40,
645 DependencyHasGroup = 0x80,
646 DependencyHasTraversalTerms = 0x100,
647 DependencyHasEntityFilterTerms = 0x200,
648 DependencyHasInheritedDataTerms = 0x400,
649 DependencyHasPotentialInheritedIdTerms = 0x800,
650 };
651
652 struct Data {
654 QueryEntityArray createSelectors;
655 QueryEntityArray exclusions;
656 QueryEntityArray relations;
657 QueryEntityArray sourceEntities;
658 uint8_t createSelectorCnt = 0;
659 uint8_t exclusionCnt = 0;
660 uint8_t relationCnt = 0;
661 uint8_t sourceEntityCnt = 0;
662 uint8_t sourceTermCnt = 0;
663 DependencyFlags flags = DependencyNone;
664
665 void clear() {
666 createSelectorCnt = 0;
667 exclusionCnt = 0;
668 relationCnt = 0;
669 sourceEntityCnt = 0;
670 sourceTermCnt = 0;
671 flags = DependencyNone;
672 }
673
674 GAIA_NODISCARD std::span<const Entity> create_selectors_view() const {
675 return {createSelectors.data(), createSelectorCnt};
676 }
677
678 GAIA_NODISCARD std::span<const Entity> exclusions_view() const {
679 return {exclusions.data(), exclusionCnt};
680 }
681
682 GAIA_NODISCARD std::span<const Entity> relations_view() const {
683 return {relations.data(), relationCnt};
684 }
685
686 GAIA_NODISCARD std::span<const Entity> src_entities_view() const {
687 return {sourceEntities.data(), sourceEntityCnt};
688 }
689
690 void set_dep_flag(DependencyFlags dependency) {
691 flags = (DependencyFlags)(flags | dependency);
692 }
693
694 GAIA_NODISCARD bool has_dep_flag(DependencyFlags dependency) const {
695 return (flags & dependency) != 0;
696 }
697
698 void add_rel(Entity relation) {
699 if (relation == EntityBad || core::has(relations_view(), relation))
700 return;
701
702 GAIA_ASSERT(relationCnt < MAX_ITEMS_IN_QUERY);
703 relations[relationCnt++] = relation;
704 }
705
706 void add_src_entity(Entity entity) {
707 if (entity == EntityBad || core::has(src_entities_view(), entity))
708 return;
709
710 GAIA_ASSERT(sourceEntityCnt < MAX_ITEMS_IN_QUERY);
711 sourceEntities[sourceEntityCnt++] = entity;
712 }
713
714 GAIA_NODISCARD bool can_reuse_src_cache() const {
715 return sourceTermCnt > 0 && sourceTermCnt == sourceEntityCnt;
716 }
717 };
718
727
734 QueryArchetypeCacheIndexMap lastMatchedArchetypeIdx_Or;
735 QueryArchetypeCacheIndexMap lastMatchedArchetypeIdx_Not;
736 uint8_t idsCnt = 0;
737 uint8_t changedCnt = 0;
747 TSortByFunc sortByFunc;
751 TGroupByFunc groupByFunc;
753 QueryMask queryMask;
756 uint32_t as_mask_0;
759 uint32_t as_mask_1;
761 uint8_t firstNot;
763 uint8_t firstAny;
765 uint8_t firstOr;
767 uint8_t groupDepCnt = 0;
772 uint16_t flags;
774 uint16_t cacheSrcTrav = 0;
776 DirectTargetEvalKind directTargetEvalKind = DirectTargetEvalKind::Generic;
790 CachePolicy cachePolicy = CachePolicy::Lazy;
792 CreateArchetypeMatchKind createArchetypeMatchKind = CreateArchetypeMatchKind::Vm;
797
798 GAIA_NODISCARD std::span<const Entity> ids_view() const {
799 return {ids.data(), idsCnt};
800 }
801
802 GAIA_NODISCARD std::span<const Entity> changed_view() const {
803 return {changed.data(), changedCnt};
804 }
805
808 return {changedFields.data(), changedCnt};
809 }
810
811 GAIA_NODISCARD std::span<const Entity> group_deps_view() const {
812 return {groupDeps.data(), groupDepCnt};
813 }
814
817 return {lookupIdentity.lookupTerms.data(), idsCnt};
818 }
819
822 return {lookupIdentity.lookupTerms.data(), idsCnt};
823 }
824
827 return {lookupIdentity.changed.data(), changedCnt};
828 }
829
832 return {lookupIdentity.changed.data(), changedCnt};
833 }
834
837 return {lookupIdentity.groupDeps.data(), groupDepCnt};
838 }
839
844
846 void add_group_dep(Entity relation) {
847 if (relation == EntityBad || core::has(group_deps_view(), relation))
848 return;
849
850 GAIA_ASSERT(groupDepCnt < MAX_ITEMS_IN_QUERY);
851 if (groupDepCnt < MAX_ITEMS_IN_QUERY)
852 groupDeps[groupDepCnt++] = relation;
853 }
854
857 deps.set_dep_flag(DependencyHasGroup);
858
859 const bool hasBuiltInGroupDep = groupBy != EntityBad && (groupByFunc == group_by_func_default ||
860 groupByFunc == group_by_func_depth_order);
861 if (hasBuiltInGroupDep)
862 deps.add_rel(groupBy);
863 for (auto relation: group_deps_view())
864 deps.add_rel(relation);
865 }
866
867 GAIA_NODISCARD std::span<QueryTerm> terms_view_mut() {
868 return {terms.data(), idsCnt};
869 }
870 GAIA_NODISCARD std::span<const QueryTerm> terms_view() const {
871 return {terms.data(), idsCnt};
872 }
873
877 if (cachePolicy != CachePolicy::Dynamic)
879
880 const bool hasVariables = deps.has_dep_flag(DependencyHasVariableTerms);
881 const bool hasSources = deps.has_dep_flag(DependencyHasSourceTerms);
882 const bool hasTraversal = deps.has_dep_flag(DependencyHasTraversalTerms);
883 const bool hasRelations = deps.relationCnt != 0;
884 if (hasSources && hasTraversal && !hasVariables)
886
887 const uint8_t depCnt = (uint8_t)hasVariables + (uint8_t)hasSources + (uint8_t)hasRelations;
888 if (depCnt > 1)
890 if (hasVariables)
892 if (hasSources)
894 if (hasRelations)
897 }
898
904
910
913 GAIA_NODISCARD bool calc_can_reuse_dynamic_cache() const {
914 if (cachePolicy != CachePolicy::Dynamic)
915 return false;
916
917 switch (dynamicCacheKind) {
919 return false;
922 return true;
924 return deps.can_reuse_src_cache();
926 return deps.can_reuse_src_cache() && cacheSrcTrav != 0;
928 if (!deps.has_dep_flag(DependencyHasSourceTerms))
929 return true;
930 if (!deps.can_reuse_src_cache())
931 return false;
932 return !deps.has_dep_flag(DependencyHasTraversalTerms) || cacheSrcTrav != 0;
933 }
934
935 GAIA_ASSERT(false);
936 return false;
937 }
938
941 GAIA_FOR(idsCnt) lookupIdentity.lookupTerms[i] = terms[i];
942 canonicalize_lookup_terms(lookup_terms_view_mut());
943 GAIA_FOR(changedCnt) lookupIdentity.changed[i] = changed[i];
944 canonicalize_lookup_changed(changed_lookup_view_mut());
946 canonicalize_lookup_changed(group_deps_lookup_view_mut());
947 }
948
950 GAIA_NODISCARD bool lookup_keys_equal(const Data& other) const {
951 {
952 const auto left = lookup_terms_view();
953 const auto right = other.lookup_terms_view();
954 GAIA_FOR((uint32_t)left.size()) {
955 if (left[i] != right[i])
956 return false;
957 }
958 }
959
960 {
961 const auto left = changed_lookup_view();
962 const auto right = other.changed_lookup_view();
963 GAIA_FOR((uint32_t)left.size()) {
964 if (left[i] != right[i])
965 return false;
966 }
967 }
968
969 {
970 const auto left = group_deps_lookup_view();
971 const auto right = other.group_deps_lookup_view();
972 GAIA_FOR((uint32_t)left.size()) {
973 if (left[i] != right[i])
974 return false;
975 }
976 }
977
978 return true;
979 }
980
982 GAIA_NODISCARD QueryLookupHash::Type hash_lookup_key_payload() const {
983 QueryLookupHash::Type hashLookup = 0;
984
985 // Ids & ops
986 {
987 QueryLookupHash::Type hash = 0;
988
989 for (const auto& pair: lookup_terms_view()) {
990 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.op);
991 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.id.value());
992 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.src.value());
993 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.entTrav.value());
994 hash = core::hash_combine(hash, (QueryLookupHash::Type)(uint8_t)pair.travKind);
995 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.travDepth);
996 hash = core::hash_combine(hash, (QueryLookupHash::Type)(uint8_t)pair.matchKind);
997 }
998 hash = core::hash_combine(hash, (QueryLookupHash::Type)idsCnt);
999 hash = core::hash_combine(hash, (QueryLookupHash::Type)readWriteMask);
1000 hash = core::hash_combine(hash, (QueryLookupHash::Type)cacheSrcTrav);
1001
1002 const bool matchPrefab = (flags & QueryFlags::MatchPrefab) != 0;
1003 hash = core::hash_combine(hash, (QueryLookupHash::Type)matchPrefab);
1004
1005 hashLookup = hash;
1006 }
1007
1008 // Filters
1009 {
1010 QueryLookupHash::Type hash = 0;
1011
1012 for (const auto entity: changed_lookup_view())
1013 hash = core::hash_combine(hash, (QueryLookupHash::Type)entity.value());
1014 hash = core::hash_combine(hash, (QueryLookupHash::Type)changedCnt);
1015
1016 hashLookup = core::hash_combine(hashLookup, hash);
1017 }
1018
1019 // Explicit grouping dependencies
1020 {
1021 QueryLookupHash::Type hash = 0;
1022
1023 for (const auto entity: group_deps_lookup_view())
1024 hash = core::hash_combine(hash, (QueryLookupHash::Type)entity.value());
1025 hash = core::hash_combine(hash, (QueryLookupHash::Type)groupDepCnt);
1026
1027 hashLookup = core::hash_combine(hashLookup, hash);
1028 }
1029
1030 return hashLookup;
1031 }
1032
1034 GAIA_NODISCARD bool grouping_payload_equal(const Data& other) const {
1035 if (groupBy != other.groupBy)
1036 return false;
1037 if (groupByFunc != other.groupByFunc)
1038 return false;
1039 return (flags & QueryFlags::OrderGroups) == (other.flags & QueryFlags::OrderGroups);
1040 }
1041
1043 GAIA_NODISCARD bool sort_payload_equal(const Data& other) const {
1044 return sortBy == other.sortBy && sortByFunc == other.sortByFunc;
1045 }
1046
1048 GAIA_NODISCARD bool has_sort_payload() const {
1049 return sortBy != EntityBad || sortByFunc != nullptr;
1050 }
1051
1053 GAIA_NODISCARD QueryLookupHash::Type hash_sort_payload() const {
1054 QueryLookupHash::Type hash = 0;
1055 hash = core::hash_combine(hash, (QueryLookupHash::Type)sortBy.value());
1056 hash = core::hash_combine(hash, (QueryLookupHash::Type)sortByFunc);
1057 return hash;
1058 }
1059
1061 GAIA_NODISCARD bool identity_payload_equal(const Data& other) const {
1062 if (idsCnt != other.idsCnt)
1063 return false;
1064 if (changedCnt != other.changedCnt)
1065 return false;
1066 if (groupDepCnt != other.groupDepCnt)
1067 return false;
1068 if (readWriteMask != other.readWriteMask)
1069 return false;
1070 if (cacheSrcTrav != other.cacheSrcTrav)
1071 return false;
1072 if (!lookup_keys_equal(other))
1073 return false;
1074 if (!sort_payload_equal(other))
1075 return false;
1076 return grouping_payload_equal(other);
1077 }
1078
1080 GAIA_NODISCARD QueryLookupHash::Type hash_grouping_payload() const {
1081 QueryLookupHash::Type hash = 0;
1082 hash = core::hash_combine(hash, (QueryLookupHash::Type)groupBy.value());
1083 hash = core::hash_combine(hash, (QueryLookupHash::Type)groupByFunc);
1084 hash = core::hash_combine(hash, (QueryLookupHash::Type)((flags & QueryFlags::OrderGroups) != 0));
1085 return hash;
1086 }
1087
1089 GAIA_NODISCARD QueryLookupHash::Type hash_identity_payload() const {
1090 QueryLookupHash::Type hash = hash_lookup_key_payload();
1091 if (has_sort_payload())
1092 hash = core::hash_combine(hash, hash_sort_payload());
1093 return core::hash_combine(hash, hash_grouping_payload());
1094 }
1095
1097 GAIA_NODISCARD QueryLookupHash calc_lookup_hash() const {
1098 return {core::calculate_hash64(hash_identity_payload())};
1099 }
1100 } data{};
1101 // Make sure that MAX_ITEMS_IN_QUERY can fit into data.readWriteMask
1102 static_assert(MAX_ITEMS_IN_QUERY < 16);
1103
1104 void init(World* pWorld) {
1105 w = pWorld;
1106 cc = &comp_cache_mut(*pWorld);
1107 }
1108
1109 void refresh() {
1110 const auto mask0_old = data.as_mask_0;
1111 const auto mask1_old = data.as_mask_1;
1112 const auto isComplex_old = data.flags & QueryFlags::Complex;
1113 const auto hasSourceTerms_old = data.flags & QueryFlags::HasSourceTerms;
1114 const auto hasVariableTerms_old = data.flags & QueryFlags::HasVariableTerms;
1115 const auto cachePolicy_old = data.cachePolicy;
1116 const auto createArchetypeMatchKind_old = data.createArchetypeMatchKind;
1117 const auto dynamicCacheKind_old = data.dynamicCacheKind;
1118 const auto canDirectTargetEval_old = data.canDirectTargetEval;
1119 const auto canDirectEntitySeedEvalShape_old = data.canDirectEntitySeedEvalShape;
1120 const auto hasOnlyDirectOrTerms_old = data.hasOnlyDirectOrTerms;
1121 const auto canReuseDynamicCache_old = data.canReuseDynamicCache;
1122 const auto dependencyFlags_old = data.deps.flags;
1123 const auto createSelectorCnt_old = data.deps.createSelectorCnt;
1124 const auto exclusionCnt_old = data.deps.exclusionCnt;
1125 const auto relationCnt_old = data.deps.relationCnt;
1126 const auto sourceEntityCnt_old = data.deps.sourceEntityCnt;
1127 const auto sourceTermCnt_old = data.deps.sourceTermCnt;
1128 auto createSelectors_old = data.deps.createSelectors;
1129 auto exclusions_old = data.deps.exclusions;
1130 auto relations_old = data.deps.relations;
1131 auto sourceEntities_old = data.deps.sourceEntities;
1132
1133 // Update masks
1134 {
1135 uint32_t as_mask_0 = 0;
1136 uint32_t as_mask_1 = 0;
1137 bool isComplex = false;
1138 bool hasSourceTerms = false;
1139 bool hasVariableTerms = false;
1140 bool hasPrefabTerms = false;
1141 bool hasCreateSelector = false;
1142 bool canDirectCreateArchetypeMatch = true;
1143 bool hasEntityFilterTerms = false;
1144 bool canDirectTargetEval = true;
1145 bool hasOnlyDirectOrTerms = true;
1146 bool hasOrTerms = false;
1147 bool hasDirectTargetEvalPositiveTerms = false;
1148 const QueryTerm* pSingleDirectTargetAllTerm = nullptr;
1149 bool singleDirectTargetEvalPossible = true;
1150 QueryEntityArray idsNoSrc;
1151 QueryEntityArray createSelectorsAll;
1152 QueryEntityArray createSelectorsOr;
1153 uint32_t idsNoSrcCnt = 0;
1154 uint8_t createSelectorAllCnt = 0;
1155 uint8_t createSelectorOrCnt = 0;
1156 data.deps.clear();
1157 data.directTargetEvalKind = DirectTargetEvalKind::Generic;
1158 data.directTargetEvalId = EntityBad;
1159 if (data.sortByFunc != nullptr)
1160 data.deps.set_dep_flag(DependencyHasSort);
1161 if (data.groupBy != EntityBad)
1162 data.add_group_deps();
1163
1164 auto terms = data.terms_view();
1165 const auto cnt = (uint32_t)terms.size();
1166 GAIA_FOR(cnt) {
1167 const auto& term = terms[i];
1168 const auto id = term.id;
1169 hasPrefabTerms |= id == Prefab;
1170 const bool isDirectIsTerm = term.src == EntityBad && term.entTrav == EntityBad &&
1171 !term_has_variables(term) && term.matchKind != QueryMatchKind::Direct &&
1172 id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
1173 !is_variable((EntityId)id.gen());
1174 const bool isPotentialInheritedTerm = query_term_uses_potential_inherited_id_matching(term);
1175 const bool isInheritedTerm = isPotentialInheritedTerm && world_term_uses_inherit_policy(*w, id);
1176 const bool isAdjunctTerm =
1177 term.src == EntityBad && term.entTrav == EntityBad && !term_has_variables(term) &&
1178 ((id.pair() && world_is_exclusive_dont_fragment_relation(*w, pair_rel(*w, id))) ||
1179 (!id.pair() && world_is_non_fragmenting_out_of_line_component(*w, id)));
1180 hasEntityFilterTerms |= isAdjunctTerm || isDirectIsTerm || isInheritedTerm;
1181 }
1182
1183 GAIA_FOR(cnt) {
1184 const auto& term = terms[i];
1185 const auto id = term.id;
1186 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term)) {
1187 singleDirectTargetEvalPossible = false;
1188 canDirectTargetEval = false;
1189 hasOnlyDirectOrTerms = false;
1190 }
1191 switch (term.op) {
1192 case QueryOpKind::All:
1193 hasDirectTargetEvalPositiveTerms = true;
1194 if (pSingleDirectTargetAllTerm == nullptr)
1195 pSingleDirectTargetAllTerm = &term;
1196 else
1197 singleDirectTargetEvalPossible = false;
1198 hasOnlyDirectOrTerms = false;
1199 break;
1200 case QueryOpKind::Or:
1201 hasDirectTargetEvalPositiveTerms = true;
1202 hasOrTerms = true;
1203 break;
1204 case QueryOpKind::Not:
1205 break;
1206 case QueryOpKind::Any:
1207 case QueryOpKind::Count:
1208 singleDirectTargetEvalPossible = false;
1209 canDirectTargetEval = false;
1210 hasOnlyDirectOrTerms = false;
1211 break;
1212 }
1213 const bool isDirectIsTerm = term.src == EntityBad && term.entTrav == EntityBad &&
1214 !term_has_variables(term) && term.matchKind != QueryMatchKind::Direct &&
1215 id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
1216 !is_variable((EntityId)id.gen());
1217 const bool isPotentialInheritedTerm = query_term_uses_potential_inherited_id_matching(term);
1218 const bool isInheritedTerm = isPotentialInheritedTerm && world_term_uses_inherit_policy(*w, id);
1219 const bool isCachedInheritedDataTerm = isInheritedTerm && !world_is_out_of_line_component(*w, id);
1220 const bool isAdjunctTerm =
1221 term.src == EntityBad && term.entTrav == EntityBad && !term_has_variables(term) &&
1222 ((id.pair() && world_is_exclusive_dont_fragment_relation(*w, pair_rel(*w, id))) ||
1223 (!id.pair() && world_is_non_fragmenting_out_of_line_component(*w, id)));
1224 canDirectCreateArchetypeMatch &= term.src == EntityBad;
1225 if (id.pair() && (is_wildcard(id.id()) || is_wildcard(id.gen())))
1226 data.deps.set_dep_flag(DependencyHasWildcardTerms);
1227 const bool hasDynamicRelationUsage =
1228 term.entTrav != EntityBad || term.src != EntityBad || term_has_variables(term);
1229 if (id.pair() && hasDynamicRelationUsage && !is_wildcard(id.id()) && !is_variable((EntityId)id.id()))
1230 data.deps.add_rel(pair_rel(*w, id));
1231 if (term.entTrav != EntityBad) {
1232 data.deps.add_rel(term.entTrav);
1233 data.deps.set_dep_flag(DependencyHasTraversalTerms);
1234 }
1235 if (term.src != EntityBad) {
1236 hasSourceTerms = true;
1237 data.deps.set_dep_flag(DependencyHasSourceTerms);
1238 ++data.deps.sourceTermCnt;
1239 if (!is_variable(term.src))
1240 data.deps.add_src_entity(term.src);
1241 }
1242
1243 if (term_has_variables(term)) {
1244 hasVariableTerms = true;
1245 data.deps.set_dep_flag(DependencyHasVariableTerms);
1246 isComplex = true;
1247 continue;
1248 }
1249
1250 if (isPotentialInheritedTerm)
1251 data.deps.set_dep_flag(DependencyHasPotentialInheritedIdTerms);
1252
1253 if (isAdjunctTerm || isDirectIsTerm || isInheritedTerm) {
1254 data.deps.set_dep_flag(DependencyHasEntityFilterTerms);
1255 if (isCachedInheritedDataTerm)
1256 data.deps.set_dep_flag(DependencyHasInheritedDataTerms);
1257 if (id.pair() && !is_wildcard(id.id()) && !is_variable((EntityId)id.id()))
1258 data.deps.add_rel(pair_rel(*w, id));
1259 continue;
1260 }
1261
1262 if (hasEntityFilterTerms && term.op == QueryOpKind::Or) {
1263 isComplex = true;
1264 continue;
1265 }
1266
1267 // Source terms are evaluated separately by the VM.
1268 // They should not affect archetype-level query masks.
1269 if (term.src != EntityBad) {
1270 continue;
1271 }
1272
1273 // ANY terms are not hard requirements and must not affect archetype prefilter masks.
1274 if (term.op != QueryOpKind::Any)
1275 idsNoSrc[idsNoSrcCnt++] = id;
1276
1277 if (term.op == QueryOpKind::All || term.op == QueryOpKind::Or) {
1278 hasCreateSelector = true;
1279 data.deps.set_dep_flag(DependencyHasPositiveTerms);
1280 if (term.op == QueryOpKind::All)
1281 createSelectorsAll[createSelectorAllCnt++] = id;
1282 else
1283 createSelectorsOr[createSelectorOrCnt++] = id;
1284 } else if (term.op == QueryOpKind::Not) {
1285 data.deps.set_dep_flag(DependencyHasNegativeTerms);
1286 data.deps.exclusions[data.deps.exclusionCnt++] = id;
1287 } else if (term.op == QueryOpKind::Any) {
1288 data.deps.set_dep_flag(DependencyHasAnyTerms);
1289 }
1290
1291 // Build the Is mask.
1292 // We will use it to identify entities with an Is relationship quickly.
1293 const bool allowSemanticIs = !(
1294 term.matchKind == QueryMatchKind::Direct && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()));
1295 if (!id.pair()) {
1296 const auto j = (uint32_t)i;
1297 const auto has_as = allowSemanticIs ? (uint32_t)is_base(*w, id) : 0U;
1298 as_mask_0 |= (has_as << j);
1299 } else {
1300 const bool idIsWildcard = is_wildcard(id.id());
1301 const bool isGenWildcard = is_wildcard(id.gen());
1302 isComplex |= (idIsWildcard || isGenWildcard);
1303
1304 if (!idIsWildcard) {
1305 const auto j = (uint32_t)i;
1306 const auto e = pair_rel(*w, id);
1307 const auto has_as = allowSemanticIs ? (uint32_t)is_base(*w, e) : 0U;
1308 as_mask_0 |= (has_as << j);
1309 }
1310
1311 if (!isGenWildcard) {
1312 const auto j = (uint32_t)i;
1313 const auto e = pair_tgt(*w, id);
1314 const auto has_as = allowSemanticIs ? (uint32_t)is_base(*w, e) : 0U;
1315 as_mask_1 |= (has_as << j);
1316 }
1317 }
1318 }
1319
1320 if (singleDirectTargetEvalPossible && pSingleDirectTargetAllTerm != nullptr) {
1321 const auto& term = *pSingleDirectTargetAllTerm;
1322 const auto id = term.id;
1323 if (term.matchKind == QueryMatchKind::In && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
1324 !is_variable((EntityId)id.gen())) {
1325 data.directTargetEvalKind = DirectTargetEvalKind::SingleAllInIs;
1326 } else if (
1327 term.matchKind == QueryMatchKind::Semantic && id.pair() && id.id() == Is.id() &&
1328 !is_wildcard(id.gen()) && !is_variable((EntityId)id.gen())) {
1329 data.directTargetEvalKind = DirectTargetEvalKind::SingleAllSemanticIs;
1330 } else if (
1331 term.matchKind == QueryMatchKind::Semantic && !is_wildcard(id) && !is_variable((EntityId)id.id()) &&
1332 (!id.pair() || !is_variable((EntityId)id.gen())) && world_term_uses_inherit_policy(*w, id)) {
1333 data.directTargetEvalKind = DirectTargetEvalKind::SingleAllInherited;
1334 } else {
1335 data.directTargetEvalKind = DirectTargetEvalKind::SingleAllDirect;
1336 }
1337 data.directTargetEvalId = id;
1338 }
1339 data.canDirectTargetEval = canDirectTargetEval && hasDirectTargetEvalPositiveTerms;
1341 data.canDirectTargetEval && data.sortByFunc == nullptr && data.groupBy == EntityBad;
1342 data.hasOnlyDirectOrTerms = hasOnlyDirectOrTerms && hasOrTerms;
1343
1344 // Update the mask
1345 data.as_mask_0 = as_mask_0;
1346 data.as_mask_1 = as_mask_1;
1347 data.deps.createSelectorCnt = 0;
1348 if (createSelectorAllCnt != 0) {
1349 auto selector_rank = [](Entity term) {
1350 if (!term.pair())
1351 return 2;
1352 if (!is_wildcard(term.id()) && !is_wildcard(term.gen()))
1353 return 0;
1354 if (is_wildcard(term.id()) && is_wildcard(term.gen()))
1355 return 3;
1356 return 1;
1357 };
1358
1359 // For immediate structural queries, we choose one required ALL selector as the create-time wake-up key.
1360 // This choice is ordered by:
1361 // 1) smaller component index bucket size first
1362 // 2) if equal, more specific selector first
1363 uint8_t bestIdx = 0;
1364 auto bestBucketSize = world_component_index_bucket_size(*w, createSelectorsAll[0]);
1365 auto bestRank = selector_rank(createSelectorsAll[0]);
1366 GAIA_FOR2_(1, createSelectorAllCnt, i) {
1367 const auto bucketSize = world_component_index_bucket_size(*w, createSelectorsAll[i]);
1368 const auto rank = selector_rank(createSelectorsAll[i]);
1369 if (bucketSize < bestBucketSize || (bucketSize == bestBucketSize && rank < bestRank)) {
1370 bestBucketSize = bucketSize;
1371 bestRank = rank;
1372 bestIdx = (uint8_t)i;
1373 }
1374 }
1375 data.deps.createSelectors[data.deps.createSelectorCnt++] = createSelectorsAll[bestIdx];
1376 } else {
1377 GAIA_FOR(createSelectorOrCnt) {
1378 data.deps.createSelectors[data.deps.createSelectorCnt++] = createSelectorsOr[i];
1379 }
1380 }
1381 if (hasPrefabTerms)
1382 data.flags |= QueryCtx::QueryFlags::HasPrefabTerms;
1383 else
1384 data.flags &= ~QueryCtx::QueryFlags::HasPrefabTerms;
1385
1386 if (hasSourceTerms)
1387 data.flags |= QueryCtx::QueryFlags::HasSourceTerms;
1388 else
1389 data.flags &= ~QueryCtx::QueryFlags::HasSourceTerms;
1390
1391 if (hasVariableTerms)
1392 data.flags |= QueryCtx::QueryFlags::HasVariableTerms;
1393 else
1394 data.flags &= ~QueryCtx::QueryFlags::HasVariableTerms;
1395
1396 if (hasSourceTerms || hasVariableTerms)
1397 data.cachePolicy = CachePolicy::Dynamic;
1398 else if (
1399 !hasEntityFilterTerms && data.sortByFunc == nullptr && data.groupBy == EntityBad && hasCreateSelector)
1400 data.cachePolicy = CachePolicy::Immediate;
1401 else
1402 data.cachePolicy = CachePolicy::Lazy;
1403
1404 data.createArchetypeMatchKind = data.cachePolicy == CachePolicy::Immediate && canDirectCreateArchetypeMatch
1405 ? CreateArchetypeMatchKind::DirectStructuralTerms
1406 : CreateArchetypeMatchKind::Vm;
1408
1409 // Traversed-source snapshot caching is only effective for traversed source terms.
1410 if (!data.deps.has_dep_flag(DependencyHasSourceTerms) || !data.deps.has_dep_flag(DependencyHasTraversalTerms))
1411 data.cacheSrcTrav = 0;
1412
1414
1415 // Calculate the component mask for simple queries
1416 isComplex |= ((data.as_mask_0 + data.as_mask_1) != 0);
1417 if (isComplex) {
1418 data.queryMask = {};
1419 data.flags |= QueryCtx::QueryFlags::Complex;
1420 } else {
1421 data.queryMask = build_entity_mask(EntitySpan{idsNoSrc.data(), idsNoSrcCnt});
1422 data.flags &= ~QueryCtx::QueryFlags::Complex;
1423 }
1424 }
1425
1426 // Request recompilation of the query if the mask has changed
1427 data.refresh_lookup_keys();
1428
1429 // Request recompilation of the query if the mask has changed
1430 if (mask0_old != data.as_mask_0 || mask1_old != data.as_mask_1 ||
1431 isComplex_old != (data.flags & QueryFlags::Complex) ||
1432 hasSourceTerms_old != (data.flags & QueryFlags::HasSourceTerms) ||
1433 hasVariableTerms_old != (data.flags & QueryFlags::HasVariableTerms) ||
1434 canDirectTargetEval_old != data.canDirectTargetEval ||
1435 canDirectEntitySeedEvalShape_old != data.canDirectEntitySeedEvalShape ||
1436 hasOnlyDirectOrTerms_old != data.hasOnlyDirectOrTerms ||
1437 canReuseDynamicCache_old != data.canReuseDynamicCache || cachePolicy_old != data.cachePolicy ||
1438 createArchetypeMatchKind_old != data.createArchetypeMatchKind ||
1439 dynamicCacheKind_old != data.dynamicCacheKind || dependencyFlags_old != data.deps.flags ||
1440 createSelectorCnt_old != data.deps.createSelectorCnt || exclusionCnt_old != data.deps.exclusionCnt ||
1441 relationCnt_old != data.deps.relationCnt || sourceEntityCnt_old != data.deps.sourceEntityCnt ||
1442 sourceTermCnt_old != data.deps.sourceTermCnt || createSelectors_old != data.deps.createSelectors ||
1443 exclusions_old != data.deps.exclusions || relations_old != data.deps.relations ||
1444 sourceEntities_old != data.deps.sourceEntities)
1445 data.flags |= QueryCtx::QueryFlags::Recompile;
1446 }
1447
1448 GAIA_NODISCARD static bool
1449 equals_no_handle_assumption(const QueryCtx& leftCtx, const QueryCtx& rightCtx) noexcept {
1450 // Lookup hash must match
1451 if (leftCtx.hashLookup != rightCtx.hashLookup)
1452 return false;
1453
1454 const auto& left = leftCtx.data;
1455 const auto& right = rightCtx.data;
1456 return left.identity_payload_equal(right);
1457 }
1458
1459 GAIA_NODISCARD bool operator==(const QueryCtx& other) const noexcept {
1460 // Comparison expected to be done only the first time the query is set up
1461 GAIA_ASSERT(q.handle.id() == QueryIdBad);
1462 // Fast path when cache ids are set
1463 // if (queryId != QueryIdBad && queryId == other.queryId)
1464 // return true;
1465
1466 return equals_no_handle_assumption(*this, other);
1467 }
1468
1469 GAIA_NODISCARD bool operator!=(const QueryCtx& other) const noexcept {
1470 return !operator==(other);
1471 }
1472 };
1473
1476 constexpr bool operator()(const QueryTerm& lhs, const QueryTerm& rhs) const {
1477 return query_term_less_for_lookup(lhs, rhs);
1478 }
1479 };
1480
1482 inline void sort(QueryCtx& ctx) {
1483 const uint32_t idsCnt = ctx.data.idsCnt;
1484 const uint32_t changedCnt = ctx.data.changedCnt;
1485
1486 auto& ctxData = ctx.data;
1487 // Canonicalize degenerate OR queries: a single OR term has AND semantics.
1488 // Rewriting it here keeps ordering/hash behavior identical to an explicit ALL term.
1489 if (idsCnt > 0) {
1490 uint32_t orCnt = 0;
1491 uint32_t orIdx = BadIndex;
1492 GAIA_FOR(idsCnt) {
1493 if (ctxData.terms[i].op != QueryOpKind::Or)
1494 continue;
1495 ++orCnt;
1496 orIdx = i;
1497 if (orCnt > 1)
1498 break;
1499 }
1500
1501 if (orCnt == 1)
1502 ctxData.terms[orIdx].op = QueryOpKind::All;
1503 }
1504
1505 // Sort data. Necessary for correct hash calculation.
1506 // Without sorting query.all<XXX, YYY> would be different than query.all<YYY, XXX>.
1507 // Also makes sure data is in optimal order for query processing.
1508 core::sort(
1509 ctxData.terms.data(), ctxData.terms.data() + ctxData.idsCnt, query_sort_cond{}, //
1510 [&](uint32_t left, uint32_t right) {
1511 core::swap(ctxData.ids[left], ctxData.ids[right]);
1512 core::swap(ctxData.terms[left], ctxData.terms[right]);
1513
1514 // Make sure masks remains correct after sorting
1515 core::swap_bits(ctxData.readWriteMask, left, right);
1516 core::swap_bits(ctxData.as_mask_0, left, right);
1517 core::swap_bits(ctxData.as_mask_1, left, right);
1518 });
1519
1520 if (idsCnt > 0) {
1521 uint32_t i = 0;
1522 while (i < idsCnt && ctxData.terms[i].op == QueryOpKind::All)
1523 ++i;
1524 ctxData.firstOr = (uint8_t)i;
1525 while (i < idsCnt && ctxData.terms[i].op == QueryOpKind::Or)
1526 ++i;
1527 ctxData.firstNot = (uint8_t)i;
1528 while (i < idsCnt && ctxData.terms[i].op == QueryOpKind::Not)
1529 ++i;
1530 ctxData.firstAny = (uint8_t)i;
1531 } else
1532 ctxData.firstOr = ctxData.firstNot = ctxData.firstAny = 0;
1533
1534 // Canonicalize filter order. This enables monotonic component lookup in filter matching
1535 // and keeps cache keys stable regardless of changed() call order.
1536 if (changedCnt > 1) {
1537 core::sort(ctxData.changed.data(), ctxData.changed.data() + changedCnt, SortComponentCond{});
1538 }
1539
1540 GAIA_FOR(changedCnt) {
1541 const auto comp = ctxData.changed[i];
1542 uint32_t compIdx = 0;
1543 while (compIdx < idsCnt && ctxData.ids[compIdx] != comp)
1544 ++compIdx;
1545
1546 GAIA_ASSERT(compIdx < idsCnt);
1547 ctxData.changedFields[i] = compIdx < idsCnt ? (uint8_t)compIdx : (uint8_t)0xFF;
1548 }
1549 }
1550
1553 inline void normalize_cache_src_trav(QueryCtx& ctx) {
1554 auto& ctxData = ctx.data;
1555 if (ctxData.cacheSrcTrav == 0)
1556 return;
1557
1558 bool hasTraversedSourceTerm = false;
1559 for (const auto& term: ctxData.terms_view()) {
1560 if (term.src == EntityBad || term.entTrav == EntityBad)
1561 continue;
1562
1563 hasTraversedSourceTerm = true;
1564 break;
1565 }
1566
1567 if (!hasTraversedSourceTerm)
1568 ctxData.cacheSrcTrav = 0;
1569 }
1570
1571 inline void calc_lookup_hash(QueryCtx& ctx) {
1572 GAIA_ASSERT(ctx.cc != nullptr);
1573 // Make sure we don't calculate the hash twice
1574 GAIA_ASSERT(ctx.hashLookup.hash == 0);
1575
1576 ctx.hashLookup = ctx.data.calc_lookup_hash();
1577 }
1578
1586 template <uint32_t MAX_COMPONENTS>
1587 GAIA_NODISCARD inline uint32_t comp_idx(const QueryTerm* pTerms, Entity entity, Entity src) {
1588 // We let the compiler know the upper iteration bound at compile-time.
1589 // This way it can optimize better (e.g. loop unrolling, vectorization).
1590 GAIA_FOR(MAX_COMPONENTS) {
1591 if (pTerms[i].id == entity && pTerms[i].src == src)
1592 return i;
1593 }
1594
1595 GAIA_ASSERT(false);
1596 return BadIndex;
1597 }
1598 } // namespace ecs
1599} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Definition span_impl.h:99
Definition archetype.h:83
Cache for compile-time defined components.
Definition component_cache.h:24
Definition world.h:174
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:224
Same API as ser_buffer_binary, but backed by fully dynamic storage.
Definition ser_buffer_binary.h:157
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
constexpr uint32_t BadIndex
Sentinel index value returned by helpers when a lookup fails.
Definition utility.h:20
Definition query_common.h:117
Hashmap lookup structure used for Entity.
Definition id.h:468
Definition id.h:247
Explicit component/entity access declarations used for scheduling decisions.
Definition query_common.h:388
GAIA_NODISCARD QueryAccess access(Entity entity) const
Returns explicitly declared access for an id.
Definition query_common.h:435
uint8_t readCnt
Number of valid entries in reads.
Definition query_common.h:394
GAIA_NODISCARD std::span< const Entity > writes_view() const
Returns the explicitly declared write ids.
Definition query_common.h:406
void add_read(Entity entity)
Declares that an id is read.
Definition query_common.h:412
cnt::sarray< Entity, MAX_ITEMS_IN_QUERY > reads
Component/entity ids read by the callback outside the query terms.
Definition query_common.h:390
GAIA_NODISCARD std::span< const Entity > reads_view() const
Returns the explicitly declared read ids.
Definition query_common.h:400
uint8_t writeCnt
Number of valid entries in writes.
Definition query_common.h:396
cnt::sarray< Entity, MAX_ITEMS_IN_QUERY > writes
Component/entity ids written by the callback outside the query terms.
Definition query_common.h:392
void add_write(Entity entity)
Declares that an id is written.
Definition query_common.h:423
Incremental query-matching cursor for one entity-to-archetype lookup bucket.
Definition query_common.h:104
uint32_t index
Number of bucket records that were already matched at revision.
Definition query_common.h:106
uint32_t revision
Lookup-bucket revision associated with index.
Definition query_common.h:108
Definition query_common.h:653
Definition query_common.h:719
QueryEntityArray changed
Canonicalized changed-filter ids reused by hash/equality for shared query dedup.
Definition query_common.h:723
cnt::sarray< QueryTerm, MAX_ITEMS_IN_QUERY > lookupTerms
Canonicalized lookup terms reused by hash/equality for shared query dedup.
Definition query_common.h:721
QueryEntityArray groupDeps
Canonicalized group dependency ids reused by hash/equality for shared query dedup.
Definition query_common.h:725
Definition query_common.h:652
uint16_t readWriteMask
Read-write mask. Bit 0 stands for component 0 in component arrays. A set bit means write access is re...
Definition query_common.h:770
QueryEntityArray groupDeps
Explicit grouping invalidation dependencies for custom group_by callbacks.
Definition query_common.h:743
Entity sortBy
Entity to sort the archetypes by. EntityBad for no sorting.
Definition query_common.h:745
TGroupByFunc groupByFunc
Function to use to perform the grouping.
Definition query_common.h:751
GAIA_NODISCARD DynamicCacheKind calc_dynamic_cache_kind() const
Returns the dynamic-cache dependency shape derived from cache policy and dependencies.
Definition query_common.h:876
uint32_t as_mask_0
Mask for items with Is relationship pair. If the id is a pair, the first part (id) is written here.
Definition query_common.h:756
Entity groupBy
Entity to group the archetypes by. EntityBad for no grouping.
Definition query_common.h:749
bool hasOnlyDirectOrTerms
True when the query contains only direct OR/NOT terms and at least one OR term.
Definition query_common.h:784
void add_group_dep(Entity relation)
Adds a declared grouping invalidation dependency.
Definition query_common.h:846
cnt::sarray< uint8_t, MAX_ITEMS_IN_QUERY > changedFields
Query term index for each changed-filter component after query canonicalization.
Definition query_common.h:741
QueryEntityArray ids
Array of queried ids.
Definition query_common.h:729
GAIA_NODISCARD QueryLookupHash::Type hash_lookup_key_payload() const
Returns the hash contribution from canonical lookup-key payload arrays.
Definition query_common.h:982
GAIA_NODISCARD bool uses_src_trav_snapshot() const
Returns whether reusable dynamic-cache checks use a traversed source closure snapshot.
Definition query_common.h:907
GAIA_NODISCARD bool has_sort_payload() const
Returns true when sort identity payload is active.
Definition query_common.h:1048
uint32_t as_mask_1
Mask for items with Is relationship pair. If the id is a pair, the second part (gen) is written here.
Definition query_common.h:759
bool canDirectEntitySeedEvalShape
True when the query shape is eligible for direct entity seed evaluation.
Definition query_common.h:782
uint8_t firstOr
First OR record in pairs/ids/ops.
Definition query_common.h:765
uint16_t cacheSrcTrav
Maximum allowed size of an explicitly cached traversed-source lookup closure.
Definition query_common.h:774
GAIA_NODISCARD QueryLookupHash::Type hash_sort_payload() const
Returns the hash contribution from sort identity payload.
Definition query_common.h:1053
QueryMask queryMask
Component mask used for faster matching of simple queries.
Definition query_common.h:753
uint8_t firstAny
First ANY record in pairs/ids/ops.
Definition query_common.h:763
GAIA_NODISCARD std::span< QueryTerm > lookup_terms_view_mut()
Returns mutable canonicalized lookup terms used by shared query deduplication.
Definition query_common.h:821
GAIA_NODISCARD bool identity_payload_equal(const Data &other) const
Returns true when the shared query identity payload matches another query context payload.
Definition query_common.h:1061
GAIA_NODISCARD std::span< const Entity > changed_lookup_view() const
Returns canonicalized changed-filter lookup ids used by shared query deduplication.
Definition query_common.h:826
void refresh_lookup_keys()
Refreshes canonical lookup arrays used by shared query deduplication.
Definition query_common.h:940
GAIA_NODISCARD std::span< const QueryTerm > lookup_terms_view() const
Returns canonicalized lookup terms used by shared query deduplication.
Definition query_common.h:816
QueryArchetypeCacheIndexMap lastMatchedArchetypeIdx_All
Index of the last checked archetype in the component-to-archetype map.
Definition query_common.h:733
CreateArchetypeMatchKind createArchetypeMatchKind
Create-time archetype matcher derived from query shape.
Definition query_common.h:792
Entity directTargetEvalId
Term id used by the specialized direct-target evaluation shape.
Definition query_common.h:778
void add_group_deps()
Adds all grouping invalidation relations to the dependency set.
Definition query_common.h:856
GAIA_NODISCARD std::span< const uint8_t > changed_fields_view() const
Returns query-term indices matching changed-filter components.
Definition query_common.h:807
GAIA_NODISCARD QueryLookupHash calc_lookup_hash() const
Returns the finalized lookup hash for shared query identity.
Definition query_common.h:1097
GAIA_NODISCARD bool sort_payload_equal(const Data &other) const
Returns true when the sort identity payload matches another query context payload.
Definition query_common.h:1043
GAIA_NODISCARD bool uses_direct_src_version_tracking() const
Returns whether reusable dynamic-cache checks use direct source entity archetype versions.
Definition query_common.h:901
GAIA_NODISCARD std::span< const Entity > group_deps_lookup_view() const
Returns canonicalized group dependency lookup ids used by shared query deduplication.
Definition query_common.h:836
DynamicCacheKind dynamicCacheKind
Dynamic-cache dependency shape derived from compiled query metadata.
Definition query_common.h:794
Dependencies deps
Explicit dependency metadata derived from query shape.
Definition query_common.h:788
GAIA_NODISCARD std::span< Entity > group_deps_lookup_view_mut()
Returns mutable canonicalized group dependency lookup ids used by shared query deduplication.
Definition query_common.h:841
TSortByFunc sortByFunc
Function to use to perform sorting.
Definition query_common.h:747
uint16_t flags
Query flags.
Definition query_common.h:772
bool canDirectTargetEval
True when the query can evaluate concrete target entities directly.
Definition query_common.h:780
GAIA_NODISCARD std::span< Entity > changed_lookup_view_mut()
Returns mutable canonicalized changed-filter lookup ids used by shared query deduplication.
Definition query_common.h:831
uint8_t firstNot
First NOT record in pairs/ids/ops.
Definition query_common.h:761
CachePolicy cachePolicy
Cache maintenance policy derived from query shape.
Definition query_common.h:790
GAIA_NODISCARD bool calc_can_reuse_dynamic_cache() const
Returns whether the current query shape can reuse dynamic-cache results.
Definition query_common.h:913
uint8_t groupDepCnt
Number of defined group dependencies.
Definition query_common.h:767
GAIA_NODISCARD bool grouping_payload_equal(const Data &other) const
Returns true when grouping identity payload matches another query context payload.
Definition query_common.h:1034
QueryEntityArray changed
Array of filtered components.
Definition query_common.h:739
GAIA_NODISCARD bool lookup_keys_equal(const Data &other) const
Returns true when canonical lookup arrays match another query context payload.
Definition query_common.h:950
GAIA_NODISCARD QueryLookupHash::Type hash_identity_payload() const
Returns the hash contribution from the full shared query identity payload.
Definition query_common.h:1089
cnt::sarray< QueryTerm, MAX_ITEMS_IN_QUERY > terms
Array of terms.
Definition query_common.h:731
GAIA_NODISCARD QueryLookupHash::Type hash_grouping_payload() const
Returns the hash contribution from grouping identity payload.
Definition query_common.h:1080
LookupIdentity lookupIdentity
Cold canonical shared-query identity payload.
Definition query_common.h:796
DirectTargetEvalKind directTargetEvalKind
Specialized direct-target evaluation shape for single-term queries.
Definition query_common.h:776
bool canReuseDynamicCache
True when a dynamic cache can be reused by checking tracked runtime inputs.
Definition query_common.h:786
Definition query_common.h:564
ComponentCache * cc
Component cache.
Definition query_common.h:568
QueryIdentity q
Query identity.
Definition query_common.h:572
QueryLookupHash hashLookup
Lookup hash for this query.
Definition query_common.h:570
DynamicCacheKind
Dynamic-cache dependency shape derived from compiled query metadata.
Definition query_common.h:613
@ Variable
Dynamic cache tracks runtime variable bindings only.
@ TraversedSource
Dynamic cache tracks a traversed source closure.
@ DirectSource
Dynamic cache tracks concrete source entity archetype versions.
@ Mixed
Dynamic cache tracks more than one dependency family.
@ None
Query does not use dynamic-cache validation.
@ RelationOnly
Dynamic cache is invalidated by relation version dependencies only.
Hashmap lookup structure used for Entity.
Definition query_common.h:193
Definition query_common.h:144
Definition query_common.h:550
QueryHandle handle
Query id.
Definition query_common.h:552
QueryId serId
Serialization id.
Definition query_common.h:554
User-provided query input.
Definition query_common.h:241
Entity entSrc
Source entity to query the id on. If id==EntityBad the source is fixed. If id!=src the source is vari...
Definition query_common.h:253
QueryTravKind travKind
Source traversal filter. Self means checking the source itself, Up means checking traversed ancestors...
Definition query_common.h:260
Entity id
Entity/Component/Pair to query.
Definition query_common.h:249
QueryAccess access
Access type.
Definition query_common.h:247
Entity entTrav
Optional traversal relation for source lookups. When set, the lookup starts at src and then walks rel...
Definition query_common.h:256
QueryOpKind op
Operation to perform with the input.
Definition query_common.h:245
QueryMatchKind matchKind
Match semantics for terms with special meaning, such as Pair(Is, X).
Definition query_common.h:265
uint8_t travDepth
Maximum number of traversal steps. 0 means unlimited traversal depth (bounded internally,...
Definition query_common.h:263
Additional options for query terms. This can be used to configure source lookup, traversal and access...
Definition query_common.h:271
uint8_t travDepth
Maximum number of traversal steps. 0 means unlimited traversal depth (bounded internally,...
Definition query_common.h:282
Entity entSrc
Source entity to query from.
Definition query_common.h:275
Entity entTrav
Optional traversal relation used for source lookup.
Definition query_common.h:277
QueryAccess access
Access mode for the term. When None, typed query terms infer read/write access from template mutabili...
Definition query_common.h:285
QueryMatchKind matchKind
Match semantics for terms with special meaning, such as Pair(Is, X).
Definition query_common.h:287
QueryTravKind travKind
Source traversal filter.
Definition query_common.h:279
Internal representation of QueryInput.
Definition query_common.h:445
Entity id
Queried id.
Definition query_common.h:447
Archetype * srcArchetype
Archetype of the src entity.
Definition query_common.h:459
uint8_t travDepth
Maximum number of traversal steps.
Definition query_common.h:455
QueryMatchKind matchKind
Match semantics for this term.
Definition query_common.h:457
uint8_t fieldIndex
Stable execution field index matching the user-defined query field order.
Definition query_common.h:463
Entity entTrav
Optional traversal relation for source lookups.
Definition query_common.h:451
QueryOpKind op
Operation to perform with the term.
Definition query_common.h:461
Entity src
Source of where the queried id is looked up at.
Definition query_common.h:449
QueryTravKind travKind
Source traversal filter.
Definition query_common.h:453
Definition query_common.h:129
Functor for sorting terms in a query before compilation.
Definition query_common.h:1475