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/mt/threadpool.h"
27#include "gaia/ser/ser_buffer_binary.h"
28#include "gaia/ser/ser_ct.h"
29#include "gaia/util/str.h"
30
31namespace gaia {
32 namespace ecs {
33 class World;
34 void world_finish_write(World& world, Entity term, Entity entity);
35
37 inline static constexpr uint16_t MaxCacheSrcTrav = 32;
38
40 enum class QueryExecType : uint32_t {
42 Serial,
44 Parallel,
46 ParallelPerf,
48 ParallelEff,
50 Default = Serial,
51 };
52
54 enum class QueryCacheKind : uint8_t {
57 None,
65 Default,
73 Auto,
79 All
80 };
81
83 enum class QueryCacheScope : uint8_t {
85 Local,
87 Shared
88 };
89
91 enum class QueryKindRes : uint8_t {
93 OK,
95 AutoSrcTrav,
97 AllNotIm,
99 AllSrcTrav
100 };
101
102 using QueryCachePolicy = QueryCtx::CachePolicy;
103
104 namespace detail {
106 enum QueryCmdType : uint8_t { ADD_ITEM, ADD_FILTER, SORT_BY, GROUP_BY, GROUP_DEP, MATCH_PREFAB };
107
109 static constexpr QueryCmdType Id = QueryCmdType::ADD_ITEM;
110 static constexpr bool InvalidatesHash = true;
111
112 QueryInput item;
113
114 void exec(QueryCtx& ctx) const {
115 auto& ctxData = ctx.data;
116
117#if GAIA_DEBUG
118 // Unique component ids only
119 GAIA_ASSERT(!core::has(ctxData.ids_view(), item.id));
120
121 // There's a limit to the amount of query items which we can store
122 if (ctxData.idsCnt >= MAX_ITEMS_IN_QUERY) {
123 GAIA_ASSERT2(false, "Trying to create a query with too many components!");
124
125 const auto* name = ctx.cc->get(item.id).name.str();
126 GAIA_LOG_E("Trying to add component '%s' to an already full ECS query!", name);
127 return;
128 }
129#endif
130
131 // Build the read-write mask.
132 // This will be used to determine what kind of access the user wants for a given component.
133 const uint16_t isReadWrite = uint16_t(item.access == QueryAccess::Write);
134 ctxData.readWriteMask |= (isReadWrite << ctxData.idsCnt);
135
136 ctxData.ids[ctxData.idsCnt] = item.id;
137 ctxData.terms[ctxData.idsCnt] = {item.id, item.entSrc, item.entTrav,
138 item.travKind, item.travDepth, item.matchKind,
139 nullptr, item.op, (uint8_t)ctxData.idsCnt};
140 ++ctxData.idsCnt;
141 }
142 };
143
145 static constexpr QueryCmdType Id = QueryCmdType::ADD_FILTER;
146 static constexpr bool InvalidatesHash = true;
147
148 Entity comp;
149
150 void exec(QueryCtx& ctx) const {
151 auto& ctxData = ctx.data;
152
153#if GAIA_DEBUG
154 GAIA_ASSERT(core::has(ctxData.ids_view(), comp));
155 GAIA_ASSERT(!core::has(ctxData.changed_view(), comp));
156
157 // There's a limit to the amount of components which we can store
158 if (ctxData.changedCnt >= MAX_ITEMS_IN_QUERY) {
159 GAIA_ASSERT2(false, "Trying to create an filter query with too many components!");
160
161 const auto* compName = ctx.cc->get(comp).name.str();
162 GAIA_LOG_E("Trying to add component %s to an already full filter query!", compName);
163 return;
164 }
165
166 uint32_t compIdx = 0;
167 for (; compIdx < ctxData.idsCnt; ++compIdx)
168 if (ctxData.ids[compIdx] == comp)
169 break;
170
171 // NOTE: Code bellow does the same as this commented piece.
172 // However, compilers can't quite optimize it as well because it does some more
173 // calculations. This is used often so go with the custom code.
174 // const auto compIdx = core::get_index_unsafe(ids, comp);
175
176 // Component has to be present in all/or lists.
177 // Filtering by NOT/ANY doesn't make sense because those are not hard requirements.
178 GAIA_ASSERT2(
179 ctxData.terms[compIdx].op != QueryOpKind::Not && ctxData.terms[compIdx].op != QueryOpKind::Any,
180 "Filtering by NOT/ANY doesn't make sense!");
181 if (ctxData.terms[compIdx].op != QueryOpKind::Not && ctxData.terms[compIdx].op != QueryOpKind::Any) {
182 ctxData.changed[ctxData.changedCnt++] = comp;
183 return;
184 }
185
186 const auto* compName = ctx.cc->get(comp).name.str();
187 GAIA_LOG_E("SetChangeFilter trying to filter component %s but it's not a part of the query!", compName);
188#else
189 ctxData.changed[ctxData.changedCnt++] = comp;
190#endif
191 }
192 };
193
195 static constexpr QueryCmdType Id = QueryCmdType::SORT_BY;
196 static constexpr bool InvalidatesHash = true;
197
198 Entity sortBy;
199 TSortByFunc func;
200
201 void exec(QueryCtx& ctx) const {
202 auto& ctxData = ctx.data;
203 ctxData.sortBy = sortBy;
204 GAIA_ASSERT(func != nullptr);
205 ctxData.sortByFunc = func;
206 }
207 };
208
210 static constexpr QueryCmdType Id = QueryCmdType::GROUP_BY;
211 static constexpr bool InvalidatesHash = true;
212
213 Entity groupBy;
214 TGroupByFunc func;
215
216 void exec(QueryCtx& ctx) const {
217 auto& ctxData = ctx.data;
218 ctxData.groupBy = groupBy;
219 GAIA_ASSERT(func != nullptr);
220 ctxData.groupByFunc = func; // group_by_func_default;
221 }
222 };
223
225 static constexpr QueryCmdType Id = QueryCmdType::GROUP_DEP;
226 static constexpr bool InvalidatesHash = true;
227
228 Entity relation;
229
230 void exec(QueryCtx& ctx) const {
231 auto& ctxData = ctx.data;
232 GAIA_ASSERT(!relation.pair());
233 ctxData.add_group_dep(relation);
234 }
235 };
236
238 static constexpr QueryCmdType Id = QueryCmdType::MATCH_PREFAB;
239 static constexpr bool InvalidatesHash = true;
240
241 void exec(QueryCtx& ctx) const {
242 ctx.data.flags |= QueryCtx::QueryFlags::MatchPrefab;
243 }
244 };
245
247 World* m_world = nullptr;
251 QueryInfo* m_pInfo = nullptr;
256 bool m_destroyed = false;
257
258 public:
259 QueryImplStorage() = default;
261 (void)try_del_from_cache();
262 delete m_pOwnedInfo;
263 }
264
265 QueryImplStorage(QueryImplStorage&& other) {
266 m_world = other.m_world;
267 m_pCache = other.m_pCache;
268 m_pInfo = other.m_pInfo;
269 m_pOwnedInfo = other.m_pOwnedInfo;
270 m_identity = other.m_identity;
271 m_destroyed = other.m_destroyed;
272
273 // Make sure old instance is invalidated
274 other.m_pInfo = nullptr;
275 other.m_pOwnedInfo = nullptr;
276 other.m_identity = {};
277 other.m_destroyed = false;
278 }
279 QueryImplStorage& operator=(QueryImplStorage&& other) {
280 GAIA_ASSERT(core::addressof(other) != this);
281
282 delete m_pOwnedInfo;
283
284 m_world = other.m_world;
285 m_pCache = other.m_pCache;
286 m_pInfo = other.m_pInfo;
287 m_pOwnedInfo = other.m_pOwnedInfo;
288 m_identity = other.m_identity;
289 m_destroyed = other.m_destroyed;
290
291 // Make sure old instance is invalidated
292 other.m_pInfo = nullptr;
293 other.m_pOwnedInfo = nullptr;
294 other.m_identity = {};
295 other.m_destroyed = false;
296
297 return *this;
298 }
299
300 QueryImplStorage(const QueryImplStorage& other) {
301 m_world = other.m_world;
302 m_pCache = other.m_pCache;
303 m_pInfo = other.m_pInfo;
304 if (other.m_pOwnedInfo != nullptr)
305 m_pOwnedInfo = new QueryInfo(*other.m_pOwnedInfo);
306 m_identity = other.m_identity;
307 m_destroyed = other.m_destroyed;
308
309 // Make sure to update the ref count of the cached query so
310 // it doesn't get deleted by accident.
311 if (!m_destroyed) {
312 auto* pInfo = try_query_info_fast();
313 if (pInfo == nullptr)
315 if (pInfo != nullptr)
316 pInfo->add_ref();
317 }
318 }
319 QueryImplStorage& operator=(const QueryImplStorage& other) {
320 GAIA_ASSERT(core::addressof(other) != this);
321
322 delete m_pOwnedInfo;
323 m_pOwnedInfo = nullptr;
324
325 m_world = other.m_world;
326 m_pCache = other.m_pCache;
327 m_pInfo = other.m_pInfo;
328 if (other.m_pOwnedInfo != nullptr)
329 m_pOwnedInfo = new QueryInfo(*other.m_pOwnedInfo);
330 m_identity = other.m_identity;
331 m_destroyed = other.m_destroyed;
332
333 // Make sure to update the ref count of the cached query so
334 // it doesn't get deleted by accident.
335 if (!m_destroyed) {
336 auto* pInfo = try_query_info_fast();
337 if (pInfo == nullptr)
339 if (pInfo != nullptr)
340 pInfo->add_ref();
341 }
342
343 return *this;
344 }
345
348 GAIA_NODISCARD World* world() {
349 return m_world;
350 }
351
354 GAIA_NODISCARD QuerySerBuffer& ser_buffer() {
355 return m_identity.ser_buffer(m_world);
356 }
357
360 return m_identity.ser_buffer_reset(m_world);
361 }
362
366 void init(World* world, QueryCache* queryCache) {
367 m_world = world;
368 m_pCache = queryCache;
369 m_pInfo = nullptr;
370 }
371
373 void reset() {
374 if (auto* pInfo = try_query_info_fast(); pInfo != nullptr)
375 pInfo->reset();
376 if (m_pOwnedInfo != nullptr)
377 m_pOwnedInfo->reset();
378 }
379
382 m_destroyed = false;
383 }
384
387 GAIA_NODISCARD bool try_del_from_cache() {
388 if (!m_destroyed && m_identity.handle.id() != QueryIdBad)
390
391 // Don't allow multiple calls to destroy to break the reference counter.
392 // One object is only allowed to destroy once.
393 m_pInfo = nullptr;
394 m_destroyed = true;
395 return false;
396 }
397
399 void invalidate() {
400 m_pInfo = nullptr;
401 m_identity.handle = {};
402 delete m_pOwnedInfo;
403 m_pOwnedInfo = nullptr;
404 }
405
408 GAIA_NODISCARD QueryInfo* try_query_info_fast() const {
409 if (m_pInfo == nullptr || m_identity.handle.id() == QueryIdBad || m_pCache == nullptr)
410 return nullptr;
411
412 auto* pInfo = m_pCache->try_get(m_identity.handle);
413 return pInfo == m_pInfo ? pInfo : nullptr;
414 }
415
418 void cache_query_info(QueryInfo& queryInfo) {
419 m_pInfo = &queryInfo;
420 }
421
424 GAIA_NODISCARD bool has_owned_query_info() const {
425 return m_pOwnedInfo != nullptr;
426 }
427
430 GAIA_NODISCARD QueryInfo& owned_query_info() {
431 GAIA_ASSERT(m_pOwnedInfo != nullptr);
432 return *m_pOwnedInfo;
433 }
434
438 if (m_pOwnedInfo == nullptr)
439 m_pOwnedInfo = new QueryInfo(GAIA_MOV(queryInfo));
440 else
441 *m_pOwnedInfo = GAIA_MOV(queryInfo);
442 }
443
446 GAIA_NODISCARD bool is_cached() const {
447 auto* pInfo = try_query_info_fast();
448 if (pInfo == nullptr)
450 return pInfo != nullptr;
451 }
452
455 GAIA_NODISCARD bool is_initialized() const {
456 return m_world != nullptr && m_pCache != nullptr;
457 }
458 };
459 class QueryImpl {
460 static constexpr uint32_t ChunkBatchSize = 32;
461
462 struct ChunkBatch {
463 const Archetype* pArchetype;
464 Chunk* pChunk;
465 const uint8_t* pCompIndices;
466 InheritedTermDataView inheritedData;
467 GroupId groupId;
468 uint16_t from;
469 uint16_t to;
470 };
471
472 using ChunkSpan = std::span<const Chunk*>;
473 using ChunkSpanMut = std::span<Chunk*>;
475 using CmdFunc = void (*)(QuerySerBuffer& buffer, QueryCtx& ctx);
476
477 struct DirectQueryScratch {
479 cnt::darray<Entity> entities;
480 cnt::darray<Entity> termEntities;
481 cnt::darray<Entity> bucketEntities;
483 uint32_t seenVersion = 1;
484 };
485
486 private:
487 GAIA_NODISCARD bool uses_query_cache_storage() const {
488 return m_cacheKind != QueryCacheKind::None;
489 }
490
491 GAIA_NODISCARD bool uses_shared_cache_layer() const {
492 return uses_query_cache_storage() && m_cacheScope == QueryCacheScope::Shared;
493 }
494
495 void invalidate_query_storage() {
496 if (uses_query_cache_storage())
497 (void)m_storage.try_del_from_cache();
498 m_storage.invalidate();
499 }
500
503 GAIA_NODISCARD static DirectQueryScratch& direct_query_scratch() {
504 static thread_local DirectQueryScratch scratch;
505 return scratch;
506 }
507
511 static void ensure_direct_query_count_capacity(DirectQueryScratch& scratch, uint32_t entityId) {
512 if (entityId < scratch.counts.size())
513 return;
514
515 const auto doubledSize = (uint32_t)scratch.counts.size() * 2U;
516 const auto minSize = doubledSize > 64U ? doubledSize : 64U;
517 const auto newSize = (entityId + 1U) > minSize ? (entityId + 1U) : minSize;
518 scratch.counts.resize(newSize, 0);
519 }
520
524 GAIA_NODISCARD static uint32_t next_direct_query_seen_version(DirectQueryScratch& scratch) {
525 update_version(scratch.seenVersion);
526 if (scratch.seenVersion == 0) {
527 scratch.seenVersion = 1;
528 core::fill(scratch.counts.begin(), scratch.counts.end(), 0);
529 }
530
531 return scratch.seenVersion;
532 }
533
534 static constexpr CmdFunc CommandBufferRead[] = {
535 // Add item
536 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
538 ser::load(buffer, cmd);
539 cmd.exec(ctx);
540 },
541 // Add filter
542 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
544 ser::load(buffer, cmd);
545 cmd.exec(ctx);
546 },
547 // SortBy
548 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
549 QueryCmd_SortBy cmd;
550 ser::load(buffer, cmd);
551 cmd.exec(ctx);
552 },
553 // GroupBy
554 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
556 ser::load(buffer, cmd);
557 cmd.exec(ctx);
558 },
559 // GroupDep
560 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
562 ser::load(buffer, cmd);
563 cmd.exec(ctx);
564 },
565 // MatchPrefab
566 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
568 ser::load(buffer, cmd);
569 cmd.exec(ctx);
570 } //
571 }; // namespace detail
572
574 QueryImplStorage m_storage;
576 ArchetypeId* m_nextArchetypeId{};
578 uint32_t* m_worldVersion{};
580 const EntityToArchetypeMap* m_entityToArchetypeMap{};
582 const ArchetypeDArray* m_allArchetypes{};
586 uint8_t m_varNamesMask = 0;
588 cnt::sarray<Entity, MaxVarCnt> m_varBindings;
590 uint8_t m_varBindingsMask = 0;
592 GroupId m_groupIdSet = 0;
594 uint32_t m_changedWorldVersion = 0;
597 cnt::darray<ChunkBatch> m_batches;
599 QueryCacheKind m_cacheKind = QueryCacheKind::Default;
601 QueryCacheScope m_cacheScope = QueryCacheScope::Local;
603 uint16_t m_cacheSrcTrav = 0;
604
606 struct EachWalkData {
608 cnt::darray<Entity> cachedInput;
610 cnt::darray<Entity> cachedOutput;
614 Entity cachedRelation = EntityBad;
616 Constraints cachedConstraints = Constraints::EnabledOnly;
618 uint32_t cachedRelationVersion = 0;
620 uint32_t cachedEntityVersion = 0;
622 cnt::darray<const Chunk*> cachedChunks;
624 bool cacheValid = false;
626 cnt::darray<Entity> scratchEntities;
628 cnt::darray<const Chunk*> scratchChunks;
630 cnt::darray<uint32_t> scratchIndegree;
632 cnt::darray<uint32_t> scratchOutdegree;
634 cnt::darray<uint32_t> scratchOffsets;
636 cnt::darray<uint32_t> scratchWriteCursor;
638 cnt::darray<uint32_t> scratchEdges;
640 cnt::darray<uint32_t> scratchCurrLevel;
642 cnt::darray<uint32_t> scratchNextLevel;
643 };
644
645 template <typename T>
646 struct OnDemandDataHolder {
647 T* pData = nullptr;
648
649 OnDemandDataHolder() = default;
650
651 ~OnDemandDataHolder() {
652 delete pData;
653 }
654
655 OnDemandDataHolder(const OnDemandDataHolder& other) {
656 if (other.pData != nullptr)
657 pData = new T(*other.pData);
658 }
659
660 OnDemandDataHolder& operator=(const OnDemandDataHolder& other) {
661 if (core::addressof(other) == this)
662 return *this;
663
664 if (other.pData == nullptr) {
665 delete pData;
666 pData = nullptr;
667 return *this;
668 }
669
670 if (pData == nullptr)
671 pData = new T(*other.pData);
672 else
673 *pData = *other.pData;
674
675 return *this;
676 }
677
678 OnDemandDataHolder(OnDemandDataHolder&& other) noexcept: pData(other.pData) {
679 other.pData = nullptr;
680 }
681
682 OnDemandDataHolder& operator=(OnDemandDataHolder&& other) noexcept {
683 if (core::addressof(other) == this)
684 return *this;
685
686 delete pData;
687 pData = other.pData;
688 other.pData = nullptr;
689 return *this;
690 }
691
692 GAIA_NODISCARD T* get() {
693 return pData;
694 }
695
696 GAIA_NODISCARD const T* get() const {
697 return pData;
698 }
699
700 GAIA_NODISCARD T& ensure() {
701 if (pData == nullptr)
702 pData = new T();
703 return *pData;
704 }
705
706 void reset() {
707 delete pData;
708 pData = nullptr;
709 }
710 };
711
713 OnDemandDataHolder<EachWalkData> m_eachWalkData;
714
716 struct DirectSeedRunData {
717 cnt::darray<Entity> cachedEntities;
718 cnt::darray<Entity> cachedChunkOrderedEntities;
720 Entity cachedSeedTerm = EntityBad;
721 QueryMatchKind cachedSeedMatchKind = QueryMatchKind::Semantic;
722 Constraints cachedConstraints = Constraints::EnabledOnly;
723 uint32_t cachedRelVersion = 0;
724 uint32_t cachedWorldVersion = 0;
725 bool cacheValid = false;
726 };
727
728 OnDemandDataHolder<DirectSeedRunData> m_directSeedRunData;
729
730 //--------------------------------------------------------------------------------
731 public:
732 static inline bool SilenceInvalidCacheKindAssertions = false;
733
738 GAIA_PROF_SCOPE(query::fetch);
739
740 // Make sure the query was created by World::query()
741 GAIA_ASSERT(m_storage.is_initialized());
742
743 if (!uses_query_cache_storage()) {
744 if GAIA_UNLIKELY (!m_storage.has_owned_query_info()) {
745 QueryCtx ctx;
746 ctx.init(m_storage.world());
747 commit(ctx);
748 m_storage.init_owned_query_info(
749 QueryInfo::create(QueryId{}, GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view()));
750 } else if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad) {
751 recommit(m_storage.owned_query_info().ctx());
752 }
753
754 return m_storage.owned_query_info();
755 }
756
757 // If queryId is set it means QueryInfo was already created.
758 // This is the common case for cached queries.
759 if GAIA_LIKELY (m_storage.m_identity.handle.id() != QueryIdBad) {
760 auto* pQueryInfo = m_storage.try_query_info_fast();
761 if GAIA_UNLIKELY (pQueryInfo == nullptr)
762 pQueryInfo = m_storage.m_pCache->try_get(m_storage.m_identity.handle);
763
764 // The only time when this can be nullptr is just once after Query::destroy is called.
765 if GAIA_LIKELY (pQueryInfo != nullptr) {
766 m_storage.cache_query_info(*pQueryInfo);
767 if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad)
768 recommit(pQueryInfo->ctx());
769 return *pQueryInfo;
770 }
771
772 m_storage.invalidate();
773 }
774
775 // No queryId is set which means QueryInfo needs to be created
776 QueryCtx ctx;
777 ctx.init(m_storage.world());
778 commit(ctx);
779 auto& queryInfo =
780 uses_shared_cache_layer()
781 ? m_storage.m_pCache->add(GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view())
782 : m_storage.m_pCache->add_local(GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view());
783 m_storage.m_identity.handle = QueryInfo::handle(queryInfo);
784 m_storage.cache_query_info(queryInfo);
785 m_storage.allow_to_destroy_again();
786 return queryInfo;
787 }
788
791 void match_all(QueryInfo& queryInfo) {
792 const auto kindError = validate_kind(queryInfo.ctx());
793 if (kindError != QueryKindRes::OK) {
794 GAIA_ASSERT2(SilenceInvalidCacheKindAssertions, kind_error_str(kindError));
795 queryInfo.reset();
796 return;
797 }
798
799 if (!uses_query_cache_storage()) {
800 queryInfo.ensure_matches_transient(
801 *m_entityToArchetypeMap, all_archetypes_view(), m_varBindings, m_varBindingsMask);
802 return;
803 }
804
805 queryInfo.ensure_matches(
806 *m_entityToArchetypeMap, all_archetypes_view(), last_archetype_id(), m_varBindings, m_varBindingsMask);
807 m_storage.m_pCache->sync_archetype_cache(queryInfo);
808 }
809
815 GAIA_NODISCARD bool match_one(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
816 if (!uses_query_cache_storage()) {
817 return queryInfo.ensure_matches_one_transient(archetype, targetEntities, m_varBindings, m_varBindingsMask);
818 }
819
820 return queryInfo.ensure_matches_one(archetype, targetEntities, m_varBindings, m_varBindingsMask);
821 }
822
828 GAIA_NODISCARD bool matches_any(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
829 const auto kindError = validate_kind(queryInfo.ctx());
830 if (kindError != QueryKindRes::OK) {
831 GAIA_ASSERT2(SilenceInvalidCacheKindAssertions, kind_error_str(kindError));
832 queryInfo.reset();
833 return false;
834 }
835
836 return matches_target_entities(queryInfo, archetype, targetEntities);
837 }
838
839 //--------------------------------------------------------------------------------
840
843 GAIA_NODISCARD QueryCachePolicy cache_policy() {
844 return fetch().cache_policy();
845 }
846
853 QueryImpl& cache_src_trav(uint16_t maxItems) {
854 if (m_cacheSrcTrav == maxItems)
855 return *this;
856
857 if (maxItems > MaxCacheSrcTrav) {
858 GAIA_ASSERT(false && "cache_src_trav should be a value smaller than MaxCacheSrcTrav");
859 maxItems = MaxCacheSrcTrav;
860 }
861
862 invalidate_each_walk_cache();
863 invalidate_direct_seed_run_cache();
864 invalidate_query_storage();
865 m_cacheSrcTrav = maxItems;
866 return *this;
867 }
868
872 GAIA_NODISCARD uint16_t cache_src_trav() const {
873 return m_cacheSrcTrav;
874 }
875
879 QueryImpl& kind(QueryCacheKind cacheKind) {
880 if (m_cacheKind == cacheKind)
881 return *this;
882
883 invalidate_each_walk_cache();
884 invalidate_direct_seed_run_cache();
885 invalidate_query_storage();
886 m_cacheKind = cacheKind;
887
888 return *this;
889 }
890
894 QueryImpl& scope(QueryCacheScope cacheScope) {
895 if (m_cacheScope == cacheScope)
896 return *this;
897
898 invalidate_each_walk_cache();
899 invalidate_direct_seed_run_cache();
900 invalidate_query_storage();
901 m_cacheScope = cacheScope;
902
903 return *this;
904 }
905
910 add_cmd(cmd);
911 return *this;
912 }
913
916 GAIA_NODISCARD QueryCacheScope scope() const {
917 return m_cacheScope;
918 }
919
922 GAIA_NODISCARD QueryCacheKind kind() const {
923 return m_cacheKind;
924 }
925
928 GAIA_NODISCARD QueryKindRes kind_error() {
929 return validate_kind(fetch().ctx());
930 }
931
934 GAIA_NODISCARD const char* kind_error_str() {
935 return kind_error_str(kind_error());
936 }
937
940 GAIA_NODISCARD bool valid() {
941 return kind_error() == QueryKindRes::OK;
942 }
943
944 //--------------------------------------------------------------------------------
945 private:
949 GAIA_NODISCARD bool uses_manual_src_trav_cache(const QueryCtx& ctx) const {
950 return m_cacheSrcTrav != 0 && //
951 ctx.data.deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms) && //
952 ctx.data.deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms);
953 }
954
958 GAIA_NODISCARD static bool uses_im_cache(const QueryCtx& ctx) {
959 return ctx.data.cachePolicy == QueryCachePolicy::Immediate;
960 }
961
965 GAIA_NODISCARD static bool uses_lazy_cache(const QueryCtx& ctx) {
966 return ctx.data.cachePolicy == QueryCachePolicy::Lazy;
967 }
968
972 GAIA_NODISCARD static bool uses_dyn_cache(const QueryCtx& ctx) {
973 return ctx.data.cachePolicy == QueryCachePolicy::Dynamic;
974 }
975
979 GAIA_NODISCARD QueryKindRes validate_kind(const QueryCtx& ctx) const {
980 if (m_cacheKind == QueryCacheKind::Auto) {
981 if (uses_manual_src_trav_cache(ctx))
982 return QueryKindRes::AutoSrcTrav;
983 }
984
985 if (m_cacheKind == QueryCacheKind::All) {
986 if (uses_manual_src_trav_cache(ctx))
987 return QueryKindRes::AllSrcTrav;
988 if (!uses_im_cache(ctx))
989 return QueryKindRes::AllNotIm;
990 }
991
992 return QueryKindRes::OK;
993 }
994
998 GAIA_NODISCARD static const char* kind_error_str(QueryKindRes error) {
999 switch (error) {
1000 case QueryKindRes::OK:
1001 return "OK";
1002 case QueryKindRes::AutoSrcTrav:
1003 return "QueryCacheKind::Auto rejects explicit traversed-source snapshot caching";
1004 case QueryKindRes::AllNotIm:
1005 return "QueryCacheKind::All requires a fully immediate structural cache";
1006 case QueryKindRes::AllSrcTrav:
1007 return "QueryCacheKind::All rejects explicit traversed-source snapshot caching";
1008 default:
1009 return "Unknown query kind validation error";
1010 }
1011 }
1012
1015 GAIA_NODISCARD EachWalkData* each_walk_data() {
1016 return m_eachWalkData.get();
1017 }
1018
1021 GAIA_NODISCARD const EachWalkData* each_walk_data() const {
1022 return m_eachWalkData.get();
1023 }
1024
1027 GAIA_NODISCARD EachWalkData& ensure_each_walk_data() {
1028 return m_eachWalkData.ensure();
1029 }
1030
1032 void invalidate_each_walk_cache() {
1033 auto* pWalkData = each_walk_data();
1034 if (pWalkData != nullptr)
1035 pWalkData->cacheValid = false;
1036 }
1037
1040 GAIA_NODISCARD DirectSeedRunData* direct_seed_run_data() {
1041 return m_directSeedRunData.get();
1042 }
1043
1046 GAIA_NODISCARD const DirectSeedRunData* direct_seed_run_data() const {
1047 return m_directSeedRunData.get();
1048 }
1049
1052 GAIA_NODISCARD DirectSeedRunData& ensure_direct_seed_run_data() {
1053 return m_directSeedRunData.ensure();
1054 }
1055
1057 void invalidate_direct_seed_run_cache() {
1058 auto* pRunData = direct_seed_run_data();
1059 if (pRunData != nullptr)
1060 pRunData->cacheValid = false;
1061 }
1062
1064 void reset_changed_filter_state() {
1065 m_changedWorldVersion = 0;
1066 }
1067
1070 ArchetypeId last_archetype_id() const {
1071 return *m_nextArchetypeId - 1;
1072 }
1073
1076 GAIA_NODISCARD std::span<const Archetype*> all_archetypes_view() const {
1077 GAIA_ASSERT(m_allArchetypes != nullptr);
1078 return {(const Archetype**)m_allArchetypes->data(), m_allArchetypes->size()};
1079 }
1080
1081 GAIA_NODISCARD static bool is_query_var_entity(Entity entity) {
1082 return is_variable((EntityId)entity.id());
1083 }
1084
1085 GAIA_NODISCARD static uint32_t query_var_idx(Entity entity) {
1086 GAIA_ASSERT(is_query_var_entity(entity));
1087 return (uint32_t)(entity.id() - Var0.id());
1088 }
1089
1090 GAIA_NODISCARD Entity query_var_entity(uint32_t idx) {
1091 GAIA_ASSERT(idx < 8);
1092 return entity_from_id((const World&)*m_storage.world(), (EntityId)(Var0.id() + idx));
1093 }
1094
1095 GAIA_NODISCARD static util::str_view normalize_var_name(util::str_view name) {
1096 auto trimmed = util::trim(name);
1097 if (trimmed.empty())
1098 return {};
1099
1100 if (trimmed.data()[0] == '$') {
1101 if (trimmed.size() == 1)
1102 return {};
1103 trimmed = util::str_view(trimmed.data() + 1, trimmed.size() - 1);
1104 }
1105
1106 return util::trim(trimmed);
1107 }
1108
1109 GAIA_NODISCARD static bool is_reserved_var_name(util::str_view varName) {
1110 return varName == "this";
1111 }
1112
1113 GAIA_NODISCARD Entity find_var_by_name(util::str_view rawName) {
1114 const auto varName = normalize_var_name(rawName);
1115 if (varName.empty() || is_reserved_var_name(varName))
1116 return EntityBad;
1117
1118 GAIA_FOR(8) {
1119 const auto bit = (uint8_t(1) << i);
1120 if ((m_varNamesMask & bit) == 0)
1121 continue;
1122 if (m_varNames[i] == varName)
1123 return query_var_entity(i);
1124 }
1125
1126 return EntityBad;
1127 }
1128
1129 bool set_var_name_internal(Entity varEntity, util::str_view rawName) {
1130 if (!is_query_var_entity(varEntity))
1131 return false;
1132
1133 const auto varName = normalize_var_name(rawName);
1134 if (varName.empty() || is_reserved_var_name(varName))
1135 return false;
1136
1137 const auto idx = query_var_idx(varEntity);
1138 const auto bit = (uint8_t(1) << idx);
1139
1140 GAIA_FOR(8) {
1141 if (i == idx)
1142 continue;
1143
1144 const auto otherBit = (uint8_t(1) << i);
1145 if ((m_varNamesMask & otherBit) == 0)
1146 continue;
1147 if (!(m_varNames[i] == varName))
1148 continue;
1149
1150 GAIA_ASSERT2(false, "Variable name is already assigned to a different query variable");
1151 return false;
1152 }
1153
1154 m_varNames[idx].assign(varName);
1155 m_varNamesMask |= bit;
1156 return true;
1157 }
1158
1159 template <typename T>
1160 void add_cmd(T& cmd) {
1161 invalidate_each_walk_cache();
1162
1163 // Make sure to invalidate if necessary.
1164 if constexpr (T::InvalidatesHash) {
1165 reset_changed_filter_state();
1166 m_storage.invalidate();
1167 }
1168
1169 auto& serBuffer = m_storage.ser_buffer();
1170 ser::save(serBuffer, T::Id);
1171 ser::save(serBuffer, T::InvalidatesHash);
1172 ser::save(serBuffer, cmd);
1173 }
1174
1175 void add_inter(QueryInput item) {
1176 // When excluding or using ANY terms make sure the access type is None.
1177 GAIA_ASSERT((item.op != QueryOpKind::Not && item.op != QueryOpKind::Any) || item.access == QueryAccess::None);
1178
1179 QueryCmd_AddItem cmd{item};
1180 add_cmd(cmd);
1181 }
1182
1183 GAIA_NODISCARD static QueryAccess normalize_access(QueryOpKind op, Entity entity, QueryAccess access) {
1184 if (op == QueryOpKind::Not || op == QueryOpKind::Any || entity.pair())
1185 return QueryAccess::None;
1186
1187 // Non-pair ALL/OR terms default to Read access when unspecified.
1188 if (access == QueryAccess::None)
1189 return QueryAccess::Read;
1190
1191 return access;
1192 }
1193
1194 void add_entity_term(QueryOpKind op, Entity entity, const QueryTermOptions& options) {
1195 const auto access = normalize_access(op, entity, options.access);
1196 add(
1197 {op, access, entity, options.entSrc, options.entTrav, options.travKind, options.travDepth,
1198 options.matchKind});
1199 }
1200
1201 template <typename T>
1202 void add_inter(QueryOpKind op) {
1203 Entity e;
1204
1205 if constexpr (is_pair<T>::value) {
1206 // Make sure the components are always registered
1207 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1208 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1209
1210 e = Pair(desc_rel.entity, desc_tgt.entity);
1211 } else {
1212 // Make sure the component is always registered
1213 const auto& desc = comp_cache_add<T>(*m_storage.world());
1214 e = desc.entity;
1215 }
1216
1217 // Determine the access type
1218 QueryAccess access = QueryAccess::None;
1219 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1220 constexpr auto isReadWrite = core::is_mut_v<T>;
1221 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1222 }
1223
1224 add_inter({op, access, e});
1225 }
1226
1227 template <typename T>
1228 void add_inter(QueryOpKind op, const QueryTermOptions& options) {
1229 Entity e;
1230
1231 if constexpr (is_pair<T>::value) {
1232 // Make sure the components are always registered
1233 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1234 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1235
1236 e = Pair(desc_rel.entity, desc_tgt.entity);
1237 } else {
1238 // Make sure the component is always registered
1239 const auto& desc = comp_cache_add<T>(*m_storage.world());
1240 e = desc.entity;
1241 }
1242
1243 QueryAccess access = QueryAccess::None;
1244 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1245 if (options.access != QueryAccess::None)
1246 access = options.access;
1247 else {
1248 constexpr auto isReadWrite = core::is_mut_v<T>;
1249 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1250 }
1251 }
1252
1253 add_inter(
1254 {op, normalize_access(op, e, access), e, options.entSrc, options.entTrav, options.travKind,
1255 options.travDepth, options.matchKind});
1256 }
1257
1258 template <typename Rel, typename Tgt>
1259 void add_inter(QueryOpKind op) {
1260 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1261 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1262 static_assert(core::is_raw_v<UO_Rel>, "Use add() with raw types only");
1263 static_assert(core::is_raw_v<UO_Tgt>, "Use add() with raw types only");
1264
1265 // Make sure the component is always registered
1266 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1267 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1268
1269 // Determine the access type
1270 QueryAccess access = QueryAccess::None;
1271 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1272 constexpr auto isReadWrite = core::is_mut_v<UO_Rel> || core::is_mut_v<UO_Tgt>;
1273 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1274 }
1275
1276 add_inter({op, access, {descRel.entity, descTgt.entity}});
1277 }
1278
1279 //--------------------------------------------------------------------------------
1280
1281 void changed_inter(Entity entity) {
1282 QueryCmd_AddFilter cmd{entity};
1283 add_cmd(cmd);
1284 }
1285
1286 template <typename T>
1287 void changed_inter() {
1288 using UO = typename component_type_t<T>::TypeOriginal;
1289 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1290
1291 // Make sure the component is always registered
1292 const auto& desc = comp_cache_add<T>(*m_storage.world());
1293 changed_inter(desc.entity);
1294 }
1295
1296 template <typename Rel, typename Tgt>
1297 void changed_inter() {
1298 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1299 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1300 static_assert(core::is_raw_v<UO_Rel>, "Use changed() with raw types only");
1301 static_assert(core::is_raw_v<UO_Tgt>, "Use changed() with raw types only");
1302
1303 // Make sure the component is always registered
1304 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1305 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1306 changed_inter({descRel.entity, descTgt.entity});
1307 }
1308
1309 //--------------------------------------------------------------------------------
1310
1311 void sort_by_inter(Entity entity, TSortByFunc func) {
1312 QueryCmd_SortBy cmd{entity, func};
1313 add_cmd(cmd);
1314 }
1315
1316 template <typename T>
1317 void sort_by_inter(TSortByFunc func) {
1318 using UO = typename component_type_t<T>::TypeOriginal;
1319 if constexpr (std::is_same_v<UO, Entity>) {
1320 sort_by_inter(EntityBad, func);
1321 } else {
1322 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1323
1324 // Make sure the component is always registered
1325 const auto& desc = comp_cache_add<T>(*m_storage.world());
1326
1327 sort_by_inter(desc.entity, func);
1328 }
1329 }
1330
1331 template <typename Rel, typename Tgt>
1332 void sort_by_inter(TSortByFunc func) {
1333 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1334 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1335 static_assert(core::is_raw_v<UO_Rel>, "Use group_by() with raw types only");
1336 static_assert(core::is_raw_v<UO_Tgt>, "Use group_by() with raw types only");
1337
1338 // Make sure the component is always registered
1339 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1340 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1341
1342 sort_by_inter({descRel.entity, descTgt.entity}, func);
1343 }
1344
1345 //--------------------------------------------------------------------------------
1346
1347 void group_by_inter(Entity entity, TGroupByFunc func) {
1348 QueryCmd_GroupBy cmd{entity, func};
1349 add_cmd(cmd);
1350 }
1351
1352 template <typename T>
1353 void group_by_inter(Entity entity, TGroupByFunc func) {
1354 using UO = typename component_type_t<T>::TypeOriginal;
1355 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1356
1357 group_by_inter(entity, func);
1358 }
1359
1360 template <typename Rel, typename Tgt>
1361 void group_by_inter(TGroupByFunc func) {
1362 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1363 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1364 static_assert(core::is_raw_v<UO_Rel>, "Use group_by() with raw types only");
1365 static_assert(core::is_raw_v<UO_Tgt>, "Use group_by() with raw types only");
1366
1367 // Make sure the component is always registered
1368 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1369 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1370
1371 group_by_inter({descRel.entity, descTgt.entity}, func);
1372 }
1373
1374 //--------------------------------------------------------------------------------
1375
1376 void group_dep_inter(Entity relation) {
1377 GAIA_ASSERT(!relation.pair());
1378 QueryCmd_GroupDep cmd{relation};
1379 add_cmd(cmd);
1380 }
1381
1382 template <typename T>
1383 void group_dep_inter() {
1384 using UO = typename component_type_t<T>::TypeOriginal;
1385 static_assert(core::is_raw_v<UO>, "Use group_dep() with raw types only");
1386
1387 const auto& desc = comp_cache_add<T>(*m_storage.world());
1388 group_dep_inter(desc.entity);
1389 }
1390
1391 //--------------------------------------------------------------------------------
1392
1393 void set_group_id_inter(GroupId groupId) {
1394 // Dummy usage of GroupIdMax to avoid warning about unused constant
1395 (void)GroupIdMax;
1396
1397 invalidate_each_walk_cache();
1398 m_groupIdSet = groupId;
1399 }
1400
1401 void set_group_id_inter(Entity groupId) {
1402 set_group_id_inter(groupId.id());
1403 }
1404
1405 template <typename T>
1406 void set_group_id_inter() {
1407 using UO = typename component_type_t<T>::TypeOriginal;
1408 static_assert(core::is_raw_v<UO>, "Use group_id() with raw types only");
1409
1410 // Make sure the component is always registered
1411 const auto& desc = comp_cache_add<T>(*m_storage.world());
1412 set_group_id_inter(desc.entity);
1413 }
1414
1415 //--------------------------------------------------------------------------------
1416
1417 void commit(QueryCtx& ctx) {
1418 GAIA_PROF_SCOPE(query::commit);
1419
1420#if GAIA_ASSERT_ENABLED
1421 GAIA_ASSERT(m_storage.m_identity.handle.id() == QueryIdBad);
1422#endif
1423
1424 auto& serBuffer = m_storage.ser_buffer();
1425
1426 // Read data from buffer and execute the command stored in it
1427 serBuffer.seek(0);
1428 while (serBuffer.tell() < serBuffer.bytes()) {
1429 QueryCmdType id{};
1430 bool invalidatesHash = false;
1431 ser::load(serBuffer, id);
1432 ser::load(serBuffer, invalidatesHash);
1433 (void)invalidatesHash; // We don't care about this during commit
1434 CommandBufferRead[id](serBuffer, ctx);
1435 }
1436
1437 // Calculate the lookup hash from the provided context
1438 if (uses_query_cache_storage()) {
1439 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1440 normalize_cache_src_trav(ctx);
1441 }
1442 if (uses_shared_cache_layer()) {
1443 auto& ctxData = ctx.data;
1444 if (ctxData.changedCnt > 1) {
1445 core::sort(ctxData.changed.data(), ctxData.changed.data() + ctxData.changedCnt, SortComponentCond{});
1446 }
1447 }
1448
1449 // We can free all temporary data now
1450 m_storage.ser_buffer_reset();
1451
1452 // Refresh the context
1453 ctx.refresh();
1454 if (uses_shared_cache_layer())
1455 calc_lookup_hash(ctx);
1456 }
1457
1458 void recommit(QueryCtx& ctx) {
1459 GAIA_PROF_SCOPE(query::recommit);
1460
1461 auto& serBuffer = m_storage.ser_buffer();
1462
1463 // Read data from buffer and execute the command stored in it
1464 serBuffer.seek(0);
1465 while (serBuffer.tell() < serBuffer.bytes()) {
1466 QueryCmdType id{};
1467 bool invalidatesHash = false;
1468 ser::load(serBuffer, id);
1469 ser::load(serBuffer, invalidatesHash);
1470 // Hash recalculation is not accepted here
1471 GAIA_ASSERT(!invalidatesHash);
1472 if (invalidatesHash)
1473 return;
1474 CommandBufferRead[id](serBuffer, ctx);
1475 }
1476 if (uses_query_cache_storage()) {
1477 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1478 normalize_cache_src_trav(ctx);
1479 }
1480
1481 // We can free all temporary data now
1482 m_storage.ser_buffer_reset();
1483 }
1484
1485 //--------------------------------------------------------------------------------
1486 public:
1487#if GAIA_ASSERT_ENABLED
1489 template <typename... T>
1490 GAIA_NODISCARD bool unpack_args_into_query_has_all(
1491 const QueryInfo& queryInfo, [[maybe_unused]] core::func_type_list<T...> types) const {
1492 if constexpr (sizeof...(T) > 0)
1493 return queryInfo.template has_all<T...>();
1494 else
1495 return true;
1496 }
1497#endif
1498
1499 //--------------------------------------------------------------------------------
1500
1501 GAIA_NODISCARD static bool
1502 match_filters(const Chunk& chunk, const QueryInfo& queryInfo, uint32_t changedWorldVersion) {
1503 GAIA_ASSERT(!chunk.empty() && "match_filters called on an empty chunk");
1504
1505 const auto queryVersion = changedWorldVersion;
1506 const auto& filtered = queryInfo.ctx().data.changed_view();
1507
1508 // Skip unchanged chunks
1509 if (filtered.empty())
1510 return false;
1511
1512 const auto filteredCnt = (uint32_t)filtered.size();
1513 auto ids = chunk.ids_view();
1514
1515 // This is the hot path for most change-filter queries.
1516 if (filteredCnt == 1) {
1517 const auto compIdx = core::get_index(ids, filtered[0]);
1518 if (compIdx != BadIndex && chunk.changed(queryVersion, compIdx))
1519 return true;
1520
1521 return chunk.changed(changedWorldVersion);
1522 }
1523
1524 // See if any component has changed
1525 uint32_t lastIdx = 0;
1526 for (const auto comp: filtered) {
1527 uint32_t compIdx = BadIndex;
1528 if (lastIdx < (uint32_t)ids.size()) {
1529 const auto suffixIdx =
1530 core::get_index(std::span<const Entity>(ids.data() + lastIdx, ids.size() - lastIdx), comp);
1531 if (suffixIdx != BadIndex)
1532 compIdx = lastIdx + suffixIdx;
1533 }
1534
1535 // Fallback for queries where change-filters are not monotonic in chunk column order
1536 // (e.g. OR-driven layouts).
1537 if (compIdx == BadIndex)
1538 compIdx = core::get_index(ids, comp);
1539 if (compIdx == BadIndex)
1540 continue;
1541
1542 if (chunk.changed(queryVersion, compIdx))
1543 return true;
1544
1545 lastIdx = compIdx;
1546 }
1547
1548 // If the component hasn't been modified, the entity itself still might have been moved.
1549 // For that reason we also need to check the entity version.
1550 return chunk.changed(changedWorldVersion);
1551 }
1552
1553 GAIA_NODISCARD bool can_process_archetype(const QueryInfo& queryInfo, const Archetype& archetype) const {
1554 // Archetypes requested for deletion are skipped for processing.
1555 if (archetype.is_req_del())
1556 return false;
1557
1558 // Prefabs are excluded from query results by default unless the query opted in
1559 // explicitly or it mentions Prefab directly.
1560 if (!queryInfo.matches_prefab_entities() && archetype.has(Prefab))
1561 return false;
1562
1563 return true;
1564 }
1565
1566 GAIA_NODISCARD static bool has_depth_order_hierarchy_enabled_barrier(const QueryInfo& queryInfo) {
1567 const auto& data = queryInfo.ctx().data;
1568 return data.groupByFunc == group_by_func_depth_order &&
1569 world_depth_order_prunes_disabled_subtrees(*queryInfo.world(), data.groupBy);
1570 }
1571
1578 GAIA_NODISCARD static bool
1580 if (!has_depth_order_hierarchy_enabled_barrier(queryInfo))
1581 return true;
1582
1583 const auto& world = *queryInfo.world();
1584 const auto relation = queryInfo.ctx().data.groupBy;
1585 auto ids = archetype.ids_view();
1586
1587 for (auto idsIdx: archetype.pair_rel_indices(relation)) {
1588 const auto pair = ids[idsIdx];
1589 const auto parent = world_pair_target_if_alive(world, pair);
1590 if (parent == EntityBad)
1591 return false;
1592 if (!world_entity_enabled_hierarchy(world, parent, relation))
1593 return false;
1594 }
1595
1596 return true;
1597 }
1598
1599 template <typename TIter>
1600 GAIA_NODISCARD bool can_process_archetype_inter(
1601 const QueryInfo& queryInfo, const Archetype& archetype, int8_t barrierPasses = -1) const {
1602 if (!can_process_archetype(queryInfo, archetype))
1603 return false;
1604 if constexpr (std::is_same_v<TIter, Iter>) {
1605 if (has_depth_order_hierarchy_enabled_barrier(queryInfo)) {
1606 if (barrierPasses >= 0)
1607 return barrierPasses != 0;
1608 if (!survives_cascade_hierarchy_enabled_barrier(queryInfo, archetype))
1609 return false;
1610 }
1611 }
1612 return true;
1613 }
1614
1615 template <typename T>
1616 GAIA_NODISCARD static constexpr bool is_write_query_arg() {
1617 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
1618 return std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>> &&
1619 !std::is_same_v<Arg, Entity>;
1620 }
1621
1622 template <typename TIter>
1623 static void finish_iter_writes(TIter& it) {
1624 if (it.chunk() == nullptr)
1625 return;
1626
1627 auto compIndices = it.touched_comp_indices();
1628 for (auto compIdx: compIndices)
1629 const_cast<Chunk*>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
1630
1631 auto terms = it.touched_terms();
1632 if (terms.empty())
1633 return;
1634
1635 auto entities = it.entity_rows();
1636 auto& world = *it.world();
1637 GAIA_EACH(terms) {
1638 const auto term = terms[i];
1639 if (!world_is_out_of_line_component(world, term)) {
1640 const auto compIdx = core::get_index(it.chunk()->ids_view(), term);
1641 if (compIdx != BadIndex) {
1642 const_cast<Chunk*>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
1643 continue;
1644 }
1645 }
1646
1647 GAIA_FOR_(entities.size(), j) {
1648 world_finish_write(world, term, entities[j]);
1649 }
1650 }
1651 }
1652
1653 template <typename... T>
1654 static void finish_typed_chunk_writes(World& world, Chunk* pChunk, uint16_t from, uint16_t to) {
1655 if (from >= to)
1656 return;
1657
1658 Entity seenTerms[sizeof...(T) > 0 ? sizeof...(T) : 1]{};
1659 uint32_t seenCnt = 0;
1660 const auto entities = pChunk->entity_view();
1661 const auto finish_term = [&](Entity term) {
1662 GAIA_FOR(seenCnt) {
1663 if (seenTerms[i] == term)
1664 return;
1665 }
1666
1667 seenTerms[seenCnt++] = term;
1668 if (!world_is_out_of_line_component(world, term)) {
1669 const auto compIdx = core::get_index(pChunk->ids_view(), term);
1670 if (compIdx != BadIndex) {
1671 pChunk->finish_write(compIdx, from, to);
1672 return;
1673 }
1674 }
1675
1676 for (uint16_t row = from; row < to; ++row)
1677 world_finish_write(world, term, entities[row]);
1678 };
1679
1680 (
1681 [&] {
1682 if constexpr (is_write_query_arg<T>()) {
1683 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
1684 finish_term(world_query_arg_id<Arg>(world));
1685 }
1686 }(),
1687 ...);
1688 }
1689
1690 template <typename T, typename TIter>
1691 static void finish_typed_iter_write_arg(TIter& it, uint32_t fieldIdx) {
1692 if constexpr (!is_write_query_arg<T>())
1693 return;
1694 else {
1695 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
1696 auto* pChunk = const_cast<Chunk*>(it.chunk());
1697 auto& world = *it.world();
1698 Entity term = it.term_ids() != nullptr ? it.term_ids()[fieldIdx] : world_query_arg_id<Arg>(world);
1699 if (term == EntityBad)
1700 term = world_query_arg_id<Arg>(world);
1701
1702 if (it.row_begin() >= it.row_end())
1703 return;
1704
1705 auto compIdx = uint8_t(0xFF);
1706 if (it.comp_indices() != nullptr && fieldIdx < ChunkHeader::MAX_COMPONENTS)
1707 compIdx = it.comp_indices()[fieldIdx];
1708 else if (!world_is_out_of_line_component(world, term))
1709 compIdx = (uint8_t)pChunk->comp_idx(term);
1710 if (compIdx != 0xFF && !world_is_out_of_line_component(world, term)) {
1711 pChunk->finish_write(compIdx, it.row_begin(), it.row_end());
1712 return;
1713 }
1714
1715 auto entities = it.entity_rows();
1716 GAIA_FOR(entities.size()) {
1717 world_finish_write(world, term, entities[i]);
1718 }
1719 }
1720 }
1721
1722 template <typename TIter, typename... T, size_t... I>
1723 static void finish_typed_iter_writes(TIter& it, std::index_sequence<I...>) {
1724 (finish_typed_iter_write_arg<T>(it, (uint32_t)I), ...);
1725 }
1726
1727 enum class ExecPayloadKind : uint8_t { Plain, Grouped, NonTrivial };
1728
1729 template <typename TIter>
1730 GAIA_NODISCARD static ExecPayloadKind exec_payload_kind(const QueryInfo& queryInfo) {
1731 if (queryInfo.has_sorted_payload())
1732 return ExecPayloadKind::NonTrivial;
1733 if (!queryInfo.has_grouped_payload())
1734 return ExecPayloadKind::Plain;
1735 if constexpr (std::is_same_v<TIter, Iter>) {
1736 if (has_depth_order_hierarchy_enabled_barrier(queryInfo))
1737 return ExecPayloadKind::NonTrivial;
1738 }
1739 return ExecPayloadKind::Grouped;
1740 }
1741
1742 template <typename TIter>
1743 GAIA_NODISCARD static bool needs_nontrivial_payload(const QueryInfo& queryInfo) {
1744 return exec_payload_kind<TIter>(queryInfo) == ExecPayloadKind::NonTrivial;
1745 }
1746
1747 //--------------------------------------------------------------------------------
1748
1750 template <typename Func, typename TIter>
1751 static void run_query_func(World* pWorld, Func func, ChunkBatch& batch) {
1752 TIter it;
1753 it.set_world(pWorld);
1754 it.set_write_im(false);
1755 it.set_archetype(batch.pArchetype);
1756 it.set_chunk(batch.pChunk, batch.from, batch.to);
1757 it.set_group_id(batch.groupId);
1758 it.set_comp_indices(batch.pCompIndices);
1759 it.set_inherited_data(batch.inheritedData);
1760 func(it);
1761 finish_iter_writes(it);
1762 it.clear_touched_writes();
1763 }
1764
1766 template <typename Func, typename TIter>
1767 static void run_query_arch_func(World* pWorld, Func func, ChunkBatch& batch) {
1768 TIter it;
1769 it.set_world(pWorld);
1770 it.set_write_im(false);
1771 it.set_archetype(batch.pArchetype);
1772 // it.set_chunk(nullptr, 0, 0); We do not need this, and calling it would assert
1773 it.set_group_id(batch.groupId);
1774 it.set_comp_indices(batch.pCompIndices);
1775 it.set_inherited_data(batch.inheritedData);
1776 func(it);
1777 it.clear_touched_writes();
1778 }
1779
1781 template <typename Func, typename TIter>
1782 static void run_query_func(World* pWorld, Func func, std::span<ChunkBatch> batches) {
1783 GAIA_PROF_SCOPE(query::run_query_func);
1784
1785 const auto chunkCnt = batches.size();
1786 GAIA_ASSERT(chunkCnt > 0);
1787
1788 TIter it;
1789 it.set_world(pWorld);
1790 it.set_write_im(false);
1791
1792 const Archetype* pLastArchetype = nullptr;
1793 const uint8_t* pLastIndices = nullptr;
1794 InheritedTermDataView lastInheritedData{};
1795 GroupId lastGroupId = GroupIdMax;
1796
1797 const auto apply_batch = [&](const ChunkBatch& batch) {
1798 if (batch.pArchetype != pLastArchetype) {
1799 it.set_archetype(batch.pArchetype);
1800 pLastArchetype = batch.pArchetype;
1801 }
1802
1803 if (batch.pCompIndices != pLastIndices) {
1804 it.set_comp_indices(batch.pCompIndices);
1805 pLastIndices = batch.pCompIndices;
1806 }
1807
1808 if (batch.inheritedData.data() != lastInheritedData.data()) {
1809 it.set_inherited_data(batch.inheritedData);
1810 lastInheritedData = batch.inheritedData;
1811 }
1812
1813 if (batch.groupId != lastGroupId) {
1814 it.set_group_id(batch.groupId);
1815 lastGroupId = batch.groupId;
1816 }
1817
1818 it.set_chunk(batch.pChunk, batch.from, batch.to);
1819 func(it);
1820 finish_iter_writes(it);
1821 it.clear_touched_writes();
1822 };
1823
1824 // We only have one chunk to process.
1825 if GAIA_UNLIKELY (chunkCnt == 1) {
1826 apply_batch(batches[0]);
1827 return;
1828 }
1829
1830 // We have many chunks to process.
1831 // Chunks might be located at different memory locations. Not even in the same memory page.
1832 // Therefore, to make it easier for the CPU we give it a hint that we want to prefetch data
1833 // for the next chunk explicitly so we do not end up stalling later.
1834 // Note, this is a micro optimization and on average it brings no performance benefit. It only
1835 // helps with edge cases.
1836 // Let us be conservative for now and go with T2. That means we will try to keep our data at
1837 // least in L3 cache or higher.
1839 apply_batch(batches[0]);
1840
1841 uint32_t chunkIdx = 1;
1842 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
1843 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
1844 apply_batch(batches[chunkIdx]);
1845 }
1846
1847 apply_batch(batches[chunkIdx]);
1848 }
1849
1850 //------------------------------------------------
1851
1852 template <bool HasFilters, typename TIter, typename Func>
1853 void run_query_batch_no_group_id(
1854 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
1855 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
1856
1857 auto cacheView = queryInfo.cache_archetype_view();
1858 const auto payloadKind = exec_payload_kind<TIter>(queryInfo);
1859 auto sortView = queryInfo.cache_sort_view();
1860 if (payloadKind != ExecPayloadKind::NonTrivial)
1861 sortView = {};
1862 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
1863 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial && std::is_same_v<TIter, Iter> &&
1864 has_depth_order_hierarchy_enabled_barrier(queryInfo);
1865 if (needsBarrierCache)
1866 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
1867
1868 lock(*m_storage.world());
1869
1870 // We are batching by chunks. Some of them might contain only few items but this state is only
1871 // temporary because defragmentation runs constantly and keeps things clean.
1872 ChunkBatchArray chunkBatches;
1873
1874 if (!sortView.empty()) {
1875 for (const auto& view: sortView) {
1876 const auto chunkEntitiesCnt = TIter::size(view.pChunk);
1877 if GAIA_UNLIKELY (chunkEntitiesCnt == 0)
1878 continue;
1879
1880 const auto viewFrom = view.startRow;
1881 const auto viewTo = (uint16_t)(view.startRow + view.count);
1882
1883 const auto minStartRow = TIter::start_index(view.pChunk);
1884 const auto minEndRow = TIter::end_index(view.pChunk);
1885 const auto startRow = core::get_max(minStartRow, viewFrom);
1886 const auto endRow = core::get_min(minEndRow, viewTo);
1887 const auto totalRows = endRow - startRow;
1888 if (totalRows == 0)
1889 continue;
1890
1891 if constexpr (HasFilters) {
1892 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
1893 continue;
1894 }
1895
1896 auto* pArchetype = const_cast<Archetype*>(cacheView[view.archetypeIdx]);
1897 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
1898 if GAIA_UNLIKELY (!can_process_archetype_inter<TIter>(queryInfo, *pArchetype, barrierPasses))
1899 continue;
1900 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
1901 const auto inheritedDataView =
1902 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
1903
1904 chunkBatches.push_back(
1905 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
1906
1907 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
1908 run_query_func<Func, TIter>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
1909 chunkBatches.clear();
1910 }
1911 }
1912 } else {
1913 for (uint32_t i = idxFrom; i < idxTo; ++i) {
1914 auto* pArchetype = const_cast<Archetype*>(cacheView[i]);
1915 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
1916 if GAIA_UNLIKELY (!can_process_archetype_inter<TIter>(queryInfo, *pArchetype, barrierPasses))
1917 continue;
1918
1919 auto indicesView = queryInfo.indices_mapping_view(i);
1920 const auto inheritedDataView =
1921 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
1922 const auto& chunks = pArchetype->chunks();
1923 uint32_t chunkOffset = 0;
1924 uint32_t itemsLeft = chunks.size();
1925 while (itemsLeft > 0) {
1926 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
1927 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
1928
1929 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
1930 for (auto* pChunk: chunkSpan) {
1931 if GAIA_UNLIKELY (TIter::size(pChunk) == 0)
1932 continue;
1933
1934 if constexpr (HasFilters) {
1935 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
1936 continue;
1937 }
1938
1939 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, 0, 0});
1940 }
1941
1942 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
1943 run_query_func<Func, TIter>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
1944 chunkBatches.clear();
1945 }
1946
1947 itemsLeft -= batchSize;
1948 chunkOffset += batchSize;
1949 }
1950 }
1951 }
1952
1953 // Take care of any leftovers not processed during run_query
1954 if (!chunkBatches.empty())
1955 run_query_func<Func, TIter>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
1956
1957 unlock(*m_storage.world());
1958 // Commit the command buffer.
1959 // TODO: Smart handling necessary
1960 commit_cmd_buffer_st(*m_storage.world());
1961 commit_cmd_buffer_mt(*m_storage.world());
1962 }
1963
1964 template <bool HasFilters, typename TIter, typename Func, QueryExecType ExecType>
1965 void run_query_batch_no_group_id_par(
1966 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
1967 static_assert(ExecType != QueryExecType::Default);
1968 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
1969
1970 auto cacheView = queryInfo.cache_archetype_view();
1971 const auto payloadKind = exec_payload_kind<TIter>(queryInfo);
1972 auto sortView = queryInfo.cache_sort_view();
1973 if (payloadKind != ExecPayloadKind::NonTrivial)
1974 sortView = {};
1975 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
1976 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial && std::is_same_v<TIter, Iter> &&
1977 has_depth_order_hierarchy_enabled_barrier(queryInfo);
1978 if (needsBarrierCache)
1979 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
1980
1981 if (!sortView.empty()) {
1982 for (const auto& view: sortView) {
1983 const auto chunkEntitiesCnt = TIter::size(view.pChunk);
1984 if GAIA_UNLIKELY (chunkEntitiesCnt == 0)
1985 continue;
1986
1987 const auto viewFrom = view.startRow;
1988 const auto viewTo = (uint16_t)(view.startRow + view.count);
1989
1990 const auto minStartRow = TIter::start_index(view.pChunk);
1991 const auto minEndRow = TIter::end_index(view.pChunk);
1992 const auto startRow = core::get_max(minStartRow, viewFrom);
1993 const auto endRow = core::get_min(minEndRow, viewTo);
1994 const auto totalRows = endRow - startRow;
1995 if (totalRows == 0)
1996 continue;
1997
1998 if constexpr (HasFilters) {
1999 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2000 continue;
2001 }
2002
2003 const auto* pArchetype = cacheView[view.archetypeIdx];
2004 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2005 if GAIA_UNLIKELY (!can_process_archetype_inter<TIter>(queryInfo, *pArchetype, barrierPasses))
2006 continue;
2007 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2008 const auto inheritedDataView =
2009 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2010
2011 m_batches.push_back(
2012 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2013 }
2014 } else {
2015 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2016 const auto* pArchetype = cacheView[i];
2017 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2018 if GAIA_UNLIKELY (!can_process_archetype_inter<TIter>(queryInfo, *pArchetype, barrierPasses))
2019 continue;
2020
2021 auto indicesView = queryInfo.indices_mapping_view(i);
2022 const auto inheritedDataView =
2023 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2024 const auto& chunks = pArchetype->chunks();
2025 for (auto* pChunk: chunks) {
2026 if GAIA_UNLIKELY (TIter::size(pChunk) == 0)
2027 continue;
2028
2029 if constexpr (HasFilters) {
2030 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2031 continue;
2032 }
2033
2034 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, 0, 0});
2035 }
2036 }
2037 }
2038
2039 if (m_batches.empty())
2040 return;
2041
2042 lock(*m_storage.world());
2043
2044 mt::JobParallel j;
2045
2046 // Use efficiency cores for low-level priority jobs
2047 if constexpr (ExecType == QueryExecType::ParallelEff)
2048 j.priority = mt::JobPriority::Low;
2049
2050 j.func = [&](const mt::JobArgs& args) {
2051 run_query_func<Func, TIter>(
2052 m_storage.world(), func, std::span(&m_batches[args.idxStart], args.idxEnd - args.idxStart));
2053 };
2054
2055 auto& tp = mt::ThreadPool::get();
2056 auto jobHandle = tp.sched_par(j, m_batches.size(), 0);
2057 tp.wait(jobHandle);
2058 m_batches.clear();
2059
2060 unlock(*m_storage.world());
2061 // Commit the command buffer.
2062 // TODO: Smart handling necessary
2063 commit_cmd_buffer_st(*m_storage.world());
2064 commit_cmd_buffer_mt(*m_storage.world());
2065 }
2066
2067 template <bool HasFilters, typename TIter, typename Func>
2068 void run_query_batch_with_group_id(
2069 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2070 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
2071
2072 ChunkBatchArray chunkBatches;
2073
2074 auto cacheView = queryInfo.cache_archetype_view();
2075 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2076 const bool needsBarrierCache = needs_nontrivial_payload<TIter>(queryInfo) && std::is_same_v<TIter, Iter> &&
2077 has_depth_order_hierarchy_enabled_barrier(queryInfo);
2078 if (needsBarrierCache)
2079 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2080
2081 lock(*m_storage.world());
2082
2083 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2084 const auto* pArchetype = cacheView[i];
2085 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2086 if GAIA_UNLIKELY (!can_process_archetype_inter<TIter>(queryInfo, *pArchetype, barrierPasses))
2087 continue;
2088
2089 auto indicesView = queryInfo.indices_mapping_view(i);
2090 const auto inheritedDataView =
2091 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2092 const auto& chunks = pArchetype->chunks();
2093 const auto groupId = queryInfo.group_id(i);
2094
2095#if GAIA_ASSERT_ENABLED
2096 GAIA_ASSERT(
2097 // ... or no groupId is set...
2098 m_groupIdSet == 0 ||
2099 // ... or the groupId must match the requested one
2100 groupId == m_groupIdSet);
2101#endif
2102
2103 uint32_t chunkOffset = 0;
2104 uint32_t itemsLeft = chunks.size();
2105 while (itemsLeft > 0) {
2106 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
2107 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
2108
2109 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
2110 for (auto* pChunk: chunkSpan) {
2111 if GAIA_UNLIKELY (TIter::size(pChunk) == 0)
2112 continue;
2113
2114 if constexpr (HasFilters) {
2115 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2116 continue;
2117 }
2118
2119 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, 0, 0});
2120 }
2121
2122 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2123 run_query_func<Func, TIter>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2124 chunkBatches.clear();
2125 }
2126
2127 itemsLeft -= batchSize;
2128 chunkOffset += batchSize;
2129 }
2130 }
2131
2132 // Take care of any leftovers not processed during run_query
2133 if (!chunkBatches.empty())
2134 run_query_func<Func, TIter>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2135
2136 unlock(*m_storage.world());
2137 // Commit the command buffer.
2138 // TODO: Smart handling necessary
2139 commit_cmd_buffer_st(*m_storage.world());
2140 commit_cmd_buffer_mt(*m_storage.world());
2141 }
2142
2143 template <bool HasFilters, typename TIter, typename Func, QueryExecType ExecType>
2144 void run_query_batch_with_group_id_par(
2145 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2146 static_assert(ExecType != QueryExecType::Default);
2147 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
2148
2149 ChunkBatchArray chunkBatch;
2150
2151 auto cacheView = queryInfo.cache_archetype_view();
2152 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2153 const bool needsBarrierCache = needs_nontrivial_payload<TIter>(queryInfo) && std::is_same_v<TIter, Iter> &&
2154 has_depth_order_hierarchy_enabled_barrier(queryInfo);
2155 if (needsBarrierCache)
2156 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2157
2158#if GAIA_ASSERT_ENABLED
2159 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2160 const auto* pArchetype = cacheView[i];
2161 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2162 if GAIA_UNLIKELY (!can_process_archetype_inter<TIter>(queryInfo, *pArchetype, barrierPasses))
2163 continue;
2164
2165 const auto groupId = queryInfo.group_id(i);
2166 GAIA_ASSERT(
2167 // ... or no groupId is set...
2168 m_groupIdSet == 0 ||
2169 // ... or the groupId must match the requested one
2170 groupId == m_groupIdSet);
2171 }
2172#endif
2173
2174 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2175 const Archetype* pArchetype = cacheView[i];
2176 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2177 if GAIA_UNLIKELY (!can_process_archetype_inter<TIter>(queryInfo, *pArchetype, barrierPasses))
2178 continue;
2179
2180 auto indicesView = queryInfo.indices_mapping_view(i);
2181 const auto inheritedDataView =
2182 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2183 const auto groupId = queryInfo.group_id(i);
2184 const auto& chunks = pArchetype->chunks();
2185 for (auto* pChunk: chunks) {
2186 if GAIA_UNLIKELY (TIter::size(pChunk) == 0)
2187 continue;
2188
2189 if constexpr (HasFilters) {
2190 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2191 continue;
2192 }
2193
2194 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, 0, 0});
2195 }
2196 }
2197
2198 if (m_batches.empty())
2199 return;
2200
2201 lock(*m_storage.world());
2202
2203 mt::JobParallel j;
2204
2205 // Use efficiency cores for low-level priority jobs
2206 if constexpr (ExecType == QueryExecType::ParallelEff)
2207 j.priority = mt::JobPriority::Low;
2208
2209 j.func = [&](const mt::JobArgs& args) {
2210 run_query_func<Func, TIter>(
2211 m_storage.world(), func, std::span(&m_batches[args.idxStart], args.idxEnd - args.idxStart));
2212 };
2213
2214 auto& tp = mt::ThreadPool::get();
2215 auto jobHandle = tp.sched_par(j, m_batches.size(), 0);
2216 tp.wait(jobHandle);
2217 m_batches.clear();
2218
2219 unlock(*m_storage.world());
2220 // Commit the command buffer.
2221 // TODO: Smart handling necessary
2222 commit_cmd_buffer_st(*m_storage.world());
2223 commit_cmd_buffer_mt(*m_storage.world());
2224 }
2225
2226 //------------------------------------------------
2227
2228 template <bool HasFilters, QueryExecType ExecType, typename TIter, typename Func>
2229 void run_query(const QueryInfo& queryInfo, Func func) {
2230 GAIA_PROF_SCOPE(query::run_query);
2231
2232 // TODO: Have archetype cache as double-linked list with pointers only.
2233 // Have chunk cache as double-linked list with pointers only.
2234 // Make it so only valid pointers are linked together.
2235 // This means one less indirection + we won't need to call can_process_archetype()
2236 // or pChunk.size()==0 in run_query_batch functions.
2237 auto cache_view = queryInfo.cache_archetype_view();
2238 if (cache_view.empty())
2239 return;
2240
2241 const bool isGroupBy = queryInfo.ctx().data.groupBy != EntityBad;
2242 const bool isGroupSet = m_groupIdSet != 0;
2243 if (!isGroupBy || !isGroupSet) {
2244 // No group requested or group filtering is currently turned off
2245 const auto idxFrom = 0;
2246 const auto idxTo = (uint32_t)cache_view.size();
2247 if constexpr (ExecType != QueryExecType::Default)
2248 run_query_batch_no_group_id_par<HasFilters, TIter, Func, ExecType>(queryInfo, idxFrom, idxTo, func);
2249 else
2250 run_query_batch_no_group_id<HasFilters, TIter, Func>(queryInfo, idxFrom, idxTo, func);
2251 } else {
2252 // We wish to iterate only a certain group
2253 const auto* pGroupData = queryInfo.selected_group_data(m_groupIdSet);
2254 if (pGroupData == nullptr)
2255 return;
2256
2257 const auto idxFrom = pGroupData->idxFirst;
2258 const auto idxTo = pGroupData->idxLast + 1;
2259 if constexpr (ExecType != QueryExecType::Default)
2260 run_query_batch_with_group_id_par<HasFilters, TIter, Func, ExecType>(queryInfo, idxFrom, idxTo, func);
2261 else
2262 run_query_batch_with_group_id<HasFilters, TIter, Func>(queryInfo, idxFrom, idxTo, func);
2263 }
2264 }
2265
2266 //------------------------------------------------
2267
2268 template <QueryExecType ExecType, typename TIter, typename Func>
2269 void run_query_on_archetypes(QueryInfo& queryInfo, Func func) {
2270 // Update the world version
2271 // We do read-only access. No need to update the version
2272 //::gaia::ecs::update_version(*m_worldVersion);
2273 lock(*m_storage.world());
2274
2275 {
2276 GAIA_PROF_SCOPE(query::run_query_a);
2277
2278 // TODO: Have archetype cache as double-linked list with pointers only.
2279 // Have chunk cache as double-linked list with pointers only.
2280 // Make it so only valid pointers are linked together.
2281 // This means one less indirection + we won't need to call can_process_archetype().
2282 auto cache_view = queryInfo.cache_archetype_view();
2283 const bool needsBarrierCache = has_depth_order_hierarchy_enabled_barrier(queryInfo);
2284 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2285 if (needsBarrierCache)
2286 queryInfo.ensure_depth_order_hierarchy_barrier_cache();
2287 GAIA_EACH(cache_view) {
2288 const auto* pArchetype = cache_view[i];
2289 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2290 if GAIA_UNLIKELY (!can_process_archetype_inter<Iter>(queryInfo, *pArchetype, barrierPasses))
2291 continue;
2292
2293 auto indicesView = queryInfo.indices_mapping_view(i);
2294 const auto inheritedDataView =
2295 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2296 ChunkBatch batch{pArchetype, nullptr, indicesView.data(), inheritedDataView, 0, 0, 0};
2297 run_query_arch_func<Func, Iter>(m_storage.world(), func, batch);
2298 }
2299 }
2300
2301 unlock(*m_storage.world());
2302 // Changed-filter state is instance-local for cached queries.
2303 }
2304
2305 //------------------------------------------------
2306
2307 template <QueryExecType ExecType, typename TIter, typename Func>
2308 void run_query_on_chunks(QueryInfo& queryInfo, Func func) {
2309 // Update the world version
2310 ::gaia::ecs::update_version(*m_worldVersion);
2311
2312 const bool hasFilters = queryInfo.has_filters();
2313 if (hasFilters)
2314 run_query<true, ExecType, TIter>(queryInfo, func);
2315 else
2316 run_query<false, ExecType, TIter>(queryInfo, func);
2317
2318 // Changed-filter state is instance-local for cached queries.
2319 m_changedWorldVersion = *m_worldVersion;
2320 }
2321
2322 GAIA_NODISCARD bool can_use_direct_chunk_iteration_fastpath(const QueryInfo& queryInfo) const {
2323 const auto& data = queryInfo.ctx().data;
2324 return data.sortByFunc == nullptr && !has_depth_order_hierarchy_enabled_barrier(queryInfo);
2325 }
2326
2327 template <typename Func, typename... T>
2328 void run_query_on_chunks_direct(QueryInfo& queryInfo, Func func, core::func_type_list<T...>) {
2329 if constexpr (has_write_query_args<T...>())
2330 ::gaia::ecs::update_version(*m_worldVersion);
2331
2332 const bool hasFilters = queryInfo.has_filters();
2333 auto cacheView = queryInfo.cache_archetype_view();
2334 if (cacheView.empty())
2335 return;
2336
2337 uint32_t idxFrom = 0;
2338 uint32_t idxTo = (uint32_t)cacheView.size();
2339 if (queryInfo.ctx().data.groupBy != EntityBad && m_groupIdSet != 0) {
2340 const auto* pGroupData = queryInfo.selected_group_data(m_groupIdSet);
2341 if (pGroupData == nullptr)
2342 return;
2343 idxFrom = pGroupData->idxFirst;
2344 idxTo = pGroupData->idxLast + 1;
2345 }
2346
2347 lock(*m_storage.world());
2348
2349 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2350 const auto* pArchetype = cacheView[i];
2351 if GAIA_UNLIKELY (!can_process_archetype_inter<Iter>(queryInfo, *pArchetype))
2352 continue;
2353
2354 const auto& chunks = pArchetype->chunks();
2355 for (auto* pChunk: chunks) {
2356 const auto from = Iter::start_index(pChunk);
2357 const auto to = Iter::end_index(pChunk);
2358 if GAIA_UNLIKELY (from == to)
2359 continue;
2360
2361 if (hasFilters) {
2362 if GAIA_UNLIKELY (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2363 continue;
2364 }
2365
2366 GAIA_PROF_SCOPE(query_func);
2367 run_query_on_chunk_rows_direct(pChunk, from, to, func, core::func_type_list<T...>{});
2368 finish_typed_chunk_writes<T...>(*queryInfo.world(), pChunk, from, to);
2369 }
2370 }
2371
2372 unlock(*m_storage.world());
2373 commit_cmd_buffer_st(*m_storage.world());
2374 commit_cmd_buffer_mt(*m_storage.world());
2375 m_changedWorldVersion = *m_worldVersion;
2376 }
2377
2378 template <typename TIter, typename Func, typename... T>
2379 GAIA_FORCEINLINE void
2380 run_query_on_chunk(TIter& it, Func func, [[maybe_unused]] core::func_type_list<T...> types) {
2381 auto& queryInfo = fetch();
2382 auto& world = *const_cast<World*>(queryInfo.world());
2383 if (can_use_direct_chunk_term_eval<T...>(world, queryInfo))
2384 run_query_on_chunk_direct(it, func, types);
2385 else
2386 run_query_on_chunk(queryInfo, it, func, types);
2387 }
2388
2389 template <typename TIter, typename Func, typename... T>
2390 GAIA_FORCEINLINE void run_query_on_chunk(
2391 const QueryInfo& queryInfo, TIter& it, Func func, [[maybe_unused]] core::func_type_list<T...> types) {
2392 const auto cnt = it.size();
2393 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
2394
2395 if constexpr (sizeof...(T) > 0) {
2396 // Pointers to the respective component types in the chunk, e.g
2397 // q.each([&](Position& p, const Velocity& v) {...}
2398 // Translates to:
2399 // auto p = it.view_mut_inter<Position, true>();
2400 // auto v = it.view_inter<Velocity>();
2401 auto dataPointerTuple = std::make_tuple(it.template view_auto_any<T>()...);
2402
2403 // Iterate over each entity in the chunk.
2404 // Translates to:
2405 // GAIA_FOR(0, cnt) func(p[i], v[i]);
2406
2407 if (!hasEntityFilters) {
2408 GAIA_FOR(cnt) {
2409 func(
2410 std::get<decltype(it.template view_auto_any<T>())>(
2411 dataPointerTuple)[it.template acc_index<T>(i)]...);
2412 }
2413 } else {
2414 const auto entities = it.template view<Entity>();
2415 GAIA_FOR(cnt) {
2416 if (!match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
2417 continue;
2418 func(
2419 std::get<decltype(it.template view_auto_any<T>())>(
2420 dataPointerTuple)[it.template acc_index<T>(i)]...);
2421 }
2422 }
2423 } else {
2424 // No functor parameters. Do an empty loop.
2425 if (!hasEntityFilters) {
2426 GAIA_FOR(cnt) {
2427 func();
2428 }
2429 } else {
2430 const auto entities = it.template view<Entity>();
2431 GAIA_FOR(cnt) {
2432 if (!match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
2433 continue;
2434 func();
2435 }
2436 }
2437 }
2438
2439 finish_typed_iter_writes<TIter, T...>(it, std::index_sequence_for<T...>{});
2440 it.clear_touched_writes();
2441 }
2442
2443 template <typename TIter, typename Func, typename... T>
2444 GAIA_FORCEINLINE void
2445 run_query_on_chunk_direct(TIter& it, Func func, [[maybe_unused]] core::func_type_list<T...> types) {
2446 run_query_on_chunk_rows_direct(const_cast<Chunk*>(it.chunk()), it.row_begin(), it.row_end(), func, types);
2447 finish_typed_iter_writes<TIter, T...>(it, std::index_sequence_for<T...>{});
2448 it.clear_touched_writes();
2449 }
2450
2451 template <typename... T>
2452 GAIA_NODISCARD static constexpr bool has_write_query_args() {
2453 return (is_write_query_arg<T>() || ...);
2454 }
2455
2456 template <typename T>
2457 GAIA_FORCEINLINE static decltype(auto) chunk_view_auto(Chunk* pChunk) {
2458 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
2459 if constexpr (std::is_same_v<Arg, Entity>)
2460 return pChunk->entity_view();
2461 else {
2462 using FT = typename component_type_t<Arg>::TypeFull;
2463 if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>)
2464 return pChunk->template sview_mut<FT>();
2465 else
2466 return pChunk->template view<FT>();
2467 }
2468 }
2469
2470 template <typename Func, typename... T>
2471 GAIA_FORCEINLINE static void run_query_on_chunk_rows_direct(
2472 Chunk* pChunk, uint16_t from, uint16_t to, Func& func, core::func_type_list<T...>) {
2473 if constexpr (sizeof...(T) > 0) {
2474 auto dataPointerTuple = std::make_tuple(chunk_view_auto<T>(pChunk)...);
2475 for (uint16_t row = from; row < to; ++row)
2476 func(std::get<decltype(chunk_view_auto<T>(pChunk))>(dataPointerTuple)[row]...);
2477 } else {
2478 for (uint16_t row = from; row < to; ++row)
2479 func();
2480 }
2481 }
2482
2483 template <typename TIter, typename Func, typename... T>
2484 GAIA_FORCEINLINE void
2485 run_query_on_direct_entity(TIter& it, Func func, [[maybe_unused]] core::func_type_list<T...> types) {
2486 if constexpr (sizeof...(T) > 0) {
2487 auto dataPointerTuple = std::make_tuple(it.template view_auto_any<T>()...);
2488 func(std::get<decltype(it.template view_auto_any<T>())>(dataPointerTuple)[it.template acc_index<T>(0)]...);
2489 } else {
2490 func();
2491 }
2492
2493 finish_typed_iter_writes<TIter, T...>(it, std::index_sequence_for<T...>{});
2494 it.clear_touched_writes();
2495 }
2496
2497 template <typename TIter, typename Func, typename... T>
2498 GAIA_FORCEINLINE void
2499 run_query_on_direct_entity_direct(TIter& it, Func func, [[maybe_unused]] core::func_type_list<T...> types) {
2500 run_query_on_chunk_rows_direct(const_cast<Chunk*>(it.chunk()), it.row_begin(), it.row_end(), func, types);
2501 finish_typed_iter_writes<TIter, T...>(it, std::index_sequence_for<T...>{});
2502 it.clear_touched_writes();
2503 }
2504
2505 //------------------------------------------------
2506
2507 template <QueryExecType ExecType, typename Func, typename... T>
2508 void each_inter(QueryInfo& queryInfo, Func func, core::func_type_list<T...>) {
2509 if (!queryInfo.has_filters() && can_use_direct_entity_seed_eval(queryInfo)) {
2510 GAIA_PROF_SCOPE(query_func);
2511 each_direct_inter<Iter>(queryInfo, func, core::func_type_list<T...>{});
2512 return;
2513 }
2514
2515 auto& world = *const_cast<World*>(queryInfo.world());
2516 if (can_use_direct_chunk_term_eval<T...>(world, queryInfo)) {
2517 if constexpr (ExecType == QueryExecType::Default) {
2518 if (can_use_direct_chunk_iteration_fastpath(queryInfo)) {
2519 run_query_on_chunks_direct(queryInfo, func, core::func_type_list<T...>{});
2520 return;
2521 }
2522 }
2523 run_query_on_chunks<ExecType, Iter>(queryInfo, [&](Iter& it) {
2524 GAIA_PROF_SCOPE(query_func);
2525 run_query_on_chunk_direct(it, func, core::func_type_list<T...>{});
2526 });
2527 } else {
2528 run_query_on_chunks<ExecType, Iter>(queryInfo, [&](Iter& it) {
2529 GAIA_PROF_SCOPE(query_func);
2530 run_query_on_chunk(queryInfo, it, func, core::func_type_list<T...>{});
2531 });
2532 }
2533 }
2534
2535 template <QueryExecType ExecType, typename Func>
2536 void each_inter(QueryInfo& queryInfo, Func func) {
2537 using InputArgs = decltype(core::func_args(&Func::operator()));
2538
2539#if GAIA_ASSERT_ENABLED
2540 // Make sure we only use components specified in the query.
2541 // Constness is respected. Therefore, if a type is const when registered to query,
2542 // it has to be const (or immutable) also in each().
2543 // in query.
2544 // Example 1:
2545 // auto q = w.query().all<MyType>(); // immutable access requested
2546 // q.each([](MyType val)) {}); // okay
2547 // q.each([](const MyType& val)) {}); // okay
2548 // q.each([](MyType& val)) {}); // error
2549 // Example 2:
2550 // auto q = w.query().all<MyType&>(); // mutable access requested
2551 // q.each([](MyType val)) {}); // error
2552 // q.each([](const MyType& val)) {}); // error
2553 // q.each([](MyType& val)) {}); // okay
2554 GAIA_ASSERT(unpack_args_into_query_has_all(queryInfo, InputArgs{}));
2555#endif
2556
2557 each_inter<ExecType>(queryInfo, func, InputArgs{});
2558 }
2559
2560 template <QueryExecType ExecType, typename Func>
2561 void each_inter(Func func) {
2562 auto& queryInfo = fetch();
2563 match_all(queryInfo);
2564
2565 if constexpr (std::is_invocable_v<Func, IterAll&>) {
2566 if (!queryInfo.has_filters() && can_use_direct_entity_seed_eval(queryInfo)) {
2567 GAIA_PROF_SCOPE(query_func);
2568 each_direct_iter_inter<IterAll>(queryInfo, func);
2569 return;
2570 }
2571 run_query_on_chunks<ExecType, IterAll>(queryInfo, [&](IterAll& it) {
2572 GAIA_PROF_SCOPE(query_func);
2573 func(it);
2574 });
2575 } else if constexpr (std::is_invocable_v<Func, Iter&>) {
2576 if (!queryInfo.has_filters() && can_use_direct_entity_seed_eval(queryInfo)) {
2577 GAIA_PROF_SCOPE(query_func);
2578 each_direct_iter_inter<Iter>(queryInfo, func);
2579 return;
2580 }
2581 run_query_on_chunks<ExecType, Iter>(queryInfo, [&](Iter& it) {
2582 GAIA_PROF_SCOPE(query_func);
2583 func(it);
2584 });
2585 } else if constexpr (std::is_invocable_v<Func, IterDisabled&>) {
2586 if (!queryInfo.has_filters() && can_use_direct_entity_seed_eval(queryInfo)) {
2587 GAIA_PROF_SCOPE(query_func);
2588 each_direct_iter_inter<IterDisabled>(queryInfo, func);
2589 return;
2590 }
2591 run_query_on_chunks<ExecType, IterDisabled>(queryInfo, [&](IterDisabled& it) {
2592 GAIA_PROF_SCOPE(query_func);
2593 func(it);
2594 });
2595 } else
2596 each_inter<ExecType>(queryInfo, func);
2597 }
2598
2599 //------------------------------------------------
2600
2605 GAIA_NODISCARD static bool is_adjunct_direct_term(const World& world, const QueryTerm& term) {
2606 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
2607 return false;
2608
2609 const auto id = term.id;
2610 return (id.pair() && world_is_exclusive_dont_fragment_relation(world, entity_from_id(world, id.id()))) ||
2611 (!id.pair() && world_is_non_fragmenting_out_of_line_component(world, id));
2612 }
2613
2617 GAIA_NODISCARD static bool uses_semantic_is_matching(const QueryTerm& term) {
2618 const auto id = term.id;
2619 return term.matchKind == QueryMatchKind::Semantic && term.src == EntityBad && term.entTrav == EntityBad &&
2620 !term_has_variables(term) && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
2621 !is_variable((EntityId)id.gen());
2622 }
2623
2627 GAIA_NODISCARD static bool uses_in_is_matching(const QueryTerm& term) {
2628 const auto id = term.id;
2629 return term.matchKind == QueryMatchKind::In && term.src == EntityBad && term.entTrav == EntityBad &&
2630 !term_has_variables(term) && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
2631 !is_variable((EntityId)id.gen());
2632 }
2633
2637 GAIA_NODISCARD static bool uses_non_direct_is_matching(const QueryTerm& term) {
2638 return uses_semantic_is_matching(term) || uses_in_is_matching(term);
2639 }
2640
2645 GAIA_NODISCARD static bool uses_inherited_id_matching(const World& world, const QueryTerm& term) {
2646 const auto id = term.id;
2647 return term.matchKind == QueryMatchKind::Semantic && term.src == EntityBad && term.entTrav == EntityBad &&
2648 !term_has_variables(term) && !is_wildcard(id) && !is_variable((EntityId)id.id()) &&
2649 (!id.pair() || !is_variable((EntityId)id.gen())) && world_term_uses_inherit_policy(world, id);
2650 }
2651
2653 GAIA_NODISCARD static bool match_entity_term(const World& world, Entity entity, const QueryTerm& term) {
2654 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
2655 return world_has_entity_term(world, entity, term.id);
2656 if (uses_in_is_matching(term))
2657 return world_has_entity_term_in(world, entity, term.id);
2658
2659 return world_has_entity_term_direct(world, entity, term.id);
2660 }
2661
2663 GAIA_NODISCARD static bool match_single_direct_target_term(
2664 const World& world, Entity entity, Entity termId, QueryCtx::DirectTargetEvalKind kind) {
2665 switch (kind) {
2666 case QueryCtx::DirectTargetEvalKind::SingleAllSemanticIs:
2667 case QueryCtx::DirectTargetEvalKind::SingleAllInherited:
2668 return world_has_entity_term(world, entity, termId);
2669 case QueryCtx::DirectTargetEvalKind::SingleAllInIs:
2670 return world_has_entity_term_in(world, entity, termId);
2671 case QueryCtx::DirectTargetEvalKind::SingleAllDirect:
2672 return world_has_entity_term_direct(world, entity, termId);
2673 case QueryCtx::DirectTargetEvalKind::Generic:
2674 break;
2675 }
2676
2677 return false;
2678 }
2679
2680 GAIA_NODISCARD static uint32_t count_direct_term_entities(const World& world, const QueryTerm& term) {
2681 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
2682 return world_count_direct_term_entities(world, term.id);
2683 if (uses_in_is_matching(term))
2684 return world_count_in_term_entities(world, term.id);
2685
2686 return world_count_direct_term_entities_direct(world, term.id);
2687 }
2688
2689 static void collect_direct_term_entities(const World& world, const QueryTerm& term, cnt::darray<Entity>& out) {
2690 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term)) {
2691 world_collect_direct_term_entities(world, term.id, out);
2692 return;
2693 }
2694 if (uses_in_is_matching(term)) {
2695 world_collect_in_term_entities(world, term.id, out);
2696 return;
2697 }
2698
2699 world_collect_direct_term_entities_direct(world, term.id, out);
2700 }
2701
2702 template <typename Func>
2703 GAIA_NODISCARD static bool for_each_direct_term_entity(const World& world, const QueryTerm& term, Func&& func) {
2704 struct Visitor {
2705 Func& func;
2706 static bool thunk(void* ctx, Entity entity) {
2707 return static_cast<Visitor*>(ctx)->func(entity);
2708 }
2709 };
2710
2711 Visitor visitor{func};
2712 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
2713 return world_for_each_direct_term_entity(world, term.id, &visitor, &Visitor::thunk);
2714 if (uses_in_is_matching(term))
2715 return world_for_each_in_term_entity(world, term.id, &visitor, &Visitor::thunk);
2716
2717 return world_for_each_direct_term_entity_direct(world, term.id, &visitor, &Visitor::thunk);
2718 }
2719
2721 GAIA_NODISCARD static bool can_use_direct_entity_seed_eval(const QueryInfo& queryInfo) {
2722 const auto& ctxData = queryInfo.ctx().data;
2723 if (ctxData.sortByFunc != nullptr || ctxData.groupBy != EntityBad)
2724 return false;
2725
2726 const auto& world = *queryInfo.world();
2727 bool hasPositiveTerm = false;
2728 bool hasSeedableTerm = false;
2729 for (const auto& term: ctxData.terms_view()) {
2730 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
2731 return false;
2732
2733 if (term.op == QueryOpKind::Any || term.op == QueryOpKind::Count)
2734 return false;
2735
2736 if (term.op == QueryOpKind::All || term.op == QueryOpKind::Or) {
2737 hasPositiveTerm = true;
2738 if (uses_non_direct_is_matching(term) || uses_inherited_id_matching(world, term) ||
2739 uses_in_is_matching(term) || is_adjunct_direct_term(world, term)) {
2740 hasSeedableTerm = true;
2741 }
2742 }
2743 }
2744
2745 return hasPositiveTerm && hasSeedableTerm;
2746 }
2747
2749 GAIA_NODISCARD static bool can_use_direct_target_eval(const QueryInfo& queryInfo) {
2750 bool hasPositiveTerm = false;
2751 for (const auto& term: queryInfo.ctx().data.terms_view()) {
2752 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
2753 return false;
2754
2755 if (term.op == QueryOpKind::Any || term.op == QueryOpKind::Count)
2756 return false;
2757
2758 if (term.op == QueryOpKind::All || term.op == QueryOpKind::Or)
2759 hasPositiveTerm = true;
2760 }
2761
2762 return hasPositiveTerm;
2763 }
2764
2766 GAIA_NODISCARD static bool has_only_direct_or_terms(const QueryInfo& queryInfo) {
2767 bool hasOr = false;
2768 for (const auto& term: queryInfo.ctx().data.terms_view()) {
2769 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
2770 return false;
2771 if (term.op == QueryOpKind::Or) {
2772 hasOr = true;
2773 continue;
2774 }
2775 if (term.op != QueryOpKind::Not)
2776 return false;
2777 }
2778
2779 return hasOr;
2780 }
2781
2782 template <typename TIter>
2783 GAIA_NODISCARD static constexpr Constraints direct_seed_constraints() {
2784 if constexpr (std::is_same_v<TIter, Iter>)
2785 return Constraints::EnabledOnly;
2786 else if constexpr (std::is_same_v<TIter, IterDisabled>)
2787 return Constraints::DisabledOnly;
2788 else
2789 return Constraints::AcceptAll;
2790 }
2791
2792 static void
2793 append_chunk_run(cnt::darray<detail::BfsChunkRun>& runs, const EntityContainer& ec, uint32_t entityOffset) {
2794 if (runs.empty()) {
2795 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
2796 return;
2797 }
2798
2799 auto& run = runs.back();
2800 if (ec.pChunk == run.pChunk && ec.row == run.to) {
2801 run.to = (uint16_t)(run.to + 1);
2802 return;
2803 }
2804
2805 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
2806 }
2807
2809 Entity seededAllTerm = EntityBad;
2810 QueryMatchKind seededAllMatchKind = QueryMatchKind::Semantic;
2811 bool seededFromAll = false;
2812 bool seededFromOr = false;
2813 };
2814
2817 Entity bestAllTerm = EntityBad;
2818 uint32_t bestAllTermCount = UINT32_MAX;
2819 QueryMatchKind bestAllTermMatchKind = QueryMatchKind::Semantic;
2820 bool hasAllTerms = false;
2821 bool hasOrTerms = false;
2822 bool preferOrSeed = false;
2823 };
2824
2828 const QueryTerm* pSingleAllTerm = nullptr;
2830 bool alwaysMatch = false;
2831 };
2832
2834 GAIA_NODISCARD static bool should_prefer_direct_seed_term(
2835 const World& world, const QueryTerm& candidate, uint32_t candidateCount, const DirectEntitySeedPlan& plan) {
2836 const bool candidateIsSemanticIs = uses_non_direct_is_matching(candidate);
2837 const bool bestIsSemanticIs = plan.bestAllTermMatchKind != QueryMatchKind::Direct &&
2838 plan.bestAllTerm.pair() && plan.bestAllTerm.id() == Is.id() &&
2839 !is_wildcard(plan.bestAllTerm.gen()) &&
2840 !is_variable((EntityId)plan.bestAllTerm.gen());
2841 const auto adjustedCandidateCount = candidateCount - (candidateIsSemanticIs && candidateCount > 0 ? 1U : 0U);
2842 const auto adjustedBestCount =
2843 plan.bestAllTermCount - (bestIsSemanticIs && plan.bestAllTermCount > 0 ? 1U : 0U);
2844 if (adjustedCandidateCount < adjustedBestCount)
2845 return true;
2846 if (adjustedCandidateCount > adjustedBestCount)
2847 return false;
2848 if (plan.bestAllTerm == EntityBad)
2849 return true;
2850
2851 if (candidateIsSemanticIs != bestIsSemanticIs)
2852 return candidateIsSemanticIs;
2853
2854 const bool candidateUsesInherited = uses_inherited_id_matching(world, candidate);
2855 const bool bestUsesInherited = plan.bestAllTermMatchKind == QueryMatchKind::Semantic &&
2856 !is_wildcard(plan.bestAllTerm) &&
2857 !is_variable((EntityId)plan.bestAllTerm.id()) &&
2858 (!plan.bestAllTerm.pair() || !is_variable((EntityId)plan.bestAllTerm.gen())) &&
2859 world_term_uses_inherit_policy(world, plan.bestAllTerm);
2860 if (candidateUsesInherited != bestUsesInherited)
2861 return !candidateUsesInherited;
2862
2863 return false;
2864 }
2865
2866 GAIA_NODISCARD static DirectEntitySeedPlan
2867 direct_entity_seed_plan(const World& world, const QueryInfo& queryInfo) {
2868 DirectEntitySeedPlan plan;
2869 uint32_t totalOrTermCount = 0;
2870
2871 for (const auto& term: queryInfo.ctx().data.terms_view()) {
2872 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
2873 continue;
2874 if (term.op == QueryOpKind::All) {
2875 plan.hasAllTerms = true;
2876 const auto cnt = count_direct_term_entities(world, term);
2877 if (should_prefer_direct_seed_term(world, term, cnt, plan)) {
2878 plan.bestAllTermCount = cnt;
2879 plan.bestAllTerm = term.id;
2880 plan.bestAllTermMatchKind = term.matchKind;
2881 }
2882 } else if (term.op == QueryOpKind::Or) {
2883 plan.hasOrTerms = true;
2884 totalOrTermCount += count_direct_term_entities(world, term);
2885 }
2886 }
2887
2888 plan.preferOrSeed = plan.hasOrTerms && (!plan.hasAllTerms || totalOrTermCount < plan.bestAllTermCount);
2889 return plan;
2890 }
2891
2893 GAIA_NODISCARD static bool match_direct_entity_terms(
2894 const World& world, Entity entity, const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
2895 bool hasOrTerms = false;
2896 bool anyOrMatched = false;
2897
2898 for (const auto& term: queryInfo.ctx().data.terms_view()) {
2899 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
2900 continue;
2901 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
2902 term.matchKind == seedInfo.seededAllMatchKind)
2903 continue;
2904 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
2905 continue;
2906
2907 const bool present = match_entity_term(world, entity, term);
2908 switch (term.op) {
2909 case QueryOpKind::All:
2910 if (!present)
2911 return false;
2912 break;
2913 case QueryOpKind::Or:
2914 hasOrTerms = true;
2915 anyOrMatched |= present;
2916 break;
2917 case QueryOpKind::Not:
2918 if (present)
2919 return false;
2920 break;
2921 case QueryOpKind::Any:
2922 case QueryOpKind::Count:
2923 break;
2924 }
2925 }
2926
2927 return !hasOrTerms || anyOrMatched;
2928 }
2929
2930 GAIA_NODISCARD static const QueryTerm*
2931 find_direct_all_seed_term(const QueryInfo& queryInfo, const DirectEntitySeedPlan& plan) {
2932 for (const auto& term: queryInfo.ctx().data.terms_view()) {
2933 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
2934 continue;
2935 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
2936 term.matchKind != plan.bestAllTermMatchKind)
2937 continue;
2938 return &term;
2939 }
2940
2941 return nullptr;
2942 }
2943
2944 GAIA_NODISCARD static DirectEntitySeedEvalPlan
2945 direct_all_seed_eval_plan(const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
2946 DirectEntitySeedEvalPlan plan{};
2947
2948 for (const auto& term: queryInfo.ctx().data.terms_view()) {
2949 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
2950 return {};
2951 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
2952 term.matchKind == seedInfo.seededAllMatchKind)
2953 continue;
2954
2955 if (term.op == QueryOpKind::All) {
2956 if (plan.pSingleAllTerm != nullptr)
2957 return {};
2958 plan.pSingleAllTerm = &term;
2959 continue;
2960 }
2961
2962 return {};
2963 }
2964
2965 plan.alwaysMatch = plan.pSingleAllTerm == nullptr;
2966 return plan;
2967 }
2968
2974 GAIA_NODISCARD static bool
2975 can_use_direct_seed_run_cache(const World& world, const QueryInfo& queryInfo, const QueryTerm& seedTerm) {
2976 if (!(uses_non_direct_is_matching(seedTerm) || uses_inherited_id_matching(world, seedTerm)))
2977 return false;
2978
2979 for (const auto& term: queryInfo.ctx().data.terms_view()) {
2980 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
2981 return false;
2982 if (term.op == QueryOpKind::Any || term.op == QueryOpKind::Count || term.op == QueryOpKind::Or)
2983 return false;
2984 if (term.op == QueryOpKind::All && term.id == seedTerm.id && term.matchKind == seedTerm.matchKind)
2985 continue;
2986 if (is_adjunct_direct_term(world, term))
2987 return false;
2988 }
2989
2990 return true;
2991 }
2992
2993 template <typename TIter>
2995 cached_direct_seed_runs(QueryInfo& queryInfo, const QueryTerm& seedTerm, const DirectEntitySeedInfo& seedInfo) {
2996 auto& runData = ensure_direct_seed_run_data();
2997 auto& world = *queryInfo.world();
2998 const auto constraints = direct_seed_constraints<TIter>();
2999 const auto relVersion = world_rel_version(world, Is);
3000 const auto worldVersion = ::gaia::ecs::world_version(world);
3001
3002 if (runData.cacheValid && runData.cachedSeedTerm == seedTerm.id &&
3003 runData.cachedSeedMatchKind == seedTerm.matchKind && runData.cachedConstraints == constraints &&
3004 runData.cachedRelVersion == relVersion && runData.cachedWorldVersion == worldVersion) {
3005 return {runData.cachedRuns.data(), runData.cachedRuns.size()};
3006 }
3007
3008 auto& runs = runData.cachedRuns;
3009 auto& entities = runData.cachedEntities;
3010 auto& chunkOrderedEntities = runData.cachedChunkOrderedEntities;
3011 runs.clear();
3012 entities.clear();
3013 chunkOrderedEntities.clear();
3014
3015 (void)for_each_direct_term_entity(world, seedTerm, [&](Entity entity) {
3016 if (!match_direct_entity_constraints<TIter>(world, queryInfo, entity))
3017 return true;
3018
3019 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
3020 return true;
3021
3022 entities.push_back(entity);
3023 return true;
3024 });
3025
3026 chunkOrderedEntities = entities;
3027 core::sort(chunkOrderedEntities, [&](Entity left, Entity right) {
3028 const auto& ecLeft = ::gaia::ecs::fetch(world, left);
3029 const auto& ecRight = ::gaia::ecs::fetch(world, right);
3030 if (ecLeft.pArchetype != ecRight.pArchetype)
3031 return ecLeft.pArchetype->id() < ecRight.pArchetype->id();
3032 if (ecLeft.pChunk != ecRight.pChunk)
3033 return ecLeft.pChunk < ecRight.pChunk;
3034 return ecLeft.row < ecRight.row;
3035 });
3036
3037 uint32_t entityOffset = 0;
3038 for (const auto entity: chunkOrderedEntities) {
3039 const auto& ec = ::gaia::ecs::fetch(world, entity);
3040 append_chunk_run(runs, ec, entityOffset++);
3041 }
3042
3043 runData.cachedSeedTerm = seedTerm.id;
3044 runData.cachedSeedMatchKind = seedTerm.matchKind;
3045 runData.cachedConstraints = constraints;
3046 runData.cachedRelVersion = relVersion;
3047 runData.cachedWorldVersion = worldVersion;
3048 runData.cacheValid = true;
3049 return {runs.data(), runs.size()};
3050 }
3051
3052 template <typename TIter>
3053 GAIA_NODISCARD std::span<const Entity> cached_direct_seed_entities(
3054 QueryInfo& queryInfo, const QueryTerm& seedTerm, const DirectEntitySeedInfo& seedInfo) {
3055 (void)cached_direct_seed_runs<TIter>(queryInfo, seedTerm, seedInfo);
3056 auto& runData = ensure_direct_seed_run_data();
3057 return {runData.cachedEntities.data(), runData.cachedEntities.size()};
3058 }
3059
3060 template <typename TIter>
3061 GAIA_NODISCARD std::span<const Entity> cached_direct_seed_chunk_entities(
3062 QueryInfo& queryInfo, const QueryTerm& seedTerm, const DirectEntitySeedInfo& seedInfo) {
3063 (void)cached_direct_seed_runs<TIter>(queryInfo, seedTerm, seedInfo);
3064 auto& runData = ensure_direct_seed_run_data();
3065 return {runData.cachedChunkOrderedEntities.data(), runData.cachedChunkOrderedEntities.size()};
3066 }
3067
3068 template <typename TIter, typename Func>
3069 GAIA_NODISCARD static bool for_each_direct_all_seed(
3070 const World& world, const QueryInfo& queryInfo, const DirectEntitySeedPlan& plan, Func&& func) {
3071 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
3072 GAIA_ASSERT(pSeedTerm != nullptr);
3073 if (pSeedTerm == nullptr)
3074 return true;
3075
3076 DirectEntitySeedInfo seedInfo{};
3077 seedInfo.seededAllTerm = pSeedTerm->id;
3078 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
3079 seedInfo.seededFromAll = true;
3080 const auto evalPlan = direct_all_seed_eval_plan(queryInfo, seedInfo);
3081 const Archetype* pLastSingleAllArchetype = nullptr;
3082 bool lastSingleAllMatch = false;
3083 bool seedImpliesSingleAllTerm = false;
3084 if (evalPlan.pSingleAllTerm != nullptr && uses_non_direct_is_matching(*pSeedTerm) &&
3085 (uses_non_direct_is_matching(*evalPlan.pSingleAllTerm) ||
3086 uses_inherited_id_matching(world, *evalPlan.pSingleAllTerm))) {
3087 const auto seedTarget = entity_from_id(world, (EntityId)pSeedTerm->id.gen());
3088 if (seedTarget != EntityBad)
3089 seedImpliesSingleAllTerm = match_entity_term(world, seedTarget, *evalPlan.pSingleAllTerm);
3090 }
3091
3092 // Stream the chosen ALL seed term directly. This avoids materializing a temporary
3093 // entity array for the common `all<T>().is(base)` shape.
3094 return for_each_direct_term_entity(world, *pSeedTerm, [&](Entity entity) {
3095 if (!match_direct_entity_constraints<TIter>(world, queryInfo, entity))
3096 return true;
3097
3098 if (evalPlan.alwaysMatch)
3099 return func(entity);
3100 if (evalPlan.pSingleAllTerm != nullptr) {
3101 if (seedImpliesSingleAllTerm)
3102 return func(entity);
3103 if (uses_non_direct_is_matching(*evalPlan.pSingleAllTerm) ||
3104 uses_inherited_id_matching(world, *evalPlan.pSingleAllTerm)) {
3105 const auto* pArchetype = world_entity_archetype(world, entity);
3106 if (pArchetype != pLastSingleAllArchetype) {
3107 lastSingleAllMatch = match_entity_term(world, entity, *evalPlan.pSingleAllTerm);
3108 pLastSingleAllArchetype = pArchetype;
3109 }
3110 if (!lastSingleAllMatch)
3111 return true;
3112 } else if (!match_entity_term(world, entity, *evalPlan.pSingleAllTerm)) {
3113 return true;
3114 }
3115 return func(entity);
3116 }
3117 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
3118 return true;
3119
3120 return func(entity);
3121 });
3122 }
3123
3125 template <typename TIter>
3126 GAIA_NODISCARD static bool
3127 match_direct_entity_constraints(const World& world, const QueryInfo& queryInfo, Entity entity) {
3128 if (!queryInfo.matches_prefab_entities() && world_entity_prefab(world, entity))
3129 return false;
3130
3131 if constexpr (std::is_same_v<TIter, Iter>)
3132 return world_entity_enabled(world, entity);
3133 else if constexpr (std::is_same_v<TIter, IterDisabled>)
3134 return !world_entity_enabled(world, entity);
3135 else
3136 return true;
3137 }
3138
3140 GAIA_NODISCARD static bool can_use_archetype_bucket_count(
3141 const World& world, const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
3142 if (!seedInfo.seededFromAll && !seedInfo.seededFromOr)
3143 return false;
3144
3145 for (const auto& term: queryInfo.ctx().data.terms_view()) {
3146 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
3147 return false;
3148 if (seedInfo.seededFromAll && term.id == seedInfo.seededAllTerm && term.op == QueryOpKind::All)
3149 continue;
3150 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
3151 continue;
3152 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Not)
3153 return false;
3154 if (is_adjunct_direct_term(world, term))
3155 return false;
3156 if (uses_inherited_id_matching(world, term))
3157 return false;
3158 }
3159
3160 return true;
3161 }
3162
3164 template <typename TIter>
3165 GAIA_NODISCARD static uint32_t count_direct_entity_seed_by_archetype(
3166 const World& world, const QueryInfo& queryInfo, const cnt::darray<Entity>& seedEntities,
3167 const DirectEntitySeedInfo& seedInfo) {
3168 auto& scratch = direct_query_scratch();
3169
3170 scratch.archetypes.clear();
3171 scratch.bucketEntities.clear();
3172 scratch.counts.clear();
3173
3174 for (const auto entity: seedEntities) {
3175 if (!match_direct_entity_constraints<TIter>(world, queryInfo, entity))
3176 continue;
3177
3178 const auto* pArchetype = world_entity_archetype(world, entity);
3179 const auto idx = core::get_index(scratch.archetypes, pArchetype);
3180 if (idx == BadIndex) {
3181 scratch.archetypes.push_back(pArchetype);
3182 scratch.bucketEntities.push_back(entity);
3183 scratch.counts.push_back(1);
3184 } else {
3185 ++scratch.counts[idx];
3186 }
3187 }
3188
3189 uint32_t cnt = 0;
3190 const auto archetypeCnt = (uint32_t)scratch.archetypes.size();
3191 GAIA_FOR(archetypeCnt) {
3192 if (match_direct_entity_terms(world, scratch.bucketEntities[i], queryInfo, seedInfo))
3193 cnt += scratch.counts[i];
3194 }
3195
3196 return cnt;
3197 }
3198
3200 template <typename TIter>
3201 GAIA_NODISCARD static uint32_t count_direct_or_union(const World& world, const QueryInfo& queryInfo) {
3202 auto& scratch = direct_query_scratch();
3203 const auto seenVersion = next_direct_query_seen_version(scratch);
3204 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
3205
3206 uint32_t cnt = 0;
3207 for (const auto& term: queryInfo.ctx().data.terms_view()) {
3208 if (term.op != QueryOpKind::Or)
3209 continue;
3210
3211 (void)for_each_direct_term_entity(world, term, [&](Entity entity) {
3212 if (!match_direct_entity_constraints<TIter>(world, queryInfo, entity))
3213 return true;
3214
3215 const auto entityId = (uint32_t)entity.id();
3216 ensure_direct_query_count_capacity(scratch, entityId);
3217
3218 if (scratch.counts[entityId] == seenVersion)
3219 return true;
3220 scratch.counts[entityId] = seenVersion;
3221
3222 bool rejected = false;
3223 if (hasDirectNotTerms) {
3224 for (const auto& notTerm: queryInfo.ctx().data.terms_view()) {
3225 if (notTerm.op != QueryOpKind::Not)
3226 continue;
3227 if (match_entity_term(world, entity, notTerm)) {
3228 rejected = true;
3229 break;
3230 }
3231 }
3232 }
3233
3234 if (!rejected)
3235 ++cnt;
3236 return true;
3237 });
3238 }
3239
3240 return cnt;
3241 }
3242
3248 template <typename TIter>
3249 GAIA_NODISCARD static bool is_empty_direct_or_union(const World& world, const QueryInfo& queryInfo) {
3250 auto& scratch = direct_query_scratch();
3251 const auto seenVersion = next_direct_query_seen_version(scratch);
3252 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
3253
3254 for (const auto& term: queryInfo.ctx().data.terms_view()) {
3255 if (term.op != QueryOpKind::Or)
3256 continue;
3257
3258 const bool completed = for_each_direct_term_entity(world, term, [&](Entity entity) {
3259 if (!match_direct_entity_constraints<TIter>(world, queryInfo, entity))
3260 return true;
3261
3262 const auto entityId = (uint32_t)entity.id();
3263 ensure_direct_query_count_capacity(scratch, entityId);
3264
3265 if (scratch.counts[entityId] == seenVersion)
3266 return true;
3267 scratch.counts[entityId] = seenVersion;
3268
3269 bool rejected = false;
3270 if (hasDirectNotTerms) {
3271 for (const auto& notTerm: queryInfo.ctx().data.terms_view()) {
3272 if (notTerm.op != QueryOpKind::Not)
3273 continue;
3274 if (match_entity_term(world, entity, notTerm)) {
3275 rejected = true;
3276 break;
3277 }
3278 }
3279 }
3280
3281 if (!rejected)
3282 return false;
3283 return true;
3284 });
3285
3286 if (!completed)
3287 return true;
3288 }
3289
3290 return false;
3291 }
3292
3294 static DirectEntitySeedInfo
3295 build_direct_entity_seed(const World& world, const QueryInfo& queryInfo, cnt::darray<Entity>& out) {
3296 auto& scratch = direct_query_scratch();
3297 out.clear();
3298 DirectEntitySeedInfo seedInfo{};
3299 const auto plan = direct_entity_seed_plan(world, queryInfo);
3300
3301 if (plan.hasAllTerms && !plan.preferOrSeed) {
3302 if (plan.bestAllTerm != EntityBad) {
3303 for (const auto& term: queryInfo.ctx().data.terms_view()) {
3304 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
3305 continue;
3306 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
3307 term.matchKind != plan.bestAllTermMatchKind)
3308 continue;
3309 collect_direct_term_entities(world, term, out);
3310 seedInfo.seededAllMatchKind = term.matchKind;
3311 break;
3312 }
3313 seedInfo.seededFromAll = true;
3314 seedInfo.seededAllTerm = plan.bestAllTerm;
3315 }
3316 return seedInfo;
3317 }
3318
3319 const auto seenVersion = next_direct_query_seen_version(scratch);
3320
3321 for (const auto& term: queryInfo.ctx().data.terms_view()) {
3322 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
3323 continue;
3324 if (term.op != QueryOpKind::Or)
3325 continue;
3326
3327 scratch.termEntities.clear();
3328 collect_direct_term_entities(world, term, scratch.termEntities);
3329 for (const auto entity: scratch.termEntities) {
3330 const auto entityId = (uint32_t)entity.id();
3331 ensure_direct_query_count_capacity(scratch, entityId);
3332
3333 if (scratch.counts[entityId] == seenVersion)
3334 continue;
3335 scratch.counts[entityId] = seenVersion;
3336 out.push_back(entity);
3337 }
3338 }
3339
3340 seedInfo.seededFromOr = true;
3341 return seedInfo;
3342 }
3343
3347 GAIA_NODISCARD static bool has_direct_not_terms(const QueryInfo& queryInfo) {
3348 for (const auto& term: queryInfo.ctx().data.terms_view()) {
3349 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
3350 continue;
3351 if (term.op == QueryOpKind::Not)
3352 return true;
3353 }
3354
3355 return false;
3356 }
3357
3364 template <typename TIter, typename Func>
3365 void for_each_direct_or_union(World& world, const QueryInfo& queryInfo, Func&& func) {
3366 auto& scratch = direct_query_scratch();
3367 const auto seenVersion = next_direct_query_seen_version(scratch);
3368 DirectEntitySeedInfo seedInfo{};
3369 seedInfo.seededFromOr = true;
3370
3371 for (const auto& term: queryInfo.ctx().data.terms_view()) {
3372 if (term.op != QueryOpKind::Or)
3373 continue;
3374
3375 (void)for_each_direct_term_entity(world, term, [&](Entity entity) {
3376 if (!match_direct_entity_constraints<TIter>(world, queryInfo, entity))
3377 return true;
3378
3379 const auto entityId = (uint32_t)entity.id();
3380 ensure_direct_query_count_capacity(scratch, entityId);
3381
3382 if (scratch.counts[entityId] == seenVersion)
3383 return true;
3384 scratch.counts[entityId] = seenVersion;
3385
3386 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
3387 return true;
3388
3389 func(entity);
3390 return true;
3391 });
3392 }
3393 }
3394
3396 template <bool UseFilters, typename TIter>
3397 GAIA_NODISCARD bool empty_inter(const QueryInfo& queryInfo) const {
3398 const bool hasRuntimeGroupFilter = queryInfo.ctx().data.groupBy != EntityBad && m_groupIdSet != 0;
3399
3400 if constexpr (!UseFilters) {
3401 if (!hasRuntimeGroupFilter && can_use_direct_entity_seed_eval(queryInfo)) {
3402 if (has_only_direct_or_terms(queryInfo))
3403 return is_empty_direct_or_union<TIter>(*queryInfo.world(), queryInfo);
3404
3405 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
3406 bool empty = true;
3407 (void)for_each_direct_all_seed<TIter>(*queryInfo.world(), queryInfo, plan, [&](Entity) {
3408 empty = false;
3409 return false;
3410 });
3411 return empty;
3412 }
3413 }
3414
3415 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
3416 const auto cacheView = queryInfo.cache_archetype_view();
3417 const bool needsBarrierCache = has_depth_order_hierarchy_enabled_barrier(queryInfo);
3418 if (needsBarrierCache)
3419 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3420 uint32_t idxFrom = 0;
3421 uint32_t idxTo = (uint32_t)cacheView.size();
3422 if (hasRuntimeGroupFilter) {
3423 const auto* pGroupData = queryInfo.selected_group_data(m_groupIdSet);
3424 if (pGroupData == nullptr)
3425 return true;
3426 idxFrom = pGroupData->idxFirst;
3427 idxTo = pGroupData->idxLast + 1;
3428 }
3429
3430 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
3431 const auto* pArchetype = cacheView[qi];
3432 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
3433 if GAIA_UNLIKELY (!can_process_archetype_inter<TIter>(queryInfo, *pArchetype, barrierPasses))
3434 continue;
3435
3436 GAIA_PROF_SCOPE(query::empty);
3437
3438 const auto& chunks = pArchetype->chunks();
3439 if (!hasEntityFilters) {
3440 for (auto* pChunk: chunks) {
3441 if (TIter::size(pChunk) == 0)
3442 continue;
3443 if constexpr (UseFilters) {
3444 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3445 continue;
3446 }
3447 return false;
3448 }
3449 continue;
3450 }
3451
3452 TIter it;
3453 it.set_world(queryInfo.world());
3454 it.set_archetype(pArchetype);
3455
3456 const bool isNotEmpty = core::has_if(chunks, [&](Chunk* pChunk) {
3457 it.set_chunk(pChunk);
3458 if constexpr (UseFilters)
3459 if (it.size() == 0 || !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3460 return false;
3461 if (!hasEntityFilters)
3462 return it.size() > 0;
3463
3464 const auto entities = it.template view<Entity>();
3465 const auto cnt = it.size();
3466 GAIA_FOR(cnt) {
3467 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
3468 return true;
3469 }
3470 return false;
3471 });
3472
3473 if (isNotEmpty)
3474 return false;
3475 }
3476
3477 return true;
3478 }
3479
3481 GAIA_NODISCARD static bool match_entity_filters(const World& world, Entity entity, const QueryInfo& queryInfo) {
3482 bool hasOrTerms = false;
3483 bool anyOrMatched = false;
3484 const bool hasEntityFilterTerms = queryInfo.has_entity_filter_terms();
3485
3486 for (const auto& term: queryInfo.ctx().data.terms_view()) {
3487 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
3488 continue;
3489
3490 const auto id = term.id;
3491 const bool isDirectIsTerm = uses_non_direct_is_matching(term);
3492 const bool isInheritedTerm = uses_inherited_id_matching(world, term);
3493 const bool isAdjunctTerm =
3494 (id.pair() && world_is_exclusive_dont_fragment_relation(world, entity_from_id(world, id.id()))) ||
3495 (!id.pair() && world_is_non_fragmenting_out_of_line_component(world, id));
3496 const bool needsEntityFilter = isAdjunctTerm || isDirectIsTerm || isInheritedTerm ||
3497 (hasEntityFilterTerms && term.op == QueryOpKind::Or);
3498 if (!needsEntityFilter)
3499 continue;
3500
3501 const bool present = match_entity_term(world, entity, term);
3502 switch (term.op) {
3503 case QueryOpKind::All:
3504 if (!present)
3505 return false;
3506 break;
3507 case QueryOpKind::Or:
3508 hasOrTerms = true;
3509 anyOrMatched |= present;
3510 break;
3511 case QueryOpKind::Not:
3512 if (present)
3513 return false;
3514 break;
3515 case QueryOpKind::Any:
3516 case QueryOpKind::Count:
3517 break;
3518 }
3519 }
3520
3521 return !hasOrTerms || anyOrMatched;
3522 }
3523
3529 GAIA_NODISCARD bool
3530 matches_target_entities(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
3531 if (targetEntities.empty())
3532 return false;
3533
3534 const auto& world = *queryInfo.world();
3535
3536 if (can_use_direct_target_eval(queryInfo)) {
3537 const auto directTargetEvalKind = queryInfo.direct_target_eval_kind();
3538 if (directTargetEvalKind != QueryCtx::DirectTargetEvalKind::Generic) {
3539 const auto termId = queryInfo.direct_target_eval_id();
3540 if (targetEntities.size() == 1) {
3541 const auto entity = targetEntities[0];
3542 if (!match_direct_entity_constraints<Iter>(world, queryInfo, entity))
3543 return false;
3544 return match_single_direct_target_term(world, entity, termId, directTargetEvalKind);
3545 }
3546
3547 for (const auto entity: targetEntities) {
3548 if (!match_direct_entity_constraints<Iter>(world, queryInfo, entity))
3549 continue;
3550 if (match_single_direct_target_term(world, entity, termId, directTargetEvalKind))
3551 return true;
3552 }
3553
3554 return false;
3555 }
3556
3557 const DirectEntitySeedInfo seedInfo{};
3558 if (targetEntities.size() == 1) {
3559 const auto entity = targetEntities[0];
3560 if (!match_direct_entity_constraints<Iter>(world, queryInfo, entity))
3561 return false;
3562 return match_direct_entity_terms(world, entity, queryInfo, seedInfo);
3563 }
3564
3565 for (const auto entity: targetEntities) {
3566 if (!match_direct_entity_constraints<Iter>(world, queryInfo, entity))
3567 continue;
3568 if (match_direct_entity_terms(world, entity, queryInfo, seedInfo))
3569 return true;
3570 }
3571
3572 return false;
3573 }
3574
3575 if (!match_one(queryInfo, archetype, targetEntities))
3576 return false;
3577
3578 if (!queryInfo.has_entity_filter_terms())
3579 return true;
3580
3581 for (const auto entity: targetEntities) {
3582 if (!match_direct_entity_constraints<Iter>(world, queryInfo, entity))
3583 continue;
3584 if (match_entity_filters(world, entity, queryInfo))
3585 return true;
3586 }
3587
3588 return false;
3589 }
3590
3592 template <bool UseFilters, typename TIter>
3593 GAIA_NODISCARD uint32_t count_inter(const QueryInfo& queryInfo) const {
3594 const bool hasRuntimeGroupFilter = queryInfo.ctx().data.groupBy != EntityBad && m_groupIdSet != 0;
3595
3596 if constexpr (!UseFilters) {
3597 if (!hasRuntimeGroupFilter && can_use_direct_entity_seed_eval(queryInfo)) {
3598 auto& scratch = direct_query_scratch();
3599 if (has_only_direct_or_terms(queryInfo))
3600 return count_direct_or_union<TIter>(*queryInfo.world(), queryInfo);
3601
3602 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
3603 const auto seedInfo = build_direct_entity_seed(*queryInfo.world(), queryInfo, scratch.entities);
3604
3605 if (can_use_archetype_bucket_count(*queryInfo.world(), queryInfo, seedInfo))
3606 return count_direct_entity_seed_by_archetype<TIter>(
3607 *queryInfo.world(), queryInfo, scratch.entities, seedInfo);
3608
3609 uint32_t cnt = 0;
3610 (void)for_each_direct_all_seed<TIter>(*queryInfo.world(), queryInfo, plan, [&](Entity) {
3611 ++cnt;
3612 return true;
3613 });
3614
3615 return cnt;
3616 }
3617 }
3618
3619 uint32_t cnt = 0;
3620 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
3621 const auto cacheView = queryInfo.cache_archetype_view();
3622 const bool needsBarrierCache = has_depth_order_hierarchy_enabled_barrier(queryInfo);
3623 if (needsBarrierCache)
3624 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3625
3626 uint32_t idxFrom = 0;
3627 uint32_t idxTo = (uint32_t)cacheView.size();
3628 if (hasRuntimeGroupFilter) {
3629 const auto* pGroupData = queryInfo.selected_group_data(m_groupIdSet);
3630 if (pGroupData == nullptr)
3631 return 0;
3632
3633 idxFrom = pGroupData->idxFirst;
3634 idxTo = pGroupData->idxLast + 1;
3635 }
3636
3637 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
3638 const auto* pArchetype = cacheView[qi];
3639 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
3640 if GAIA_UNLIKELY (!can_process_archetype_inter<TIter>(queryInfo, *pArchetype, barrierPasses))
3641 continue;
3642
3643 GAIA_PROF_SCOPE(query::count);
3644
3645 const auto& chunks = pArchetype->chunks();
3646 if (!hasEntityFilters) {
3647 for (auto* pChunk: chunks) {
3648 const auto entityCnt = TIter::size(pChunk);
3649 if (entityCnt == 0)
3650 continue;
3651
3652 if constexpr (UseFilters) {
3653 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3654 continue;
3655 }
3656
3657 cnt += entityCnt;
3658 }
3659 continue;
3660 }
3661
3662 TIter it;
3663 it.set_world(queryInfo.world());
3664 it.set_archetype(pArchetype);
3665 for (auto* pChunk: chunks) {
3666 it.set_chunk(pChunk);
3667
3668 const auto entityCnt = it.size();
3669 if (entityCnt == 0)
3670 continue;
3671
3672 // Filters
3673 if constexpr (UseFilters) {
3674 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3675 continue;
3676 }
3677
3678 if (hasEntityFilters) {
3679 const auto entities = it.template view<Entity>();
3680 GAIA_FOR(entityCnt) {
3681 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
3682 ++cnt;
3683 }
3684 continue;
3685 }
3686
3687 // Entity count
3688 cnt += entityCnt;
3689 }
3690 }
3691
3692 return cnt;
3693 }
3694
3695 template <typename TIter>
3696 static void init_direct_entity_iter(
3697 const QueryInfo& queryInfo, const World& world, const EntityContainer& ec, TIter& it, uint8_t* pIndices,
3698 Entity* pTermIds, const Archetype*& pLastArchetype) {
3699 GAIA_ASSERT(ec.pArchetype != nullptr);
3700 GAIA_ASSERT(ec.pChunk != nullptr);
3701 GAIA_ASSERT(ec.row < ec.pChunk->size());
3702
3703 if (ec.pArchetype != pLastArchetype) {
3704 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
3705 pIndices[i] = 0xFF;
3706 pTermIds[i] = EntityBad;
3707 }
3708
3709 const auto terms = queryInfo.ctx().data.terms_view();
3710 const auto queryIdCnt = (uint32_t)terms.size();
3711 auto indicesView = queryInfo.try_indices_mapping_view(ec.pArchetype);
3712 GAIA_FOR(queryIdCnt) {
3713 const auto& term = terms[i];
3714 const auto fieldIdx = term.fieldIndex;
3715 const auto queryId = term.id;
3716 pTermIds[fieldIdx] = queryId;
3717 if (!indicesView.empty()) {
3718 pIndices[fieldIdx] = indicesView[fieldIdx];
3719 continue;
3720 }
3721 if (!queryId.pair() && world_is_out_of_line_component(world, queryId)) {
3722 const auto compIdx = core::get_index_unsafe(ec.pArchetype->ids_view(), queryId);
3723 GAIA_ASSERT(compIdx != BadIndex);
3724 pIndices[fieldIdx] = 0xFF;
3725 continue;
3726 }
3727
3728 auto compIdx = world_component_index_comp_idx(world, *ec.pArchetype, queryId);
3729 if (compIdx == BadIndex)
3730 compIdx = core::get_index(ec.pArchetype->ids_view(), queryId);
3731 pIndices[fieldIdx] = (uint8_t)compIdx;
3732 }
3733
3734 it.set_archetype(ec.pArchetype);
3735 it.set_comp_indices(pIndices);
3736 const auto inheritedDataView = queryInfo.inherited_data_view(ec.pArchetype);
3737 it.set_inherited_data(inheritedDataView);
3738 it.set_term_ids(pTermIds);
3739 pLastArchetype = ec.pArchetype;
3740 }
3741
3742 it.set_chunk(ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
3743 it.set_group_id(0);
3744 }
3745
3746 template <typename TIter>
3747 static void init_direct_entity_iter(
3748 const QueryInfo& queryInfo, const World& world, Entity entity, TIter& it, uint8_t* pIndices,
3749 Entity* pTermIds) {
3750 const auto& ec = ::gaia::ecs::fetch(world, entity);
3751 const Archetype* pLastArchetype = nullptr;
3752 it.set_world(&world);
3753 init_direct_entity_iter(queryInfo, world, ec, it, pIndices, pTermIds, pLastArchetype);
3754 }
3755
3756 template <typename TIter>
3757 static void
3758 init_direct_entity_iter_basic(const EntityContainer& ec, TIter& it, const Archetype*& pLastArchetype) {
3759 GAIA_ASSERT(ec.pArchetype != nullptr);
3760 GAIA_ASSERT(ec.pChunk != nullptr);
3761 GAIA_ASSERT(ec.row < ec.pChunk->size());
3762
3763 if (ec.pArchetype != pLastArchetype) {
3764 it.set_archetype(ec.pArchetype);
3765 pLastArchetype = ec.pArchetype;
3766 }
3767 it.set_chunk(ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
3768 it.set_group_id(0);
3769 }
3770
3771 template <typename TIter, typename Func>
3772 void each_chunk_runs_iter(QueryInfo& queryInfo, std::span<const detail::BfsChunkRun> runs, Func func) {
3773 auto& world = *queryInfo.world();
3774 TIter it;
3775 it.set_world(&world);
3776 it.set_write_im(false);
3777 const Archetype* pLastArchetype = nullptr;
3778 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
3779 Entity termIds[ChunkHeader::MAX_COMPONENTS];
3780
3781 for (const auto& run: runs) {
3782 const auto& ec = ::gaia::ecs::fetch(world, run.pChunk->entity_view()[run.from]);
3783 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
3784 it.set_chunk(run.pChunk, run.from, run.to);
3785 it.set_group_id(0);
3786 func(it);
3787 finish_iter_writes(it);
3788 it.clear_touched_writes();
3789 }
3790 }
3791
3792 template <typename TIter, typename Func, typename... T>
3793 void each_chunk_runs(
3794 QueryInfo& queryInfo, std::span<const detail::BfsChunkRun> runs, Func func,
3795 [[maybe_unused]] core::func_type_list<T...>) {
3796 auto& world = *queryInfo.world();
3797 const bool canUseBasicInit = (can_use_direct_bfs_chunk_term_eval<T>(world, queryInfo) && ...);
3798 if constexpr ((can_use_raw_chunk_row_arg<T>() && ...)) {
3799 if (canUseBasicInit) {
3800 for (const auto& run: runs)
3801 run_query_on_chunk_rows_direct(run.pChunk, run.from, run.to, func, core::func_type_list<T...>{});
3802 return;
3803 }
3804 }
3805
3806 if (canUseBasicInit) {
3807 TIter it;
3808 it.set_world(&world);
3809 const Archetype* pLastArchetype = nullptr;
3810 for (const auto& run: runs) {
3811 if (run.pArchetype != pLastArchetype) {
3812 it.set_archetype(run.pArchetype);
3813 pLastArchetype = run.pArchetype;
3814 }
3815
3816 it.set_chunk(run.pChunk, run.from, run.to);
3817 it.set_group_id(0);
3818 run_query_on_chunk_direct(it, func, core::func_type_list<T...>{});
3819 }
3820 return;
3821 }
3822
3823 TIter it;
3824 it.set_world(&world);
3825 const Archetype* pLastArchetype = nullptr;
3826 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
3827 Entity termIds[ChunkHeader::MAX_COMPONENTS];
3828 for (const auto& run: runs) {
3829 const auto& ec = ::gaia::ecs::fetch(world, run.pChunk->entity_view()[run.from]);
3830 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
3831 it.set_chunk(run.pChunk, run.from, run.to);
3832 it.set_group_id(0);
3833 run_query_on_chunk_direct(it, func, core::func_type_list<T...>{});
3834 }
3835 }
3836
3837 template <typename TIter, typename Func>
3838 void each_direct_entities_iter(QueryInfo& queryInfo, std::span<const Entity> entities, Func func) {
3839 auto& world = *queryInfo.world();
3840 auto& walkData = ensure_each_walk_data();
3841 TIter it;
3842 it.set_world(&world);
3843 it.set_write_im(false);
3844 if (!walkData.cachedRuns.empty()) {
3845 each_chunk_runs_iter<TIter>(queryInfo, walkData.cachedRuns, func);
3846 return;
3847 }
3848
3849 const Archetype* pLastArchetype = nullptr;
3850 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
3851 Entity termIds[ChunkHeader::MAX_COMPONENTS];
3852 for (const auto entity: entities) {
3853 const auto& ec = ::gaia::ecs::fetch(world, entity);
3854 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
3855 func(it);
3856 finish_iter_writes(it);
3857 it.clear_touched_writes();
3858 }
3859 }
3860
3861 template <typename T>
3862 GAIA_NODISCARD static bool can_use_direct_bfs_chunk_term_eval(World& world, const QueryInfo& queryInfo) {
3863 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
3864 if constexpr (std::is_same_v<Arg, Entity>)
3865 return true;
3866 else {
3867 using FT = typename component_type_t<Arg>::TypeFull;
3868 if constexpr (is_pair<FT>::value)
3869 return false;
3870 const auto id = comp_cache(world).template get<FT>().entity;
3871 if (world_is_out_of_line_component(world, id))
3872 return false;
3873 for (const auto& term: queryInfo.ctx().data.terms_view()) {
3874 if (term.id != id)
3875 continue;
3876 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
3877 return false;
3878 if (uses_non_direct_is_matching(term) || uses_inherited_id_matching(world, term) ||
3879 is_adjunct_direct_term(world, term))
3880 return false;
3881 return true;
3882 }
3883
3884 return false;
3885 }
3886 }
3887
3888 template <typename... T>
3889 GAIA_NODISCARD static bool can_use_direct_chunk_term_eval(World& world, const QueryInfo& queryInfo) {
3890 if (queryInfo.has_entity_filter_terms())
3891 return false;
3892
3893 if constexpr (sizeof...(T) == 0)
3894 return true;
3895 else
3896 return (can_use_direct_bfs_chunk_term_eval<T>(world, queryInfo) && ...);
3897 }
3898
3899 template <typename T>
3900 GAIA_NODISCARD static constexpr bool can_use_raw_chunk_row_arg() {
3901 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
3902 if constexpr (std::is_same_v<Arg, Entity>)
3903 return true;
3904 else
3905 return !std::is_lvalue_reference_v<T> || std::is_const_v<std::remove_reference_t<T>>;
3906 }
3907
3908 template <typename TIter, typename Func, typename... T>
3909 void each_direct_entities(
3910 QueryInfo& queryInfo, std::span<const Entity> entities, Func func,
3911 [[maybe_unused]] core::func_type_list<T...>) {
3912 auto& world = *queryInfo.world();
3913 const bool canUseBasicInit = (can_use_direct_bfs_chunk_term_eval<T>(world, queryInfo) && ...);
3914 auto& walkData = ensure_each_walk_data();
3915 if (!walkData.cachedRuns.empty()) {
3916 each_chunk_runs<TIter>(queryInfo, walkData.cachedRuns, func, core::func_type_list<T...>{});
3917 return;
3918 }
3919
3920 TIter it;
3921 it.set_world(&world);
3922 const Archetype* pLastArchetype = nullptr;
3923 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
3924 Entity termIds[ChunkHeader::MAX_COMPONENTS];
3925 for (const auto entity: entities) {
3926 const auto& ec = ::gaia::ecs::fetch(world, entity);
3927 if (canUseBasicInit)
3928 init_direct_entity_iter_basic(ec, it, pLastArchetype);
3929 else
3930 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
3931
3932 if (canUseBasicInit)
3933 run_query_on_direct_entity_direct(it, func, core::func_type_list<T...>{});
3934 else
3935 run_query_on_direct_entity(it, func, core::func_type_list<T...>{});
3936 }
3937 }
3938
3939 template <typename T>
3940 static Entity inherited_query_arg_id(World& world) {
3941 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
3942 if constexpr (std::is_same_v<Arg, Entity>)
3943 return EntityBad;
3944 else {
3945 using FT = typename component_type_t<Arg>::TypeFull;
3946 if constexpr (is_pair<FT>::value) {
3947 const auto rel = comp_cache(world).template get<typename FT::rel>().entity;
3948 const auto tgt = comp_cache(world).template get<typename FT::tgt>().entity;
3949 return (Entity)Pair(rel, tgt);
3950 } else
3951 return comp_cache(world).template get<FT>().entity;
3952 }
3953 }
3954
3955 template <typename T>
3956 static decltype(auto) inherited_query_entity_arg_by_id(World& world, Entity entity, Entity termId) {
3957 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
3958 if constexpr (std::is_same_v<Arg, Entity>)
3959 return entity;
3960 else if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>)
3961 return world_query_entity_arg_by_id_raw<T>(world, entity, termId);
3962 else
3963 return world_query_entity_arg_by_id<T>(world, entity, termId);
3964 }
3965
3966 template <typename T>
3967 static decltype(auto) inherited_query_entity_arg_by_id_cached(
3968 World& world, Entity entity, Entity termId, const Archetype*& pLastArchetype, Entity& cachedOwner,
3969 bool& cachedDirect) {
3970 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
3971 if constexpr (std::is_same_v<Arg, Entity>)
3972 return entity;
3973 else if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>)
3974 return inherited_query_entity_arg_by_id<T>(world, entity, termId);
3975 else
3976 return world_query_entity_arg_by_id_cached_const<T>(
3977 world, entity, termId, pLastArchetype, cachedOwner, cachedDirect);
3978 }
3979
3980 template <typename... T, typename Func, size_t... I>
3981 static void invoke_inherited_query_args_by_id(
3982 World& world, Entity entity, const Entity* pArgIds, Func& func, std::index_sequence<I...>) {
3983 func(inherited_query_entity_arg_by_id<T>(world, entity, pArgIds[I])...);
3984 }
3985
3986 template <typename... T, typename Func, size_t... I>
3987 static void invoke_inherited_query_args_by_id_cached(
3988 World& world, Entity entity, const Entity* pArgIds, const Archetype** pLastArchetypes,
3989 Entity* pCachedOwners, bool* pCachedDirect, Func& func, std::index_sequence<I...>) {
3990 func(
3991 inherited_query_entity_arg_by_id_cached<T>(
3992 world, entity, pArgIds[I], pLastArchetypes[I], pCachedOwners[I], pCachedDirect[I])...);
3993 }
3994
3995 template <typename... T, typename Func, size_t... I>
3996 static void invoke_query_args_by_id(
3997 World& world, Entity entity, const Entity* pArgIds, Func& func, std::index_sequence<I...>) {
3998 func(world_query_entity_arg_by_id<T>(world, entity, pArgIds[I])...);
3999 }
4000
4001 template <typename... T, size_t... I>
4002 static void
4003 finish_query_args_by_id(World& world, Entity entity, const Entity* pArgIds, std::index_sequence<I...>) {
4004 Entity seenTerms[sizeof...(T) > 0 ? sizeof...(T) : 1]{};
4005 uint32_t seenCnt = 0;
4006 const auto finish_term = [&](Entity term) {
4007 GAIA_FOR(seenCnt) {
4008 if (seenTerms[i] == term)
4009 return;
4010 }
4011 seenTerms[seenCnt++] = term;
4012 world_finish_write(world, term, entity);
4013 };
4014
4015 (
4016 [&] {
4017 if constexpr (is_write_query_arg<T>())
4018 finish_term(pArgIds[I]);
4019 }(),
4020 ...);
4021 }
4022
4024 template <typename TIter, typename Func>
4025 void each_direct_iter_inter(QueryInfo& queryInfo, Func func) {
4026 auto& world = *queryInfo.world();
4027 const bool hasWriteTerms = queryInfo.ctx().data.readWriteMask != 0;
4028 const auto plan = direct_entity_seed_plan(world, queryInfo);
4029
4030 auto exec_entity = [&](Entity entity) {
4031 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
4033 TIter it;
4034 init_direct_entity_iter(queryInfo, world, entity, it, indices, termIds);
4035 it.set_write_im(false);
4036 func(it);
4037 finish_iter_writes(it);
4038 };
4039
4040 if (hasWriteTerms) {
4041 auto& scratch = direct_query_scratch();
4042 // Writable callbacks may add local overrides and reshuffle direct-term indices,
4043 // so direct-seeded execution must iterate a stable snapshot.
4044 const auto seedInfo = build_direct_entity_seed(world, queryInfo, scratch.entities);
4045 for (const auto entity: scratch.entities) {
4046 if (!match_direct_entity_constraints<TIter>(world, queryInfo, entity))
4047 continue;
4048 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4049 continue;
4050 exec_entity(entity);
4051 }
4052 return;
4053 }
4054
4055 if (!plan.preferOrSeed) {
4056 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
4057 if (pSeedTerm != nullptr && can_use_direct_seed_run_cache(world, queryInfo, *pSeedTerm)) {
4058 DirectEntitySeedInfo seedInfo{};
4059 seedInfo.seededAllTerm = pSeedTerm->id;
4060 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
4061 seedInfo.seededFromAll = true;
4062 each_chunk_runs_iter<TIter>(
4063 queryInfo, cached_direct_seed_runs<TIter>(queryInfo, *pSeedTerm, seedInfo), func);
4064 return;
4065 }
4066 }
4067
4068 if (plan.preferOrSeed) {
4069 for_each_direct_or_union<TIter>(world, queryInfo, exec_entity);
4070 return;
4071 }
4072
4073 (void)for_each_direct_all_seed<TIter>(world, queryInfo, plan, [&](Entity entity) {
4074 exec_entity(entity);
4075 return true;
4076 });
4077 }
4078
4080 template <typename TIter, typename Func, typename... T>
4081 void each_direct_inter(QueryInfo& queryInfo, Func func, [[maybe_unused]] core::func_type_list<T...>) {
4082 constexpr bool needsInheritedArgIds =
4083 (!std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, Entity> || ... || false);
4084
4085 auto& world = *queryInfo.world();
4086 const auto plan = direct_entity_seed_plan(world, queryInfo);
4087 const bool hasWriteTerms = queryInfo.ctx().data.readWriteMask != 0;
4088 bool hasInheritedTerms = false;
4089 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4090 if (uses_inherited_id_matching(world, term)) {
4091 hasInheritedTerms = true;
4092 break;
4093 }
4094 }
4095
4096 if (!hasWriteTerms && !plan.preferOrSeed) {
4097 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
4098 if (pSeedTerm != nullptr && can_use_direct_seed_run_cache(world, queryInfo, *pSeedTerm)) {
4099 DirectEntitySeedInfo seedInfo{};
4100 seedInfo.seededAllTerm = pSeedTerm->id;
4101 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
4102 seedInfo.seededFromAll = true;
4103 if (!hasInheritedTerms) {
4104 each_chunk_runs<TIter>(
4105 queryInfo, cached_direct_seed_runs<TIter>(queryInfo, *pSeedTerm, seedInfo), func,
4107 } else {
4108 const auto entities = cached_direct_seed_chunk_entities<TIter>(queryInfo, *pSeedTerm, seedInfo);
4109 Entity inheritedArgIds[sizeof...(T) > 0 ? sizeof...(T) : 1] = {inherited_query_arg_id<T>(world)...};
4110 const Archetype* lastArchetypes[sizeof...(T) > 0 ? sizeof...(T) : 1]{};
4111 Entity cachedOwners[sizeof...(T) > 0 ? sizeof...(T) : 1]{};
4112 bool cachedDirect[sizeof...(T) > 0 ? sizeof...(T) : 1]{};
4113 for (const auto entity: entities) {
4114 invoke_inherited_query_args_by_id_cached<T...>(
4115 world, entity, inheritedArgIds, lastArchetypes, cachedOwners, cachedDirect, func,
4116 std::index_sequence_for<T...>{});
4117 }
4118 }
4119 return;
4120 }
4121 }
4122
4123 auto exec_direct_entity = [&](Entity entity) {
4124 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
4126 TIter it;
4127 init_direct_entity_iter(queryInfo, world, entity, it, indices, termIds);
4128 // Entity filters already ran in the seed walker, so invoke the inner loop directly.
4129 run_query_on_chunk_direct(it, func, core::func_type_list<T...>{});
4130 };
4131 auto walk_entities = [&](auto&& exec_entity) {
4132 if (hasWriteTerms) {
4133 auto& scratch = direct_query_scratch();
4134 // Writable callbacks may add local overrides and reshuffle direct-term indices,
4135 // so direct-seeded execution must iterate a stable snapshot.
4136 const auto seedInfo = build_direct_entity_seed(world, queryInfo, scratch.entities);
4137 for (const auto entity: scratch.entities) {
4138 if (!match_direct_entity_constraints<TIter>(world, queryInfo, entity))
4139 continue;
4140 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4141 continue;
4142 exec_entity(entity);
4143 }
4144 return;
4145 }
4146
4147 if (plan.preferOrSeed) {
4148 for_each_direct_or_union<TIter>(world, queryInfo, [&](Entity entity) {
4149 exec_entity(entity);
4150 return true;
4151 });
4152 return;
4153 }
4154
4155 (void)for_each_direct_all_seed<TIter>(world, queryInfo, plan, [&](Entity entity) {
4156 exec_entity(entity);
4157 return true;
4158 });
4159 };
4160
4161 if constexpr (!needsInheritedArgIds)
4162 walk_entities(exec_direct_entity);
4163 else {
4164 Entity inheritedArgIds[sizeof...(T) > 0 ? sizeof...(T) : 1] = {inherited_query_arg_id<T>(world)...};
4165 auto exec_entity = [&](Entity entity) {
4166 if (hasInheritedTerms) {
4167 invoke_inherited_query_args_by_id<T...>(
4168 world, entity, inheritedArgIds, func, std::index_sequence_for<T...>{});
4169 finish_query_args_by_id<T...>(world, entity, inheritedArgIds, std::index_sequence_for<T...>{});
4170 return;
4171 }
4172
4173 exec_direct_entity(entity);
4174 };
4175
4176 walk_entities(exec_entity);
4177 }
4178 }
4179
4180 template <bool UseFilters, typename TIter, typename ContainerOut>
4181 void arr_inter(QueryInfo& queryInfo, ContainerOut& outArray) {
4182 using ContainerItemType = typename ContainerOut::value_type;
4183 if constexpr (!UseFilters) {
4184 if (can_use_direct_entity_seed_eval(queryInfo)) {
4185 auto& world = *queryInfo.world();
4186 const auto plan = direct_entity_seed_plan(world, queryInfo);
4187 if (plan.preferOrSeed) {
4188 for_each_direct_or_union<TIter>(world, queryInfo, [&](Entity entity) {
4189 if constexpr (std::is_same_v<ContainerItemType, Entity>)
4190 outArray.push_back(entity);
4191 else {
4192 auto tmp = world_direct_entity_arg<ContainerItemType>(world, entity);
4193 outArray.push_back(tmp);
4194 }
4195 });
4196 return;
4197 }
4198
4199 (void)for_each_direct_all_seed<TIter>(world, queryInfo, plan, [&](Entity entity) {
4200 if constexpr (std::is_same_v<ContainerItemType, Entity>)
4201 outArray.push_back(entity);
4202 else {
4203 auto tmp = world_direct_entity_arg<ContainerItemType>(world, entity);
4204 outArray.push_back(tmp);
4205 }
4206 return true;
4207 });
4208
4209 return;
4210 }
4211 }
4212
4213 auto& world = *const_cast<World*>(queryInfo.world());
4214 if constexpr (!UseFilters) {
4215 if (!queryInfo.has_entity_filter_terms() &&
4216 can_use_direct_chunk_term_eval<ContainerItemType>(world, queryInfo) &&
4217 can_use_direct_chunk_iteration_fastpath(queryInfo)) {
4218 const auto cacheView = queryInfo.cache_archetype_view();
4219 uint32_t idxFrom = 0;
4220 uint32_t idxTo = (uint32_t)cacheView.size();
4221 if (queryInfo.ctx().data.groupBy != EntityBad && m_groupIdSet != 0) {
4222 const auto* pGroupData = queryInfo.selected_group_data(m_groupIdSet);
4223 if (pGroupData == nullptr)
4224 return;
4225 idxFrom = pGroupData->idxFirst;
4226 idxTo = pGroupData->idxLast + 1;
4227 }
4228
4229 for (uint32_t i = idxFrom; i < idxTo; ++i) {
4230 auto* pArchetype = cacheView[i];
4231 if GAIA_UNLIKELY (!can_process_archetype_inter<TIter>(queryInfo, *pArchetype))
4232 continue;
4233
4234 GAIA_PROF_SCOPE(query::arr);
4235
4236 const auto& chunks = pArchetype->chunks();
4237 for (auto* pChunk: chunks) {
4238 const auto from = TIter::start_index(pChunk);
4239 const auto to = TIter::end_index(pChunk);
4240 if GAIA_UNLIKELY (from == to)
4241 continue;
4242
4243 const auto dataView = chunk_view_auto<ContainerItemType>(pChunk);
4244 for (uint16_t row = from; row < to; ++row)
4245 outArray.push_back(dataView[row]);
4246 }
4247 }
4248
4249 return;
4250 }
4251 }
4252
4253 TIter it;
4254 it.set_world(queryInfo.world());
4255 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
4256 const auto cacheView = queryInfo.cache_archetype_view();
4257 const auto sortView = queryInfo.cache_sort_view();
4258 const bool needsBarrierCache = has_depth_order_hierarchy_enabled_barrier(queryInfo);
4259 if (needsBarrierCache)
4260 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
4261 uint32_t idxFrom = 0;
4262 uint32_t idxTo = (uint32_t)cacheView.size();
4263 if (queryInfo.ctx().data.groupBy != EntityBad && m_groupIdSet != 0) {
4264 const auto* pGroupData = queryInfo.selected_group_data(m_groupIdSet);
4265 if (pGroupData == nullptr)
4266 return;
4267 idxFrom = pGroupData->idxFirst;
4268 idxTo = pGroupData->idxLast + 1;
4269 }
4270
4271 const auto append_rows = [&](const uint32_t archetypeIdx, auto* pArchetype, auto* pChunk, uint16_t from,
4272 uint16_t to) {
4273 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(archetypeIdx);
4274 if GAIA_UNLIKELY (!can_process_archetype_inter<TIter>(queryInfo, *pArchetype, barrierPasses))
4275 return;
4276
4277 GAIA_PROF_SCOPE(query::arr);
4278
4279 it.set_archetype(pArchetype);
4280 it.set_chunk(pChunk, from, to);
4281
4282 const auto cnt = it.size();
4283 if (cnt == 0)
4284 return;
4285
4286 if constexpr (UseFilters) {
4287 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4288 return;
4289 }
4290
4291 const auto dataView = it.template view<ContainerItemType>();
4292 if (!hasEntityFilters) {
4293 GAIA_FOR(cnt) {
4294 const auto idx = it.template acc_index<ContainerItemType>(i);
4295 auto tmp = dataView[idx];
4296 outArray.push_back(tmp);
4297 }
4298 } else {
4299 const auto entities = it.template view<Entity>();
4300 GAIA_FOR(cnt) {
4301 if (!match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
4302 continue;
4303 const auto idx = it.template acc_index<ContainerItemType>(i);
4304 auto tmp = dataView[idx];
4305 outArray.push_back(tmp);
4306 }
4307 }
4308 };
4309
4310 if (!sortView.empty()) {
4311 for (const auto& view: sortView) {
4312 if (view.archetypeIdx < idxFrom || view.archetypeIdx >= idxTo)
4313 continue;
4314
4315 const auto minStartRow = TIter::start_index(view.pChunk);
4316 const auto minEndRow = TIter::end_index(view.pChunk);
4317 const auto viewFrom = view.startRow;
4318 const auto viewTo = (uint16_t)(view.startRow + view.count);
4319 const auto startRow = core::get_max(minStartRow, viewFrom);
4320 const auto endRow = core::get_min(minEndRow, viewTo);
4321 if (startRow == endRow)
4322 continue;
4323
4324 append_rows(
4325 view.archetypeIdx, const_cast<Archetype*>(cacheView[view.archetypeIdx]), view.pChunk, startRow,
4326 endRow);
4327 }
4328 return;
4329 }
4330
4331 for (uint32_t i = idxFrom; i < idxTo; ++i) {
4332 auto* pArchetype = cacheView[i];
4333 const auto& chunks = pArchetype->chunks();
4334 for (auto* pChunk: chunks)
4335 append_rows(i, pArchetype, pChunk, 0, 0);
4336 }
4337 }
4338
4339 public:
4340 QueryImpl() = default;
4341 ~QueryImpl() = default;
4342
4343 QueryImpl(
4344 World& world, QueryCache& queryCache, ArchetypeId& nextArchetypeId, uint32_t& worldVersion,
4345 const EntityToArchetypeMap& entityToArchetypeMap, const ArchetypeDArray& allArchetypes):
4346 m_nextArchetypeId(&nextArchetypeId), m_worldVersion(&worldVersion),
4347 m_entityToArchetypeMap(&entityToArchetypeMap), m_allArchetypes(&allArchetypes) {
4348 m_storage.init(&world, &queryCache);
4349 }
4350
4353 GAIA_NODISCARD QueryId id() const {
4354 if (!uses_query_cache_storage())
4355 return QueryIdBad;
4356 return m_storage.m_identity.handle.id();
4357 }
4358
4361 GAIA_NODISCARD uint32_t gen() const {
4362 if (!uses_query_cache_storage())
4363 return QueryIdBad;
4364 return m_storage.m_identity.handle.gen();
4365 }
4366
4367 //------------------------------------------------
4368
4370 void reset() {
4371 m_storage.reset();
4372 m_eachWalkData.reset();
4373 m_directSeedRunData.reset();
4374 reset_changed_filter_state();
4375 invalidate_each_walk_cache();
4376 invalidate_direct_seed_run_cache();
4377 }
4378
4380 void destroy() {
4381 (void)m_storage.try_del_from_cache();
4382 m_eachWalkData.reset();
4383 m_directSeedRunData.reset();
4384 reset_changed_filter_state();
4385 invalidate_each_walk_cache();
4386 invalidate_direct_seed_run_cache();
4387 }
4388
4391 GAIA_NODISCARD bool is_cached() const {
4392 return uses_query_cache_storage() && m_storage.is_cached();
4393 }
4394
4395 //------------------------------------------------
4396
4438 QueryImpl& add(const char* str, ...) {
4439 GAIA_ASSERT(str != nullptr);
4440 if (str == nullptr)
4441 return *this;
4442
4443 va_list args{};
4444 va_start(args, str);
4445
4446 uint32_t pos = 0;
4447 uint32_t exp0 = 0;
4448 uint32_t parentDepth = 0;
4449
4451 uint32_t varNamesCnt = 0;
4452 auto is_this_expr = [](std::span<const char> exprRaw) {
4453 auto expr = util::trim(exprRaw);
4454 return expr.size() == 5 && expr[0] == '$' && expr[1] == 't' && expr[2] == 'h' && expr[3] == 'i' &&
4455 expr[4] == 's';
4456 };
4457
4458 auto find_or_alloc_var = [&](std::span<const char> varExpr) -> Entity {
4459 auto varNameSpan = util::trim(varExpr);
4460 if (varNameSpan.empty())
4461 return EntityBad;
4462
4463 const util::str_view varName{varNameSpan.data(), (uint32_t)varNameSpan.size()};
4464 if (is_reserved_var_name(varName)) {
4465 GAIA_ASSERT2(false, "$this is reserved and can only be used as a source expression: Id($this)");
4466 return EntityBad;
4467 }
4468
4469 const auto namedVar = find_var_by_name(varName);
4470 if (namedVar != EntityBad)
4471 return namedVar;
4472
4473 GAIA_FOR(varNamesCnt) {
4474 if (varNames[i].size() != varName.size())
4475 continue;
4476 if (varNames[i].size() > 0 && memcmp(varNames[i].data(), varName.data(), varName.size()) != 0)
4477 continue;
4478 return query_var_entity(i);
4479 }
4480
4481 if (varNamesCnt >= varNames.size()) {
4482 GAIA_ASSERT2(false, "Too many query variables in expression");
4483 return EntityBad;
4484 }
4485
4486 const auto idx = varNamesCnt++;
4487 varNames[idx] = varName;
4488
4489 const auto varEntity = query_var_entity(idx);
4490 (void)set_var_name_internal(varEntity, varName);
4491 return varEntity;
4492 };
4493
4494 auto parse_entity_expr = [&](auto&& self, std::span<const char> exprRaw) -> Entity {
4495 auto expr = util::trim(exprRaw);
4496 if (expr.empty())
4497 return EntityBad;
4498
4499 if (expr[0] == '$')
4500 return find_or_alloc_var(expr.subspan(1));
4501
4502 if (expr[0] == '(') {
4503 if (expr.back() != ')') {
4504 GAIA_ASSERT2(false, "Expression '(' not terminated");
4505 return EntityBad;
4506 }
4507
4508 const auto idStr = expr.subspan(1, expr.size() - 2);
4509 const auto commaIdx = core::get_index(idStr, ',');
4510 if (commaIdx == BadIndex) {
4511 GAIA_ASSERT2(false, "Pair expression does not contain ','");
4512 return EntityBad;
4513 }
4514
4515 const auto first = self(self, idStr.subspan(0, commaIdx));
4516 if (first == EntityBad)
4517 return EntityBad;
4518 const auto second = self(self, idStr.subspan(commaIdx + 1));
4519 if (second == EntityBad)
4520 return EntityBad;
4521
4522 return ecs::Pair(first, second);
4523 }
4524
4525 return expr_to_entity((const World&)*m_storage.world(), args, expr);
4526 };
4527
4528 auto parse_src_expr = [&](std::span<const char> srcExprRaw, Entity& srcOut) -> bool {
4529 auto srcExpr = util::trim(srcExprRaw);
4530 if (srcExpr.empty())
4531 return false;
4532
4533 // `$this` explicitly means the default source for the term.
4534 if (is_this_expr(srcExpr)) {
4535 srcOut = EntityBad;
4536 return true;
4537 }
4538
4539 srcOut = parse_entity_expr(parse_entity_expr, srcExpr);
4540 return srcOut != EntityBad;
4541 };
4542
4543 auto parse_term_expr = [&](std::span<const char> exprRaw, Entity& id, QueryTermOptions& options) -> bool {
4544 auto expr = util::trim(exprRaw);
4545 if (expr.empty())
4546 return false;
4547
4548 if (expr.back() == ')') {
4549 int32_t depth = 0;
4550 int32_t openIdx = -1;
4551 for (int32_t i = (int32_t)expr.size() - 1; i >= 0; --i) {
4552 if (expr[(uint32_t)i] == ')')
4553 ++depth;
4554 else if (expr[(uint32_t)i] == '(') {
4555 --depth;
4556 if (depth == 0) {
4557 openIdx = i;
4558 break;
4559 }
4560 }
4561 }
4562
4563 // `Id(src)` form. Keep `(Rel,Tgt)` intact by requiring a non-empty prefix.
4564 if (openIdx > 0) {
4565 auto idExpr = util::trim(expr.subspan(0, (uint32_t)openIdx));
4566 auto srcExpr = util::trim(expr.subspan((uint32_t)openIdx + 1, expr.size() - (uint32_t)openIdx - 2));
4567 if (!idExpr.empty() && !srcExpr.empty()) {
4568 id = parse_entity_expr(parse_entity_expr, idExpr);
4569 if (id == EntityBad)
4570 return false;
4571
4572 Entity src = EntityBad;
4573 if (!parse_src_expr(srcExpr, src))
4574 return false;
4575
4576 options.src(src);
4577 return true;
4578 }
4579 }
4580 }
4581
4582 id = parse_entity_expr(parse_entity_expr, expr);
4583 return id != EntityBad;
4584 };
4585
4586 auto add_term = [&](QueryOpKind op, std::span<const char> exprRaw) {
4587 auto expr = util::trim(exprRaw);
4588 if (expr.empty())
4589 return false;
4590
4591 bool isReadWrite = false;
4592 if (!expr.empty() && expr[0] == '&') {
4593 isReadWrite = true;
4594 expr = util::trim(expr.subspan(1));
4595 }
4596
4597 QueryTermOptions options{};
4598 if (isReadWrite)
4599 options.write();
4600
4601 Entity entity = EntityBad;
4602 if (!parse_term_expr(expr, entity, options))
4603 return false;
4604
4605 switch (op) {
4606 case QueryOpKind::All:
4607 all(entity, options);
4608 break;
4609 case QueryOpKind::Or:
4610 or_(entity, options);
4611 break;
4612 case QueryOpKind::Not:
4613 no(entity, options);
4614 break;
4615 case QueryOpKind::Any:
4616 any(entity, options);
4617 break;
4618 default:
4619 GAIA_ASSERT(false);
4620 return false;
4621 }
4622
4623 return true;
4624 };
4625
4626 auto process = [&]() {
4627 std::span<const char> exprRaw(&str[exp0], pos - exp0);
4628 exp0 = ++pos;
4629
4630 auto expr = util::trim(exprRaw);
4631 if (expr.empty())
4632 return true;
4633
4634 // OR-chain at top level maps to query::or_ terms.
4635 bool hasOrChain = false;
4636 {
4637 uint32_t depth = 0;
4638 const auto cnt = (uint32_t)expr.size();
4639 for (uint32_t i = 0; i + 1 < cnt; ++i) {
4640 const auto ch = expr[i];
4641 if (ch == '(')
4642 ++depth;
4643 else if (ch == ')') {
4644 GAIA_ASSERT(depth > 0);
4645 --depth;
4646 } else if (depth == 0 && ch == '|' && expr[i + 1] == '|') {
4647 hasOrChain = true;
4648 break;
4649 }
4650 }
4651 }
4652
4653 if (hasOrChain) {
4654 uint32_t depth = 0;
4655 uint32_t partBeg = 0;
4656 const auto cnt = (uint32_t)expr.size();
4657 for (uint32_t i = 0; i < cnt; ++i) {
4658 const auto ch = expr[i];
4659 if (ch == '(')
4660 ++depth;
4661 else if (ch == ')') {
4662 GAIA_ASSERT(depth > 0);
4663 --depth;
4664 }
4665
4666 const bool isOr = i + 1 < cnt && depth == 0 && ch == '|' && expr[i + 1] == '|';
4667 const bool isEnd = i + 1 == cnt;
4668 if (!isOr && !isEnd)
4669 continue;
4670
4671 const auto partEnd = isOr ? i : (i + 1);
4672 auto partExpr = expr.subspan(partBeg, partEnd - partBeg);
4673 if (!add_term(QueryOpKind::Or, partExpr))
4674 return false;
4675
4676 if (isOr) {
4677 partBeg = i + 2;
4678 ++i;
4679 }
4680 }
4681
4682 return true;
4683 }
4684
4685 QueryOpKind op = QueryOpKind::All;
4686 if (expr[0] == '?') {
4687 op = QueryOpKind::Any;
4688 expr = util::trim(expr.subspan(1));
4689 } else if (expr[0] == '!') {
4690 op = QueryOpKind::Not;
4691 expr = util::trim(expr.subspan(1));
4692 }
4693
4694 return add_term(op, expr);
4695 };
4696
4697 for (; str[pos] != 0; ++pos) {
4698 if (str[pos] == '(')
4699 ++parentDepth;
4700 else if (str[pos] == ')') {
4701 GAIA_ASSERT(parentDepth > 0);
4702 --parentDepth;
4703 } else if (str[pos] == ',' && parentDepth == 0) {
4704 if (!process())
4705 goto add_end;
4706 }
4707 }
4708 process();
4709
4710 add_end:
4711 va_end(args);
4712 return *this;
4713 }
4714
4719 // Add commands to the command buffer
4720 add_inter(item);
4721 return *this;
4722 }
4723
4724 //------------------------------------------------
4725
4730 QueryImpl& is(Entity entity, const QueryTermOptions& options = QueryTermOptions{}) {
4731 return all(Pair(Is, entity), options);
4732 }
4733
4734 //------------------------------------------------
4735
4741 options.in();
4742 return all(Pair(Is, entity), options);
4743 }
4744
4745 //------------------------------------------------
4746
4752 add_entity_term(QueryOpKind::All, entity, options);
4753 return *this;
4754 }
4755
4760 template <typename T>
4761 QueryImpl& all(const QueryTermOptions& options) {
4762 add_inter<T>(QueryOpKind::All, options);
4763 return *this;
4764 }
4765
4769 template <typename T>
4771 // Add commands to the command buffer
4772 add_inter<T>(QueryOpKind::All);
4773 return *this;
4774 }
4775
4776 //------------------------------------------------
4777
4783 add_entity_term(QueryOpKind::Any, entity, options);
4784 return *this;
4785 }
4786
4791 template <typename T>
4792 QueryImpl& any(const QueryTermOptions& options) {
4793 add_inter<T>(QueryOpKind::Any, options);
4794 return *this;
4795 }
4796
4800 template <typename T>
4802 // Add commands to the command buffer
4803 add_inter<T>(QueryOpKind::Any);
4804 return *this;
4805 }
4806
4807 //------------------------------------------------
4808
4815 add_entity_term(QueryOpKind::Or, entity, options);
4816 return *this;
4817 }
4818
4823 template <typename T>
4824 QueryImpl& or_(const QueryTermOptions& options) {
4825 add_inter<T>(QueryOpKind::Or, options);
4826 return *this;
4827 }
4828
4832 template <typename T>
4834 add_inter<T>(QueryOpKind::Or);
4835 return *this;
4836 }
4837
4838 //------------------------------------------------
4839
4844 QueryImpl& no(Entity entity, const QueryTermOptions& options = QueryTermOptions{}) {
4845 add_entity_term(QueryOpKind::Not, entity, options);
4846 return *this;
4847 }
4848
4853 template <typename T>
4854 QueryImpl& no(const QueryTermOptions& options) {
4855 add_inter<T>(QueryOpKind::Not, options);
4856 return *this;
4857 }
4858
4862 template <typename T>
4864 // Add commands to the command buffer
4865 add_inter<T>(QueryOpKind::Not);
4866 return *this;
4867 }
4868
4875 [[maybe_unused]] const bool ok = set_var_name_internal(varEntity, name);
4876 GAIA_ASSERT(ok);
4877 return *this;
4878 }
4880 QueryImpl& var_name(Entity varEntity, const char* name) {
4881 GAIA_ASSERT(name != nullptr);
4882 if (name == nullptr)
4883 return *this;
4884 return var_name(varEntity, util::str_view{name, (uint32_t)GAIA_STRLEN(name, 256)});
4885 }
4886
4891 QueryImpl& set_var(Entity varEntity, Entity value) {
4892 const bool ok = is_query_var_entity(varEntity);
4893 GAIA_ASSERT(ok);
4894 if (!ok)
4895 return *this;
4896
4897 const auto idx = query_var_idx(varEntity);
4898 m_varBindings[idx] = value;
4899 m_varBindingsMask |= (uint8_t(1) << idx);
4900 return *this;
4901 }
4906 const auto varEntity = find_var_by_name(name);
4907 GAIA_ASSERT(varEntity != EntityBad);
4908 if (varEntity == EntityBad)
4909 return *this;
4910 return set_var(varEntity, value);
4911 }
4913 QueryImpl& set_var(const char* name, Entity value) {
4914 GAIA_ASSERT(name != nullptr);
4915 if (name == nullptr)
4916 return *this;
4917 return set_var(util::str_view{name, (uint32_t)GAIA_STRLEN(name, 256)}, value);
4918 }
4919
4923 const bool ok = is_query_var_entity(varEntity);
4924 GAIA_ASSERT(ok);
4925 if (!ok)
4926 return *this;
4927
4928 const auto idx = query_var_idx(varEntity);
4929 m_varBindingsMask &= (uint8_t)~(uint8_t(1) << idx);
4930 return *this;
4931 }
4934 m_varBindingsMask = 0;
4935 return *this;
4936 }
4937
4938 //------------------------------------------------
4939
4944 changed_inter(entity);
4945 return *this;
4946 }
4947
4951 template <typename T>
4953 // Add commands to the command buffer
4954 changed_inter<T>();
4955 return *this;
4956 }
4957
4958 //------------------------------------------------
4959
4962 // or anything else to sort by components.
4965 QueryImpl& sort_by(Entity entity, TSortByFunc func) {
4966 sort_by_inter(entity, func);
4967 return *this;
4968 }
4969
4974 template <typename T>
4975 QueryImpl& sort_by(TSortByFunc func) {
4976 sort_by_inter<T>(func);
4977 return *this;
4978 }
4979
4985 template <typename Rel, typename Tgt>
4986 QueryImpl& sort_by(TSortByFunc func) {
4987 sort_by_inter<Rel, Tgt>(func);
4988 return *this;
4989 }
4990
4991 //------------------------------------------------
4992
4993 class OrderByWalkView final {
4994 QueryImpl* m_query = nullptr;
4995 Entity m_relation = EntityBad;
4996
4997 public:
4998 OrderByWalkView(QueryImpl& query, Entity relation): m_query(&query), m_relation(relation) {}
4999
5000 template <typename Func>
5001 void each(Func func) {
5002 m_query->each_walk(func, m_relation);
5003 }
5004 };
5005
5006 //------------------------------------------------
5007
5012 GAIA_NODISCARD OrderByWalkView walk(Entity relation) {
5013 return OrderByWalkView(*this, relation);
5014 }
5015
5016 //------------------------------------------------
5017
5024 QueryImpl& depth_order(Entity relation = ChildOf) {
5025 GAIA_ASSERT(!relation.pair());
5026 GAIA_ASSERT(world_supports_depth_order(*m_storage.world(), relation));
5027 group_by_inter(relation, group_by_func_depth_order);
5028 return *this;
5029 }
5030
5033 template <typename Rel>
5035 using UO = typename component_type_t<Rel>::TypeOriginal;
5036 static_assert(core::is_raw_v<UO>, "Use depth_order() with raw relation types only");
5037
5038 const auto& desc = comp_cache_add<Rel>(*m_storage.world());
5039 return depth_order(desc.entity);
5040 }
5041
5042 //------------------------------------------------
5043
5047 QueryImpl& group_by(Entity entity, TGroupByFunc func = group_by_func_default) {
5048 group_by_inter(entity, func);
5049 return *this;
5050 }
5051
5055 template <typename T>
5056 QueryImpl& group_by(TGroupByFunc func = group_by_func_default) {
5057 group_by_inter<T>(func);
5058 return *this;
5059 }
5060
5065 template <typename Rel, typename Tgt>
5066 QueryImpl& group_by(TGroupByFunc func = group_by_func_default) {
5067 group_by_inter<Rel, Tgt>(func);
5068 return *this;
5069 }
5070
5071 //------------------------------------------------
5072
5077 group_dep_inter(relation);
5078 return *this;
5079 }
5080
5084 template <typename Rel>
5086 group_dep_inter<Rel>();
5087 return *this;
5088 }
5089
5090 //------------------------------------------------
5091
5094 QueryImpl& group_id(GroupId groupId) {
5095 set_group_id_inter(groupId);
5096 return *this;
5097 }
5098
5102 GAIA_ASSERT(!entity.pair());
5103 set_group_id_inter(entity.id());
5104 return *this;
5105 }
5106
5109 template <typename T>
5111 set_group_id_inter<T>();
5112 return *this;
5113 }
5114
5115 //------------------------------------------------
5116
5119 template <typename Func>
5120 void each(Func func) {
5121 each_inter<QueryExecType::Default, Func>(func);
5122 }
5123
5127 template <typename Func>
5128 void each(Func func, QueryExecType execType) {
5129 switch (execType) {
5130 case QueryExecType::Parallel:
5131 each_inter<QueryExecType::Parallel, Func>(func);
5132 break;
5133 case QueryExecType::ParallelPerf:
5134 each_inter<QueryExecType::ParallelPerf, Func>(func);
5135 break;
5136 case QueryExecType::ParallelEff:
5137 each_inter<QueryExecType::ParallelEff, Func>(func);
5138 break;
5139 default:
5140 each_inter<QueryExecType::Default, Func>(func);
5141 break;
5142 }
5143 }
5144
5145 //------------------------------------------------
5146
5149 template <typename Func>
5150 void each_arch(Func func) {
5151 auto& queryInfo = fetch();
5152 match_all(queryInfo);
5153
5154 if constexpr (std::is_invocable_v<Func, IterAll&>) {
5155 run_query_on_archetypes<QueryExecType::Default, IterAll>(queryInfo, [&](IterAll& it) {
5156 GAIA_PROF_SCOPE(query_func_a);
5157 func(it);
5158 });
5159 } else if constexpr (std::is_invocable_v<Func, Iter&>) {
5160 run_query_on_archetypes<QueryExecType::Default, Iter>(queryInfo, [&](Iter& it) {
5161 GAIA_PROF_SCOPE(query_func_a);
5162 func(it);
5163 });
5164 } else if constexpr (std::is_invocable_v<Func, IterDisabled&>) {
5165 run_query_on_archetypes<QueryExecType::Default, IterDisabled>(queryInfo, [&](IterDisabled& it) {
5166 GAIA_PROF_SCOPE(query_func_a);
5167 func(it);
5168 });
5169 }
5170 }
5171
5172 //------------------------------------------------
5173
5182 bool empty(Constraints constraints = Constraints::EnabledOnly) {
5183 auto& queryInfo = fetch();
5184 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
5185 switch (constraints) {
5186 case Constraints::EnabledOnly:
5187 return empty_inter<false, Iter>(queryInfo);
5188 case Constraints::DisabledOnly:
5189 return empty_inter<false, IterDisabled>(queryInfo);
5190 case Constraints::AcceptAll:
5191 return empty_inter<false, IterAll>(queryInfo);
5192 }
5193 }
5194
5195 match_all(queryInfo);
5196
5197 const bool hasFilters = queryInfo.has_filters();
5198 if (hasFilters) {
5199 switch (constraints) {
5200 case Constraints::EnabledOnly:
5201 return empty_inter<true, Iter>(queryInfo);
5202 case Constraints::DisabledOnly:
5203 return empty_inter<true, IterDisabled>(queryInfo);
5204 case Constraints::AcceptAll:
5205 return empty_inter<true, IterAll>(queryInfo);
5206 }
5207 } else {
5208 switch (constraints) {
5209 case Constraints::EnabledOnly:
5210 return empty_inter<false, Iter>(queryInfo);
5211 case Constraints::DisabledOnly:
5212 return empty_inter<false, IterDisabled>(queryInfo);
5213 case Constraints::AcceptAll:
5214 return empty_inter<false, IterAll>(queryInfo);
5215 }
5216 }
5217
5218 return true;
5219 }
5220
5228 uint32_t count(Constraints constraints = Constraints::EnabledOnly) {
5229 auto& queryInfo = fetch();
5230 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
5231 switch (constraints) {
5232 case Constraints::EnabledOnly:
5233 return count_inter<false, Iter>(queryInfo);
5234 case Constraints::DisabledOnly:
5235 return count_inter<false, IterDisabled>(queryInfo);
5236 case Constraints::AcceptAll:
5237 return count_inter<false, IterAll>(queryInfo);
5238 }
5239 }
5240
5241 match_all(queryInfo);
5242
5243 uint32_t entCnt = 0;
5244
5245 const bool hasFilters = queryInfo.has_filters();
5246 if (hasFilters) {
5247 switch (constraints) {
5248 case Constraints::EnabledOnly: {
5249 entCnt += count_inter<true, Iter>(queryInfo);
5250 } break;
5251 case Constraints::DisabledOnly: {
5252 entCnt += count_inter<true, IterDisabled>(queryInfo);
5253 } break;
5254 case Constraints::AcceptAll: {
5255 entCnt += count_inter<true, IterAll>(queryInfo);
5256 } break;
5257 }
5258 } else {
5259 switch (constraints) {
5260 case Constraints::EnabledOnly: {
5261 entCnt += count_inter<false, Iter>(queryInfo);
5262 } break;
5263 case Constraints::DisabledOnly: {
5264 entCnt += count_inter<false, IterDisabled>(queryInfo);
5265 } break;
5266 case Constraints::AcceptAll: {
5267 entCnt += count_inter<false, IterAll>(queryInfo);
5268 } break;
5269 }
5270 }
5271
5272 return entCnt;
5273 }
5274
5279 template <typename Container>
5280 void arr(Container& outArray, Constraints constraints = Constraints::EnabledOnly) {
5281 const auto entCnt = count(constraints);
5282 if (entCnt == 0)
5283 return;
5284
5285 outArray.reserve(entCnt);
5286 auto& queryInfo = fetch();
5287 match_all(queryInfo);
5288
5289 const bool hasFilters = queryInfo.has_filters();
5290 if (hasFilters) {
5291 switch (constraints) {
5292 case Constraints::EnabledOnly:
5293 arr_inter<true, Iter>(queryInfo, outArray);
5294 break;
5295 case Constraints::DisabledOnly:
5296 arr_inter<true, IterDisabled>(queryInfo, outArray);
5297 break;
5298 case Constraints::AcceptAll:
5299 arr_inter<true, IterAll>(queryInfo, outArray);
5300 break;
5301 }
5302 } else {
5303 switch (constraints) {
5304 case Constraints::EnabledOnly:
5305 arr_inter<false, Iter>(queryInfo, outArray);
5306 break;
5307 case Constraints::DisabledOnly:
5308 arr_inter<false, IterDisabled>(queryInfo, outArray);
5309 break;
5310 case Constraints::AcceptAll:
5311 arr_inter<false, IterAll>(queryInfo, outArray);
5312 break;
5313 }
5314 }
5315 }
5316
5323 QueryInfo& queryInfo, Entity relation, Constraints constraints = Constraints::EnabledOnly) {
5324 struct OrderedWalkTargetCtx {
5325 const cnt::darray<Entity>* pEntities = nullptr;
5326 uint32_t cnt = 0;
5327 uint32_t dependentIdx = 0;
5328 cnt::darray<uint32_t>* pIndegree = nullptr;
5329 cnt::darray<uint32_t>* pOutdegree = nullptr;
5330 cnt::darray<uint32_t>* pWriteCursor = nullptr;
5331 cnt::darray<uint32_t>* pEdges = nullptr;
5332 uint32_t* pEdgeCnt = nullptr;
5333
5334 GAIA_NODISCARD static uint32_t
5335 find_entity_idx(const cnt::darray<Entity>& entities, uint32_t cnt, Entity entity) {
5336 const auto targetId = entity.id();
5337 uint32_t low = 0;
5338 uint32_t high = cnt;
5339 while (low < high) {
5340 const uint32_t mid = low + ((high - low) >> 1);
5341 if (entities[mid].id() < targetId)
5342 low = mid + 1;
5343 else
5344 high = mid;
5345 }
5346
5347 if (low < cnt && entities[low].id() == targetId)
5348 return low;
5349 return cnt;
5350 }
5351
5352 static void count_edge(void* rawCtx, Entity dependency) {
5353 auto& ctx = *static_cast<OrderedWalkTargetCtx*>(rawCtx);
5354 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
5355 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
5356 return;
5357
5358 ++(*ctx.pOutdegree)[dependencyIdx];
5359 ++(*ctx.pIndegree)[ctx.dependentIdx];
5360 ++*ctx.pEdgeCnt;
5361 }
5362
5363 static void write_edge(void* rawCtx, Entity dependency) {
5364 auto& ctx = *static_cast<OrderedWalkTargetCtx*>(rawCtx);
5365 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
5366 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
5367 return;
5368
5369 (*ctx.pEdges)[(*ctx.pWriteCursor)[dependencyIdx]++] = ctx.dependentIdx;
5370 }
5371 };
5372
5373 auto& walkData = ensure_each_walk_data();
5374 auto& world = *m_storage.world();
5375 const uint32_t relationVersion = world_rel_version(world, relation);
5376 const uint32_t worldVersion = ::gaia::ecs::world_version(world);
5377
5378 const bool needsTraversalBarrierState =
5379 constraints == Constraints::EnabledOnly && ::gaia::ecs::valid(world, relation);
5380 auto survives_disabled_barrier = [&](Entity entity) {
5381 if (!needsTraversalBarrierState)
5382 return true;
5383
5384 auto curr = entity;
5385 GAIA_FOR(MAX_TRAV_DEPTH) {
5386 const auto next = target(world, curr, relation);
5387 if (next == EntityBad || next == curr)
5388 break;
5389 if (!world_entity_enabled(world, next))
5390 return false;
5391 curr = next;
5392 }
5393
5394 return true;
5395 };
5396
5397 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedConstraints == constraints &&
5398 walkData.cachedRelationVersion == relationVersion &&
5399 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
5400 !queryInfo.has_filters()) {
5401 auto& chunks = walkData.scratchChunks;
5402 chunks.clear();
5403
5404 bool chunkChanged = false;
5405 for (auto* pArchetype: queryInfo) {
5406 if (pArchetype == nullptr || !can_process_archetype(queryInfo, *pArchetype))
5407 continue;
5408
5409 for (const auto* pChunk: pArchetype->chunks()) {
5410 if (pChunk == nullptr)
5411 continue;
5412
5413 chunks.push_back(pChunk);
5414 if (!chunkChanged && pChunk->changed(walkData.cachedEntityVersion))
5415 chunkChanged = true;
5416 }
5417 }
5418
5419 bool sameChunks = chunks.size() == walkData.cachedChunks.size();
5420 if (sameChunks) {
5421 for (uint32_t i = 0; i < (uint32_t)chunks.size(); ++i) {
5422 if (chunks[i] != walkData.cachedChunks[i]) {
5423 sameChunks = false;
5424 break;
5425 }
5426 }
5427 }
5428
5429 if (sameChunks && !chunkChanged) {
5430 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
5431 }
5432 }
5433
5434 auto& entities = walkData.scratchEntities;
5435 entities.clear();
5436 arr(entities, constraints);
5437 if (entities.empty())
5438 return {};
5439
5440 if (needsTraversalBarrierState) {
5441 uint32_t writeIdx = 0;
5442 const auto cnt = (uint32_t)entities.size();
5443 GAIA_FOR(cnt) {
5444 const auto entity = entities[i];
5445 if (!survives_disabled_barrier(entity))
5446 continue;
5447 entities[writeIdx++] = entity;
5448 }
5449 entities.resize(writeIdx);
5450 if (entities.empty())
5451 return {};
5452 }
5453
5454 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedConstraints == constraints &&
5455 walkData.cachedRelationVersion == relationVersion &&
5456 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
5457 entities.size() == walkData.cachedInput.size()) {
5458 bool sameInput = true;
5459 for (uint32_t i = 0; i < (uint32_t)entities.size(); ++i) {
5460 if (entities[i] != walkData.cachedInput[i]) {
5461 sameInput = false;
5462 break;
5463 }
5464 }
5465
5466 if (sameInput) {
5467 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
5468 }
5469 }
5470
5471 auto& ordered = walkData.cachedOutput;
5472 walkData.cachedInput = entities;
5473 ordered.clear();
5474 if (!::gaia::ecs::valid(world, relation)) {
5475 core::sort(entities, [](Entity left, Entity right) {
5476 return left.id() < right.id();
5477 });
5478 ordered = entities;
5479 } else {
5480 // Keep execution deterministic regardless of archetype iteration order.
5481 core::sort(entities, [](Entity left, Entity right) {
5482 return left.id() < right.id();
5483 });
5484
5485 const auto cnt = (uint32_t)entities.size();
5486
5487 auto& indegree = walkData.scratchIndegree;
5488 indegree.resize(cnt);
5489 auto& outdegree = walkData.scratchOutdegree;
5490 outdegree.resize(cnt);
5491 for (uint32_t i = 0; i < cnt; ++i) {
5492 indegree[i] = 0;
5493 outdegree[i] = 0;
5494 }
5495
5496 uint32_t edgeCnt = 0;
5497 OrderedWalkTargetCtx edgeCtx;
5498 edgeCtx.pEntities = &entities;
5499 edgeCtx.cnt = cnt;
5500 edgeCtx.pIndegree = &indegree;
5501 edgeCtx.pOutdegree = &outdegree;
5502 edgeCtx.pEdgeCnt = &edgeCnt;
5503 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
5504 const auto dependent = entities[dependentIdx];
5505 edgeCtx.dependentIdx = dependentIdx;
5506 world_for_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::count_edge);
5507 }
5508
5509 auto& offsets = walkData.scratchOffsets;
5510 offsets.resize(cnt + 1);
5511 offsets[0] = 0;
5512 for (uint32_t i = 0; i < cnt; ++i)
5513 offsets[i + 1] = offsets[i] + outdegree[i];
5514
5515 auto& writeCursor = walkData.scratchWriteCursor;
5516 writeCursor.resize(cnt);
5517 for (uint32_t i = 0; i < cnt; ++i)
5518 writeCursor[i] = offsets[i];
5519
5520 auto& edges = walkData.scratchEdges;
5521 edges.resize(edgeCnt);
5522 edgeCtx.pWriteCursor = &writeCursor;
5523 edgeCtx.pEdges = &edges;
5524 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
5525 const auto dependent = entities[dependentIdx];
5526 edgeCtx.dependentIdx = dependentIdx;
5527 world_for_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::write_edge);
5528 }
5529
5530 ordered.reserve(cnt);
5531
5532 auto& currLevel = walkData.scratchCurrLevel;
5533 currLevel.clear();
5534 auto& nextLevel = walkData.scratchNextLevel;
5535 nextLevel.clear();
5536 for (uint32_t i = 0; i < cnt; ++i) {
5537 if (indegree[i] == 0)
5538 currLevel.push_back(i);
5539 }
5540
5541 auto sort_level = [&](cnt::darray<uint32_t>& level) {
5542 core::sort(level, [&](uint32_t left, uint32_t right) {
5543 return entities[left].id() < entities[right].id();
5544 });
5545 };
5546
5547 sort_level(currLevel);
5548
5549 uint32_t executedCnt = 0;
5550 while (!currLevel.empty()) {
5551 for (auto idx: currLevel) {
5552 ordered.push_back(entities[idx]);
5553 ++executedCnt;
5554 }
5555
5556 nextLevel.clear();
5557 for (auto idx: currLevel) {
5558 for (uint32_t edgePos = offsets[idx]; edgePos < offsets[idx + 1]; ++edgePos) {
5559 const auto dependentIdx = edges[edgePos];
5560 if (indegree[dependentIdx] == 0)
5561 continue;
5562
5563 --indegree[dependentIdx];
5564 if (indegree[dependentIdx] == 0)
5565 nextLevel.push_back(dependentIdx);
5566 }
5567 }
5568
5569 if (nextLevel.empty())
5570 break;
5571
5572 sort_level(nextLevel);
5573 currLevel.clear();
5574 currLevel.reserve(nextLevel.size());
5575 for (auto idx: nextLevel)
5576 currLevel.push_back(idx);
5577 }
5578
5579 // A cycle can still appear in user data. Keep behavior deterministic and output the rest by id.
5580 if (executedCnt != cnt) {
5581 for (uint32_t i = 0; i < cnt; ++i) {
5582 if (indegree[i] > 0)
5583 ordered.push_back(entities[i]);
5584 }
5585 }
5586 }
5587
5588 walkData.cachedRelation = relation;
5589 walkData.cachedConstraints = constraints;
5590 walkData.cachedRelationVersion = relationVersion;
5591 walkData.cachedEntityVersion = ::gaia::ecs::world_version(world);
5592 walkData.cachedRuns.clear();
5593
5594 {
5595 const auto orderedCnt = (uint32_t)ordered.size();
5596 if (orderedCnt != 0) {
5597 for (uint32_t i = 0; i < orderedCnt; ++i) {
5598 const auto& ec = ::gaia::ecs::fetch(world, ordered[i]);
5599 if (walkData.cachedRuns.empty()) {
5600 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
5601 continue;
5602 }
5603
5604 auto& run = walkData.cachedRuns.back();
5605 if (ec.pChunk == run.pChunk && ec.row == run.to) {
5606 run.to = (uint16_t)(run.to + 1);
5607 } else {
5608 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
5609 }
5610 }
5611 }
5612 }
5613
5614 if (!queryInfo.has_filters()) {
5615 auto& chunks = walkData.scratchChunks;
5616 chunks.clear();
5617 for (auto* pArchetype: queryInfo) {
5618 if (pArchetype == nullptr || !can_process_archetype(queryInfo, *pArchetype))
5619 continue;
5620
5621 for (const auto* pChunk: pArchetype->chunks()) {
5622 if (pChunk == nullptr)
5623 continue;
5624 chunks.push_back(pChunk);
5625 }
5626 }
5627 walkData.cachedChunks = chunks;
5628 } else
5629 walkData.cachedChunks.clear();
5630 walkData.cacheValid = true;
5631
5632 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
5633 }
5634
5642 template <typename Func>
5643 void each_walk(Func func, Entity relation, Constraints constraints = Constraints::EnabledOnly) {
5644 auto& queryInfo = fetch();
5645 match_all(queryInfo);
5646 const auto ordered = ordered_entities_walk(queryInfo, relation, constraints);
5647
5648 if constexpr (std::is_invocable_v<Func, IterAll&>) {
5649 each_direct_entities_iter<IterAll>(queryInfo, ordered, func);
5650 } else if constexpr (std::is_invocable_v<Func, Iter&>) {
5651 each_direct_entities_iter<Iter>(queryInfo, ordered, func);
5652 } else if constexpr (std::is_invocable_v<Func, IterDisabled&>) {
5653 each_direct_entities_iter<IterDisabled>(queryInfo, ordered, func);
5654 } else if constexpr (std::is_invocable_v<Func, const Entity&> || std::is_invocable_v<Func, Entity>) {
5655 for (const auto entity: ordered)
5656 func(entity);
5657 } else {
5658 using InputArgs = decltype(core::func_args(&Func::operator()));
5659 GAIA_ASSERT(unpack_args_into_query_has_all(queryInfo, InputArgs{}));
5660 GAIA_ASSERT(can_use_direct_target_eval(queryInfo));
5661 if (!can_use_direct_target_eval(queryInfo))
5662 return;
5663
5664 switch (constraints) {
5665 case Constraints::EnabledOnly:
5666 each_direct_entities<Iter>(queryInfo, ordered, func, InputArgs{});
5667 break;
5668 case Constraints::DisabledOnly:
5669 each_direct_entities<IterDisabled>(queryInfo, ordered, func, InputArgs{});
5670 break;
5671 case Constraints::AcceptAll:
5672 each_direct_entities<IterAll>(queryInfo, ordered, func, InputArgs{});
5673 break;
5674 }
5675 }
5676 }
5677
5678 //------------------------------------------------
5679
5681 void diag() {
5682 // Make sure matching happened
5683 auto& queryInfo = fetch();
5684 match_all(queryInfo);
5685 if (uses_shared_cache_layer())
5686 GAIA_LOG_N("BEG DIAG Query %u.%u [S]", id(), gen());
5687 else if (uses_query_cache_storage())
5688 GAIA_LOG_N("BEG DIAG Query %u.%u [L]", id(), gen());
5689 else
5690 GAIA_LOG_N("BEG DIAG Query [U]");
5691 for (const auto* pArchetype: queryInfo)
5692 Archetype::diag_basic_info(*m_storage.world(), *pArchetype);
5693 GAIA_LOG_N("END DIAG Query");
5694 }
5695
5698 GAIA_NODISCARD util::str bytecode() {
5699 auto& queryInfo = fetch();
5700 return queryInfo.bytecode();
5701 }
5702
5705 const auto dump = bytecode();
5706 if (uses_shared_cache_layer())
5707 GAIA_LOG_N("BEG DIAG Query Bytecode %u.%u [S]", id(), gen());
5708 else if (uses_query_cache_storage())
5709 GAIA_LOG_N("BEG DIAG Query Bytecode %u.%u [L]", id(), gen());
5710 else
5711 GAIA_LOG_N("BEG DIAG Query Bytecode [U]");
5712 GAIA_LOG_N("%.*s", (int)dump.size(), dump.data());
5713 GAIA_LOG_N("END DIAG Query");
5714 }
5715 };
5716 } // namespace detail
5717
5718 using Query = detail::QueryImpl;
5720 struct QueryUncached {};
5721 } // namespace ecs
5722} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Array of elements of type.
Definition sarray_ext_impl.h:27
Array of elements of type.
Definition sarray_impl.h:26
Definition span_impl.h:99
Definition archetype.h:83
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:1793
GAIA_NODISCARD const ComponentCacheItem & get(detail::ComponentDescId compDescId) const noexcept
Returns the component cache item given the compDescId.
Definition component_cache.h:373
Iterator for iterating both enabled and disabled entities. Disabled entities always precede enabled o...
Definition chunk_iterator.h:1652
Iterator for iterating disabled entities.
Definition chunk_iterator.h:1648
Iterator for iterating enabled entities.
Definition chunk_iterator.h:1646
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:211
bool del(QueryHandle handle)
Deletes an existing QueryInfo object given the provided query handle.
Definition query_cache.h:262
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:244
Definition query_info.h:86
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:1877
GAIA_NODISCARD bool matches_prefab_entities() const
Returns true when prefab-tagged entities should participate in query results.
Definition query_info.h:1798
GAIA_NODISCARD bool has_entity_filter_terms() const
Returns true when direct non-fragmenting terms must be rechecked per entity.
Definition query_info.h:1784
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:1849
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:1634
Definition world.h:88
Definition query.h:459
QueryImpl & sort_by(Entity entity, TSortByFunc func)
Sorts the query by the specified entity and function.
Definition query.h:4965
static void run_query_func(World *pWorld, Func func, ChunkBatch &batch)
Execute the functor for a given chunk batch.
Definition query.h:1751
QueryImpl & scope(QueryCacheScope cacheScope)
Sets the cache scope used by cached queries.
Definition query.h:894
void arr(Container &outArray, Constraints constraints=Constraints::EnabledOnly)
Appends all components or entities matching the query to the output array.
Definition query.h:5280
QueryImpl & group_by(TGroupByFunc func=group_by_func_default)
Organizes matching archetypes into groups according to the grouping function.
Definition query.h:5056
QueryImpl & or_()
Adds an OR typed term.
Definition query.h:4833
QueryImpl & sort_by(TSortByFunc func)
Sorts the query by the specified pair and function.
Definition query.h:4986
void reset()
Release any data allocated by the query.
Definition query.h:4370
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:3530
QueryImpl & all(const QueryTermOptions &options)
Adds a required typed term.
Definition query.h:4761
static void run_query_arch_func(World *pWorld, Func func, ChunkBatch &batch)
Execute the functor for a given chunk batch.
Definition query.h:1767
QueryImpl & set_var(util::str_view name, Entity value)
Binds a named query variable to a concrete entity value.
Definition query.h:4905
QueryImpl & group_by(TGroupByFunc func=group_by_func_default)
Organizes matching archetypes into groups according to the grouping function.
Definition query.h:5066
GAIA_NODISCARD util::str bytecode()
Returns a textual dump of the generated query VM bytecode.
Definition query.h:5698
QueryImpl & group_dep(Entity relation)
Declares an explicit relation dependency for grouped cache invalidation. Useful for custom group_by c...
Definition query.h:5076
QueryImpl & sort_by(TSortByFunc func)
Sorts the query by the specified component and function.
Definition query.h:4975
static void run_query_func(World *pWorld, Func func, std::span< ChunkBatch > batches)
Execute the functor in batches.
Definition query.h:1782
GAIA_NODISCARD uint32_t gen() const
Returns the cache handle generation of this query.
Definition query.h:4361
QueryImpl & kind(QueryCacheKind cacheKind)
Sets the hard cache-kind requirement for the query.
Definition query.h:879
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:828
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:2766
QueryImpl & set_var(const char *name, Entity value)
Binds a named query variable to a concrete entity value.
Definition query.h:4913
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:1579
QueryImpl & no(const QueryTermOptions &options)
Adds an excluded typed term.
Definition query.h:4854
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:2721
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:2617
QueryImpl & any()
Adds an optional typed term.
Definition query.h:4801
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:4891
GAIA_NODISCARD const char * kind_error_str()
Returns a human-readable description of the current kind validation result.
Definition query.h:934
GAIA_NODISCARD uint16_t cache_src_trav() const
Returns the traversed-source snapshot cap. 0 disables explicit traversed-source snapshot caching.
Definition query.h:872
GAIA_NODISCARD bool is_cached() const
Returns whether the query is stored in the query cache.
Definition query.h:4391
QueryImpl & all()
Adds a required typed term.
Definition query.h:4770
QueryImpl & add(const char *str,...)
Creates a query from a null-terminated expression string.
Definition query.h:4438
QueryImpl & is(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds a semantic Is(entity) requirement.
Definition query.h:4730
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:3140
QueryImpl & in(Entity entity, QueryTermOptions options=QueryTermOptions{})
Adds an inherited in(entity) requirement.
Definition query.h:4740
GAIA_NODISCARD OrderByWalkView walk(Entity relation)
Walks the relation graph in breadth-first levels for the given relation. Pair(relation,...
Definition query.h:5012
static GAIA_NODISCARD uint32_t count_direct_or_union(const World &world, const QueryInfo &queryInfo)
Counts the union of direct OR term entity sets while deduplicating entities across terms.
Definition query.h:3201
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)
Groups seeded entities by archetype and counts whole buckets when only structural ALL/NOT terms remai...
Definition query.h:3165
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:853
void diag_bytecode()
Prints a textual dump of the generated query VM bytecode.
Definition query.h:5704
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:4922
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:2605
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:2645
QueryImpl & depth_order(Entity relation=ChildOf)
Orders cached query entries by fragmenting relation depth so iteration runs breadth-first top-down....
Definition query.h:5024
QueryImpl & add(QueryInput item)
Adds a prebuilt query input item.
Definition query.h:4718
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:3481
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:2637
QueryImpl & any(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds an optional entity or pair term.
Definition query.h:4782
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:3347
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:4874
QueryImpl & group_dep()
Declares an explicit relation dependency for grouped cache invalidation. Useful for custom group_by c...
Definition query.h:5085
static GAIA_NODISCARD bool match_direct_entity_constraints(const World &world, const QueryInfo &queryInfo, Entity entity)
Applies iterator-specific entity state constraints to the direct seeded path.
Definition query.h:3127
void each_arch(Func func)
Iterates matching archetypes instead of individual entities.
Definition query.h:5150
QueryImpl & var_name(Entity varEntity, const char *name)
Assigns a human-readable name to a query variable entity (Var0..Var7).
Definition query.h:4880
void each(Func func, QueryExecType execType)
Iterates query matches using the selected execution mode.
Definition query.h:5128
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:2653
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:2627
QueryImpl & match_prefab()
Makes the query include prefab entities in matches.
Definition query.h:908
GAIA_NODISCARD uint32_t count_inter(const QueryInfo &queryInfo) const
Fast count() path for direct non-fragmenting queries that can seed from entity-backed indices.
Definition query.h:3593
void diag()
Run diagnostics.
Definition query.h:5681
QueryImpl & group_id(Entity entity)
Selects the group to iterate over.
Definition query.h:5101
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:3295
void for_each_direct_or_union(World &world, const QueryInfo &queryInfo, Func &&func)
Visits the deduplicated OR union for direct-seeded queries without materializing an entity seed array...
Definition query.h:3365
void match_all(QueryInfo &queryInfo)
Matches the query against all relevant archetypes.
Definition query.h:791
QueryImpl & no()
Adds an excluded typed term.
Definition query.h:4863
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:2749
GAIA_NODISCARD QueryCacheScope scope() const
Returns the currently requested cache scope.
Definition query.h:916
QueryImpl & changed(Entity entity)
Marks a runtime component or pair for changed() filtering.
Definition query.h:4943
uint32_t count(Constraints constraints=Constraints::EnabledOnly)
Calculates the number of entities matching the query.
Definition query.h:5228
void destroy()
Destroys the current cached query state and local scratch data.
Definition query.h:4380
QueryImpl & group_id()
Selects the group to iterate over.
Definition query.h:5110
bool empty(Constraints constraints=Constraints::EnabledOnly)
Returns true or false depending on whether there are any entities matching the query.
Definition query.h:5182
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:2663
void each_walk(Func func, Entity relation, Constraints constraints=Constraints::EnabledOnly)
Iterates entities matching the query ordered in dependency BFS levels. For relation R this treats Pai...
Definition query.h:5643
QueryImpl & all(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds a required entity or pair term.
Definition query.h:4751
QueryImpl & changed()
Marks a typed term for changed() filtering.
Definition query.h:4952
QueryImpl & clear_vars()
Clears all runtime variable bindings.
Definition query.h:4933
QueryImpl & no(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds an excluded entity or pair term.
Definition query.h:4844
GAIA_NODISCARD bool empty_inter(const QueryInfo &queryInfo) const
Fast empty() path for direct non-fragmenting queries that can seed from entity-backed indices.
Definition query.h:3397
void each_direct_inter(QueryInfo &queryInfo, Func func, core::func_type_list< T... >)
Runs a typed each() callback over directly seeded entities.
Definition query.h:4081
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:2893
QueryImpl & or_(const QueryTermOptions &options)
Adds an OR typed term.
Definition query.h:4824
QueryInfo & fetch()
Fetches the QueryInfo object. Creates or refreshes the backing QueryInfo if needed.
Definition query.h:737
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:2975
void each_direct_iter_inter(QueryInfo &queryInfo, Func func)
Runs an iterator-based each() callback over directly seeded entities using one-row chunk views.
Definition query.h:4025
void each(Func func)
Iterates query matches using the default execution mode.
Definition query.h:5120
static GAIA_NODISCARD bool is_empty_direct_or_union(const World &world, const QueryInfo &queryInfo)
Returns whether the direct OR union becomes empty after applying NOT terms and iterator constraints.
Definition query.h:3249
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:2834
GAIA_NODISCARD QueryKindRes kind_error()
Returns the validation result for the current query shape and requested kind.
Definition query.h:928
GAIA_NODISCARD bool valid()
Returns whether the current query shape satisfies the requested kind.
Definition query.h:940
QueryImpl & depth_order()
Orders cached query entries by fragmenting relation depth so iteration runs breadth-first top-down.
Definition query.h:5034
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:5047
GAIA_NODISCARD bool match_one(QueryInfo &queryInfo, const Archetype &archetype, EntitySpan targetEntities)
Matches the query against a single archetype.
Definition query.h:815
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:4814
GAIA_NODISCARD std::span< const Entity > ordered_entities_walk(QueryInfo &queryInfo, Entity relation, Constraints constraints=Constraints::EnabledOnly)
Builds and caches BFS walk order for the current query result.
Definition query.h:5322
QueryImpl & group_id(GroupId groupId)
Selects the group to iterate over.
Definition query.h:5094
GAIA_NODISCARD QueryCacheKind kind() const
Returns the currently requested cache kind.
Definition query.h:922
QueryImpl & any(const QueryTermOptions &options)
Adds an optional typed term.
Definition query.h:4792
GAIA_NODISCARD QueryCachePolicy cache_policy()
Returns the effective cache policy chosen for the query.
Definition query.h:843
GAIA_NODISCARD QueryId id() const
Returns the cache handle id of this query.
Definition query.h:4353
Wrapper for two Entities forming a relationship pair.
Definition id.h:500
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:218
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
Definition robin_hood.h:720
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
Definition utility.h:445
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:84
Definition entity_container.h:59
uint16_t row
Row at which the entity is stored in the chunk.
Definition entity_container.h:93
Archetype * pArchetype
Archetype (stable address)
Definition entity_container.h:111
Chunk * pChunk
Chunk the entity currently resides in (stable address)
Definition entity_container.h:113
Definition id.h:241
uint16_t readWriteMask
Read-write mask. Bit 0 stands for component 0 in component arrays. A set bit means write access is re...
Definition query_common.h:653
Entity sortBy
Entity to sort the archetypes by. EntityBad for no sorting.
Definition query_common.h:628
Entity groupBy
Entity to group the archetypes by. EntityBad for no grouping.
Definition query_common.h:632
Dependencies deps
Explicit dependency metadata derived from query shape.
Definition query_common.h:663
uint8_t flags
Query flags.
Definition query_common.h:655
CachePolicy cachePolicy
Cache maintenance policy derived from query shape.
Definition query_common.h:665
Definition query_common.h:471
ComponentCache * cc
Component cache.
Definition query_common.h:475
Definition query_common.h:457
QueryHandle handle
Query id.
Definition query_common.h:459
QueryId serId
Serialization id.
Definition query_common.h:461
User-provided query input.
Definition query_common.h:222
Entity entSrc
Source entity to query the id on. If id==EntityBad the source is fixed. If id!=src the source is vari...
Definition query_common.h:234
QueryTravKind travKind
Source traversal filter. Self means checking the source itself, Up means checking traversed ancestors...
Definition query_common.h:241
Entity id
Entity/Component/Pair to query.
Definition query_common.h:230
QueryAccess access
Access type.
Definition query_common.h:228
Entity entTrav
Optional traversal relation for source lookups. When set, the lookup starts at src and then walks rel...
Definition query_common.h:237
QueryOpKind op
Operation to perform with the input.
Definition query_common.h:226
QueryMatchKind matchKind
Match semantics for terms with special meaning, such as Pair(Is, X).
Definition query_common.h:246
uint8_t travDepth
Maximum number of traversal steps. 0 means unlimited traversal depth (bounded internally,...
Definition query_common.h:244
Additional options for query terms. This can be used to configure source lookup, traversal and access...
Definition query_common.h:252
Internal representation of QueryInput.
Definition query_common.h:363
Entity id
Queried id.
Definition query_common.h:365
QueryMatchKind matchKind
Match semantics for this term.
Definition query_common.h:375
Entity entTrav
Optional traversal relation for source lookups.
Definition query_common.h:369
Entity src
Source of where the queried id is looked up at.
Definition query_common.h:367
Marker type used by tests to request World::uquery().
Definition query.h:5720
void init_owned_query_info(QueryInfo &&queryInfo)
Stores a locally-owned QueryInfo, replacing the old one if present.
Definition query.h:437
QueryIdentity m_identity
Query identity.
Definition query.h:255
void allow_to_destroy_again()
Allows this storage object to destroy cache-backed state again after a move/copy handoff.
Definition query.h:381
GAIA_NODISCARD QueryInfo & owned_query_info()
Returns the locally-owned QueryInfo.
Definition query.h:430
void invalidate()
Invalidates the query handle.
Definition query.h:399
GAIA_NODISCARD QueryInfo * try_query_info_fast() const
Returns the cached QueryInfo pointer when the fast-path cache is still valid.
Definition query.h:408
QueryInfo * m_pOwnedInfo
Locally-owned query plan used when the query does not use cache-backed storage.
Definition query.h:253
GAIA_NODISCARD QuerySerBuffer & ser_buffer()
Returns the serialized command buffer for this query.
Definition query.h:354
GAIA_NODISCARD bool is_cached() const
Returns whether the query is found in the query cache.
Definition query.h:446
QueryInfo * m_pInfo
Hot cached query pointer. Validated against m_identity.handle before use.
Definition query.h:251
GAIA_NODISCARD bool is_initialized() const
Returns whether the query is ready to be used.
Definition query.h:455
GAIA_NODISCARD bool has_owned_query_info() const
Returns whether storage owns a local QueryInfo instance.
Definition query.h:424
void ser_buffer_reset()
Releases the serialized command buffer for this query.
Definition query.h:359
void cache_query_info(QueryInfo &queryInfo)
Caches the hot QueryInfo pointer locally.
Definition query.h:418
QueryCache * m_pCache
QueryImpl cache (stable pointer to parent world's query cache)
Definition query.h:249
GAIA_NODISCARD bool try_del_from_cache()
Tries to delete the query from the query cache.
Definition query.h:387
void reset()
Releases any data allocated by the query.
Definition query.h:373
void init(World *world, QueryCache *queryCache)
Initializes storage against a world and query cache.
Definition query.h:366
GAIA_NODISCARD World * world()
Returns the world associated with this query storage.
Definition query.h:348
bool alwaysMatch
No remaining terms after consuming the seed.
Definition query.h:2830
const QueryTerm * pSingleAllTerm
Fast path for the common ALL + direct/semantic Is shape: after consuming the seed term,...
Definition query.h:2828
Describes which direct term should seed direct non-fragmenting evaluation.
Definition query.h:2816
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