Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
query.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cstdarg>
5#include <cstdint>
6#include <type_traits>
7
8#include "gaia/cnt/darray.h"
9#include "gaia/cnt/map.h"
10#include "gaia/cnt/sarray_ext.h"
11#include "gaia/config/profiler.h"
12#include "gaia/core/hashing_policy.h"
13#include "gaia/core/utility.h"
14#include "gaia/ecs/api.h"
15#include "gaia/ecs/archetype.h"
16#include "gaia/ecs/archetype_common.h"
17#include "gaia/ecs/chunk.h"
18#include "gaia/ecs/chunk_iterator.h"
19#include "gaia/ecs/common.h"
20#include "gaia/ecs/component.h"
21#include "gaia/ecs/component_cache.h"
22#include "gaia/ecs/id.h"
23#include "gaia/ecs/query_cache.h"
24#include "gaia/ecs/query_common.h"
25#include "gaia/ecs/query_info.h"
26#include "gaia/ecs/sched.h"
27#include "gaia/mem/smallblock_allocator.h"
28#include "gaia/ser/ser_buffer_binary.h"
29#include "gaia/ser/ser_ct.h"
30#include "gaia/util/str.h"
31
32namespace gaia {
33 namespace ecs {
34 class World;
35 void world_finish_write(World& world, Entity term, Entity entity);
36 const Sched& world_sched(const World& world);
37
39 inline static constexpr uint16_t MaxCacheSrcTrav = 32;
40
42 enum class QueryExecType : uint32_t {
44 Serial,
46 Parallel,
48 ParallelPerf,
50 ParallelEff,
52 Default = Serial,
53 };
54
56 enum class QueryCacheKind : uint8_t {
59 None,
67 Default,
75 Auto,
81 All
82 };
83
85 enum class QueryCacheScope : uint8_t {
87 Local,
89 Shared
90 };
91
93 enum class QueryKindRes : uint8_t {
95 OK,
97 AutoSrcTrav,
99 AllNotIm,
101 AllSrcTrav
102 };
103
108 enum class TravOrder : uint8_t {
110 Up,
112 Postorder = Up,
114 Down,
116 Preorder = Down,
118 ReverseUp,
120 ReversePostorder = ReverseUp,
122 ReverseDown,
124 ReversePreorder = ReverseDown
125 };
126
127 using QueryCachePolicy = QueryCtx::CachePolicy;
128 struct TypedQueryExecState;
129
130 namespace detail {
131 template <typename Func>
132 inline constexpr bool is_query_iter_callback_v = std::is_invocable_v<Func, Iter&>;
133
134 template <typename Func>
135 inline constexpr bool is_query_walk_core_callback_v =
136 is_query_iter_callback_v<Func> || std::is_invocable_v<Func, const Entity&> ||
137 std::is_invocable_v<Func, Entity>;
138
140 enum QueryCmdType : uint8_t { ADD_ITEM, ADD_FILTER, SORT_BY, GROUP_BY, GROUP_DEP, MATCH_PREFAB };
141
143 static constexpr QueryCmdType Id = QueryCmdType::ADD_ITEM;
144 static constexpr bool InvalidatesHash = true;
145
146 QueryInput item;
147
148 void exec(QueryCtx& ctx) const {
149 auto& ctxData = ctx.data;
150
151#if GAIA_DEBUG
152 // Unique component ids only
153 GAIA_ASSERT(!core::has(ctxData.ids_view(), item.id));
154
155 // There's a limit to the amount of query items which we can store
156 if (ctxData.idsCnt >= MAX_ITEMS_IN_QUERY) {
157 GAIA_ASSERT2(false, "Trying to create a query with too many components!");
158
159 const auto* name = ctx.cc->get(item.id).name.str();
160 GAIA_LOG_E("Trying to add component '%s' to an already full ECS query!", name);
161 return;
162 }
163#endif
164
165 // Build the read-write mask.
166 // This will be used to determine what kind of access the user wants for a given component.
167 const uint16_t isReadWrite = uint16_t(item.access == QueryAccess::Write);
168 ctxData.readWriteMask |= (isReadWrite << ctxData.idsCnt);
169
170 ctxData.ids[ctxData.idsCnt] = item.id;
171 ctxData.terms[ctxData.idsCnt] = {item.id, item.entSrc, item.entTrav,
172 item.travKind, item.travDepth, item.matchKind,
173 nullptr, item.op, (uint8_t)ctxData.idsCnt};
174 ++ctxData.idsCnt;
175 }
176 };
177
179 static constexpr QueryCmdType Id = QueryCmdType::ADD_FILTER;
180 static constexpr bool InvalidatesHash = true;
181
182 Entity comp;
183
184 void exec(QueryCtx& ctx) const {
185 auto& ctxData = ctx.data;
186
187#if GAIA_DEBUG
188 GAIA_ASSERT(core::has(ctxData.ids_view(), comp));
189 GAIA_ASSERT(!core::has(ctxData.changed_view(), comp));
190
191 // There's a limit to the amount of components which we can store
192 if (ctxData.changedCnt >= MAX_ITEMS_IN_QUERY) {
193 GAIA_ASSERT2(false, "Trying to create an filter query with too many components!");
194
195 const auto* compName = ctx.cc->get(comp).name.str();
196 GAIA_LOG_E("Trying to add component %s to an already full filter query!", compName);
197 return;
198 }
199
200 uint32_t compIdx = 0;
201 for (; compIdx < ctxData.idsCnt; ++compIdx)
202 if (ctxData.ids[compIdx] == comp)
203 break;
204
205 // NOTE: Code bellow does the same as this commented piece.
206 // However, compilers can't quite optimize it as well because it does some more
207 // calculations. This is used often so go with the custom code.
208 // const auto compIdx = core::get_index_unsafe(ids, comp);
209
210 // Component has to be present in all/or lists.
211 // Filtering by NOT/ANY doesn't make sense because those are not hard requirements.
212 GAIA_ASSERT2(
213 ctxData.terms[compIdx].op != QueryOpKind::Not && ctxData.terms[compIdx].op != QueryOpKind::Any,
214 "Filtering by NOT/ANY doesn't make sense!");
215 if (ctxData.terms[compIdx].op != QueryOpKind::Not && ctxData.terms[compIdx].op != QueryOpKind::Any) {
216 ctxData.changed[ctxData.changedCnt++] = comp;
217 return;
218 }
219
220 const auto* compName = ctx.cc->get(comp).name.str();
221 GAIA_LOG_E("SetChangeFilter trying to filter component %s but it's not a part of the query!", compName);
222#else
223 ctxData.changed[ctxData.changedCnt++] = comp;
224#endif
225 }
226 };
227
229 static constexpr QueryCmdType Id = QueryCmdType::SORT_BY;
230 static constexpr bool InvalidatesHash = true;
231
232 Entity sortBy;
233 TSortByFunc func;
234
235 void exec(QueryCtx& ctx) const {
236 auto& ctxData = ctx.data;
237 ctxData.sortBy = sortBy;
238 GAIA_ASSERT(func != nullptr);
239 ctxData.sortByFunc = func;
240 }
241 };
242
244 static constexpr QueryCmdType Id = QueryCmdType::GROUP_BY;
245 static constexpr bool InvalidatesHash = true;
246
247 Entity groupBy;
248 TGroupByFunc func;
249 uint16_t flags;
250
251 void exec(QueryCtx& ctx) const {
252 auto& ctxData = ctx.data;
253 ctxData.groupBy = groupBy;
254 GAIA_ASSERT(func != nullptr);
255 ctxData.groupByFunc = func; // group_by_func_default;
256 if ((flags & QueryCtx::QueryFlags::OrderGroups) != 0)
257 ctxData.flags |= QueryCtx::QueryFlags::OrderGroups;
258 else
259 ctxData.flags &= ~QueryCtx::QueryFlags::OrderGroups;
260 }
261 };
262
264 static constexpr QueryCmdType Id = QueryCmdType::GROUP_DEP;
265 static constexpr bool InvalidatesHash = true;
266
267 Entity relation;
268
269 void exec(QueryCtx& ctx) const {
270 auto& ctxData = ctx.data;
271 GAIA_ASSERT(!relation.pair());
272 ctxData.add_group_dep(relation);
273 }
274 };
275
277 static constexpr QueryCmdType Id = QueryCmdType::MATCH_PREFAB;
278 static constexpr bool InvalidatesHash = true;
279
280 void exec(QueryCtx& ctx) const {
281 ctx.data.flags |= QueryCtx::QueryFlags::MatchPrefab;
282 }
283 };
284
286 World* m_world = nullptr;
290 QueryInfo* m_pInfo = nullptr;
295 bool m_destroyed = false;
296
297 public:
298 QueryImplStorage() = default;
300 (void)try_del_from_cache();
301 delete m_pOwnedInfo;
302 }
303
304 QueryImplStorage(QueryImplStorage&& other) {
305 m_world = other.m_world;
306 m_pCache = other.m_pCache;
307 m_pInfo = other.m_pInfo;
308 m_pOwnedInfo = other.m_pOwnedInfo;
309 m_identity = other.m_identity;
310 m_destroyed = other.m_destroyed;
311
312 // Make sure old instance is invalidated
313 other.m_pInfo = nullptr;
314 other.m_pOwnedInfo = nullptr;
315 other.m_identity = {};
316 other.m_destroyed = false;
317 }
318 QueryImplStorage& operator=(QueryImplStorage&& other) {
319 GAIA_ASSERT(core::addressof(other) != this);
320
321 (void)try_del_from_cache();
322 delete m_pOwnedInfo;
323
324 m_world = other.m_world;
325 m_pCache = other.m_pCache;
326 m_pInfo = other.m_pInfo;
327 m_pOwnedInfo = other.m_pOwnedInfo;
328 m_identity = other.m_identity;
329 m_destroyed = other.m_destroyed;
330
331 // Make sure old instance is invalidated
332 other.m_pInfo = nullptr;
333 other.m_pOwnedInfo = nullptr;
334 other.m_identity = {};
335 other.m_destroyed = false;
336
337 return *this;
338 }
339
340 QueryImplStorage(const QueryImplStorage& other) {
341 m_world = other.m_world;
342 m_pCache = other.m_pCache;
343 m_pInfo = other.m_pInfo;
344 if (other.m_pOwnedInfo != nullptr)
345 m_pOwnedInfo = new QueryInfo(*other.m_pOwnedInfo);
346 m_identity = other.m_identity;
347 m_destroyed = other.m_destroyed;
348
349 // Make sure to update the ref count of the cached query so
350 // it doesn't get deleted by accident.
351 if (!m_destroyed) {
352 auto* pInfo = try_query_info_fast();
353 if (pInfo == nullptr)
355 if (pInfo != nullptr)
356 pInfo->add_ref();
357 }
358 }
359 QueryImplStorage& operator=(const QueryImplStorage& other) {
360 GAIA_ASSERT(core::addressof(other) != this);
361
362 (void)try_del_from_cache();
363 delete m_pOwnedInfo;
364 m_pOwnedInfo = nullptr;
365
366 m_world = other.m_world;
367 m_pCache = other.m_pCache;
368 m_pInfo = other.m_pInfo;
369 if (other.m_pOwnedInfo != nullptr)
370 m_pOwnedInfo = new QueryInfo(*other.m_pOwnedInfo);
371 m_identity = other.m_identity;
372 m_destroyed = other.m_destroyed;
373
374 // Make sure to update the ref count of the cached query so
375 // it doesn't get deleted by accident.
376 if (!m_destroyed) {
377 auto* pInfo = try_query_info_fast();
378 if (pInfo == nullptr)
380 if (pInfo != nullptr)
381 pInfo->add_ref();
382 }
383
384 return *this;
385 }
386
389 GAIA_NODISCARD World* world() {
390 return m_world;
391 }
392
395 GAIA_NODISCARD QuerySerBuffer& ser_buffer() {
396 return m_identity.ser_buffer(m_world);
397 }
398
401 return m_identity.ser_buffer_reset(m_world);
402 }
403
407 void init(World* world, QueryCache* queryCache) {
408 m_world = world;
409 m_pCache = queryCache;
410 m_pInfo = nullptr;
411 }
412
414 void reset() {
415 if (auto* pInfo = try_query_info_fast(); pInfo != nullptr)
416 pInfo->reset();
417 if (m_pOwnedInfo != nullptr)
419 }
420
423 m_destroyed = false;
424 }
425
428 GAIA_NODISCARD bool try_del_from_cache() {
429 if (!m_destroyed && m_identity.handle.id() != QueryIdBad)
431
432 // Don't allow multiple calls to destroy to break the reference counter.
433 // One object is only allowed to destroy once.
434 m_pInfo = nullptr;
435 m_destroyed = true;
436 return false;
437 }
438
440 void invalidate() {
441 m_pInfo = nullptr;
442 m_identity.handle = {};
443 delete m_pOwnedInfo;
444 m_pOwnedInfo = nullptr;
445 }
446
449 GAIA_NODISCARD QueryInfo* try_query_info_fast() const {
450 if (m_pInfo == nullptr || m_identity.handle.id() == QueryIdBad || m_pCache == nullptr)
451 return nullptr;
452
453 auto* pInfo = m_pCache->try_get(m_identity.handle);
454 return pInfo == m_pInfo ? pInfo : nullptr;
455 }
456
459 void cache_query_info(QueryInfo& queryInfo) {
460 m_pInfo = &queryInfo;
461 }
462
465 GAIA_NODISCARD bool has_owned_query_info() const {
466 return m_pOwnedInfo != nullptr;
467 }
468
471 GAIA_NODISCARD QueryInfo& owned_query_info() {
472 GAIA_ASSERT(m_pOwnedInfo != nullptr);
473 return *m_pOwnedInfo;
474 }
475
479 if (m_pOwnedInfo == nullptr)
480 m_pOwnedInfo = new QueryInfo(GAIA_MOV(queryInfo));
481 else
482 *m_pOwnedInfo = GAIA_MOV(queryInfo);
483 }
484
487 GAIA_NODISCARD bool is_cached() const {
488 auto* pInfo = try_query_info_fast();
489 if (pInfo == nullptr)
491 return pInfo != nullptr;
492 }
493
496 GAIA_NODISCARD bool is_initialized() const {
497 return m_world != nullptr && m_pCache != nullptr;
498 }
499 };
500 class QueryImpl {
501 static constexpr uint32_t ChunkBatchSize = 32;
502 friend class SystemBuilder;
503
504 struct ChunkBatch {
505 const Archetype* pArchetype;
506 Chunk* pChunk;
507 const uint8_t* pCompIndices;
508 InheritedTermDataView inheritedData;
509 GroupId groupId;
510 uint16_t from;
511 uint16_t to;
512 };
513
517 using CmdFunc = void (*)(QuerySerBuffer& buffer, QueryCtx& ctx);
518
519 struct DirectQueryScratch {
521 cnt::darray<Entity> entities;
522 cnt::darray<Entity> termEntities;
523 cnt::darray<Entity> bucketEntities;
525 uint32_t seenVersion = 1;
526 };
527
528 private:
529 GAIA_NODISCARD bool uses_query_cache_storage() const {
530 return m_cacheKind != QueryCacheKind::None;
531 }
532
533 GAIA_NODISCARD bool uses_shared_cache_layer() const {
534 return uses_query_cache_storage() && m_cacheScope == QueryCacheScope::Shared;
535 }
536
537 void invalidate_query_storage() {
538 if (uses_query_cache_storage())
539 (void)m_storage.try_del_from_cache();
540 m_storage.invalidate();
541 }
542
545 GAIA_NODISCARD static DirectQueryScratch& direct_query_scratch() {
546 static thread_local DirectQueryScratch scratch;
547 return scratch;
548 }
549
553 static void ensure_direct_query_count_capacity(DirectQueryScratch& scratch, uint32_t entityId) {
554 if (entityId < scratch.counts.size())
555 return;
556
557 const auto doubledSize = (uint32_t)scratch.counts.size() * 2U;
558 const auto minSize = doubledSize > 64U ? doubledSize : 64U;
559 const auto newSize = (entityId + 1U) > minSize ? (entityId + 1U) : minSize;
560 scratch.counts.resize(newSize, 0);
561 }
562
566 GAIA_NODISCARD static uint32_t next_direct_query_seen_version(DirectQueryScratch& scratch) {
567 update_version(scratch.seenVersion);
568 if (scratch.seenVersion == 0) {
569 scratch.seenVersion = 1;
570 core::fill(scratch.counts.begin(), scratch.counts.end(), 0);
571 }
572
573 return scratch.seenVersion;
574 }
575
576 static constexpr CmdFunc CommandBufferRead[] = {
577 // Add item
578 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
580 ser::load(buffer, cmd);
581 cmd.exec(ctx);
582 },
583 // Add filter
584 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
586 ser::load(buffer, cmd);
587 cmd.exec(ctx);
588 },
589 // SortBy
590 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
591 QueryCmd_SortBy cmd;
592 ser::load(buffer, cmd);
593 cmd.exec(ctx);
594 },
595 // GroupBy
596 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
598 ser::load(buffer, cmd);
599 cmd.exec(ctx);
600 },
601 // GroupDep
602 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
604 ser::load(buffer, cmd);
605 cmd.exec(ctx);
606 },
607 // MatchPrefab
608 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
610 ser::load(buffer, cmd);
611 cmd.exec(ctx);
612 } //
613 }; // namespace detail
614
616 QueryImplStorage m_storage;
618 ArchetypeId* m_nextArchetypeId{};
620 uint32_t* m_worldVersion{};
622 const EntityToArchetypeMap* m_entityToArchetypeMap{};
624 const EntityToArchetypeVersionMap* m_entityToArchetypeMapVersions{};
626 const ArchetypeDArray* m_allArchetypes{};
630 uint8_t m_varNamesMask = 0;
632 cnt::sarray<Entity, MaxVarCnt> m_varBindings;
634 uint8_t m_varBindingsMask = 0;
636 GroupId m_groupIdSet = 0;
638 uint32_t m_changedWorldVersion = 0;
641 cnt::darray<ChunkBatch> m_batches;
643 QueryCacheKind m_cacheKind = QueryCacheKind::Default;
645 QueryCacheScope m_cacheScope = QueryCacheScope::Local;
647 uint16_t m_cacheSrcTrav = 0;
649 void* m_ctx = nullptr;
651 bool m_mainThread = false;
653 QueryAccessSet m_access;
654
656 struct EachWalkData {
658 cnt::darray<Entity> cachedInput;
660 cnt::darray<Entity> cachedOutput;
664 Entity cachedRelation = EntityBad;
666 TravOrder cachedOrder = TravOrder::Down;
668 Constraints cachedConstraints = Constraints::EnabledOnly;
670 uint32_t cachedRelationVersion = 0;
672 uint32_t cachedEntityVersion = 0;
674 cnt::darray<const Chunk*> cachedChunks;
676 bool cacheValid = false;
678 cnt::darray<Entity> scratchEntities;
680 cnt::darray<const Chunk*> scratchChunks;
682 cnt::darray<uint32_t> scratchIndegree;
684 cnt::darray<uint32_t> scratchOutdegree;
686 cnt::darray<uint32_t> scratchOffsets;
688 cnt::darray<uint32_t> scratchWriteCursor;
690 cnt::darray<uint32_t> scratchEdges;
692 cnt::darray<uint32_t> scratchCurrLevel;
694 cnt::darray<uint32_t> scratchNextLevel;
695 };
696
697 template <typename T>
698 struct OnDemandDataHolder {
699 T* pData = nullptr;
700
701 OnDemandDataHolder() = default;
702
703 ~OnDemandDataHolder() {
704 delete pData;
705 }
706
707 OnDemandDataHolder(const OnDemandDataHolder& other) {
708 if (other.pData != nullptr)
709 pData = new T(*other.pData);
710 }
711
712 OnDemandDataHolder& operator=(const OnDemandDataHolder& other) {
713 if (core::addressof(other) == this)
714 return *this;
715
716 if (other.pData == nullptr) {
717 delete pData;
718 pData = nullptr;
719 return *this;
720 }
721
722 if (pData == nullptr)
723 pData = new T(*other.pData);
724 else
725 *pData = *other.pData;
726
727 return *this;
728 }
729
730 OnDemandDataHolder(OnDemandDataHolder&& other) noexcept: pData(other.pData) {
731 other.pData = nullptr;
732 }
733
734 OnDemandDataHolder& operator=(OnDemandDataHolder&& other) noexcept {
735 if (core::addressof(other) == this)
736 return *this;
737
738 delete pData;
739 pData = other.pData;
740 other.pData = nullptr;
741 return *this;
742 }
743
744 GAIA_NODISCARD T* get() {
745 return pData;
746 }
747
748 GAIA_NODISCARD const T* get() const {
749 return pData;
750 }
751
752 GAIA_NODISCARD T& ensure() {
753 if (pData == nullptr)
754 pData = new T();
755 return *pData;
756 }
757
758 void reset() {
759 delete pData;
760 pData = nullptr;
761 }
762 };
763
765 OnDemandDataHolder<EachWalkData> m_eachWalkData;
766
768 struct DirectSeedRunData {
769 cnt::darray<Entity> cachedEntities;
770 cnt::darray<Entity> cachedChunkOrderedEntities;
772 Entity cachedSeedTerm = EntityBad;
773 QueryMatchKind cachedSeedMatchKind = QueryMatchKind::Semantic;
774 Constraints cachedConstraints = Constraints::EnabledOnly;
775 uint32_t cachedRelVersion = 0;
776 uint32_t cachedWorldVersion = 0;
777 bool cacheValid = false;
778 };
779
780 OnDemandDataHolder<DirectSeedRunData> m_directSeedRunData;
781
786 GAIA_NODISCARD static QueryAccess merge_access(QueryAccess lhs, QueryAccess rhs) {
787 if (lhs == QueryAccess::Write || rhs == QueryAccess::Write)
788 return QueryAccess::Write;
789 if (lhs == QueryAccess::Read || rhs == QueryAccess::Read)
790 return QueryAccess::Read;
791 return QueryAccess::None;
792 }
793
798 GAIA_NODISCARD static QueryAccess term_access(const QueryCtx::Data& data, Entity entity) {
799 if (entity == EntityBad || entity.pair())
800 return QueryAccess::None;
801
802 QueryAccess access = QueryAccess::None;
803 const auto terms = data.terms_view();
804 GAIA_FOR((uint32_t)terms.size()) {
805 const auto& term = terms[i];
806 if (term.id != entity || (term.op != QueryOpKind::All && term.op != QueryOpKind::Or))
807 continue;
808
809 if ((data.readWriteMask & (uint16_t(1) << i)) != 0)
810 return QueryAccess::Write;
811 access = QueryAccess::Read;
812 }
813
814 return access;
815 }
816
822 GAIA_NODISCARD static QueryAccess
823 effective_access(const QueryCtx::Data& data, const QueryAccessSet& accessSet, Entity entity) {
824 return merge_access(term_access(data, entity), accessSet.access(entity));
825 }
826
831 GAIA_NODISCARD static bool access_conflicts(QueryAccess lhs, QueryAccess rhs) {
832 return (lhs == QueryAccess::Write && rhs != QueryAccess::None) ||
833 (rhs == QueryAccess::Write && lhs != QueryAccess::None);
834 }
835
842 GAIA_NODISCARD static bool conflicts_one_way(
843 const QueryCtx::Data& leftData, const QueryAccessSet& leftAccess, const QueryCtx::Data& rightData,
844 const QueryAccessSet& rightAccess) {
845 const auto terms = leftData.terms_view();
846 GAIA_FOR((uint32_t)terms.size()) {
847 const auto id = terms[i].id;
848 const auto access = term_access(leftData, id);
849 if (access_conflicts(access, effective_access(rightData, rightAccess, id)))
850 return true;
851 }
852
853 for (const auto id: leftAccess.reads_view()) {
854 if (access_conflicts(QueryAccess::Read, effective_access(rightData, rightAccess, id)))
855 return true;
856 }
857 for (const auto id: leftAccess.writes_view()) {
858 if (access_conflicts(QueryAccess::Write, effective_access(rightData, rightAccess, id)))
859 return true;
860 }
861
862 return false;
863 }
864
868 template <typename T>
869 GAIA_NODISCARD Entity access_entity_inter() {
870 if constexpr (is_pair<T>::value) {
871 const auto& descRel = comp_cache_add<typename T::rel_type>(*m_storage.world());
872 const auto& descTgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
873 return Pair(descRel.entity, descTgt.entity);
874 } else {
875 using UO = typename component_type_t<T>::TypeOriginal;
876 static_assert(core::is_raw_v<UO>, "Use reads()/writes() with raw types only");
877 const auto& desc = comp_cache_add<T>(*m_storage.world());
878 return desc.entity;
879 }
880 }
881
882 //--------------------------------------------------------------------------------
883 public:
884 static inline bool SilenceInvalidCacheKindAssertions = false;
885
890 GAIA_PROF_SCOPE(query::fetch);
891
892 // Make sure the query was created by World::query()
893 GAIA_ASSERT(m_storage.is_initialized());
894
895 if (!uses_query_cache_storage()) {
896 if GAIA_UNLIKELY (!m_storage.has_owned_query_info()) {
898 ctx.init(m_storage.world());
899 commit(ctx);
900 m_storage.init_owned_query_info(
901 QueryInfo::create(QueryId{}, GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view()));
902 } else if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad) {
903 recommit(m_storage.owned_query_info().ctx());
904 }
905
906 return m_storage.owned_query_info();
907 }
908
909 // If queryId is set it means QueryInfo was already created.
910 // This is the common case for cached queries.
911 if GAIA_LIKELY (m_storage.m_identity.handle.id() != QueryIdBad) {
912 auto* pQueryInfo = m_storage.try_query_info_fast();
913 if GAIA_UNLIKELY (pQueryInfo == nullptr)
914 pQueryInfo = m_storage.m_pCache->try_get(m_storage.m_identity.handle);
915
916 // The only time when this can be nullptr is just once after Query::destroy is called.
917 if GAIA_LIKELY (pQueryInfo != nullptr) {
918 m_storage.cache_query_info(*pQueryInfo);
919 if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad)
920 recommit(pQueryInfo->ctx());
921 return *pQueryInfo;
922 }
923
924 m_storage.invalidate();
925 }
926
927 // No queryId is set which means QueryInfo needs to be created
929 ctx.init(m_storage.world());
930 commit(ctx);
931 auto& queryInfo =
932 uses_shared_cache_layer()
933 ? m_storage.m_pCache->add(GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view())
934 : m_storage.m_pCache->add_local(GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view());
935 m_storage.m_identity.handle = QueryInfo::handle(queryInfo);
936 m_storage.cache_query_info(queryInfo);
937 m_storage.allow_to_destroy_again();
938 return queryInfo;
939 }
940
943 void match_all(QueryInfo& queryInfo) {
944 const auto kindError = validate_kind(queryInfo.ctx());
945 if (kindError != QueryKindRes::OK) {
946 GAIA_ASSERT2(SilenceInvalidCacheKindAssertions, kind_error_str(kindError));
947 queryInfo.reset();
948 return;
949 }
950
951 if (!uses_query_cache_storage()) {
952 queryInfo.ensure_matches_transient(
953 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, m_varBindings,
954 m_varBindingsMask);
955 return;
956 }
957
958 queryInfo.ensure_matches(
959 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, last_archetype_id(),
960 m_varBindings, m_varBindingsMask);
961 m_storage.m_pCache->sync_archetype_cache(queryInfo);
962 }
963
969 GAIA_NODISCARD bool match_one(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
970 if (!uses_query_cache_storage()) {
971 return queryInfo.ensure_matches_one_transient(archetype, targetEntities, m_varBindings, m_varBindingsMask);
972 }
973
974 return queryInfo.ensure_matches_one(archetype, targetEntities, m_varBindings, m_varBindingsMask);
975 }
976
982 GAIA_NODISCARD bool matches_any(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
983 const auto kindError = validate_kind(queryInfo.ctx());
984 if (kindError != QueryKindRes::OK) {
985 GAIA_ASSERT2(SilenceInvalidCacheKindAssertions, kind_error_str(kindError));
986 queryInfo.reset();
987 return false;
988 }
989
990 return matches_target_entities(queryInfo, archetype, targetEntities);
991 }
992
993 //--------------------------------------------------------------------------------
994
997 GAIA_NODISCARD QueryCachePolicy cache_policy() {
998 return fetch().cache_policy();
999 }
1000
1007 QueryImpl& cache_src_trav(uint16_t maxItems) {
1008 if (m_cacheSrcTrav == maxItems)
1009 return *this;
1010
1011 if (maxItems > MaxCacheSrcTrav) {
1012 GAIA_ASSERT(false && "cache_src_trav should be a value smaller than MaxCacheSrcTrav");
1013 maxItems = MaxCacheSrcTrav;
1014 }
1015
1016 invalidate_each_walk_cache();
1017 invalidate_direct_seed_run_cache();
1018 invalidate_query_storage();
1019 m_cacheSrcTrav = maxItems;
1020 return *this;
1021 }
1022
1026 GAIA_NODISCARD uint16_t cache_src_trav() const {
1027 return m_cacheSrcTrav;
1028 }
1029
1035
1043 QueryImpl& ctx(void* pCtx) {
1044 m_ctx = pCtx;
1045 return *this;
1046 }
1047
1050 GAIA_NODISCARD void* ctx() const {
1051 return m_ctx;
1052 }
1054
1064 QueryImpl& main_thread(bool required = true) {
1065 m_mainThread = required;
1066 return *this;
1067 }
1068
1071 GAIA_NODISCARD bool main_thread_required() const {
1072 return m_mainThread;
1073 }
1075
1085 QueryImpl& reads(Entity entity) {
1086 m_access.add_read(entity);
1087 return *this;
1088 }
1089
1094 template <typename T>
1096 return reads(access_entity_inter<T>());
1097 }
1098
1107 m_access.add_write(entity);
1108 return *this;
1109 }
1110
1115 template <typename T>
1117 return writes(access_entity_inter<T>());
1118 }
1119
1122 GAIA_NODISCARD std::span<const Entity> custom_reads() const {
1123 return m_access.reads_view();
1124 }
1125
1129 return m_access.writes_view();
1130 }
1131
1139 GAIA_NODISCARD QueryAccess access(Entity entity) {
1140 return effective_access(fetch().ctx().data, m_access, entity);
1141 }
1142
1150 GAIA_NODISCARD bool conflicts_with(QueryImpl& other) {
1151 const auto& leftData = fetch().ctx().data;
1152 const auto& rightData = other.fetch().ctx().data;
1153 return conflicts_one_way(leftData, m_access, rightData, other.m_access) ||
1154 conflicts_one_way(rightData, other.m_access, leftData, m_access);
1155 }
1156
1160 GAIA_NODISCARD bool can_run_parallel(QueryImpl& other) {
1161 return !m_mainThread && !other.m_mainThread && !conflicts_with(other);
1162 }
1164
1168 QueryImpl& kind(QueryCacheKind cacheKind) {
1169 if (m_cacheKind == cacheKind)
1170 return *this;
1171
1172 invalidate_each_walk_cache();
1173 invalidate_direct_seed_run_cache();
1174 invalidate_query_storage();
1175 m_cacheKind = cacheKind;
1176
1177 return *this;
1178 }
1179
1183 QueryImpl& scope(QueryCacheScope cacheScope) {
1184 if (m_cacheScope == cacheScope)
1185 return *this;
1186
1187 invalidate_each_walk_cache();
1188 invalidate_direct_seed_run_cache();
1189 invalidate_query_storage();
1190 m_cacheScope = cacheScope;
1191
1192 return *this;
1193 }
1194
1199 add_cmd(cmd);
1200 return *this;
1201 }
1202
1205 GAIA_NODISCARD QueryCacheScope scope() const {
1206 return m_cacheScope;
1207 }
1208
1211 GAIA_NODISCARD QueryCacheKind kind() const {
1212 return m_cacheKind;
1213 }
1214
1217 GAIA_NODISCARD QueryKindRes kind_error() {
1218 return validate_kind(fetch().ctx());
1219 }
1220
1223 GAIA_NODISCARD const char* kind_error_str() {
1224 return kind_error_str(kind_error());
1225 }
1226
1229 GAIA_NODISCARD bool valid() {
1230 return kind_error() == QueryKindRes::OK;
1231 }
1232
1233 //--------------------------------------------------------------------------------
1234 private:
1238 GAIA_NODISCARD bool uses_manual_src_trav_cache(const QueryCtx& ctx) const {
1239 return m_cacheSrcTrav != 0 && //
1240 ctx.data.deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms) && //
1241 ctx.data.deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms);
1242 }
1243
1247 GAIA_NODISCARD static bool uses_im_cache(const QueryCtx& ctx) {
1248 return ctx.data.cachePolicy == QueryCachePolicy::Immediate;
1249 }
1250
1254 GAIA_NODISCARD static bool uses_lazy_cache(const QueryCtx& ctx) {
1255 return ctx.data.cachePolicy == QueryCachePolicy::Lazy;
1256 }
1257
1261 GAIA_NODISCARD static bool uses_dyn_cache(const QueryCtx& ctx) {
1262 return ctx.data.cachePolicy == QueryCachePolicy::Dynamic;
1263 }
1264
1268 GAIA_NODISCARD QueryKindRes validate_kind(const QueryCtx& ctx) const {
1269 if (m_cacheKind == QueryCacheKind::Auto) {
1270 if (uses_manual_src_trav_cache(ctx))
1271 return QueryKindRes::AutoSrcTrav;
1272 }
1273
1274 if (m_cacheKind == QueryCacheKind::All) {
1275 if (uses_manual_src_trav_cache(ctx))
1276 return QueryKindRes::AllSrcTrav;
1277 if (!uses_im_cache(ctx))
1278 return QueryKindRes::AllNotIm;
1279 }
1280
1281 return QueryKindRes::OK;
1282 }
1283
1287 GAIA_NODISCARD static const char* kind_error_str(QueryKindRes error) {
1288 switch (error) {
1289 case QueryKindRes::OK:
1290 return "OK";
1291 case QueryKindRes::AutoSrcTrav:
1292 return "QueryCacheKind::Auto rejects explicit traversed-source snapshot caching";
1293 case QueryKindRes::AllNotIm:
1294 return "QueryCacheKind::All requires a fully immediate structural cache";
1295 case QueryKindRes::AllSrcTrav:
1296 return "QueryCacheKind::All rejects explicit traversed-source snapshot caching";
1297 default:
1298 return "Unknown query kind validation error";
1299 }
1300 }
1301
1304 GAIA_NODISCARD EachWalkData* each_walk_data() {
1305 return m_eachWalkData.get();
1306 }
1307
1310 GAIA_NODISCARD const EachWalkData* each_walk_data() const {
1311 return m_eachWalkData.get();
1312 }
1313
1316 GAIA_NODISCARD EachWalkData& ensure_each_walk_data() {
1317 return m_eachWalkData.ensure();
1318 }
1319
1321 void invalidate_each_walk_cache() {
1322 auto* pWalkData = each_walk_data();
1323 if (pWalkData != nullptr)
1324 pWalkData->cacheValid = false;
1325 }
1326
1329 GAIA_NODISCARD DirectSeedRunData* direct_seed_run_data() {
1330 return m_directSeedRunData.get();
1331 }
1332
1335 GAIA_NODISCARD const DirectSeedRunData* direct_seed_run_data() const {
1336 return m_directSeedRunData.get();
1337 }
1338
1341 GAIA_NODISCARD DirectSeedRunData& ensure_direct_seed_run_data() {
1342 return m_directSeedRunData.ensure();
1343 }
1344
1346 void invalidate_direct_seed_run_cache() {
1347 auto* pRunData = direct_seed_run_data();
1348 if (pRunData != nullptr)
1349 pRunData->cacheValid = false;
1350 }
1351
1353 void reset_changed_filter_state() {
1354 m_changedWorldVersion = 0;
1355 }
1356
1359 ArchetypeId last_archetype_id() const {
1360 return *m_nextArchetypeId - 1;
1361 }
1362
1365 GAIA_NODISCARD std::span<const Archetype*> all_archetypes_view() const {
1366 GAIA_ASSERT(m_allArchetypes != nullptr);
1367 return {(const Archetype**)m_allArchetypes->data(), m_allArchetypes->size()};
1368 }
1369
1370 GAIA_NODISCARD static bool is_query_var_entity(Entity entity) {
1371 return is_variable((EntityId)entity.id());
1372 }
1373
1374 GAIA_NODISCARD static uint32_t query_var_idx(Entity entity) {
1375 GAIA_ASSERT(is_query_var_entity(entity));
1376 return (uint32_t)(entity.id() - Var0.id());
1377 }
1378
1379 GAIA_NODISCARD Entity query_var_entity(uint32_t idx) {
1380 GAIA_ASSERT(idx < 8);
1381 return entity_from_id((const World&)*m_storage.world(), (EntityId)(Var0.id() + idx));
1382 }
1383
1384 GAIA_NODISCARD static util::str_view normalize_var_name(util::str_view name) {
1385 auto trimmed = util::trim(name);
1386 if (trimmed.empty())
1387 return {};
1388
1389 if (trimmed.data()[0] == '$') {
1390 if (trimmed.size() == 1)
1391 return {};
1392 trimmed = util::str_view(trimmed.data() + 1, trimmed.size() - 1);
1393 }
1394
1395 return util::trim(trimmed);
1396 }
1397
1398 GAIA_NODISCARD static bool is_reserved_var_name(util::str_view varName) {
1399 return varName == "this";
1400 }
1401
1402 GAIA_NODISCARD Entity find_var_by_name(util::str_view rawName) {
1403 const auto varName = normalize_var_name(rawName);
1404 if (varName.empty() || is_reserved_var_name(varName))
1405 return EntityBad;
1406
1407 GAIA_FOR(8) {
1408 const auto bit = (uint8_t(1) << i);
1409 if ((m_varNamesMask & bit) == 0)
1410 continue;
1411 if (m_varNames[i] == varName)
1412 return query_var_entity(i);
1413 }
1414
1415 return EntityBad;
1416 }
1417
1418 bool set_var_name_internal(Entity varEntity, util::str_view rawName) {
1419 if (!is_query_var_entity(varEntity))
1420 return false;
1421
1422 const auto varName = normalize_var_name(rawName);
1423 if (varName.empty() || is_reserved_var_name(varName))
1424 return false;
1425
1426 const auto idx = query_var_idx(varEntity);
1427 const auto bit = (uint8_t(1) << idx);
1428
1429 GAIA_FOR(8) {
1430 if (i == idx)
1431 continue;
1432
1433 const auto otherBit = (uint8_t(1) << i);
1434 if ((m_varNamesMask & otherBit) == 0)
1435 continue;
1436 if (!(m_varNames[i] == varName))
1437 continue;
1438
1439 GAIA_ASSERT2(false, "Variable name is already assigned to a different query variable");
1440 return false;
1441 }
1442
1443 m_varNames[idx].assign(varName);
1444 m_varNamesMask |= bit;
1445 return true;
1446 }
1447
1448 template <typename T>
1449 void add_cmd(T& cmd) {
1450 invalidate_each_walk_cache();
1451
1452 // Make sure to invalidate if necessary.
1453 if constexpr (T::InvalidatesHash) {
1454 reset_changed_filter_state();
1455 m_storage.invalidate();
1456 }
1457
1458 auto& serBuffer = m_storage.ser_buffer();
1459 ser::save(serBuffer, T::Id);
1460 ser::save(serBuffer, T::InvalidatesHash);
1461 ser::save(serBuffer, cmd);
1462 }
1463
1464 void add_inter(QueryInput item) {
1465 // When excluding or using ANY terms make sure the access type is None.
1466 GAIA_ASSERT((item.op != QueryOpKind::Not && item.op != QueryOpKind::Any) || item.access == QueryAccess::None);
1467
1468 QueryCmd_AddItem cmd{item};
1469 add_cmd(cmd);
1470 }
1471
1472 GAIA_NODISCARD static QueryAccess normalize_access(QueryOpKind op, Entity entity, QueryAccess access) {
1473 if (op == QueryOpKind::Not || op == QueryOpKind::Any || entity.pair())
1474 return QueryAccess::None;
1475
1476 // Non-pair ALL/OR terms default to Read access when unspecified.
1477 if (access == QueryAccess::None)
1478 return QueryAccess::Read;
1479
1480 return access;
1481 }
1482
1483 void add_entity_term(QueryOpKind op, Entity entity, const QueryTermOptions& options) {
1484 const auto access = normalize_access(op, entity, options.access);
1485 add(
1486 {op, access, entity, options.entSrc, options.entTrav, options.travKind, options.travDepth,
1487 options.matchKind});
1488 }
1489
1490 template <typename T>
1491 void add_inter(QueryOpKind op) {
1492 Entity e;
1493
1494 if constexpr (is_pair<T>::value) {
1495 // Make sure the components are always registered
1496 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1497 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1498
1499 e = Pair(desc_rel.entity, desc_tgt.entity);
1500 } else {
1501 // Make sure the component is always registered
1502 const auto& desc = comp_cache_add<T>(*m_storage.world());
1503 e = desc.entity;
1504 }
1505
1506 // Determine the access type
1507 QueryAccess access = QueryAccess::None;
1508 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1509 constexpr auto isReadWrite = core::is_mut_v<T>;
1510 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1511 }
1512
1513 add_inter({op, access, e});
1514 }
1515
1516 template <typename T>
1517 void add_inter(QueryOpKind op, const QueryTermOptions& options) {
1518 Entity e;
1519
1520 if constexpr (is_pair<T>::value) {
1521 // Make sure the components are always registered
1522 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1523 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1524
1525 e = Pair(desc_rel.entity, desc_tgt.entity);
1526 } else {
1527 // Make sure the component is always registered
1528 const auto& desc = comp_cache_add<T>(*m_storage.world());
1529 e = desc.entity;
1530 }
1531
1532 QueryAccess access = QueryAccess::None;
1533 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1534 if (options.access != QueryAccess::None)
1535 access = options.access;
1536 else {
1537 constexpr auto isReadWrite = core::is_mut_v<T>;
1538 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1539 }
1540 }
1541
1542 add_inter(
1543 {op, normalize_access(op, e, access), e, options.entSrc, options.entTrav, options.travKind,
1544 options.travDepth, options.matchKind});
1545 }
1546
1547 template <typename Rel, typename Tgt>
1548 void add_inter(QueryOpKind op) {
1549 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1550 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1551 static_assert(core::is_raw_v<UO_Rel>, "Use add() with raw types only");
1552 static_assert(core::is_raw_v<UO_Tgt>, "Use add() with raw types only");
1553
1554 // Make sure the component is always registered
1555 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1556 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1557
1558 // Determine the access type
1559 QueryAccess access = QueryAccess::None;
1560 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1561 constexpr auto isReadWrite = core::is_mut_v<UO_Rel> || core::is_mut_v<UO_Tgt>;
1562 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1563 }
1564
1565 add_inter({op, access, {descRel.entity, descTgt.entity}});
1566 }
1567
1568 //--------------------------------------------------------------------------------
1569
1570 void changed_inter(Entity entity) {
1571 QueryCmd_AddFilter cmd{entity};
1572 add_cmd(cmd);
1573 }
1574
1575 template <typename T>
1576 void changed_inter() {
1577 using UO = typename component_type_t<T>::TypeOriginal;
1578 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1579
1580 // Make sure the component is always registered
1581 const auto& desc = comp_cache_add<T>(*m_storage.world());
1582 changed_inter(desc.entity);
1583 }
1584
1585 template <typename Rel, typename Tgt>
1586 void changed_inter() {
1587 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1588 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1589 static_assert(core::is_raw_v<UO_Rel>, "Use changed() with raw types only");
1590 static_assert(core::is_raw_v<UO_Tgt>, "Use changed() with raw types only");
1591
1592 // Make sure the component is always registered
1593 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1594 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1595 changed_inter({descRel.entity, descTgt.entity});
1596 }
1597
1598 //--------------------------------------------------------------------------------
1599
1600 void sort_by_inter(Entity entity, TSortByFunc func) {
1601 QueryCmd_SortBy cmd{entity, func};
1602 add_cmd(cmd);
1603 }
1604
1605 template <typename T>
1606 void sort_by_inter(TSortByFunc func) {
1607 using UO = typename component_type_t<T>::TypeOriginal;
1608 if constexpr (std::is_same_v<UO, Entity>) {
1609 sort_by_inter(EntityBad, func);
1610 } else {
1611 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1612
1613 // Make sure the component is always registered
1614 const auto& desc = comp_cache_add<T>(*m_storage.world());
1615
1616 sort_by_inter(desc.entity, func);
1617 }
1618 }
1619
1620 template <typename Rel, typename Tgt>
1621 void sort_by_inter(TSortByFunc func) {
1622 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1623 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1624 static_assert(core::is_raw_v<UO_Rel>, "Use group_by() with raw types only");
1625 static_assert(core::is_raw_v<UO_Tgt>, "Use group_by() with raw types only");
1626
1627 // Make sure the component is always registered
1628 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1629 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1630
1631 sort_by_inter({descRel.entity, descTgt.entity}, func);
1632 }
1633
1634 //--------------------------------------------------------------------------------
1635
1636 void group_by_inter(Entity entity, TGroupByFunc func, bool orderGroups = false) {
1637 QueryCmd_GroupBy cmd{entity, func, orderGroups ? (uint16_t)QueryCtx::QueryFlags::OrderGroups : (uint16_t)0};
1638 add_cmd(cmd);
1639 }
1640
1641 template <typename T>
1642 void group_by_inter(Entity entity, TGroupByFunc func) {
1643 using UO = typename component_type_t<T>::TypeOriginal;
1644 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1645
1646 group_by_inter(entity, func);
1647 }
1648
1649 template <typename Rel, typename Tgt>
1650 void group_by_inter(TGroupByFunc func) {
1651 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1652 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1653 static_assert(core::is_raw_v<UO_Rel>, "Use group_by() with raw types only");
1654 static_assert(core::is_raw_v<UO_Tgt>, "Use group_by() with raw types only");
1655
1656 // Make sure the component is always registered
1657 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1658 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1659
1660 group_by_inter({descRel.entity, descTgt.entity}, func);
1661 }
1662
1663 //--------------------------------------------------------------------------------
1664
1665 void group_dep_inter(Entity relation) {
1666 GAIA_ASSERT(!relation.pair());
1667 QueryCmd_GroupDep cmd{relation};
1668 add_cmd(cmd);
1669 }
1670
1671 template <typename T>
1672 void group_dep_inter() {
1673 using UO = typename component_type_t<T>::TypeOriginal;
1674 static_assert(core::is_raw_v<UO>, "Use group_dep() with raw types only");
1675
1676 const auto& desc = comp_cache_add<T>(*m_storage.world());
1677 group_dep_inter(desc.entity);
1678 }
1679
1680 //--------------------------------------------------------------------------------
1681
1682 void set_group_id_inter(GroupId groupId) {
1683 // Dummy usage of GroupIdMax to avoid warning about unused constant
1684 (void)GroupIdMax;
1685
1686 invalidate_each_walk_cache();
1687 m_groupIdSet = groupId;
1688 }
1689
1690 void set_group_id_inter(Entity groupId) {
1691 set_group_id_inter(groupId.id());
1692 }
1693
1694 template <typename T>
1695 void set_group_id_inter() {
1696 using UO = typename component_type_t<T>::TypeOriginal;
1697 static_assert(core::is_raw_v<UO>, "Use group_id() with raw types only");
1698
1699 // Make sure the component is always registered
1700 const auto& desc = comp_cache_add<T>(*m_storage.world());
1701 set_group_id_inter(desc.entity);
1702 }
1703
1704 //--------------------------------------------------------------------------------
1705
1706 void commit(QueryCtx& ctx) {
1707 GAIA_PROF_SCOPE(query::commit);
1708
1709#if GAIA_ASSERT_ENABLED
1710 GAIA_ASSERT(m_storage.m_identity.handle.id() == QueryIdBad);
1711#endif
1712
1713 auto& serBuffer = m_storage.ser_buffer();
1714
1715 // Read data from buffer and execute the command stored in it
1716 serBuffer.seek(0);
1717 while (serBuffer.tell() < serBuffer.bytes()) {
1718 QueryCmdType id{};
1719 bool invalidatesHash = false;
1720 ser::load(serBuffer, id);
1721 ser::load(serBuffer, invalidatesHash);
1722 (void)invalidatesHash; // We don't care about this during commit
1723 CommandBufferRead[id](serBuffer, ctx);
1724 }
1725
1726 // Calculate the lookup hash from the provided context
1727 if (uses_query_cache_storage()) {
1728 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1729 normalize_cache_src_trav(ctx);
1730 }
1731 if (uses_shared_cache_layer()) {
1732 auto& ctxData = ctx.data;
1733 if (ctxData.changedCnt > 1) {
1734 core::sort(ctxData.changed.data(), ctxData.changed.data() + ctxData.changedCnt, SortComponentCond{});
1735 }
1736 }
1737
1738 // We can free all temporary data now
1739 m_storage.ser_buffer_reset();
1740
1741 // Refresh the context
1742 ctx.refresh();
1743 if (uses_shared_cache_layer())
1744 calc_lookup_hash(ctx);
1745 }
1746
1747 void recommit(QueryCtx& ctx) {
1748 GAIA_PROF_SCOPE(query::recommit);
1749
1750 auto& serBuffer = m_storage.ser_buffer();
1751
1752 // Read data from buffer and execute the command stored in it
1753 serBuffer.seek(0);
1754 while (serBuffer.tell() < serBuffer.bytes()) {
1755 QueryCmdType id{};
1756 bool invalidatesHash = false;
1757 ser::load(serBuffer, id);
1758 ser::load(serBuffer, invalidatesHash);
1759 // Hash recalculation is not accepted here
1760 GAIA_ASSERT(!invalidatesHash);
1761 if (invalidatesHash)
1762 return;
1763 CommandBufferRead[id](serBuffer, ctx);
1764 }
1765 if (uses_query_cache_storage()) {
1766 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1767 normalize_cache_src_trav(ctx);
1768 }
1769
1770 // We can free all temporary data now
1771 m_storage.ser_buffer_reset();
1772 }
1773
1774 //--------------------------------------------------------------------------------
1775 public:
1781 GAIA_NODISCARD static bool match_filters(
1782 const Chunk& chunk, const QueryInfo& queryInfo, uint32_t changedWorldVersion,
1783 std::span<const uint8_t> compIndices) {
1784 GAIA_ASSERT(!chunk.empty() && "match_filters called on an empty chunk");
1785
1786 const auto queryVersion = changedWorldVersion;
1787 const auto& data = queryInfo.ctx().data;
1788 if ((data.flags & (QueryCtx::QueryFlags::HasVariableTerms | QueryCtx::QueryFlags::HasSourceTerms)) != 0)
1789 return match_filters(chunk, queryInfo, changedWorldVersion);
1790
1791 const auto changedFields = data.changed_fields_view();
1792
1793 if (changedFields.empty())
1794 return false;
1795
1796 const auto changedCnt = (uint32_t)changedFields.size();
1797 if (changedCnt == 1) {
1798 const auto fieldIdx = changedFields[0];
1799 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1800 if (compIdx == (uint8_t)0xFF)
1801 return match_filters(chunk, queryInfo, changedWorldVersion);
1802 if (chunk.changed(queryVersion, compIdx))
1803 return true;
1804
1805 return chunk.entity_order_changed(changedWorldVersion);
1806 }
1807
1808 GAIA_FOR(changedCnt) {
1809 const auto fieldIdx = changedFields[i];
1810 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1811 if (compIdx == (uint8_t)0xFF)
1812 return match_filters(chunk, queryInfo, changedWorldVersion);
1813 if (chunk.changed(queryVersion, compIdx))
1814 return true;
1815 }
1816
1817 return chunk.entity_order_changed(changedWorldVersion);
1818 }
1819
1824 GAIA_NODISCARD static bool
1825 match_filters(const Chunk& chunk, const QueryInfo& queryInfo, uint32_t changedWorldVersion) {
1826 GAIA_ASSERT(!chunk.empty() && "match_filters called on an empty chunk");
1827
1828 const auto queryVersion = changedWorldVersion;
1829 const auto& filtered = queryInfo.ctx().data.changed_view();
1830
1831 // Skip unchanged chunks
1832 if (filtered.empty())
1833 return false;
1834
1835 const auto filteredCnt = (uint32_t)filtered.size();
1836 auto ids = chunk.ids_view();
1837
1838 // This is the hot path for most change-filter queries.
1839 if (filteredCnt == 1) {
1840 const auto compIdx = core::get_index(ids, filtered[0]);
1841 if (compIdx != BadIndex && chunk.changed(queryVersion, compIdx))
1842 return true;
1843
1844 return chunk.entity_order_changed(changedWorldVersion);
1845 }
1846
1847 // See if any component has changed
1848 uint32_t lastIdx = 0;
1849 for (const auto comp: filtered) {
1850 uint32_t compIdx = BadIndex;
1851 if (lastIdx < (uint32_t)ids.size()) {
1852 const auto suffixIdx =
1853 core::get_index(std::span<const Entity>(ids.data() + lastIdx, ids.size() - lastIdx), comp);
1854 if (suffixIdx != BadIndex)
1855 compIdx = lastIdx + suffixIdx;
1856 }
1857
1858 // Fallback for queries where change-filters are not monotonic in chunk column order
1859 // (e.g. OR-driven layouts).
1860 if (compIdx == BadIndex)
1861 compIdx = core::get_index(ids, comp);
1862 if (compIdx == BadIndex)
1863 continue;
1864
1865 if (chunk.changed(queryVersion, compIdx))
1866 return true;
1867
1868 lastIdx = compIdx;
1869 }
1870
1871 // If none of the tracked components changed, row movement can still make the
1872 // filtered query observable because newly added or moved entities must be seen.
1873 return chunk.entity_order_changed(changedWorldVersion);
1874 }
1875
1876 GAIA_NODISCARD bool can_process_archetype(const QueryInfo& queryInfo, const Archetype& archetype) const {
1877 // Archetypes requested for deletion are skipped for processing.
1878 if (archetype.is_req_del())
1879 return false;
1880
1881 // Prefabs are excluded from query results by default unless the query opted in
1882 // explicitly or it mentions Prefab directly.
1883 if (!queryInfo.matches_prefab_entities() && archetype.has(Prefab))
1884 return false;
1885
1886 return true;
1887 }
1888
1892 GAIA_NODISCARD static bool has_depth_order_hierarchy_enabled_barrier(const QueryInfo& queryInfo) {
1893 const auto& data = queryInfo.ctx().data;
1894 return data.groupByFunc == group_by_func_depth_order &&
1895 world_depth_order_prunes_disabled_subtrees(*queryInfo.world(), data.groupBy);
1896 }
1897
1902 GAIA_NODISCARD static bool
1903 needs_depth_order_hierarchy_barrier_cache(const QueryInfo& queryInfo, Constraints constraints) {
1904 return constraints != Constraints::AcceptAll && has_depth_order_hierarchy_enabled_barrier(queryInfo);
1905 }
1906
1915 Chunk* pChunk, Constraints constraints, bool needsBarrierCache, bool barrierPasses, uint16_t& from,
1916 uint16_t& to) noexcept {
1917 if (needsBarrierCache && constraints == Constraints::DisabledOnly && !barrierPasses) {
1918 from = 0;
1919 to = pChunk->size();
1920 return;
1921 }
1922
1923 from = detail::ChunkIterImpl::start_index(pChunk, constraints);
1924 to = detail::ChunkIterImpl::end_index(pChunk, constraints);
1925 }
1926
1930 GAIA_NODISCARD static bool depth_order_hierarchy_barrier_prunes(const QueryInfo& queryInfo) {
1931 return has_depth_order_hierarchy_enabled_barrier(queryInfo) && queryInfo.barrier_may_prune();
1932 }
1933
1943 GAIA_NODISCARD static bool
1946 return true;
1947
1948 const auto& world = *queryInfo.world();
1949 const auto relation = queryInfo.ctx().data.groupBy;
1950 auto ids = archetype.ids_view();
1951
1952 for (auto idsIdx: archetype.pair_rel_indices(relation)) {
1953 const auto pair = ids[idsIdx];
1954 const auto parent = world_pair_target_if_alive(world, pair);
1955 if (parent == EntityBad)
1956 return false;
1957 if (!world_entity_enabled_hierarchy(world, parent, relation))
1958 return false;
1959 }
1960
1961 return true;
1962 }
1963
1970 GAIA_NODISCARD bool can_process_archetype_inter(
1971 const QueryInfo& queryInfo, const Archetype& archetype, Constraints constraints,
1972 int8_t barrierPasses = -1) const {
1973 if (!can_process_archetype(queryInfo, archetype))
1974 return false;
1975 if (constraints == Constraints::EnabledOnly) {
1977 if (barrierPasses >= 0)
1978 return barrierPasses != 0;
1979 if (!survives_cascade_hierarchy_enabled_barrier(queryInfo, archetype))
1980 return false;
1981 }
1982 }
1983 return true;
1984 }
1985
1986 template <typename TIter>
1987 static void finish_iter_writes(TIter& it) {
1988 if (it.chunk() == nullptr)
1989 return;
1990
1991 auto compIndices = it.touched_comp_indices();
1992 for (auto compIdx: compIndices)
1993 const_cast<Chunk*>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
1994
1995 auto terms = it.touched_terms();
1996 if (terms.empty())
1997 return;
1998
1999 auto entities = it.entity_rows();
2000 auto& world = *it.world();
2001 GAIA_EACH(terms) {
2002 const auto term = terms[i];
2003 if (!world_is_out_of_line_component(world, term)) {
2004 const auto compIdx = core::get_index(it.chunk()->ids_view(), term);
2005 if (compIdx != BadIndex) {
2006 const_cast<Chunk*>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
2007 continue;
2008 }
2009 }
2010
2011 GAIA_FOR_(entities.size(), j) {
2012 world_finish_write(world, term, entities[j]);
2013 }
2014 }
2015 }
2016
2017 static void finish_typed_chunk_writes_runtime(
2018 World& world, Chunk* pChunk, uint16_t from, uint16_t to, const Entity* pArgIds, const bool* pWriteFlags,
2019 uint32_t argCnt, uint32_t firstWriteArg);
2020
2021 template <typename... T>
2022 static void finish_typed_chunk_writes(World& world, Chunk* pChunk, uint16_t from, uint16_t to);
2023
2024 static void finish_typed_iter_writes_runtime(
2025 Iter& it, const Entity* pArgIds, const bool* pWriteFlags, uint32_t argCnt, uint32_t firstWriteArg);
2026
2028 enum class ExecPayloadKind : uint8_t {
2030 Plain,
2032 Grouped,
2035 };
2036
2041 GAIA_NODISCARD static ExecPayloadKind exec_payload_kind(const QueryInfo& queryInfo, Constraints constraints) {
2042 if (queryInfo.has_sorted_payload())
2044 if (!queryInfo.has_grouped_payload())
2046 if (needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints))
2049 }
2050
2056 enum class QueryPlanMode : uint8_t {
2058 Empty,
2060 General,
2062 EntitySeed,
2069 Sorted,
2071 Traversal
2072 };
2073
2094
2108
2110 struct QueryCacheRange final {
2112 uint32_t idxFrom = 0;
2114 uint32_t idxTo = 0;
2116 bool hasSelectedGroup = false;
2118 bool valid = true;
2119 };
2120
2122 struct IterModeEnabled final {};
2124 struct IterModeDisabledOnly final {};
2126 struct IterModeAcceptAll final {};
2127
2131 template <typename TMode>
2132 GAIA_NODISCARD static constexpr Constraints iter_mode_constraints() {
2133 if constexpr (std::is_same_v<TMode, IterModeDisabledOnly>)
2134 return Constraints::DisabledOnly;
2135 else if constexpr (std::is_same_v<TMode, IterModeAcceptAll>)
2136 return Constraints::AcceptAll;
2137 else
2138 return Constraints::EnabledOnly;
2139 }
2140
2141 //--------------------------------------------------------------------------------
2142
2150 template <typename Func, typename TMode>
2151 static void run_query_func(World* pWorld, Func func, ChunkBatch& batch) {
2152 Iter it;
2153 it.init_query_state(pWorld, iter_mode_constraints<TMode>(), false);
2154 it.set_archetype(batch.pArchetype);
2155 it.set_chunk(batch.pChunk, batch.from, batch.to);
2156 it.set_group_id(batch.groupId);
2157 it.set_comp_indices(batch.pCompIndices);
2158 it.set_inherited_data(batch.inheritedData);
2159 func(it);
2160 finish_iter_writes(it);
2161 it.clear_touched_writes();
2162 }
2163
2172 template <typename Func>
2173 static void run_query_arch_func(World* pWorld, Func func, ChunkBatch& batch, Constraints constraints) {
2174 Iter it;
2175 it.init_query_state(pWorld, constraints, false);
2176 it.set_archetype(batch.pArchetype);
2177 // it.set_chunk(nullptr, 0, 0); We do not need this, and calling it would assert
2178 it.set_group_id(batch.groupId);
2179 it.set_comp_indices(batch.pCompIndices);
2180 it.set_inherited_data(batch.inheritedData);
2181 func(it);
2182 it.clear_touched_writes();
2183 }
2184
2192 template <typename Func, typename TMode>
2193 static void run_query_func(World* pWorld, Func func, std::span<ChunkBatch> batches) {
2194 GAIA_PROF_SCOPE(query::run_query_func);
2195
2196 const auto chunkCnt = batches.size();
2197 GAIA_ASSERT(chunkCnt > 0);
2198
2199 Iter it;
2200 it.init_query_state(pWorld, iter_mode_constraints<TMode>(), false);
2201
2202 const Archetype* pLastArchetype = nullptr;
2203 const uint8_t* pLastIndices = nullptr;
2204 InheritedTermDataView lastInheritedData{};
2205 GroupId lastGroupId = GroupIdMax;
2206
2207 const auto apply_batch = [&](const ChunkBatch& batch) {
2208 if (batch.pArchetype != pLastArchetype) {
2209 it.set_archetype(batch.pArchetype);
2210 pLastArchetype = batch.pArchetype;
2211 }
2212
2213 if (batch.pCompIndices != pLastIndices) {
2214 it.set_comp_indices(batch.pCompIndices);
2215 pLastIndices = batch.pCompIndices;
2216 }
2217
2218 if (batch.inheritedData.data() != lastInheritedData.data()) {
2219 it.set_inherited_data(batch.inheritedData);
2220 lastInheritedData = batch.inheritedData;
2221 }
2222
2223 if (batch.groupId != lastGroupId) {
2224 it.set_group_id(batch.groupId);
2225 lastGroupId = batch.groupId;
2226 }
2227
2228 it.set_chunk(batch.pChunk, batch.from, batch.to);
2229 func(it);
2230 finish_iter_writes(it);
2231 it.clear_touched_writes();
2232 };
2233
2234 // We only have one chunk to process.
2235 if GAIA_UNLIKELY (chunkCnt == 1) {
2236 apply_batch(batches[0]);
2237 return;
2238 }
2239
2240 // We have many chunks to process.
2241 // Chunks might be located at different memory locations. Not even in the same memory page.
2242 // Therefore, to make it easier for the CPU we give it a hint that we want to prefetch data
2243 // for the next chunk explicitly so we do not end up stalling later.
2244 // Note, this is a micro optimization and on average it brings no performance benefit. It only
2245 // helps with edge cases.
2246 // Let us be conservative for now and go with T2. That means we will try to keep our data at
2247 // least in L3 cache or higher.
2249 apply_batch(batches[0]);
2250
2251 uint32_t chunkIdx = 1;
2252 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
2253 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
2254 apply_batch(batches[chunkIdx]);
2255 }
2256
2257 apply_batch(batches[chunkIdx]);
2258 }
2259
2260 //------------------------------------------------
2261
2262 template <typename Func, typename TMode>
2264 QueryImpl* pSelf = nullptr;
2265 World* pWorld = nullptr;
2267 Func func;
2268
2269 GAIA_USE_SMALLBLOCK(QueryJobCtx)
2270 };
2271
2272 template <typename Func>
2274 QueryImpl* pSelf = nullptr;
2275 Func func;
2276 QueryExecType execType = QueryExecType::Default;
2277
2278 GAIA_USE_SMALLBLOCK(QueryTaskJobCtx)
2279 };
2280
2283 template <typename Func>
2286 QueryImpl* pSelf = nullptr;
2288 Func func;
2289
2292 void operator()(Iter& it) {
2293 it.ctx(pSelf->ctx());
2294 func(it);
2295 }
2296 };
2297
2300 template <typename Func>
2303 QueryImpl* pSelf = nullptr;
2305 Func func;
2306
2309 void operator()(Iter& it) {
2310 pSelf->each_iter(it, func);
2311 }
2312 };
2313
2314 template <typename Func>
2315 static void invoke_query_task_job(void* pCtx) {
2316 auto& ctx = *reinterpret_cast<QueryTaskJobCtx<Func>*>(pCtx);
2317 ctx.pSelf->each(ctx.func, ctx.execType);
2318 }
2319
2320 template <typename Func>
2321 static void cleanup_query_task_job(void* pCtx) {
2322 auto* pJobCtx = reinterpret_cast<QueryTaskJobCtx<Func>*>(pCtx);
2323 if (pJobCtx == nullptr)
2324 return;
2325 delete pJobCtx;
2326 }
2327
2328 template <typename Func, typename TMode>
2329 static void cleanup_query_job(void* pCtx) {
2330 auto* pJobCtx = reinterpret_cast<QueryJobCtx<Func, TMode>*>(pCtx);
2331 if (pJobCtx == nullptr)
2332 return;
2333
2334 auto* pWorld = pJobCtx->pWorld;
2335 if (pWorld != nullptr) {
2336 unlock(*pWorld);
2337 commit_cmd_buffer_st(*pWorld);
2338 commit_cmd_buffer_mt(*pWorld);
2339 if (pJobCtx->pSelf != nullptr)
2340 pJobCtx->pSelf->m_changedWorldVersion = *pJobCtx->pSelf->m_worldVersion;
2341 }
2342
2343 delete pJobCtx;
2344 }
2345
2346 template <typename Func, typename TMode, QueryExecType ExecType>
2347 GAIA_NODISCARD SchedJob add_parallel_query_job(Func func) {
2348 static_assert(ExecType != QueryExecType::Default);
2349 if (m_batches.empty()) {
2350 m_changedWorldVersion = *m_worldVersion;
2351 return {};
2352 }
2353
2354 auto* pWorld = m_storage.world();
2355 lock(*pWorld);
2356
2357 auto* pCtx = new QueryJobCtx<Func, TMode>{this, pWorld, {}, GAIA_MOV(func)};
2358 pCtx->batches.resize(m_batches.size());
2359 GAIA_EACH(m_batches) pCtx->batches[i] = m_batches[i];
2360 m_batches.clear();
2361
2362 SchedParDesc desc{};
2363 desc.pCtx = pCtx;
2364 desc.itemCount = (uint32_t)pCtx->batches.size();
2365 desc.groupSize = 0;
2366 desc.execType = ExecType;
2367 desc.invoke = [](void* pInvokeCtx, uint32_t idxStart, uint32_t idxEnd) {
2368 auto& ctx = *reinterpret_cast<QueryJobCtx<Func, TMode>*>(pInvokeCtx);
2369 run_query_func<Func, TMode>(ctx.pWorld, ctx.func, std::span(&ctx.batches[idxStart], idxEnd - idxStart));
2370 };
2371
2372 return sched_add_par(world_sched(*pWorld), desc, pCtx, &cleanup_query_job<Func, TMode>);
2373 }
2374
2375 template <bool HasFilters>
2376 void
2377 collect_runtime_parallel_batches(const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints) {
2378 auto cacheView = queryInfo.cache_archetype_view();
2379 const bool hasSortedPlanPayload =
2380 plan.payloadKind == ExecPayloadKind::NonTrivial && (plan.flags & QueryPlanFlag_Sorted) != 0;
2381 const auto sortView =
2382 hasSortedPlanPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
2383 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
2384 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
2385 if (needsBarrierCache)
2386 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2387
2388 if (!sortView.empty()) {
2389 for (const auto& view: sortView) {
2390 const auto* pArchetype = cacheView[view.archetypeIdx];
2391 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2392 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2393 continue;
2394
2395 const auto viewFrom = view.startRow;
2396 const auto viewTo = (uint16_t)(view.startRow + view.count);
2397 uint16_t minStartRow = 0;
2398 uint16_t minEndRow = 0;
2399 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2400 const auto startRow = core::get_max(minStartRow, viewFrom);
2401 const auto endRow = core::get_min(minEndRow, viewTo);
2402 if (endRow == startRow)
2403 continue;
2404
2405 if constexpr (HasFilters) {
2406 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2407 continue;
2408 }
2409
2410 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2411 const auto inheritedDataView =
2412 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2413 m_batches.push_back(
2414 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2415 }
2416 return;
2417 }
2418
2419 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
2420 const auto* pArchetype = cacheView[i];
2421 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2422 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2423 continue;
2424
2425 auto indicesView = queryInfo.indices_mapping_view(i);
2426 const auto inheritedDataView =
2427 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2428 const auto& chunks = pArchetype->chunks();
2429 for (auto* pChunk: chunks) {
2430 uint16_t from = 0;
2431 uint16_t to = 0;
2432 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2433 if GAIA_UNLIKELY (from == to)
2434 continue;
2435
2436 if constexpr (HasFilters) {
2437 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2438 continue;
2439 }
2440
2441 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2442 }
2443 }
2444 }
2445
2446 template <typename Func>
2447 GAIA_NODISCARD SchedJob add_query_task_job(Func func, QueryExecType execType) {
2448 auto* pCtx = new QueryTaskJobCtx<Func>{this, GAIA_MOV(func), execType};
2449
2450 SchedTaskDesc desc{};
2451 desc.pCtx = pCtx;
2452 desc.invoke = &invoke_query_task_job<Func>;
2453 desc.execType = execType;
2454
2455 return sched_add(world_sched(*m_storage.world()), desc, pCtx, &cleanup_query_task_job<Func>);
2456 }
2457
2458 template <typename Func, QueryExecType ExecType>
2459 GAIA_NODISCARD SchedJob add_iter_parallel_job(Func func) {
2460 static_assert(ExecType != QueryExecType::Default);
2461
2462 auto& queryInfo = fetch();
2463 match_all(queryInfo);
2464 const auto constraints = Constraints::EnabledOnly;
2465 const auto plan = prepare_query_plan(queryInfo, constraints);
2466 if (plan.mode == QueryPlanMode::Empty || plan.idxFrom >= plan.idxTo)
2467 return {};
2468 if (plan.mode == QueryPlanMode::EntitySeed)
2469 return add_query_task_job(GAIA_MOV(func), ExecType);
2470
2471 const auto cacheRange = selected_query_cache_range(queryInfo);
2472 if (cacheRange.hasSelectedGroup)
2473 return add_query_task_job(GAIA_MOV(func), ExecType);
2474
2475 ::gaia::ecs::update_version(*m_worldVersion);
2476 m_batches.clear();
2477 if ((plan.flags & QueryPlanFlag_Filtered) != 0)
2478 collect_runtime_parallel_batches<true>(queryInfo, plan, constraints);
2479 else
2480 collect_runtime_parallel_batches<false>(queryInfo, plan, constraints);
2481
2482 using JobFunc = IterJobCallback<Func>;
2483 return add_parallel_query_job<JobFunc, IterModeEnabled, ExecType>(JobFunc{this, GAIA_MOV(func)});
2484 }
2485
2486 //------------------------------------------------
2487
2488 template <bool HasFilters, typename TMode, typename Func>
2489 void run_query_batch_no_group_id(
2490 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2491 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
2492
2493 auto cacheView = queryInfo.cache_archetype_view();
2494 constexpr auto constraints = iter_mode_constraints<TMode>();
2495 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2496 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2497 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2498 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2499 const bool hasSortedBatchPayload =
2500 payloadKind == ExecPayloadKind::NonTrivial && (queryInfo.has_sorted_payload() || needsBarrierCache);
2501 const auto sortView =
2502 hasSortedBatchPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
2503 if (needsBarrierCache)
2504 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2505
2506 lock(*m_storage.world());
2507
2508 // We are batching by chunks. Some of them might contain only few items but this state is only
2509 // temporary because defragmentation runs constantly and keeps things clean.
2510 ChunkBatchArray chunkBatches;
2511
2512 if (!sortView.empty()) {
2513 for (const auto& view: sortView) {
2514 auto* pArchetype = const_cast<Archetype*>(cacheView[view.archetypeIdx]);
2515 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2516 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2517 continue;
2518
2519 const auto viewFrom = view.startRow;
2520 const auto viewTo = (uint16_t)(view.startRow + view.count);
2521 uint16_t minStartRow = 0;
2522 uint16_t minEndRow = 0;
2523 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2524 const auto startRow = core::get_max(minStartRow, viewFrom);
2525 const auto endRow = core::get_min(minEndRow, viewTo);
2526 const auto totalRows = endRow - startRow;
2527 if (totalRows == 0)
2528 continue;
2529
2530 if constexpr (HasFilters) {
2531 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2532 continue;
2533 }
2534
2535 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2536 const auto inheritedDataView =
2537 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2538
2539 chunkBatches.push_back(
2540 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2541
2542 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2543 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2544 chunkBatches.clear();
2545 }
2546 }
2547 } else {
2548 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2549 auto* pArchetype = const_cast<Archetype*>(cacheView[i]);
2550 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2551 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2552 continue;
2553
2554 auto indicesView = queryInfo.indices_mapping_view(i);
2555 const auto inheritedDataView =
2556 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2557 const auto& chunks = pArchetype->chunks();
2558 uint32_t chunkOffset = 0;
2559 uint32_t itemsLeft = chunks.size();
2560 while (itemsLeft > 0) {
2561 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
2562 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
2563
2564 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
2565 for (auto* pChunk: chunkSpan) {
2566 uint16_t from = 0;
2567 uint16_t to = 0;
2568 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2569 if GAIA_UNLIKELY (from == to)
2570 continue;
2571
2572 if constexpr (HasFilters) {
2573 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2574 continue;
2575 }
2576
2577 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2578 }
2579
2580 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2581 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2582 chunkBatches.clear();
2583 }
2584
2585 itemsLeft -= batchSize;
2586 chunkOffset += batchSize;
2587 }
2588 }
2589 }
2590
2591 // Take care of any leftovers not processed during run_query
2592 if (!chunkBatches.empty())
2593 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2594
2595 unlock(*m_storage.world());
2596 // Commit the command buffer.
2597 // TODO: Smart handling necessary
2598 commit_cmd_buffer_st(*m_storage.world());
2599 commit_cmd_buffer_mt(*m_storage.world());
2600 }
2601
2602 template <bool HasFilters, typename TMode, typename Func, QueryExecType ExecType>
2603 void run_query_batch_no_group_id_par(
2604 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2605 static_assert(ExecType != QueryExecType::Default);
2606 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
2607
2608 auto cacheView = queryInfo.cache_archetype_view();
2609 constexpr auto constraints = iter_mode_constraints<TMode>();
2610 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2611 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2612 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2613 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2614 const bool hasSortedBatchPayload =
2615 payloadKind == ExecPayloadKind::NonTrivial && (queryInfo.has_sorted_payload() || needsBarrierCache);
2616 const auto sortView =
2617 hasSortedBatchPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
2618 if (needsBarrierCache)
2619 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2620
2621 if (!sortView.empty()) {
2622 for (const auto& view: sortView) {
2623 const auto* pArchetype = cacheView[view.archetypeIdx];
2624 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2625 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2626 continue;
2627
2628 const auto viewFrom = view.startRow;
2629 const auto viewTo = (uint16_t)(view.startRow + view.count);
2630 uint16_t minStartRow = 0;
2631 uint16_t minEndRow = 0;
2632 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2633 const auto startRow = core::get_max(minStartRow, viewFrom);
2634 const auto endRow = core::get_min(minEndRow, viewTo);
2635 const auto totalRows = endRow - startRow;
2636 if (totalRows == 0)
2637 continue;
2638
2639 if constexpr (HasFilters) {
2640 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2641 continue;
2642 }
2643
2644 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2645 const auto inheritedDataView =
2646 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2647
2648 m_batches.push_back(
2649 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2650 }
2651 } else {
2652 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2653 const auto* pArchetype = cacheView[i];
2654 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2655 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2656 continue;
2657
2658 auto indicesView = queryInfo.indices_mapping_view(i);
2659 const auto inheritedDataView =
2660 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2661 const auto& chunks = pArchetype->chunks();
2662 for (auto* pChunk: chunks) {
2663 uint16_t from = 0;
2664 uint16_t to = 0;
2665 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2666 if GAIA_UNLIKELY (from == to)
2667 continue;
2668
2669 if constexpr (HasFilters) {
2670 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2671 continue;
2672 }
2673
2674 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2675 }
2676 }
2677 }
2678
2679 if (m_batches.empty())
2680 return;
2681
2682 lock(*m_storage.world());
2683
2684 struct ParallelQueryBatchCtx {
2685 QueryImpl* pSelf;
2686 Func* pFunc;
2687 };
2688 ParallelQueryBatchCtx ctx{this, &func};
2689 SchedParDesc desc{};
2690 desc.pCtx = &ctx;
2691 desc.itemCount = (uint32_t)m_batches.size();
2692 desc.groupSize = 0;
2693 desc.execType = ExecType;
2694 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
2695 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
2696 run_query_func<Func, TMode>(
2697 ctx.pSelf->m_storage.world(), *ctx.pFunc,
2698 std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
2699 };
2700
2701 const auto& sched = world_sched(*m_storage.world());
2702 const auto token = sched_par(sched, desc);
2703 sched_wait(sched, token);
2704 sched_del(sched, token);
2705 m_batches.clear();
2706
2707 unlock(*m_storage.world());
2708 // Commit the command buffer.
2709 // TODO: Smart handling necessary
2710 commit_cmd_buffer_st(*m_storage.world());
2711 commit_cmd_buffer_mt(*m_storage.world());
2712 }
2713
2714 template <bool HasFilters, typename TMode, typename Func>
2715 void run_query_batch_with_group_id(
2716 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2717 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
2718
2719 ChunkBatchArray chunkBatches;
2720
2721 auto cacheView = queryInfo.cache_archetype_view();
2722 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2723 constexpr auto constraints = iter_mode_constraints<TMode>();
2724 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2725 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2726 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2727 if (needsBarrierCache)
2728 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2729
2730 lock(*m_storage.world());
2731
2732 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2733 const auto* pArchetype = cacheView[i];
2734 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2735 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2736 continue;
2737
2738 auto indicesView = queryInfo.indices_mapping_view(i);
2739 const auto inheritedDataView =
2740 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2741 const auto& chunks = pArchetype->chunks();
2742 const auto groupId = queryInfo.group_id(i);
2743
2744#if GAIA_ASSERT_ENABLED
2745 GAIA_ASSERT(
2746 // ... or no groupId is set...
2747 m_groupIdSet == 0 ||
2748 // ... or the groupId must match the requested one
2749 groupId == m_groupIdSet);
2750#endif
2751
2752 uint32_t chunkOffset = 0;
2753 uint32_t itemsLeft = chunks.size();
2754 while (itemsLeft > 0) {
2755 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
2756 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
2757
2758 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
2759 for (auto* pChunk: chunkSpan) {
2760 uint16_t from = 0;
2761 uint16_t to = 0;
2762 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2763 if GAIA_UNLIKELY (from == to)
2764 continue;
2765
2766 if constexpr (HasFilters) {
2767 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2768 continue;
2769 }
2770
2771 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
2772 }
2773
2774 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2775 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2776 chunkBatches.clear();
2777 }
2778
2779 itemsLeft -= batchSize;
2780 chunkOffset += batchSize;
2781 }
2782 }
2783
2784 // Take care of any leftovers not processed during run_query
2785 if (!chunkBatches.empty())
2786 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2787
2788 unlock(*m_storage.world());
2789 // Commit the command buffer.
2790 // TODO: Smart handling necessary
2791 commit_cmd_buffer_st(*m_storage.world());
2792 commit_cmd_buffer_mt(*m_storage.world());
2793 }
2794
2795 template <bool HasFilters, typename TMode, typename Func, QueryExecType ExecType>
2796 void run_query_batch_with_group_id_par(
2797 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2798 static_assert(ExecType != QueryExecType::Default);
2799 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
2800
2801 ChunkBatchArray chunkBatch;
2802
2803 auto cacheView = queryInfo.cache_archetype_view();
2804 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2805 constexpr auto constraints = iter_mode_constraints<TMode>();
2806 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2807 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2808 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2809 if (needsBarrierCache)
2810 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2811
2812#if GAIA_ASSERT_ENABLED
2813 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2814 const auto* pArchetype = cacheView[i];
2815 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2816 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2817 continue;
2818
2819 const auto groupId = queryInfo.group_id(i);
2820 GAIA_ASSERT(
2821 // ... or no groupId is set...
2822 m_groupIdSet == 0 ||
2823 // ... or the groupId must match the requested one
2824 groupId == m_groupIdSet);
2825 }
2826#endif
2827
2828 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2829 const Archetype* pArchetype = cacheView[i];
2830 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2831 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2832 continue;
2833
2834 auto indicesView = queryInfo.indices_mapping_view(i);
2835 const auto inheritedDataView =
2836 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2837 const auto groupId = queryInfo.group_id(i);
2838 const auto& chunks = pArchetype->chunks();
2839 for (auto* pChunk: chunks) {
2840 uint16_t from = 0;
2841 uint16_t to = 0;
2842 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2843 if GAIA_UNLIKELY (from == to)
2844 continue;
2845
2846 if constexpr (HasFilters) {
2847 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2848 continue;
2849 }
2850
2851 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
2852 }
2853 }
2854
2855 if (m_batches.empty())
2856 return;
2857
2858 lock(*m_storage.world());
2859
2860 struct ParallelQueryBatchCtx {
2861 QueryImpl* pSelf;
2862 Func* pFunc;
2863 };
2864 ParallelQueryBatchCtx ctx{this, &func};
2865 SchedParDesc desc{};
2866 desc.pCtx = &ctx;
2867 desc.itemCount = (uint32_t)m_batches.size();
2868 desc.groupSize = 0;
2869 desc.execType = ExecType;
2870 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
2871 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
2872 run_query_func<Func, TMode>(
2873 ctx.pSelf->m_storage.world(), *ctx.pFunc,
2874 std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
2875 };
2876
2877 const auto& sched = world_sched(*m_storage.world());
2878 const auto token = sched_par(sched, desc);
2879 sched_wait(sched, token);
2880 sched_del(sched, token);
2881 m_batches.clear();
2882
2883 unlock(*m_storage.world());
2884 // Commit the command buffer.
2885 // TODO: Smart handling necessary
2886 commit_cmd_buffer_st(*m_storage.world());
2887 commit_cmd_buffer_mt(*m_storage.world());
2888 }
2889
2890 //------------------------------------------------
2891
2892 template <bool HasFilters, QueryExecType ExecType, typename TMode, typename Func>
2893 void run_query(const QueryInfo& queryInfo, Func func) {
2894 GAIA_PROF_SCOPE(query::run_query);
2895
2896 // TODO: Have archetype cache as double-linked list with pointers only.
2897 // Have chunk cache as double-linked list with pointers only.
2898 // Make it so only valid pointers are linked together.
2899 // This means one less indirection + we won't need to call can_process_archetype()
2900 // or pChunk.size()==0 in run_query_batch functions.
2901 auto cache_view = queryInfo.cache_archetype_view();
2902 if (cache_view.empty())
2903 return;
2904
2905 const auto cacheRange = selected_query_cache_range(queryInfo);
2906 if (!cacheRange.hasSelectedGroup) {
2907 // No group requested or group filtering is currently turned off
2908 if constexpr (ExecType != QueryExecType::Default)
2909 run_query_batch_no_group_id_par<HasFilters, TMode, Func, ExecType>(
2910 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
2911 else
2912 run_query_batch_no_group_id<HasFilters, TMode, Func>(
2913 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
2914 } else {
2915 // We wish to iterate only a certain group
2916 if (!cacheRange.valid)
2917 return;
2918
2919 if constexpr (ExecType != QueryExecType::Default)
2920 run_query_batch_with_group_id_par<HasFilters, TMode, Func, ExecType>(
2921 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
2922 else
2923 run_query_batch_with_group_id<HasFilters, TMode, Func>(
2924 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
2925 }
2926 }
2927
2928 //------------------------------------------------
2929
2930 template <QueryExecType ExecType, typename Func>
2931 void run_query_on_archetypes(QueryInfo& queryInfo, Func func, Constraints constraints) {
2932 // Update the world version
2933 // We do read-only access. No need to update the version
2934 //::gaia::ecs::update_version(*m_worldVersion);
2935 lock(*m_storage.world());
2936
2937 {
2938 GAIA_PROF_SCOPE(query::run_query_a);
2939
2940 // TODO: Have archetype cache as double-linked list with pointers only.
2941 // Have chunk cache as double-linked list with pointers only.
2942 // Make it so only valid pointers are linked together.
2943 // This means one less indirection + we won't need to call can_process_archetype().
2944 auto cache_view = queryInfo.cache_archetype_view();
2945 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2946 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2947 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2948 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2949 if (needsBarrierCache)
2950 queryInfo.ensure_depth_order_hierarchy_barrier_cache();
2951 GAIA_EACH(cache_view) {
2952 const auto* pArchetype = cache_view[i];
2953 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2954 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2955 continue;
2956
2957 auto indicesView = queryInfo.indices_mapping_view(i);
2958 const auto inheritedDataView =
2959 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2960 ChunkBatch batch{pArchetype, nullptr, indicesView.data(), inheritedDataView, 0, 0, 0};
2961 run_query_arch_func(m_storage.world(), func, batch, constraints);
2962 }
2963 }
2964
2965 unlock(*m_storage.world());
2966 // Changed-filter state is instance-local for cached queries.
2967 }
2968
2969 //------------------------------------------------
2970
2971 template <QueryExecType ExecType, typename TMode, typename Func>
2972 void run_query_on_chunks(QueryInfo& queryInfo, Func func) {
2973 // Update the world version
2974 ::gaia::ecs::update_version(*m_worldVersion);
2975
2976 const bool hasFilters = queryInfo.has_filters();
2977 if (hasFilters)
2978 run_query<true, ExecType, TMode>(queryInfo, func);
2979 else
2980 run_query<false, ExecType, TMode>(queryInfo, func);
2981
2982 // Changed-filter state is instance-local for cached queries.
2983 m_changedWorldVersion = *m_worldVersion;
2984 }
2985
2986 template <typename Func>
2987 static void
2988 run_query_func_runtime(World* pWorld, Func func, std::span<ChunkBatch> batches, Constraints constraints) {
2989 GAIA_PROF_SCOPE(query::run_query_func);
2990
2991 const auto chunkCnt = batches.size();
2992 GAIA_ASSERT(chunkCnt > 0);
2993
2994 Iter it;
2995 it.init_query_state(pWorld, constraints, false);
2996
2997 const Archetype* pLastArchetype = nullptr;
2998 const uint8_t* pLastIndices = nullptr;
2999 InheritedTermDataView lastInheritedData{};
3000 GroupId lastGroupId = GroupIdMax;
3001
3002 const auto apply_batch = [&](const ChunkBatch& batch) {
3003 if (batch.pArchetype != pLastArchetype) {
3004 it.set_archetype(batch.pArchetype);
3005 pLastArchetype = batch.pArchetype;
3006 }
3007
3008 if (batch.pCompIndices != pLastIndices) {
3009 it.set_comp_indices(batch.pCompIndices);
3010 pLastIndices = batch.pCompIndices;
3011 }
3012
3013 if (batch.inheritedData.data() != lastInheritedData.data()) {
3014 it.set_inherited_data(batch.inheritedData);
3015 lastInheritedData = batch.inheritedData;
3016 }
3017
3018 if (batch.groupId != lastGroupId) {
3019 it.set_group_id(batch.groupId);
3020 lastGroupId = batch.groupId;
3021 }
3022
3023 it.set_chunk(batch.pChunk, batch.from, batch.to);
3024 func(it);
3025 finish_iter_writes(it);
3026 it.clear_touched_writes();
3027 };
3028
3029 if GAIA_UNLIKELY (chunkCnt == 1) {
3030 apply_batch(batches[0]);
3031 return;
3032 }
3033
3035 apply_batch(batches[0]);
3036
3037 uint32_t chunkIdx = 1;
3038 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
3039 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
3040 apply_batch(batches[chunkIdx]);
3041 }
3042
3043 apply_batch(batches[chunkIdx]);
3044 }
3045
3046 template <bool HasFilters, typename Func>
3047 void run_query_batch_no_group_id_runtime(
3048 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3049 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
3050
3051 auto cacheView = queryInfo.cache_archetype_view();
3052 const bool hasSortedPlanPayload =
3053 plan.payloadKind == ExecPayloadKind::NonTrivial && (plan.flags & QueryPlanFlag_Sorted) != 0;
3054 const auto sortView =
3055 hasSortedPlanPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
3056 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3057 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3058 if (needsBarrierCache)
3059 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3060
3061 lock(*m_storage.world());
3062 ChunkBatchArray chunkBatches;
3063
3064 if (!sortView.empty()) {
3065 for (const auto& view: sortView) {
3066 auto* pArchetype = const_cast<Archetype*>(cacheView[view.archetypeIdx]);
3067 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3068 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3069 continue;
3070
3071 const auto viewFrom = view.startRow;
3072 const auto viewTo = (uint16_t)(view.startRow + view.count);
3073 uint16_t minStartRow = 0;
3074 uint16_t minEndRow = 0;
3075 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3076 const auto startRow = core::get_max(minStartRow, viewFrom);
3077 const auto endRow = core::get_min(minEndRow, viewTo);
3078 const auto totalRows = endRow - startRow;
3079 if (totalRows == 0)
3080 continue;
3081
3082 if constexpr (HasFilters) {
3083 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3084 continue;
3085 }
3086
3087 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3088 const auto inheritedDataView =
3089 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3090
3091 chunkBatches.push_back(
3092 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
3093
3094 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3095 run_query_func_runtime(
3096 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3097 chunkBatches.clear();
3098 }
3099 }
3100 } else {
3101 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3102 auto* pArchetype = const_cast<Archetype*>(cacheView[i]);
3103 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3104 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3105 continue;
3106
3107 auto indicesView = queryInfo.indices_mapping_view(i);
3108 const auto inheritedDataView =
3109 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3110 const auto& chunks = pArchetype->chunks();
3111 uint32_t chunkOffset = 0;
3112 uint32_t itemsLeft = chunks.size();
3113 while (itemsLeft > 0) {
3114 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3115 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3116
3117 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3118 for (auto* pChunk: chunkSpan) {
3119 uint16_t from = 0;
3120 uint16_t to = 0;
3121 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3122 if GAIA_UNLIKELY (from == to)
3123 continue;
3124
3125 if constexpr (HasFilters) {
3126 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3127 continue;
3128 }
3129
3130 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
3131 }
3132
3133 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3134 run_query_func_runtime(
3135 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3136 chunkBatches.clear();
3137 }
3138
3139 itemsLeft -= batchSize;
3140 chunkOffset += batchSize;
3141 }
3142 }
3143 }
3144
3145 if (!chunkBatches.empty())
3146 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3147
3148 unlock(*m_storage.world());
3149 commit_cmd_buffer_st(*m_storage.world());
3150 commit_cmd_buffer_mt(*m_storage.world());
3151 }
3152
3153 template <bool HasFilters, typename Func, QueryExecType ExecType>
3154 void run_query_batch_no_group_id_runtime_par(
3155 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3156 static_assert(ExecType != QueryExecType::Default);
3157 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
3158
3159 auto cacheView = queryInfo.cache_archetype_view();
3160 const bool hasSortedPlanPayload =
3161 plan.payloadKind == ExecPayloadKind::NonTrivial && (plan.flags & QueryPlanFlag_Sorted) != 0;
3162 const auto sortView =
3163 hasSortedPlanPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
3164 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3165 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3166 if (needsBarrierCache)
3167 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3168
3169 if (!sortView.empty()) {
3170 for (const auto& view: sortView) {
3171 const auto* pArchetype = cacheView[view.archetypeIdx];
3172 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3173 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3174 continue;
3175
3176 const auto viewFrom = view.startRow;
3177 const auto viewTo = (uint16_t)(view.startRow + view.count);
3178 uint16_t minStartRow = 0;
3179 uint16_t minEndRow = 0;
3180 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3181 const auto startRow = core::get_max(minStartRow, viewFrom);
3182 const auto endRow = core::get_min(minEndRow, viewTo);
3183 const auto totalRows = endRow - startRow;
3184 if (totalRows == 0)
3185 continue;
3186
3187 if constexpr (HasFilters) {
3188 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3189 continue;
3190 }
3191
3192 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3193 const auto inheritedDataView =
3194 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3195
3196 m_batches.push_back(
3197 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
3198 }
3199 } else {
3200 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3201 const auto* pArchetype = cacheView[i];
3202 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3203 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3204 continue;
3205
3206 auto indicesView = queryInfo.indices_mapping_view(i);
3207 const auto inheritedDataView =
3208 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3209 const auto& chunks = pArchetype->chunks();
3210 for (auto* pChunk: chunks) {
3211 uint16_t from = 0;
3212 uint16_t to = 0;
3213 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3214 if GAIA_UNLIKELY (from == to)
3215 continue;
3216
3217 if constexpr (HasFilters) {
3218 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3219 continue;
3220 }
3221
3222 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
3223 }
3224 }
3225 }
3226
3227 if (m_batches.empty())
3228 return;
3229
3230 lock(*m_storage.world());
3231
3232 struct ParallelQueryBatchCtx {
3233 QueryImpl* pSelf;
3234 Func* pFunc;
3235 Constraints constraints;
3236 };
3237 ParallelQueryBatchCtx ctx{this, &func, constraints};
3238 SchedParDesc desc{};
3239 desc.pCtx = &ctx;
3240 desc.itemCount = (uint32_t)m_batches.size();
3241 desc.groupSize = 0;
3242 desc.execType = ExecType;
3243 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3244 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
3245 run_query_func_runtime(
3246 ctx.pSelf->m_storage.world(), *ctx.pFunc, std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3247 ctx.constraints);
3248 };
3249
3250 const auto& sched = world_sched(*m_storage.world());
3251 const auto token = sched_par(sched, desc);
3252 sched_wait(sched, token);
3253 sched_del(sched, token);
3254 m_batches.clear();
3255
3256 unlock(*m_storage.world());
3257 commit_cmd_buffer_st(*m_storage.world());
3258 commit_cmd_buffer_mt(*m_storage.world());
3259 }
3260
3261 template <bool HasFilters, typename Func>
3262 void run_query_batch_with_group_id_runtime(
3263 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3264 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
3265
3266 ChunkBatchArray chunkBatches;
3267 auto cacheView = queryInfo.cache_archetype_view();
3268 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3269 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3270 if (needsBarrierCache)
3271 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3272
3273 lock(*m_storage.world());
3274
3275 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3276 const auto* pArchetype = cacheView[i];
3277 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3278 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3279 continue;
3280
3281 auto indicesView = queryInfo.indices_mapping_view(i);
3282 const auto inheritedDataView =
3283 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3284 const auto& chunks = pArchetype->chunks();
3285 const auto groupId = queryInfo.group_id(i);
3286
3287 uint32_t chunkOffset = 0;
3288 uint32_t itemsLeft = chunks.size();
3289 while (itemsLeft > 0) {
3290 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3291 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3292
3293 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3294 for (auto* pChunk: chunkSpan) {
3295 uint16_t from = 0;
3296 uint16_t to = 0;
3297 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3298 if GAIA_UNLIKELY (from == to)
3299 continue;
3300
3301 if constexpr (HasFilters) {
3302 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3303 continue;
3304 }
3305
3306 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3307 }
3308
3309 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3310 run_query_func_runtime(
3311 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3312 chunkBatches.clear();
3313 }
3314
3315 itemsLeft -= batchSize;
3316 chunkOffset += batchSize;
3317 }
3318 }
3319
3320 if (!chunkBatches.empty())
3321 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3322
3323 unlock(*m_storage.world());
3324 commit_cmd_buffer_st(*m_storage.world());
3325 commit_cmd_buffer_mt(*m_storage.world());
3326 }
3327
3328 template <bool HasFilters, typename Func, QueryExecType ExecType>
3329 void run_query_batch_with_group_id_runtime_par(
3330 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3331 static_assert(ExecType != QueryExecType::Default);
3332 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
3333
3334 ChunkBatchArray chunkBatch;
3335 auto cacheView = queryInfo.cache_archetype_view();
3336 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3337 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3338 if (needsBarrierCache)
3339 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3340
3341 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3342 const auto* pArchetype = cacheView[i];
3343 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3344 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3345 continue;
3346
3347 auto indicesView = queryInfo.indices_mapping_view(i);
3348 const auto inheritedDataView =
3349 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3350 const auto groupId = queryInfo.group_id(i);
3351 const auto& chunks = pArchetype->chunks();
3352 for (auto* pChunk: chunks) {
3353 uint16_t from = 0;
3354 uint16_t to = 0;
3355 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3356 if GAIA_UNLIKELY (from == to)
3357 continue;
3358
3359 if constexpr (HasFilters) {
3360 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3361 continue;
3362 }
3363
3364 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3365 }
3366 }
3367
3368 if (m_batches.empty())
3369 return;
3370
3371 lock(*m_storage.world());
3372
3373 struct ParallelQueryBatchCtx {
3374 QueryImpl* pSelf;
3375 Func* pFunc;
3376 Constraints constraints;
3377 };
3378 ParallelQueryBatchCtx ctx{this, &func, constraints};
3379 SchedParDesc desc{};
3380 desc.pCtx = &ctx;
3381 desc.itemCount = (uint32_t)m_batches.size();
3382 desc.groupSize = 0;
3383 desc.execType = ExecType;
3384 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3385 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
3386 run_query_func_runtime(
3387 ctx.pSelf->m_storage.world(), *ctx.pFunc, std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3388 ctx.constraints);
3389 };
3390
3391 const auto& sched = world_sched(*m_storage.world());
3392 const auto token = sched_par(sched, desc);
3393 sched_wait(sched, token);
3394 sched_del(sched, token);
3395 m_batches.clear();
3396
3397 unlock(*m_storage.world());
3398 commit_cmd_buffer_st(*m_storage.world());
3399 commit_cmd_buffer_mt(*m_storage.world());
3400 }
3401
3402 template <bool HasFilters, QueryExecType ExecType, typename Func>
3403 void run_query_runtime_planned(
3404 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3405 GAIA_PROF_SCOPE(query::run_query);
3406 if (plan.mode == QueryPlanMode::Empty || plan.idxFrom >= plan.idxTo)
3407 return;
3408
3409 const auto cacheRange = selected_query_cache_range(queryInfo);
3410 if (!cacheRange.hasSelectedGroup) {
3411 if constexpr (ExecType != QueryExecType::Default)
3412 run_query_batch_no_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3413 else
3414 run_query_batch_no_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3415 } else {
3416 if constexpr (ExecType != QueryExecType::Default)
3417 run_query_batch_with_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3418 else
3419 run_query_batch_with_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3420 }
3421 }
3422
3430 template <QueryExecType ExecType, typename Func>
3432 QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3433 if (plan.mode == QueryPlanMode::Empty)
3434 return;
3435
3436 ::gaia::ecs::update_version(*m_worldVersion);
3437 if ((plan.flags & QueryPlanFlag_Filtered) != 0)
3438 run_query_runtime_planned<true, ExecType>(queryInfo, plan, constraints, func);
3439 else
3440 run_query_runtime_planned<false, ExecType>(queryInfo, plan, constraints, func);
3441
3442 m_changedWorldVersion = *m_worldVersion;
3443 }
3444
3449 GAIA_NODISCARD bool can_use_direct_chunk_iteration_fastpath(const QueryInfo& queryInfo) const {
3450 const auto& data = queryInfo.ctx().data;
3451 return data.sortByFunc == nullptr &&
3453 }
3454
3458 GAIA_NODISCARD QueryCacheRange selected_query_cache_range(const QueryInfo& queryInfo) const {
3459 QueryCacheRange range{};
3460 range.idxTo = (uint32_t)queryInfo.cache_archetype_view().size();
3461
3462 const auto& data = queryInfo.ctx().data;
3463 if (data.groupBy == EntityBad || m_groupIdSet == 0)
3464 return range;
3465
3466 range.hasSelectedGroup = true;
3467 const auto* pGroupData = queryInfo.selected_group_data(m_groupIdSet);
3468 if (pGroupData == nullptr) {
3469 range.idxFrom = 0;
3470 range.idxTo = 0;
3471 range.valid = false;
3472 return range;
3473 }
3474
3475 range.idxFrom = pGroupData->idxFirst;
3476 range.idxTo = pGroupData->idxLast + 1;
3477 return range;
3478 }
3479
3484 GAIA_NODISCARD QueryPlan prepare_query_plan(const QueryInfo& queryInfo, const TypedQueryExecState& state) const;
3485
3486 template <bool HasFilters, typename Func, typename... T>
3488 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, Func& func,
3490
3491 void run_query_on_chunks_direct(
3492 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, void* pFunc,
3493 void (*runChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&));
3494
3495 void run_query_on_chunks_direct_iter(
3496 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, void* pFunc,
3497 void (*runChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&));
3498
3499 template <QueryExecType ExecType>
3500 void each_inter(
3501 QueryInfo& queryInfo, const QueryPlan& plan, void* pFunc, const TypedQueryExecState& state,
3502 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
3503 void (*runDirectChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
3504 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&),
3505 bool needsInheritedArgIds, void (*invokeInherited)(World&, Entity, const Entity*, void*));
3506
3507 void each_typed_erased(
3508 QueryExecType execType, void* pFunc, const TypedQueryExecState& state,
3509 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
3510 void (*runDirectChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
3511 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&),
3512 bool needsInheritedArgIds, void (*invokeInherited)(World&, Entity, const Entity*, void*));
3513
3514 template <QueryExecType ExecType, typename Func>
3515 void each_typed_inter(QueryInfo& queryInfo, Func func);
3516
3517 template <QueryExecType ExecType>
3518 void each_iter_inter_erased(
3519 QueryInfo& queryInfo, const QueryPlan& plan, void* pFunc, const TypedQueryExecState& state,
3520 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
3521 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
3522
3523 void each_walk_inter(
3524 QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints, void* pFunc,
3525 const TypedQueryExecState& state,
3526 void (*runChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
3527
3532 GAIA_NODISCARD QueryPlan prepare_query_plan(const QueryInfo& queryInfo, Constraints constraints) const {
3533 QueryPlan plan{};
3534 const auto cacheRange = selected_query_cache_range(queryInfo);
3535 plan.idxFrom = cacheRange.idxFrom;
3536 plan.idxTo = cacheRange.idxTo;
3537 const bool hasFilters = queryInfo.has_filters();
3538 const bool hasSortedPayload = queryInfo.has_sorted_payload();
3539 const bool hasDepthOrderBarrier = has_depth_order_hierarchy_enabled_barrier(queryInfo);
3540 bool hasConstrainedDepthOrderBarrier = constraints != Constraints::AcceptAll && hasDepthOrderBarrier;
3541 if (hasFilters)
3542 plan.flags |= QueryPlanFlag_Filtered;
3543 if (queryInfo.has_entity_filter_terms())
3544 plan.flags |= QueryPlanFlag_EntityFilter;
3545 if (queryInfo.has_inherited_data_payload())
3546 plan.flags |= QueryPlanFlag_InheritedPayload;
3547 if (queryInfo.has_grouped_payload())
3548 plan.flags |= QueryPlanFlag_Grouped;
3549 if (hasSortedPayload || hasDepthOrderBarrier)
3550 plan.flags |= QueryPlanFlag_Sorted;
3551 plan.payloadKind = exec_payload_kind(queryInfo, constraints);
3552
3553 if (cacheRange.hasSelectedGroup) {
3554 plan.flags |= QueryPlanFlag_Grouped;
3555 plan.payloadKind = ExecPayloadKind::Grouped;
3556 if (!cacheRange.valid) {
3557 plan.mode = QueryPlanMode::Empty;
3558 return plan;
3559 }
3560 }
3561
3562 if ((plan.flags & QueryPlanFlag_Filtered) == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
3563 plan.mode = QueryPlanMode::EntitySeed;
3564 return plan;
3565 }
3566
3567 if (plan.idxFrom >= plan.idxTo) {
3568 plan.mode = QueryPlanMode::Empty;
3569 return plan;
3570 }
3571
3572 if (hasConstrainedDepthOrderBarrier && !depth_order_hierarchy_barrier_prunes(queryInfo)) {
3573 hasConstrainedDepthOrderBarrier = false;
3574 if (!hasSortedPayload && (plan.flags & QueryPlanFlag_InheritedPayload) == 0)
3575 plan.payloadKind = queryInfo.has_grouped_payload() ? ExecPayloadKind::Grouped : ExecPayloadKind::Plain;
3576 }
3577 if (hasConstrainedDepthOrderBarrier)
3578 plan.flags |= QueryPlanFlag_BarrierCache;
3579
3580 if (hasSortedPayload) {
3581 plan.mode = QueryPlanMode::Sorted;
3582 return plan;
3583 }
3584
3585 if (hasConstrainedDepthOrderBarrier || (plan.flags & QueryPlanFlag_InheritedPayload) != 0) {
3586 plan.mode = QueryPlanMode::Traversal;
3587 return plan;
3588 }
3589
3590 if ((plan.flags & QueryPlanFlag_EntityFilter) != 0)
3591 return plan;
3592
3593 if (plan.payloadKind != ExecPayloadKind::Plain) {
3594 if (plan.payloadKind != ExecPayloadKind::Grouped || !hasDepthOrderBarrier)
3595 return plan;
3596 if (hasConstrainedDepthOrderBarrier)
3597 return plan;
3599 return plan;
3600
3601 plan.mode = QueryPlanMode::DirectDense;
3602 return plan;
3603 }
3604
3606 return plan;
3607
3608 plan.mode = QueryPlanMode::DirectDense;
3609 return plan;
3610 }
3611
3620 template <bool HasFilters, bool HasGroups, typename Func>
3622 QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func& func) {
3623 ::gaia::ecs::update_version(*m_worldVersion);
3624
3625 auto cacheView = queryInfo.cache_archetype_view();
3626 lock(*m_storage.world());
3627
3628 Iter it;
3629 it.init_query_state(queryInfo.world(), constraints, false);
3630
3631 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3632 auto* pArchetype = const_cast<Archetype*>(cacheView[i]);
3633 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints))
3634 continue;
3635
3636 auto indicesView = queryInfo.indices_mapping_view(i);
3637 const auto* pIndices = indicesView.data();
3638 const auto groupId = HasGroups ? queryInfo.group_id(i) : GroupId(0);
3639 const auto& chunks = pArchetype->chunks();
3640 for (auto* pChunk: chunks) {
3641 const auto from = detail::ChunkIterImpl::start_index(pChunk, constraints);
3642 const auto to = detail::ChunkIterImpl::end_index(pChunk, constraints);
3643 if GAIA_UNLIKELY (from == to)
3644 continue;
3645 if constexpr (HasFilters) {
3646 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion, indicesView))
3647 continue;
3648 }
3649
3650 it.set_query_chunk(pArchetype, pIndices, pChunk, from, to);
3651 if constexpr (HasGroups)
3652 it.set_group_id(groupId);
3653 it.ctx(m_ctx);
3654 {
3655 GAIA_PROF_SCOPE(query_func);
3656 func(it);
3657 }
3658 finish_iter_writes(it);
3659 it.clear_touched_writes();
3660 }
3661 }
3662
3663 unlock(*m_storage.world());
3664 commit_cmd_buffer_st(*m_storage.world());
3665 commit_cmd_buffer_mt(*m_storage.world());
3666 m_changedWorldVersion = *m_worldVersion;
3667 }
3668
3674 template <QueryExecType ExecType, typename Func>
3675 void each_runtime_inter(Func func, Constraints constraints = Constraints::EnabledOnly) {
3676 if constexpr (ExecType == QueryExecType::Default) {
3677 auto& queryInfo = fetch();
3678 match_all(queryInfo);
3679 const auto plan = prepare_query_plan(queryInfo, constraints);
3680 if (plan.mode == QueryPlanMode::DirectDense) {
3681 const bool hasGroups = (plan.flags & QueryPlanFlag_Grouped) != 0;
3682 if ((plan.flags & QueryPlanFlag_Filtered) != 0) {
3683 if (hasGroups)
3684 run_query_on_chunks_runtime_direct_plain_impl<true, true>(queryInfo, plan, constraints, func);
3685 else
3686 run_query_on_chunks_runtime_direct_plain_impl<true, false>(queryInfo, plan, constraints, func);
3687 } else {
3688 if (hasGroups)
3689 run_query_on_chunks_runtime_direct_plain_impl<false, true>(queryInfo, plan, constraints, func);
3690 else
3691 run_query_on_chunks_runtime_direct_plain_impl<false, false>(queryInfo, plan, constraints, func);
3692 }
3693 return;
3694 }
3695
3697 queryInfo, plan, ExecType, static_cast<void*>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
3698 return;
3699 }
3700
3701 each_runtime_erased(ExecType, static_cast<void*>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
3702 }
3703
3709 template <typename Func, typename TIter>
3710 static void invoke_runtime_iter(void* pFunc, TIter& it) {
3711 auto& func = *static_cast<Func*>(pFunc);
3712 func(it);
3713 }
3714
3716 void* pFunc;
3717 void* pCtx;
3718 void (*invoke)(void*, Iter&);
3719
3720 void operator()(Iter& it) const {
3721 GAIA_PROF_SCOPE(query_func);
3722 it.ctx(pCtx);
3723 invoke(pFunc, it);
3724 }
3725 };
3726
3728 QueryImpl* pSelf;
3729 void* pFunc;
3730 const TypedQueryExecState* pState;
3731 void (*runChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&);
3732
3733 void operator()(Iter& it) const {
3734 GAIA_PROF_SCOPE(query_func);
3735 it.ctx(pSelf->ctx());
3736 runChunk(*pSelf, it, pFunc, *pState);
3737 }
3738 };
3739
3741 QueryImpl* pSelf;
3742 const QueryInfo* pQueryInfo;
3743 void* pFunc;
3744 const TypedQueryExecState* pState;
3745 void (*runChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&);
3746
3747 void operator()(Iter& it) const {
3748 GAIA_PROF_SCOPE(query_func);
3749 it.ctx(pSelf->ctx());
3750 runChunk(*pSelf, *pQueryInfo, it, pFunc, *pState);
3751 }
3752 };
3753
3755 QueryImpl* pSelf;
3756 void* pFunc;
3757 const TypedQueryExecState* pState;
3758 void (*runDirect)(QueryImpl&, Iter&, void*, const TypedQueryExecState&);
3759 void (*runChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&);
3760
3761 void operator()(Iter& it) const {
3762 GAIA_PROF_SCOPE(query_func);
3763 it.ctx(pSelf->ctx());
3764 pSelf->each_iter_erased(it, pFunc, *pState, runDirect, runChunk);
3765 }
3766 };
3767
3774 QueryExecType execType, void* pFunc, void (*invoke)(void*, Iter&), Constraints constraints) {
3775 auto& queryInfo = fetch();
3776 match_all(queryInfo);
3777 const auto plan = prepare_query_plan(queryInfo, constraints);
3778 each_runtime_erased(queryInfo, plan, execType, pFunc, invoke, constraints);
3779 }
3780
3789 QueryInfo& queryInfo, const QueryPlan& plan, QueryExecType execType, void* pFunc,
3790 void (*invoke)(void*, Iter&), Constraints constraints) {
3791 RuntimeIterCallback cb{pFunc, m_ctx, invoke};
3792
3793 if (plan.mode == QueryPlanMode::EntitySeed) {
3794 each_direct_iter_inter(queryInfo, constraints, cb);
3795 return;
3796 }
3797
3798 switch (execType) {
3799 case QueryExecType::Parallel:
3800 run_query_on_chunks_runtime_planned<QueryExecType::Parallel>(queryInfo, plan, constraints, cb);
3801 break;
3802 case QueryExecType::ParallelPerf:
3803 run_query_on_chunks_runtime_planned<QueryExecType::ParallelPerf>(queryInfo, plan, constraints, cb);
3804 break;
3805 case QueryExecType::ParallelEff:
3806 run_query_on_chunks_runtime_planned<QueryExecType::ParallelEff>(queryInfo, plan, constraints, cb);
3807 break;
3808 default:
3809 run_query_on_chunks_runtime_planned<QueryExecType::Default>(queryInfo, plan, constraints, cb);
3810 break;
3811 }
3812 }
3813
3814 //------------------------------------------------
3815
3820 GAIA_NODISCARD static bool is_adjunct_direct_term(const World& world, const QueryTerm& term) {
3821 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
3822 return false;
3823
3824 const auto id = term.id;
3825 return (id.pair() && world_is_exclusive_dont_fragment_relation(world, pair_rel(world, id))) ||
3826 (!id.pair() && world_is_non_fragmenting_out_of_line_component(world, id));
3827 }
3828
3832 GAIA_NODISCARD static bool uses_semantic_is_matching(const QueryTerm& term) {
3833 const auto id = term.id;
3834 return term.matchKind == QueryMatchKind::Semantic && term.src == EntityBad && term.entTrav == EntityBad &&
3835 !term_has_variables(term) && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
3836 !is_variable((EntityId)id.gen());
3837 }
3838
3842 GAIA_NODISCARD static bool uses_in_is_matching(const QueryTerm& term) {
3843 const auto id = term.id;
3844 return term.matchKind == QueryMatchKind::In && term.src == EntityBad && term.entTrav == EntityBad &&
3845 !term_has_variables(term) && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
3846 !is_variable((EntityId)id.gen());
3847 }
3848
3852 GAIA_NODISCARD static bool uses_non_direct_is_matching(const QueryTerm& term) {
3853 return uses_semantic_is_matching(term) || uses_in_is_matching(term);
3854 }
3855
3860 GAIA_NODISCARD static bool uses_potential_inherited_id_matching(const QueryTerm& term) {
3861 return query_term_uses_potential_inherited_id_matching(term);
3862 }
3863
3868 GAIA_NODISCARD static bool uses_inherited_id_matching(const World& world, const QueryTerm& term) {
3869 return uses_potential_inherited_id_matching(term) && world_term_uses_inherit_policy(world, term.id);
3870 }
3871
3873 GAIA_NODISCARD static bool match_entity_term(const World& world, Entity entity, const QueryTerm& term) {
3874 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
3875 return world_has_entity_term(world, entity, term.id);
3876 if (uses_in_is_matching(term))
3877 return world_has_entity_term_in(world, entity, term.id);
3878
3879 return world_has_entity_term_direct(world, entity, term.id);
3880 }
3881
3883 GAIA_NODISCARD static bool match_single_direct_target_term(
3884 const World& world, Entity entity, Entity termId, QueryCtx::DirectTargetEvalKind kind) {
3885 switch (kind) {
3886 case QueryCtx::DirectTargetEvalKind::SingleAllSemanticIs:
3887 case QueryCtx::DirectTargetEvalKind::SingleAllInherited:
3888 return world_has_entity_term(world, entity, termId);
3889 case QueryCtx::DirectTargetEvalKind::SingleAllInIs:
3890 return world_has_entity_term_in(world, entity, termId);
3891 case QueryCtx::DirectTargetEvalKind::SingleAllDirect:
3892 return world_has_entity_term_direct(world, entity, termId);
3893 case QueryCtx::DirectTargetEvalKind::Generic:
3894 break;
3895 }
3896
3897 return false;
3898 }
3899
3900 GAIA_NODISCARD static uint32_t count_direct_term_entities(const World& world, const QueryTerm& term) {
3901 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
3902 return world_count_direct_term_entities(world, term.id);
3903 if (uses_in_is_matching(term))
3904 return world_count_in_term_entities(world, term.id);
3905
3906 return world_count_direct_term_entities_direct(world, term.id);
3907 }
3908
3909 static void collect_direct_term_entities(const World& world, const QueryTerm& term, cnt::darray<Entity>& out) {
3910 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term)) {
3911 world_collect_direct_term_entities(world, term.id, out);
3912 return;
3913 }
3914 if (uses_in_is_matching(term)) {
3915 world_collect_in_term_entities(world, term.id, out);
3916 return;
3917 }
3918
3919 world_collect_direct_term_entities_direct(world, term.id, out);
3920 }
3921
3922 template <typename Func>
3923 GAIA_NODISCARD static bool for_each_direct_term_entity(const World& world, const QueryTerm& term, Func&& func) {
3924 struct Visitor {
3925 Func& func;
3926 static bool thunk(void* ctx, Entity entity) {
3927 return static_cast<Visitor*>(ctx)->func(entity);
3928 }
3929 };
3930
3931 Visitor visitor{func};
3932 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
3933 return world_for_each_direct_term_entity(world, term.id, &visitor, &Visitor::thunk);
3934 if (uses_in_is_matching(term))
3935 return world_for_each_in_term_entity(world, term.id, &visitor, &Visitor::thunk);
3936
3937 return world_for_each_direct_term_entity_direct(world, term.id, &visitor, &Visitor::thunk);
3938 }
3939
3941 GAIA_NODISCARD static bool can_use_direct_entity_seed_eval(const QueryInfo& queryInfo) {
3942 if (!queryInfo.can_direct_entity_seed_eval_shape())
3943 return false;
3944
3945 const auto& world = *queryInfo.world();
3946 bool hasSeedableTerm = false;
3947 for (const auto& term: queryInfo.ctx().data.terms_view()) {
3948 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Or)
3949 continue;
3950 if (uses_non_direct_is_matching(term) || uses_inherited_id_matching(world, term) ||
3951 uses_in_is_matching(term) || is_adjunct_direct_term(world, term))
3952 hasSeedableTerm = true;
3953 }
3954
3955 return hasSeedableTerm;
3956 }
3957
3959 GAIA_NODISCARD static bool can_use_direct_target_eval(const QueryInfo& queryInfo) {
3960 return queryInfo.can_direct_target_eval();
3961 }
3962
3964 GAIA_NODISCARD static bool has_only_direct_or_terms(const QueryInfo& queryInfo) {
3965 return queryInfo.has_only_direct_or_terms();
3966 }
3967
3968 static void
3969 add_chunk_run(cnt::darray<detail::BfsChunkRun>& runs, const EntityContainer& ec, uint32_t entityOffset) {
3970 if (runs.empty()) {
3971 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
3972 return;
3973 }
3974
3975 auto& run = runs.back();
3976 if (ec.pChunk == run.pChunk && ec.row == run.to) {
3977 run.to = (uint16_t)(run.to + 1);
3978 return;
3979 }
3980
3981 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
3982 }
3983
3985 Entity seededAllTerm = EntityBad;
3986 QueryMatchKind seededAllMatchKind = QueryMatchKind::Semantic;
3987 bool seededFromAll = false;
3988 bool seededFromOr = false;
3989 };
3990
3993 Entity bestAllTerm = EntityBad;
3994 uint32_t bestAllTermCount = UINT32_MAX;
3995 QueryMatchKind bestAllTermMatchKind = QueryMatchKind::Semantic;
3996 bool hasAllTerms = false;
3997 bool hasOrTerms = false;
3998 bool preferOrSeed = false;
3999 };
4000
4004 const QueryTerm* pSingleAllTerm = nullptr;
4006 bool alwaysMatch = false;
4007 };
4008
4010 GAIA_NODISCARD static bool should_prefer_direct_seed_term(
4011 const World& world, const QueryTerm& candidate, uint32_t candidateCount, const DirectEntitySeedPlan& plan) {
4012 const bool candidateIsSemanticIs = uses_non_direct_is_matching(candidate);
4013 const bool bestIsSemanticIs = plan.bestAllTermMatchKind != QueryMatchKind::Direct &&
4014 plan.bestAllTerm.pair() && plan.bestAllTerm.id() == Is.id() &&
4015 !is_wildcard(plan.bestAllTerm.gen()) &&
4016 !is_variable((EntityId)plan.bestAllTerm.gen());
4017 const auto adjustedCandidateCount = candidateCount - (candidateIsSemanticIs && candidateCount > 0 ? 1U : 0U);
4018 const auto adjustedBestCount =
4019 plan.bestAllTermCount - (bestIsSemanticIs && plan.bestAllTermCount > 0 ? 1U : 0U);
4020 if (adjustedCandidateCount < adjustedBestCount)
4021 return true;
4022 if (adjustedCandidateCount > adjustedBestCount)
4023 return false;
4024 if (plan.bestAllTerm == EntityBad)
4025 return true;
4026
4027 if (candidateIsSemanticIs != bestIsSemanticIs)
4028 return candidateIsSemanticIs;
4029
4030 const bool candidateUsesInherited = uses_inherited_id_matching(world, candidate);
4031 const bool bestUsesInherited = plan.bestAllTermMatchKind == QueryMatchKind::Semantic &&
4032 !is_wildcard(plan.bestAllTerm) &&
4033 !is_variable((EntityId)plan.bestAllTerm.id()) &&
4034 (!plan.bestAllTerm.pair() || !is_variable((EntityId)plan.bestAllTerm.gen())) &&
4035 world_term_uses_inherit_policy(world, plan.bestAllTerm);
4036 if (candidateUsesInherited != bestUsesInherited)
4037 return !candidateUsesInherited;
4038
4039 return false;
4040 }
4041
4042 GAIA_NODISCARD static DirectEntitySeedPlan
4043 direct_entity_seed_plan(const World& world, const QueryInfo& queryInfo) {
4044 DirectEntitySeedPlan plan;
4045 uint32_t totalOrTermCount = 0;
4046
4047 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4048 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4049 continue;
4050 if (term.op == QueryOpKind::All) {
4051 plan.hasAllTerms = true;
4052 const auto cnt = count_direct_term_entities(world, term);
4053 if (should_prefer_direct_seed_term(world, term, cnt, plan)) {
4054 plan.bestAllTermCount = cnt;
4055 plan.bestAllTerm = term.id;
4056 plan.bestAllTermMatchKind = term.matchKind;
4057 }
4058 } else if (term.op == QueryOpKind::Or) {
4059 plan.hasOrTerms = true;
4060 totalOrTermCount += count_direct_term_entities(world, term);
4061 }
4062 }
4063
4064 plan.preferOrSeed = plan.hasOrTerms && (!plan.hasAllTerms || totalOrTermCount < plan.bestAllTermCount);
4065 return plan;
4066 }
4067
4069 GAIA_NODISCARD static bool match_direct_entity_terms(
4070 const World& world, Entity entity, const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
4071 bool hasOrTerms = false;
4072 bool anyOrMatched = false;
4073
4074 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4075 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4076 continue;
4077 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4078 term.matchKind == seedInfo.seededAllMatchKind)
4079 continue;
4080 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4081 continue;
4082
4083 const bool present = match_entity_term(world, entity, term);
4084 switch (term.op) {
4085 case QueryOpKind::All:
4086 if (!present)
4087 return false;
4088 break;
4089 case QueryOpKind::Or:
4090 hasOrTerms = true;
4091 anyOrMatched |= present;
4092 break;
4093 case QueryOpKind::Not:
4094 if (present)
4095 return false;
4096 break;
4097 case QueryOpKind::Any:
4098 case QueryOpKind::Count:
4099 break;
4100 }
4101 }
4102
4103 return !hasOrTerms || anyOrMatched;
4104 }
4105
4106 GAIA_NODISCARD static const QueryTerm*
4107 find_direct_all_seed_term(const QueryInfo& queryInfo, const DirectEntitySeedPlan& plan) {
4108 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4109 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4110 continue;
4111 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4112 term.matchKind != plan.bestAllTermMatchKind)
4113 continue;
4114 return &term;
4115 }
4116
4117 return nullptr;
4118 }
4119
4120 GAIA_NODISCARD static DirectEntitySeedEvalPlan
4121 direct_all_seed_eval_plan(const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
4122 DirectEntitySeedEvalPlan plan{};
4123
4124 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4125 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4126 return {};
4127 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4128 term.matchKind == seedInfo.seededAllMatchKind)
4129 continue;
4130
4131 if (term.op == QueryOpKind::All) {
4132 if (plan.pSingleAllTerm != nullptr)
4133 return {};
4134 plan.pSingleAllTerm = &term;
4135 continue;
4136 }
4137
4138 return {};
4139 }
4140
4141 plan.alwaysMatch = plan.pSingleAllTerm == nullptr;
4142 return plan;
4143 }
4144
4150 GAIA_NODISCARD static bool
4151 can_use_direct_seed_run_cache(const World& world, const QueryInfo& queryInfo, const QueryTerm& seedTerm) {
4152 if (!(uses_non_direct_is_matching(seedTerm) || uses_inherited_id_matching(world, seedTerm)))
4153 return false;
4154
4155 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4156 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4157 return false;
4158 if (term.op == QueryOpKind::Any || term.op == QueryOpKind::Count || term.op == QueryOpKind::Or)
4159 return false;
4160 if (term.op == QueryOpKind::All && term.id == seedTerm.id && term.matchKind == seedTerm.matchKind)
4161 continue;
4162 if (is_adjunct_direct_term(world, term))
4163 return false;
4164 }
4165
4166 return true;
4167 }
4168
4169 GAIA_NODISCARD std::span<const detail::BfsChunkRun> cached_direct_seed_runs(
4170 QueryInfo& queryInfo, const QueryTerm& seedTerm, const DirectEntitySeedInfo& seedInfo,
4171 Constraints constraints) {
4172 auto& runData = ensure_direct_seed_run_data();
4173 auto& world = *queryInfo.world();
4174 const auto cachedConstraints = constraints;
4175 const auto relVersion = world_rel_version(world, Is);
4176 const auto worldVersion = ::gaia::ecs::world_version(world);
4177
4178 if (runData.cacheValid && runData.cachedSeedTerm == seedTerm.id &&
4179 runData.cachedSeedMatchKind == seedTerm.matchKind && runData.cachedConstraints == cachedConstraints &&
4180 runData.cachedRelVersion == relVersion && runData.cachedWorldVersion == worldVersion) {
4181 return {runData.cachedRuns.data(), runData.cachedRuns.size()};
4182 }
4183
4184 auto& runs = runData.cachedRuns;
4185 auto& entities = runData.cachedEntities;
4186 auto& chunkOrderedEntities = runData.cachedChunkOrderedEntities;
4187 runs.clear();
4188 entities.clear();
4189 chunkOrderedEntities.clear();
4190
4191 (void)for_each_direct_term_entity(world, seedTerm, [&](Entity entity) {
4192 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4193 return true;
4194
4195 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4196 return true;
4197
4198 entities.push_back(entity);
4199 return true;
4200 });
4201
4202 chunkOrderedEntities = entities;
4203 core::sort(chunkOrderedEntities, [&](Entity left, Entity right) {
4204 const auto& ecLeft = ::gaia::ecs::fetch(world, left);
4205 const auto& ecRight = ::gaia::ecs::fetch(world, right);
4206 if (ecLeft.pArchetype != ecRight.pArchetype)
4207 return ecLeft.pArchetype->id() < ecRight.pArchetype->id();
4208 if (ecLeft.pChunk != ecRight.pChunk)
4209 return ecLeft.pChunk < ecRight.pChunk;
4210 return ecLeft.row < ecRight.row;
4211 });
4212
4213 uint32_t entityOffset = 0;
4214 for (const auto entity: chunkOrderedEntities) {
4215 const auto& ec = ::gaia::ecs::fetch(world, entity);
4216 add_chunk_run(runs, ec, entityOffset++);
4217 }
4218
4219 runData.cachedSeedTerm = seedTerm.id;
4220 runData.cachedSeedMatchKind = seedTerm.matchKind;
4221 runData.cachedConstraints = cachedConstraints;
4222 runData.cachedRelVersion = relVersion;
4223 runData.cachedWorldVersion = worldVersion;
4224 runData.cacheValid = true;
4225 return {runs.data(), runs.size()};
4226 }
4227
4228 GAIA_NODISCARD std::span<const Entity> cached_direct_seed_chunk_entities(
4229 QueryInfo& queryInfo, const QueryTerm& seedTerm, const DirectEntitySeedInfo& seedInfo,
4230 Constraints constraints) {
4231 (void)cached_direct_seed_runs(queryInfo, seedTerm, seedInfo, constraints);
4232 auto& runData = ensure_direct_seed_run_data();
4233 return {runData.cachedChunkOrderedEntities.data(), runData.cachedChunkOrderedEntities.size()};
4234 }
4235
4236 template <typename Func>
4237 GAIA_NODISCARD static bool for_each_direct_all_seed(
4238 const World& world, const QueryInfo& queryInfo, const DirectEntitySeedPlan& plan, Constraints constraints,
4239 Func&& func) {
4240 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
4241 GAIA_ASSERT(pSeedTerm != nullptr);
4242 if (pSeedTerm == nullptr)
4243 return true;
4244
4245 DirectEntitySeedInfo seedInfo{};
4246 seedInfo.seededAllTerm = pSeedTerm->id;
4247 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
4248 seedInfo.seededFromAll = true;
4249 const auto evalPlan = direct_all_seed_eval_plan(queryInfo, seedInfo);
4250 const Archetype* pLastSingleAllArchetype = nullptr;
4251 bool lastSingleAllMatch = false;
4252 bool seedImpliesSingleAllTerm = false;
4253 if (evalPlan.pSingleAllTerm != nullptr && uses_non_direct_is_matching(*pSeedTerm) &&
4254 (uses_non_direct_is_matching(*evalPlan.pSingleAllTerm) ||
4255 uses_inherited_id_matching(world, *evalPlan.pSingleAllTerm))) {
4256 const auto seedTarget = pair_tgt(world, pSeedTerm->id);
4257 if (seedTarget != EntityBad)
4258 seedImpliesSingleAllTerm = match_entity_term(world, seedTarget, *evalPlan.pSingleAllTerm);
4259 }
4260
4261 // Stream the chosen ALL seed term directly. This avoids materializing a temporary
4262 // entity array for the common `all<T>().is(base)` shape.
4263 return for_each_direct_term_entity(world, *pSeedTerm, [&](Entity entity) {
4264 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4265 return true;
4266
4267 if (evalPlan.alwaysMatch)
4268 return func(entity);
4269 if (evalPlan.pSingleAllTerm != nullptr) {
4270 if (seedImpliesSingleAllTerm)
4271 return func(entity);
4272 if (uses_non_direct_is_matching(*evalPlan.pSingleAllTerm) ||
4273 uses_inherited_id_matching(world, *evalPlan.pSingleAllTerm)) {
4274 const auto* pArchetype = world_entity_archetype(world, entity);
4275 if (pArchetype != pLastSingleAllArchetype) {
4276 lastSingleAllMatch = match_entity_term(world, entity, *evalPlan.pSingleAllTerm);
4277 pLastSingleAllArchetype = pArchetype;
4278 }
4279 if (!lastSingleAllMatch)
4280 return true;
4281 } else if (!match_entity_term(world, entity, *evalPlan.pSingleAllTerm)) {
4282 return true;
4283 }
4284 return func(entity);
4285 }
4286 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4287 return true;
4288
4289 return func(entity);
4290 });
4291 }
4292
4294 GAIA_NODISCARD static bool match_direct_entity_constraints(
4295 const World& world, const QueryInfo& queryInfo, Entity entity, Constraints constraints) {
4296 if (!queryInfo.matches_prefab_entities() && world_entity_prefab(world, entity))
4297 return false;
4298
4299 if (constraints == Constraints::EnabledOnly)
4300 return world_entity_enabled(world, entity);
4301 if (constraints == Constraints::DisabledOnly)
4302 return !world_entity_enabled(world, entity);
4303 return true;
4304 }
4305
4307 GAIA_NODISCARD static bool can_use_archetype_bucket_count(
4308 const World& world, const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
4309 if (!seedInfo.seededFromAll && !seedInfo.seededFromOr)
4310 return false;
4311
4312 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4313 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4314 return false;
4315 if (seedInfo.seededFromAll && term.id == seedInfo.seededAllTerm && term.op == QueryOpKind::All)
4316 continue;
4317 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4318 continue;
4319 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Not)
4320 return false;
4321 if (is_adjunct_direct_term(world, term))
4322 return false;
4323 if (uses_inherited_id_matching(world, term))
4324 return false;
4325 }
4326
4327 return true;
4328 }
4329
4331 GAIA_NODISCARD static uint32_t count_direct_entity_seed_by_archetype(
4332 const World& world, const QueryInfo& queryInfo, const cnt::darray<Entity>& seedEntities,
4333 const DirectEntitySeedInfo& seedInfo, Constraints constraints) {
4334 auto& scratch = direct_query_scratch();
4335
4336 scratch.archetypes.clear();
4337 scratch.bucketEntities.clear();
4338 scratch.counts.clear();
4339
4340 for (const auto entity: seedEntities) {
4341 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4342 continue;
4343
4344 const auto* pArchetype = world_entity_archetype(world, entity);
4345 const auto idx = core::get_index(scratch.archetypes, pArchetype);
4346 if (idx == BadIndex) {
4347 scratch.archetypes.push_back(pArchetype);
4348 scratch.bucketEntities.push_back(entity);
4349 scratch.counts.push_back(1);
4350 } else {
4351 ++scratch.counts[idx];
4352 }
4353 }
4354
4355 uint32_t cnt = 0;
4356 const auto archetypeCnt = (uint32_t)scratch.archetypes.size();
4357 GAIA_FOR(archetypeCnt) {
4358 if (match_direct_entity_terms(world, scratch.bucketEntities[i], queryInfo, seedInfo))
4359 cnt += scratch.counts[i];
4360 }
4361
4362 return cnt;
4363 }
4364
4366 GAIA_NODISCARD static uint32_t
4367 count_direct_or_union(const World& world, const QueryInfo& queryInfo, Constraints constraints) {
4368 auto& scratch = direct_query_scratch();
4369 const auto seenVersion = next_direct_query_seen_version(scratch);
4370 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
4371
4372 uint32_t cnt = 0;
4373 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4374 if (term.op != QueryOpKind::Or)
4375 continue;
4376
4377 (void)for_each_direct_term_entity(world, term, [&](Entity entity) {
4378 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4379 return true;
4380
4381 const auto entityId = (uint32_t)entity.id();
4382 ensure_direct_query_count_capacity(scratch, entityId);
4383
4384 if (scratch.counts[entityId] == seenVersion)
4385 return true;
4386 scratch.counts[entityId] = seenVersion;
4387
4388 bool rejected = false;
4389 if (hasDirectNotTerms) {
4390 for (const auto& notTerm: queryInfo.ctx().data.terms_view()) {
4391 if (notTerm.op != QueryOpKind::Not)
4392 continue;
4393 if (match_entity_term(world, entity, notTerm)) {
4394 rejected = true;
4395 break;
4396 }
4397 }
4398 }
4399
4400 if (!rejected)
4401 ++cnt;
4402 return true;
4403 });
4404 }
4405
4406 return cnt;
4407 }
4408
4414 GAIA_NODISCARD static bool
4415 is_empty_direct_or_union(const World& world, const QueryInfo& queryInfo, Constraints constraints) {
4416 auto& scratch = direct_query_scratch();
4417 const auto seenVersion = next_direct_query_seen_version(scratch);
4418 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
4419
4420 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4421 if (term.op != QueryOpKind::Or)
4422 continue;
4423
4424 const bool completed = for_each_direct_term_entity(world, term, [&](Entity entity) {
4425 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4426 return true;
4427
4428 const auto entityId = (uint32_t)entity.id();
4429 ensure_direct_query_count_capacity(scratch, entityId);
4430
4431 if (scratch.counts[entityId] == seenVersion)
4432 return true;
4433 scratch.counts[entityId] = seenVersion;
4434
4435 bool rejected = false;
4436 if (hasDirectNotTerms) {
4437 for (const auto& notTerm: queryInfo.ctx().data.terms_view()) {
4438 if (notTerm.op != QueryOpKind::Not)
4439 continue;
4440 if (match_entity_term(world, entity, notTerm)) {
4441 rejected = true;
4442 break;
4443 }
4444 }
4445 }
4446
4447 if (!rejected)
4448 return false;
4449 return true;
4450 });
4451
4452 if (!completed)
4453 return true;
4454 }
4455
4456 return false;
4457 }
4458
4460 static DirectEntitySeedInfo
4461 build_direct_entity_seed(const World& world, const QueryInfo& queryInfo, cnt::darray<Entity>& out) {
4462 auto& scratch = direct_query_scratch();
4463 out.clear();
4464 DirectEntitySeedInfo seedInfo{};
4465 const auto plan = direct_entity_seed_plan(world, queryInfo);
4466
4467 if (plan.hasAllTerms && !plan.preferOrSeed) {
4468 if (plan.bestAllTerm != EntityBad) {
4469 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4470 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4471 continue;
4472 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4473 term.matchKind != plan.bestAllTermMatchKind)
4474 continue;
4475 collect_direct_term_entities(world, term, out);
4476 seedInfo.seededAllMatchKind = term.matchKind;
4477 break;
4478 }
4479 seedInfo.seededFromAll = true;
4480 seedInfo.seededAllTerm = plan.bestAllTerm;
4481 }
4482 return seedInfo;
4483 }
4484
4485 const auto seenVersion = next_direct_query_seen_version(scratch);
4486
4487 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4488 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4489 continue;
4490 if (term.op != QueryOpKind::Or)
4491 continue;
4492
4493 scratch.termEntities.clear();
4494 collect_direct_term_entities(world, term, scratch.termEntities);
4495 for (const auto entity: scratch.termEntities) {
4496 const auto entityId = (uint32_t)entity.id();
4497 ensure_direct_query_count_capacity(scratch, entityId);
4498
4499 if (scratch.counts[entityId] == seenVersion)
4500 continue;
4501 scratch.counts[entityId] = seenVersion;
4502 out.push_back(entity);
4503 }
4504 }
4505
4506 seedInfo.seededFromOr = true;
4507 return seedInfo;
4508 }
4509
4513 GAIA_NODISCARD static bool has_direct_not_terms(const QueryInfo& queryInfo) {
4514 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4515 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4516 continue;
4517 if (term.op == QueryOpKind::Not)
4518 return true;
4519 }
4520
4521 return false;
4522 }
4523
4530 template <typename Func>
4531 void for_each_direct_or_union(World& world, const QueryInfo& queryInfo, Constraints constraints, Func&& func) {
4532 auto& scratch = direct_query_scratch();
4533 const auto seenVersion = next_direct_query_seen_version(scratch);
4534 DirectEntitySeedInfo seedInfo{};
4535 seedInfo.seededFromOr = true;
4536
4537 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4538 if (term.op != QueryOpKind::Or)
4539 continue;
4540
4541 (void)for_each_direct_term_entity(world, term, [&](Entity entity) {
4542 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4543 return true;
4544
4545 const auto entityId = (uint32_t)entity.id();
4546 ensure_direct_query_count_capacity(scratch, entityId);
4547
4548 if (scratch.counts[entityId] == seenVersion)
4549 return true;
4550 scratch.counts[entityId] = seenVersion;
4551
4552 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4553 return true;
4554
4555 func(entity);
4556 return true;
4557 });
4558 }
4559 }
4560
4567 template <bool UseFilters>
4568 GAIA_NODISCARD bool empty_inter(const QueryInfo& queryInfo, Constraints constraints) const {
4569 const auto cacheRange = selected_query_cache_range(queryInfo);
4570
4571 if constexpr (!UseFilters) {
4572 if (!cacheRange.hasSelectedGroup && can_use_direct_entity_seed_eval(queryInfo)) {
4573 if (has_only_direct_or_terms(queryInfo))
4574 return is_empty_direct_or_union(*queryInfo.world(), queryInfo, constraints);
4575
4576 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
4577 bool empty = true;
4578 (void)for_each_direct_all_seed(*queryInfo.world(), queryInfo, plan, constraints, [&](Entity) {
4579 empty = false;
4580 return false;
4581 });
4582 return empty;
4583 }
4584 }
4585
4586 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
4587 const auto cacheView = queryInfo.cache_archetype_view();
4588 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
4589 if (needsBarrierCache)
4590 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
4591 if (!cacheRange.valid)
4592 return true;
4593 const auto idxFrom = cacheRange.idxFrom;
4594 const auto idxTo = cacheRange.idxTo;
4595
4596 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
4597 const auto* pArchetype = cacheView[qi];
4598 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
4599 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
4600 continue;
4601
4602 GAIA_PROF_SCOPE(query::empty);
4603
4604 const auto& chunks = pArchetype->chunks();
4605 Iter it;
4606 it.init_query_state(queryInfo.world(), constraints, false);
4607 it.set_archetype(pArchetype);
4608
4609 if (!hasEntityFilters) {
4610 for (auto* pChunk: chunks) {
4611 uint16_t from = 0;
4612 uint16_t to = 0;
4613 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
4614 if (from == to)
4615 continue;
4616 it.set_chunk(pChunk, from, to);
4617 if constexpr (UseFilters) {
4618 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4619 continue;
4620 }
4621 return false;
4622 }
4623 continue;
4624 }
4625
4626 const bool isNotEmpty = core::has_if(chunks, [&](Chunk* pChunk) {
4627 uint16_t from = 0;
4628 uint16_t to = 0;
4629 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
4630 if (from == to)
4631 return false;
4632 it.set_chunk(pChunk, from, to);
4633 if constexpr (UseFilters)
4634 if (it.size() == 0 || !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4635 return false;
4636 if (!hasEntityFilters)
4637 return it.size() > 0;
4638
4639 const auto entities = it.template view<Entity>();
4640 const auto cnt = it.size();
4641 GAIA_FOR(cnt) {
4642 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
4643 return true;
4644 }
4645 return false;
4646 });
4647
4648 if (isNotEmpty)
4649 return false;
4650 }
4651
4652 return true;
4653 }
4654
4656 GAIA_NODISCARD static bool match_entity_filters(const World& world, Entity entity, const QueryInfo& queryInfo) {
4657 bool hasOrTerms = false;
4658 bool anyOrMatched = false;
4659 const bool hasEntityFilterTerms = queryInfo.has_entity_filter_terms();
4660
4661 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4662 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4663 continue;
4664
4665 const auto id = term.id;
4666 const bool isDirectIsTerm = uses_non_direct_is_matching(term);
4667 const bool isInheritedTerm = uses_inherited_id_matching(world, term);
4668 const bool isAdjunctTerm =
4669 (id.pair() && world_is_exclusive_dont_fragment_relation(world, pair_rel(world, id))) ||
4670 (!id.pair() && world_is_non_fragmenting_out_of_line_component(world, id));
4671 const bool needsEntityFilter = isAdjunctTerm || isDirectIsTerm || isInheritedTerm ||
4672 (hasEntityFilterTerms && term.op == QueryOpKind::Or);
4673 if (!needsEntityFilter)
4674 continue;
4675
4676 const bool present = match_entity_term(world, entity, term);
4677 switch (term.op) {
4678 case QueryOpKind::All:
4679 if (!present)
4680 return false;
4681 break;
4682 case QueryOpKind::Or:
4683 hasOrTerms = true;
4684 anyOrMatched |= present;
4685 break;
4686 case QueryOpKind::Not:
4687 if (present)
4688 return false;
4689 break;
4690 case QueryOpKind::Any:
4691 case QueryOpKind::Count:
4692 break;
4693 }
4694 }
4695
4696 return !hasOrTerms || anyOrMatched;
4697 }
4698
4704 GAIA_NODISCARD bool
4705 matches_target_entities(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
4706 if (targetEntities.empty())
4707 return false;
4708
4709 const auto& world = *queryInfo.world();
4710
4711 if (can_use_direct_target_eval(queryInfo)) {
4712 const auto directTargetEvalKind = queryInfo.direct_target_eval_kind();
4713 if (directTargetEvalKind != QueryCtx::DirectTargetEvalKind::Generic) {
4714 const auto termId = queryInfo.direct_target_eval_id();
4715 if (targetEntities.size() == 1) {
4716 const auto entity = targetEntities[0];
4717 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4718 return false;
4719 return match_single_direct_target_term(world, entity, termId, directTargetEvalKind);
4720 }
4721
4722 for (const auto entity: targetEntities) {
4723 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4724 continue;
4725 if (match_single_direct_target_term(world, entity, termId, directTargetEvalKind))
4726 return true;
4727 }
4728
4729 return false;
4730 }
4731
4732 const DirectEntitySeedInfo seedInfo{};
4733 if (targetEntities.size() == 1) {
4734 const auto entity = targetEntities[0];
4735 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4736 return false;
4737 return match_direct_entity_terms(world, entity, queryInfo, seedInfo);
4738 }
4739
4740 for (const auto entity: targetEntities) {
4741 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4742 continue;
4743 if (match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4744 return true;
4745 }
4746
4747 return false;
4748 }
4749
4750 if (!match_one(queryInfo, archetype, targetEntities))
4751 return false;
4752
4753 if (!queryInfo.has_entity_filter_terms())
4754 return true;
4755
4756 for (const auto entity: targetEntities) {
4757 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4758 continue;
4759 if (match_entity_filters(world, entity, queryInfo))
4760 return true;
4761 }
4762
4763 return false;
4764 }
4765
4772 template <bool UseFilters>
4773 GAIA_NODISCARD uint32_t count_inter(const QueryInfo& queryInfo, Constraints constraints) const {
4774 const auto cacheRange = selected_query_cache_range(queryInfo);
4775
4776 if constexpr (!UseFilters) {
4777 if (!cacheRange.hasSelectedGroup && can_use_direct_entity_seed_eval(queryInfo)) {
4778 auto& scratch = direct_query_scratch();
4779 if (has_only_direct_or_terms(queryInfo))
4780 return count_direct_or_union(*queryInfo.world(), queryInfo, constraints);
4781
4782 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
4783 const auto seedInfo = build_direct_entity_seed(*queryInfo.world(), queryInfo, scratch.entities);
4784
4785 if (can_use_archetype_bucket_count(*queryInfo.world(), queryInfo, seedInfo))
4787 *queryInfo.world(), queryInfo, scratch.entities, seedInfo, constraints);
4788
4789 uint32_t cnt = 0;
4790 (void)for_each_direct_all_seed(*queryInfo.world(), queryInfo, plan, constraints, [&](Entity) {
4791 ++cnt;
4792 return true;
4793 });
4794
4795 return cnt;
4796 }
4797 }
4798
4799 uint32_t cnt = 0;
4800 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
4801 const auto cacheView = queryInfo.cache_archetype_view();
4802 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
4803 if (needsBarrierCache)
4804 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
4805
4806 if (!cacheRange.valid)
4807 return 0;
4808 const auto idxFrom = cacheRange.idxFrom;
4809 const auto idxTo = cacheRange.idxTo;
4810
4811 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
4812 const auto* pArchetype = cacheView[qi];
4813 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
4814 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
4815 continue;
4816
4817 GAIA_PROF_SCOPE(query::count);
4818
4819 const auto& chunks = pArchetype->chunks();
4820 Iter it;
4821 it.init_query_state(queryInfo.world(), constraints, false);
4822 it.set_archetype(pArchetype);
4823
4824 if (!hasEntityFilters) {
4825 for (auto* pChunk: chunks) {
4826 uint16_t from = 0;
4827 uint16_t to = 0;
4828 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
4829 const auto entityCnt = to - from;
4830 if (entityCnt == 0)
4831 continue;
4832 it.set_chunk(pChunk, from, to);
4833
4834 if constexpr (UseFilters) {
4835 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4836 continue;
4837 }
4838
4839 cnt += entityCnt;
4840 }
4841 continue;
4842 }
4843 for (auto* pChunk: chunks) {
4844 uint16_t from = 0;
4845 uint16_t to = 0;
4846 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
4847 const auto entityCnt = to - from;
4848 if (entityCnt == 0)
4849 continue;
4850 it.set_chunk(pChunk, from, to);
4851
4852 // Filters
4853 if constexpr (UseFilters) {
4854 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4855 continue;
4856 }
4857
4858 if (hasEntityFilters) {
4859 const auto entities = it.template view<Entity>();
4860 GAIA_FOR(entityCnt) {
4861 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
4862 ++cnt;
4863 }
4864 continue;
4865 }
4866
4867 // Entity count
4868 cnt += entityCnt;
4869 }
4870 }
4871
4872 return cnt;
4873 }
4874
4875 static void init_direct_entity_iter(
4876 const QueryInfo& queryInfo, const World& world, const EntityContainer& ec, Iter& it, uint8_t* pIndices,
4877 Entity* pTermIds, const Archetype*& pLastArchetype) {
4878 GAIA_ASSERT(ec.pArchetype != nullptr);
4879 GAIA_ASSERT(ec.pChunk != nullptr);
4880 GAIA_ASSERT(ec.row < ec.pChunk->size());
4881
4882 if (ec.pArchetype != pLastArchetype) {
4883 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
4884 pIndices[i] = 0xFF;
4885 pTermIds[i] = EntityBad;
4886 }
4887
4888 const auto terms = queryInfo.ctx().data.terms_view();
4889 const auto queryIdCnt = (uint32_t)terms.size();
4890 auto indicesView = queryInfo.try_indices_mapping_view(ec.pArchetype);
4891 GAIA_FOR(queryIdCnt) {
4892 const auto& term = terms[i];
4893 const auto fieldIdx = term.fieldIndex;
4894 const auto queryId = term.id;
4895 pTermIds[fieldIdx] = queryId;
4896 if (!indicesView.empty()) {
4897 pIndices[fieldIdx] = indicesView[fieldIdx];
4898 continue;
4899 }
4900 if (!queryId.pair() && world_is_out_of_line_component(world, queryId)) {
4901#if GAIA_ASSERT_ENABLED
4902 const auto compIdx = core::get_index_unsafe(ec.pArchetype->ids_view(), queryId);
4903 GAIA_ASSERT(compIdx != BadIndex);
4904#endif
4905 pIndices[fieldIdx] = 0xFF;
4906 continue;
4907 }
4908
4909 auto compIdx = world_component_index_comp_idx(world, *ec.pArchetype, queryId);
4910 if (compIdx == BadIndex)
4911 compIdx = core::get_index(ec.pArchetype->ids_view(), queryId);
4912 pIndices[fieldIdx] = (uint8_t)compIdx;
4913 }
4914
4915 it.set_archetype(ec.pArchetype);
4916 it.set_comp_indices(pIndices);
4917 const auto inheritedDataView = queryInfo.inherited_data_view(ec.pArchetype);
4918 it.set_inherited_data(inheritedDataView);
4919 it.set_term_ids(pTermIds);
4920 pLastArchetype = ec.pArchetype;
4921 }
4922
4923 it.set_chunk(ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
4924 it.set_group_id(0);
4925 }
4926
4927 static void init_direct_entity_iter(
4928 const QueryInfo& queryInfo, const World& world, Entity entity, Iter& it, uint8_t* pIndices,
4929 Entity* pTermIds) {
4930 const auto& ec = ::gaia::ecs::fetch(world, entity);
4931 const Archetype* pLastArchetype = nullptr;
4932 it.set_world(&world);
4933 init_direct_entity_iter(queryInfo, world, ec, it, pIndices, pTermIds, pLastArchetype);
4934 }
4935
4936 template <typename Func>
4937 void each_chunk_runs_iter(
4938 QueryInfo& queryInfo, std::span<const detail::BfsChunkRun> runs, Constraints constraints, Func func) {
4939 auto& world = *queryInfo.world();
4940 Iter it;
4941 it.init_query_state(&world, constraints, false);
4942 const Archetype* pLastArchetype = nullptr;
4943 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
4944 Entity termIds[ChunkHeader::MAX_COMPONENTS];
4945
4946 for (const auto& run: runs) {
4947 const auto& ec = ::gaia::ecs::fetch(world, run.pChunk->entity_view()[run.from]);
4948 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
4949 it.set_chunk(run.pChunk, run.from, run.to);
4950 it.set_group_id(0);
4951 it.ctx(m_ctx);
4952 func(it);
4953 finish_iter_writes(it);
4954 it.clear_touched_writes();
4955 }
4956 }
4957
4959 Entity id = EntityBad;
4960 bool isEntity = false;
4961 bool isPair = false;
4962 };
4963
4969 GAIA_NODISCARD static bool can_use_direct_chunk_term_eval_arg(
4970 World& world, const QueryInfo& queryInfo, const DirectChunkArgEvalDesc& desc) {
4971 if (desc.isEntity)
4972 return true;
4973 if (desc.isPair)
4974 return false;
4975 if (world_is_out_of_line_component(world, desc.id))
4976 return false;
4977
4978 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4979 if (term.id != desc.id)
4980 continue;
4981 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4982 return false;
4983 if (uses_non_direct_is_matching(term) || uses_inherited_id_matching(world, term) ||
4984 is_adjunct_direct_term(world, term))
4985 return false;
4986 return true;
4987 }
4988
4989 return false;
4990 }
4991
4992 GAIA_NODISCARD static bool can_use_direct_chunk_term_eval_descs(
4993 World& world, const QueryInfo& queryInfo, const DirectChunkArgEvalDesc* pDescs, uint32_t descCnt) {
4994 if (queryInfo.has_entity_filter_terms())
4995 return false;
4996
4997 GAIA_FOR(descCnt) {
4998 if (!can_use_direct_chunk_term_eval_arg(world, queryInfo, pDescs[i]))
4999 return false;
5000 }
5001
5002 return true;
5003 }
5004
5005 template <typename Func>
5006 void each_direct_entities_iter(
5007 QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints, Func func) {
5008 auto& world = *queryInfo.world();
5009 auto& walkData = ensure_each_walk_data();
5010 Iter it;
5011 it.init_query_state(&world, constraints, false);
5012 if (!walkData.cachedRuns.empty()) {
5013 each_chunk_runs_iter(queryInfo, walkData.cachedRuns, constraints, func);
5014 return;
5015 }
5016
5017 const Archetype* pLastArchetype = nullptr;
5018 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5019 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5020 for (const auto entity: entities) {
5021 const auto& ec = ::gaia::ecs::fetch(world, entity);
5022 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
5023 it.ctx(m_ctx);
5024 func(it);
5025 finish_iter_writes(it);
5026 it.clear_touched_writes();
5027 }
5028 }
5029
5036 template <typename Func>
5037 void each_direct_iter_inter(QueryInfo& queryInfo, Constraints constraints, Func func) {
5038 auto& world = *queryInfo.world();
5039 const bool hasWriteTerms = queryInfo.ctx().data.readWriteMask != 0;
5040 const auto plan = direct_entity_seed_plan(world, queryInfo);
5041
5042 auto exec_entity = [&](Entity entity) {
5043 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5045 Iter it;
5046 it.set_constraints(constraints);
5047 init_direct_entity_iter(queryInfo, world, entity, it, indices, termIds);
5048 it.set_write_im(false);
5049 it.ctx(m_ctx);
5050 func(it);
5051 finish_iter_writes(it);
5052 };
5053
5054 if (hasWriteTerms) {
5055 auto& scratch = direct_query_scratch();
5056 // Writable callbacks may add local overrides and reshuffle direct-term indices,
5057 // so direct-seeded execution must iterate a stable snapshot.
5058 const auto seedInfo = build_direct_entity_seed(world, queryInfo, scratch.entities);
5059 for (const auto entity: scratch.entities) {
5060 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
5061 continue;
5062 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
5063 continue;
5064 exec_entity(entity);
5065 }
5066 return;
5067 }
5068
5069 if (!plan.preferOrSeed) {
5070 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
5071 if (pSeedTerm != nullptr && can_use_direct_seed_run_cache(world, queryInfo, *pSeedTerm)) {
5072 DirectEntitySeedInfo seedInfo{};
5073 seedInfo.seededAllTerm = pSeedTerm->id;
5074 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
5075 seedInfo.seededFromAll = true;
5076 each_chunk_runs_iter(
5077 queryInfo, cached_direct_seed_runs(queryInfo, *pSeedTerm, seedInfo, constraints), constraints, func);
5078 return;
5079 }
5080 }
5081
5082 if (plan.preferOrSeed) {
5083 for_each_direct_or_union(world, queryInfo, constraints, exec_entity);
5084 return;
5085 }
5086
5087 (void)for_each_direct_all_seed(world, queryInfo, plan, constraints, [&](Entity entity) {
5088 exec_entity(entity);
5089 return true;
5090 });
5091 }
5092
5094 void each_direct_inter(
5095 QueryInfo& queryInfo, Constraints constraints, void* pFunc, const TypedQueryExecState& state,
5096 void (*runDirectChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&), bool needsInheritedArgIds,
5097 void (*invokeInherited)(World&, Entity, const Entity*, void*));
5098
5099 template <bool UseFilters, typename ContainerOut>
5100 void arr_inter(QueryInfo& queryInfo, ContainerOut& outArray, Constraints constraints);
5101
5102 public:
5103 QueryImpl() = default;
5104 ~QueryImpl() = default;
5105
5106 QueryImpl(
5107 World& world, QueryCache& queryCache, ArchetypeId& nextArchetypeId, uint32_t& worldVersion,
5108 const EntityToArchetypeMap& entityToArchetypeMap,
5109 const EntityToArchetypeVersionMap& entityToArchetypeMapVersions, const ArchetypeDArray& allArchetypes):
5110 m_nextArchetypeId(&nextArchetypeId), m_worldVersion(&worldVersion),
5111 m_entityToArchetypeMap(&entityToArchetypeMap),
5112 m_entityToArchetypeMapVersions(&entityToArchetypeMapVersions), m_allArchetypes(&allArchetypes) {
5113 m_storage.init(&world, &queryCache);
5114 }
5115
5116#if GAIA_ECS_TEST_HOOKS
5117 template <typename Func>
5118 GAIA_NODISCARD QueryPlan test_typed_plan(Func func);
5119
5123 GAIA_NODISCARD QueryPlan test_iter_plan(Constraints constraints = Constraints::EnabledOnly);
5124#endif
5125
5128 GAIA_NODISCARD QueryId id() const {
5129 if (!uses_query_cache_storage())
5130 return QueryIdBad;
5131 return m_storage.m_identity.handle.id();
5132 }
5133
5136 GAIA_NODISCARD uint32_t gen() const {
5137 if (!uses_query_cache_storage())
5138 return QueryIdBad;
5139 return m_storage.m_identity.handle.gen();
5140 }
5141
5142 //------------------------------------------------
5143
5145 void reset() {
5146 m_storage.reset();
5147 m_eachWalkData.reset();
5148 m_directSeedRunData.reset();
5149 reset_changed_filter_state();
5150 invalidate_each_walk_cache();
5151 invalidate_direct_seed_run_cache();
5152 }
5153
5155 void destroy() {
5156 (void)m_storage.try_del_from_cache();
5157 m_eachWalkData.reset();
5158 m_directSeedRunData.reset();
5159 reset_changed_filter_state();
5160 invalidate_each_walk_cache();
5161 invalidate_direct_seed_run_cache();
5162 }
5163
5166 GAIA_NODISCARD bool is_cached() const {
5167 return uses_query_cache_storage() && m_storage.is_cached();
5168 }
5169
5170 //------------------------------------------------
5171
5213 QueryImpl& add(const char* str, ...) {
5214 GAIA_ASSERT(str != nullptr);
5215 if (str == nullptr)
5216 return *this;
5217
5218 va_list args{};
5219 va_start(args, str);
5220
5221 uint32_t pos = 0;
5222 uint32_t exp0 = 0;
5223 uint32_t parentDepth = 0;
5224
5226 uint32_t varNamesCnt = 0;
5227 auto is_this_expr = [](std::span<const char> exprRaw) {
5228 auto expr = util::trim(exprRaw);
5229 return expr.size() == 5 && expr[0] == '$' && expr[1] == 't' && expr[2] == 'h' && expr[3] == 'i' &&
5230 expr[4] == 's';
5231 };
5232
5233 auto find_or_alloc_var = [&](std::span<const char> varExpr) -> Entity {
5234 auto varNameSpan = util::trim(varExpr);
5235 if (varNameSpan.empty())
5236 return EntityBad;
5237
5238 const util::str_view varName{varNameSpan.data(), (uint32_t)varNameSpan.size()};
5239 if (is_reserved_var_name(varName)) {
5240 GAIA_ASSERT2(false, "$this is reserved and can only be used as a source expression: Id($this)");
5241 return EntityBad;
5242 }
5243
5244 const auto namedVar = find_var_by_name(varName);
5245 if (namedVar != EntityBad)
5246 return namedVar;
5247
5248 GAIA_FOR(varNamesCnt) {
5249 if (varNames[i].size() != varName.size())
5250 continue;
5251 if (varNames[i].size() > 0 && memcmp(varNames[i].data(), varName.data(), varName.size()) != 0)
5252 continue;
5253 return query_var_entity(i);
5254 }
5255
5256 if (varNamesCnt >= varNames.size()) {
5257 GAIA_ASSERT2(false, "Too many query variables in expression");
5258 return EntityBad;
5259 }
5260
5261 const auto idx = varNamesCnt++;
5262 varNames[idx] = varName;
5263
5264 const auto varEntity = query_var_entity(idx);
5265 (void)set_var_name_internal(varEntity, varName);
5266 return varEntity;
5267 };
5268
5269 auto parse_entity_expr = [&](auto&& self, std::span<const char> exprRaw) -> Entity {
5270 auto expr = util::trim(exprRaw);
5271 if (expr.empty())
5272 return EntityBad;
5273
5274 if (expr[0] == '$')
5275 return find_or_alloc_var(expr.subspan(1));
5276
5277 if (expr[0] == '(') {
5278 if (expr.back() != ')') {
5279 GAIA_ASSERT2(false, "Expression '(' not terminated");
5280 return EntityBad;
5281 }
5282
5283 const auto idStr = expr.subspan(1, expr.size() - 2);
5284 const auto commaIdx = core::get_index(idStr, ',');
5285 if (commaIdx == BadIndex) {
5286 GAIA_ASSERT2(false, "Pair expression does not contain ','");
5287 return EntityBad;
5288 }
5289
5290 const auto first = self(self, idStr.subspan(0, commaIdx));
5291 if (first == EntityBad)
5292 return EntityBad;
5293 const auto second = self(self, idStr.subspan(commaIdx + 1));
5294 if (second == EntityBad)
5295 return EntityBad;
5296
5297 return ecs::Pair(first, second);
5298 }
5299
5300 return expr_to_entity((const World&)*m_storage.world(), args, expr);
5301 };
5302
5303 auto parse_src_expr = [&](std::span<const char> srcExprRaw, Entity& srcOut) -> bool {
5304 auto srcExpr = util::trim(srcExprRaw);
5305 if (srcExpr.empty())
5306 return false;
5307
5308 // `$this` explicitly means the default source for the term.
5309 if (is_this_expr(srcExpr)) {
5310 srcOut = EntityBad;
5311 return true;
5312 }
5313
5314 srcOut = parse_entity_expr(parse_entity_expr, srcExpr);
5315 return srcOut != EntityBad;
5316 };
5317
5318 auto parse_term_expr = [&](std::span<const char> exprRaw, Entity& id, QueryTermOptions& options) -> bool {
5319 auto expr = util::trim(exprRaw);
5320 if (expr.empty())
5321 return false;
5322
5323 if (expr.back() == ')') {
5324 int32_t depth = 0;
5325 int32_t openIdx = -1;
5326 for (int32_t i = (int32_t)expr.size() - 1; i >= 0; --i) {
5327 if (expr[(uint32_t)i] == ')')
5328 ++depth;
5329 else if (expr[(uint32_t)i] == '(') {
5330 --depth;
5331 if (depth == 0) {
5332 openIdx = i;
5333 break;
5334 }
5335 }
5336 }
5337
5338 // `Id(src)` form. Keep `(Rel,Tgt)` intact by requiring a non-empty prefix.
5339 if (openIdx > 0) {
5340 auto idExpr = util::trim(expr.subspan(0, (uint32_t)openIdx));
5341 auto srcExpr = util::trim(expr.subspan((uint32_t)openIdx + 1, expr.size() - (uint32_t)openIdx - 2));
5342 if (!idExpr.empty() && !srcExpr.empty()) {
5343 id = parse_entity_expr(parse_entity_expr, idExpr);
5344 if (id == EntityBad)
5345 return false;
5346
5347 Entity src = EntityBad;
5348 if (!parse_src_expr(srcExpr, src))
5349 return false;
5350
5351 options.src(src);
5352 return true;
5353 }
5354 }
5355 }
5356
5357 id = parse_entity_expr(parse_entity_expr, expr);
5358 return id != EntityBad;
5359 };
5360
5361 auto add_term = [&](QueryOpKind op, std::span<const char> exprRaw) {
5362 auto expr = util::trim(exprRaw);
5363 if (expr.empty())
5364 return false;
5365
5366 bool isReadWrite = false;
5367 if (!expr.empty() && expr[0] == '&') {
5368 isReadWrite = true;
5369 expr = util::trim(expr.subspan(1));
5370 }
5371
5372 QueryTermOptions options{};
5373 if (isReadWrite)
5374 options.write();
5375
5376 Entity entity = EntityBad;
5377 if (!parse_term_expr(expr, entity, options))
5378 return false;
5379
5380 switch (op) {
5381 case QueryOpKind::All:
5382 all(entity, options);
5383 break;
5384 case QueryOpKind::Or:
5385 or_(entity, options);
5386 break;
5387 case QueryOpKind::Not:
5388 no(entity, options);
5389 break;
5390 case QueryOpKind::Any:
5391 any(entity, options);
5392 break;
5393 default:
5394 GAIA_ASSERT(false);
5395 return false;
5396 }
5397
5398 return true;
5399 };
5400
5401 auto process = [&]() {
5402 std::span<const char> exprRaw(&str[exp0], pos - exp0);
5403 exp0 = ++pos;
5404
5405 auto expr = util::trim(exprRaw);
5406 if (expr.empty())
5407 return true;
5408
5409 // OR-chain at top level maps to query::or_ terms.
5410 bool hasOrChain = false;
5411 {
5412 uint32_t depth = 0;
5413 const auto cnt = (uint32_t)expr.size();
5414 for (uint32_t i = 0; i + 1 < cnt; ++i) {
5415 const auto ch = expr[i];
5416 if (ch == '(')
5417 ++depth;
5418 else if (ch == ')') {
5419 GAIA_ASSERT(depth > 0);
5420 --depth;
5421 } else if (depth == 0 && ch == '|' && expr[i + 1] == '|') {
5422 hasOrChain = true;
5423 break;
5424 }
5425 }
5426 }
5427
5428 if (hasOrChain) {
5429 uint32_t depth = 0;
5430 uint32_t partBeg = 0;
5431 const auto cnt = (uint32_t)expr.size();
5432 for (uint32_t i = 0; i < cnt; ++i) {
5433 const auto ch = expr[i];
5434 if (ch == '(')
5435 ++depth;
5436 else if (ch == ')') {
5437 GAIA_ASSERT(depth > 0);
5438 --depth;
5439 }
5440
5441 const bool isOr = i + 1 < cnt && depth == 0 && ch == '|' && expr[i + 1] == '|';
5442 const bool isEnd = i + 1 == cnt;
5443 if (!isOr && !isEnd)
5444 continue;
5445
5446 const auto partEnd = isOr ? i : (i + 1);
5447 auto partExpr = expr.subspan(partBeg, partEnd - partBeg);
5448 if (!add_term(QueryOpKind::Or, partExpr))
5449 return false;
5450
5451 if (isOr) {
5452 partBeg = i + 2;
5453 ++i;
5454 }
5455 }
5456
5457 return true;
5458 }
5459
5460 QueryOpKind op = QueryOpKind::All;
5461 if (expr[0] == '?') {
5462 op = QueryOpKind::Any;
5463 expr = util::trim(expr.subspan(1));
5464 } else if (expr[0] == '!') {
5465 op = QueryOpKind::Not;
5466 expr = util::trim(expr.subspan(1));
5467 }
5468
5469 return add_term(op, expr);
5470 };
5471
5472 for (; str[pos] != 0; ++pos) {
5473 if (str[pos] == '(')
5474 ++parentDepth;
5475 else if (str[pos] == ')') {
5476 GAIA_ASSERT(parentDepth > 0);
5477 --parentDepth;
5478 } else if (str[pos] == ',' && parentDepth == 0) {
5479 if (!process())
5480 goto add_end;
5481 }
5482 }
5483 process();
5484
5485 add_end:
5486 va_end(args);
5487 return *this;
5488 }
5489
5494 // Add commands to the command buffer
5495 add_inter(item);
5496 return *this;
5497 }
5498
5499 //------------------------------------------------
5500
5505 QueryImpl& is(Entity entity, const QueryTermOptions& options = QueryTermOptions{}) {
5506 return all(Pair(Is, entity), options);
5507 }
5508
5509 //------------------------------------------------
5510
5516 options.in();
5517 return all(Pair(Is, entity), options);
5518 }
5519
5520 //------------------------------------------------
5521
5527 add_entity_term(QueryOpKind::All, entity, options);
5528 return *this;
5529 }
5530
5535 template <typename T>
5536 QueryImpl& all(const QueryTermOptions& options);
5537
5541 template <typename T>
5542 QueryImpl& all();
5543
5544 //------------------------------------------------
5545
5551 add_entity_term(QueryOpKind::Any, entity, options);
5552 return *this;
5553 }
5554
5559 template <typename T>
5560 QueryImpl& any(const QueryTermOptions& options);
5561
5565 template <typename T>
5566 QueryImpl& any();
5567
5568 //------------------------------------------------
5569
5576 add_entity_term(QueryOpKind::Or, entity, options);
5577 return *this;
5578 }
5579
5584 template <typename T>
5585 QueryImpl& or_(const QueryTermOptions& options);
5586
5590 template <typename T>
5591 QueryImpl& or_();
5592
5593 //------------------------------------------------
5594
5599 QueryImpl& no(Entity entity, const QueryTermOptions& options = QueryTermOptions{}) {
5600 add_entity_term(QueryOpKind::Not, entity, options);
5601 return *this;
5602 }
5603
5608 template <typename T>
5609 QueryImpl& no(const QueryTermOptions& options);
5610
5614 template <typename T>
5615 QueryImpl& no();
5616
5623 [[maybe_unused]] const bool ok = set_var_name_internal(varEntity, name);
5624 GAIA_ASSERT(ok);
5625 return *this;
5626 }
5628 QueryImpl& var_name(Entity varEntity, const char* name) {
5629 GAIA_ASSERT(name != nullptr);
5630 if (name == nullptr)
5631 return *this;
5632 return var_name(varEntity, util::str_view{name, (uint32_t)GAIA_STRLEN(name, 256)});
5633 }
5634
5639 QueryImpl& set_var(Entity varEntity, Entity value) {
5640 const bool ok = is_query_var_entity(varEntity);
5641 GAIA_ASSERT(ok);
5642 if (!ok)
5643 return *this;
5644
5645 const auto idx = query_var_idx(varEntity);
5646 m_varBindings[idx] = value;
5647 m_varBindingsMask |= (uint8_t(1) << idx);
5648 return *this;
5649 }
5654 const auto varEntity = find_var_by_name(name);
5655 GAIA_ASSERT(varEntity != EntityBad);
5656 if (varEntity == EntityBad)
5657 return *this;
5658 return set_var(varEntity, value);
5659 }
5661 QueryImpl& set_var(const char* name, Entity value) {
5662 GAIA_ASSERT(name != nullptr);
5663 if (name == nullptr)
5664 return *this;
5665 return set_var(util::str_view{name, (uint32_t)GAIA_STRLEN(name, 256)}, value);
5666 }
5667
5671 const bool ok = is_query_var_entity(varEntity);
5672 GAIA_ASSERT(ok);
5673 if (!ok)
5674 return *this;
5675
5676 const auto idx = query_var_idx(varEntity);
5677 m_varBindingsMask &= (uint8_t)~(uint8_t(1) << idx);
5678 return *this;
5679 }
5682 m_varBindingsMask = 0;
5683 return *this;
5684 }
5685
5686 //------------------------------------------------
5687
5692 changed_inter(entity);
5693 return *this;
5694 }
5695
5699 template <typename T>
5700 QueryImpl& changed();
5701
5702 //------------------------------------------------
5703
5709 QueryImpl& sort_by(Entity entity, TSortByFunc func) {
5710 sort_by_inter(entity, func);
5711 return *this;
5712 }
5713
5718 template <typename T>
5719 QueryImpl& sort_by(TSortByFunc func);
5720
5726 template <typename Rel, typename Tgt>
5727 QueryImpl& sort_by(TSortByFunc func);
5728
5729 //------------------------------------------------
5730
5731 class OrderByTravView final {
5732 QueryImpl* m_query = nullptr;
5733 Entity m_relation = EntityBad;
5734 TravOrder m_order = TravOrder::Down;
5735
5736 public:
5741 OrderByTravView(QueryImpl& query, Entity relation, TravOrder order):
5742 m_query(&query), m_relation(relation), m_order(order) {}
5743
5747 template <typename Func>
5748 void each(Func func) {
5749 m_query->each_walk(func, m_relation, m_order);
5750 }
5751 };
5752
5753 //------------------------------------------------
5754
5769 GAIA_NODISCARD OrderByTravView order_by(Entity relation, TravOrder order) {
5770 return OrderByTravView(*this, relation, order);
5771 }
5772
5778 template <typename Rel>
5779 GAIA_NODISCARD OrderByTravView order_by(TravOrder order);
5780
5781 //------------------------------------------------
5782
5790 QueryImpl& depth_order(Entity relation = ChildOf) {
5791 GAIA_ASSERT(!relation.pair());
5792 GAIA_ASSERT(world_supports_depth_order(*m_storage.world(), relation));
5793 group_by_inter(relation, group_by_func_depth_order, true);
5794 return *this;
5795 }
5796
5800 template <typename Rel>
5802
5803 //------------------------------------------------
5804
5811 QueryImpl& group_by(Entity entity, TGroupByFunc func = group_by_func_default) {
5812 group_by_inter(entity, func);
5813 return *this;
5814 }
5815
5822 template <typename T>
5823 QueryImpl& group_by(TGroupByFunc func = group_by_func_default);
5824
5832 template <typename Rel, typename Tgt>
5833 QueryImpl& group_by(TGroupByFunc func = group_by_func_default);
5834
5835 //------------------------------------------------
5836
5841 group_dep_inter(relation);
5842 return *this;
5843 }
5844
5848 template <typename Rel>
5850
5851 //------------------------------------------------
5852
5855 QueryImpl& group_id(GroupId groupId) {
5856 set_group_id_inter(groupId);
5857 return *this;
5858 }
5859
5863 GAIA_ASSERT(!entity.pair());
5864 set_group_id_inter(entity.id());
5865 return *this;
5866 }
5867
5870 template <typename T>
5872
5873 //------------------------------------------------
5874
5888 template <typename Func>
5889 GAIA_NODISCARD SchedJob job(Func func, QueryExecType execType) {
5890 if constexpr (detail::is_query_iter_callback_v<Func>) {
5891 switch (execType) {
5892 case QueryExecType::Parallel:
5893 return add_iter_parallel_job<Func, QueryExecType::Parallel>(GAIA_MOV(func));
5894 case QueryExecType::ParallelPerf:
5895 return add_iter_parallel_job<Func, QueryExecType::ParallelPerf>(GAIA_MOV(func));
5896 case QueryExecType::ParallelEff:
5897 return add_iter_parallel_job<Func, QueryExecType::ParallelEff>(GAIA_MOV(func));
5898 default:
5899 break;
5900 }
5901 } else {
5902 switch (execType) {
5903 case QueryExecType::Parallel:
5904 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::Parallel>(
5905 TypedJobCallback<Func>{this, GAIA_MOV(func)});
5906 case QueryExecType::ParallelPerf:
5907 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelPerf>(
5908 TypedJobCallback<Func>{this, GAIA_MOV(func)});
5909 case QueryExecType::ParallelEff:
5910 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelEff>(
5911 TypedJobCallback<Func>{this, GAIA_MOV(func)});
5912 default:
5913 break;
5914 }
5915 }
5916
5917 return add_query_task_job(GAIA_MOV(func), execType);
5918 }
5919
5924 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
5925 void each(Func func) {
5926 each_runtime_inter<QueryExecType::Default, Func>(func, Constraints::EnabledOnly);
5927 }
5928
5929 template <typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>, int> = 0>
5930 void each(Func func);
5931
5937 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
5938 void each(Func func, QueryExecType execType) {
5939 each(func, execType, Constraints::EnabledOnly);
5940 }
5941
5942 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
5943 void each(Func func, Constraints constraints) {
5944 each(func, QueryExecType::Default, constraints);
5945 }
5946
5947 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
5948 void each(Func func, QueryExecType execType, Constraints constraints) {
5949 switch (execType) {
5950 case QueryExecType::Parallel:
5951 each_runtime_inter<QueryExecType::Parallel, Func>(func, constraints);
5952 break;
5953 case QueryExecType::ParallelPerf:
5954 each_runtime_inter<QueryExecType::ParallelPerf, Func>(func, constraints);
5955 break;
5956 case QueryExecType::ParallelEff:
5957 each_runtime_inter<QueryExecType::ParallelEff, Func>(func, constraints);
5958 break;
5959 default:
5960 each_runtime_inter<QueryExecType::Default, Func>(func, constraints);
5961 break;
5962 }
5963 }
5964
5965 template <typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>, int> = 0>
5966 void each(Func func, QueryExecType execType);
5967
5974 template <typename Func>
5975 void each_iter(Iter& it, Func func);
5976
5977 void each_iter_erased(
5978 QueryExecType execType, void* pFunc, const TypedQueryExecState& state,
5979 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
5980 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
5981
5982 void each_iter_erased(
5983 Iter& it, void* pFunc, const TypedQueryExecState& state,
5984 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
5985 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
5986
5987 //------------------------------------------------
5988
5994 template <typename Func>
5995 void each_arch(Func func, Constraints constraints = Constraints::EnabledOnly) {
5996 auto& queryInfo = fetch();
5997 match_all(queryInfo);
5998 run_query_on_archetypes<QueryExecType::Default>(
5999 queryInfo,
6000 [&](Iter& it) {
6001 GAIA_PROF_SCOPE(query_func_a);
6002 it.ctx(m_ctx);
6003 func(it);
6004 },
6005 constraints);
6006 }
6007
6008 //------------------------------------------------
6009
6018 bool empty(Constraints constraints = Constraints::EnabledOnly) {
6019 auto& queryInfo = fetch();
6020 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6021 return empty_inter<false>(queryInfo, constraints);
6022 }
6023
6024 match_all(queryInfo);
6025
6026 const bool hasFilters = queryInfo.has_filters();
6027 if (hasFilters) {
6028 return empty_inter<true>(queryInfo, constraints);
6029 } else {
6030 return empty_inter<false>(queryInfo, constraints);
6031 }
6032 }
6033
6041 uint32_t count(Constraints constraints = Constraints::EnabledOnly) {
6042 auto& queryInfo = fetch();
6043 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6044 return count_inter<false>(queryInfo, constraints);
6045 }
6046
6047 match_all(queryInfo);
6048
6049 const bool hasFilters = queryInfo.has_filters();
6050 return hasFilters ? count_inter<true>(queryInfo, constraints) : count_inter<false>(queryInfo, constraints);
6051 }
6052
6056 void each_entity_enabled(void* pCtx, void (*func)(void*, Entity)) {
6057 auto& queryInfo = fetch();
6058 match_all(queryInfo);
6059 ::gaia::ecs::update_version(*m_worldVersion);
6060
6061 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6062 auto& world = *queryInfo.world();
6063 if (has_only_direct_or_terms(queryInfo)) {
6064 for_each_direct_or_union(world, queryInfo, Constraints::EnabledOnly, [&](Entity entity) {
6065 func(pCtx, entity);
6066 return true;
6067 });
6068 } else {
6069 const auto plan = direct_entity_seed_plan(world, queryInfo);
6070 (void)for_each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](Entity entity) {
6071 func(pCtx, entity);
6072 return true;
6073 });
6074 }
6075
6076 m_changedWorldVersion = *m_worldVersion;
6077 return;
6078 }
6079
6080 const bool hasFilters = queryInfo.has_filters();
6081 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
6082 const auto cacheView = queryInfo.cache_archetype_view();
6083 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, Constraints::EnabledOnly);
6084 if (needsBarrierCache)
6086
6087 const auto cacheRange = selected_query_cache_range(queryInfo);
6088 if (!cacheRange.valid) {
6089 m_changedWorldVersion = *m_worldVersion;
6090 return;
6091 }
6092 const auto idxFrom = cacheRange.idxFrom;
6093 const auto idxTo = cacheRange.idxTo;
6094
6095 Iter it;
6096 it.init_query_state(queryInfo.world(), Constraints::EnabledOnly, false);
6097 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6098 const auto* pArchetype = cacheView[qi];
6099 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
6100 if GAIA_UNLIKELY (!can_process_archetype_inter(
6101 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6102 continue;
6103
6104 const auto& chunks = pArchetype->chunks();
6105 if (!hasEntityFilters) {
6106 for (auto* pChunk: chunks) {
6107 const auto from = Iter::start_index(pChunk);
6108 const auto to = Iter::end_index(pChunk);
6109 if (from == to)
6110 continue;
6111 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6112 continue;
6113
6114 const auto entityCnt = (uint32_t)(to - from);
6115 const auto entities = pChunk->entity_view();
6116 GAIA_FOR(entityCnt) {
6117 func(pCtx, entities[from + i]);
6118 }
6119 }
6120 continue;
6121 }
6122
6123 it.set_archetype(pArchetype);
6124 for (auto* pChunk: chunks) {
6125 it.set_chunk(pChunk);
6126 const auto entityCnt = it.size();
6127 if (entityCnt == 0)
6128 continue;
6129 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6130 continue;
6131
6132 const auto entities = it.view<Entity>();
6133 GAIA_FOR(entityCnt) {
6134 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
6135 func(pCtx, entities[i]);
6136 }
6137 }
6138 }
6139
6140 m_changedWorldVersion = *m_worldVersion;
6141 }
6142
6143 void collect_entities_enabled(cnt::darray<Entity>& out) {
6144 auto& queryInfo = fetch();
6145 match_all(queryInfo);
6146 ::gaia::ecs::update_version(*m_worldVersion);
6147
6148 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6149 auto& world = *queryInfo.world();
6150 if (has_only_direct_or_terms(queryInfo)) {
6151 for_each_direct_or_union(world, queryInfo, Constraints::EnabledOnly, [&](Entity entity) {
6152 out.push_back(entity);
6153 return true;
6154 });
6155 } else {
6156 const auto plan = direct_entity_seed_plan(world, queryInfo);
6157 (void)for_each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](Entity entity) {
6158 out.push_back(entity);
6159 return true;
6160 });
6161 }
6162
6163 m_changedWorldVersion = *m_worldVersion;
6164 return;
6165 }
6166
6167 const bool hasFilters = queryInfo.has_filters();
6168 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
6169 const auto cacheView = queryInfo.cache_archetype_view();
6170 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, Constraints::EnabledOnly);
6171 if (needsBarrierCache)
6173
6174 const auto cacheRange = selected_query_cache_range(queryInfo);
6175 if (!cacheRange.valid) {
6176 m_changedWorldVersion = *m_worldVersion;
6177 return;
6178 }
6179 const auto idxFrom = cacheRange.idxFrom;
6180 const auto idxTo = cacheRange.idxTo;
6181
6182 Iter it;
6183 it.init_query_state(queryInfo.world(), Constraints::EnabledOnly, false);
6184 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6185 const auto* pArchetype = cacheView[qi];
6186 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
6187 if GAIA_UNLIKELY (!can_process_archetype_inter(
6188 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6189 continue;
6190
6191 const auto& chunks = pArchetype->chunks();
6192 if (!hasEntityFilters) {
6193 for (auto* pChunk: chunks) {
6194 const auto from = Iter::start_index(pChunk);
6195 const auto to = Iter::end_index(pChunk);
6196 if (from == to)
6197 continue;
6198 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6199 continue;
6200
6201 const auto oldSize = out.size();
6202 const auto entityCnt = (uint32_t)(to - from);
6203 const auto entities = pChunk->entity_view();
6204 out.resize(oldSize + entityCnt);
6205 GAIA_FOR(entityCnt) {
6206 out[oldSize + i] = entities[from + i];
6207 }
6208 }
6209 continue;
6210 }
6211
6212 it.set_archetype(pArchetype);
6213 for (auto* pChunk: chunks) {
6214 it.set_chunk(pChunk);
6215 const auto entityCnt = it.size();
6216 if (entityCnt == 0)
6217 continue;
6218 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6219 continue;
6220
6221 const auto entities = it.view<Entity>();
6222 GAIA_FOR(entityCnt) {
6223 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
6224 out.push_back(entities[i]);
6225 }
6226 }
6227 }
6228
6229 m_changedWorldVersion = *m_worldVersion;
6230 }
6231
6236 template <typename Container>
6237 void arr(Container& outArray, Constraints constraints = Constraints::EnabledOnly);
6238
6246 QueryInfo& queryInfo, Entity relation, TravOrder order,
6247 Constraints constraints = Constraints::EnabledOnly) {
6248 struct OrderedWalkTargetCtx {
6249 const cnt::darray<Entity>* pEntities = nullptr;
6250 uint32_t cnt = 0;
6251 uint32_t dependentIdx = 0;
6252 cnt::darray<uint32_t>* pIndegree = nullptr;
6253 cnt::darray<uint32_t>* pOutdegree = nullptr;
6254 cnt::darray<uint32_t>* pWriteCursor = nullptr;
6255 cnt::darray<uint32_t>* pEdges = nullptr;
6256 uint32_t* pEdgeCnt = nullptr;
6257
6258 GAIA_NODISCARD static uint32_t
6259 find_entity_idx(const cnt::darray<Entity>& entities, uint32_t cnt, Entity entity) {
6260 const auto targetId = entity.id();
6261 uint32_t low = 0;
6262 uint32_t high = cnt;
6263 while (low < high) {
6264 const uint32_t mid = low + ((high - low) >> 1);
6265 if (entities[mid].id() < targetId)
6266 low = mid + 1;
6267 else
6268 high = mid;
6269 }
6270
6271 if (low < cnt && entities[low].id() == targetId)
6272 return low;
6273 return cnt;
6274 }
6275
6276 static void count_edge(void* rawCtx, Entity dependency) {
6277 auto& ctx = *static_cast<OrderedWalkTargetCtx*>(rawCtx);
6278 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6279 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6280 return;
6281
6282 ++(*ctx.pOutdegree)[dependencyIdx];
6283 ++(*ctx.pIndegree)[ctx.dependentIdx];
6284 ++*ctx.pEdgeCnt;
6285 }
6286
6287 static void write_edge(void* rawCtx, Entity dependency) {
6288 auto& ctx = *static_cast<OrderedWalkTargetCtx*>(rawCtx);
6289 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6290 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6291 return;
6292
6293 (*ctx.pEdges)[(*ctx.pWriteCursor)[dependencyIdx]++] = ctx.dependentIdx;
6294 }
6295 };
6296
6297 auto& walkData = ensure_each_walk_data();
6298 auto& world = *m_storage.world();
6299 const uint32_t relationVersion = world_rel_version(world, relation);
6300 const uint32_t worldVersion = ::gaia::ecs::world_version(world);
6301
6302 const bool needsTraversalBarrierState =
6303 constraints == Constraints::EnabledOnly && ::gaia::ecs::valid(world, relation);
6304 auto survives_disabled_barrier = [&](Entity entity) {
6305 if (!needsTraversalBarrierState)
6306 return true;
6307
6308 auto curr = entity;
6309 GAIA_FOR(MAX_TRAV_DEPTH) {
6310 const auto next = target(world, curr, relation);
6311 if (next == EntityBad || next == curr)
6312 break;
6313 if (!world_entity_enabled(world, next))
6314 return false;
6315 curr = next;
6316 }
6317
6318 return true;
6319 };
6320
6321 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
6322 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
6323 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
6324 !queryInfo.has_filters()) {
6325 auto& chunks = walkData.scratchChunks;
6326 chunks.clear();
6327
6328 bool chunkChanged = false;
6329 for (auto* pArchetype: queryInfo) {
6330 if (pArchetype == nullptr || !can_process_archetype(queryInfo, *pArchetype))
6331 continue;
6332
6333 for (const auto* pChunk: pArchetype->chunks()) {
6334 if (pChunk == nullptr)
6335 continue;
6336
6337 chunks.push_back(pChunk);
6338 if (!chunkChanged && pChunk->changed(walkData.cachedEntityVersion))
6339 chunkChanged = true;
6340 }
6341 }
6342
6343 bool sameChunks = chunks.size() == walkData.cachedChunks.size();
6344 if (sameChunks) {
6345 for (uint32_t i = 0; i < (uint32_t)chunks.size(); ++i) {
6346 if (chunks[i] != walkData.cachedChunks[i]) {
6347 sameChunks = false;
6348 break;
6349 }
6350 }
6351 }
6352
6353 if (sameChunks && !chunkChanged) {
6354 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
6355 }
6356 }
6357
6358 auto& entities = walkData.scratchEntities;
6359 entities.clear();
6360 arr(entities, constraints);
6361 if (entities.empty())
6362 return {};
6363
6364 if (needsTraversalBarrierState) {
6365 uint32_t writeIdx = 0;
6366 const auto cnt = (uint32_t)entities.size();
6367 GAIA_FOR(cnt) {
6368 const auto entity = entities[i];
6369 if (!survives_disabled_barrier(entity))
6370 continue;
6371 entities[writeIdx++] = entity;
6372 }
6373 entities.resize(writeIdx);
6374 if (entities.empty())
6375 return {};
6376 }
6377
6378 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
6379 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
6380 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
6381 entities.size() == walkData.cachedInput.size()) {
6382 bool sameInput = true;
6383 for (uint32_t i = 0; i < (uint32_t)entities.size(); ++i) {
6384 if (entities[i] != walkData.cachedInput[i]) {
6385 sameInput = false;
6386 break;
6387 }
6388 }
6389
6390 if (sameInput) {
6391 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
6392 }
6393 }
6394
6395 auto& ordered = walkData.cachedOutput;
6396 walkData.cachedInput = entities;
6397 ordered.clear();
6398 if (!::gaia::ecs::valid(world, relation)) {
6399 core::sort(entities, [](Entity left, Entity right) {
6400 return left.id() < right.id();
6401 });
6402 ordered = entities;
6403 } else {
6404 // Keep execution deterministic regardless of archetype iteration order.
6405 core::sort(entities, [](Entity left, Entity right) {
6406 return left.id() < right.id();
6407 });
6408
6409 const auto cnt = (uint32_t)entities.size();
6410
6411 auto& indegree = walkData.scratchIndegree;
6412 indegree.resize(cnt);
6413 auto& outdegree = walkData.scratchOutdegree;
6414 outdegree.resize(cnt);
6415 for (uint32_t i = 0; i < cnt; ++i) {
6416 indegree[i] = 0;
6417 outdegree[i] = 0;
6418 }
6419
6420 uint32_t edgeCnt = 0;
6421 OrderedWalkTargetCtx edgeCtx;
6422 edgeCtx.pEntities = &entities;
6423 edgeCtx.cnt = cnt;
6424 edgeCtx.pIndegree = &indegree;
6425 edgeCtx.pOutdegree = &outdegree;
6426 edgeCtx.pEdgeCnt = &edgeCnt;
6427 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
6428 const auto dependent = entities[dependentIdx];
6429 edgeCtx.dependentIdx = dependentIdx;
6430 world_for_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::count_edge);
6431 }
6432
6433 auto& offsets = walkData.scratchOffsets;
6434 offsets.resize(cnt + 1);
6435 offsets[0] = 0;
6436 for (uint32_t i = 0; i < cnt; ++i)
6437 offsets[i + 1] = offsets[i] + outdegree[i];
6438
6439 auto& writeCursor = walkData.scratchWriteCursor;
6440 writeCursor.resize(cnt);
6441 for (uint32_t i = 0; i < cnt; ++i)
6442 writeCursor[i] = offsets[i];
6443
6444 auto& edges = walkData.scratchEdges;
6445 edges.resize(edgeCnt);
6446 edgeCtx.pWriteCursor = &writeCursor;
6447 edgeCtx.pEdges = &edges;
6448 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
6449 const auto dependent = entities[dependentIdx];
6450 edgeCtx.dependentIdx = dependentIdx;
6451 world_for_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::write_edge);
6452 }
6453
6454 ordered.reserve(cnt);
6455
6456 const bool isUp = order == TravOrder::Up || order == TravOrder::ReverseUp;
6457 const bool needsReverse = order == TravOrder::ReverseUp || order == TravOrder::ReverseDown;
6458
6459 auto& visited = walkData.scratchWriteCursor;
6460 visited.resize(cnt);
6461 for (uint32_t i = 0; i < cnt; ++i)
6462 visited[i] = 0;
6463
6464 auto& stack = walkData.scratchCurrLevel;
6465 stack.clear();
6466 auto& cursorStack = walkData.scratchNextLevel;
6467 cursorStack.clear();
6468
6469 auto append_from_root = [&](uint32_t rootIdx) {
6470 stack.push_back(rootIdx);
6471 cursorStack.push_back(offsets[rootIdx]);
6472
6473 while (!stack.empty()) {
6474 const auto idx = stack.back();
6475 if (visited[idx] == 0) {
6476 visited[idx] = 1;
6477 if (!isUp)
6478 ordered.push_back(entities[idx]);
6479 }
6480
6481 bool pushedChild = false;
6482 auto& cursor = cursorStack.back();
6483 while (cursor < offsets[idx + 1]) {
6484 const auto childIdx = edges[cursor++];
6485 if (visited[childIdx] != 0)
6486 continue;
6487
6488 stack.push_back(childIdx);
6489 cursorStack.push_back(offsets[childIdx]);
6490 pushedChild = true;
6491 break;
6492 }
6493
6494 if (pushedChild)
6495 continue;
6496
6497 if (isUp)
6498 ordered.push_back(entities[idx]);
6499 visited[idx] = 2;
6500 stack.pop_back();
6501 cursorStack.pop_back();
6502 }
6503 };
6504
6505 for (uint32_t i = 0; i < cnt; ++i) {
6506 if (indegree[i] == 0 && visited[i] == 0)
6507 append_from_root(i);
6508 }
6509
6510 // Cycles are invalid relationship data, but keep traversal deterministic by visiting leftovers by id.
6511 for (uint32_t i = 0; i < cnt; ++i) {
6512 if (visited[i] == 0)
6513 append_from_root(i);
6514 }
6515
6516 if (needsReverse) {
6517 const auto orderedCnt = (uint32_t)ordered.size();
6518 for (uint32_t i = 0; i < orderedCnt / 2; ++i)
6519 core::swap(ordered[i], ordered[orderedCnt - i - 1]);
6520 }
6521 }
6522
6523 walkData.cachedRelation = relation;
6524 walkData.cachedOrder = order;
6525 walkData.cachedConstraints = constraints;
6526 walkData.cachedRelationVersion = relationVersion;
6527 walkData.cachedEntityVersion = ::gaia::ecs::world_version(world);
6528 walkData.cachedRuns.clear();
6529
6530 {
6531 const auto orderedCnt = (uint32_t)ordered.size();
6532 if (orderedCnt != 0) {
6533 for (uint32_t i = 0; i < orderedCnt; ++i) {
6534 const auto& ec = ::gaia::ecs::fetch(world, ordered[i]);
6535 if (walkData.cachedRuns.empty()) {
6536 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
6537 continue;
6538 }
6539
6540 auto& run = walkData.cachedRuns.back();
6541 if (ec.pChunk == run.pChunk && ec.row == run.to) {
6542 run.to = (uint16_t)(run.to + 1);
6543 } else {
6544 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
6545 }
6546 }
6547 }
6548 }
6549
6550 if (!queryInfo.has_filters()) {
6551 auto& chunks = walkData.scratchChunks;
6552 chunks.clear();
6553 for (auto* pArchetype: queryInfo) {
6554 if (pArchetype == nullptr || !can_process_archetype(queryInfo, *pArchetype))
6555 continue;
6556
6557 for (const auto* pChunk: pArchetype->chunks()) {
6558 if (pChunk == nullptr)
6559 continue;
6560 chunks.push_back(pChunk);
6561 }
6562 }
6563 walkData.cachedChunks = chunks;
6564 } else
6565 walkData.cachedChunks.clear();
6566 walkData.cacheValid = true;
6567
6568 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
6569 }
6570
6580 template <typename Func, std::enable_if_t<detail::is_query_walk_core_callback_v<Func>, int> = 0>
6582 Func func, Entity relation, TravOrder order = TravOrder::Down,
6583 Constraints constraints = Constraints::EnabledOnly) {
6584 auto& queryInfo = fetch();
6585 match_all(queryInfo);
6586 const auto ordered = ordered_entities_walk(queryInfo, relation, order, constraints);
6587
6588 if constexpr (std::is_invocable_v<Func, Iter&>) {
6589 each_direct_entities_iter(queryInfo, ordered, constraints, func);
6590 } else if constexpr (std::is_invocable_v<Func, const Entity&> || std::is_invocable_v<Func, Entity>) {
6591 for (const auto entity: ordered)
6592 func(entity);
6593 }
6594 }
6595
6596 template <typename Func, std::enable_if_t<!detail::is_query_walk_core_callback_v<Func>, int> = 0>
6597 void each_walk(
6598 Func func, Entity relation, TravOrder order = TravOrder::Down,
6599 Constraints constraints = Constraints::EnabledOnly);
6600
6601 //------------------------------------------------
6602
6604 void diag() {
6605 // Make sure matching happened
6606 auto& queryInfo = fetch();
6607 match_all(queryInfo);
6608 if (uses_shared_cache_layer())
6609 GAIA_LOG_N("BEG DIAG Query %u.%u [S]", id(), gen());
6610 else if (uses_query_cache_storage())
6611 GAIA_LOG_N("BEG DIAG Query %u.%u [L]", id(), gen());
6612 else
6613 GAIA_LOG_N("BEG DIAG Query [U]");
6614 for (const auto* pArchetype: queryInfo)
6615 Archetype::diag_basic_info(*m_storage.world(), *pArchetype);
6616 GAIA_LOG_N("END DIAG Query");
6617 }
6618
6621 GAIA_NODISCARD util::str bytecode() {
6622 auto& queryInfo = fetch();
6623 return queryInfo.bytecode();
6624 }
6625
6628 const auto dump = bytecode();
6629 if (uses_shared_cache_layer())
6630 GAIA_LOG_N("BEG DIAG Query Bytecode %u.%u [S]", id(), gen());
6631 else if (uses_query_cache_storage())
6632 GAIA_LOG_N("BEG DIAG Query Bytecode %u.%u [L]", id(), gen());
6633 else
6634 GAIA_LOG_N("BEG DIAG Query Bytecode [U]");
6635 GAIA_LOG_N("%.*s", (int)dump.size(), dump.data());
6636 GAIA_LOG_N("END DIAG Query");
6637 }
6638 };
6639 } // namespace detail
6640
6641 using Query = detail::QueryImpl;
6642 } // namespace ecs
6643} // namespace gaia
6644
6645#include "gaia/ecs/query_builder_typed.inl"
6646#include "gaia/ecs/query_typed.inl"
Array with variable size of elements of type.
Definition darray_impl.h:25
Definition span_impl.h:99
Definition archetype.h:83
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:839
GAIA_NODISCARD bool is_req_del() const
Returns true if this archetype is requested to be deleted.
Definition archetype.h:1158
Definition chunk.h:35
GAIA_NODISCARD uint16_t size() const
Returns the total number of entities in the chunk (both enabled and disabled)
Definition chunk.h:1799
GAIA_NODISCARD bool entity_order_changed(uint32_t requiredVersion) const
Returns true if entity order changed since requiredVersion. This is narrower than changed(requiredVer...
Definition chunk.h:1847
GAIA_NODISCARD bool changed(uint32_t requiredVersion) const
Returns true if the provided version is newer than the one stored internally. Use when checking if th...
Definition chunk.h:1831
GAIA_NODISCARD bool empty() const
Checks is there are any entities in the chunk.
Definition chunk.h:1804
GAIA_NODISCARD const ComponentCacheItem & get(Entity entity) const noexcept
Returns the component cache item.
Definition component_cache.h:408
Iterator for iterating entity subsets selected by Constraints. Disabled entities always precede enabl...
Definition chunk_iterator.h:1469
Definition query_cache.h:61
QueryInfo & add(QueryCtx &&ctx, const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes)
Registers the provided query lookup context ctx. If it already exists it is returned.
Definition query_cache.h:286
bool del(QueryHandle handle)
Deletes an existing QueryInfo object given the provided query handle.
Definition query_cache.h:337
QueryInfo * try_get(QueryHandle handle)
Returns a QueryInfo object associated with handle.
Definition query_cache.h:183
QueryInfo & add_local(QueryCtx &&ctx, const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes)
Registers a cached query without deduplicating against the shared lookup map.
Definition query_cache.h:319
Definition query_info.h:100
GAIA_NODISCARD GroupId group_id(uint32_t archetypeIdx) const
Returns the cached group id for a matched archetype index.
Definition query_info.h:2254
GAIA_NODISCARD bool can_direct_entity_seed_eval_shape() const
Returns true when the query shape is eligible for direct entity seed evaluation.
Definition query_info.h:2149
void reset()
Resets cached query state for slot reuse while keeping the compiled context object.
Definition query_info.h:856
GAIA_NODISCARD bool has_only_direct_or_terms() const
Returns true when the query contains only direct OR/NOT terms and at least one OR term.
Definition query_info.h:2154
GAIA_NODISCARD bool barrier_may_prune() const
Returns true when any cached archetype can be pruned by the hierarchy barrier.
Definition query_info.h:2271
GAIA_NODISCARD bool barrier_passes(uint32_t archetypeIdx) const
Returns true when the matched archetype passes the depth-order hierarchy barrier.
Definition query_info.h:2262
GAIA_NODISCARD util::str bytecode() const
Returns a textual dump of the compiled VM bytecode.
Definition query_info.h:2101
GAIA_NODISCARD std::span< const Archetype * > cache_archetype_view() const
Returns the cached result archetypes as a span.
Definition query_info.h:2307
GAIA_NODISCARD bool has_inherited_data_payload() const
Returns true when query iteration needs cached inherited data payloads.
Definition query_info.h:1651
std::span< const uint8_t > try_indices_mapping_view(const Archetype *pArchetype) const
Returns a cached indices mapping view for an exact archetype match, or an empty span when absent.
Definition query_info.h:2232
GAIA_NODISCARD bool matches_prefab_entities() const
Returns true when prefab-tagged entities should participate in query results.
Definition query_info.h:2159
InheritedTermDataView inherited_data_view(uint32_t archetypeIdx) const
Returns cached inherited-term data for a matched archetype index.
Definition query_info.h:2207
GAIA_NODISCARD QueryCtx::CachePolicy cache_policy() const
Returns the query cache policy selected during compilation.
Definition query_info.h:964
GAIA_NODISCARD bool has_filters() const
Returns true when the query has per-entity changed/filter terms.
Definition query_info.h:2116
GAIA_NODISCARD bool has_grouped_payload() const
Returns true when grouped-query payloads are active for this query.
Definition query_info.h:969
GAIA_NODISCARD World * world()
Returns the mutable world owning this query.
Definition query_info.h:2072
GAIA_NODISCARD Entity direct_target_eval_id() const
Returns the concrete direct target id used by direct target evaluation.
Definition query_info.h:2139
void add_ref()
Adds one external reference to this query slot.
Definition query_info.h:833
GAIA_NODISCARD bool has_sorted_payload() const
Returns true when sorted-query payloads are active for this query.
Definition query_info.h:974
GAIA_NODISCARD bool has_entity_filter_terms() const
Returns true when direct non-fragmenting terms must be rechecked per entity.
Definition query_info.h:2122
GAIA_NODISCARD QueryCtx & ctx()
Returns the mutable compiled query context.
Definition query_info.h:2092
std::span< const uint8_t > indices_mapping_view(uint32_t archetypeIdx) const
Returns a view of indices mapping for component entities in a given archetype.
Definition query_info.h:2199
static GAIA_NODISCARD QueryHandle handle(const QueryInfo &info)
Builds a stable query handle from query slot metadata.
Definition query_info.h:943
void ensure_depth_order_hierarchy_barrier_cache()
Ensures depth-order hierarchy barrier results are current before public reads.
Definition query_info.h:2227
GAIA_NODISCARD bool can_direct_target_eval() const
Returns true when the query can evaluate concrete target entities directly.
Definition query_info.h:2144
GAIA_NODISCARD QueryCtx::DirectTargetEvalKind direct_target_eval_kind() const
Returns the direct target evaluation mode selected during compilation.
Definition query_info.h:2134
GAIA_NODISCARD const GroupData * selected_group_data(GroupId runtimeGroupId) const
Returns cached group bounds for the currently selected group filter. The cached range is invalidated ...
Definition query_info.h:1937
static GAIA_NODISCARD QueryInfo create(QueryId id, QueryCtx &&ctx, const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes)
Creates and compiles a query info object from a moved query context.
Definition query_info.h:898
Move-only wrapper for scheduler-owned ECS work.
Definition sched.h:118
Definition world.h:174
void init_query_state(const World *pWorld, Constraints constraints, bool writeIm)
Initializes stable query execution state stored by the iterator.
Definition chunk_iterator.h:998
void ctx(void *pCtx)
Sets the user-owned context pointer visible through ctx().
Definition chunk_iterator.h:1095
GAIA_NODISCARD auto view() const
Returns a read-only entity or component view for the owned chunk-backed fast path....
Definition chunk_iterator.h:1192
void set_query_chunk(const Archetype *pArchetype, const uint8_t *pCompIndices, Chunk *pChunk, uint16_t from, uint16_t to)
Binds the iterator to the next query chunk and keeps archetype/mapping state on the iterator.
Definition chunk_iterator.h:1011
OrderByTravView(QueryImpl &query, Entity relation, TravOrder order)
Creates a query traversal view.
Definition query.h:5741
void each(Func func)
Iterates the query result through the requested relation order.
Definition query.h:5748
Definition query.h:500
QueryImpl & sort_by(Entity entity, TSortByFunc func)
Sorts the query by the specified entity and function.
Definition query.h:5709
static void run_query_func(World *pWorld, Func func, ChunkBatch &batch)
Executes an iterator callback for one prepared chunk batch.
Definition query.h:2151
static void invoke_runtime_iter(void *pFunc, TIter &it)
Invokes a type-erased public iterator callback.
Definition query.h:3710
QueryImpl & scope(QueryCacheScope cacheScope)
Sets the cache scope used by cached queries.
Definition query.h:1183
GAIA_NODISCARD QueryAccess access(Entity entity)
Returns the effective read/write access for an id.
Definition query.h:1139
static GAIA_NODISCARD bool match_filters(const Chunk &chunk, const QueryInfo &queryInfo, uint32_t changedWorldVersion)
Returns whether a chunk passes the query's changed filters.
Definition query.h:1825
static void chunk_effective_range(Chunk *pChunk, Constraints constraints, bool needsBarrierCache, bool barrierPasses, uint16_t &from, uint16_t &to) noexcept
Calculates the row range of a chunk after applying row constraints and depth-order barrier state.
Definition query.h:1914
void arr(Container &outArray, Constraints constraints=Constraints::EnabledOnly)
Appends all components or entities matching the query to the output array.
Definition query_typed.inl:1351
QueryImpl & or_()
Adds an OR typed term.
Definition query_builder_typed.inl:74
static GAIA_NODISCARD bool is_empty_direct_or_union(const World &world, const QueryInfo &queryInfo, Constraints constraints)
Returns whether the direct OR union becomes empty after applying NOT terms and iterator constraints.
Definition query.h:4415
void reset()
Release any data allocated by the query.
Definition query.h:5145
GAIA_NODISCARD bool matches_target_entities(QueryInfo &queryInfo, const Archetype &archetype, EntitySpan targetEntities)
Returns whether any of the provided target entities matches the query semantics.
Definition query.h:4705
GAIA_NODISCARD QueryPlan prepare_query_plan(const QueryInfo &queryInfo, Constraints constraints) const
Selects the prepared execution plan for public iterator callbacks.
Definition query.h:3532
QueryImpl & set_var(util::str_view name, Entity value)
Binds a named query variable to a concrete entity value.
Definition query.h:5653
static GAIA_NODISCARD bool needs_depth_order_hierarchy_barrier_cache(const QueryInfo &queryInfo, Constraints constraints)
Checks whether the current row constraints require the depth-order hierarchy barrier cache.
Definition query.h:1903
GAIA_NODISCARD util::str bytecode()
Returns a textual dump of the generated query VM bytecode.
Definition query.h:6621
QueryImpl & group_dep(Entity relation)
Declares an explicit relation dependency for grouped cache invalidation. Useful for custom group_by c...
Definition query.h:5840
static void run_query_func(World *pWorld, Func func, std::span< ChunkBatch > batches)
Executes an iterator callback over a contiguous list of prepared chunk batches.
Definition query.h:2193
QueryImpl & reads()
Declares an additional component or pair type read by this query callback.
Definition query.h:1095
GAIA_NODISCARD QueryPlan prepare_query_plan(const QueryInfo &queryInfo, const TypedQueryExecState &state) const
Selects the prepared execution plan for typed callbacks.
Definition query_typed.inl:658
GAIA_NODISCARD uint32_t gen() const
Returns the cache handle generation of this query.
Definition query.h:5136
static GAIA_NODISCARD bool match_direct_entity_constraints(const World &world, const QueryInfo &queryInfo, Entity entity, Constraints constraints)
Applies iterator-specific entity state constraints to the direct seeded path.
Definition query.h:4294
GAIA_NODISCARD SchedJob job(Func func, QueryExecType execType)
Adds a query execution job without submitting it.
Definition query.h:5889
QueryImpl & kind(QueryCacheKind cacheKind)
Sets the hard cache-kind requirement for the query.
Definition query.h:1168
GAIA_NODISCARD bool matches_any(QueryInfo &queryInfo, const Archetype &archetype, EntitySpan targetEntities)
Returns whether any supplied target entity matches the query on archetype.
Definition query.h:982
static GAIA_NODISCARD bool has_only_direct_or_terms(const QueryInfo &queryInfo)
Detects the special direct OR/NOT shape that can be answered from a union of direct term entity sets.
Definition query.h:3964
GAIA_NODISCARD bool can_use_direct_chunk_iteration_fastpath(const QueryInfo &queryInfo) const
Checks whether typed callbacks can use dense chunk iteration while preserving required cache ordering...
Definition query.h:3449
QueryImpl & set_var(const char *name, Entity value)
Binds a named query variable to a concrete entity value.
Definition query.h:5661
static GAIA_NODISCARD bool survives_cascade_hierarchy_enabled_barrier(const QueryInfo &queryInfo, const Archetype &archetype)
Fast enabled-subtree gate for cached depth_order(...) queries over fragmenting hierarchy relations....
Definition query.h:1944
void each_arch(Func func, Constraints constraints=Constraints::EnabledOnly)
Iterates matching archetypes instead of individual entities.
Definition query.h:5995
static GAIA_NODISCARD bool can_use_direct_entity_seed_eval(const QueryInfo &queryInfo)
Detects queries that can skip archetype seeding and start directly from entity-backed term indices.
Definition query.h:3941
static GAIA_NODISCARD bool uses_semantic_is_matching(const QueryTerm &term)
Returns whether a term uses semantic Is matching rather than direct storage matching.
Definition query.h:3832
GAIA_NODISCARD bool can_run_parallel(QueryImpl &other)
Returns whether this query can run concurrently with another query based on declared scheduling metad...
Definition query.h:1160
static GAIA_NODISCARD uint32_t count_direct_entity_seed_by_archetype(const World &world, const QueryInfo &queryInfo, const cnt::darray< Entity > &seedEntities, const DirectEntitySeedInfo &seedInfo, Constraints constraints)
Groups seeded entities by archetype and counts whole buckets when only structural ALL/NOT terms remai...
Definition query.h:4331
static GAIA_NODISCARD constexpr Constraints iter_mode_constraints()
Maps an iteration-mode tag to the runtime row constraints used by iterators.
Definition query.h:2132
ExecPayloadKind
Runtime payload layout required by generic chunk-batch execution.
Definition query.h:2028
@ Plain
Plain batches without group ids, inherited data, sorted slices, or barrier metadata.
@ Grouped
Batches carry group ids but do not require sorted slices or inherited/barrier metadata.
@ NonTrivial
Batches require non-trivial side payload such as sorted slices, inherited data, or barriers.
void each_runtime_erased(QueryExecType execType, void *pFunc, void(*invoke)(void *, Iter &), Constraints constraints)
Runs a type-erased public iterator callback through generic query execution.
Definition query.h:3773
QueryImpl & any()
Adds an optional typed term.
Definition query_builder_typed.inl:63
QueryImpl & set_var(Entity varEntity, Entity value)
Binds a query variable (Var0..Var7) to a concrete entity value. Bound values are applied at runtime b...
Definition query.h:5639
GAIA_NODISCARD const char * kind_error_str()
Returns a human-readable description of the current kind validation result.
Definition query.h:1223
GAIA_NODISCARD uint16_t cache_src_trav() const
Returns the traversed-source snapshot cap. 0 disables explicit traversed-source snapshot caching.
Definition query.h:1026
GAIA_NODISCARD bool is_cached() const
Returns whether the query is stored in the query cache.
Definition query.h:5166
void for_each_direct_or_union(World &world, const QueryInfo &queryInfo, Constraints constraints, Func &&func)
Visits the deduplicated OR union for direct-seeded queries without materializing an entity seed array...
Definition query.h:4531
void run_query_on_chunks_runtime_direct_plain_impl(QueryInfo &queryInfo, const QueryPlan &plan, Constraints constraints, Func &func)
Runs a public Iter callback over cached chunks without creating chunk batches.
Definition query.h:3621
QueryImpl & all()
Adds a required typed term.
Definition query_builder_typed.inl:52
GAIA_NODISCARD OrderByTravView order_by(Entity relation, TravOrder order)
Iterates matching entities in an explicit relation traversal order.
Definition query.h:5769
QueryPlanFlags
Orthogonal flags attached to a prepared query plan.
Definition query.h:2078
@ QueryPlanFlag_Sorted
The plan may need sorted cache slices; runners use them only with non-trivial payload.
Definition query.h:2090
@ QueryPlanFlag_Filtered
The query has per-chunk filters such as changed terms.
Definition query.h:2082
@ QueryPlanFlag_InheritedPayload
The query carries inherited component data into iterator payloads.
Definition query.h:2086
@ QueryPlanFlag_None
No additional query-plan properties are present.
Definition query.h:2080
@ QueryPlanFlag_BarrierCache
The plan must use the depth-order hierarchy barrier cache when checking archetype/row ranges.
Definition query.h:2092
@ QueryPlanFlag_Grouped
The query uses grouped payload/ranges or grouped cache ordering.
Definition query.h:2088
@ QueryPlanFlag_EntityFilter
The query has entity-filter terms that require per-entity rechecks.
Definition query.h:2084
QueryImpl & add(const char *str,...)
Creates a query from a null-terminated expression string.
Definition query.h:5213
QueryImpl & is(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds a semantic Is(entity) requirement.
Definition query.h:5505
static GAIA_NODISCARD bool can_use_archetype_bucket_count(const World &world, const QueryInfo &queryInfo, const DirectEntitySeedInfo &seedInfo)
Detects when a direct seed can be counted by archetype buckets instead of per entity checks.
Definition query.h:4307
QueryImpl & in(Entity entity, QueryTermOptions options=QueryTermOptions{})
Adds an inherited in(entity) requirement.
Definition query.h:5515
GAIA_NODISCARD uint32_t count_inter(const QueryInfo &queryInfo, Constraints constraints) const
Fast count() path for direct non-fragmenting queries that can seed from entity-backed indices.
Definition query.h:4773
void each_direct_iter_inter(QueryInfo &queryInfo, Constraints constraints, Func func)
Runs an iterator-based each() callback over directly seeded entities using one-row chunk views.
Definition query.h:5037
QueryImpl & cache_src_trav(uint16_t maxItems)
Enables traversed-source snapshot reuse and caps the cached source closure size. This only matters fo...
Definition query.h:1007
void diag_bytecode()
Prints a textual dump of the generated query VM bytecode.
Definition query.h:6627
QueryImpl & clear_var(Entity varEntity)
Clears binding for a single query variable (Var0..Var7). The variable becomes unbound for the next qu...
Definition query.h:5670
static GAIA_NODISCARD bool is_adjunct_direct_term(const World &world, const QueryTerm &term)
Returns whether a direct term is backed by non-fragmenting storage and must be evaluated per entity.
Definition query.h:3820
QueryImpl & writes(Entity entity)
Declares an additional id written by this query callback.
Definition query.h:1106
static GAIA_NODISCARD bool uses_inherited_id_matching(const World &world, const QueryTerm &term)
Returns whether a term uses semantic inherited-id matching rather than direct storage matching.
Definition query.h:3868
GAIA_NODISCARD std::span< const Entity > custom_writes() const
Returns explicitly declared write ids that are not query terms.
Definition query.h:1128
QueryImpl & depth_order(Entity relation=ChildOf)
Orders cached query entries by fragmenting relation depth so iteration runs breadth-first top-down....
Definition query.h:5790
GAIA_NODISCARD bool main_thread_required() const
Returns whether this query must run on the main thread/serial path.
Definition query.h:1071
QueryImpl & add(QueryInput item)
Adds a prebuilt query input item.
Definition query.h:5493
static GAIA_NODISCARD bool match_entity_filters(const World &world, Entity entity, const QueryInfo &queryInfo)
Evaluates the entity-level terms that are not fully represented by archetype membership.
Definition query.h:4656
static GAIA_NODISCARD bool uses_non_direct_is_matching(const QueryTerm &term)
Returns whether a term uses any semantic Is matching rather than direct storage matching.
Definition query.h:3852
QueryImpl & any(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds an optional entity or pair term.
Definition query.h:5550
static GAIA_NODISCARD bool has_direct_not_terms(const QueryInfo &queryInfo)
Returns whether direct OR evaluation still has direct NOT terms that must be checked per entity.
Definition query.h:4513
void each_iter(Iter &it, Func func)
Runs a typed callback against an already prepared iterator. This is used by higher-level adapters tha...
Definition query_typed.inl:1104
GAIA_NODISCARD bool empty_inter(const QueryInfo &queryInfo, Constraints constraints) const
Fast empty() path for direct non-fragmenting queries that can seed from entity-backed indices.
Definition query.h:4568
QueryImpl & var_name(Entity varEntity, util::str_view name)
Assigns a human-readable name to a query variable entity (Var0..Var7). The name can be used later by ...
Definition query.h:5622
QueryImpl & group_dep()
Declares an explicit relation dependency for grouped cache invalidation. Useful for custom group_by c...
Definition query_builder_typed.inl:129
QueryImpl & ctx(void *pCtx)
Sets the user-owned context pointer visible through Iter::ctx() during iterator callbacks....
Definition query.h:1043
void each(Func func, QueryExecType execType)
Iterates query matches using the selected execution mode.
Definition query.h:5938
QueryImpl & var_name(Entity varEntity, const char *name)
Assigns a human-readable name to a query variable entity (Var0..Var7).
Definition query.h:5628
static GAIA_NODISCARD ExecPayloadKind exec_payload_kind(const QueryInfo &queryInfo, Constraints constraints)
Classifies the generic batch payload needed for a matched query under row constraints.
Definition query.h:2041
void each(Func func)
Iterates query matches using the default execution mode.
Definition query.h:5925
static GAIA_NODISCARD bool can_use_direct_chunk_term_eval_arg(World &world, const QueryInfo &queryInfo, const DirectChunkArgEvalDesc &desc)
Returns whether a runtime query argument descriptor can use direct chunk access.
Definition query.h:4969
static GAIA_NODISCARD bool match_entity_term(const World &world, Entity entity, const QueryTerm &term)
Evaluates term presence for a concrete entity using either direct or semantic semantics.
Definition query.h:3873
static GAIA_NODISCARD bool uses_in_is_matching(const QueryTerm &term)
Returns whether a term uses strict semantic Is matching that excludes the base entity itself.
Definition query.h:3842
static GAIA_NODISCARD bool depth_order_hierarchy_barrier_prunes(const QueryInfo &queryInfo)
Checks whether cached depth-order barrier results can prune any matched archetype.
Definition query.h:1930
GAIA_NODISCARD QueryCacheRange selected_query_cache_range(const QueryInfo &queryInfo) const
Selects the cache range visible to this query, applying a selected group id when present.
Definition query.h:3458
QueryImpl & match_prefab()
Makes the query include prefab entities in matches.
Definition query.h:1197
GAIA_NODISCARD std::span< const Entity > custom_reads() const
Returns explicitly declared read ids that are not query terms.
Definition query.h:1122
static GAIA_NODISCARD bool has_depth_order_hierarchy_enabled_barrier(const QueryInfo &queryInfo)
Checks whether depth-order grouping can prune disabled hierarchy subtrees.
Definition query.h:1892
void run_query_on_chunks_direct_typed(QueryInfo &queryInfo, const QueryPlan &plan, const TypedQueryExecState &state, Func &func, core::func_type_list< T... >)
Runs the prepared direct typed row path for simple cached queries.
Definition query_typed.inl:768
void diag()
Run diagnostics.
Definition query.h:6604
QueryImpl & group_id(Entity entity)
Selects the group to iterate over.
Definition query.h:5862
static DirectEntitySeedInfo build_direct_entity_seed(const World &world, const QueryInfo &queryInfo, cnt::darray< Entity > &out)
Builds the best direct entity seed set from the smallest positive ALL term or the OR union fallback.
Definition query.h:4461
void match_all(QueryInfo &queryInfo)
Matches the query against all relevant archetypes.
Definition query.h:943
void each_runtime_erased(QueryInfo &queryInfo, const QueryPlan &plan, QueryExecType execType, void *pFunc, void(*invoke)(void *, Iter &), Constraints constraints)
Runs a type-erased public iterator callback using an already prepared query cache and plan.
Definition query.h:3788
static GAIA_NODISCARD bool uses_potential_inherited_id_matching(const QueryTerm &term)
Returns whether a term could use inherited-id matching based on query shape alone....
Definition query.h:3860
QueryImpl & no()
Adds an excluded typed term.
Definition query_builder_typed.inl:85
static GAIA_NODISCARD bool can_use_direct_target_eval(const QueryInfo &queryInfo)
Detects queries whose terms can be evaluated directly against concrete target entities.
Definition query.h:3959
void each_runtime_inter(Func func, Constraints constraints=Constraints::EnabledOnly)
Runs a public iterator callback through the fastest supported runtime path.
Definition query.h:3675
GAIA_NODISCARD QueryCacheScope scope() const
Returns the currently requested cache scope.
Definition query.h:1205
GAIA_NODISCARD std::span< const Entity > ordered_entities_walk(QueryInfo &queryInfo, Entity relation, TravOrder order, Constraints constraints=Constraints::EnabledOnly)
Builds and caches relation traversal order for the current query result.
Definition query.h:6245
QueryImpl & changed(Entity entity)
Marks a runtime component or pair for changed() filtering.
Definition query.h:5691
uint32_t count(Constraints constraints=Constraints::EnabledOnly)
Calculates the number of entities matching the query.
Definition query.h:6041
static GAIA_NODISCARD uint32_t count_direct_or_union(const World &world, const QueryInfo &queryInfo, Constraints constraints)
Counts the union of direct OR term entity sets while deduplicating entities across terms.
Definition query.h:4367
void destroy()
Destroys the current cached query state and local scratch data.
Definition query.h:5155
QueryImpl & group_id()
Selects the group to iterate over.
Definition query_builder_typed.inl:134
static GAIA_NODISCARD bool match_filters(const Chunk &chunk, const QueryInfo &queryInfo, uint32_t changedWorldVersion, std::span< const uint8_t > compIndices)
Returns whether a chunk passes the query's changed filters.
Definition query.h:1781
GAIA_NODISCARD bool conflicts_with(QueryImpl &other)
Returns whether this query conflicts with another query's effective access declarations.
Definition query.h:1150
bool empty(Constraints constraints=Constraints::EnabledOnly)
Returns true or false depending on whether there are any entities matching the query.
Definition query.h:6018
void each_entity_enabled(void *pCtx, void(*func)(void *, Entity))
Iterates matching enabled entities through a non-template erased callback.
Definition query.h:6056
static GAIA_NODISCARD bool match_single_direct_target_term(const World &world, Entity entity, Entity termId, QueryCtx::DirectTargetEvalKind kind)
Evaluates a compiled single-term direct-target query without re-walking the generic term loop.
Definition query.h:3883
void each_walk(Func func, Entity relation, TravOrder order=TravOrder::Down, Constraints constraints=Constraints::EnabledOnly)
Iterates entities matching the query in a requested relation traversal order. For relation R this tre...
Definition query.h:6581
QueryImpl & all(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds a required entity or pair term.
Definition query.h:5526
QueryImpl & changed()
Marks a typed term for changed() filtering.
Definition query_builder_typed.inl:90
GAIA_NODISCARD void * ctx() const
Returns the user-owned context pointer attached to this query.
Definition query.h:1050
QueryImpl & clear_vars()
Clears all runtime variable bindings.
Definition query.h:5681
QueryPlanMode
Prepared query runner mode shared by typed callbacks and public Iter callbacks.
Definition query.h:2056
@ General
Use the generic query execution path because no specialized runner is valid.
@ Sorted
Sorted payload execution that must preserve cache-provided chunk order.
@ DirectDense
Direct cached archetype/chunk iteration with query-term indices matching storage layout.
@ EntitySeed
Direct entity-seed evaluation over explicitly selected entities.
@ MappedDense
Typed dense cached archetype/chunk iteration using mapped component access. Public Iter callbacks use...
@ Empty
The selected group/range has no matching archetypes, so execution can return immediately.
@ Traversal
Traversal or inherited payload execution that requires the mapped generic path.
QueryImpl & no(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds an excluded entity or pair term.
Definition query.h:5599
static GAIA_NODISCARD bool match_direct_entity_terms(const World &world, Entity entity, const QueryInfo &queryInfo, const DirectEntitySeedInfo &seedInfo)
Evaluates the remaining direct terms for a single seeded entity after the seed term itself was consum...
Definition query.h:4069
QueryInfo & fetch()
Fetches the QueryInfo object. Creates or refreshes the backing QueryInfo if needed.
Definition query.h:889
static GAIA_NODISCARD bool can_use_direct_seed_run_cache(const World &world, const QueryInfo &queryInfo, const QueryTerm &seedTerm)
Returns whether a repeated semantic or inherited seed can be cached as chunk runs.
Definition query.h:4151
GAIA_NODISCARD bool can_process_archetype_inter(const QueryInfo &queryInfo, const Archetype &archetype, Constraints constraints, int8_t barrierPasses=-1) const
Checks whether a matched archetype can be processed for the current row constraints.
Definition query.h:1970
static void run_query_arch_func(World *pWorld, Func func, ChunkBatch &batch, Constraints constraints)
Executes an archetype-level iterator callback for one prepared chunk batch. The iterator is initializ...
Definition query.h:2173
static GAIA_NODISCARD bool should_prefer_direct_seed_term(const World &world, const QueryTerm &candidate, uint32_t candidateCount, const DirectEntitySeedPlan &plan)
Chooses the narrowest available seed for direct non-fragmenting evaluation.
Definition query.h:4010
GAIA_NODISCARD QueryKindRes kind_error()
Returns the validation result for the current query shape and requested kind.
Definition query.h:1217
GAIA_NODISCARD bool valid()
Returns whether the current query shape satisfies the requested kind.
Definition query.h:1229
QueryImpl & depth_order()
Orders cached query entries by fragmenting relation depth so iteration runs breadth-first top-down.
Definition query_builder_typed.inl:109
QueryImpl & group_by(Entity entity, TGroupByFunc func=group_by_func_default)
Organizes matching archetypes into groups according to the grouping function and entity....
Definition query.h:5811
GAIA_NODISCARD bool match_one(QueryInfo &queryInfo, const Archetype &archetype, EntitySpan targetEntities)
Matches the query against a single archetype.
Definition query.h:969
QueryImpl & or_(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
OR terms (at least one has to match). A single OR term is canonicalized to ALL during query normaliza...
Definition query.h:5575
GAIA_NODISCARD OrderByTravView order_by(TravOrder order)
Iterates matching entities in an explicit typed relation traversal order.
QueryImpl & group_id(GroupId groupId)
Selects the group to iterate over.
Definition query.h:5855
GAIA_NODISCARD QueryCacheKind kind() const
Returns the currently requested cache kind.
Definition query.h:1211
void each_direct_inter(QueryInfo &queryInfo, Constraints constraints, void *pFunc, const TypedQueryExecState &state, void(*runDirectChunk)(QueryImpl &, Iter &, void *, const TypedQueryExecState &), bool needsInheritedArgIds, void(*invokeInherited)(World &, Entity, const Entity *, void *))
Runs a typed each() callback over directly seeded entities.
Definition query_typed.inl:1132
GAIA_NODISCARD QueryCachePolicy cache_policy()
Returns the effective cache policy chosen for the query.
Definition query.h:997
GAIA_NODISCARD QueryId id() const
Returns the cache handle id of this query.
Definition query.h:5128
void run_query_on_chunks_runtime_planned(QueryInfo &queryInfo, const QueryPlan &plan, Constraints constraints, Func func)
Runs public iterator chunk execution from an already prepared plan.
Definition query.h:3431
QueryImpl & writes()
Declares an additional component or pair type written by this query callback.
Definition query.h:1116
Wrapper for two Entities forming a relationship pair.
Definition id.h:529
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:224
void seek(uint32_t pos)
Changes the current position in the buffer.
Definition ser_buffer_binary.h:70
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
@ PREFETCH_HINT_T2
Temporal data with respect to second level cache misses — prefetch data into L3 cache and higher,...
Definition config_core_end.h:127
GAIA_FORCEINLINE void prefetch(const void *x, int hint)
Prefetch intrinsic.
Definition config_core_end.h:134
constexpr uint32_t BadIndex
Sentinel index value returned by helpers when a lookup fails.
Definition utility.h:20
Lightweight carrier for a function argument type pack.
Definition utility.h:643
static constexpr uint32_t MAX_COMPONENTS
Maximum number of components on archetype.
Definition chunk_header.h:53
SymbolLookupKey name
Component name.
Definition component_cache_item.h:93
Definition entity_container.h:60
uint16_t row
Row at which the entity is stored in the chunk.
Definition entity_container.h:94
Archetype * pArchetype
Archetype (stable address)
Definition entity_container.h:112
Chunk * pChunk
Chunk the entity currently resides in (stable address)
Definition entity_container.h:114
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
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
GAIA_NODISCARD std::span< const Entity > reads_view() const
Returns the explicitly declared read ids.
Definition query_common.h:400
void add_write(Entity entity)
Declares that an id is written.
Definition query_common.h:423
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
Entity sortBy
Entity to sort the archetypes by. EntityBad for no sorting.
Definition query_common.h:745
Entity groupBy
Entity to group the archetypes by. EntityBad for no grouping.
Definition query_common.h:749
uint16_t flags
Query flags.
Definition query_common.h:772
Definition query_common.h:564
ComponentCache * cc
Component cache.
Definition query_common.h:568
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
Internal representation of QueryInput.
Definition query_common.h:445
Entity id
Queried id.
Definition query_common.h:447
QueryMatchKind matchKind
Match semantics for this term.
Definition query_common.h:457
Entity entTrav
Optional traversal relation for source lookups.
Definition query_common.h:451
Entity src
Source of where the queried id is looked up at.
Definition query_common.h:449
Definition query_adapter_typed.inl:5
void init_owned_query_info(QueryInfo &&queryInfo)
Stores a locally-owned QueryInfo, replacing the old one if present.
Definition query.h:478
QueryIdentity m_identity
Query identity.
Definition query.h:294
void allow_to_destroy_again()
Allows this storage object to destroy cache-backed state again after a move/copy handoff.
Definition query.h:422
GAIA_NODISCARD QueryInfo & owned_query_info()
Returns the locally-owned QueryInfo.
Definition query.h:471
void invalidate()
Invalidates the query handle.
Definition query.h:440
GAIA_NODISCARD QueryInfo * try_query_info_fast() const
Returns the cached QueryInfo pointer when the fast-path cache is still valid.
Definition query.h:449
QueryInfo * m_pOwnedInfo
Locally-owned query plan used when the query does not use cache-backed storage.
Definition query.h:292
GAIA_NODISCARD QuerySerBuffer & ser_buffer()
Returns the serialized command buffer for this query.
Definition query.h:395
GAIA_NODISCARD bool is_cached() const
Returns whether the query is found in the query cache.
Definition query.h:487
QueryInfo * m_pInfo
Hot cached query pointer. Validated against m_identity.handle before use.
Definition query.h:290
GAIA_NODISCARD bool is_initialized() const
Returns whether the query is ready to be used.
Definition query.h:496
GAIA_NODISCARD bool has_owned_query_info() const
Returns whether storage owns a local QueryInfo instance.
Definition query.h:465
void ser_buffer_reset()
Releases the serialized command buffer for this query.
Definition query.h:400
void cache_query_info(QueryInfo &queryInfo)
Caches the hot QueryInfo pointer locally.
Definition query.h:459
QueryCache * m_pCache
QueryImpl cache (stable pointer to parent world's query cache)
Definition query.h:288
GAIA_NODISCARD bool try_del_from_cache()
Tries to delete the query from the query cache.
Definition query.h:428
void reset()
Releases any data allocated by the query.
Definition query.h:414
void init(World *world, QueryCache *queryCache)
Initializes storage against a world and query cache.
Definition query.h:407
GAIA_NODISCARD World * world()
Returns the world associated with this query storage.
Definition query.h:389
bool alwaysMatch
No remaining terms after consuming the seed.
Definition query.h:4006
const QueryTerm * pSingleAllTerm
Fast path for the common ALL + direct/semantic Is shape: after consuming the seed term,...
Definition query.h:4004
Describes which direct term should seed direct non-fragmenting evaluation.
Definition query.h:3992
Callback adapter that sets iterator context before invoking an Iter& user callback.
Definition query.h:2284
void operator()(Iter &it)
Invokes the stored callback for it.
Definition query.h:2292
QueryImpl * pSelf
Query owning the runtime context pointer.
Definition query.h:2286
Func func
User callback copied into the deferred job context.
Definition query.h:2288
Tag selecting unconstrained row iteration for prepared query iteration.
Definition query.h:2126
Tag selecting disabled-only row constraints for prepared query iteration.
Definition query.h:2124
Tag selecting enabled-only row constraints for prepared query iteration.
Definition query.h:2122
Cache range selected by the query's optional group id filter.
Definition query.h:2110
bool hasSelectedGroup
True when m_groupIdSet narrowed the range to one cache group.
Definition query.h:2116
uint32_t idxTo
One-past-the-end cached archetype index to process.
Definition query.h:2114
bool valid
False when the selected group id is absent from the matched cache.
Definition query.h:2118
uint32_t idxFrom
First cached archetype index to process.
Definition query.h:2112
Prepared query execution metadata shared by typed callbacks and public Iter callbacks.
Definition query.h:2096
uint32_t idxTo
One-past-the-end cached archetype index to process.
Definition query.h:2106
QueryPlanMode mode
Runner family selected for the current matched query cache.
Definition query.h:2098
uint32_t idxFrom
First cached archetype index to process.
Definition query.h:2104
ExecPayloadKind payloadKind
Payload layout required by generic chunk-batch runners independent of sorted-cache availability.
Definition query.h:2102
uint8_t flags
Orthogonal plan properties such as filtering, entity filters, grouping, or payload requirements.
Definition query.h:2100
Callback adapter that materializes typed callback arguments on top of a prepared iterator.
Definition query.h:2301
void operator()(Iter &it)
Runs the typed callback for it.
Definition query.h:2309
Func func
User callback copied into the deferred job context.
Definition query.h:2305
QueryImpl * pSelf
Query that owns typed execution metadata and runtime context.
Definition query.h:2303
Definition id.h:239
Lightweight non-owning string view over a character sequence.
Definition str.h:13
GAIA_NODISCARD constexpr const char * data() const
Returns the underlying character pointer.
Definition str.h:35
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:331