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 GAIA_NODISCARD uint32_t world_component_index_bucket_size(const World& world, Entity term);
49 GAIA_NODISCARD uint32_t world_component_index_comp_idx(const World& world, const Archetype& archetype, Entity term);
50 GAIA_NODISCARD uint32_t
51 world_component_index_match_count(const World& world, const Archetype& archetype, Entity term);
53 GAIA_NODISCARD GroupId group_by_func_depth_order(const World& world, const Archetype& archetype, Entity relation);
54 template <typename T>
55 GAIA_NODISCARD decltype(auto) world_direct_entity_arg(World& world, Entity entity);
56 template <typename T>
57 GAIA_NODISCARD decltype(auto) world_query_entity_arg(World& world, Entity entity);
59 GAIA_NODISCARD uint32_t world_entity_archetype_version(const World& world, Entity entity);
60
62 static constexpr uint32_t MAX_ITEMS_IN_QUERY = 12U;
64 static constexpr uint32_t MAX_TRAV_DEPTH = 128U;
65
66 GAIA_GCC_WARNING_PUSH()
67 // GCC is unnecessarily too strict about shadowing.
68 // We have a globally defined entity All and thinks QueryOpKind::All shadows it.
69 GAIA_GCC_WARNING_DISABLE("-Wshadow")
70
72 enum class QueryOpKind : uint8_t { All, Or, Not, Any, Count };
74 enum class QueryAccess : uint8_t { None, Read, Write };
76 enum class QueryMatchKind : uint8_t { Semantic, In, Direct };
78 enum class QueryInputFlags : uint8_t { None, Variable };
80 enum class QueryTravKind : uint8_t { None = 0x00, Self = 0x01, Up = 0x02, Down = 0x04 };
81
82 GAIA_GCC_WARNING_POP()
83
84 GAIA_NODISCARD constexpr QueryTravKind operator|(QueryTravKind lhs, QueryTravKind rhs) {
85 return (QueryTravKind)((uint8_t)lhs | (uint8_t)rhs);
86 }
87
88 GAIA_NODISCARD constexpr bool query_trav_has(QueryTravKind value, QueryTravKind bit) {
89 return (((uint8_t)value) & ((uint8_t)bit)) != 0;
90 }
91
92 using QueryLookupHash = core::direct_hash_key<uint64_t>;
93 using QueryEntityArray = cnt::sarray<Entity, MAX_ITEMS_IN_QUERY>;
94 using QueryArchetypeCacheIndexMap = cnt::map<EntityLookupKey, uint32_t>;
95 using QuerySerMap = cnt::map<QueryId, QuerySerBuffer>;
96 static constexpr uint16_t ComponentIndexBad = (uint16_t)-1;
97
99 Archetype* pArchetype = nullptr;
100 uint16_t compIdx = ComponentIndexBad;
101 uint16_t matchCount = 0;
102
103 GAIA_NODISCARD bool matches(const Archetype* pOther) const {
104 return pArchetype == pOther;
105 }
106 };
107
109
111 EntityLookupKey key = EntityBadLookupKey;
112 ComponentIndexEntry entry{};
113
114 GAIA_NODISCARD bool matches(EntityLookupKey other) const {
115 return key == other;
116 }
117 };
118
119 // Exact id + (X, id) + (id, X) + (*, *) wildcard-derived records for a single archetype.
121
122 static constexpr QueryId QueryIdBad = (QueryId)-1;
123 static constexpr GroupId GroupIdMax = ((GroupId)-1) - 1;
124
125 struct QueryHandle {
126 static constexpr uint32_t IdMask = QueryIdBad;
127
128 private:
129 struct HandleData {
130 QueryId id;
131 uint32_t gen;
132 };
133
134 union {
135 HandleData data;
136 uint64_t val;
137 };
138
139 public:
140 constexpr QueryHandle() noexcept: val((uint64_t)-1) {};
141
142 QueryHandle(QueryId id, uint32_t gen) {
143 data.id = id;
144 data.gen = gen;
145 }
146 ~QueryHandle() = default;
147
148 QueryHandle(QueryHandle&&) noexcept = default;
149 QueryHandle(const QueryHandle&) = default;
150 QueryHandle& operator=(QueryHandle&&) noexcept = default;
151 QueryHandle& operator=(const QueryHandle&) = default;
152
153 GAIA_NODISCARD constexpr bool operator==(const QueryHandle& other) const noexcept {
154 return val == other.val;
155 }
156 GAIA_NODISCARD constexpr bool operator!=(const QueryHandle& other) const noexcept {
157 return val != other.val;
158 }
159
160 GAIA_NODISCARD auto id() const {
161 return data.id;
162 }
163 GAIA_NODISCARD auto gen() const {
164 return data.gen;
165 }
166 GAIA_NODISCARD auto value() const {
167 return val;
168 }
169 };
170
171 inline static const QueryHandle QueryHandleBad = QueryHandle();
172
176
177 private:
179 QueryHandle m_handle;
181 LookupHash m_hash;
182
183 static LookupHash calc(QueryHandle handle) {
184 return {core::calculate_hash64(handle.value())};
185 }
186
187 public:
188 static constexpr bool IsDirectHashKey = true;
189
190 QueryHandleLookupKey() = default;
191 explicit QueryHandleLookupKey(QueryHandle handle): m_handle(handle), m_hash(calc(handle)) {}
192 ~QueryHandleLookupKey() = default;
193
196 QueryHandleLookupKey& operator=(const QueryHandleLookupKey&) = default;
197 QueryHandleLookupKey& operator=(QueryHandleLookupKey&&) = default;
198
199 QueryHandle handle() const {
200 return m_handle;
201 }
202
203 size_t hash() const {
204 return (size_t)m_hash.hash;
205 }
206
207 bool operator==(const QueryHandleLookupKey& other) const {
208 if GAIA_LIKELY (m_hash != other.m_hash)
209 return false;
210
211 return m_handle == other.m_handle;
212 }
213
214 bool operator!=(const QueryHandleLookupKey& other) const {
215 return !operator==(other);
216 }
217 };
218
219 inline static const QueryHandleLookupKey QueryHandleBadLookupKey = QueryHandleLookupKey(QueryHandleBad);
220
222 struct QueryInput {
223 static constexpr uint8_t TravDepthUnlimited = 0;
224
226 QueryOpKind op = QueryOpKind::All;
228 QueryAccess access = QueryAccess::Read;
234 Entity entSrc = EntityBad;
237 Entity entTrav = EntityBad;
241 QueryTravKind travKind = QueryTravKind::Self | QueryTravKind::Up;
244 uint8_t travDepth = TravDepthUnlimited;
246 QueryMatchKind matchKind = QueryMatchKind::Semantic;
247 };
248
253 static constexpr uint8_t TravDepthUnlimited = QueryInput::TravDepthUnlimited;
254
256 Entity entSrc = EntityBad;
258 Entity entTrav = EntityBad;
260 QueryTravKind travKind = QueryTravKind::Self | QueryTravKind::Up;
263 uint8_t travDepth = TravDepthUnlimited;
266 QueryAccess access = QueryAccess::None;
268 QueryMatchKind matchKind = QueryMatchKind::Semantic;
269
270 QueryTermOptions& src(Entity source) {
271 entSrc = source;
272 return *this;
273 }
274
275 QueryTermOptions& trav(Entity relation = ChildOf) {
276 entTrav = relation;
277 travKind = QueryTravKind::Self | QueryTravKind::Up;
278 travDepth = TravDepthUnlimited;
279 return *this;
280 }
281
282 QueryTermOptions& trav_up(Entity relation = ChildOf) {
283 entTrav = relation;
284 travKind = QueryTravKind::Up;
285 travDepth = TravDepthUnlimited;
286 return *this;
287 }
288
289 QueryTermOptions& trav_parent(Entity relation = ChildOf) {
290 entTrav = relation;
291 travKind = QueryTravKind::Up;
292 travDepth = 1;
293 return *this;
294 }
295
296 QueryTermOptions& trav_self_parent(Entity relation = ChildOf) {
297 entTrav = relation;
298 travKind = QueryTravKind::Self | QueryTravKind::Up;
299 travDepth = 1;
300 return *this;
301 }
302
303 QueryTermOptions& trav_down(Entity relation = ChildOf) {
304 entTrav = relation;
305 travKind = QueryTravKind::Down;
306 travDepth = TravDepthUnlimited;
307 return *this;
308 }
309
310 QueryTermOptions& trav_self_down(Entity relation = ChildOf) {
311 entTrav = relation;
312 travKind = QueryTravKind::Self | QueryTravKind::Down;
313 travDepth = TravDepthUnlimited;
314 return *this;
315 }
316
317 QueryTermOptions& trav_child(Entity relation = ChildOf) {
318 entTrav = relation;
319 travKind = QueryTravKind::Down;
320 travDepth = 1;
321 return *this;
322 }
323
324 QueryTermOptions& trav_self_child(Entity relation = ChildOf) {
325 entTrav = relation;
326 travKind = QueryTravKind::Self | QueryTravKind::Down;
327 travDepth = 1;
328 return *this;
329 }
330
331 QueryTermOptions& trav_kind(QueryTravKind kind) {
332 travKind = kind;
333 return *this;
334 }
335
336 QueryTermOptions& trav_depth(uint8_t maxDepth) {
337 travDepth = maxDepth;
338 return *this;
339 }
340
341 QueryTermOptions& read() {
342 access = QueryAccess::Read;
343 return *this;
344 }
345
346 QueryTermOptions& write() {
347 access = QueryAccess::Write;
348 return *this;
349 }
350
351 QueryTermOptions& direct() {
352 matchKind = QueryMatchKind::Direct;
353 return *this;
354 }
355
356 QueryTermOptions& in() {
357 matchKind = QueryMatchKind::In;
358 return *this;
359 }
360 };
361
363 struct QueryTerm {
371 QueryTravKind travKind;
373 uint8_t travDepth;
375 QueryMatchKind matchKind;
379 QueryOpKind op;
381 uint8_t fieldIndex = 0;
382
383 bool operator==(const QueryTerm& other) const {
384 return id == other.id && src == other.src && entTrav == other.entTrav && travKind == other.travKind &&
385 travDepth == other.travDepth && matchKind == other.matchKind && op == other.op;
386 }
387 bool operator!=(const QueryTerm& other) const {
388 return !operator==(other);
389 }
390 };
391
392 constexpr bool query_term_less_for_lookup(const QueryTerm& lhs, const QueryTerm& rhs) {
393 if (lhs.op != rhs.op)
394 return lhs.op < rhs.op;
395
396 if (lhs.id != rhs.id)
397 return SortComponentCond()(lhs.id, rhs.id);
398
399 if (lhs.src != rhs.src)
400 return SortComponentCond()(lhs.src, rhs.src);
401
402 if (lhs.entTrav != rhs.entTrav)
403 return SortComponentCond()(lhs.entTrav, rhs.entTrav);
404
405 if (lhs.travKind != rhs.travKind)
406 return (uint8_t)lhs.travKind < (uint8_t)rhs.travKind;
407
408 if (lhs.travDepth != rhs.travDepth)
409 return lhs.travDepth < rhs.travDepth;
410
411 return (uint8_t)lhs.matchKind < (uint8_t)rhs.matchKind;
412 }
413
414 inline void canonicalize_lookup_terms(std::span<QueryTerm> terms) {
415 const auto idsCnt = (uint32_t)terms.size();
416 if (idsCnt > 0) {
417 uint32_t orCnt = 0;
418 uint32_t orIdx = BadIndex;
419 GAIA_FOR(idsCnt) {
420 if (terms[i].op != QueryOpKind::Or)
421 continue;
422 ++orCnt;
423 orIdx = i;
424 if (orCnt > 1)
425 break;
426 }
427
428 if (orCnt == 1)
429 terms[orIdx].op = QueryOpKind::All;
430 }
431
432 core::sort(terms.data(), terms.data() + idsCnt, [](const QueryTerm& left, const QueryTerm& right) {
433 return query_term_less_for_lookup(left, right);
434 });
435 }
436
437 inline void canonicalize_lookup_changed(std::span<Entity> changed) {
438 const auto changedCnt = (uint32_t)changed.size();
439 if (changedCnt > 1)
440 core::sort(changed.data(), changed.data() + changedCnt, SortComponentCond{});
441 }
442
443 GAIA_NODISCARD inline bool term_has_variables(const QueryTerm& term) {
444 if (is_variable(term.src))
445 return true;
446
447 if (term.id.pair())
448 return is_variable(EntityId(term.id.id())) || is_variable(EntityId(term.id.gen()));
449
450 return is_variable(EntityId(term.id.id()));
451 }
452
453 using QueryTermArray = cnt::sarray_ext<QueryTerm, MAX_ITEMS_IN_QUERY>;
454 using QueryTermSpan = std::span<QueryTerm>;
455 using QueryRemappingArray = cnt::sarray_ext<uint8_t, MAX_ITEMS_IN_QUERY>;
456
461 QueryId serId = QueryIdBad;
462
463 GAIA_NODISCARD QuerySerBuffer& ser_buffer(World* world) {
464 return query_buffer(*world, serId);
465 }
466 void ser_buffer_reset(World* world) {
467 query_buffer_reset(*world, serId);
468 }
469 };
470
471 struct QueryCtx {
472 // World
473 const World* w{};
480
481 enum QueryFlags : uint8_t {
482 Empty = 0x00,
483 // Entities need sorting
484 SortEntities = 0x01,
485 // Groups need sorting
486 SortGroups = 0x02,
487 // Complex query
488 Complex = 0x04,
489 // Recompilation requested
490 Recompile = 0x08,
491 // Query contains source-based lookup terms
492 HasSourceTerms = 0x10,
493 // Query contains variable-based lookup terms
494 HasVariableTerms = 0x20,
495 // Include entities tagged with Prefab even when the query does not mention Prefab explicitly.
496 MatchPrefab = 0x40,
497 // Query mentions Prefab explicitly and therefore must not auto-exclude it.
498 HasPrefabTerms = 0x80,
499 };
500
501 enum class CachePolicy : uint8_t {
502 // Structural query with a positive selector term. Safe to update immediately on archetype creation.
503 Immediate,
504 // Structural query that stays cached but refreshes lazily on the next read.
505 Lazy,
506 // Query with source or variable terms. Cached state is repaired on demand.
507 Dynamic,
508 };
509
510 enum class CreateArchetypeMatchKind : uint8_t {
511 // Use the normal one-archetype VM path.
512 Vm,
513 // Match a small immediate structural query with ALL/OR/NOT terms directly on the archetype.
514 DirectStructuralTerms,
515 };
516
517 enum class DirectTargetEvalKind : uint8_t {
518 Generic,
519 SingleAllDirect,
520 SingleAllSemanticIs,
521 SingleAllInIs,
522 SingleAllInherited,
523 };
524
525 enum DependencyFlags : uint16_t {
526 DependencyNone = 0x00,
527 DependencyHasSourceTerms = 0x01,
528 DependencyHasVariableTerms = 0x02,
529 DependencyHasPositiveTerms = 0x04,
530 DependencyHasNegativeTerms = 0x08,
531 DependencyHasAnyTerms = 0x10,
532 DependencyHasWildcardTerms = 0x20,
533 DependencyHasSort = 0x40,
534 DependencyHasGroup = 0x80,
535 DependencyHasTraversalTerms = 0x100,
536 DependencyHasEntityFilterTerms = 0x200,
537 DependencyHasInheritedDataTerms = 0x400,
538 };
539
540 struct Data {
542 QueryEntityArray createSelectors;
543 QueryEntityArray exclusions;
544 QueryEntityArray relations;
545 QueryEntityArray sourceEntities;
546 uint8_t createSelectorCnt = 0;
547 uint8_t exclusionCnt = 0;
548 uint8_t relationCnt = 0;
549 uint8_t sourceEntityCnt = 0;
550 uint8_t sourceTermCnt = 0;
551 DependencyFlags flags = DependencyNone;
552
553 void clear() {
554 createSelectorCnt = 0;
555 exclusionCnt = 0;
556 relationCnt = 0;
557 sourceEntityCnt = 0;
558 sourceTermCnt = 0;
559 flags = DependencyNone;
560 }
561
562 GAIA_NODISCARD std::span<const Entity> create_selectors_view() const {
563 return {createSelectors.data(), createSelectorCnt};
564 }
565
566 GAIA_NODISCARD std::span<const Entity> exclusions_view() const {
567 return {exclusions.data(), exclusionCnt};
568 }
569
570 GAIA_NODISCARD std::span<const Entity> relations_view() const {
571 return {relations.data(), relationCnt};
572 }
573
574 GAIA_NODISCARD std::span<const Entity> src_entities_view() const {
575 return {sourceEntities.data(), sourceEntityCnt};
576 }
577
578 void set_dep_flag(DependencyFlags dependency) {
579 flags = (DependencyFlags)(flags | dependency);
580 }
581
582 GAIA_NODISCARD bool has_dep_flag(DependencyFlags dependency) const {
583 return (flags & dependency) != 0;
584 }
585
586 void add_rel(Entity relation) {
587 if (relation == EntityBad || core::has(relations_view(), relation))
588 return;
589
590 GAIA_ASSERT(relationCnt < MAX_ITEMS_IN_QUERY);
591 relations[relationCnt++] = relation;
592 }
593
594 void add_src_entity(Entity entity) {
595 if (entity == EntityBad || core::has(src_entities_view(), entity))
596 return;
597
598 GAIA_ASSERT(sourceEntityCnt < MAX_ITEMS_IN_QUERY);
599 sourceEntities[sourceEntityCnt++] = entity;
600 }
601
602 GAIA_NODISCARD bool can_reuse_src_cache() const {
603 return sourceTermCnt > 0 && sourceTermCnt == sourceEntityCnt;
604 }
605 };
606
615 QueryArchetypeCacheIndexMap lastMatchedArchetypeIdx_Or;
616 QueryArchetypeCacheIndexMap lastMatchedArchetypeIdx_Not;
617 uint8_t idsCnt = 0;
618 uint8_t changedCnt = 0;
630 TSortByFunc sortByFunc;
634 TGroupByFunc groupByFunc;
636 QueryMask queryMask;
639 uint32_t as_mask_0;
642 uint32_t as_mask_1;
644 uint8_t firstNot;
646 uint8_t firstAny;
648 uint8_t firstOr;
650 uint8_t groupDepCnt = 0;
655 uint8_t flags;
657 uint16_t cacheSrcTrav = 0;
659 DirectTargetEvalKind directTargetEvalKind = DirectTargetEvalKind::Generic;
665 CachePolicy cachePolicy = CachePolicy::Lazy;
667 CreateArchetypeMatchKind createArchetypeMatchKind = CreateArchetypeMatchKind::Vm;
668
669 GAIA_NODISCARD std::span<const Entity> ids_view() const {
670 return {ids.data(), idsCnt};
671 }
672
673 GAIA_NODISCARD std::span<const Entity> changed_view() const {
674 return {changed.data(), changedCnt};
675 }
676
677 GAIA_NODISCARD std::span<const Entity> group_deps_view() const {
678 return {groupDeps.data(), groupDepCnt};
679 }
680
682 void add_group_dep(Entity relation) {
683 if (relation == EntityBad || core::has(group_deps_view(), relation))
684 return;
685
686 GAIA_ASSERT(groupDepCnt < MAX_ITEMS_IN_QUERY);
687 if (groupDepCnt < MAX_ITEMS_IN_QUERY)
688 groupDeps[groupDepCnt++] = relation;
689 }
690
693 deps.set_dep_flag(DependencyHasGroup);
694
695 const bool hasBuiltInGroupDep = groupBy != EntityBad && (groupByFunc == group_by_func_default ||
696 groupByFunc == group_by_func_depth_order);
697 if (hasBuiltInGroupDep)
698 deps.add_rel(groupBy);
699 for (auto relation: group_deps_view())
700 deps.add_rel(relation);
701 }
702
703 GAIA_NODISCARD std::span<QueryTerm> terms_view_mut() {
704 return {terms.data(), idsCnt};
705 }
706 GAIA_NODISCARD std::span<const QueryTerm> terms_view() const {
707 return {terms.data(), idsCnt};
708 }
709 } data{};
710 // Make sure that MAX_ITEMS_IN_QUERY can fit into data.readWriteMask
711 static_assert(MAX_ITEMS_IN_QUERY < 16);
712
713 void init(World* pWorld) {
714 w = pWorld;
715 cc = &comp_cache_mut(*pWorld);
716 }
717
718 void refresh() {
719 const auto mask0_old = data.as_mask_0;
720 const auto mask1_old = data.as_mask_1;
721 const auto isComplex_old = data.flags & QueryFlags::Complex;
722 const auto hasSourceTerms_old = data.flags & QueryFlags::HasSourceTerms;
723 const auto hasVariableTerms_old = data.flags & QueryFlags::HasVariableTerms;
724 const auto cachePolicy_old = data.cachePolicy;
725 const auto createArchetypeMatchKind_old = data.createArchetypeMatchKind;
726 const auto dependencyFlags_old = data.deps.flags;
727 const auto createSelectorCnt_old = data.deps.createSelectorCnt;
728 const auto exclusionCnt_old = data.deps.exclusionCnt;
729 const auto relationCnt_old = data.deps.relationCnt;
730 const auto sourceEntityCnt_old = data.deps.sourceEntityCnt;
731 const auto sourceTermCnt_old = data.deps.sourceTermCnt;
732 auto createSelectors_old = data.deps.createSelectors;
733 auto exclusions_old = data.deps.exclusions;
734 auto relations_old = data.deps.relations;
735 auto sourceEntities_old = data.deps.sourceEntities;
736
737 // Update masks
738 {
739 uint32_t as_mask_0 = 0;
740 uint32_t as_mask_1 = 0;
741 bool isComplex = false;
742 bool hasSourceTerms = false;
743 bool hasVariableTerms = false;
744 bool hasPrefabTerms = false;
745 bool hasCreateSelector = false;
746 bool canDirectCreateArchetypeMatch = true;
747 bool hasEntityFilterTerms = false;
748 const QueryTerm* pSingleDirectTargetAllTerm = nullptr;
749 bool singleDirectTargetEvalPossible = true;
750 QueryEntityArray idsNoSrc;
751 QueryEntityArray createSelectorsAll;
752 QueryEntityArray createSelectorsOr;
753 uint32_t idsNoSrcCnt = 0;
754 uint8_t createSelectorAllCnt = 0;
755 uint8_t createSelectorOrCnt = 0;
756 data.deps.clear();
757 data.directTargetEvalKind = DirectTargetEvalKind::Generic;
758 data.directTargetEvalId = EntityBad;
759 if (data.sortByFunc != nullptr)
760 data.deps.set_dep_flag(DependencyHasSort);
761 if (data.groupBy != EntityBad)
762 data.add_group_deps();
763
764 auto terms = data.terms_view();
765 const auto cnt = (uint32_t)terms.size();
766 GAIA_FOR(cnt) {
767 const auto& term = terms[i];
768 const auto id = term.id;
769 hasPrefabTerms |= id == Prefab;
770 const bool isDirectIsTerm = term.src == EntityBad && term.entTrav == EntityBad &&
771 !term_has_variables(term) && term.matchKind != QueryMatchKind::Direct &&
772 id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
773 !is_variable((EntityId)id.gen());
774 const bool isInheritedTerm =
775 term.src == EntityBad && term.entTrav == EntityBad && !term_has_variables(term) &&
776 term.matchKind == QueryMatchKind::Semantic && !is_wildcard(id) && !is_variable((EntityId)id.id()) &&
777 (!id.pair() || !is_variable((EntityId)id.gen())) && world_term_uses_inherit_policy(*w, id);
778 const bool isAdjunctTerm =
779 term.src == EntityBad && term.entTrav == EntityBad && !term_has_variables(term) &&
780 ((id.pair() && world_is_exclusive_dont_fragment_relation(*w, entity_from_id(*w, id.id()))) ||
781 (!id.pair() && world_is_non_fragmenting_out_of_line_component(*w, id)));
782 hasEntityFilterTerms |= isAdjunctTerm || isDirectIsTerm || isInheritedTerm;
783 }
784
785 GAIA_FOR(cnt) {
786 const auto& term = terms[i];
787 const auto id = term.id;
788 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
789 singleDirectTargetEvalPossible = false;
790 switch (term.op) {
791 case QueryOpKind::All:
792 if (pSingleDirectTargetAllTerm == nullptr)
793 pSingleDirectTargetAllTerm = &term;
794 else
795 singleDirectTargetEvalPossible = false;
796 break;
797 case QueryOpKind::Or:
798 case QueryOpKind::Not:
799 case QueryOpKind::Any:
800 case QueryOpKind::Count:
801 singleDirectTargetEvalPossible = false;
802 break;
803 }
804 const bool isDirectIsTerm = term.src == EntityBad && term.entTrav == EntityBad &&
805 !term_has_variables(term) && term.matchKind != QueryMatchKind::Direct &&
806 id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
807 !is_variable((EntityId)id.gen());
808 const bool isInheritedTerm =
809 term.src == EntityBad && term.entTrav == EntityBad && !term_has_variables(term) &&
810 term.matchKind == QueryMatchKind::Semantic && !is_wildcard(id) && !is_variable((EntityId)id.id()) &&
811 (!id.pair() || !is_variable((EntityId)id.gen())) && world_term_uses_inherit_policy(*w, id);
812 const bool isCachedInheritedDataTerm = isInheritedTerm && !world_is_out_of_line_component(*w, id);
813 const bool isAdjunctTerm =
814 term.src == EntityBad && term.entTrav == EntityBad && !term_has_variables(term) &&
815 ((id.pair() && world_is_exclusive_dont_fragment_relation(*w, entity_from_id(*w, id.id()))) ||
816 (!id.pair() && world_is_non_fragmenting_out_of_line_component(*w, id)));
817 canDirectCreateArchetypeMatch &= term.src == EntityBad;
818 if (id.pair() && (is_wildcard(id.id()) || is_wildcard(id.gen())))
819 data.deps.set_dep_flag(DependencyHasWildcardTerms);
820 const bool hasDynamicRelationUsage =
821 term.entTrav != EntityBad || term.src != EntityBad || term_has_variables(term);
822 if (id.pair() && hasDynamicRelationUsage && !is_wildcard(id.id()) && !is_variable((EntityId)id.id()))
823 data.deps.add_rel(entity_from_id(*w, id.id()));
824 if (term.entTrav != EntityBad) {
825 data.deps.add_rel(term.entTrav);
826 data.deps.set_dep_flag(DependencyHasTraversalTerms);
827 }
828 if (term.src != EntityBad) {
829 hasSourceTerms = true;
830 data.deps.set_dep_flag(DependencyHasSourceTerms);
831 ++data.deps.sourceTermCnt;
832 if (!is_variable(term.src))
833 data.deps.add_src_entity(term.src);
834 }
835
836 if (term_has_variables(term)) {
837 hasVariableTerms = true;
838 data.deps.set_dep_flag(DependencyHasVariableTerms);
839 isComplex = true;
840 continue;
841 }
842
843 if (isAdjunctTerm || isDirectIsTerm || isInheritedTerm) {
844 data.deps.set_dep_flag(DependencyHasEntityFilterTerms);
845 if (isCachedInheritedDataTerm)
846 data.deps.set_dep_flag(DependencyHasInheritedDataTerms);
847 if (id.pair() && !is_wildcard(id.id()) && !is_variable((EntityId)id.id()))
848 data.deps.add_rel(entity_from_id(*w, id.id()));
849 continue;
850 }
851
852 if (hasEntityFilterTerms && term.op == QueryOpKind::Or) {
853 isComplex = true;
854 continue;
855 }
856
857 // Source terms are evaluated separately by the VM.
858 // They should not affect archetype-level query masks.
859 if (term.src != EntityBad) {
860 continue;
861 }
862
863 // ANY terms are not hard requirements and must not affect archetype prefilter masks.
864 if (term.op != QueryOpKind::Any)
865 idsNoSrc[idsNoSrcCnt++] = id;
866
867 if (term.op == QueryOpKind::All || term.op == QueryOpKind::Or) {
868 hasCreateSelector = true;
869 data.deps.set_dep_flag(DependencyHasPositiveTerms);
870 if (term.op == QueryOpKind::All)
871 createSelectorsAll[createSelectorAllCnt++] = id;
872 else
873 createSelectorsOr[createSelectorOrCnt++] = id;
874 } else if (term.op == QueryOpKind::Not) {
875 data.deps.set_dep_flag(DependencyHasNegativeTerms);
876 data.deps.exclusions[data.deps.exclusionCnt++] = id;
877 } else if (term.op == QueryOpKind::Any) {
878 data.deps.set_dep_flag(DependencyHasAnyTerms);
879 }
880
881 // Build the Is mask.
882 // We will use it to identify entities with an Is relationship quickly.
883 const bool allowSemanticIs = !(
884 term.matchKind == QueryMatchKind::Direct && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()));
885 if (!id.pair()) {
886 const auto j = (uint32_t)i;
887 const auto has_as = allowSemanticIs ? (uint32_t)is_base(*w, id) : 0U;
888 as_mask_0 |= (has_as << j);
889 } else {
890 const bool idIsWildcard = is_wildcard(id.id());
891 const bool isGenWildcard = is_wildcard(id.gen());
892 isComplex |= (idIsWildcard || isGenWildcard);
893
894 if (!idIsWildcard) {
895 const auto j = (uint32_t)i;
896 const auto e = entity_from_id(*w, id.id());
897 const auto has_as = allowSemanticIs ? (uint32_t)is_base(*w, e) : 0U;
898 as_mask_0 |= (has_as << j);
899 }
900
901 if (!isGenWildcard) {
902 const auto j = (uint32_t)i;
903 const auto e = entity_from_id(*w, id.gen());
904 const auto has_as = allowSemanticIs ? (uint32_t)is_base(*w, e) : 0U;
905 as_mask_1 |= (has_as << j);
906 }
907 }
908 }
909
910 if (singleDirectTargetEvalPossible && pSingleDirectTargetAllTerm != nullptr) {
911 const auto& term = *pSingleDirectTargetAllTerm;
912 const auto id = term.id;
913 if (term.matchKind == QueryMatchKind::In && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
914 !is_variable((EntityId)id.gen())) {
915 data.directTargetEvalKind = DirectTargetEvalKind::SingleAllInIs;
916 } else if (
917 term.matchKind == QueryMatchKind::Semantic && id.pair() && id.id() == Is.id() &&
918 !is_wildcard(id.gen()) && !is_variable((EntityId)id.gen())) {
919 data.directTargetEvalKind = DirectTargetEvalKind::SingleAllSemanticIs;
920 } else if (
921 term.matchKind == QueryMatchKind::Semantic && !is_wildcard(id) && !is_variable((EntityId)id.id()) &&
922 (!id.pair() || !is_variable((EntityId)id.gen())) && world_term_uses_inherit_policy(*w, id)) {
923 data.directTargetEvalKind = DirectTargetEvalKind::SingleAllInherited;
924 } else {
925 data.directTargetEvalKind = DirectTargetEvalKind::SingleAllDirect;
926 }
927 data.directTargetEvalId = id;
928 }
929
930 // Update the mask
931 data.as_mask_0 = as_mask_0;
932 data.as_mask_1 = as_mask_1;
933 data.deps.createSelectorCnt = 0;
934 if (createSelectorAllCnt != 0) {
935 auto selector_rank = [](Entity term) {
936 if (!term.pair())
937 return 2;
938 if (!is_wildcard(term.id()) && !is_wildcard(term.gen()))
939 return 0;
940 if (is_wildcard(term.id()) && is_wildcard(term.gen()))
941 return 3;
942 return 1;
943 };
944
945 // For immediate structural queries, we choose one required ALL selector as the create-time wake-up key.
946 // This choice is ordered by:
947 // 1) smaller component index bucket size first
948 // 2) if equal, more specific selector first
949 uint8_t bestIdx = 0;
950 auto bestBucketSize = world_component_index_bucket_size(*w, createSelectorsAll[0]);
951 auto bestRank = selector_rank(createSelectorsAll[0]);
952 GAIA_FOR2_(1, createSelectorAllCnt, i) {
953 const auto bucketSize = world_component_index_bucket_size(*w, createSelectorsAll[i]);
954 const auto rank = selector_rank(createSelectorsAll[i]);
955 if (bucketSize < bestBucketSize || (bucketSize == bestBucketSize && rank < bestRank)) {
956 bestBucketSize = bucketSize;
957 bestRank = rank;
958 bestIdx = (uint8_t)i;
959 }
960 }
961 data.deps.createSelectors[data.deps.createSelectorCnt++] = createSelectorsAll[bestIdx];
962 } else {
963 GAIA_FOR(createSelectorOrCnt) {
964 data.deps.createSelectors[data.deps.createSelectorCnt++] = createSelectorsOr[i];
965 }
966 }
967 if (hasPrefabTerms)
968 data.flags |= QueryCtx::QueryFlags::HasPrefabTerms;
969 else
970 data.flags &= ~QueryCtx::QueryFlags::HasPrefabTerms;
971
972 if (hasSourceTerms)
973 data.flags |= QueryCtx::QueryFlags::HasSourceTerms;
974 else
975 data.flags &= ~QueryCtx::QueryFlags::HasSourceTerms;
976
977 if (hasVariableTerms)
978 data.flags |= QueryCtx::QueryFlags::HasVariableTerms;
979 else
980 data.flags &= ~QueryCtx::QueryFlags::HasVariableTerms;
981
982 if (hasSourceTerms || hasVariableTerms)
983 data.cachePolicy = CachePolicy::Dynamic;
984 else if (
985 !hasEntityFilterTerms && data.sortByFunc == nullptr && data.groupBy == EntityBad && hasCreateSelector)
986 data.cachePolicy = CachePolicy::Immediate;
987 else
988 data.cachePolicy = CachePolicy::Lazy;
989
990 data.createArchetypeMatchKind = data.cachePolicy == CachePolicy::Immediate && canDirectCreateArchetypeMatch
991 ? CreateArchetypeMatchKind::DirectStructuralTerms
992 : CreateArchetypeMatchKind::Vm;
993
994 // Traversed-source snapshot caching is only effective for traversed source terms.
995 if (!data.deps.has_dep_flag(DependencyHasSourceTerms) || !data.deps.has_dep_flag(DependencyHasTraversalTerms))
996 data.cacheSrcTrav = 0;
997
998 // Calculate the component mask for simple queries
999 isComplex |= ((data.as_mask_0 + data.as_mask_1) != 0);
1000 if (isComplex) {
1001 data.queryMask = {};
1002 data.flags |= QueryCtx::QueryFlags::Complex;
1003 } else {
1004 data.queryMask = build_entity_mask(EntitySpan{idsNoSrc.data(), idsNoSrcCnt});
1005 data.flags &= ~QueryCtx::QueryFlags::Complex;
1006 }
1007 }
1008
1009 // Request recompilation of the query if the mask has changed
1010 GAIA_FOR(data.idsCnt) data.lookupTerms[i] = data.terms[i];
1011 canonicalize_lookup_terms(std::span<QueryTerm>{data.lookupTerms.data(), data.idsCnt});
1012 GAIA_FOR(data.changedCnt) data.changedLookup[i] = data.changed[i];
1013 canonicalize_lookup_changed(std::span<Entity>{data.changedLookup.data(), data.changedCnt});
1014 GAIA_FOR(data.groupDepCnt) data.groupDepsLookup[i] = data.groupDeps[i];
1015 canonicalize_lookup_changed(std::span<Entity>{data.groupDepsLookup.data(), data.groupDepCnt});
1016
1017 // Request recompilation of the query if the mask has changed
1018 if (mask0_old != data.as_mask_0 || mask1_old != data.as_mask_1 ||
1019 isComplex_old != (data.flags & QueryFlags::Complex) ||
1020 hasSourceTerms_old != (data.flags & QueryFlags::HasSourceTerms) ||
1021 hasVariableTerms_old != (data.flags & QueryFlags::HasVariableTerms) ||
1022 cachePolicy_old != data.cachePolicy || createArchetypeMatchKind_old != data.createArchetypeMatchKind ||
1023 dependencyFlags_old != data.deps.flags || createSelectorCnt_old != data.deps.createSelectorCnt ||
1024 exclusionCnt_old != data.deps.exclusionCnt || relationCnt_old != data.deps.relationCnt ||
1025 sourceEntityCnt_old != data.deps.sourceEntityCnt || sourceTermCnt_old != data.deps.sourceTermCnt ||
1026 createSelectors_old != data.deps.createSelectors || exclusions_old != data.deps.exclusions ||
1027 relations_old != data.deps.relations || sourceEntities_old != data.deps.sourceEntities)
1028 data.flags |= QueryCtx::QueryFlags::Recompile;
1029 }
1030
1031 GAIA_NODISCARD static bool
1032 equals_no_handle_assumption(const QueryCtx& leftCtx, const QueryCtx& rightCtx) noexcept {
1033 // Lookup hash must match
1034 if (leftCtx.hashLookup != rightCtx.hashLookup)
1035 return false;
1036
1037 const auto& left = leftCtx.data;
1038 const auto& right = rightCtx.data;
1039
1040 // Check array sizes first
1041 if (left.idsCnt != right.idsCnt)
1042 return false;
1043 if (left.changedCnt != right.changedCnt)
1044 return false;
1045 if (left.groupDepCnt != right.groupDepCnt)
1046 return false;
1047 if (left.readWriteMask != right.readWriteMask)
1048 return false;
1049 if (left.cacheSrcTrav != right.cacheSrcTrav)
1050 return false;
1051
1052 {
1053 const auto cnt = left.idsCnt;
1054 GAIA_FOR(cnt) {
1055 if (left.lookupTerms[i] != right.lookupTerms[i])
1056 return false;
1057 }
1058 }
1059
1060 {
1061 const auto cnt = left.changedCnt;
1062 GAIA_FOR(cnt) {
1063 if (left.changedLookup[i] != right.changedLookup[i])
1064 return false;
1065 }
1066 }
1067
1068 {
1069 const auto cnt = left.groupDepCnt;
1070 GAIA_FOR(cnt) {
1071 if (left.groupDepsLookup[i] != right.groupDepsLookup[i])
1072 return false;
1073 }
1074 }
1075
1076 // SortBy data need to match
1077 if (left.sortBy != right.sortBy)
1078 return false;
1079 if (left.sortByFunc != right.sortByFunc)
1080 return false;
1081
1082 // Grouping data need to match
1083 if (left.groupBy != right.groupBy)
1084 return false;
1085 if (left.groupByFunc != right.groupByFunc)
1086 return false;
1087
1088 return true;
1089 }
1090
1091 GAIA_NODISCARD bool operator==(const QueryCtx& other) const noexcept {
1092 // Comparison expected to be done only the first time the query is set up
1093 GAIA_ASSERT(q.handle.id() == QueryIdBad);
1094 // Fast path when cache ids are set
1095 // if (queryId != QueryIdBad && queryId == other.queryId)
1096 // return true;
1097
1098 return equals_no_handle_assumption(*this, other);
1099 }
1100
1101 GAIA_NODISCARD bool operator!=(const QueryCtx& other) const noexcept {
1102 return !operator==(other);
1103 }
1104 };
1105
1108 constexpr bool operator()(const QueryTerm& lhs, const QueryTerm& rhs) const {
1109 return query_term_less_for_lookup(lhs, rhs);
1110 }
1111 };
1112
1114 inline void sort(QueryCtx& ctx) {
1115 const uint32_t idsCnt = ctx.data.idsCnt;
1116 const uint32_t changedCnt = ctx.data.changedCnt;
1117
1118 auto& ctxData = ctx.data;
1119 // Canonicalize degenerate OR queries: a single OR term has AND semantics.
1120 // Rewriting it here keeps ordering/hash behavior identical to an explicit ALL term.
1121 if (idsCnt > 0) {
1122 uint32_t orCnt = 0;
1123 uint32_t orIdx = BadIndex;
1124 GAIA_FOR(idsCnt) {
1125 if (ctxData.terms[i].op != QueryOpKind::Or)
1126 continue;
1127 ++orCnt;
1128 orIdx = i;
1129 if (orCnt > 1)
1130 break;
1131 }
1132
1133 if (orCnt == 1)
1134 ctxData.terms[orIdx].op = QueryOpKind::All;
1135 }
1136
1137 // Sort data. Necessary for correct hash calculation.
1138 // Without sorting query.all<XXX, YYY> would be different than query.all<YYY, XXX>.
1139 // Also makes sure data is in optimal order for query processing.
1140 core::sort(
1141 ctxData.terms.data(), ctxData.terms.data() + ctxData.idsCnt, query_sort_cond{}, //
1142 [&](uint32_t left, uint32_t right) {
1143 core::swap(ctxData.ids[left], ctxData.ids[right]);
1144 core::swap(ctxData.terms[left], ctxData.terms[right]);
1145
1146 // Make sure masks remains correct after sorting
1147 core::swap_bits(ctxData.readWriteMask, left, right);
1148 core::swap_bits(ctxData.as_mask_0, left, right);
1149 core::swap_bits(ctxData.as_mask_1, left, right);
1150 });
1151
1152 if (idsCnt > 0) {
1153 uint32_t i = 0;
1154 while (i < idsCnt && ctxData.terms[i].op == QueryOpKind::All)
1155 ++i;
1156 ctxData.firstOr = (uint8_t)i;
1157 while (i < idsCnt && ctxData.terms[i].op == QueryOpKind::Or)
1158 ++i;
1159 ctxData.firstNot = (uint8_t)i;
1160 while (i < idsCnt && ctxData.terms[i].op == QueryOpKind::Not)
1161 ++i;
1162 ctxData.firstAny = (uint8_t)i;
1163 } else
1164 ctxData.firstOr = ctxData.firstNot = ctxData.firstAny = 0;
1165
1166 // Canonicalize filter order. This enables monotonic component lookup in filter matching
1167 // and keeps cache keys stable regardless of changed() call order.
1168 if (changedCnt > 1) {
1169 core::sort(ctxData.changed.data(), ctxData.changed.data() + changedCnt, SortComponentCond{});
1170 }
1171 }
1172
1175 inline void normalize_cache_src_trav(QueryCtx& ctx) {
1176 auto& ctxData = ctx.data;
1177 if (ctxData.cacheSrcTrav == 0)
1178 return;
1179
1180 bool hasTraversedSourceTerm = false;
1181 for (const auto& term: ctxData.terms_view()) {
1182 if (term.src == EntityBad || term.entTrav == EntityBad)
1183 continue;
1184
1185 hasTraversedSourceTerm = true;
1186 break;
1187 }
1188
1189 if (!hasTraversedSourceTerm)
1190 ctxData.cacheSrcTrav = 0;
1191 }
1192
1193 inline void calc_lookup_hash(QueryCtx& ctx) {
1194 GAIA_ASSERT(ctx.cc != nullptr);
1195 // Make sure we don't calculate the hash twice
1196 GAIA_ASSERT(ctx.hashLookup.hash == 0);
1197
1198 QueryLookupHash::Type hashLookup = 0;
1199
1200 const auto& ctxData = ctx.data;
1201
1202 // Ids & ops
1203 {
1204 QueryLookupHash::Type hash = 0;
1205
1206 for (uint32_t i = 0; i < ctxData.idsCnt; ++i) {
1207 const auto& pair = ctxData.lookupTerms[i];
1208 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.op);
1209 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.id.value());
1210 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.src.value());
1211 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.entTrav.value());
1212 hash = core::hash_combine(hash, (QueryLookupHash::Type)(uint8_t)pair.travKind);
1213 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.travDepth);
1214 hash = core::hash_combine(hash, (QueryLookupHash::Type)(uint8_t)pair.matchKind);
1215 }
1216 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.idsCnt);
1217 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.readWriteMask);
1218 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.cacheSrcTrav);
1219
1220 const bool matchPrefab = (ctxData.flags & QueryCtx::QueryFlags::MatchPrefab) != 0;
1221 hash = core::hash_combine(hash, (QueryLookupHash::Type)matchPrefab);
1222
1223 hashLookup = hash;
1224 }
1225
1226 // Filters
1227 {
1228 QueryLookupHash::Type hash = 0;
1229
1230 for (uint32_t i = 0; i < ctxData.changedCnt; ++i)
1231 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.changedLookup[i].value());
1232 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.changedCnt);
1233
1234 hashLookup = core::hash_combine(hashLookup, hash);
1235 }
1236
1237 // Explicit grouping dependencies
1238 {
1239 QueryLookupHash::Type hash = 0;
1240
1241 for (uint32_t i = 0; i < ctxData.groupDepCnt; ++i)
1242 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.groupDepsLookup[i].value());
1243 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.groupDepCnt);
1244
1245 hashLookup = core::hash_combine(hashLookup, hash);
1246 }
1247
1248 // Grouping
1249 {
1250 QueryLookupHash::Type hash = 0;
1251
1252 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.groupBy.value());
1253 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.groupByFunc);
1254
1255 hashLookup = core::hash_combine(hashLookup, hash);
1256 }
1257
1258 ctx.hashLookup = {core::calculate_hash64(hashLookup)};
1259 }
1260
1267 template <uint32_t MAX_COMPONENTS>
1268 GAIA_NODISCARD inline uint32_t comp_idx(const QueryTerm* pTerms, Entity entity, Entity src) {
1269 // We let the compiler know the upper iteration bound at compile-time.
1270 // This way it can optimize better (e.g. loop unrolling, vectorization).
1271 GAIA_FOR(MAX_COMPONENTS) {
1272 if (pTerms[i].id == entity && pTerms[i].src == src)
1273 return i;
1274 }
1275
1276 GAIA_ASSERT(false);
1277 return BadIndex;
1278 }
1279 } // namespace ecs
1280} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Array of elements of type.
Definition sarray_ext_impl.h:27
Array of elements of type.
Definition sarray_impl.h:26
Definition span_impl.h:99
Definition archetype.h:83
Cache for compile-time defined components.
Definition component_cache.h:25
Definition world.h:88
Same API as ser_buffer_binary, but backed by fully dynamic storage.
Definition ser_buffer_binary.h:157
Definition robin_hood.h:720
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition query_common.h:98
Hashmap lookup structure used for Entity.
Definition id.h:439
Definition id.h:241
Definition query_common.h:541
Definition query_common.h:540
cnt::sarray< QueryTerm, MAX_ITEMS_IN_QUERY > lookupTerms
Canonicalized lookup terms reused by hash/equality for shared query dedup.
Definition query_common.h:612
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:653
QueryEntityArray groupDeps
Explicit grouping invalidation dependencies for custom group_by callbacks.
Definition query_common.h:624
Entity sortBy
Entity to sort the archetypes by. EntityBad for no sorting.
Definition query_common.h:628
TGroupByFunc groupByFunc
Function to use to perform the grouping.
Definition query_common.h:634
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:639
Entity groupBy
Entity to group the archetypes by. EntityBad for no grouping.
Definition query_common.h:632
void add_group_dep(Entity relation)
Adds a declared grouping invalidation dependency.
Definition query_common.h:682
QueryEntityArray ids
Array of queried ids.
Definition query_common.h:608
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:642
uint8_t firstOr
First OR record in pairs/ids/ops.
Definition query_common.h:648
uint16_t cacheSrcTrav
Maximum allowed size of an explicitly cached traversed-source lookup closure.
Definition query_common.h:657
QueryEntityArray changedLookup
Canonicalized changed-filter ids reused by hash/equality for shared query dedup.
Definition query_common.h:622
QueryMask queryMask
Component mask used for faster matching of simple queries.
Definition query_common.h:636
uint8_t firstAny
First ANY record in pairs/ids/ops.
Definition query_common.h:646
QueryArchetypeCacheIndexMap lastMatchedArchetypeIdx_All
Index of the last checked archetype in the component-to-archetype map.
Definition query_common.h:614
CreateArchetypeMatchKind createArchetypeMatchKind
Create-time archetype matcher derived from query shape.
Definition query_common.h:667
Entity directTargetEvalId
Term id used by the specialized direct-target evaluation shape.
Definition query_common.h:661
void add_group_deps()
Adds all grouping invalidation relations to the dependency set.
Definition query_common.h:692
QueryEntityArray groupDepsLookup
Canonicalized group dependency ids reused by hash/equality for shared query dedup.
Definition query_common.h:626
Dependencies deps
Explicit dependency metadata derived from query shape.
Definition query_common.h:663
TSortByFunc sortByFunc
Function to use to perform sorting.
Definition query_common.h:630
uint8_t flags
Query flags.
Definition query_common.h:655
uint8_t firstNot
First NOT record in pairs/ids/ops.
Definition query_common.h:644
CachePolicy cachePolicy
Cache maintenance policy derived from query shape.
Definition query_common.h:665
uint8_t groupDepCnt
Number of defined group dependencies.
Definition query_common.h:650
QueryEntityArray changed
Array of filtered components.
Definition query_common.h:620
cnt::sarray< QueryTerm, MAX_ITEMS_IN_QUERY > terms
Array of terms.
Definition query_common.h:610
DirectTargetEvalKind directTargetEvalKind
Specialized direct-target evaluation shape for single-term queries.
Definition query_common.h:659
Definition query_common.h:471
ComponentCache * cc
Component cache.
Definition query_common.h:475
QueryIdentity q
Query identity.
Definition query_common.h:479
QueryLookupHash hashLookup
Lookup hash for this query.
Definition query_common.h:477
Hashmap lookup structure used for Entity.
Definition query_common.h:174
Definition query_common.h:125
Definition query_common.h:457
QueryHandle handle
Query id.
Definition query_common.h:459
QueryId serId
Serialization id.
Definition query_common.h:461
User-provided query input.
Definition query_common.h:222
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:234
QueryTravKind travKind
Source traversal filter. Self means checking the source itself, Up means checking traversed ancestors...
Definition query_common.h:241
Entity id
Entity/Component/Pair to query.
Definition query_common.h:230
QueryAccess access
Access type.
Definition query_common.h:228
Entity entTrav
Optional traversal relation for source lookups. When set, the lookup starts at src and then walks rel...
Definition query_common.h:237
QueryOpKind op
Operation to perform with the input.
Definition query_common.h:226
QueryMatchKind matchKind
Match semantics for terms with special meaning, such as Pair(Is, X).
Definition query_common.h:246
uint8_t travDepth
Maximum number of traversal steps. 0 means unlimited traversal depth (bounded internally,...
Definition query_common.h:244
Additional options for query terms. This can be used to configure source lookup, traversal and access...
Definition query_common.h:252
uint8_t travDepth
Maximum number of traversal steps. 0 means unlimited traversal depth (bounded internally,...
Definition query_common.h:263
Entity entSrc
Source entity to query from.
Definition query_common.h:256
Entity entTrav
Optional traversal relation used for source lookup.
Definition query_common.h:258
QueryAccess access
Access mode for the term. When None, typed query terms infer read/write access from template mutabili...
Definition query_common.h:266
QueryMatchKind matchKind
Match semantics for terms with special meaning, such as Pair(Is, X).
Definition query_common.h:268
QueryTravKind travKind
Source traversal filter.
Definition query_common.h:260
Internal representation of QueryInput.
Definition query_common.h:363
Entity id
Queried id.
Definition query_common.h:365
Archetype * srcArchetype
Archetype of the src entity.
Definition query_common.h:377
uint8_t travDepth
Maximum number of traversal steps.
Definition query_common.h:373
QueryMatchKind matchKind
Match semantics for this term.
Definition query_common.h:375
uint8_t fieldIndex
Stable execution field index matching the user-defined query field order.
Definition query_common.h:381
Entity entTrav
Optional traversal relation for source lookups.
Definition query_common.h:369
QueryOpKind op
Operation to perform with the term.
Definition query_common.h:379
Entity src
Source of where the queried id is looked up at.
Definition query_common.h:367
QueryTravKind travKind
Source traversal filter.
Definition query_common.h:371
Definition query_common.h:110
Functor for sorting terms in a query before compilation.
Definition query_common.h:1107