Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
query.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cstdarg>
5#include <cstdint>
6#include <type_traits>
7
8#include "gaia/cnt/darray.h"
9#include "gaia/cnt/map.h"
10#include "gaia/cnt/sarray_ext.h"
11#include "gaia/config/profiler.h"
12#include "gaia/core/hashing_policy.h"
13#include "gaia/core/utility.h"
14#include "gaia/ecs/api.h"
15#include "gaia/ecs/archetype.h"
16#include "gaia/ecs/archetype_common.h"
17#include "gaia/ecs/chunk.h"
18#include "gaia/ecs/chunk_iterator.h"
19#include "gaia/ecs/common.h"
20#include "gaia/ecs/component.h"
21#include "gaia/ecs/component_cache.h"
22#include "gaia/ecs/id.h"
23#include "gaia/ecs/query_cache.h"
24#include "gaia/ecs/query_common.h"
25#include "gaia/ecs/query_info.h"
26#include "gaia/ecs/sched.h"
27#include "gaia/mem/smallblock_allocator.h"
28#include "gaia/ser/ser_buffer_binary.h"
29#include "gaia/ser/ser_ct.h"
30#include "gaia/util/str.h"
31
32namespace gaia {
33 namespace ecs {
34 class World;
35 void world_finish_write(World& world, Entity term, Entity entity);
36 const Sched& world_sched(const World& world);
37
39 inline static constexpr uint16_t MaxCacheSrcTrav = 32;
40
42 enum class QueryExecType : uint32_t {
44 Serial,
46 Parallel,
48 ParallelPerf,
50 ParallelEff,
52 Default = Serial,
53 };
54
56 enum class QueryCacheKind : uint8_t {
59 None,
67 Default,
75 Auto,
81 All
82 };
83
85 enum class QueryCacheScope : uint8_t {
87 Local,
89 Shared
90 };
91
93 enum class QueryKindRes : uint8_t {
95 OK,
97 AutoSrcTrav,
99 AllNotIm,
101 AllSrcTrav
102 };
103
108 enum class TravOrder : uint8_t {
110 Up,
112 Postorder = Up,
114 Down,
116 Preorder = Down,
118 ReverseUp,
120 ReversePostorder = ReverseUp,
122 ReverseDown,
124 ReversePreorder = ReverseDown
125 };
126
128 using QueryCachePolicy = QueryCtx::CachePolicy;
129 struct TypedQueryExecState;
130
131 namespace detail {
133 template <typename Func>
134 inline constexpr bool is_query_iter_callback_v = std::is_invocable_v<Func, Iter&>;
135
136 template <typename Func>
137 inline constexpr bool is_query_walk_core_callback_v =
138 is_query_iter_callback_v<Func> || std::is_invocable_v<Func, const Entity&> ||
139 std::is_invocable_v<Func, Entity>;
140
142 enum QueryCmdType : uint8_t { ADD_ITEM, ADD_FILTER, SORT_BY, GROUP_BY, GROUP_DEP, MATCH_PREFAB };
143
144 struct QueryCmd_AddItem {
145 static constexpr QueryCmdType Id = QueryCmdType::ADD_ITEM;
146 static constexpr bool InvalidatesHash = true;
147
148 QueryInput item;
149
150 void exec(QueryCtx& ctx) const {
151 auto& ctxData = ctx.data;
152
153#if GAIA_DEBUG
154 // Unique component ids only
155 GAIA_ASSERT(!core::has(ctxData.ids_view(), item.id));
156
157 // There's a limit to the amount of query items which we can store
158 if (ctxData.idsCnt >= MAX_ITEMS_IN_QUERY) {
159 GAIA_ASSERT2(false, "Trying to create a query with too many components!");
160
161 const auto name = ctx.cc->get(item.id).symbol_name();
162 GAIA_LOG_E("Trying to add component '%.*s' to an already full ECS query!", (int)name.size(), name.data());
163 return;
164 }
165#endif
166
167 // Build the read-write mask.
168 // This will be used to determine what kind of access the user wants for a given component.
169 const uint16_t isReadWrite = uint16_t(item.access == QueryAccess::Write);
170 ctxData.readWriteMask |= (isReadWrite << ctxData.idsCnt);
171
172 ctxData.ids[ctxData.idsCnt] = item.id;
173 ctxData.terms[ctxData.idsCnt] = {item.id, item.entSrc, item.entTrav,
174 item.travKind, item.travDepth, item.matchKind,
175 nullptr, item.op, (uint8_t)ctxData.idsCnt};
176 ++ctxData.idsCnt;
177 }
178 };
179
180 struct QueryCmd_AddFilter {
181 static constexpr QueryCmdType Id = QueryCmdType::ADD_FILTER;
182 static constexpr bool InvalidatesHash = true;
183
184 Entity comp;
185
186 void exec(QueryCtx& ctx) const {
187 auto& ctxData = ctx.data;
188
189#if GAIA_DEBUG
190 GAIA_ASSERT(core::has(ctxData.ids_view(), comp));
191 GAIA_ASSERT(!core::has(ctxData.changed_view(), comp));
192
193 // There's a limit to the amount of components which we can store
194 if (ctxData.changedCnt >= MAX_ITEMS_IN_QUERY) {
195 GAIA_ASSERT2(false, "Trying to create an filter query with too many components!");
196
197 const auto compName = ctx.cc->get(comp).symbol_name();
198 GAIA_LOG_E(
199 "Trying to add component %.*s to an already full filter query!", (int)compName.size(), compName.data());
200 return;
201 }
202
203 uint32_t compIdx = 0;
204 for (; compIdx < ctxData.idsCnt; ++compIdx)
205 if (ctxData.ids[compIdx] == comp)
206 break;
207
208 // NOTE: Code bellow does the same as this commented piece.
209 // However, compilers can't quite optimize it as well because it does some more
210 // calculations. This is used often so go with the custom code.
211 // const auto compIdx = core::get_index_unsafe(ids, comp);
212
213 // Component has to be present in all/or lists.
214 // Filtering by NOT/ANY doesn't make sense because those are not hard requirements.
215 GAIA_ASSERT2(
216 ctxData.terms[compIdx].op != QueryOpKind::Not && ctxData.terms[compIdx].op != QueryOpKind::Any,
217 "Filtering by NOT/ANY doesn't make sense!");
218 if (ctxData.terms[compIdx].op != QueryOpKind::Not && ctxData.terms[compIdx].op != QueryOpKind::Any) {
219 ctxData.changed[ctxData.changedCnt++] = comp;
220 return;
221 }
222
223 const auto compName = ctx.cc->get(comp).symbol_name();
224 GAIA_LOG_E(
225 "SetChangeFilter trying to filter component %.*s but it's not a part of the query!", (int)compName.size(),
226 compName.data());
227#else
228 ctxData.changed[ctxData.changedCnt++] = comp;
229#endif
230 }
231 };
232
233 struct QueryCmd_SortBy {
234 static constexpr QueryCmdType Id = QueryCmdType::SORT_BY;
235 static constexpr bool InvalidatesHash = true;
236
237 Entity sortBy;
238 TSortByFunc func;
239
240 void exec(QueryCtx& ctx) const {
241 auto& ctxData = ctx.data;
242 ctxData.sortBy = sortBy;
243 GAIA_ASSERT(func != nullptr);
244 ctxData.sortByFunc = func;
245 }
246 };
247
248 struct QueryCmd_GroupBy {
249 static constexpr QueryCmdType Id = QueryCmdType::GROUP_BY;
250 static constexpr bool InvalidatesHash = true;
251
252 Entity groupBy;
253 TGroupByFunc func;
254 uint16_t flags;
255
256 void exec(QueryCtx& ctx) const {
257 auto& ctxData = ctx.data;
258 ctxData.groupBy = groupBy;
259 GAIA_ASSERT(func != nullptr);
260 ctxData.groupByFunc = func; // group_by_func_default;
261 if ((flags & QueryCtx::QueryFlags::OrderGroups) != 0)
262 ctxData.flags |= QueryCtx::QueryFlags::OrderGroups;
263 else
264 ctxData.flags &= ~QueryCtx::QueryFlags::OrderGroups;
265 }
266 };
267
268 struct QueryCmd_GroupDep {
269 static constexpr QueryCmdType Id = QueryCmdType::GROUP_DEP;
270 static constexpr bool InvalidatesHash = true;
271
272 Entity relation;
273
274 void exec(QueryCtx& ctx) const {
275 auto& ctxData = ctx.data;
276 GAIA_ASSERT(!relation.pair());
277 ctxData.add_group_dep(relation);
278 }
279 };
280
281 struct QueryCmd_MatchPrefab {
282 static constexpr QueryCmdType Id = QueryCmdType::MATCH_PREFAB;
283 static constexpr bool InvalidatesHash = true;
284
285 void exec(QueryCtx& ctx) const {
286 ctx.data.flags |= QueryCtx::QueryFlags::MatchPrefab;
287 }
288 };
289
290 struct QueryImplStorage {
291 World* m_world = nullptr;
293 QueryCache* m_pCache = nullptr;
295 QueryInfo* m_pInfo = nullptr;
297 QueryInfo* m_pOwnedInfo = nullptr;
299 QueryIdentity m_identity{};
300 bool m_destroyed = false;
301
302 public:
303 QueryImplStorage() = default;
304 ~QueryImplStorage() {
305 (void)try_del_from_cache();
306 delete m_pOwnedInfo;
307 }
308
309 QueryImplStorage(QueryImplStorage&& other) {
310 m_world = other.m_world;
311 m_pCache = other.m_pCache;
312 m_pInfo = other.m_pInfo;
313 m_pOwnedInfo = other.m_pOwnedInfo;
314 m_identity = other.m_identity;
315 m_destroyed = other.m_destroyed;
316
317 // Make sure old instance is invalidated
318 other.m_pInfo = nullptr;
319 other.m_pOwnedInfo = nullptr;
320 other.m_identity = {};
321 other.m_destroyed = false;
322 }
323 QueryImplStorage& operator=(QueryImplStorage&& other) {
324 GAIA_ASSERT(core::addressof(other) != this);
325
326 (void)try_del_from_cache();
327 delete m_pOwnedInfo;
328
329 m_world = other.m_world;
330 m_pCache = other.m_pCache;
331 m_pInfo = other.m_pInfo;
332 m_pOwnedInfo = other.m_pOwnedInfo;
333 m_identity = other.m_identity;
334 m_destroyed = other.m_destroyed;
335
336 // Make sure old instance is invalidated
337 other.m_pInfo = nullptr;
338 other.m_pOwnedInfo = nullptr;
339 other.m_identity = {};
340 other.m_destroyed = false;
341
342 return *this;
343 }
344
345 QueryImplStorage(const QueryImplStorage& other) {
346 m_world = other.m_world;
347 m_pCache = other.m_pCache;
348 m_pInfo = other.m_pInfo;
349 if (other.m_pOwnedInfo != nullptr)
350 m_pOwnedInfo = new QueryInfo(*other.m_pOwnedInfo);
351 m_identity = other.m_identity;
352 m_destroyed = other.m_destroyed;
353
354 // Make sure to update the ref count of the cached query so
355 // it doesn't get deleted by accident.
356 if (!m_destroyed && m_pCache != nullptr) {
357 auto* pInfo = try_query_info_fast();
358 if (pInfo == nullptr)
359 pInfo = m_pCache->try_get(m_identity.handle);
360 if (pInfo != nullptr)
361 pInfo->add_ref();
362 }
363 }
364 QueryImplStorage& operator=(const QueryImplStorage& other) {
365 GAIA_ASSERT(core::addressof(other) != this);
366
367 (void)try_del_from_cache();
368 delete m_pOwnedInfo;
369 m_pOwnedInfo = nullptr;
370
371 m_world = other.m_world;
372 m_pCache = other.m_pCache;
373 m_pInfo = other.m_pInfo;
374 if (other.m_pOwnedInfo != nullptr)
375 m_pOwnedInfo = new QueryInfo(*other.m_pOwnedInfo);
376 m_identity = other.m_identity;
377 m_destroyed = other.m_destroyed;
378
379 // Make sure to update the ref count of the cached query so
380 // it doesn't get deleted by accident.
381 if (!m_destroyed && m_pCache != nullptr) {
382 auto* pInfo = try_query_info_fast();
383 if (pInfo == nullptr)
384 pInfo = m_pCache->try_get(m_identity.handle);
385 if (pInfo != nullptr)
386 pInfo->add_ref();
387 }
388
389 return *this;
390 }
391
394 GAIA_NODISCARD World* world() {
395 return m_world;
396 }
397
400 GAIA_NODISCARD QuerySerBuffer& ser_buffer() {
401 return m_identity.ser_buffer(m_world);
402 }
403
405 void ser_buffer_reset() {
406 return m_identity.ser_buffer_reset(m_world);
407 }
408
412 void init(World* world, QueryCache* queryCache) {
413 m_world = world;
414 m_pCache = queryCache;
415 m_pInfo = nullptr;
416 }
417
419 void reset() {
420 if (auto* pInfo = try_query_info_fast(); pInfo != nullptr)
421 pInfo->reset();
422 if (m_pOwnedInfo != nullptr)
423 m_pOwnedInfo->reset();
424 }
425
427 void allow_to_destroy_again() {
428 m_destroyed = false;
429 }
430
433 GAIA_NODISCARD bool try_del_from_cache() {
434 if (!m_destroyed && m_identity.handle.id() != QueryIdBad)
435 m_pCache->del(m_identity.handle);
436
437 // Don't allow multiple calls to destroy to break the reference counter.
438 // One object is only allowed to destroy once.
439 m_pInfo = nullptr;
440 m_destroyed = true;
441 return false;
442 }
443
445 void invalidate() {
446 m_pInfo = nullptr;
447 m_identity.handle = {};
448 delete m_pOwnedInfo;
449 m_pOwnedInfo = nullptr;
450 }
451
454 GAIA_NODISCARD QueryInfo* try_query_info_fast() const {
455 if (m_pInfo == nullptr || m_identity.handle.id() == QueryIdBad || m_pCache == nullptr)
456 return nullptr;
457
458 auto* pInfo = m_pCache->try_get(m_identity.handle);
459 return pInfo == m_pInfo ? pInfo : nullptr;
460 }
461
464 void cache_query_info(QueryInfo& queryInfo) {
465 m_pInfo = &queryInfo;
466 }
467
470 GAIA_NODISCARD bool has_owned_query_info() const {
471 return m_pOwnedInfo != nullptr;
472 }
473
476 GAIA_NODISCARD QueryInfo& owned_query_info() {
477 GAIA_ASSERT(m_pOwnedInfo != nullptr);
478 return *m_pOwnedInfo;
479 }
480
483 void init_owned_query_info(QueryInfo&& queryInfo) {
484 if (m_pOwnedInfo == nullptr)
485 m_pOwnedInfo = new QueryInfo(GAIA_MOV(queryInfo));
486 else
487 *m_pOwnedInfo = GAIA_MOV(queryInfo);
488 }
489
492 GAIA_NODISCARD bool is_cached() const {
493 auto* pInfo = try_query_info_fast();
494 if (pInfo == nullptr)
495 pInfo = m_pCache->try_get(m_identity.handle);
496 return pInfo != nullptr;
497 }
498
501 GAIA_NODISCARD bool is_initialized() const {
502 return m_world != nullptr && m_pCache != nullptr;
503 }
504 };
507 class QueryImpl {
508 static constexpr uint32_t ChunkBatchSize = 32;
509 friend class SystemBuilder;
510
511 struct ChunkBatch {
512 const Archetype* pArchetype;
513 Chunk* pChunk;
514 const uint8_t* pCompIndices;
515 InheritedTermDataView inheritedData;
516 GroupId groupId;
517 uint16_t from;
518 uint16_t to;
519 };
520
521 using ChunkSpan = std::span<const Chunk*>;
522 using ChunkSpanMut = std::span<Chunk*>;
524 using CmdFunc = void (*)(QuerySerBuffer& buffer, QueryCtx& ctx);
525
526 struct DirectQueryScratch {
528 cnt::darray<Entity> entities;
529 cnt::darray<Entity> termEntities;
530 cnt::darray<Entity> bucketEntities;
532 uint32_t seenVersion = 1;
533 };
534
535 private:
536 GAIA_NODISCARD bool uses_query_cache_storage() const {
537 return m_cacheKind != QueryCacheKind::None;
538 }
539
540 GAIA_NODISCARD bool uses_shared_cache_layer() const {
541 return uses_query_cache_storage() && m_cacheScope == QueryCacheScope::Shared;
542 }
543
544 void invalidate_query_storage() {
545 if (uses_query_cache_storage())
546 (void)m_storage.try_del_from_cache();
547 m_storage.invalidate();
548 }
549
552 GAIA_NODISCARD static DirectQueryScratch& direct_query_scratch() {
553 static thread_local DirectQueryScratch scratch;
554 return scratch;
555 }
556
560 static void ensure_direct_query_count_capacity(DirectQueryScratch& scratch, uint32_t entityId) {
561 if (entityId < scratch.counts.size())
562 return;
563
564 const auto doubledSize = (uint32_t)scratch.counts.size() * 2U;
565 const auto minSize = doubledSize > 64U ? doubledSize : 64U;
566 const auto newSize = (entityId + 1U) > minSize ? (entityId + 1U) : minSize;
567 scratch.counts.resize(newSize, 0);
568 }
569
573 GAIA_NODISCARD static uint32_t next_direct_query_seen_version(DirectQueryScratch& scratch) {
574 update_version(scratch.seenVersion);
575 if (scratch.seenVersion == 0) {
576 scratch.seenVersion = 1;
577 core::fill(scratch.counts.begin(), scratch.counts.end(), 0);
578 }
579
580 return scratch.seenVersion;
581 }
582
583 static constexpr CmdFunc CommandBufferRead[] = {
584 // Add item
585 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
586 QueryCmd_AddItem cmd;
587 ser::load(buffer, cmd);
588 cmd.exec(ctx);
589 },
590 // Add filter
591 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
592 QueryCmd_AddFilter cmd;
593 ser::load(buffer, cmd);
594 cmd.exec(ctx);
595 },
596 // SortBy
597 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
598 QueryCmd_SortBy cmd;
599 ser::load(buffer, cmd);
600 cmd.exec(ctx);
601 },
602 // GroupBy
603 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
604 QueryCmd_GroupBy cmd;
605 ser::load(buffer, cmd);
606 cmd.exec(ctx);
607 },
608 // GroupDep
609 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
610 QueryCmd_GroupDep cmd;
611 ser::load(buffer, cmd);
612 cmd.exec(ctx);
613 },
614 // MatchPrefab
615 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
616 QueryCmd_MatchPrefab cmd;
617 ser::load(buffer, cmd);
618 cmd.exec(ctx);
619 } //
620 }; // namespace detail
621
623 QueryImplStorage m_storage;
625 ArchetypeId* m_nextArchetypeId{};
627 uint32_t* m_worldVersion{};
629 const EntityToArchetypeMap* m_entityToArchetypeMap{};
631 const EntityToArchetypeVersionMap* m_entityToArchetypeMapVersions{};
633 const ArchetypeDArray* m_allArchetypes{};
637 uint8_t m_varNamesMask = 0;
639 cnt::sarray<Entity, MaxVarCnt> m_varBindings;
641 uint8_t m_varBindingsMask = 0;
643 GroupId m_groupIdSet = 0;
645 uint32_t m_changedWorldVersion = 0;
648 cnt::darray<ChunkBatch> m_batches;
650 QueryCacheKind m_cacheKind = QueryCacheKind::Default;
652 QueryCacheScope m_cacheScope = QueryCacheScope::Local;
654 uint16_t m_cacheSrcTrav = 0;
656 void* m_ctx = nullptr;
658 bool m_mainThread = false;
660 QueryAccessSet m_access;
661
663 struct EachWalkData {
665 cnt::darray<Entity> cachedInput;
667 cnt::darray<Entity> cachedOutput;
671 Entity cachedRelation = EntityBad;
673 TravOrder cachedOrder = TravOrder::Down;
675 Constraints cachedConstraints = Constraints::EnabledOnly;
677 uint32_t cachedRelationVersion = 0;
679 uint32_t cachedEntityVersion = 0;
681 uint32_t cachedResultCacheRevision = 0;
683 cnt::darray<const Chunk*> cachedChunks;
685 bool cacheValid = false;
687 cnt::darray<Entity> scratchEntities;
689 cnt::darray<const Chunk*> scratchChunks;
691 cnt::darray<uint32_t> scratchIndegree;
693 cnt::darray<uint32_t> scratchOutdegree;
695 cnt::darray<uint32_t> scratchOffsets;
697 cnt::darray<uint32_t> scratchWriteCursor;
699 cnt::darray<uint32_t> scratchEdges;
701 cnt::darray<uint32_t> scratchCurrLevel;
703 cnt::darray<uint32_t> scratchNextLevel;
704 };
705
706 template <typename T>
707 struct OnDemandDataHolder {
708 T* pData = nullptr;
709
710 OnDemandDataHolder() = default;
711
712 ~OnDemandDataHolder() {
713 delete pData;
714 }
715
716 OnDemandDataHolder(const OnDemandDataHolder& other) {
717 if (other.pData != nullptr)
718 pData = new T(*other.pData);
719 }
720
721 OnDemandDataHolder& operator=(const OnDemandDataHolder& other) {
722 if (core::addressof(other) == this)
723 return *this;
724
725 if (other.pData == nullptr) {
726 delete pData;
727 pData = nullptr;
728 return *this;
729 }
730
731 if (pData == nullptr)
732 pData = new T(*other.pData);
733 else
734 *pData = *other.pData;
735
736 return *this;
737 }
738
739 OnDemandDataHolder(OnDemandDataHolder&& other) noexcept: pData(other.pData) {
740 other.pData = nullptr;
741 }
742
743 OnDemandDataHolder& operator=(OnDemandDataHolder&& other) noexcept {
744 if (core::addressof(other) == this)
745 return *this;
746
747 delete pData;
748 pData = other.pData;
749 other.pData = nullptr;
750 return *this;
751 }
752
753 GAIA_NODISCARD T* get() {
754 return pData;
755 }
756
757 GAIA_NODISCARD const T* get() const {
758 return pData;
759 }
760
761 GAIA_NODISCARD T& ensure() {
762 if (pData == nullptr)
763 pData = new T();
764 return *pData;
765 }
766
767 void reset() {
768 delete pData;
769 pData = nullptr;
770 }
771 };
772
774 OnDemandDataHolder<EachWalkData> m_eachWalkData;
775
777 struct DirectSeedRunData {
778 cnt::darray<Entity> cachedEntities;
779 cnt::darray<Entity> cachedChunkOrderedEntities;
781 Entity cachedSeedTerm = EntityBad;
782 QueryMatchKind cachedSeedMatchKind = QueryMatchKind::Semantic;
783 Constraints cachedConstraints = Constraints::EnabledOnly;
784 uint32_t cachedRelVersion = 0;
785 uint32_t cachedWorldVersion = 0;
786 bool cacheValid = false;
787 };
788
789 OnDemandDataHolder<DirectSeedRunData> m_directSeedRunData;
790
795 GAIA_NODISCARD static QueryAccess merge_access(QueryAccess lhs, QueryAccess rhs) {
796 if (lhs == QueryAccess::Write || rhs == QueryAccess::Write)
797 return QueryAccess::Write;
798 if (lhs == QueryAccess::Read || rhs == QueryAccess::Read)
799 return QueryAccess::Read;
800 return QueryAccess::None;
801 }
802
807 GAIA_NODISCARD static QueryAccess term_access(const QueryCtx::Data& data, Entity entity) {
808 if (entity == EntityBad || entity.pair())
809 return QueryAccess::None;
810
811 QueryAccess access = QueryAccess::None;
812 const auto terms = data.terms_view();
813 GAIA_FOR((uint32_t)terms.size()) {
814 const auto& term = terms[i];
815 if (term.id != entity || (term.op != QueryOpKind::All && term.op != QueryOpKind::Or))
816 continue;
817
818 if ((data.readWriteMask & (uint16_t(1) << i)) != 0)
819 return QueryAccess::Write;
820 access = QueryAccess::Read;
821 }
822
823 return access;
824 }
825
831 GAIA_NODISCARD static QueryAccess
832 effective_access(const QueryCtx::Data& data, const QueryAccessSet& accessSet, Entity entity) {
833 return merge_access(term_access(data, entity), accessSet.access(entity));
834 }
835
840 GAIA_NODISCARD static bool access_conflicts(QueryAccess lhs, QueryAccess rhs) {
841 return (lhs == QueryAccess::Write && rhs != QueryAccess::None) ||
842 (rhs == QueryAccess::Write && lhs != QueryAccess::None);
843 }
844
851 GAIA_NODISCARD static bool conflicts_one_way(
852 const QueryCtx::Data& leftData, const QueryAccessSet& leftAccess, const QueryCtx::Data& rightData,
853 const QueryAccessSet& rightAccess) {
854 const auto terms = leftData.terms_view();
855 GAIA_FOR((uint32_t)terms.size()) {
856 const auto id = terms[i].id;
857 const auto access = term_access(leftData, id);
858 if (access_conflicts(access, effective_access(rightData, rightAccess, id)))
859 return true;
860 }
861
862 for (const auto id: leftAccess.reads_view()) {
863 if (access_conflicts(QueryAccess::Read, effective_access(rightData, rightAccess, id)))
864 return true;
865 }
866 for (const auto id: leftAccess.writes_view()) {
867 if (access_conflicts(QueryAccess::Write, effective_access(rightData, rightAccess, id)))
868 return true;
869 }
870
871 return false;
872 }
873
877 template <typename T>
878 GAIA_NODISCARD Entity access_entity_inter() {
879 if constexpr (is_pair<T>::value) {
880 const auto& descRel = comp_cache_add<typename T::rel_type>(*m_storage.world());
881 const auto& descTgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
882 return Pair(descRel.entity, descTgt.entity);
883 } else {
884 using UO = typename component_type_t<T>::TypeOriginal;
885 static_assert(core::is_raw_v<UO>, "Use reads()/writes() with raw types only");
886 const auto& desc = comp_cache_add<T>(*m_storage.world());
887 return desc.entity;
888 }
889 }
890
891 //--------------------------------------------------------------------------------
892 public:
894 static inline bool SilenceInvalidCacheKindAssertions = false;
895
900 GAIA_PROF_SCOPE(query::fetch);
901
902 // Make sure the query was created by World::query()
903 GAIA_ASSERT(m_storage.is_initialized());
904
905 if (!uses_query_cache_storage()) {
906 if GAIA_UNLIKELY (!m_storage.has_owned_query_info()) {
908 ctx.init(m_storage.world());
909 commit(ctx);
910 m_storage.init_owned_query_info(
911 QueryInfo::create(QueryId{}, GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view()));
912 } else if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad) {
913 recommit(m_storage.owned_query_info().ctx());
914 }
915
916 return m_storage.owned_query_info();
917 }
918
919 // If queryId is set it means QueryInfo was already created.
920 // This is the common case for cached queries.
921 if GAIA_LIKELY (m_storage.m_identity.handle.id() != QueryIdBad) {
922 auto* pQueryInfo = m_storage.try_query_info_fast();
923 if GAIA_UNLIKELY (pQueryInfo == nullptr)
924 pQueryInfo = m_storage.m_pCache->try_get(m_storage.m_identity.handle);
925
926 // The only time when this can be nullptr is just once after Query::destroy is called.
927 if GAIA_LIKELY (pQueryInfo != nullptr) {
928 m_storage.cache_query_info(*pQueryInfo);
929 if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad)
930 recommit(pQueryInfo->ctx());
931 return *pQueryInfo;
932 }
933
934 m_storage.invalidate();
935 }
936
937 // No queryId is set which means QueryInfo needs to be created
939 ctx.init(m_storage.world());
940 commit(ctx);
941 auto& queryInfo =
942 uses_shared_cache_layer()
943 ? m_storage.m_pCache->add(GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view())
944 : m_storage.m_pCache->add_local(GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view());
945 m_storage.m_identity.handle = QueryInfo::handle(queryInfo);
946 m_storage.cache_query_info(queryInfo);
947 m_storage.allow_to_destroy_again();
948 return queryInfo;
949 }
950
953 void match_all(QueryInfo& queryInfo) {
954 const auto kindError = validate_kind(queryInfo.ctx());
955 if (kindError != QueryKindRes::OK) {
956 GAIA_ASSERT2(SilenceInvalidCacheKindAssertions, kind_error_str(kindError));
957 queryInfo.reset();
958 return;
959 }
960
961 if (!uses_query_cache_storage()) {
962 queryInfo.ensure_matches_transient(
963 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, m_varBindings,
964 m_varBindingsMask);
965 return;
966 }
967
968 queryInfo.ensure_matches(
969 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, last_archetype_id(),
970 m_varBindings, m_varBindingsMask);
971 m_storage.m_pCache->sync_archetype_cache(queryInfo);
972 }
973
979 GAIA_NODISCARD bool match_one(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
980 if (!uses_query_cache_storage()) {
981 return queryInfo.ensure_matches_one_transient(archetype, targetEntities, m_varBindings, m_varBindingsMask);
982 }
983
984 return queryInfo.ensure_matches_one(archetype, targetEntities, m_varBindings, m_varBindingsMask);
985 }
986
992 GAIA_NODISCARD bool matches_any(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
993 const auto kindError = validate_kind(queryInfo.ctx());
994 if (kindError != QueryKindRes::OK) {
995 GAIA_ASSERT2(SilenceInvalidCacheKindAssertions, kind_error_str(kindError));
996 queryInfo.reset();
997 return false;
998 }
999
1000 return matches_target_entities(queryInfo, archetype, targetEntities);
1001 }
1002
1003 //--------------------------------------------------------------------------------
1004
1008 return fetch().cache_policy();
1009 }
1010
1020 QueryImpl& cache_src_trav(uint16_t maxItems) {
1021 if (m_cacheSrcTrav == maxItems)
1022 return *this;
1023
1024 if (maxItems > MaxCacheSrcTrav) {
1025 GAIA_ASSERT(false && "cache_src_trav should be a value smaller than MaxCacheSrcTrav");
1026 maxItems = MaxCacheSrcTrav;
1027 }
1028
1029 invalidate_each_walk_cache();
1030 invalidate_direct_seed_run_cache();
1031 invalidate_query_storage();
1032 m_cacheSrcTrav = maxItems;
1033 return *this;
1034 }
1035
1039 GAIA_NODISCARD uint16_t cache_src_trav() const {
1040 return m_cacheSrcTrav;
1041 }
1042
1048
1056 QueryImpl& ctx(void* pCtx) {
1057 m_ctx = pCtx;
1058 return *this;
1059 }
1060
1063 GAIA_NODISCARD void* ctx() const {
1064 return m_ctx;
1065 }
1067
1071
1079 QueryImpl& main_thread(bool required = true) {
1080 m_mainThread = required;
1081 return *this;
1082 }
1083
1086 GAIA_NODISCARD bool main_thread_required() const {
1087 return m_mainThread;
1088 }
1090
1094
1103 m_access.add_read(entity);
1104 return *this;
1105 }
1106
1111 template <typename T>
1113 return reads(access_entity_inter<T>());
1114 }
1115
1124 m_access.add_write(entity);
1125 return *this;
1126 }
1127
1132 template <typename T>
1134 return writes(access_entity_inter<T>());
1135 }
1136
1139 GAIA_NODISCARD std::span<const Entity> custom_reads() const {
1140 return m_access.reads_view();
1141 }
1142
1145 GAIA_NODISCARD std::span<const Entity> custom_writes() const {
1146 return m_access.writes_view();
1147 }
1148
1156 GAIA_NODISCARD QueryAccess access(Entity entity) {
1157 return effective_access(fetch().ctx().data, m_access, entity);
1158 }
1159
1167 GAIA_NODISCARD bool conflicts_with(QueryImpl& other) {
1168 const auto& leftData = fetch().ctx().data;
1169 const auto& rightData = other.fetch().ctx().data;
1170 return conflicts_one_way(leftData, m_access, rightData, other.m_access) ||
1171 conflicts_one_way(rightData, other.m_access, leftData, m_access);
1172 }
1173
1177 GAIA_NODISCARD bool can_run_parallel(QueryImpl& other) {
1178 return !m_mainThread && !other.m_mainThread && !conflicts_with(other);
1179 }
1181
1185 QueryImpl& kind(QueryCacheKind cacheKind) {
1186 if (m_cacheKind == cacheKind)
1187 return *this;
1188
1189 invalidate_each_walk_cache();
1190 invalidate_direct_seed_run_cache();
1191 invalidate_query_storage();
1192 m_cacheKind = cacheKind;
1193
1194 return *this;
1195 }
1196
1200 QueryImpl& scope(QueryCacheScope cacheScope) {
1201 if (m_cacheScope == cacheScope)
1202 return *this;
1203
1204 invalidate_each_walk_cache();
1205 invalidate_direct_seed_run_cache();
1206 invalidate_query_storage();
1207 m_cacheScope = cacheScope;
1208
1209 return *this;
1210 }
1211
1215 QueryCmd_MatchPrefab cmd{};
1216 add_cmd(cmd);
1217 return *this;
1218 }
1219
1222 GAIA_NODISCARD QueryCacheScope scope() const {
1223 return m_cacheScope;
1224 }
1225
1228 GAIA_NODISCARD QueryCacheKind kind() const {
1229 return m_cacheKind;
1230 }
1231
1234 GAIA_NODISCARD QueryKindRes kind_error() {
1235 return validate_kind(fetch().ctx());
1236 }
1237
1240 GAIA_NODISCARD const char* kind_error_str() {
1241 return kind_error_str(kind_error());
1242 }
1243
1246 GAIA_NODISCARD bool valid() {
1247 return kind_error() == QueryKindRes::OK;
1248 }
1249
1250 //--------------------------------------------------------------------------------
1251 private:
1255 GAIA_NODISCARD bool uses_manual_src_trav_cache(const QueryCtx& ctx) const {
1256 return m_cacheSrcTrav != 0 && //
1257 ctx.data.deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms) && //
1258 ctx.data.deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms);
1259 }
1260
1264 GAIA_NODISCARD static bool uses_im_cache(const QueryCtx& ctx) {
1265 return ctx.data.cachePolicy == QueryCachePolicy::Immediate;
1266 }
1267
1271 GAIA_NODISCARD static bool uses_lazy_cache(const QueryCtx& ctx) {
1272 return ctx.data.cachePolicy == QueryCachePolicy::Lazy;
1273 }
1274
1278 GAIA_NODISCARD static bool uses_dyn_cache(const QueryCtx& ctx) {
1279 return ctx.data.cachePolicy == QueryCachePolicy::Dynamic;
1280 }
1281
1285 GAIA_NODISCARD QueryKindRes validate_kind(const QueryCtx& ctx) const {
1286 if (m_cacheKind == QueryCacheKind::Auto) {
1287 if (uses_manual_src_trav_cache(ctx))
1288 return QueryKindRes::AutoSrcTrav;
1289 }
1290
1291 if (m_cacheKind == QueryCacheKind::All) {
1292 if (uses_manual_src_trav_cache(ctx))
1293 return QueryKindRes::AllSrcTrav;
1294 if (!uses_im_cache(ctx))
1295 return QueryKindRes::AllNotIm;
1296 }
1297
1298 return QueryKindRes::OK;
1299 }
1300
1304 GAIA_NODISCARD static const char* kind_error_str(QueryKindRes error) {
1305 switch (error) {
1306 case QueryKindRes::OK:
1307 return "OK";
1308 case QueryKindRes::AutoSrcTrav:
1309 return "QueryCacheKind::Auto rejects explicit traversed-source snapshot caching";
1310 case QueryKindRes::AllNotIm:
1311 return "QueryCacheKind::All requires a fully immediate structural cache";
1312 case QueryKindRes::AllSrcTrav:
1313 return "QueryCacheKind::All rejects explicit traversed-source snapshot caching";
1314 default:
1315 return "Unknown query kind validation error";
1316 }
1317 }
1318
1321 GAIA_NODISCARD EachWalkData* each_walk_data() {
1322 return m_eachWalkData.get();
1323 }
1324
1327 GAIA_NODISCARD const EachWalkData* each_walk_data() const {
1328 return m_eachWalkData.get();
1329 }
1330
1333 GAIA_NODISCARD EachWalkData& ensure_each_walk_data() {
1334 return m_eachWalkData.ensure();
1335 }
1336
1338 void invalidate_each_walk_cache() {
1339 auto* pWalkData = each_walk_data();
1340 if (pWalkData != nullptr)
1341 pWalkData->cacheValid = false;
1342 }
1343
1346 GAIA_NODISCARD DirectSeedRunData* direct_seed_run_data() {
1347 return m_directSeedRunData.get();
1348 }
1349
1352 GAIA_NODISCARD const DirectSeedRunData* direct_seed_run_data() const {
1353 return m_directSeedRunData.get();
1354 }
1355
1358 GAIA_NODISCARD DirectSeedRunData& ensure_direct_seed_run_data() {
1359 return m_directSeedRunData.ensure();
1360 }
1361
1363 void invalidate_direct_seed_run_cache() {
1364 auto* pRunData = direct_seed_run_data();
1365 if (pRunData != nullptr)
1366 pRunData->cacheValid = false;
1367 }
1368
1370 void reset_changed_filter_state() {
1371 m_changedWorldVersion = 0;
1372 }
1373
1376 ArchetypeId last_archetype_id() const {
1377 return *m_nextArchetypeId - 1;
1378 }
1379
1382 GAIA_NODISCARD std::span<const Archetype*> all_archetypes_view() const {
1383 GAIA_ASSERT(m_allArchetypes != nullptr);
1384 return {(const Archetype**)m_allArchetypes->data(), m_allArchetypes->size()};
1385 }
1386
1387 GAIA_NODISCARD static bool is_query_var_entity(Entity entity) {
1388 return is_variable((EntityId)entity.id());
1389 }
1390
1391 GAIA_NODISCARD static uint32_t query_var_idx(Entity entity) {
1392 GAIA_ASSERT(is_query_var_entity(entity));
1393 return (uint32_t)(entity.id() - Var0.id());
1394 }
1395
1396 GAIA_NODISCARD Entity query_var_entity(uint32_t idx) {
1397 GAIA_ASSERT(idx < 8);
1398 return entity_from_id((const World&)*m_storage.world(), (EntityId)(Var0.id() + idx));
1399 }
1400
1401 GAIA_NODISCARD static util::str_view normalize_var_name(util::str_view name) {
1402 auto trimmed = util::trim(name);
1403 if (trimmed.empty())
1404 return {};
1405
1406 if (trimmed.data()[0] == '$') {
1407 if (trimmed.size() == 1)
1408 return {};
1409 trimmed = util::str_view(trimmed.data() + 1, trimmed.size() - 1);
1410 }
1411
1412 return util::trim(trimmed);
1413 }
1414
1415 GAIA_NODISCARD static bool is_reserved_var_name(util::str_view varName) {
1416 return varName == "this";
1417 }
1418
1419 GAIA_NODISCARD Entity find_var_by_name(util::str_view rawName) {
1420 const auto varName = normalize_var_name(rawName);
1421 if (varName.empty() || is_reserved_var_name(varName))
1422 return EntityBad;
1423
1424 GAIA_FOR(8) {
1425 const auto bit = (uint8_t(1) << i);
1426 if ((m_varNamesMask & bit) == 0)
1427 continue;
1428 if (m_varNames[i] == varName)
1429 return query_var_entity(i);
1430 }
1431
1432 return EntityBad;
1433 }
1434
1435 bool set_var_name_internal(Entity varEntity, util::str_view rawName) {
1436 if (!is_query_var_entity(varEntity))
1437 return false;
1438
1439 const auto varName = normalize_var_name(rawName);
1440 if (varName.empty() || is_reserved_var_name(varName))
1441 return false;
1442
1443 const auto idx = query_var_idx(varEntity);
1444 const auto bit = (uint8_t(1) << idx);
1445
1446 GAIA_FOR(8) {
1447 if (i == idx)
1448 continue;
1449
1450 const auto otherBit = (uint8_t(1) << i);
1451 if ((m_varNamesMask & otherBit) == 0)
1452 continue;
1453 if (!(m_varNames[i] == varName))
1454 continue;
1455
1456 GAIA_ASSERT2(false, "Variable name is already assigned to a different query variable");
1457 return false;
1458 }
1459
1460 m_varNames[idx].assign(varName);
1461 m_varNamesMask |= bit;
1462 return true;
1463 }
1464
1465 template <typename T>
1466 void add_cmd(T& cmd) {
1467 invalidate_each_walk_cache();
1468
1469 // Make sure to invalidate if necessary.
1470 if constexpr (T::InvalidatesHash) {
1471 reset_changed_filter_state();
1472 m_storage.invalidate();
1473 }
1474
1475 auto& serBuffer = m_storage.ser_buffer();
1476 ser::save(serBuffer, T::Id);
1477 ser::save(serBuffer, T::InvalidatesHash);
1478 ser::save(serBuffer, cmd);
1479 }
1480
1481 void add_inter(QueryInput item) {
1482 // When excluding or using ANY terms make sure the access type is None.
1483 GAIA_ASSERT((item.op != QueryOpKind::Not && item.op != QueryOpKind::Any) || item.access == QueryAccess::None);
1484
1485 QueryCmd_AddItem cmd{item};
1486 add_cmd(cmd);
1487 }
1488
1489 GAIA_NODISCARD static QueryAccess normalize_access(QueryOpKind op, Entity entity, QueryAccess access) {
1490 if (op == QueryOpKind::Not || op == QueryOpKind::Any || entity.pair())
1491 return QueryAccess::None;
1492
1493 // Non-pair ALL/OR terms default to Read access when unspecified.
1494 if (access == QueryAccess::None)
1495 return QueryAccess::Read;
1496
1497 return access;
1498 }
1499
1500 void add_entity_term(QueryOpKind op, Entity entity, const QueryTermOptions& options) {
1501 const auto access = normalize_access(op, entity, options.access);
1502 add(
1503 {op, access, entity, options.entSrc, options.entTrav, options.travKind, options.travDepth,
1504 options.matchKind});
1505 }
1506
1507 template <typename T>
1508 void add_inter(QueryOpKind op) {
1509 Entity e;
1510
1511 if constexpr (is_pair<T>::value) {
1512 // Make sure the components are always registered
1513 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1514 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1515
1516 e = Pair(desc_rel.entity, desc_tgt.entity);
1517 } else {
1518 // Make sure the component is always registered
1519 const auto& desc = comp_cache_add<T>(*m_storage.world());
1520 e = desc.entity;
1521 }
1522
1523 // Determine the access type
1524 QueryAccess access = QueryAccess::None;
1525 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1526 constexpr auto isReadWrite = core::is_mut_v<T>;
1527 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1528 }
1529
1530 add_inter({op, access, e});
1531 }
1532
1533 template <typename T>
1534 void add_inter(QueryOpKind op, const QueryTermOptions& options) {
1535 Entity e;
1536
1537 if constexpr (is_pair<T>::value) {
1538 // Make sure the components are always registered
1539 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1540 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1541
1542 e = Pair(desc_rel.entity, desc_tgt.entity);
1543 } else {
1544 // Make sure the component is always registered
1545 const auto& desc = comp_cache_add<T>(*m_storage.world());
1546 e = desc.entity;
1547 }
1548
1549 QueryAccess access = QueryAccess::None;
1550 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1551 if (options.access != QueryAccess::None)
1552 access = options.access;
1553 else {
1554 constexpr auto isReadWrite = core::is_mut_v<T>;
1555 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1556 }
1557 }
1558
1559 add_inter(
1560 {op, normalize_access(op, e, access), e, options.entSrc, options.entTrav, options.travKind,
1561 options.travDepth, options.matchKind});
1562 }
1563
1564 template <typename Rel, typename Tgt>
1565 void add_inter(QueryOpKind op) {
1566 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1567 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1568 static_assert(core::is_raw_v<UO_Rel>, "Use add() with raw types only");
1569 static_assert(core::is_raw_v<UO_Tgt>, "Use add() with raw types only");
1570
1571 // Make sure the component is always registered
1572 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1573 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1574
1575 // Determine the access type
1576 QueryAccess access = QueryAccess::None;
1577 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1578 constexpr auto isReadWrite = core::is_mut_v<UO_Rel> || core::is_mut_v<UO_Tgt>;
1579 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1580 }
1581
1582 add_inter({op, access, {descRel.entity, descTgt.entity}});
1583 }
1584
1585 //--------------------------------------------------------------------------------
1586
1587 void changed_inter(Entity entity) {
1588 QueryCmd_AddFilter cmd{entity};
1589 add_cmd(cmd);
1590 }
1591
1592 template <typename T>
1593 void changed_inter() {
1594 using UO = typename component_type_t<T>::TypeOriginal;
1595 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1596
1597 // Make sure the component is always registered
1598 const auto& desc = comp_cache_add<T>(*m_storage.world());
1599 changed_inter(desc.entity);
1600 }
1601
1602 template <typename Rel, typename Tgt>
1603 void changed_inter() {
1604 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1605 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1606 static_assert(core::is_raw_v<UO_Rel>, "Use changed() with raw types only");
1607 static_assert(core::is_raw_v<UO_Tgt>, "Use changed() with raw types only");
1608
1609 // Make sure the component is always registered
1610 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1611 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1612 changed_inter({descRel.entity, descTgt.entity});
1613 }
1614
1615 //--------------------------------------------------------------------------------
1616
1617 void sort_by_inter(Entity entity, TSortByFunc func) {
1618 QueryCmd_SortBy cmd{entity, func};
1619 add_cmd(cmd);
1620 }
1621
1622 template <typename T>
1623 void sort_by_inter(TSortByFunc func) {
1624 using UO = typename component_type_t<T>::TypeOriginal;
1625 if constexpr (std::is_same_v<UO, Entity>) {
1626 sort_by_inter(EntityBad, func);
1627 } else {
1628 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1629
1630 // Make sure the component is always registered
1631 const auto& desc = comp_cache_add<T>(*m_storage.world());
1632
1633 sort_by_inter(desc.entity, func);
1634 }
1635 }
1636
1637 template <typename Rel, typename Tgt>
1638 void sort_by_inter(TSortByFunc func) {
1639 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1640 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1641 static_assert(core::is_raw_v<UO_Rel>, "Use group_by() with raw types only");
1642 static_assert(core::is_raw_v<UO_Tgt>, "Use group_by() with raw types only");
1643
1644 // Make sure the component is always registered
1645 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1646 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1647
1648 sort_by_inter({descRel.entity, descTgt.entity}, func);
1649 }
1650
1651 //--------------------------------------------------------------------------------
1652
1653 void group_by_inter(Entity entity, TGroupByFunc func, bool orderGroups = false) {
1654 QueryCmd_GroupBy cmd{entity, func, orderGroups ? (uint16_t)QueryCtx::QueryFlags::OrderGroups : (uint16_t)0};
1655 add_cmd(cmd);
1656 }
1657
1658 template <typename T>
1659 void group_by_inter(Entity entity, TGroupByFunc func) {
1660 using UO = typename component_type_t<T>::TypeOriginal;
1661 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1662
1663 group_by_inter(entity, func);
1664 }
1665
1666 template <typename Rel, typename Tgt>
1667 void group_by_inter(TGroupByFunc func) {
1668 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1669 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1670 static_assert(core::is_raw_v<UO_Rel>, "Use group_by() with raw types only");
1671 static_assert(core::is_raw_v<UO_Tgt>, "Use group_by() with raw types only");
1672
1673 // Make sure the component is always registered
1674 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1675 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1676
1677 group_by_inter({descRel.entity, descTgt.entity}, func);
1678 }
1679
1680 //--------------------------------------------------------------------------------
1681
1682 void group_dep_inter(Entity relation) {
1683 GAIA_ASSERT(!relation.pair());
1684 QueryCmd_GroupDep cmd{relation};
1685 add_cmd(cmd);
1686 }
1687
1688 template <typename T>
1689 void group_dep_inter() {
1690 using UO = typename component_type_t<T>::TypeOriginal;
1691 static_assert(core::is_raw_v<UO>, "Use group_dep() with raw types only");
1692
1693 const auto& desc = comp_cache_add<T>(*m_storage.world());
1694 group_dep_inter(desc.entity);
1695 }
1696
1697 //--------------------------------------------------------------------------------
1698
1699 void set_group_id_inter(GroupId groupId) {
1700 // Dummy usage of GroupIdMax to avoid warning about unused constant
1701 (void)GroupIdMax;
1702
1703 invalidate_each_walk_cache();
1704 m_groupIdSet = groupId;
1705 }
1706
1707 void set_group_id_inter(Entity groupId) {
1708 set_group_id_inter(groupId.id());
1709 }
1710
1711 template <typename T>
1712 void set_group_id_inter() {
1713 using UO = typename component_type_t<T>::TypeOriginal;
1714 static_assert(core::is_raw_v<UO>, "Use group_id() with raw types only");
1715
1716 // Make sure the component is always registered
1717 const auto& desc = comp_cache_add<T>(*m_storage.world());
1718 set_group_id_inter(desc.entity);
1719 }
1720
1721 //--------------------------------------------------------------------------------
1722
1723 void commit(QueryCtx& ctx) {
1724 GAIA_PROF_SCOPE(query::commit);
1725
1726#if GAIA_ASSERT_ENABLED
1727 GAIA_ASSERT(m_storage.m_identity.handle.id() == QueryIdBad);
1728#endif
1729
1730 auto& serBuffer = m_storage.ser_buffer();
1731
1732 // Read data from buffer and execute the command stored in it
1733 serBuffer.seek(0);
1734 while (serBuffer.tell() < serBuffer.bytes()) {
1735 QueryCmdType id{};
1736 bool invalidatesHash = false;
1737 ser::load(serBuffer, id);
1738 ser::load(serBuffer, invalidatesHash);
1739 (void)invalidatesHash; // We don't care about this during commit
1740 CommandBufferRead[id](serBuffer, ctx);
1741 }
1742
1743 // Calculate the lookup hash from the provided context
1744 if (uses_query_cache_storage()) {
1745 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1746 normalize_cache_src_trav(ctx);
1747 }
1748 if (uses_shared_cache_layer()) {
1749 auto& ctxData = ctx.data;
1750 if (ctxData.changedCnt > 1) {
1751 core::sort(ctxData.changed.data(), ctxData.changed.data() + ctxData.changedCnt, SortComponentCond{});
1752 }
1753 }
1754
1755 // We can free all temporary data now
1756 m_storage.ser_buffer_reset();
1757
1758 // Refresh the context
1759 ctx.refresh();
1760 if (uses_shared_cache_layer())
1761 calc_lookup_hash(ctx);
1762 }
1763
1764 void recommit(QueryCtx& ctx) {
1765 GAIA_PROF_SCOPE(query::recommit);
1766
1767 auto& serBuffer = m_storage.ser_buffer();
1768
1769 // Read data from buffer and execute the command stored in it
1770 serBuffer.seek(0);
1771 while (serBuffer.tell() < serBuffer.bytes()) {
1772 QueryCmdType id{};
1773 bool invalidatesHash = false;
1774 ser::load(serBuffer, id);
1775 ser::load(serBuffer, invalidatesHash);
1776 // Hash recalculation is not accepted here
1777 GAIA_ASSERT(!invalidatesHash);
1778 if (invalidatesHash)
1779 return;
1780 CommandBufferRead[id](serBuffer, ctx);
1781 }
1782 if (uses_query_cache_storage()) {
1783 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1784 normalize_cache_src_trav(ctx);
1785 }
1786
1787 // We can free all temporary data now
1788 m_storage.ser_buffer_reset();
1789 }
1790
1791 //--------------------------------------------------------------------------------
1792 public:
1799 GAIA_NODISCARD static bool match_filters(
1800 const Chunk& chunk, const QueryInfo& queryInfo, uint32_t changedWorldVersion,
1801 std::span<const uint8_t> compIndices) {
1802 GAIA_ASSERT(!chunk.empty() && "match_filters called on an empty chunk");
1803
1804 const auto queryVersion = changedWorldVersion;
1805 const auto& data = queryInfo.ctx().data;
1807 return match_filters(chunk, queryInfo, changedWorldVersion);
1808
1809 const auto changedFields = data.changed_fields_view();
1810
1811 if (changedFields.empty())
1812 return false;
1813
1814 const auto changedCnt = (uint32_t)changedFields.size();
1815 if (changedCnt == 1) {
1816 const auto fieldIdx = changedFields[0];
1817 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1818 if (compIdx == (uint8_t)0xFF)
1819 return match_filters(chunk, queryInfo, changedWorldVersion);
1820 if (chunk.changed(queryVersion, compIdx))
1821 return true;
1822
1823 return chunk.entity_order_changed(changedWorldVersion);
1824 }
1825
1826 GAIA_FOR(changedCnt) {
1827 const auto fieldIdx = changedFields[i];
1828 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1829 if (compIdx == (uint8_t)0xFF)
1830 return match_filters(chunk, queryInfo, changedWorldVersion);
1831 if (chunk.changed(queryVersion, compIdx))
1832 return true;
1833 }
1834
1835 return chunk.entity_order_changed(changedWorldVersion);
1836 }
1837
1843 GAIA_NODISCARD static bool
1844 match_filters(const Chunk& chunk, const QueryInfo& queryInfo, uint32_t changedWorldVersion) {
1845 GAIA_ASSERT(!chunk.empty() && "match_filters called on an empty chunk");
1846
1847 const auto queryVersion = changedWorldVersion;
1848 const auto& filtered = queryInfo.ctx().data.changed_view();
1849
1850 // Skip unchanged chunks
1851 if (filtered.empty())
1852 return false;
1853
1854 const auto filteredCnt = (uint32_t)filtered.size();
1855 auto ids = chunk.ids_view();
1856
1857 // This is the hot path for most change-filter queries.
1858 if (filteredCnt == 1) {
1859 const auto compIdx = core::get_index(ids, filtered[0]);
1860 if (compIdx != BadIndex && chunk.changed(queryVersion, compIdx))
1861 return true;
1862
1863 return chunk.entity_order_changed(changedWorldVersion);
1864 }
1865
1866 // See if any component has changed
1867 uint32_t lastIdx = 0;
1868 for (const auto comp: filtered) {
1869 uint32_t compIdx = BadIndex;
1870 if (lastIdx < (uint32_t)ids.size()) {
1871 const auto suffixIdx =
1872 core::get_index(std::span<const Entity>(ids.data() + lastIdx, ids.size() - lastIdx), comp);
1873 if (suffixIdx != BadIndex)
1874 compIdx = lastIdx + suffixIdx;
1875 }
1876
1877 // Fallback for queries where change-filters are not monotonic in chunk column order
1878 // (e.g. OR-driven layouts).
1879 if (compIdx == BadIndex)
1880 compIdx = core::get_index(ids, comp);
1881 if (compIdx == BadIndex)
1882 continue;
1883
1884 if (chunk.changed(queryVersion, compIdx))
1885 return true;
1886
1887 lastIdx = compIdx;
1888 }
1889
1890 // If none of the tracked components changed, row movement can still make the
1891 // filtered query observable because newly added or moved entities must be seen.
1892 return chunk.entity_order_changed(changedWorldVersion);
1893 }
1894
1899 GAIA_NODISCARD bool can_process_archetype(const QueryInfo& queryInfo, const Archetype& archetype) const {
1900 // Archetypes requested for deletion are skipped for processing.
1901 if (archetype.is_req_del())
1902 return false;
1903
1904 // Prefabs are excluded from query results by default unless the query opted in
1905 // explicitly or it mentions Prefab directly.
1906 if (!queryInfo.matches_prefab_entities() && archetype.has(Prefab))
1907 return false;
1908
1909 return true;
1910 }
1911
1915 GAIA_NODISCARD static bool has_depth_order_hierarchy_enabled_barrier(const QueryInfo& queryInfo) {
1916 const auto& data = queryInfo.ctx().data;
1917 return data.groupByFunc == group_by_func_depth_order &&
1918 world_relation_depth_order_prunes_disabled_subtrees(*queryInfo.world(), data.groupBy);
1919 }
1920
1925 GAIA_NODISCARD static bool
1926 needs_depth_order_hierarchy_barrier_cache(const QueryInfo& queryInfo, Constraints constraints) {
1927 return constraints != Constraints::AcceptAll && has_depth_order_hierarchy_enabled_barrier(queryInfo);
1928 }
1929
1938 Chunk* pChunk, Constraints constraints, bool needsBarrierCache, bool barrierPasses, uint16_t& from,
1939 uint16_t& to) noexcept {
1940 if (needsBarrierCache && constraints == Constraints::DisabledOnly && !barrierPasses) {
1941 from = 0;
1942 to = pChunk->size();
1943 return;
1944 }
1945
1946 from = detail::ChunkIterImpl::start_index(pChunk, constraints);
1947 to = detail::ChunkIterImpl::end_index(pChunk, constraints);
1948 }
1949
1953 GAIA_NODISCARD static bool depth_order_hierarchy_barrier_prunes(const QueryInfo& queryInfo) {
1954 return has_depth_order_hierarchy_enabled_barrier(queryInfo) && queryInfo.barrier_may_prune();
1955 }
1956
1966 GAIA_NODISCARD static bool
1969 return true;
1970
1971 const auto& world = *queryInfo.world();
1972 const auto relation = queryInfo.ctx().data.groupBy;
1973 auto ids = archetype.ids_view();
1974
1975 for (auto idsIdx: archetype.pair_rel_indices(relation)) {
1976 const auto pair = ids[idsIdx];
1977 const auto parent = world_pair_target_if_alive(world, pair);
1978 if (parent == EntityBad)
1979 return false;
1980 if (!world_entity_enabled_hierarchy(world, parent, relation))
1981 return false;
1982 }
1983
1984 return true;
1985 }
1986
1993 GAIA_NODISCARD bool can_process_archetype_inter(
1994 const QueryInfo& queryInfo, const Archetype& archetype, Constraints constraints,
1995 int8_t barrierPasses = -1) const {
1996 if (!can_process_archetype(queryInfo, archetype))
1997 return false;
1998 if (constraints == Constraints::EnabledOnly) {
2000 if (barrierPasses >= 0)
2001 return barrierPasses != 0;
2002 if (!survives_cascade_hierarchy_enabled_barrier(queryInfo, archetype))
2003 return false;
2004 }
2005 }
2006 return true;
2007 }
2008
2016 class ParallelScope final {
2017 World* m_pWorld;
2018
2019 public:
2020 ParallelScope(World& world, uint32_t itemCount): m_pWorld(&world) {
2021 world_defer_parallel_begin(*m_pWorld, itemCount);
2022 }
2023 ~ParallelScope() {
2024 world_defer_parallel_end(*m_pWorld);
2025 }
2026
2027 ParallelScope(ParallelScope&&) = delete;
2028 ParallelScope(const ParallelScope&) = delete;
2029 ParallelScope& operator=(ParallelScope&&) = delete;
2030 ParallelScope& operator=(const ParallelScope&) = delete;
2031 };
2032
2038 class ParallelSlot final {
2039 DeferSlotScope m_scope;
2040
2041 public:
2042 explicit ParallelSlot(uint32_t idxStart): m_scope(idxStart) {}
2043
2044 ParallelSlot(ParallelSlot&&) = delete;
2045 ParallelSlot(const ParallelSlot&) = delete;
2046 ParallelSlot& operator=(ParallelSlot&&) = delete;
2047 ParallelSlot& operator=(const ParallelSlot&) = delete;
2048 };
2049
2051 template <typename TIter>
2052 static void finish_iter_writes(TIter& it) {
2053 if (it.chunk() == nullptr)
2054 return;
2055
2056 auto compIndices = it.touched_comp_indices();
2057 for (auto compIdx: compIndices)
2058 const_cast<Chunk*>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
2059
2060 auto terms = it.touched_terms();
2061 if (terms.empty())
2062 return;
2063
2064 auto entities = it.entity_rows();
2065 auto& world = *it.world();
2066 GAIA_EACH(terms) {
2067 const auto term = terms[i];
2068 if (!world_component_uses_sparse_storage(world, term)) {
2069 const auto compIdx = core::get_index(it.chunk()->ids_view(), term);
2070 if (compIdx != BadIndex) {
2071 const_cast<Chunk*>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
2072 continue;
2073 }
2074 }
2075
2076 GAIA_FOR_(entities.size(), j) {
2077 world_finish_write(world, term, entities[j]);
2078 }
2079 }
2080 }
2081
2082 static void finish_typed_chunk_writes_runtime(
2083 World& world, Chunk* pChunk, uint16_t from, uint16_t to, const Entity* pArgIds, const bool* pWriteFlags,
2084 uint32_t argCnt, uint32_t firstWriteArg, void* const* pSparseStores = nullptr);
2085
2086 template <typename... T>
2087 static void finish_typed_chunk_writes(World& world, Chunk* pChunk, uint16_t from, uint16_t to);
2088
2089 static void finish_typed_iter_writes_runtime(
2090 Iter& it, const Entity* pArgIds, const bool* pWriteFlags, uint32_t argCnt, uint32_t firstWriteArg);
2092
2094 enum class ExecPayloadKind : uint8_t {
2096 Plain,
2098 Grouped,
2101 };
2102
2107 GAIA_NODISCARD static ExecPayloadKind exec_payload_kind(const QueryInfo& queryInfo, Constraints constraints) {
2108 if (queryInfo.has_sorted_payload())
2110 if (!queryInfo.has_grouped_payload())
2112 if (needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints))
2115 }
2116
2122 enum class QueryPlanMode : uint8_t {
2124 Empty,
2126 General,
2128 EntitySeed,
2137 Sorted,
2139 Traversal
2140 };
2141
2162
2176
2178 struct QueryCacheRange final {
2180 uint32_t idxFrom = 0;
2182 uint32_t idxTo = 0;
2184 bool hasSelectedGroup = false;
2186 bool valid = true;
2187 };
2188
2190 struct IterModeEnabled final {};
2192 struct IterModeDisabledOnly final {};
2194 struct IterModeAcceptAll final {};
2195
2199 template <typename TMode>
2200 GAIA_NODISCARD static constexpr Constraints iter_mode_constraints() {
2201 if constexpr (std::is_same_v<TMode, IterModeDisabledOnly>)
2202 return Constraints::DisabledOnly;
2203 else if constexpr (std::is_same_v<TMode, IterModeAcceptAll>)
2204 return Constraints::AcceptAll;
2205 else
2206 return Constraints::EnabledOnly;
2207 }
2208
2209 //--------------------------------------------------------------------------------
2210
2218 template <typename Func, typename TMode>
2219 static void run_query_func(World* pWorld, Func func, ChunkBatch& batch) {
2220 Iter it;
2221 it.init_query_state(pWorld, iter_mode_constraints<TMode>(), false);
2222 it.set_archetype(batch.pArchetype);
2223 it.set_chunk(batch.pChunk, batch.from, batch.to);
2224 it.set_group_id(batch.groupId);
2225 it.set_comp_indices(batch.pCompIndices);
2226 it.set_inherited_data(batch.inheritedData);
2227 func(it);
2228 finish_iter_writes(it);
2229 it.clear_touched_writes();
2230 }
2231
2240 template <typename Func>
2241 static void run_query_arch_func(World* pWorld, Func func, ChunkBatch& batch, Constraints constraints) {
2242 Iter it;
2243 it.init_query_state(pWorld, constraints, false);
2244 it.set_archetype(batch.pArchetype);
2245 // it.set_chunk(nullptr, 0, 0); We do not need this, and calling it would assert
2246 it.set_group_id(batch.groupId);
2247 it.set_comp_indices(batch.pCompIndices);
2248 it.set_inherited_data(batch.inheritedData);
2249 func(it);
2250 it.clear_touched_writes();
2251 }
2252
2260 template <typename Func, typename TMode>
2261 static void run_query_func(World* pWorld, Func func, std::span<ChunkBatch> batches) {
2262 GAIA_PROF_SCOPE(query::run_query_func);
2263
2264 const auto chunkCnt = batches.size();
2265 GAIA_ASSERT(chunkCnt > 0);
2266
2267 Iter it;
2268 it.init_query_state(pWorld, iter_mode_constraints<TMode>(), false);
2269
2270 const Archetype* pLastArchetype = nullptr;
2271 const uint8_t* pLastIndices = nullptr;
2272 InheritedTermDataView lastInheritedData{};
2273 GroupId lastGroupId = GroupIdMax;
2274
2275 const auto apply_batch = [&](const ChunkBatch& batch) {
2276 if (batch.pArchetype != pLastArchetype) {
2277 it.set_archetype(batch.pArchetype);
2278 pLastArchetype = batch.pArchetype;
2279 }
2280
2281 if (batch.pCompIndices != pLastIndices) {
2282 it.set_comp_indices(batch.pCompIndices);
2283 pLastIndices = batch.pCompIndices;
2284 }
2285
2286 if (batch.inheritedData.data() != lastInheritedData.data()) {
2287 it.set_inherited_data(batch.inheritedData);
2288 lastInheritedData = batch.inheritedData;
2289 }
2290
2291 if (batch.groupId != lastGroupId) {
2292 it.set_group_id(batch.groupId);
2293 lastGroupId = batch.groupId;
2294 }
2295
2296 it.set_chunk(batch.pChunk, batch.from, batch.to);
2297 func(it);
2298 finish_iter_writes(it);
2299 it.clear_touched_writes();
2300 };
2301
2302 // We only have one chunk to process.
2303 if GAIA_UNLIKELY (chunkCnt == 1) {
2304 apply_batch(batches[0]);
2305 return;
2306 }
2307
2308 // We have many chunks to process.
2309 // Chunks might be located at different memory locations. Not even in the same memory page.
2310 // Therefore, to make it easier for the CPU we give it a hint that we want to prefetch data
2311 // for the next chunk explicitly so we do not end up stalling later.
2312 // Note, this is a micro optimization and on average it brings no performance benefit. It only
2313 // helps with edge cases.
2314 // Let us be conservative for now and go with T2. That means we will try to keep our data at
2315 // least in L3 cache or higher.
2316 gaia::prefetch(batches[1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
2317 apply_batch(batches[0]);
2318
2319 uint32_t chunkIdx = 1;
2320 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
2321 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
2322 apply_batch(batches[chunkIdx]);
2323 }
2324
2325 apply_batch(batches[chunkIdx]);
2326 }
2327
2328 //------------------------------------------------
2329
2331 template <typename Func, typename TMode>
2332 struct QueryJobCtx {
2333 QueryImpl* pSelf = nullptr;
2334 World* pWorld = nullptr;
2336 Func func;
2337
2338 GAIA_USE_SMALLBLOCK(QueryJobCtx)
2339 };
2340
2341 template <typename Func>
2342 struct QueryTaskJobCtx {
2343 QueryImpl* pSelf = nullptr;
2344 Func func;
2345 QueryExecType execType = QueryExecType::Default;
2346
2347 GAIA_USE_SMALLBLOCK(QueryTaskJobCtx)
2348 };
2349
2352 template <typename Func>
2353 struct IterJobCallback {
2355 QueryImpl* pSelf = nullptr;
2357 Func func;
2358
2361 void operator()(Iter& it) {
2362 it.ctx(pSelf->ctx());
2363 func(it);
2364 }
2365 };
2366
2369 template <typename Func>
2370 struct TypedJobCallback {
2372 QueryImpl* pSelf = nullptr;
2374 Func func;
2375
2378 void operator()(Iter& it) {
2379 pSelf->each_iter(it, func);
2380 }
2381 };
2382
2383 template <typename Func>
2384 static void invoke_query_task_job(void* pCtx) {
2385 auto& ctx = *reinterpret_cast<QueryTaskJobCtx<Func>*>(pCtx);
2386 ctx.pSelf->each(ctx.func, ctx.execType);
2387 }
2388
2389 template <typename Func>
2390 static void cleanup_query_task_job(void* pCtx) {
2391 auto* pJobCtx = reinterpret_cast<QueryTaskJobCtx<Func>*>(pCtx);
2392 if (pJobCtx == nullptr)
2393 return;
2394 delete pJobCtx;
2395 }
2396
2397 template <typename Func, typename TMode>
2398 static void cleanup_query_job(void* pCtx) {
2399 auto* pJobCtx = reinterpret_cast<QueryJobCtx<Func, TMode>*>(pCtx);
2400 if (pJobCtx == nullptr)
2401 return;
2402
2403 auto* pWorld = pJobCtx->pWorld;
2404 if (pWorld != nullptr) {
2405 unlock(*pWorld);
2406 // Apply the sorted-query invalidations and deliver the OnSet notifications workers
2407 // recorded. This runs once the job finished, on the thread that waited for it,
2408 // with the world already unlocked.
2409 world_defer_parallel_end(*pWorld);
2410 commit_cmd_buffer_st(*pWorld);
2411 commit_cmd_buffer_mt(*pWorld);
2412 if (pJobCtx->pSelf != nullptr)
2413 pJobCtx->pSelf->m_changedWorldVersion = *pJobCtx->pSelf->m_worldVersion;
2414 }
2415
2416 delete pJobCtx;
2417 }
2418
2419 template <typename Func, typename TMode, QueryExecType ExecType>
2420 GAIA_NODISCARD SchedJob add_parallel_query_job(Func func) {
2421 static_assert(ExecType != QueryExecType::Default);
2422 if (m_batches.empty()) {
2423 m_changedWorldVersion = *m_worldVersion;
2424 return {};
2425 }
2426
2427 auto* pWorld = m_storage.world();
2428 lock(*pWorld);
2429
2430 auto* pCtx = new QueryJobCtx<Func, TMode>{this, pWorld, {}, GAIA_MOV(func)};
2431 pCtx->batches.resize(m_batches.size());
2432 GAIA_EACH(m_batches) pCtx->batches[i] = m_batches[i];
2433 m_batches.clear();
2434
2435 SchedParDesc desc{};
2436 desc.pCtx = pCtx;
2437 desc.itemCount = (uint32_t)pCtx->batches.size();
2438 desc.groupSize = 0;
2439 desc.execType = ExecType;
2440 desc.invoke = [](void* pInvokeCtx, uint32_t idxStart, uint32_t idxEnd) {
2441 auto& ctx = *reinterpret_cast<QueryJobCtx<Func, TMode>*>(pInvokeCtx);
2442 ParallelSlot slot(idxStart);
2443 run_query_func<Func, TMode>(ctx.pWorld, ctx.func, std::span(&ctx.batches[idxStart], idxEnd - idxStart));
2444 };
2445
2446 // Matched by world_defer_parallel_end() in cleanup_query_job(), which runs after
2447 // the job completed and the world was unlocked.
2448 world_defer_parallel_begin(*pWorld, desc.itemCount);
2449
2450 return sched_add_par(world_sched(*pWorld), desc, pCtx, &cleanup_query_job<Func, TMode>);
2451 }
2452
2453 template <bool HasFilters>
2454 void
2455 collect_runtime_parallel_batches(const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints) {
2456 auto cacheView = queryInfo.cache_archetype_view();
2457 const bool hasSortedPlanPayload =
2458 plan.payloadKind == ExecPayloadKind::NonTrivial && (plan.flags & QueryPlanFlag_Sorted) != 0;
2459 const auto sortView =
2460 hasSortedPlanPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
2461 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
2462 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
2463 if (needsBarrierCache)
2464 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2465
2466 if (!sortView.empty()) {
2467 for (const auto& view: sortView) {
2468 const auto* pArchetype = cacheView[view.archetypeIdx];
2469 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2470 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2471 continue;
2472
2473 const auto viewFrom = view.startRow;
2474 const auto viewTo = (uint16_t)(view.startRow + view.count);
2475 uint16_t minStartRow = 0;
2476 uint16_t minEndRow = 0;
2477 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2478 const auto startRow = core::get_max(minStartRow, viewFrom);
2479 const auto endRow = core::get_min(minEndRow, viewTo);
2480 if (endRow == startRow)
2481 continue;
2482
2483 if constexpr (HasFilters) {
2484 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2485 continue;
2486 }
2487
2488 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2489 const auto inheritedDataView =
2490 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2491 m_batches.push_back(
2492 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2493 }
2494 return;
2495 }
2496
2497 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
2498 const auto* pArchetype = cacheView[i];
2499 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2500 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2501 continue;
2502
2503 auto indicesView = queryInfo.indices_mapping_view(i);
2504 const auto inheritedDataView =
2505 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2506 const auto& chunks = pArchetype->chunks();
2507 for (auto* pChunk: chunks) {
2508 uint16_t from = 0;
2509 uint16_t to = 0;
2510 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2511 if GAIA_UNLIKELY (from == to)
2512 continue;
2513
2514 if constexpr (HasFilters) {
2515 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2516 continue;
2517 }
2518
2519 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2520 }
2521 }
2522 }
2523
2524 template <typename Func>
2525 GAIA_NODISCARD SchedJob add_query_task_job(Func func, QueryExecType execType) {
2526 auto* pCtx = new QueryTaskJobCtx<Func>{this, GAIA_MOV(func), execType};
2527
2528 SchedTaskDesc desc{};
2529 desc.pCtx = pCtx;
2530 desc.invoke = &invoke_query_task_job<Func>;
2531 desc.execType = execType;
2532
2533 return sched_add(world_sched(*m_storage.world()), desc, pCtx, &cleanup_query_task_job<Func>);
2534 }
2535
2536 template <typename Func, QueryExecType ExecType>
2537 GAIA_NODISCARD SchedJob add_iter_parallel_job(Func func) {
2538 static_assert(ExecType != QueryExecType::Default);
2539
2540 auto& queryInfo = fetch();
2541 match_all(queryInfo);
2542 const auto constraints = Constraints::EnabledOnly;
2543 const auto plan = prepare_query_plan(queryInfo, constraints);
2544 if (plan.mode == QueryPlanMode::Empty || plan.idxFrom >= plan.idxTo)
2545 return {};
2546 if (plan.mode == QueryPlanMode::EntitySeed)
2547 return add_query_task_job(GAIA_MOV(func), ExecType);
2548
2549 const auto cacheRange = selected_query_cache_range(queryInfo);
2550 if (cacheRange.hasSelectedGroup)
2551 return add_query_task_job(GAIA_MOV(func), ExecType);
2552
2553 ::gaia::ecs::update_version(*m_worldVersion);
2554 m_batches.clear();
2555 if ((plan.flags & QueryPlanFlag_Filtered) != 0)
2556 collect_runtime_parallel_batches<true>(queryInfo, plan, constraints);
2557 else
2558 collect_runtime_parallel_batches<false>(queryInfo, plan, constraints);
2559
2560 using JobFunc = IterJobCallback<Func>;
2561 return add_parallel_query_job<JobFunc, IterModeEnabled, ExecType>(JobFunc{this, GAIA_MOV(func)});
2562 }
2563
2564 //------------------------------------------------
2565
2566 template <bool HasFilters, typename TMode, typename Func>
2567 void run_query_batch_no_group_id(
2568 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2569 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
2570
2571 auto cacheView = queryInfo.cache_archetype_view();
2572 constexpr auto constraints = iter_mode_constraints<TMode>();
2573 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2574 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2575 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2576 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2577 const bool hasSortedBatchPayload =
2578 payloadKind == ExecPayloadKind::NonTrivial && (queryInfo.has_sorted_payload() || needsBarrierCache);
2579 const auto sortView =
2580 hasSortedBatchPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
2581 if (needsBarrierCache)
2582 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2583
2584 lock(*m_storage.world());
2585
2586 // We are batching by chunks. Some of them might contain only few items but this state is only
2587 // temporary because defragmentation runs constantly and keeps things clean.
2588 ChunkBatchArray chunkBatches;
2589
2590 if (!sortView.empty()) {
2591 for (const auto& view: sortView) {
2592 auto* pArchetype = const_cast<Archetype*>(cacheView[view.archetypeIdx]);
2593 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2594 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2595 continue;
2596
2597 const auto viewFrom = view.startRow;
2598 const auto viewTo = (uint16_t)(view.startRow + view.count);
2599 uint16_t minStartRow = 0;
2600 uint16_t minEndRow = 0;
2601 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2602 const auto startRow = core::get_max(minStartRow, viewFrom);
2603 const auto endRow = core::get_min(minEndRow, viewTo);
2604 const auto totalRows = endRow - startRow;
2605 if (totalRows == 0)
2606 continue;
2607
2608 if constexpr (HasFilters) {
2609 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2610 continue;
2611 }
2612
2613 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2614 const auto inheritedDataView =
2615 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2616
2617 chunkBatches.push_back(
2618 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2619
2620 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2621 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2622 chunkBatches.clear();
2623 }
2624 }
2625 } else {
2626 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2627 auto* pArchetype = const_cast<Archetype*>(cacheView[i]);
2628 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2629 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2630 continue;
2631
2632 auto indicesView = queryInfo.indices_mapping_view(i);
2633 const auto inheritedDataView =
2634 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2635 const auto& chunks = pArchetype->chunks();
2636 uint32_t chunkOffset = 0;
2637 uint32_t itemsLeft = chunks.size();
2638 while (itemsLeft > 0) {
2639 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
2640 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
2641
2642 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
2643 for (auto* pChunk: chunkSpan) {
2644 uint16_t from = 0;
2645 uint16_t to = 0;
2646 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2647 if GAIA_UNLIKELY (from == to)
2648 continue;
2649
2650 if constexpr (HasFilters) {
2651 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2652 continue;
2653 }
2654
2655 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2656 }
2657
2658 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2659 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2660 chunkBatches.clear();
2661 }
2662
2663 itemsLeft -= batchSize;
2664 chunkOffset += batchSize;
2665 }
2666 }
2667 }
2668
2669 // Take care of any leftovers not processed during run_query
2670 if (!chunkBatches.empty())
2671 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2672
2673 unlock(*m_storage.world());
2674 // Commit the command buffer.
2675 // TODO: Smart handling necessary
2676 commit_cmd_buffer_st(*m_storage.world());
2677 commit_cmd_buffer_mt(*m_storage.world());
2678 }
2679
2680 template <bool HasFilters, typename TMode, typename Func, QueryExecType ExecType>
2681 void run_query_batch_no_group_id_par(
2682 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2683 static_assert(ExecType != QueryExecType::Default);
2684 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
2685
2686 auto cacheView = queryInfo.cache_archetype_view();
2687 constexpr auto constraints = iter_mode_constraints<TMode>();
2688 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2689 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2690 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2691 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2692 const bool hasSortedBatchPayload =
2693 payloadKind == ExecPayloadKind::NonTrivial && (queryInfo.has_sorted_payload() || needsBarrierCache);
2694 const auto sortView =
2695 hasSortedBatchPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
2696 if (needsBarrierCache)
2697 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2698
2699 if (!sortView.empty()) {
2700 for (const auto& view: sortView) {
2701 const auto* pArchetype = cacheView[view.archetypeIdx];
2702 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2703 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2704 continue;
2705
2706 const auto viewFrom = view.startRow;
2707 const auto viewTo = (uint16_t)(view.startRow + view.count);
2708 uint16_t minStartRow = 0;
2709 uint16_t minEndRow = 0;
2710 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2711 const auto startRow = core::get_max(minStartRow, viewFrom);
2712 const auto endRow = core::get_min(minEndRow, viewTo);
2713 const auto totalRows = endRow - startRow;
2714 if (totalRows == 0)
2715 continue;
2716
2717 if constexpr (HasFilters) {
2718 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2719 continue;
2720 }
2721
2722 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2723 const auto inheritedDataView =
2724 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2725
2726 m_batches.push_back(
2727 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2728 }
2729 } else {
2730 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2731 const auto* pArchetype = cacheView[i];
2732 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2733 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2734 continue;
2735
2736 auto indicesView = queryInfo.indices_mapping_view(i);
2737 const auto inheritedDataView =
2738 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2739 const auto& chunks = pArchetype->chunks();
2740 for (auto* pChunk: chunks) {
2741 uint16_t from = 0;
2742 uint16_t to = 0;
2743 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2744 if GAIA_UNLIKELY (from == to)
2745 continue;
2746
2747 if constexpr (HasFilters) {
2748 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2749 continue;
2750 }
2751
2752 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2753 }
2754 }
2755 }
2756
2757 if (m_batches.empty())
2758 return;
2759
2760 lock(*m_storage.world());
2761
2762 struct ParallelQueryBatchCtx {
2763 QueryImpl* pSelf;
2764 Func* pFunc;
2765 };
2766 ParallelQueryBatchCtx ctx{this, &func};
2767 SchedParDesc desc{};
2768 desc.pCtx = &ctx;
2769 desc.itemCount = (uint32_t)m_batches.size();
2770 desc.groupSize = 0;
2771 desc.execType = ExecType;
2772 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
2773 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
2774 ParallelSlot slot(idxStart);
2775 run_query_func<Func, TMode>(
2776 ctx.pSelf->m_storage.world(), *ctx.pFunc,
2777 std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
2778 };
2779
2780 {
2781 // Observers recorded by workers are dispatched when this scope ends, after the
2782 // world is unlocked again, so callbacks see the same world state as on the
2783 // serial path.
2784 ParallelScope scope(*m_storage.world(), desc.itemCount);
2785
2786 const auto& sched = world_sched(*m_storage.world());
2787 const auto token = sched_par(sched, desc);
2788 sched_wait(sched, token);
2789 sched_del(sched, token);
2790 m_batches.clear();
2791
2792 unlock(*m_storage.world());
2793 }
2794
2795 // Commit the command buffer.
2796 // TODO: Smart handling necessary
2797 commit_cmd_buffer_st(*m_storage.world());
2798 commit_cmd_buffer_mt(*m_storage.world());
2799 }
2800
2801 template <bool HasFilters, typename TMode, typename Func>
2802 void run_query_batch_with_group_id(
2803 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2804 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
2805
2806 ChunkBatchArray chunkBatches;
2807
2808 auto cacheView = queryInfo.cache_archetype_view();
2809 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2810 constexpr auto constraints = iter_mode_constraints<TMode>();
2811 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2812 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2813 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2814 if (needsBarrierCache)
2815 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2816
2817 lock(*m_storage.world());
2818
2819 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2820 const auto* pArchetype = cacheView[i];
2821 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2822 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2823 continue;
2824
2825 auto indicesView = queryInfo.indices_mapping_view(i);
2826 const auto inheritedDataView =
2827 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2828 const auto& chunks = pArchetype->chunks();
2829 const auto groupId = queryInfo.group_id(i);
2830
2831#if GAIA_ASSERT_ENABLED
2832 GAIA_ASSERT(
2833 // ... or no groupId is set...
2834 m_groupIdSet == 0 ||
2835 // ... or the groupId must match the requested one
2836 groupId == m_groupIdSet);
2837#endif
2838
2839 uint32_t chunkOffset = 0;
2840 uint32_t itemsLeft = chunks.size();
2841 while (itemsLeft > 0) {
2842 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
2843 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
2844
2845 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
2846 for (auto* pChunk: chunkSpan) {
2847 uint16_t from = 0;
2848 uint16_t to = 0;
2849 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2850 if GAIA_UNLIKELY (from == to)
2851 continue;
2852
2853 if constexpr (HasFilters) {
2854 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2855 continue;
2856 }
2857
2858 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
2859 }
2860
2861 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2862 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2863 chunkBatches.clear();
2864 }
2865
2866 itemsLeft -= batchSize;
2867 chunkOffset += batchSize;
2868 }
2869 }
2870
2871 // Take care of any leftovers not processed during run_query
2872 if (!chunkBatches.empty())
2873 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2874
2875 unlock(*m_storage.world());
2876 // Commit the command buffer.
2877 // TODO: Smart handling necessary
2878 commit_cmd_buffer_st(*m_storage.world());
2879 commit_cmd_buffer_mt(*m_storage.world());
2880 }
2881
2882 template <bool HasFilters, typename TMode, typename Func, QueryExecType ExecType>
2883 void run_query_batch_with_group_id_par(
2884 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2885 static_assert(ExecType != QueryExecType::Default);
2886 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
2887
2888 ChunkBatchArray chunkBatch;
2889
2890 auto cacheView = queryInfo.cache_archetype_view();
2891 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2892 constexpr auto constraints = iter_mode_constraints<TMode>();
2893 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2894 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2895 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2896 if (needsBarrierCache)
2897 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2898
2899#if GAIA_ASSERT_ENABLED
2900 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2901 const auto* pArchetype = cacheView[i];
2902 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2903 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2904 continue;
2905
2906 const auto groupId = queryInfo.group_id(i);
2907 GAIA_ASSERT(
2908 // ... or no groupId is set...
2909 m_groupIdSet == 0 ||
2910 // ... or the groupId must match the requested one
2911 groupId == m_groupIdSet);
2912 }
2913#endif
2914
2915 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2916 const Archetype* pArchetype = cacheView[i];
2917 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2918 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2919 continue;
2920
2921 auto indicesView = queryInfo.indices_mapping_view(i);
2922 const auto inheritedDataView =
2923 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2924 const auto groupId = queryInfo.group_id(i);
2925 const auto& chunks = pArchetype->chunks();
2926 for (auto* pChunk: chunks) {
2927 uint16_t from = 0;
2928 uint16_t to = 0;
2929 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2930 if GAIA_UNLIKELY (from == to)
2931 continue;
2932
2933 if constexpr (HasFilters) {
2934 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2935 continue;
2936 }
2937
2938 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
2939 }
2940 }
2941
2942 if (m_batches.empty())
2943 return;
2944
2945 lock(*m_storage.world());
2946
2947 struct ParallelQueryBatchCtx {
2948 QueryImpl* pSelf;
2949 Func* pFunc;
2950 };
2951 ParallelQueryBatchCtx ctx{this, &func};
2952 SchedParDesc desc{};
2953 desc.pCtx = &ctx;
2954 desc.itemCount = (uint32_t)m_batches.size();
2955 desc.groupSize = 0;
2956 desc.execType = ExecType;
2957 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
2958 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
2959 ParallelSlot slot(idxStart);
2960 run_query_func<Func, TMode>(
2961 ctx.pSelf->m_storage.world(), *ctx.pFunc,
2962 std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
2963 };
2964
2965 {
2966 ParallelScope scope(*m_storage.world(), desc.itemCount);
2967
2968 const auto& sched = world_sched(*m_storage.world());
2969 const auto token = sched_par(sched, desc);
2970 sched_wait(sched, token);
2971 sched_del(sched, token);
2972 m_batches.clear();
2973
2974 unlock(*m_storage.world());
2975 }
2976
2977 // Commit the command buffer.
2978 // TODO: Smart handling necessary
2979 commit_cmd_buffer_st(*m_storage.world());
2980 commit_cmd_buffer_mt(*m_storage.world());
2981 }
2982
2983 //------------------------------------------------
2984
2985 template <bool HasFilters, QueryExecType ExecType, typename TMode, typename Func>
2986 void run_query(const QueryInfo& queryInfo, Func func) {
2987 GAIA_PROF_SCOPE(query::run_query);
2988
2989 // TODO: Have archetype cache as double-linked list with pointers only.
2990 // Have chunk cache as double-linked list with pointers only.
2991 // Make it so only valid pointers are linked together.
2992 // This means one less indirection + we won't need to call can_process_archetype()
2993 // or pChunk.size()==0 in run_query_batch functions.
2994 auto cache_view = queryInfo.cache_archetype_view();
2995 if (cache_view.empty())
2996 return;
2997
2998 const auto cacheRange = selected_query_cache_range(queryInfo);
2999 if (!cacheRange.hasSelectedGroup) {
3000 // No group requested or group filtering is currently turned off
3001 if constexpr (ExecType != QueryExecType::Default)
3002 run_query_batch_no_group_id_par<HasFilters, TMode, Func, ExecType>(
3003 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3004 else
3005 run_query_batch_no_group_id<HasFilters, TMode, Func>(
3006 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3007 } else {
3008 // We wish to iterate only a certain group
3009 if (!cacheRange.valid)
3010 return;
3011
3012 if constexpr (ExecType != QueryExecType::Default)
3013 run_query_batch_with_group_id_par<HasFilters, TMode, Func, ExecType>(
3014 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3015 else
3016 run_query_batch_with_group_id<HasFilters, TMode, Func>(
3017 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3018 }
3019 }
3020
3021 //------------------------------------------------
3022
3023 template <QueryExecType ExecType, typename Func>
3024 void run_query_on_archetypes(QueryInfo& queryInfo, Func func, Constraints constraints) {
3025 // Update the world version
3026 // We do read-only access. No need to update the version
3027 //::gaia::ecs::update_version(*m_worldVersion);
3028 lock(*m_storage.world());
3029
3030 {
3031 GAIA_PROF_SCOPE(query::run_query_a);
3032
3033 // TODO: Have archetype cache as double-linked list with pointers only.
3034 // Have chunk cache as double-linked list with pointers only.
3035 // Make it so only valid pointers are linked together.
3036 // This means one less indirection + we won't need to call can_process_archetype().
3037 auto cache_view = queryInfo.cache_archetype_view();
3038 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
3039 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
3040 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
3041 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
3042 if (needsBarrierCache)
3043 queryInfo.ensure_depth_order_hierarchy_barrier_cache();
3044 GAIA_EACH(cache_view) {
3045 const auto* pArchetype = cache_view[i];
3046 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3047 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3048 continue;
3049
3050 auto indicesView = queryInfo.indices_mapping_view(i);
3051 const auto inheritedDataView =
3052 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3053 ChunkBatch batch{pArchetype, nullptr, indicesView.data(), inheritedDataView, 0, 0, 0};
3054 run_query_arch_func(m_storage.world(), func, batch, constraints);
3055 }
3056 }
3057
3058 unlock(*m_storage.world());
3059 // Changed-filter state is instance-local for cached queries.
3060 }
3061
3062 //------------------------------------------------
3063
3064 template <QueryExecType ExecType, typename TMode, typename Func>
3065 void run_query_on_chunks(QueryInfo& queryInfo, Func func) {
3066 // Update the world version
3067 ::gaia::ecs::update_version(*m_worldVersion);
3068
3069 const bool hasFilters = queryInfo.has_filters();
3070 if (hasFilters)
3071 run_query<true, ExecType, TMode>(queryInfo, func);
3072 else
3073 run_query<false, ExecType, TMode>(queryInfo, func);
3074
3075 // Changed-filter state is instance-local for cached queries.
3076 m_changedWorldVersion = *m_worldVersion;
3077 }
3078
3079 template <typename Func>
3080 static void
3081 run_query_func_runtime(World* pWorld, Func func, std::span<ChunkBatch> batches, Constraints constraints) {
3082 GAIA_PROF_SCOPE(query::run_query_func);
3083
3084 const auto chunkCnt = batches.size();
3085 GAIA_ASSERT(chunkCnt > 0);
3086
3087 Iter it;
3088 it.init_query_state(pWorld, constraints, false);
3089
3090 const Archetype* pLastArchetype = nullptr;
3091 const uint8_t* pLastIndices = nullptr;
3092 InheritedTermDataView lastInheritedData{};
3093 GroupId lastGroupId = GroupIdMax;
3094
3095 const auto apply_batch = [&](const ChunkBatch& batch) {
3096 if (batch.pArchetype != pLastArchetype) {
3097 it.set_archetype(batch.pArchetype);
3098 pLastArchetype = batch.pArchetype;
3099 }
3100
3101 if (batch.pCompIndices != pLastIndices) {
3102 it.set_comp_indices(batch.pCompIndices);
3103 pLastIndices = batch.pCompIndices;
3104 }
3105
3106 if (batch.inheritedData.data() != lastInheritedData.data()) {
3107 it.set_inherited_data(batch.inheritedData);
3108 lastInheritedData = batch.inheritedData;
3109 }
3110
3111 if (batch.groupId != lastGroupId) {
3112 it.set_group_id(batch.groupId);
3113 lastGroupId = batch.groupId;
3114 }
3115
3116 it.set_chunk(batch.pChunk, batch.from, batch.to);
3117 func(it);
3118 finish_iter_writes(it);
3119 it.clear_touched_writes();
3120 };
3121
3122 if GAIA_UNLIKELY (chunkCnt == 1) {
3123 apply_batch(batches[0]);
3124 return;
3125 }
3126
3127 gaia::prefetch(batches[1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
3128 apply_batch(batches[0]);
3129
3130 uint32_t chunkIdx = 1;
3131 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
3132 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
3133 apply_batch(batches[chunkIdx]);
3134 }
3135
3136 apply_batch(batches[chunkIdx]);
3137 }
3138
3139 template <bool HasFilters, typename Func>
3140 void run_query_batch_no_group_id_runtime(
3141 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3142 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
3143
3144 auto cacheView = queryInfo.cache_archetype_view();
3145 const bool hasSortedPlanPayload =
3146 plan.payloadKind == ExecPayloadKind::NonTrivial && (plan.flags & QueryPlanFlag_Sorted) != 0;
3147 const auto sortView =
3148 hasSortedPlanPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
3149 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3150 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3151 if (needsBarrierCache)
3152 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3153
3154 lock(*m_storage.world());
3155 ChunkBatchArray chunkBatches;
3156
3157 if (!sortView.empty()) {
3158 for (const auto& view: sortView) {
3159 auto* pArchetype = const_cast<Archetype*>(cacheView[view.archetypeIdx]);
3160 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3161 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3162 continue;
3163
3164 const auto viewFrom = view.startRow;
3165 const auto viewTo = (uint16_t)(view.startRow + view.count);
3166 uint16_t minStartRow = 0;
3167 uint16_t minEndRow = 0;
3168 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3169 const auto startRow = core::get_max(minStartRow, viewFrom);
3170 const auto endRow = core::get_min(minEndRow, viewTo);
3171 const auto totalRows = endRow - startRow;
3172 if (totalRows == 0)
3173 continue;
3174
3175 if constexpr (HasFilters) {
3176 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3177 continue;
3178 }
3179
3180 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3181 const auto inheritedDataView =
3182 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3183
3184 chunkBatches.push_back(
3185 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
3186
3187 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3188 run_query_func_runtime(
3189 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3190 chunkBatches.clear();
3191 }
3192 }
3193 } else {
3194 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3195 auto* pArchetype = const_cast<Archetype*>(cacheView[i]);
3196 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3197 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3198 continue;
3199
3200 auto indicesView = queryInfo.indices_mapping_view(i);
3201 const auto inheritedDataView =
3202 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3203 const auto& chunks = pArchetype->chunks();
3204 uint32_t chunkOffset = 0;
3205 uint32_t itemsLeft = chunks.size();
3206 while (itemsLeft > 0) {
3207 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3208 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3209
3210 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3211 for (auto* pChunk: chunkSpan) {
3212 uint16_t from = 0;
3213 uint16_t to = 0;
3214 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3215 if GAIA_UNLIKELY (from == to)
3216 continue;
3217
3218 if constexpr (HasFilters) {
3219 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3220 continue;
3221 }
3222
3223 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
3224 }
3225
3226 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3227 run_query_func_runtime(
3228 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3229 chunkBatches.clear();
3230 }
3231
3232 itemsLeft -= batchSize;
3233 chunkOffset += batchSize;
3234 }
3235 }
3236 }
3237
3238 if (!chunkBatches.empty())
3239 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3240
3241 unlock(*m_storage.world());
3242 commit_cmd_buffer_st(*m_storage.world());
3243 commit_cmd_buffer_mt(*m_storage.world());
3244 }
3245
3246 template <bool HasFilters, typename Func, QueryExecType ExecType>
3247 void run_query_batch_no_group_id_runtime_par(
3248 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3249 static_assert(ExecType != QueryExecType::Default);
3250 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
3251
3252 auto cacheView = queryInfo.cache_archetype_view();
3253 const bool hasSortedPlanPayload =
3254 plan.payloadKind == ExecPayloadKind::NonTrivial && (plan.flags & QueryPlanFlag_Sorted) != 0;
3255 const auto sortView =
3256 hasSortedPlanPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
3257 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3258 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3259 if (needsBarrierCache)
3260 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3261
3262 if (!sortView.empty()) {
3263 for (const auto& view: sortView) {
3264 const auto* pArchetype = cacheView[view.archetypeIdx];
3265 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3266 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3267 continue;
3268
3269 const auto viewFrom = view.startRow;
3270 const auto viewTo = (uint16_t)(view.startRow + view.count);
3271 uint16_t minStartRow = 0;
3272 uint16_t minEndRow = 0;
3273 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3274 const auto startRow = core::get_max(minStartRow, viewFrom);
3275 const auto endRow = core::get_min(minEndRow, viewTo);
3276 const auto totalRows = endRow - startRow;
3277 if (totalRows == 0)
3278 continue;
3279
3280 if constexpr (HasFilters) {
3281 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3282 continue;
3283 }
3284
3285 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3286 const auto inheritedDataView =
3287 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3288
3289 m_batches.push_back(
3290 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
3291 }
3292 } else {
3293 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3294 const auto* pArchetype = cacheView[i];
3295 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3296 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3297 continue;
3298
3299 auto indicesView = queryInfo.indices_mapping_view(i);
3300 const auto inheritedDataView =
3301 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3302 const auto& chunks = pArchetype->chunks();
3303 for (auto* pChunk: chunks) {
3304 uint16_t from = 0;
3305 uint16_t to = 0;
3306 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3307 if GAIA_UNLIKELY (from == to)
3308 continue;
3309
3310 if constexpr (HasFilters) {
3311 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3312 continue;
3313 }
3314
3315 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
3316 }
3317 }
3318 }
3319
3320 if (m_batches.empty())
3321 return;
3322
3323 lock(*m_storage.world());
3324
3325 struct ParallelQueryBatchCtx {
3326 QueryImpl* pSelf;
3327 Func* pFunc;
3328 Constraints constraints;
3329 };
3330 ParallelQueryBatchCtx ctx{this, &func, constraints};
3331 SchedParDesc desc{};
3332 desc.pCtx = &ctx;
3333 desc.itemCount = (uint32_t)m_batches.size();
3334 desc.groupSize = 0;
3335 desc.execType = ExecType;
3336 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3337 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
3338 ParallelSlot slot(idxStart);
3339 run_query_func_runtime(
3340 ctx.pSelf->m_storage.world(), *ctx.pFunc, std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3341 ctx.constraints);
3342 };
3343
3344 {
3345 ParallelScope scope(*m_storage.world(), desc.itemCount);
3346
3347 const auto& sched = world_sched(*m_storage.world());
3348 const auto token = sched_par(sched, desc);
3349 sched_wait(sched, token);
3350 sched_del(sched, token);
3351 m_batches.clear();
3352
3353 unlock(*m_storage.world());
3354 }
3355
3356 commit_cmd_buffer_st(*m_storage.world());
3357 commit_cmd_buffer_mt(*m_storage.world());
3358 }
3359
3360 template <bool HasFilters, typename Func>
3361 void run_query_batch_with_group_id_runtime(
3362 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3363 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
3364
3365 ChunkBatchArray chunkBatches;
3366 auto cacheView = queryInfo.cache_archetype_view();
3367 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3368 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3369 if (needsBarrierCache)
3370 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3371
3372 lock(*m_storage.world());
3373
3374 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3375 const auto* pArchetype = cacheView[i];
3376 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3377 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3378 continue;
3379
3380 auto indicesView = queryInfo.indices_mapping_view(i);
3381 const auto inheritedDataView =
3382 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3383 const auto& chunks = pArchetype->chunks();
3384 const auto groupId = queryInfo.group_id(i);
3385
3386 uint32_t chunkOffset = 0;
3387 uint32_t itemsLeft = chunks.size();
3388 while (itemsLeft > 0) {
3389 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3390 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3391
3392 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3393 for (auto* pChunk: chunkSpan) {
3394 uint16_t from = 0;
3395 uint16_t to = 0;
3396 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3397 if GAIA_UNLIKELY (from == to)
3398 continue;
3399
3400 if constexpr (HasFilters) {
3401 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3402 continue;
3403 }
3404
3405 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3406 }
3407
3408 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3409 run_query_func_runtime(
3410 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3411 chunkBatches.clear();
3412 }
3413
3414 itemsLeft -= batchSize;
3415 chunkOffset += batchSize;
3416 }
3417 }
3418
3419 if (!chunkBatches.empty())
3420 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3421
3422 unlock(*m_storage.world());
3423 commit_cmd_buffer_st(*m_storage.world());
3424 commit_cmd_buffer_mt(*m_storage.world());
3425 }
3426
3427 template <bool HasFilters, typename Func, QueryExecType ExecType>
3428 void run_query_batch_with_group_id_runtime_par(
3429 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3430 static_assert(ExecType != QueryExecType::Default);
3431 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
3432
3433 ChunkBatchArray chunkBatch;
3434 auto cacheView = queryInfo.cache_archetype_view();
3435 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3436 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3437 if (needsBarrierCache)
3438 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3439
3440 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3441 const auto* pArchetype = cacheView[i];
3442 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3443 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3444 continue;
3445
3446 auto indicesView = queryInfo.indices_mapping_view(i);
3447 const auto inheritedDataView =
3448 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3449 const auto groupId = queryInfo.group_id(i);
3450 const auto& chunks = pArchetype->chunks();
3451 for (auto* pChunk: chunks) {
3452 uint16_t from = 0;
3453 uint16_t to = 0;
3454 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3455 if GAIA_UNLIKELY (from == to)
3456 continue;
3457
3458 if constexpr (HasFilters) {
3459 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3460 continue;
3461 }
3462
3463 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3464 }
3465 }
3466
3467 if (m_batches.empty())
3468 return;
3469
3470 lock(*m_storage.world());
3471
3472 struct ParallelQueryBatchCtx {
3473 QueryImpl* pSelf;
3474 Func* pFunc;
3475 Constraints constraints;
3476 };
3477 ParallelQueryBatchCtx ctx{this, &func, constraints};
3478 SchedParDesc desc{};
3479 desc.pCtx = &ctx;
3480 desc.itemCount = (uint32_t)m_batches.size();
3481 desc.groupSize = 0;
3482 desc.execType = ExecType;
3483 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3484 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
3485 ParallelSlot slot(idxStart);
3486 run_query_func_runtime(
3487 ctx.pSelf->m_storage.world(), *ctx.pFunc, std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3488 ctx.constraints);
3489 };
3490
3491 {
3492 ParallelScope scope(*m_storage.world(), desc.itemCount);
3493
3494 const auto& sched = world_sched(*m_storage.world());
3495 const auto token = sched_par(sched, desc);
3496 sched_wait(sched, token);
3497 sched_del(sched, token);
3498 m_batches.clear();
3499
3500 unlock(*m_storage.world());
3501 }
3502
3503 commit_cmd_buffer_st(*m_storage.world());
3504 commit_cmd_buffer_mt(*m_storage.world());
3505 }
3506
3507 template <bool HasFilters, QueryExecType ExecType, typename Func>
3508 void run_query_runtime_planned(
3509 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3510 GAIA_PROF_SCOPE(query::run_query);
3511 if (plan.mode == QueryPlanMode::Empty || plan.idxFrom >= plan.idxTo)
3512 return;
3513
3514 const auto cacheRange = selected_query_cache_range(queryInfo);
3515 if (!cacheRange.hasSelectedGroup) {
3516 if constexpr (ExecType != QueryExecType::Default)
3517 run_query_batch_no_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3518 else
3519 run_query_batch_no_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3520 } else {
3521 if constexpr (ExecType != QueryExecType::Default)
3522 run_query_batch_with_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3523 else
3524 run_query_batch_with_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3525 }
3526 }
3527
3535 template <QueryExecType ExecType, typename Func>
3536 void run_query_on_chunks_runtime_planned(
3537 QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3538 if (plan.mode == QueryPlanMode::Empty)
3539 return;
3540
3541 ::gaia::ecs::update_version(*m_worldVersion);
3542 if ((plan.flags & QueryPlanFlag_Filtered) != 0)
3543 run_query_runtime_planned<true, ExecType>(queryInfo, plan, constraints, func);
3544 else
3545 run_query_runtime_planned<false, ExecType>(queryInfo, plan, constraints, func);
3546
3547 m_changedWorldVersion = *m_worldVersion;
3548 }
3549
3554 GAIA_NODISCARD bool can_use_direct_chunk_iteration_fastpath(const QueryInfo& queryInfo) const {
3555 const auto& data = queryInfo.ctx().data;
3556 return data.sortByFunc == nullptr &&
3557 (!has_depth_order_hierarchy_enabled_barrier(queryInfo) || !queryInfo.barrier_may_prune());
3558 }
3559
3563 GAIA_NODISCARD QueryCacheRange selected_query_cache_range(const QueryInfo& queryInfo) const {
3564 QueryCacheRange range{};
3565 range.idxTo = (uint32_t)queryInfo.cache_archetype_view().size();
3566
3567 const auto& data = queryInfo.ctx().data;
3568 if (data.groupBy == EntityBad || m_groupIdSet == 0)
3569 return range;
3570
3571 range.hasSelectedGroup = true;
3572 const auto* pGroupData = queryInfo.selected_group_data(m_groupIdSet);
3573 if (pGroupData == nullptr) {
3574 range.idxFrom = 0;
3575 range.idxTo = 0;
3576 range.valid = false;
3577 return range;
3578 }
3579
3580 range.idxFrom = pGroupData->idxFirst;
3581 range.idxTo = pGroupData->idxLast + 1;
3582 return range;
3583 }
3584
3589 GAIA_NODISCARD QueryPlan prepare_query_plan(const QueryInfo& queryInfo, const TypedQueryExecState& state) const;
3590
3591 template <bool HasFilters, typename Func, typename... T>
3592 void run_query_on_chunks_direct_typed(
3593 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, Func& func,
3594 core::func_type_list<T...>);
3595
3596 template <bool HasFilters, typename Func, typename... T>
3597 void run_query_on_chunks_sparse_typed(
3598 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, Func& func,
3599 core::func_type_list<T...>);
3600
3601 template <typename Func, typename... T>
3602 void run_query_on_sparse_entities_typed(
3603 QueryInfo& queryInfo, const TypedQueryExecState& state, Func& func, core::func_type_list<T...>);
3604
3605 void run_query_on_chunks_direct(
3606 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, void* pFunc,
3607 void (*runChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&));
3608
3609 void run_query_on_chunks_direct_iter(
3610 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, void* pFunc,
3611 void (*runChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&));
3612
3613 struct TypedQueryErasedOps {
3614 void (*runSparsePlan)(QueryImpl&, QueryInfo&, const QueryPlan&, void*, const TypedQueryExecState&) = nullptr;
3615 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&) = nullptr;
3616 void (*runDirectChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&) = nullptr;
3617 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&) = nullptr;
3618 void (*invokeInherited)(World&, Entity, const Entity*, void*) = nullptr;
3619 bool needsInheritedArgIds = false;
3620 };
3621
3622 template <QueryExecType ExecType>
3623 void each_inter(
3624 QueryInfo& queryInfo, const QueryPlan& plan, void* pFunc, const TypedQueryExecState& state,
3625 const TypedQueryErasedOps& ops);
3626
3627 void each_typed_erased(
3628 QueryExecType execType, void* pFunc, const TypedQueryExecState& state, const TypedQueryErasedOps& ops);
3629
3630 template <QueryExecType ExecType, typename Func>
3631 void each_typed_inter(QueryInfo& queryInfo, Func func);
3632
3633 template <QueryExecType ExecType>
3634 void each_iter_inter_erased(
3635 QueryInfo& queryInfo, const QueryPlan& plan, void* pFunc, const TypedQueryExecState& state,
3636 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
3637 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
3638
3639 void each_walk_inter(
3640 QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints, void* pFunc,
3641 const TypedQueryExecState& state,
3642 void (*runChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
3644
3649 GAIA_NODISCARD QueryPlan prepare_query_plan(const QueryInfo& queryInfo, Constraints constraints) const {
3650 QueryPlan plan{};
3651 const auto cacheRange = selected_query_cache_range(queryInfo);
3652 plan.idxFrom = cacheRange.idxFrom;
3653 plan.idxTo = cacheRange.idxTo;
3654 const bool hasFilters = queryInfo.has_filters();
3655 const bool hasSortedPayload = queryInfo.has_sorted_payload();
3656 const bool hasDepthOrderBarrier = has_depth_order_hierarchy_enabled_barrier(queryInfo);
3657 bool hasConstrainedDepthOrderBarrier = constraints != Constraints::AcceptAll && hasDepthOrderBarrier;
3658 if (hasFilters)
3659 plan.flags |= QueryPlanFlag_Filtered;
3660 if (queryInfo.has_entity_filter_terms())
3661 plan.flags |= QueryPlanFlag_EntityFilter;
3662 if (queryInfo.has_inherited_data_payload())
3663 plan.flags |= QueryPlanFlag_InheritedPayload;
3664 if (queryInfo.has_grouped_payload())
3665 plan.flags |= QueryPlanFlag_Grouped;
3666 if (hasSortedPayload || hasDepthOrderBarrier)
3667 plan.flags |= QueryPlanFlag_Sorted;
3668 plan.payloadKind = exec_payload_kind(queryInfo, constraints);
3669
3670 if (cacheRange.hasSelectedGroup) {
3671 plan.flags |= QueryPlanFlag_Grouped;
3672 plan.payloadKind = ExecPayloadKind::Grouped;
3673 if (!cacheRange.valid) {
3674 plan.mode = QueryPlanMode::Empty;
3675 return plan;
3676 }
3677 }
3678
3679 if ((plan.flags & QueryPlanFlag_Filtered) == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
3680 plan.mode = QueryPlanMode::EntitySeed;
3681 return plan;
3682 }
3683
3684 if (plan.idxFrom >= plan.idxTo) {
3685 plan.mode = QueryPlanMode::Empty;
3686 return plan;
3687 }
3688
3689 if (hasConstrainedDepthOrderBarrier && !depth_order_hierarchy_barrier_prunes(queryInfo)) {
3690 hasConstrainedDepthOrderBarrier = false;
3691 if (!hasSortedPayload && (plan.flags & QueryPlanFlag_InheritedPayload) == 0)
3692 plan.payloadKind = queryInfo.has_grouped_payload() ? ExecPayloadKind::Grouped : ExecPayloadKind::Plain;
3693 }
3694 if (hasConstrainedDepthOrderBarrier)
3695 plan.flags |= QueryPlanFlag_BarrierCache;
3696
3697 if (hasSortedPayload) {
3698 plan.mode = QueryPlanMode::Sorted;
3699 return plan;
3700 }
3701
3702 if (hasConstrainedDepthOrderBarrier || (plan.flags & QueryPlanFlag_InheritedPayload) != 0) {
3703 plan.mode = QueryPlanMode::Traversal;
3704 return plan;
3705 }
3706
3707 if ((plan.flags & QueryPlanFlag_EntityFilter) != 0)
3708 return plan;
3709
3710 if (plan.payloadKind != ExecPayloadKind::Plain) {
3711 if (plan.payloadKind != ExecPayloadKind::Grouped || !hasDepthOrderBarrier)
3712 return plan;
3713 if (hasConstrainedDepthOrderBarrier)
3714 return plan;
3715 if (!can_use_direct_chunk_iteration_fastpath(queryInfo))
3716 return plan;
3717
3718 plan.mode = QueryPlanMode::DirectDense;
3719 return plan;
3720 }
3721
3722 if (!can_use_direct_chunk_iteration_fastpath(queryInfo))
3723 return plan;
3724
3725 plan.mode = QueryPlanMode::DirectDense;
3726 return plan;
3727 }
3728
3737 template <bool HasFilters, bool HasGroups, typename Func>
3739 QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func& func) {
3740 ::gaia::ecs::update_version(*m_worldVersion);
3741
3742 auto cacheView = queryInfo.cache_archetype_view();
3743 lock(*m_storage.world());
3744
3745 Iter it;
3746 it.init_query_state(queryInfo.world(), constraints, false);
3747
3750 const bool canSkipProcessCheck =
3752
3753 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3754 auto* pArchetype = const_cast<Archetype*>(cacheView[i]);
3755 if (canSkipProcessCheck) {
3756 if GAIA_UNLIKELY (pArchetype->is_req_del())
3757 continue;
3758 } else if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints))
3759 continue;
3760
3761 auto indicesView = queryInfo.indices_mapping_view(i);
3762 const auto* pIndices = indicesView.data();
3763 const auto groupId = HasGroups ? queryInfo.group_id(i) : GroupId(0);
3764 const auto& chunks = pArchetype->chunks();
3765 for (auto* pChunk: chunks) {
3766 const auto from = detail::ChunkIterImpl::start_index(pChunk, constraints);
3767 const auto to = detail::ChunkIterImpl::end_index(pChunk, constraints);
3768 if GAIA_UNLIKELY (from == to)
3769 continue;
3770 if constexpr (HasFilters) {
3771 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion, indicesView))
3772 continue;
3773 }
3774
3775 it.set_query_chunk(pArchetype, pIndices, pChunk, from, to);
3776 if constexpr (HasGroups)
3777 it.set_group_id(groupId);
3778 it.ctx(m_ctx);
3779 {
3780 GAIA_PROF_SCOPE(query_func);
3781 func(it);
3782 }
3783 finish_iter_writes(it);
3784 it.clear_touched_writes();
3785 }
3786 }
3787
3788 unlock(*m_storage.world());
3789 commit_cmd_buffer_st(*m_storage.world());
3790 commit_cmd_buffer_mt(*m_storage.world());
3791 m_changedWorldVersion = *m_worldVersion;
3792 }
3793
3799 template <QueryExecType ExecType, typename Func>
3800 void each_runtime_inter(Func func, Constraints constraints = Constraints::EnabledOnly) {
3801 if constexpr (ExecType == QueryExecType::Default) {
3802 auto& queryInfo = fetch();
3803 match_all(queryInfo);
3804 const auto plan = prepare_query_plan(queryInfo, constraints);
3805 if (plan.mode == QueryPlanMode::DirectDense) {
3806 const bool hasGroups = (plan.flags & QueryPlanFlag_Grouped) != 0;
3807 if ((plan.flags & QueryPlanFlag_Filtered) != 0) {
3808 if (hasGroups)
3809 run_query_on_chunks_runtime_direct_plain_impl<true, true>(queryInfo, plan, constraints, func);
3810 else
3811 run_query_on_chunks_runtime_direct_plain_impl<true, false>(queryInfo, plan, constraints, func);
3812 } else {
3813 if (hasGroups)
3814 run_query_on_chunks_runtime_direct_plain_impl<false, true>(queryInfo, plan, constraints, func);
3815 else
3816 run_query_on_chunks_runtime_direct_plain_impl<false, false>(queryInfo, plan, constraints, func);
3817 }
3818 return;
3819 }
3820
3822 queryInfo, plan, ExecType, static_cast<void*>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
3823 return;
3824 }
3825
3826 each_runtime_erased(ExecType, static_cast<void*>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
3827 }
3828
3834 template <typename Func, typename TIter>
3835 static void invoke_runtime_iter(void* pFunc, TIter& it) {
3836 auto& func = *static_cast<Func*>(pFunc);
3837 func(it);
3838 }
3839
3841 struct RuntimeIterCallback {
3842 void* pFunc;
3843 void* pCtx;
3844 void (*invoke)(void*, Iter&);
3845
3846 void operator()(Iter& it) const {
3847 GAIA_PROF_SCOPE(query_func);
3848 it.ctx(pCtx);
3849 invoke(pFunc, it);
3850 }
3851 };
3852
3853 struct TypedDirectChunkCallback {
3854 QueryImpl* pSelf;
3855 void* pFunc;
3856 const TypedQueryExecState* pState;
3857 void (*runChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&);
3858
3859 void operator()(Iter& it) const {
3860 GAIA_PROF_SCOPE(query_func);
3861 it.ctx(pSelf->ctx());
3862 runChunk(*pSelf, it, pFunc, *pState);
3863 }
3864 };
3865
3866 struct TypedMappedChunkCallback {
3867 QueryImpl* pSelf;
3868 const QueryInfo* pQueryInfo;
3869 void* pFunc;
3870 const TypedQueryExecState* pState;
3871 void (*runChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&);
3872
3873 void operator()(Iter& it) const {
3874 GAIA_PROF_SCOPE(query_func);
3875 it.ctx(pSelf->ctx());
3876 runChunk(*pSelf, *pQueryInfo, it, pFunc, *pState);
3877 }
3878 };
3879
3880 struct TypedIterErasedCallback {
3881 QueryImpl* pSelf;
3882 void* pFunc;
3883 const TypedQueryExecState* pState;
3884 void (*runDirect)(QueryImpl&, Iter&, void*, const TypedQueryExecState&);
3885 void (*runChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&);
3886
3887 void operator()(Iter& it) const {
3888 GAIA_PROF_SCOPE(query_func);
3889 it.ctx(pSelf->ctx());
3890 pSelf->each_iter_erased(it, pFunc, *pState, runDirect, runChunk);
3891 }
3892 };
3894
3901 QueryExecType execType, void* pFunc, void (*invoke)(void*, Iter&), Constraints constraints) {
3902 auto& queryInfo = fetch();
3903 match_all(queryInfo);
3904 const auto plan = prepare_query_plan(queryInfo, constraints);
3905 each_runtime_erased(queryInfo, plan, execType, pFunc, invoke, constraints);
3906 }
3907
3916 QueryInfo& queryInfo, const QueryPlan& plan, QueryExecType execType, void* pFunc,
3917 void (*invoke)(void*, Iter&), Constraints constraints) {
3918 RuntimeIterCallback cb{pFunc, m_ctx, invoke};
3919
3920 if (plan.mode == QueryPlanMode::EntitySeed) {
3921 each_direct_iter_inter(queryInfo, constraints, cb);
3922 return;
3923 }
3924
3925 switch (execType) {
3926 case QueryExecType::Parallel:
3927 run_query_on_chunks_runtime_planned<QueryExecType::Parallel>(queryInfo, plan, constraints, cb);
3928 break;
3929 case QueryExecType::ParallelPerf:
3930 run_query_on_chunks_runtime_planned<QueryExecType::ParallelPerf>(queryInfo, plan, constraints, cb);
3931 break;
3932 case QueryExecType::ParallelEff:
3933 run_query_on_chunks_runtime_planned<QueryExecType::ParallelEff>(queryInfo, plan, constraints, cb);
3934 break;
3935 default:
3936 run_query_on_chunks_runtime_planned<QueryExecType::Default>(queryInfo, plan, constraints, cb);
3937 break;
3938 }
3939 }
3940
3941 //------------------------------------------------
3942
3947 GAIA_NODISCARD static bool is_non_fragmenting_direct_term(const World& world, const QueryTerm& term) {
3948 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
3949 return false;
3950
3951 const auto id = term.id;
3952 return (id.pair() && world_relation_uses_non_fragmenting_storage(world, pair_rel(world, id))) ||
3953 (!id.pair() && world_component_is_non_fragmenting(world, id));
3954 }
3955
3959 GAIA_NODISCARD static bool uses_semantic_is_matching(const QueryTerm& term) {
3960 const auto id = term.id;
3961 return term.matchKind == QueryMatchKind::Semantic && term.src == EntityBad && term.entTrav == EntityBad &&
3962 !term_has_variables(term) && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
3963 !is_variable((EntityId)id.gen());
3964 }
3965
3969 GAIA_NODISCARD static bool uses_in_is_matching(const QueryTerm& term) {
3970 const auto id = term.id;
3971 return term.matchKind == QueryMatchKind::In && term.src == EntityBad && term.entTrav == EntityBad &&
3972 !term_has_variables(term) && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
3973 !is_variable((EntityId)id.gen());
3974 }
3975
3979 GAIA_NODISCARD static bool uses_non_direct_is_matching(const QueryTerm& term) {
3980 return uses_semantic_is_matching(term) || uses_in_is_matching(term);
3981 }
3982
3987 GAIA_NODISCARD static bool uses_potential_inherited_id_matching(const QueryTerm& term) {
3988 return query_term_uses_potential_inherited_id_matching(term);
3989 }
3990
3995 GAIA_NODISCARD static bool uses_inherited_id_matching(const World& world, const QueryTerm& term) {
3996 return uses_potential_inherited_id_matching(term) && world_term_uses_inherit_policy(world, term.id);
3997 }
3998
4001 GAIA_NODISCARD static bool match_entity_term(const World& world, Entity entity, const QueryTerm& term) {
4002 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
4003 return world_has_entity_term(world, entity, term.id);
4004 if (uses_in_is_matching(term))
4005 return world_has_entity_term_in(world, entity, term.id);
4006
4007 return world_has_entity_term_direct(world, entity, term.id);
4008 }
4009
4011 GAIA_NODISCARD static bool match_single_direct_target_term(
4012 const World& world, Entity entity, Entity termId, QueryCtx::DirectTargetEvalKind kind) {
4013 switch (kind) {
4016 return world_has_entity_term(world, entity, termId);
4018 return world_has_entity_term_in(world, entity, termId);
4020 return world_has_entity_term_direct(world, entity, termId);
4022 break;
4023 }
4024
4025 return false;
4026 }
4027
4028 GAIA_NODISCARD static uint32_t count_direct_term_entities(const World& world, const QueryTerm& term) {
4029 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
4030 return world_count_direct_term_entities(world, term.id);
4031 if (uses_in_is_matching(term))
4032 return world_count_in_term_entities(world, term.id);
4033
4034 return world_count_direct_term_entities_direct(world, term.id);
4035 }
4036
4037 static void collect_direct_term_entities(const World& world, const QueryTerm& term, cnt::darray<Entity>& out) {
4038 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term)) {
4039 world_collect_direct_term_entities(world, term.id, out);
4040 return;
4041 }
4042 if (uses_in_is_matching(term)) {
4043 world_collect_in_term_entities(world, term.id, out);
4044 return;
4045 }
4046
4047 world_collect_direct_term_entities_direct(world, term.id, out);
4048 }
4049
4050 template <typename Func>
4051 GAIA_NODISCARD static bool for_each_direct_term_entity(const World& world, const QueryTerm& term, Func&& func) {
4052 struct Visitor {
4053 Func& func;
4054 static bool thunk(void* ctx, Entity entity) {
4055 return static_cast<Visitor*>(ctx)->func(entity);
4056 }
4057 };
4058
4059 Visitor visitor{func};
4060 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
4061 return world_for_each_direct_term_entity(world, term.id, &visitor, &Visitor::thunk);
4062 if (uses_in_is_matching(term))
4063 return world_for_each_in_term_entity(world, term.id, &visitor, &Visitor::thunk);
4064
4065 return world_for_each_direct_term_entity_direct(world, term.id, &visitor, &Visitor::thunk);
4066 }
4067
4069 GAIA_NODISCARD static bool can_use_direct_entity_seed_eval(const QueryInfo& queryInfo) {
4070 if (!queryInfo.can_direct_entity_seed_eval_shape())
4071 return false;
4072
4073 const auto& world = *queryInfo.world();
4074 bool hasSeedableTerm = false;
4075 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4076 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Or)
4077 continue;
4078 if (uses_non_direct_is_matching(term) || uses_inherited_id_matching(world, term) ||
4080 hasSeedableTerm = true;
4081 }
4082
4083 return hasSeedableTerm;
4084 }
4085
4087 GAIA_NODISCARD static bool can_use_direct_target_eval(const QueryInfo& queryInfo) {
4088 return queryInfo.can_direct_target_eval();
4089 }
4090
4092 GAIA_NODISCARD static bool has_only_direct_or_terms(const QueryInfo& queryInfo) {
4093 return queryInfo.has_only_direct_or_terms();
4094 }
4095
4096 static void
4097 add_chunk_run(cnt::darray<detail::BfsChunkRun>& runs, const EntityContainer& ec, uint32_t entityOffset) {
4098 if (runs.empty()) {
4099 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
4100 return;
4101 }
4102
4103 auto& run = runs.back();
4104 if (ec.pChunk == run.pChunk && ec.row == run.to) {
4105 run.to = (uint16_t)(run.to + 1);
4106 return;
4107 }
4108
4109 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
4110 }
4111
4112 struct DirectEntitySeedInfo {
4113 Entity seededAllTerm = EntityBad;
4114 QueryMatchKind seededAllMatchKind = QueryMatchKind::Semantic;
4115 bool seededFromAll = false;
4116 bool seededFromOr = false;
4117 };
4118
4120 struct DirectEntitySeedPlan {
4121 Entity bestAllTerm = EntityBad;
4122 uint32_t bestAllTermCount = UINT32_MAX;
4123 QueryMatchKind bestAllTermMatchKind = QueryMatchKind::Semantic;
4124 bool hasAllTerms = false;
4125 bool hasOrTerms = false;
4126 bool preferOrSeed = false;
4127 };
4128
4129 struct DirectEntitySeedEvalPlan {
4132 const QueryTerm* pSingleAllTerm = nullptr;
4134 bool alwaysMatch = false;
4135 };
4136
4138 GAIA_NODISCARD static bool should_prefer_direct_seed_term(
4139 const World& world, const QueryTerm& candidate, uint32_t candidateCount, const DirectEntitySeedPlan& plan) {
4140 const bool candidateIsSemanticIs = uses_non_direct_is_matching(candidate);
4141 const bool bestIsSemanticIs = plan.bestAllTermMatchKind != QueryMatchKind::Direct &&
4142 plan.bestAllTerm.pair() && plan.bestAllTerm.id() == Is.id() &&
4143 !is_wildcard(plan.bestAllTerm.gen()) &&
4144 !is_variable((EntityId)plan.bestAllTerm.gen());
4145 const auto adjustedCandidateCount = candidateCount - (candidateIsSemanticIs && candidateCount > 0 ? 1U : 0U);
4146 const auto adjustedBestCount =
4147 plan.bestAllTermCount - (bestIsSemanticIs && plan.bestAllTermCount > 0 ? 1U : 0U);
4148 if (adjustedCandidateCount < adjustedBestCount)
4149 return true;
4150 if (adjustedCandidateCount > adjustedBestCount)
4151 return false;
4152 if (plan.bestAllTerm == EntityBad)
4153 return true;
4154
4155 if (candidateIsSemanticIs != bestIsSemanticIs)
4156 return candidateIsSemanticIs;
4157
4158 const bool candidateUsesInherited = uses_inherited_id_matching(world, candidate);
4159 const bool bestUsesInherited = plan.bestAllTermMatchKind == QueryMatchKind::Semantic &&
4160 !is_wildcard(plan.bestAllTerm) &&
4161 !is_variable((EntityId)plan.bestAllTerm.id()) &&
4162 (!plan.bestAllTerm.pair() || !is_variable((EntityId)plan.bestAllTerm.gen())) &&
4163 world_term_uses_inherit_policy(world, plan.bestAllTerm);
4164 if (candidateUsesInherited != bestUsesInherited)
4165 return !candidateUsesInherited;
4166
4167 return false;
4168 }
4169
4170 GAIA_NODISCARD static DirectEntitySeedPlan
4171 direct_entity_seed_plan(const World& world, const QueryInfo& queryInfo) {
4172 DirectEntitySeedPlan plan;
4173 uint32_t totalOrTermCount = 0;
4174
4175 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4176 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4177 continue;
4178 if (term.op == QueryOpKind::All) {
4179 plan.hasAllTerms = true;
4180 const auto cnt = count_direct_term_entities(world, term);
4181 if (should_prefer_direct_seed_term(world, term, cnt, plan)) {
4182 plan.bestAllTermCount = cnt;
4183 plan.bestAllTerm = term.id;
4184 plan.bestAllTermMatchKind = term.matchKind;
4185 }
4186 } else if (term.op == QueryOpKind::Or) {
4187 plan.hasOrTerms = true;
4188 totalOrTermCount += count_direct_term_entities(world, term);
4189 }
4190 }
4191
4192 plan.preferOrSeed = plan.hasOrTerms && (!plan.hasAllTerms || totalOrTermCount < plan.bestAllTermCount);
4193 return plan;
4194 }
4195
4197 GAIA_NODISCARD static bool match_direct_entity_terms(
4198 const World& world, Entity entity, const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
4199 bool hasOrTerms = false;
4200 bool anyOrMatched = false;
4201
4202 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4203 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4204 continue;
4205 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4206 term.matchKind == seedInfo.seededAllMatchKind)
4207 continue;
4208 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4209 continue;
4210
4211 const bool present = match_entity_term(world, entity, term);
4212 switch (term.op) {
4213 case QueryOpKind::All:
4214 if (!present)
4215 return false;
4216 break;
4217 case QueryOpKind::Or:
4218 hasOrTerms = true;
4219 anyOrMatched |= present;
4220 break;
4221 case QueryOpKind::Not:
4222 if (present)
4223 return false;
4224 break;
4225 case QueryOpKind::Any:
4226 case QueryOpKind::Count:
4227 break;
4228 }
4229 }
4230
4231 return !hasOrTerms || anyOrMatched;
4232 }
4233
4234 GAIA_NODISCARD static const QueryTerm*
4235 find_direct_all_seed_term(const QueryInfo& queryInfo, const DirectEntitySeedPlan& plan) {
4236 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4237 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4238 continue;
4239 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4240 term.matchKind != plan.bestAllTermMatchKind)
4241 continue;
4242 return &term;
4243 }
4244
4245 return nullptr;
4246 }
4247
4248 GAIA_NODISCARD static DirectEntitySeedEvalPlan
4249 direct_all_seed_eval_plan(const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
4250 DirectEntitySeedEvalPlan plan{};
4251
4252 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4253 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4254 return {};
4255 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4256 term.matchKind == seedInfo.seededAllMatchKind)
4257 continue;
4258
4259 if (term.op == QueryOpKind::All) {
4260 if (plan.pSingleAllTerm != nullptr)
4261 return {};
4262 plan.pSingleAllTerm = &term;
4263 continue;
4264 }
4265
4266 return {};
4267 }
4268
4269 plan.alwaysMatch = plan.pSingleAllTerm == nullptr;
4270 return plan;
4271 }
4272
4278 GAIA_NODISCARD static bool
4279 can_use_direct_seed_run_cache(const World& world, const QueryInfo& queryInfo, const QueryTerm& seedTerm) {
4280 if (!(uses_non_direct_is_matching(seedTerm) || uses_inherited_id_matching(world, seedTerm)))
4281 return false;
4282
4283 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4284 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4285 return false;
4286 if (term.op == QueryOpKind::Any || term.op == QueryOpKind::Count || term.op == QueryOpKind::Or)
4287 return false;
4288 if (term.op == QueryOpKind::All && term.id == seedTerm.id && term.matchKind == seedTerm.matchKind)
4289 continue;
4290 if (is_non_fragmenting_direct_term(world, term))
4291 return false;
4292 }
4293
4294 return true;
4295 }
4296
4297 GAIA_NODISCARD std::span<const detail::BfsChunkRun> cached_direct_seed_runs(
4298 QueryInfo& queryInfo, const QueryTerm& seedTerm, const DirectEntitySeedInfo& seedInfo,
4299 Constraints constraints) {
4300 auto& runData = ensure_direct_seed_run_data();
4301 auto& world = *queryInfo.world();
4302 const auto cachedConstraints = constraints;
4303 const auto relVersion = world_rel_version(world, Is);
4304 const auto worldVersion = ::gaia::ecs::world_version(world);
4305
4306 if (runData.cacheValid && runData.cachedSeedTerm == seedTerm.id &&
4307 runData.cachedSeedMatchKind == seedTerm.matchKind && runData.cachedConstraints == cachedConstraints &&
4308 runData.cachedRelVersion == relVersion && runData.cachedWorldVersion == worldVersion) {
4309 return {runData.cachedRuns.data(), runData.cachedRuns.size()};
4310 }
4311
4312 auto& runs = runData.cachedRuns;
4313 auto& entities = runData.cachedEntities;
4314 auto& chunkOrderedEntities = runData.cachedChunkOrderedEntities;
4315 runs.clear();
4316 entities.clear();
4317 chunkOrderedEntities.clear();
4318
4319 (void)for_each_direct_term_entity(world, seedTerm, [&](Entity entity) {
4320 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4321 return true;
4322
4323 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4324 return true;
4325
4326 entities.push_back(entity);
4327 return true;
4328 });
4329
4330 chunkOrderedEntities = entities;
4331 core::sort(chunkOrderedEntities, [&](Entity left, Entity right) {
4332 const auto& ecLeft = ::gaia::ecs::fetch(world, left);
4333 const auto& ecRight = ::gaia::ecs::fetch(world, right);
4334 if (ecLeft.pArchetype != ecRight.pArchetype)
4335 return ecLeft.pArchetype->id() < ecRight.pArchetype->id();
4336 if (ecLeft.pChunk != ecRight.pChunk)
4337 return ecLeft.pChunk < ecRight.pChunk;
4338 return ecLeft.row < ecRight.row;
4339 });
4340
4341 uint32_t entityOffset = 0;
4342 for (const auto entity: chunkOrderedEntities) {
4343 const auto& ec = ::gaia::ecs::fetch(world, entity);
4344 add_chunk_run(runs, ec, entityOffset++);
4345 }
4346
4347 runData.cachedSeedTerm = seedTerm.id;
4348 runData.cachedSeedMatchKind = seedTerm.matchKind;
4349 runData.cachedConstraints = cachedConstraints;
4350 runData.cachedRelVersion = relVersion;
4351 runData.cachedWorldVersion = worldVersion;
4352 runData.cacheValid = true;
4353 return {runs.data(), runs.size()};
4354 }
4355
4356 GAIA_NODISCARD std::span<const Entity> cached_direct_seed_chunk_entities(
4357 QueryInfo& queryInfo, const QueryTerm& seedTerm, const DirectEntitySeedInfo& seedInfo,
4358 Constraints constraints) {
4359 (void)cached_direct_seed_runs(queryInfo, seedTerm, seedInfo, constraints);
4360 auto& runData = ensure_direct_seed_run_data();
4361 return {runData.cachedChunkOrderedEntities.data(), runData.cachedChunkOrderedEntities.size()};
4362 }
4363
4364 template <typename Func>
4365 GAIA_NODISCARD static bool for_each_direct_all_seed(
4366 const World& world, const QueryInfo& queryInfo, const DirectEntitySeedPlan& plan, Constraints constraints,
4367 Func&& func) {
4368 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
4369 GAIA_ASSERT(pSeedTerm != nullptr);
4370 if (pSeedTerm == nullptr)
4371 return true;
4372
4373 DirectEntitySeedInfo seedInfo{};
4374 seedInfo.seededAllTerm = pSeedTerm->id;
4375 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
4376 seedInfo.seededFromAll = true;
4377 const auto evalPlan = direct_all_seed_eval_plan(queryInfo, seedInfo);
4378 const Archetype* pLastSingleAllArchetype = nullptr;
4379 bool lastSingleAllMatch = false;
4380 bool seedImpliesSingleAllTerm = false;
4381 if (evalPlan.pSingleAllTerm != nullptr && uses_non_direct_is_matching(*pSeedTerm) &&
4382 (uses_non_direct_is_matching(*evalPlan.pSingleAllTerm) ||
4383 uses_inherited_id_matching(world, *evalPlan.pSingleAllTerm))) {
4384 const auto seedTarget = pair_tgt(world, pSeedTerm->id);
4385 if (seedTarget != EntityBad)
4386 seedImpliesSingleAllTerm = match_entity_term(world, seedTarget, *evalPlan.pSingleAllTerm);
4387 }
4388
4389 // Stream the chosen ALL seed term directly. This avoids materializing a temporary
4390 // entity array for the common `all<T>().is(base)` shape.
4391 return for_each_direct_term_entity(world, *pSeedTerm, [&](Entity entity) {
4392 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4393 return true;
4394
4395 if (evalPlan.alwaysMatch)
4396 return func(entity);
4397 if (evalPlan.pSingleAllTerm != nullptr) {
4398 if (seedImpliesSingleAllTerm)
4399 return func(entity);
4400 if (uses_non_direct_is_matching(*evalPlan.pSingleAllTerm) ||
4401 uses_inherited_id_matching(world, *evalPlan.pSingleAllTerm)) {
4402 const auto* pArchetype = world_entity_archetype(world, entity);
4403 if (pArchetype != pLastSingleAllArchetype) {
4404 lastSingleAllMatch = match_entity_term(world, entity, *evalPlan.pSingleAllTerm);
4405 pLastSingleAllArchetype = pArchetype;
4406 }
4407 if (!lastSingleAllMatch)
4408 return true;
4409 } else if (!match_entity_term(world, entity, *evalPlan.pSingleAllTerm)) {
4410 return true;
4411 }
4412 return func(entity);
4413 }
4414 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4415 return true;
4416
4417 return func(entity);
4418 });
4419 }
4420
4422 GAIA_NODISCARD static bool match_direct_entity_constraints(
4423 const World& world, const QueryInfo& queryInfo, Entity entity, Constraints constraints) {
4424 if (!queryInfo.matches_prefab_entities() && world_entity_prefab(world, entity))
4425 return false;
4426
4427 if (constraints == Constraints::EnabledOnly)
4428 return world_entity_enabled(world, entity);
4429 if (constraints == Constraints::DisabledOnly)
4430 return !world_entity_enabled(world, entity);
4431 return true;
4432 }
4433
4435 GAIA_NODISCARD static bool can_use_archetype_bucket_count(
4436 const World& world, const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
4437 if (!seedInfo.seededFromAll && !seedInfo.seededFromOr)
4438 return false;
4439
4440 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4441 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4442 return false;
4443 if (seedInfo.seededFromAll && term.id == seedInfo.seededAllTerm && term.op == QueryOpKind::All)
4444 continue;
4445 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4446 continue;
4447 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Not)
4448 return false;
4449 if (is_non_fragmenting_direct_term(world, term))
4450 return false;
4451 if (uses_inherited_id_matching(world, term))
4452 return false;
4453 }
4454
4455 return true;
4456 }
4457
4459 GAIA_NODISCARD static uint32_t count_direct_entity_seed_by_archetype(
4460 const World& world, const QueryInfo& queryInfo, const cnt::darray<Entity>& seedEntities,
4461 const DirectEntitySeedInfo& seedInfo, Constraints constraints) {
4462 auto& scratch = direct_query_scratch();
4463
4464 scratch.archetypes.clear();
4465 scratch.bucketEntities.clear();
4466 scratch.counts.clear();
4467
4468 for (const auto entity: seedEntities) {
4469 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4470 continue;
4471
4472 const auto* pArchetype = world_entity_archetype(world, entity);
4473 const auto idx = core::get_index(scratch.archetypes, pArchetype);
4474 if (idx == BadIndex) {
4475 scratch.archetypes.push_back(pArchetype);
4476 scratch.bucketEntities.push_back(entity);
4477 scratch.counts.push_back(1);
4478 } else {
4479 ++scratch.counts[idx];
4480 }
4481 }
4482
4483 uint32_t cnt = 0;
4484 const auto archetypeCnt = (uint32_t)scratch.archetypes.size();
4485 GAIA_FOR(archetypeCnt) {
4486 if (match_direct_entity_terms(world, scratch.bucketEntities[i], queryInfo, seedInfo))
4487 cnt += scratch.counts[i];
4488 }
4489
4490 return cnt;
4491 }
4492
4494 GAIA_NODISCARD static uint32_t
4495 count_direct_or_union(const World& world, const QueryInfo& queryInfo, Constraints constraints) {
4496 auto& scratch = direct_query_scratch();
4497 const auto seenVersion = next_direct_query_seen_version(scratch);
4498 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
4499
4500 uint32_t cnt = 0;
4501 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4502 if (term.op != QueryOpKind::Or)
4503 continue;
4504
4505 (void)for_each_direct_term_entity(world, term, [&](Entity entity) {
4506 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4507 return true;
4508
4509 const auto entityId = (uint32_t)entity.id();
4510 ensure_direct_query_count_capacity(scratch, entityId);
4511
4512 if (scratch.counts[entityId] == seenVersion)
4513 return true;
4514 scratch.counts[entityId] = seenVersion;
4515
4516 bool rejected = false;
4517 if (hasDirectNotTerms) {
4518 for (const auto& notTerm: queryInfo.ctx().data.terms_view()) {
4519 if (notTerm.op != QueryOpKind::Not)
4520 continue;
4521 if (match_entity_term(world, entity, notTerm)) {
4522 rejected = true;
4523 break;
4524 }
4525 }
4526 }
4527
4528 if (!rejected)
4529 ++cnt;
4530 return true;
4531 });
4532 }
4533
4534 return cnt;
4535 }
4536
4542 GAIA_NODISCARD static bool
4543 is_empty_direct_or_union(const World& world, const QueryInfo& queryInfo, Constraints constraints) {
4544 auto& scratch = direct_query_scratch();
4545 const auto seenVersion = next_direct_query_seen_version(scratch);
4546 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
4547
4548 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4549 if (term.op != QueryOpKind::Or)
4550 continue;
4551
4552 const bool completed = for_each_direct_term_entity(world, term, [&](Entity entity) {
4553 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4554 return true;
4555
4556 const auto entityId = (uint32_t)entity.id();
4557 ensure_direct_query_count_capacity(scratch, entityId);
4558
4559 if (scratch.counts[entityId] == seenVersion)
4560 return true;
4561 scratch.counts[entityId] = seenVersion;
4562
4563 bool rejected = false;
4564 if (hasDirectNotTerms) {
4565 for (const auto& notTerm: queryInfo.ctx().data.terms_view()) {
4566 if (notTerm.op != QueryOpKind::Not)
4567 continue;
4568 if (match_entity_term(world, entity, notTerm)) {
4569 rejected = true;
4570 break;
4571 }
4572 }
4573 }
4574
4575 if (!rejected)
4576 return false;
4577 return true;
4578 });
4579
4580 if (!completed)
4581 return true;
4582 }
4583
4584 return false;
4585 }
4586
4588 static DirectEntitySeedInfo
4589 build_direct_entity_seed(const World& world, const QueryInfo& queryInfo, cnt::darray<Entity>& out) {
4590 auto& scratch = direct_query_scratch();
4591 out.clear();
4592 DirectEntitySeedInfo seedInfo{};
4593 const auto plan = direct_entity_seed_plan(world, queryInfo);
4594
4595 if (plan.hasAllTerms && !plan.preferOrSeed) {
4596 if (plan.bestAllTerm != EntityBad) {
4597 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4598 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4599 continue;
4600 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4601 term.matchKind != plan.bestAllTermMatchKind)
4602 continue;
4603 collect_direct_term_entities(world, term, out);
4604 seedInfo.seededAllMatchKind = term.matchKind;
4605 break;
4606 }
4607 seedInfo.seededFromAll = true;
4608 seedInfo.seededAllTerm = plan.bestAllTerm;
4609 }
4610 return seedInfo;
4611 }
4612
4613 const auto seenVersion = next_direct_query_seen_version(scratch);
4614
4615 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4616 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4617 continue;
4618 if (term.op != QueryOpKind::Or)
4619 continue;
4620
4621 scratch.termEntities.clear();
4622 collect_direct_term_entities(world, term, scratch.termEntities);
4623 for (const auto entity: scratch.termEntities) {
4624 const auto entityId = (uint32_t)entity.id();
4625 ensure_direct_query_count_capacity(scratch, entityId);
4626
4627 if (scratch.counts[entityId] == seenVersion)
4628 continue;
4629 scratch.counts[entityId] = seenVersion;
4630 out.push_back(entity);
4631 }
4632 }
4633
4634 seedInfo.seededFromOr = true;
4635 return seedInfo;
4636 }
4637
4641 GAIA_NODISCARD static bool has_direct_not_terms(const QueryInfo& queryInfo) {
4642 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4643 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4644 continue;
4645 if (term.op == QueryOpKind::Not)
4646 return true;
4647 }
4648
4649 return false;
4650 }
4651
4658 template <typename Func>
4659 void for_each_direct_or_union(World& world, const QueryInfo& queryInfo, Constraints constraints, Func&& func) {
4660 auto& scratch = direct_query_scratch();
4661 const auto seenVersion = next_direct_query_seen_version(scratch);
4662 DirectEntitySeedInfo seedInfo{};
4663 seedInfo.seededFromOr = true;
4664
4665 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4666 if (term.op != QueryOpKind::Or)
4667 continue;
4668
4669 (void)for_each_direct_term_entity(world, term, [&](Entity entity) {
4670 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4671 return true;
4672
4673 const auto entityId = (uint32_t)entity.id();
4674 ensure_direct_query_count_capacity(scratch, entityId);
4675
4676 if (scratch.counts[entityId] == seenVersion)
4677 return true;
4678 scratch.counts[entityId] = seenVersion;
4679
4680 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4681 return true;
4682
4683 func(entity);
4684 return true;
4685 });
4686 }
4687 }
4688
4695 template <bool UseFilters>
4696 GAIA_NODISCARD bool empty_inter(const QueryInfo& queryInfo, Constraints constraints) const {
4697 const auto cacheRange = selected_query_cache_range(queryInfo);
4698
4699 if constexpr (!UseFilters) {
4700 if (!cacheRange.hasSelectedGroup && can_use_direct_entity_seed_eval(queryInfo)) {
4701 if (has_only_direct_or_terms(queryInfo))
4702 return is_empty_direct_or_union(*queryInfo.world(), queryInfo, constraints);
4703
4704 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
4705 bool empty = true;
4706 (void)for_each_direct_all_seed(*queryInfo.world(), queryInfo, plan, constraints, [&](Entity) {
4707 empty = false;
4708 return false;
4709 });
4710 return empty;
4711 }
4712 }
4713
4714 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
4715 const auto cacheView = queryInfo.cache_archetype_view();
4716 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
4717 if (needsBarrierCache)
4718 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
4719 if (!cacheRange.valid)
4720 return true;
4721 const auto idxFrom = cacheRange.idxFrom;
4722 const auto idxTo = cacheRange.idxTo;
4723
4724 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
4725 const auto* pArchetype = cacheView[qi];
4726 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
4727 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
4728 continue;
4729
4730 GAIA_PROF_SCOPE(query::empty);
4731
4732 const auto& chunks = pArchetype->chunks();
4733 Iter it;
4734 it.init_query_state(queryInfo.world(), constraints, false);
4735 it.set_archetype(pArchetype);
4736
4737 if (!hasEntityFilters) {
4738 for (auto* pChunk: chunks) {
4739 uint16_t from = 0;
4740 uint16_t to = 0;
4741 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
4742 if (from == to)
4743 continue;
4744 it.set_chunk(pChunk, from, to);
4745 if constexpr (UseFilters) {
4746 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4747 continue;
4748 }
4749 return false;
4750 }
4751 continue;
4752 }
4753
4754 const bool isNotEmpty = core::has_if(chunks, [&](Chunk* pChunk) {
4755 uint16_t from = 0;
4756 uint16_t to = 0;
4757 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
4758 if (from == to)
4759 return false;
4760 it.set_chunk(pChunk, from, to);
4761 if constexpr (UseFilters)
4762 if (it.size() == 0 || !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4763 return false;
4764 if (!hasEntityFilters)
4765 return it.size() > 0;
4766
4767 const auto entities = it.template view<Entity>();
4768 const auto cnt = it.size();
4769 GAIA_FOR(cnt) {
4770 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
4771 return true;
4772 }
4773 return false;
4774 });
4775
4776 if (isNotEmpty)
4777 return false;
4778 }
4779
4780 return true;
4781 }
4782
4784 GAIA_NODISCARD static bool match_entity_filters(const World& world, Entity entity, const QueryInfo& queryInfo) {
4785 bool hasOrTerms = false;
4786 bool anyOrMatched = false;
4787 const bool hasEntityFilterTerms = queryInfo.has_entity_filter_terms();
4788
4789 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4790 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4791 continue;
4792
4793 const auto id = term.id;
4794 const bool isDirectIsTerm = uses_non_direct_is_matching(term);
4795 const bool isInheritedTerm = uses_inherited_id_matching(world, term);
4796 const bool isNonFragmentingTerm =
4797 (id.pair() && world_relation_uses_non_fragmenting_storage(world, pair_rel(world, id))) ||
4798 (!id.pair() && world_component_is_non_fragmenting(world, id));
4799 const bool needsEntityFilter = isNonFragmentingTerm || isDirectIsTerm || isInheritedTerm ||
4800 (hasEntityFilterTerms && term.op == QueryOpKind::Or);
4801 if (!needsEntityFilter)
4802 continue;
4803
4804 const bool present = match_entity_term(world, entity, term);
4805 switch (term.op) {
4806 case QueryOpKind::All:
4807 if (!present)
4808 return false;
4809 break;
4810 case QueryOpKind::Or:
4811 hasOrTerms = true;
4812 anyOrMatched |= present;
4813 break;
4814 case QueryOpKind::Not:
4815 if (present)
4816 return false;
4817 break;
4818 case QueryOpKind::Any:
4819 case QueryOpKind::Count:
4820 break;
4821 }
4822 }
4823
4824 return !hasOrTerms || anyOrMatched;
4825 }
4826
4832 GAIA_NODISCARD bool
4833 matches_target_entities(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
4834 if (targetEntities.empty())
4835 return false;
4836
4837 const auto& world = *queryInfo.world();
4838
4839 if (can_use_direct_target_eval(queryInfo)) {
4840 const auto directTargetEvalKind = queryInfo.direct_target_eval_kind();
4841 if (directTargetEvalKind != QueryCtx::DirectTargetEvalKind::Generic) {
4842 const auto termId = queryInfo.direct_target_eval_id();
4843 if (targetEntities.size() == 1) {
4844 const auto entity = targetEntities[0];
4845 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4846 return false;
4847 return match_single_direct_target_term(world, entity, termId, directTargetEvalKind);
4848 }
4849
4850 for (const auto entity: targetEntities) {
4851 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4852 continue;
4853 if (match_single_direct_target_term(world, entity, termId, directTargetEvalKind))
4854 return true;
4855 }
4856
4857 return false;
4858 }
4859
4860 const DirectEntitySeedInfo seedInfo{};
4861 if (targetEntities.size() == 1) {
4862 const auto entity = targetEntities[0];
4863 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4864 return false;
4865 return match_direct_entity_terms(world, entity, queryInfo, seedInfo);
4866 }
4867
4868 for (const auto entity: targetEntities) {
4869 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4870 continue;
4871 if (match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4872 return true;
4873 }
4874
4875 return false;
4876 }
4877
4878 if (!match_one(queryInfo, archetype, targetEntities))
4879 return false;
4880
4881 if (!queryInfo.has_entity_filter_terms())
4882 return true;
4883
4884 for (const auto entity: targetEntities) {
4885 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4886 continue;
4887 if (match_entity_filters(world, entity, queryInfo))
4888 return true;
4889 }
4890
4891 return false;
4892 }
4893
4900 template <bool UseFilters>
4901 GAIA_NODISCARD uint32_t count_inter(const QueryInfo& queryInfo, Constraints constraints) const {
4902 const auto cacheRange = selected_query_cache_range(queryInfo);
4903
4904 if constexpr (!UseFilters) {
4905 if (!cacheRange.hasSelectedGroup && can_use_direct_entity_seed_eval(queryInfo)) {
4906 auto& scratch = direct_query_scratch();
4907 if (has_only_direct_or_terms(queryInfo))
4908 return count_direct_or_union(*queryInfo.world(), queryInfo, constraints);
4909
4910 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
4911 const auto seedInfo = build_direct_entity_seed(*queryInfo.world(), queryInfo, scratch.entities);
4912
4913 if (can_use_archetype_bucket_count(*queryInfo.world(), queryInfo, seedInfo))
4914 return count_direct_entity_seed_by_archetype(
4915 *queryInfo.world(), queryInfo, scratch.entities, seedInfo, constraints);
4916
4917 uint32_t cnt = 0;
4918 (void)for_each_direct_all_seed(*queryInfo.world(), queryInfo, plan, constraints, [&](Entity) {
4919 ++cnt;
4920 return true;
4921 });
4922
4923 return cnt;
4924 }
4925 }
4926
4927 uint32_t cnt = 0;
4928 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
4929 const auto cacheView = queryInfo.cache_archetype_view();
4930 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
4931 if (needsBarrierCache)
4932 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
4933
4934 if (!cacheRange.valid)
4935 return 0;
4936 const auto idxFrom = cacheRange.idxFrom;
4937 const auto idxTo = cacheRange.idxTo;
4938
4939 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
4940 const auto* pArchetype = cacheView[qi];
4941 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
4942 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
4943 continue;
4944
4945 GAIA_PROF_SCOPE(query::count);
4946
4947 const auto& chunks = pArchetype->chunks();
4948 Iter it;
4949 it.init_query_state(queryInfo.world(), constraints, false);
4950 it.set_archetype(pArchetype);
4951
4952 if (!hasEntityFilters) {
4953 for (auto* pChunk: chunks) {
4954 uint16_t from = 0;
4955 uint16_t to = 0;
4956 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
4957 const uint16_t entityCnt = to - from;
4958 if (entityCnt == 0)
4959 continue;
4960 it.set_chunk(pChunk, from, to);
4961
4962 if constexpr (UseFilters) {
4963 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4964 continue;
4965 }
4966
4967 cnt += entityCnt;
4968 }
4969 continue;
4970 }
4971 for (auto* pChunk: chunks) {
4972 uint16_t from = 0;
4973 uint16_t to = 0;
4974 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
4975 const uint16_t entityCnt = to - from;
4976 if (entityCnt == 0)
4977 continue;
4978 it.set_chunk(pChunk, from, to);
4979
4980 // Filters
4981 if constexpr (UseFilters) {
4982 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4983 continue;
4984 }
4985
4986 if (hasEntityFilters) {
4987 const auto entities = it.template view<Entity>();
4988 GAIA_FOR(entityCnt) {
4989 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
4990 ++cnt;
4991 }
4992 continue;
4993 }
4994
4995 // Entity count
4996 cnt += entityCnt;
4997 }
4998 }
4999
5000 return cnt;
5001 }
5002
5003 static void init_direct_entity_iter(
5004 const QueryInfo& queryInfo, const World& world, const EntityContainer& ec, Iter& it, uint8_t* pIndices,
5005 Entity* pTermIds, const Archetype*& pLastArchetype) {
5006 GAIA_ASSERT(ec.pArchetype != nullptr);
5007 GAIA_ASSERT(ec.pChunk != nullptr);
5008 GAIA_ASSERT(ec.row < ec.pChunk->size());
5009
5010 if (ec.pArchetype != pLastArchetype) {
5011 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
5012 pIndices[i] = 0xFF;
5013 pTermIds[i] = EntityBad;
5014 }
5015
5016 const auto terms = queryInfo.ctx().data.terms_view();
5017 const auto queryIdCnt = (uint32_t)terms.size();
5018 auto indicesView = queryInfo.try_indices_mapping_view(ec.pArchetype);
5019 GAIA_FOR(queryIdCnt) {
5020 const auto& term = terms[i];
5021 const auto fieldIdx = term.fieldIndex;
5022 const auto queryId = term.id;
5023 pTermIds[fieldIdx] = queryId;
5024 if (!indicesView.empty()) {
5025 pIndices[fieldIdx] = indicesView[fieldIdx];
5026 continue;
5027 }
5028 if (!query_term_maps_to_current_archetype(term))
5029 continue;
5030
5031 if (!queryId.pair() && world_component_uses_sparse_storage(world, queryId)) {
5032#if GAIA_ASSERT_ENABLED
5033 const auto compIdx = core::get_index_unsafe(ec.pArchetype->ids_view(), queryId);
5034 GAIA_ASSERT(compIdx != BadIndex);
5035#endif
5036 pIndices[fieldIdx] = 0xFF;
5037 continue;
5038 }
5039
5040 auto compIdx = world_component_index_comp_idx(world, *ec.pArchetype, queryId);
5041 if (compIdx == BadIndex)
5042 compIdx = core::get_index(ec.pArchetype->ids_view(), queryId);
5043 pIndices[fieldIdx] = (uint8_t)compIdx;
5044 }
5045
5046 it.set_archetype(ec.pArchetype);
5047 it.set_comp_indices(pIndices);
5048 const auto inheritedDataView = queryInfo.inherited_data_view(ec.pArchetype);
5049 it.set_inherited_data(inheritedDataView);
5050 it.set_term_ids(pTermIds);
5051 pLastArchetype = ec.pArchetype;
5052 }
5053
5054 it.set_chunk(ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
5055 it.set_group_id(0);
5056 }
5057
5058 static void init_direct_entity_iter(
5059 const QueryInfo& queryInfo, const World& world, Entity entity, Iter& it, uint8_t* pIndices,
5060 Entity* pTermIds) {
5061 const auto& ec = ::gaia::ecs::fetch(world, entity);
5062 const Archetype* pLastArchetype = nullptr;
5063 it.set_world(&world);
5064 init_direct_entity_iter(queryInfo, world, ec, it, pIndices, pTermIds, pLastArchetype);
5065 }
5066
5067 template <typename Func>
5068 void each_chunk_runs_iter(
5069 QueryInfo& queryInfo, std::span<const detail::BfsChunkRun> runs, Constraints constraints, Func func) {
5070 auto& world = *queryInfo.world();
5071 Iter it;
5072 it.init_query_state(&world, constraints, false);
5073 const Archetype* pLastArchetype = nullptr;
5074 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5075 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5076
5077 for (const auto& run: runs) {
5078 const auto& ec = ::gaia::ecs::fetch(world, run.pChunk->entity_view()[run.from]);
5079 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
5080 it.set_chunk(run.pChunk, run.from, run.to);
5081 it.set_group_id(0);
5082 it.ctx(m_ctx);
5083 func(it);
5084 finish_iter_writes(it);
5085 it.clear_touched_writes();
5086 }
5087 }
5088
5089 struct DirectChunkArgEvalDesc {
5090 Entity id = EntityBad;
5091 bool isEntity = false;
5092 bool isPair = false;
5093 bool usesSparseStorage = false;
5094 };
5095
5101 GAIA_NODISCARD static bool can_use_direct_chunk_term_eval_arg(
5102 World& world, const QueryInfo& queryInfo, const DirectChunkArgEvalDesc& desc) {
5103 if (desc.isEntity)
5104 return true;
5105 if (desc.isPair)
5106 return false;
5107 if (world_component_uses_sparse_storage(world, desc.id))
5108 return false;
5109
5110 for (const auto& term: queryInfo.ctx().data.terms_view()) {
5111 if (term.id != desc.id)
5112 continue;
5113 if (!query_term_maps_to_current_archetype(term))
5114 return false;
5115 if (uses_non_direct_is_matching(term) || uses_inherited_id_matching(world, term) ||
5116 is_non_fragmenting_direct_term(world, term))
5117 return false;
5118 return true;
5119 }
5120
5121 return false;
5122 }
5123
5124 GAIA_NODISCARD static bool can_use_direct_chunk_term_eval_descs(
5125 World& world, const QueryInfo& queryInfo, const DirectChunkArgEvalDesc* pDescs, uint32_t descCnt) {
5126 if (queryInfo.has_entity_filter_terms())
5127 return false;
5128
5129 GAIA_FOR(descCnt) {
5130 if (!can_use_direct_chunk_term_eval_arg(world, queryInfo, pDescs[i]))
5131 return false;
5132 }
5133
5134 return true;
5135 }
5136
5143 GAIA_NODISCARD static bool can_use_sparse_chunk_term_eval_descs(
5144 World& world, const QueryInfo& queryInfo, const DirectChunkArgEvalDesc* pDescs, uint32_t descCnt) {
5145 if (queryInfo.has_entity_filter_terms())
5146 return false;
5147
5148 GAIA_FOR(descCnt) {
5149 const auto& desc = pDescs[i];
5150 if (!desc.usesSparseStorage) {
5151 if (!can_use_direct_chunk_term_eval_arg(world, queryInfo, desc))
5152 return false;
5153 continue;
5154 }
5155 bool found = false;
5156 for (const auto& term: queryInfo.ctx().data.terms_view()) {
5157 if (term.id != desc.id)
5158 continue;
5159 found = true;
5160 if (!query_term_maps_to_current_archetype(term) || uses_non_direct_is_matching(term) ||
5162 return false;
5163 break;
5164 }
5165 if (!found)
5166 return false;
5167 }
5168 return true;
5169 }
5170
5178 template <typename Func>
5179 void each_direct_entities_iter(
5180 QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints, Func func) {
5181 auto& world = *queryInfo.world();
5182 auto& walkData = ensure_each_walk_data();
5183 Iter it;
5184 it.init_query_state(&world, constraints, false);
5185 if (!walkData.cachedRuns.empty()) {
5186 each_chunk_runs_iter(queryInfo, walkData.cachedRuns, constraints, func);
5187 return;
5188 }
5189
5190 const Archetype* pLastArchetype = nullptr;
5191 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5192 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5193 for (const auto entity: entities) {
5194 const auto& ec = ::gaia::ecs::fetch(world, entity);
5195 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
5196 it.ctx(m_ctx);
5197 func(it);
5198 finish_iter_writes(it);
5199 it.clear_touched_writes();
5200 }
5201 }
5202
5209 template <typename Func>
5210 void each_direct_iter_inter(QueryInfo& queryInfo, Constraints constraints, Func func) {
5211 auto& world = *queryInfo.world();
5212 const bool hasWriteTerms = queryInfo.ctx().data.readWriteMask != 0;
5213 const auto plan = direct_entity_seed_plan(world, queryInfo);
5214
5215 auto exec_entity = [&](Entity entity) {
5216 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5217 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5218 Iter it;
5219 it.set_constraints(constraints);
5220 init_direct_entity_iter(queryInfo, world, entity, it, indices, termIds);
5221 it.set_write_im(false);
5222 it.ctx(m_ctx);
5223 func(it);
5224 finish_iter_writes(it);
5225 };
5226
5227 if (hasWriteTerms) {
5228 auto& scratch = direct_query_scratch();
5229 // Writable callbacks may add local overrides and reshuffle direct-term indices,
5230 // so direct-seeded execution must iterate a stable snapshot.
5231 const auto seedInfo = build_direct_entity_seed(world, queryInfo, scratch.entities);
5232 for (const auto entity: scratch.entities) {
5233 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
5234 continue;
5235 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
5236 continue;
5237 exec_entity(entity);
5238 }
5239 return;
5240 }
5241
5242 if (!plan.preferOrSeed) {
5243 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
5244 if (pSeedTerm != nullptr && can_use_direct_seed_run_cache(world, queryInfo, *pSeedTerm)) {
5245 DirectEntitySeedInfo seedInfo{};
5246 seedInfo.seededAllTerm = pSeedTerm->id;
5247 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
5248 seedInfo.seededFromAll = true;
5249 each_chunk_runs_iter(
5250 queryInfo, cached_direct_seed_runs(queryInfo, *pSeedTerm, seedInfo, constraints), constraints, func);
5251 return;
5252 }
5253 }
5254
5255 if (plan.preferOrSeed) {
5256 for_each_direct_or_union(world, queryInfo, constraints, exec_entity);
5257 return;
5258 }
5259
5260 (void)for_each_direct_all_seed(world, queryInfo, plan, constraints, [&](Entity entity) {
5261 exec_entity(entity);
5262 return true;
5263 });
5264 }
5265
5278 QueryInfo& queryInfo, Constraints constraints, void* pFunc, const TypedQueryExecState& state,
5279 void (*runDirectChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&), bool needsInheritedArgIds,
5280 void (*invokeInherited)(World&, Entity, const Entity*, void*));
5281
5283 template <bool UseFilters, typename ContainerOut>
5284 void arr_inter(QueryInfo& queryInfo, ContainerOut& outArray, Constraints constraints);
5286
5287 public:
5288 QueryImpl() = default;
5289 ~QueryImpl() = default;
5290
5300 World& world, QueryCache& queryCache, ArchetypeId& nextArchetypeId, uint32_t& worldVersion,
5301 const EntityToArchetypeMap& entityToArchetypeMap,
5302 const EntityToArchetypeVersionMap& entityToArchetypeMapVersions, const ArchetypeDArray& allArchetypes):
5303 m_nextArchetypeId(&nextArchetypeId), m_worldVersion(&worldVersion),
5304 m_entityToArchetypeMap(&entityToArchetypeMap),
5305 m_entityToArchetypeMapVersions(&entityToArchetypeMapVersions), m_allArchetypes(&allArchetypes) {
5306 m_storage.init(&world, &queryCache);
5307 }
5308
5309#if GAIA_ECS_TEST_HOOKS
5310 template <typename Func>
5311 GAIA_NODISCARD QueryPlan test_typed_plan(Func func);
5312
5316 GAIA_NODISCARD QueryPlan test_iter_plan(Constraints constraints = Constraints::EnabledOnly);
5317#endif
5318
5321 GAIA_NODISCARD QueryId id() const {
5322 if (!uses_query_cache_storage())
5323 return QueryIdBad;
5324 return m_storage.m_identity.handle.id();
5325 }
5326
5329 GAIA_NODISCARD uint32_t gen() const {
5330 if (!uses_query_cache_storage())
5331 return QueryIdBad;
5332 return m_storage.m_identity.handle.gen();
5333 }
5334
5335 //------------------------------------------------
5336
5338 void reset() {
5339 m_storage.reset();
5340 m_eachWalkData.reset();
5341 m_directSeedRunData.reset();
5342 reset_changed_filter_state();
5343 invalidate_each_walk_cache();
5344 invalidate_direct_seed_run_cache();
5345 }
5346
5348 void destroy() {
5349 (void)m_storage.try_del_from_cache();
5350 m_eachWalkData.reset();
5351 m_directSeedRunData.reset();
5352 reset_changed_filter_state();
5353 invalidate_each_walk_cache();
5354 invalidate_direct_seed_run_cache();
5355 }
5356
5359 GAIA_NODISCARD bool is_cached() const {
5360 return uses_query_cache_storage() && m_storage.is_cached();
5361 }
5362
5363 //------------------------------------------------
5364
5406 QueryImpl& add(const char* str, ...) {
5407 GAIA_ASSERT(str != nullptr);
5408 if (str == nullptr)
5409 return *this;
5410
5411 va_list args{};
5412 va_start(args, str);
5413
5414 uint32_t pos = 0;
5415 uint32_t exp0 = 0;
5416 uint32_t parentDepth = 0;
5417
5419 uint32_t varNamesCnt = 0;
5420 auto is_this_expr = [](std::span<const char> exprRaw) {
5421 auto expr = util::trim(exprRaw);
5422 return expr.size() == 5 && expr[0] == '$' && expr[1] == 't' && expr[2] == 'h' && expr[3] == 'i' &&
5423 expr[4] == 's';
5424 };
5425
5426 auto find_or_alloc_var = [&](std::span<const char> varExpr) -> Entity {
5427 auto varNameSpan = util::trim(varExpr);
5428 if (varNameSpan.empty())
5429 return EntityBad;
5430
5431 const util::str_view varName{varNameSpan.data(), (uint32_t)varNameSpan.size()};
5432 if (is_reserved_var_name(varName)) {
5433 GAIA_ASSERT2(false, "$this is reserved and can only be used as a source expression: Id($this)");
5434 return EntityBad;
5435 }
5436
5437 const auto namedVar = find_var_by_name(varName);
5438 if (namedVar != EntityBad)
5439 return namedVar;
5440
5441 GAIA_FOR(varNamesCnt) {
5442 if (varNames[i].size() != varName.size())
5443 continue;
5444 if (varNames[i].size() > 0 && memcmp(varNames[i].data(), varName.data(), varName.size()) != 0)
5445 continue;
5446 return query_var_entity(i);
5447 }
5448
5449 if (varNamesCnt >= varNames.size()) {
5450 GAIA_ASSERT2(false, "Too many query variables in expression");
5451 return EntityBad;
5452 }
5453
5454 const auto idx = varNamesCnt++;
5455 varNames[idx] = varName;
5456
5457 const auto varEntity = query_var_entity(idx);
5458 (void)set_var_name_internal(varEntity, varName);
5459 return varEntity;
5460 };
5461
5462 auto parse_entity_expr = [&](auto&& self, std::span<const char> exprRaw) -> Entity {
5463 auto expr = util::trim(exprRaw);
5464 if (expr.empty())
5465 return EntityBad;
5466
5467 if (expr[0] == '$')
5468 return find_or_alloc_var(expr.subspan(1));
5469
5470 if (expr[0] == '(') {
5471 if (expr.back() != ')') {
5472 GAIA_ASSERT2(false, "Expression '(' not terminated");
5473 return EntityBad;
5474 }
5475
5476 const auto idStr = expr.subspan(1, expr.size() - 2);
5477 const auto commaIdx = core::get_index(idStr, ',');
5478 if (commaIdx == BadIndex) {
5479 GAIA_ASSERT2(false, "Pair expression does not contain ','");
5480 return EntityBad;
5481 }
5482
5483 const auto first = self(self, idStr.subspan(0, commaIdx));
5484 if (first == EntityBad)
5485 return EntityBad;
5486 const auto second = self(self, idStr.subspan(commaIdx + 1));
5487 if (second == EntityBad)
5488 return EntityBad;
5489
5490 return ecs::Pair(first, second);
5491 }
5492
5493 return expr_to_entity((const World&)*m_storage.world(), args, expr);
5494 };
5495
5496 auto parse_src_expr = [&](std::span<const char> srcExprRaw, Entity& srcOut) -> bool {
5497 auto srcExpr = util::trim(srcExprRaw);
5498 if (srcExpr.empty())
5499 return false;
5500
5501 // `$this` explicitly means the default source for the term.
5502 if (is_this_expr(srcExpr)) {
5503 srcOut = EntityBad;
5504 return true;
5505 }
5506
5507 srcOut = parse_entity_expr(parse_entity_expr, srcExpr);
5508 return srcOut != EntityBad;
5509 };
5510
5511 auto parse_term_expr = [&](std::span<const char> exprRaw, Entity& id, QueryTermOptions& options) -> bool {
5512 auto expr = util::trim(exprRaw);
5513 if (expr.empty())
5514 return false;
5515
5516 if (expr.back() == ')') {
5517 int32_t depth = 0;
5518 int32_t openIdx = -1;
5519 for (int32_t i = (int32_t)expr.size() - 1; i >= 0; --i) {
5520 if (expr[(uint32_t)i] == ')')
5521 ++depth;
5522 else if (expr[(uint32_t)i] == '(') {
5523 --depth;
5524 if (depth == 0) {
5525 openIdx = i;
5526 break;
5527 }
5528 }
5529 }
5530
5531 // `Id(src)` form. Keep `(Rel,Tgt)` intact by requiring a non-empty prefix.
5532 if (openIdx > 0) {
5533 auto idExpr = util::trim(expr.subspan(0, (uint32_t)openIdx));
5534 auto srcExpr = util::trim(expr.subspan((uint32_t)openIdx + 1, expr.size() - (uint32_t)openIdx - 2));
5535 if (!idExpr.empty() && !srcExpr.empty()) {
5536 id = parse_entity_expr(parse_entity_expr, idExpr);
5537 if (id == EntityBad)
5538 return false;
5539
5540 Entity src = EntityBad;
5541 if (!parse_src_expr(srcExpr, src))
5542 return false;
5543
5544 options.src(src);
5545 return true;
5546 }
5547 }
5548 }
5549
5550 id = parse_entity_expr(parse_entity_expr, expr);
5551 return id != EntityBad;
5552 };
5553
5554 auto add_term = [&](QueryOpKind op, std::span<const char> exprRaw) {
5555 auto expr = util::trim(exprRaw);
5556 if (expr.empty())
5557 return false;
5558
5559 bool isReadWrite = false;
5560 if (!expr.empty() && expr[0] == '&') {
5561 isReadWrite = true;
5562 expr = util::trim(expr.subspan(1));
5563 }
5564
5565 QueryTermOptions options{};
5566 if (isReadWrite)
5567 options.write();
5568
5569 Entity entity = EntityBad;
5570 if (!parse_term_expr(expr, entity, options))
5571 return false;
5572
5573 switch (op) {
5574 case QueryOpKind::All:
5575 all(entity, options);
5576 break;
5577 case QueryOpKind::Or:
5578 or_(entity, options);
5579 break;
5580 case QueryOpKind::Not:
5581 no(entity, options);
5582 break;
5583 case QueryOpKind::Any:
5584 any(entity, options);
5585 break;
5586 default:
5587 GAIA_ASSERT(false);
5588 return false;
5589 }
5590
5591 return true;
5592 };
5593
5594 auto process = [&]() {
5595 std::span<const char> exprRaw(&str[exp0], pos - exp0);
5596 exp0 = ++pos;
5597
5598 auto expr = util::trim(exprRaw);
5599 if (expr.empty())
5600 return true;
5601
5602 // OR-chain at top level maps to query::or_ terms.
5603 bool hasOrChain = false;
5604 {
5605 uint32_t depth = 0;
5606 const auto cnt = (uint32_t)expr.size();
5607 for (uint32_t i = 0; i + 1 < cnt; ++i) {
5608 const auto ch = expr[i];
5609 if (ch == '(')
5610 ++depth;
5611 else if (ch == ')') {
5612 GAIA_ASSERT(depth > 0);
5613 --depth;
5614 } else if (depth == 0 && ch == '|' && expr[i + 1] == '|') {
5615 hasOrChain = true;
5616 break;
5617 }
5618 }
5619 }
5620
5621 if (hasOrChain) {
5622 uint32_t depth = 0;
5623 uint32_t partBeg = 0;
5624 const auto cnt = (uint32_t)expr.size();
5625 for (uint32_t i = 0; i < cnt; ++i) {
5626 const auto ch = expr[i];
5627 if (ch == '(')
5628 ++depth;
5629 else if (ch == ')') {
5630 GAIA_ASSERT(depth > 0);
5631 --depth;
5632 }
5633
5634 const bool isOr = i + 1 < cnt && depth == 0 && ch == '|' && expr[i + 1] == '|';
5635 const bool isEnd = i + 1 == cnt;
5636 if (!isOr && !isEnd)
5637 continue;
5638
5639 const auto partEnd = isOr ? i : (i + 1);
5640 auto partExpr = expr.subspan(partBeg, partEnd - partBeg);
5641 if (!add_term(QueryOpKind::Or, partExpr))
5642 return false;
5643
5644 if (isOr) {
5645 partBeg = i + 2;
5646 ++i;
5647 }
5648 }
5649
5650 return true;
5651 }
5652
5653 QueryOpKind op = QueryOpKind::All;
5654 if (expr[0] == '?') {
5655 op = QueryOpKind::Any;
5656 expr = util::trim(expr.subspan(1));
5657 } else if (expr[0] == '!') {
5658 op = QueryOpKind::Not;
5659 expr = util::trim(expr.subspan(1));
5660 }
5661
5662 return add_term(op, expr);
5663 };
5664
5665 for (; str[pos] != 0; ++pos) {
5666 if (str[pos] == '(')
5667 ++parentDepth;
5668 else if (str[pos] == ')') {
5669 GAIA_ASSERT(parentDepth > 0);
5670 --parentDepth;
5671 } else if (str[pos] == ',' && parentDepth == 0) {
5672 if (!process())
5673 goto add_end;
5674 }
5675 }
5676 process();
5677
5678 add_end:
5679 va_end(args);
5680 return *this;
5681 }
5682
5687 // Add commands to the command buffer
5688 add_inter(item);
5689 return *this;
5690 }
5691
5692 //------------------------------------------------
5693
5698 QueryImpl& is(Entity entity, const QueryTermOptions& options = QueryTermOptions{}) {
5699 return all(Pair(Is, entity), options);
5700 }
5701
5702 //------------------------------------------------
5703
5709 options.in();
5710 return all(Pair(Is, entity), options);
5711 }
5712
5713 //------------------------------------------------
5714
5720 add_entity_term(QueryOpKind::All, entity, options);
5721 return *this;
5722 }
5723
5728 template <typename T>
5730
5734 template <typename T>
5736
5737 //------------------------------------------------
5738
5744 add_entity_term(QueryOpKind::Any, entity, options);
5745 return *this;
5746 }
5747
5752 template <typename T>
5754
5758 template <typename T>
5760
5761 //------------------------------------------------
5762
5769 add_entity_term(QueryOpKind::Or, entity, options);
5770 return *this;
5771 }
5772
5777 template <typename T>
5779
5783 template <typename T>
5785
5786 //------------------------------------------------
5787
5792 QueryImpl& no(Entity entity, const QueryTermOptions& options = QueryTermOptions{}) {
5793 add_entity_term(QueryOpKind::Not, entity, options);
5794 return *this;
5795 }
5796
5801 template <typename T>
5803
5807 template <typename T>
5809
5817 [[maybe_unused]] const bool ok = set_var_name_internal(varEntity, name);
5818 GAIA_ASSERT(ok);
5819 return *this;
5820 }
5825 QueryImpl& var_name(Entity varEntity, const char* name) {
5826 GAIA_ASSERT(name != nullptr);
5827 if (name == nullptr)
5828 return *this;
5829 return var_name(varEntity, util::str_view{name, (uint32_t)GAIA_STRLEN(name, 256)});
5830 }
5831
5837 QueryImpl& set_var(Entity varEntity, Entity value) {
5838 const bool ok = is_query_var_entity(varEntity);
5839 GAIA_ASSERT(ok);
5840 if (!ok)
5841 return *this;
5842
5843 const auto idx = query_var_idx(varEntity);
5844 m_varBindings[idx] = value;
5845 m_varBindingsMask |= (uint8_t(1) << idx);
5846 return *this;
5847 }
5853 const auto varEntity = find_var_by_name(name);
5854 GAIA_ASSERT(varEntity != EntityBad);
5855 if (varEntity == EntityBad)
5856 return *this;
5857 return set_var(varEntity, value);
5858 }
5863 QueryImpl& set_var(const char* name, Entity value) {
5864 GAIA_ASSERT(name != nullptr);
5865 if (name == nullptr)
5866 return *this;
5867 return set_var(util::str_view{name, (uint32_t)GAIA_STRLEN(name, 256)}, value);
5868 }
5869
5875 const bool ok = is_query_var_entity(varEntity);
5876 GAIA_ASSERT(ok);
5877 if (!ok)
5878 return *this;
5879
5880 const auto idx = query_var_idx(varEntity);
5881 m_varBindingsMask &= (uint8_t)~(uint8_t(1) << idx);
5882 return *this;
5883 }
5887 m_varBindingsMask = 0;
5888 return *this;
5889 }
5890
5891 //------------------------------------------------
5892
5897 changed_inter(entity);
5898 return *this;
5899 }
5900
5904 template <typename T>
5906
5907 //------------------------------------------------
5908
5915 QueryImpl& sort_by(Entity entity, TSortByFunc func) {
5916 sort_by_inter(entity, func);
5917 return *this;
5918 }
5919
5925 template <typename T>
5926 QueryImpl& sort_by(TSortByFunc func);
5927
5934 template <typename Rel, typename Tgt>
5935 QueryImpl& sort_by(TSortByFunc func);
5936
5937 //------------------------------------------------
5938
5940 class OrderByTravView final {
5941 QueryImpl* m_query = nullptr;
5942 Entity m_relation = EntityBad;
5943 TravOrder m_order = TravOrder::Down;
5944
5945 public:
5950 OrderByTravView(QueryImpl& query, Entity relation, TravOrder order):
5951 m_query(&query), m_relation(relation), m_order(order) {}
5952
5956 template <typename Func>
5957 void each(Func func) {
5958 m_query->each_walk(func, m_relation, m_order);
5959 }
5960 };
5961
5962 //------------------------------------------------
5963
5978 GAIA_NODISCARD OrderByTravView order_by(Entity relation, TravOrder order) {
5979 return OrderByTravView(*this, relation, order);
5980 }
5981
5987 template <typename Rel>
5988 GAIA_NODISCARD OrderByTravView order_by(TravOrder order);
5989
5990 //------------------------------------------------
5991
5999 QueryImpl& depth_order(Entity relation = ChildOf) {
6000 GAIA_ASSERT(!relation.pair());
6001 GAIA_ASSERT(world_relation_supports_depth_order(*m_storage.world(), relation));
6002 group_by_inter(relation, group_by_func_depth_order, true);
6003 return *this;
6004 }
6005
6009 template <typename Rel>
6011
6012 //------------------------------------------------
6013
6020 QueryImpl& group_by(Entity entity, TGroupByFunc func = group_by_func_default) {
6021 group_by_inter(entity, func);
6022 return *this;
6023 }
6024
6031 template <typename T>
6032 QueryImpl& group_by(TGroupByFunc func = group_by_func_default);
6033
6041 template <typename Rel, typename Tgt>
6042 QueryImpl& group_by(TGroupByFunc func = group_by_func_default);
6043
6044 //------------------------------------------------
6045
6051 group_dep_inter(relation);
6052 return *this;
6053 }
6054
6059 template <typename Rel>
6061
6062 //------------------------------------------------
6063
6067 QueryImpl& group_id(GroupId groupId) {
6068 set_group_id_inter(groupId);
6069 return *this;
6070 }
6071
6076 GAIA_ASSERT(!entity.pair());
6077 set_group_id_inter(entity.id());
6078 return *this;
6079 }
6080
6084 template <typename T>
6086
6092 template <typename Container>
6093 void groups(Container& out, bool sortGroups) {
6094 auto& queryInfo = fetch();
6095 match_all(queryInfo);
6096 queryInfo.group_ids(out, sortGroups);
6097 }
6098
6099 //------------------------------------------------
6100
6114 template <typename Func>
6115 GAIA_NODISCARD SchedJob job(Func func, QueryExecType execType) {
6116 if constexpr (detail::is_query_iter_callback_v<Func>) {
6117 switch (execType) {
6118 case QueryExecType::Parallel:
6119 return add_iter_parallel_job<Func, QueryExecType::Parallel>(GAIA_MOV(func));
6120 case QueryExecType::ParallelPerf:
6121 return add_iter_parallel_job<Func, QueryExecType::ParallelPerf>(GAIA_MOV(func));
6122 case QueryExecType::ParallelEff:
6123 return add_iter_parallel_job<Func, QueryExecType::ParallelEff>(GAIA_MOV(func));
6124 default:
6125 break;
6126 }
6127 } else {
6128 switch (execType) {
6129 case QueryExecType::Parallel:
6130 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::Parallel>(
6131 TypedJobCallback<Func>{this, GAIA_MOV(func)});
6132 case QueryExecType::ParallelPerf:
6133 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelPerf>(
6134 TypedJobCallback<Func>{this, GAIA_MOV(func)});
6135 case QueryExecType::ParallelEff:
6136 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelEff>(
6137 TypedJobCallback<Func>{this, GAIA_MOV(func)});
6138 default:
6139 break;
6140 }
6141 }
6142
6143 return add_query_task_job(GAIA_MOV(func), execType);
6144 }
6145
6150 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
6151 void each(Func func) {
6152 each_runtime_inter<QueryExecType::Default, Func>(func, Constraints::EnabledOnly);
6153 }
6154
6158 template <typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>, int> = 0>
6159 void each(Func func);
6160
6166 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
6167 void each(Func func, QueryExecType execType) {
6168 each(func, execType, Constraints::EnabledOnly);
6169 }
6170
6175 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
6176 void each(Func func, Constraints constraints) {
6177 each(func, QueryExecType::Default, constraints);
6178 }
6179
6185 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
6186 void each(Func func, QueryExecType execType, Constraints constraints) {
6187 switch (execType) {
6188 case QueryExecType::Parallel:
6189 each_runtime_inter<QueryExecType::Parallel, Func>(func, constraints);
6190 break;
6191 case QueryExecType::ParallelPerf:
6192 each_runtime_inter<QueryExecType::ParallelPerf, Func>(func, constraints);
6193 break;
6194 case QueryExecType::ParallelEff:
6195 each_runtime_inter<QueryExecType::ParallelEff, Func>(func, constraints);
6196 break;
6197 default:
6198 each_runtime_inter<QueryExecType::Default, Func>(func, constraints);
6199 break;
6200 }
6201 }
6202
6207 template <typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>, int> = 0>
6208 void each(Func func, QueryExecType execType);
6209
6216 template <typename Func>
6217 void each_iter(Iter& it, Func func);
6218
6220 void each_iter_erased(
6221 QueryExecType execType, void* pFunc, const TypedQueryExecState& state,
6222 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
6223 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
6224
6225 void each_iter_erased(
6226 Iter& it, void* pFunc, const TypedQueryExecState& state,
6227 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
6228 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
6230
6231 //------------------------------------------------
6232
6238 template <typename Func>
6239 void each_arch(Func func, Constraints constraints = Constraints::EnabledOnly) {
6240 auto& queryInfo = fetch();
6241 match_all(queryInfo);
6242 run_query_on_archetypes<QueryExecType::Default>(
6243 queryInfo,
6244 [&](Iter& it) {
6245 GAIA_PROF_SCOPE(query_func_a);
6246 it.ctx(m_ctx);
6247 func(it);
6248 },
6249 constraints);
6250 }
6251
6252 //------------------------------------------------
6253
6263 bool empty(Constraints constraints = Constraints::EnabledOnly) {
6264 auto& queryInfo = fetch();
6265 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6266 return empty_inter<false>(queryInfo, constraints);
6267 }
6268
6269 match_all(queryInfo);
6270
6271 const bool hasFilters = queryInfo.has_filters();
6272 if (hasFilters) {
6273 return empty_inter<true>(queryInfo, constraints);
6274 } else {
6275 return empty_inter<false>(queryInfo, constraints);
6276 }
6277 }
6278
6287 uint32_t count(Constraints constraints = Constraints::EnabledOnly) {
6288 auto& queryInfo = fetch();
6289 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6290 return count_inter<false>(queryInfo, constraints);
6291 }
6292
6293 match_all(queryInfo);
6294
6295 const bool hasFilters = queryInfo.has_filters();
6296 return hasFilters ? count_inter<true>(queryInfo, constraints) : count_inter<false>(queryInfo, constraints);
6297 }
6298
6302 void each_entity_enabled(void* pCtx, void (*func)(void*, Entity)) {
6303 auto& queryInfo = fetch();
6304 match_all(queryInfo);
6305 ::gaia::ecs::update_version(*m_worldVersion);
6306
6307 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6308 auto& world = *queryInfo.world();
6309 if (has_only_direct_or_terms(queryInfo)) {
6310 for_each_direct_or_union(world, queryInfo, Constraints::EnabledOnly, [&](Entity entity) {
6311 func(pCtx, entity);
6312 return true;
6313 });
6314 } else {
6315 const auto plan = direct_entity_seed_plan(world, queryInfo);
6316 (void)for_each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](Entity entity) {
6317 func(pCtx, entity);
6318 return true;
6319 });
6320 }
6321
6322 m_changedWorldVersion = *m_worldVersion;
6323 return;
6324 }
6325
6326 const bool hasFilters = queryInfo.has_filters();
6327 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
6328 const auto cacheView = queryInfo.cache_archetype_view();
6329 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, Constraints::EnabledOnly);
6330 if (needsBarrierCache)
6332
6333 const auto cacheRange = selected_query_cache_range(queryInfo);
6334 if (!cacheRange.valid) {
6335 m_changedWorldVersion = *m_worldVersion;
6336 return;
6337 }
6338 const auto idxFrom = cacheRange.idxFrom;
6339 const auto idxTo = cacheRange.idxTo;
6340
6341 Iter it;
6342 it.init_query_state(queryInfo.world(), Constraints::EnabledOnly, false);
6343 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6344 const auto* pArchetype = cacheView[qi];
6345 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
6346 if GAIA_UNLIKELY (!can_process_archetype_inter(
6347 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6348 continue;
6349
6350 const auto& chunks = pArchetype->chunks();
6351 if (!hasEntityFilters) {
6352 for (auto* pChunk: chunks) {
6353 const auto from = Iter::start_index(pChunk);
6354 const auto to = Iter::end_index(pChunk);
6355 if (from == to)
6356 continue;
6357 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6358 continue;
6359
6360 const auto entityCnt = (uint32_t)(to - from);
6361 const auto entities = pChunk->entity_view();
6362 GAIA_FOR(entityCnt) {
6363 func(pCtx, entities[from + i]);
6364 }
6365 }
6366 continue;
6367 }
6368
6369 it.set_archetype(pArchetype);
6370 for (auto* pChunk: chunks) {
6371 it.set_chunk(pChunk);
6372 const auto entityCnt = it.size();
6373 if (entityCnt == 0)
6374 continue;
6375 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6376 continue;
6377
6378 const auto entities = it.view<Entity>();
6379 GAIA_FOR(entityCnt) {
6380 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
6381 func(pCtx, entities[i]);
6382 }
6383 }
6384 }
6385
6386 m_changedWorldVersion = *m_worldVersion;
6387 }
6388
6390 void collect_entities_enabled(cnt::darray<Entity>& out) {
6391 auto& queryInfo = fetch();
6392 match_all(queryInfo);
6393 ::gaia::ecs::update_version(*m_worldVersion);
6394
6395 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6396 auto& world = *queryInfo.world();
6397 if (has_only_direct_or_terms(queryInfo)) {
6398 for_each_direct_or_union(world, queryInfo, Constraints::EnabledOnly, [&](Entity entity) {
6399 out.push_back(entity);
6400 return true;
6401 });
6402 } else {
6403 const auto plan = direct_entity_seed_plan(world, queryInfo);
6404 (void)for_each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](Entity entity) {
6405 out.push_back(entity);
6406 return true;
6407 });
6408 }
6409
6410 m_changedWorldVersion = *m_worldVersion;
6411 return;
6412 }
6413
6414 const bool hasFilters = queryInfo.has_filters();
6415 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
6416 const auto cacheView = queryInfo.cache_archetype_view();
6417 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, Constraints::EnabledOnly);
6418 if (needsBarrierCache)
6420
6421 const auto cacheRange = selected_query_cache_range(queryInfo);
6422 if (!cacheRange.valid) {
6423 m_changedWorldVersion = *m_worldVersion;
6424 return;
6425 }
6426 const auto idxFrom = cacheRange.idxFrom;
6427 const auto idxTo = cacheRange.idxTo;
6428
6429 Iter it;
6430 it.init_query_state(queryInfo.world(), Constraints::EnabledOnly, false);
6431 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6432 const auto* pArchetype = cacheView[qi];
6433 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
6434 if GAIA_UNLIKELY (!can_process_archetype_inter(
6435 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6436 continue;
6437
6438 const auto& chunks = pArchetype->chunks();
6439 if (!hasEntityFilters) {
6440 for (auto* pChunk: chunks) {
6441 const auto from = Iter::start_index(pChunk);
6442 const auto to = Iter::end_index(pChunk);
6443 if (from == to)
6444 continue;
6445 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6446 continue;
6447
6448 const auto oldSize = out.size();
6449 const auto entityCnt = (uint32_t)(to - from);
6450 const auto entities = pChunk->entity_view();
6451 out.resize(oldSize + entityCnt);
6452 GAIA_FOR(entityCnt) {
6453 out[oldSize + i] = entities[from + i];
6454 }
6455 }
6456 continue;
6457 }
6458
6459 it.set_archetype(pArchetype);
6460 for (auto* pChunk: chunks) {
6461 it.set_chunk(pChunk);
6462 const auto entityCnt = it.size();
6463 if (entityCnt == 0)
6464 continue;
6465 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6466 continue;
6467
6468 const auto entities = it.view<Entity>();
6469 GAIA_FOR(entityCnt) {
6470 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
6471 out.push_back(entities[i]);
6472 }
6473 }
6474 }
6475
6476 m_changedWorldVersion = *m_worldVersion;
6477 }
6478
6484 template <typename Container>
6485 void arr(Container& outArray, Constraints constraints = Constraints::EnabledOnly);
6486
6493 GAIA_NODISCARD std::span<const Entity> ordered_entities_walk(
6494 QueryInfo& queryInfo, Entity relation, TravOrder order,
6495 Constraints constraints = Constraints::EnabledOnly) {
6496 struct OrderedWalkTargetCtx {
6497 const cnt::darray<Entity>* pEntities = nullptr;
6498 uint32_t cnt = 0;
6499 uint32_t dependentIdx = 0;
6500 cnt::darray<uint32_t>* pIndegree = nullptr;
6501 cnt::darray<uint32_t>* pOutdegree = nullptr;
6502 cnt::darray<uint32_t>* pWriteCursor = nullptr;
6503 cnt::darray<uint32_t>* pEdges = nullptr;
6504 uint32_t* pEdgeCnt = nullptr;
6505
6506 GAIA_NODISCARD static uint32_t
6507 find_entity_idx(const cnt::darray<Entity>& entities, uint32_t cnt, Entity entity) {
6508 const auto targetId = entity.id();
6509 uint32_t low = 0;
6510 uint32_t high = cnt;
6511 while (low < high) {
6512 const uint32_t mid = low + ((high - low) >> 1);
6513 if (entities[mid].id() < targetId)
6514 low = mid + 1;
6515 else
6516 high = mid;
6517 }
6518
6519 if (low < cnt && entities[low].id() == targetId)
6520 return low;
6521 return cnt;
6522 }
6523
6524 static void count_edge(void* rawCtx, Entity dependency) {
6525 auto& ctx = *static_cast<OrderedWalkTargetCtx*>(rawCtx);
6526 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6527 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6528 return;
6529
6530 ++(*ctx.pOutdegree)[dependencyIdx];
6531 ++(*ctx.pIndegree)[ctx.dependentIdx];
6532 ++*ctx.pEdgeCnt;
6533 }
6534
6535 static void write_edge(void* rawCtx, Entity dependency) {
6536 auto& ctx = *static_cast<OrderedWalkTargetCtx*>(rawCtx);
6537 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6538 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6539 return;
6540
6541 (*ctx.pEdges)[(*ctx.pWriteCursor)[dependencyIdx]++] = ctx.dependentIdx;
6542 }
6543 };
6544
6545 auto& walkData = ensure_each_walk_data();
6546 auto& world = *m_storage.world();
6547 const uint32_t relationVersion = world_rel_version(world, relation);
6548 const uint32_t worldVersion = ::gaia::ecs::world_version(world);
6549 const uint32_t resultCacheRevision = queryInfo.result_cache_rev();
6550
6551 const bool needsTraversalBarrierState =
6552 constraints == Constraints::EnabledOnly && ::gaia::ecs::valid(world, relation);
6553 auto survives_disabled_barrier = [&](Entity entity) {
6554 if (!needsTraversalBarrierState)
6555 return true;
6556
6557 auto curr = entity;
6558 GAIA_FOR(MAX_TRAV_DEPTH) {
6559 const auto next = target(world, curr, relation);
6560 if (next == EntityBad || next == curr)
6561 break;
6562 if (!world_entity_enabled(world, next))
6563 return false;
6564 curr = next;
6565 }
6566
6567 return true;
6568 };
6569
6570 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
6571 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
6572 walkData.cachedEntityVersion == worldVersion &&
6573 walkData.cachedResultCacheRevision == resultCacheRevision && !queryInfo.has_filters()) {
6574 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
6575 }
6576
6577 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
6578 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
6579 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
6580 !queryInfo.has_filters()) {
6581 auto& chunks = walkData.scratchChunks;
6582 chunks.clear();
6583
6584 bool chunkChanged = false;
6585 for (auto* pArchetype: queryInfo) {
6586 if (pArchetype == nullptr || !can_process_archetype(queryInfo, *pArchetype))
6587 continue;
6588
6589 for (const auto* pChunk: pArchetype->chunks()) {
6590 if (pChunk == nullptr)
6591 continue;
6592
6593 chunks.push_back(pChunk);
6594 if (!chunkChanged && pChunk->changed(walkData.cachedEntityVersion))
6595 chunkChanged = true;
6596 }
6597 }
6598
6599 bool sameChunks = chunks.size() == walkData.cachedChunks.size();
6600 if (sameChunks) {
6601 for (uint32_t i = 0; i < (uint32_t)chunks.size(); ++i) {
6602 if (chunks[i] != walkData.cachedChunks[i]) {
6603 sameChunks = false;
6604 break;
6605 }
6606 }
6607 }
6608
6609 if (sameChunks && !chunkChanged) {
6610 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
6611 }
6612 }
6613
6614 auto& entities = walkData.scratchEntities;
6615 entities.clear();
6616 arr(entities, constraints);
6617 if (entities.empty())
6618 return {};
6619
6620 if (needsTraversalBarrierState) {
6621 uint32_t writeIdx = 0;
6622 const auto cnt = (uint32_t)entities.size();
6623 GAIA_FOR(cnt) {
6624 const auto entity = entities[i];
6625 if (!survives_disabled_barrier(entity))
6626 continue;
6627 entities[writeIdx++] = entity;
6628 }
6629 entities.resize(writeIdx);
6630 if (entities.empty())
6631 return {};
6632 }
6633
6634 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
6635 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
6636 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
6637 entities.size() == walkData.cachedInput.size()) {
6638 bool sameInput = true;
6639 for (uint32_t i = 0; i < (uint32_t)entities.size(); ++i) {
6640 if (entities[i] != walkData.cachedInput[i]) {
6641 sameInput = false;
6642 break;
6643 }
6644 }
6645
6646 if (sameInput) {
6647 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
6648 }
6649 }
6650
6651 auto& ordered = walkData.cachedOutput;
6652 walkData.cachedInput = entities;
6653 ordered.clear();
6654 if (!::gaia::ecs::valid(world, relation)) {
6655 core::sort(entities, [](Entity left, Entity right) {
6656 return left.id() < right.id();
6657 });
6658 ordered = entities;
6659 } else {
6660 // Keep execution deterministic regardless of archetype iteration order.
6661 core::sort(entities, [](Entity left, Entity right) {
6662 return left.id() < right.id();
6663 });
6664
6665 const auto cnt = (uint32_t)entities.size();
6666
6667 auto& indegree = walkData.scratchIndegree;
6668 indegree.resize(cnt);
6669 auto& outdegree = walkData.scratchOutdegree;
6670 outdegree.resize(cnt);
6671 for (uint32_t i = 0; i < cnt; ++i) {
6672 indegree[i] = 0;
6673 outdegree[i] = 0;
6674 }
6675
6676 uint32_t edgeCnt = 0;
6677 OrderedWalkTargetCtx edgeCtx;
6678 edgeCtx.pEntities = &entities;
6679 edgeCtx.cnt = cnt;
6680 edgeCtx.pIndegree = &indegree;
6681 edgeCtx.pOutdegree = &outdegree;
6682 edgeCtx.pEdgeCnt = &edgeCnt;
6683 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
6684 const auto dependent = entities[dependentIdx];
6685 edgeCtx.dependentIdx = dependentIdx;
6686 world_for_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::count_edge);
6687 }
6688
6689 auto& offsets = walkData.scratchOffsets;
6690 offsets.resize(cnt + 1);
6691 offsets[0] = 0;
6692 for (uint32_t i = 0; i < cnt; ++i)
6693 offsets[i + 1] = offsets[i] + outdegree[i];
6694
6695 auto& writeCursor = walkData.scratchWriteCursor;
6696 writeCursor.resize(cnt);
6697 for (uint32_t i = 0; i < cnt; ++i)
6698 writeCursor[i] = offsets[i];
6699
6700 auto& edges = walkData.scratchEdges;
6701 edges.resize(edgeCnt);
6702 edgeCtx.pWriteCursor = &writeCursor;
6703 edgeCtx.pEdges = &edges;
6704 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
6705 const auto dependent = entities[dependentIdx];
6706 edgeCtx.dependentIdx = dependentIdx;
6707 world_for_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::write_edge);
6708 }
6709
6710 ordered.reserve(cnt);
6711
6712 const bool isUp = order == TravOrder::Up || order == TravOrder::ReverseUp;
6713 const bool needsReverse = order == TravOrder::ReverseUp || order == TravOrder::ReverseDown;
6714
6715 auto& visited = walkData.scratchWriteCursor;
6716 visited.resize(cnt);
6717 for (uint32_t i = 0; i < cnt; ++i)
6718 visited[i] = 0;
6719
6720 auto& stack = walkData.scratchCurrLevel;
6721 stack.clear();
6722 auto& cursorStack = walkData.scratchNextLevel;
6723 cursorStack.clear();
6724
6725 auto append_from_root = [&](uint32_t rootIdx) {
6726 stack.push_back(rootIdx);
6727 cursorStack.push_back(offsets[rootIdx]);
6728
6729 while (!stack.empty()) {
6730 const auto idx = stack.back();
6731 if (visited[idx] == 0) {
6732 visited[idx] = 1;
6733 if (!isUp)
6734 ordered.push_back(entities[idx]);
6735 }
6736
6737 bool pushedChild = false;
6738 auto& cursor = cursorStack.back();
6739 while (cursor < offsets[idx + 1]) {
6740 const auto childIdx = edges[cursor++];
6741 if (visited[childIdx] != 0)
6742 continue;
6743
6744 stack.push_back(childIdx);
6745 cursorStack.push_back(offsets[childIdx]);
6746 pushedChild = true;
6747 break;
6748 }
6749
6750 if (pushedChild)
6751 continue;
6752
6753 if (isUp)
6754 ordered.push_back(entities[idx]);
6755 visited[idx] = 2;
6756 stack.pop_back();
6757 cursorStack.pop_back();
6758 }
6759 };
6760
6761 for (uint32_t i = 0; i < cnt; ++i) {
6762 if (indegree[i] == 0 && visited[i] == 0)
6763 append_from_root(i);
6764 }
6765
6766 // Cycles are invalid relationship data, but keep traversal deterministic by visiting leftovers by id.
6767 for (uint32_t i = 0; i < cnt; ++i) {
6768 if (visited[i] == 0)
6769 append_from_root(i);
6770 }
6771
6772 if (needsReverse) {
6773 const auto orderedCnt = (uint32_t)ordered.size();
6774 for (uint32_t i = 0; i < orderedCnt / 2; ++i)
6775 core::swap(ordered[i], ordered[orderedCnt - i - 1]);
6776 }
6777 }
6778
6779 walkData.cachedRelation = relation;
6780 walkData.cachedOrder = order;
6781 walkData.cachedConstraints = constraints;
6782 walkData.cachedRelationVersion = relationVersion;
6783 walkData.cachedEntityVersion = ::gaia::ecs::world_version(world);
6784 walkData.cachedResultCacheRevision = resultCacheRevision;
6785 walkData.cachedRuns.clear();
6786
6787 {
6788 const auto orderedCnt = (uint32_t)ordered.size();
6789 if (orderedCnt != 0) {
6790 for (uint32_t i = 0; i < orderedCnt; ++i) {
6791 const auto& ec = ::gaia::ecs::fetch(world, ordered[i]);
6792 if (walkData.cachedRuns.empty()) {
6793 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
6794 continue;
6795 }
6796
6797 auto& run = walkData.cachedRuns.back();
6798 if (ec.pChunk == run.pChunk && ec.row == run.to) {
6799 run.to = (uint16_t)(run.to + 1);
6800 } else {
6801 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
6802 }
6803 }
6804 }
6805 }
6806
6807 if (!queryInfo.has_filters()) {
6808 auto& chunks = walkData.scratchChunks;
6809 chunks.clear();
6810 for (auto* pArchetype: queryInfo) {
6811 if (pArchetype == nullptr || !can_process_archetype(queryInfo, *pArchetype))
6812 continue;
6813
6814 for (const auto* pChunk: pArchetype->chunks()) {
6815 if (pChunk == nullptr)
6816 continue;
6817 chunks.push_back(pChunk);
6818 }
6819 }
6820 walkData.cachedChunks = chunks;
6821 } else
6822 walkData.cachedChunks.clear();
6823 walkData.cacheValid = true;
6824
6825 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
6826 }
6827
6837 template <typename Func, std::enable_if_t<detail::is_query_walk_core_callback_v<Func>, int> = 0>
6839 Func func, Entity relation, TravOrder order = TravOrder::Down,
6840 Constraints constraints = Constraints::EnabledOnly) {
6841 auto& queryInfo = fetch();
6842 match_all(queryInfo);
6843 const auto ordered = ordered_entities_walk(queryInfo, relation, order, constraints);
6844
6845 if constexpr (std::is_invocable_v<Func, Iter&>) {
6846 each_direct_entities_iter(queryInfo, ordered, constraints, func);
6847 } else if constexpr (std::is_invocable_v<Func, const Entity&> || std::is_invocable_v<Func, Entity>) {
6848 for (const auto entity: ordered)
6849 func(entity);
6850 }
6851 }
6852
6859 template <typename Func, std::enable_if_t<!detail::is_query_walk_core_callback_v<Func>, int> = 0>
6861 Func func, Entity relation, TravOrder order = TravOrder::Down,
6862 Constraints constraints = Constraints::EnabledOnly);
6863
6864 //------------------------------------------------
6865
6867 void diag() {
6868 // Make sure matching happened
6869 auto& queryInfo = fetch();
6870 match_all(queryInfo);
6871 if (uses_shared_cache_layer())
6872 GAIA_LOG_N("BEG DIAG Query %u.%u [S]", id(), gen());
6873 else if (uses_query_cache_storage())
6874 GAIA_LOG_N("BEG DIAG Query %u.%u [L]", id(), gen());
6875 else
6876 GAIA_LOG_N("BEG DIAG Query [U]");
6877 for (const auto* pArchetype: queryInfo)
6878 Archetype::diag_basic_info(*m_storage.world(), *pArchetype);
6879 GAIA_LOG_N("END DIAG Query");
6880 }
6881
6884 GAIA_NODISCARD util::str bytecode() {
6885 auto& queryInfo = fetch();
6886 return queryInfo.bytecode();
6887 }
6888
6891 const auto dump = bytecode();
6892 if (uses_shared_cache_layer())
6893 GAIA_LOG_N("BEG DIAG Query Bytecode %u.%u [S]", id(), gen());
6894 else if (uses_query_cache_storage())
6895 GAIA_LOG_N("BEG DIAG Query Bytecode %u.%u [L]", id(), gen());
6896 else
6897 GAIA_LOG_N("BEG DIAG Query Bytecode [U]");
6898 GAIA_LOG_N("%.*s", (int)dump.size(), dump.data());
6899 GAIA_LOG_N("END DIAG Query");
6900 }
6901 };
6902 } // namespace detail
6903
6905 using Query = detail::QueryImpl;
6906 } // namespace ecs
6907} // namespace gaia
6908
6909#include "gaia/ecs/query_builder_typed.inl"
6910#include "gaia/ecs/query_typed.inl"
Array with variable size of elements of type.
Definition darray_impl.h:27
GAIA_NODISCARD size_type size() const noexcept
Returns the number of elements.
Definition darray_impl.h:504
void clear() noexcept
Removes all elements.
Definition darray_impl.h:449
void resize(size_type count)
Changes the number of elements.
Definition darray_impl.h:240
GAIA_NODISCARD bool empty() const noexcept
Checks whether the container has no elements.
Definition darray_impl.h:510
void push_back(const T &arg)
Appends an element.
Definition darray_impl.h:309
Fixed-shape group of chunks storing entities that share the same component layout....
Definition archetype.h:97
GAIA_NODISCARD EntitySpan ids_view() const
Span over the component and entity identifiers defining the archetype shape.
Definition archetype.h:831
static void diag_basic_info(const World &world, const Archetype &archetype)
Logs basic archetype diagnostics: sizes, chunk count, entity counts, and component ids.
Definition archetype.h:1328
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:892
GAIA_NODISCARD bool is_req_del() const
Returns true if this archetype is requested to be deleted.
Definition archetype.h:1234
GAIA_NODISCARD std::span< const uint8_t > pair_rel_indices(Entity relation) const
Indices of pairs whose relation matches the given relation.
Definition archetype.h:878
Fixed-capacity archetype storage unit holding entities and their component columns.
Definition chunk.h:36
GAIA_NODISCARD World & world()
Owning world mutable reference.
Definition chunk.h:856
GAIA_NODISCARD EntitySpan ids_view() const
Span over the component and entity identifiers held by this chunk.
Definition chunk.h:868
GAIA_NODISCARD bool entity_order_changed(uint32_t requiredVersion) const
Returns true if entity order changed since requiredVersion. This is narrower than changed(requiredVer...
Definition chunk.h:1968
GAIA_NODISCARD bool changed(uint32_t requiredVersion) const
Returns true if the provided version is newer than the one stored internally. Use when checking if th...
Definition chunk.h:1947
GAIA_NODISCARD bool empty() const
Checks is there are any entities in the chunk.
Definition chunk.h:1914
Binds the calling thread to a work-item deferral slot for the lifetime of the scope.
Definition api.h:179
Iterator for iterating entity subsets selected by Constraints. Disabled entities always precede enabl...
Definition chunk_iterator.h:1968
static GAIA_NODISCARD uint16_t size(Chunk *pChunk) noexcept
Returns the number of enabled rows in a chunk.
Definition chunk_iterator.h:1996
static GAIA_NODISCARD uint16_t end_index(Chunk *pChunk) noexcept
Returns the end of the enabled row range in a chunk.
Definition chunk_iterator.h:1989
static GAIA_NODISCARD uint16_t start_index(Chunk *pChunk) noexcept
Returns the first enabled row in a chunk.
Definition chunk_iterator.h:1982
Compiled query plan and its incrementally maintained result caches.
Definition query_info.h:131
bool ensure_matches_one_transient(const Archetype &archetype, EntitySpan targetEntities, const cnt::sarray< Entity, MaxVarCnt > &runtimeVarBindings, uint8_t runtimeVarBindingMask)
Rebuilds transient result membership by evaluating one target archetype.
Definition query_info.h:1490
GAIA_NODISCARD GroupId group_id(uint32_t archetypeIdx) const
Returns the cached group id for a matched archetype index.
Definition query_info.h:2632
bool ensure_matches_one(const Archetype &archetype, EntitySpan targetEntities, const cnt::sarray< Entity, MaxVarCnt > &runtimeVarBindings, uint8_t runtimeVarBindingMask)
Ensures persistent result membership for one target archetype.
Definition query_info.h:1478
void ensure_matches(const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes, const EntityToArchetypeVersionMap &entityToArchetypeMapVersions, ArchetypeId archetypeLastId, const cnt::sarray< Entity, MaxVarCnt > &runtimeVarBindings, uint8_t runtimeVarBindingMask)
Refreshes persistent query matches when cache state or world archetypes changed.
Definition query_info.h:1410
void reset()
Resets cached query state for slot reuse while keeping the compiled context object.
Definition query_info.h:980
GAIA_NODISCARD bool barrier_may_prune() const
Returns true when any cached archetype can be pruned by the hierarchy barrier.
Definition query_info.h:2651
GAIA_NODISCARD bool barrier_passes(uint32_t archetypeIdx) const
Returns true when the matched archetype passes the depth-order hierarchy barrier.
Definition query_info.h:2641
GAIA_NODISCARD util::str bytecode() const
Returns a textual dump of the compiled VM bytecode.
Definition query_info.h:2436
GAIA_NODISCARD std::span< const Archetype * > cache_archetype_view() const
Returns the cached result archetypes as a span.
Definition query_info.h:2694
GAIA_NODISCARD bool has_inherited_data_payload() const
Returns true when query iteration needs cached inherited data payloads.
Definition query_info.h:1952
GAIA_NODISCARD uint32_t result_cache_rev() const
Returns the result membership revision used by reverse-index cache users.
Definition query_info.h:1131
void group_ids(Container &out, bool sortGroups)
Collects the query's active non-zero group ids.
Definition query_info.h:1110
GAIA_NODISCARD bool result_cache_may_need_prefab_filter() const
Returns true when the result cache contains archetypes that need default prefab filtering.
Definition query_info.h:1137
GAIA_NODISCARD bool matches_prefab_entities() const
Returns true when prefab-tagged entities should participate in query results.
Definition query_info.h:2505
GAIA_NODISCARD QueryCtx::CachePolicy cache_policy() const
Returns the query cache policy selected during compilation.
Definition query_info.h:1095
GAIA_NODISCARD bool has_filters() const
Returns true when the query has per-entity changed/filter terms.
Definition query_info.h:2454
GAIA_NODISCARD bool has_grouped_payload() const
Returns true when grouped-query payloads are active for this query.
Definition query_info.h:1101
GAIA_NODISCARD World * world()
Returns the mutable world owning this query.
Definition query_info.h:2402
GAIA_NODISCARD bool has_sorted_payload() const
Returns true when sorted-query payloads are active for this query.
Definition query_info.h:1125
GAIA_NODISCARD bool has_entity_filter_terms() const
Returns true when direct non-fragmenting terms must be rechecked per entity.
Definition query_info.h:2461
GAIA_NODISCARD QueryCtx & ctx()
Returns the mutable compiled query context.
Definition query_info.h:2425
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:2554
static GAIA_NODISCARD QueryHandle handle(const QueryInfo &info)
Builds a stable query handle from query slot metadata.
Definition query_info.h:1071
void ensure_matches_transient(const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes, const EntityToArchetypeVersionMap &entityToArchetypeMapVersions, const cnt::sarray< Entity, MaxVarCnt > &runtimeVarBindings, uint8_t runtimeVarBindingMask)
Rebuilds transient result membership without retaining persistent seed-cache state.
Definition query_info.h:1425
void ensure_depth_order_hierarchy_barrier_cache()
Ensures depth-order hierarchy barrier results are current before public reads.
Definition query_info.h:2602
static GAIA_NODISCARD QueryInfo create(QueryId id, QueryCtx &&ctx, const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes)
Creates and compiles a query info object from a moved query context.
Definition query_info.h:1023
Move-only wrapper for scheduler-owned ECS work.
Definition sched.h:118
Owns entities, components, archetypes, queries, observers, and systems.
Definition world.h:80
Lightweight view that executes a query in deterministic relation traversal order.
Definition query.h:5940
OrderByTravView(QueryImpl &query, Entity relation, TravOrder order)
Creates a query traversal view.
Definition query.h:5950
void each(Func func)
Iterates the query result through the requested relation order.
Definition query.h:5957
Records OnSet notifications and sorted-query invalidations produced by a parallel region instead of a...
Definition query.h:2016
Binds a parallel work-item range to its shared deferred-notification slot for the lifetime of the sco...
Definition query.h:2038
Builds, caches, and executes a Gaia-ECS query.
Definition query.h:507
QueryImpl & sort_by(Entity entity, TSortByFunc func)
Sorts the query by the specified entity and function.
Definition query.h:5915
static void run_query_func(World *pWorld, Func func, ChunkBatch &batch)
Executes an iterator callback for one prepared chunk batch.
Definition query.h:2219
static void invoke_runtime_iter(void *pFunc, TIter &it)
Invokes a type-erased public iterator callback.
Definition query.h:3835
QueryImpl & scope(QueryCacheScope cacheScope)
Sets the cache scope used by cached queries.
Definition query.h:1200
QueryImpl & main_thread(bool required=true)
Marks whether this query must run on the main thread/serial path.
Definition query.h:1079
GAIA_NODISCARD QueryAccess access(Entity entity)
Returns the effective read/write access for an id.
Definition query.h:1156
static GAIA_NODISCARD bool match_filters(const Chunk &chunk, const QueryInfo &queryInfo, uint32_t changedWorldVersion)
Returns whether a chunk passes the query's changed filters.
Definition query.h:1844
static void chunk_effective_range(Chunk *pChunk, Constraints constraints, bool needsBarrierCache, bool barrierPasses, uint16_t &from, uint16_t &to) noexcept
Calculates the row range of a chunk after applying row constraints and depth-order barrier state.
Definition query.h:1937
void arr(Container &outArray, Constraints constraints=Constraints::EnabledOnly)
Appends all components or entities matching the query to the output array.
QueryImpl & group_by(TGroupByFunc func=group_by_func_default)
Organizes matching archetypes into groups according to the grouping function. Does not order iteratio...
QueryImpl & or_()
Adds an OR typed term.
QueryImpl & sort_by(TSortByFunc func)
Sorts the query by the specified pair and function.
void reset()
Release any data allocated by the query.
Definition query.h:5338
QueryImpl & all(const QueryTermOptions &options)
Adds a required typed term.
GAIA_NODISCARD QueryPlan prepare_query_plan(const QueryInfo &queryInfo, Constraints constraints) const
Selects the prepared execution plan for public iterator callbacks.
Definition query.h:3649
QueryImpl & set_var(util::str_view name, Entity value)
Binds a named query variable to a concrete entity value.
Definition query.h:5852
static GAIA_NODISCARD bool needs_depth_order_hierarchy_barrier_cache(const QueryInfo &queryInfo, Constraints constraints)
Checks whether the current row constraints require the depth-order hierarchy barrier cache.
Definition query.h:1926
QueryImpl & group_by(TGroupByFunc func=group_by_func_default)
Organizes matching archetypes into groups according to the grouping function. Does not order iteratio...
GAIA_NODISCARD util::str bytecode()
Returns a textual dump of the generated query VM bytecode.
Definition query.h:6884
void each(Func func, QueryExecType execType, Constraints constraints)
Iterates query matches with an iterator callback using the selected execution mode and row constraint...
Definition query.h:6186
static bool SilenceInvalidCacheKindAssertions
Suppresses assertions when an invalid cache-kind combination is queried for diagnostics.
Definition query.h:894
QueryImpl & group_dep(Entity relation)
Declares an explicit relation dependency for grouped cache invalidation. Useful for custom group_by c...
Definition query.h:6050
QueryImpl & sort_by(TSortByFunc func)
Sorts the query by the specified component and function.
static void run_query_func(World *pWorld, Func func, std::span< ChunkBatch > batches)
Executes an iterator callback over a contiguous list of prepared chunk batches.
Definition query.h:2261
QueryImpl & reads()
Declares an additional component or pair type read by this query callback.
Definition query.h:1112
GAIA_NODISCARD uint32_t gen() const
Returns the cache handle generation of this query.
Definition query.h:5329
GAIA_NODISCARD SchedJob job(Func func, QueryExecType execType)
Adds a query execution job without submitting it.
Definition query.h:6115
QueryImpl & kind(QueryCacheKind cacheKind)
Sets the hard cache-kind requirement for the query.
Definition query.h:1185
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:992
QueryImpl & set_var(const char *name, Entity value)
Binds a named query variable to a concrete entity value.
Definition query.h:5863
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:1967
QueryImpl & no(const QueryTermOptions &options)
Adds an excluded typed term.
void each_arch(Func func, Constraints constraints=Constraints::EnabledOnly)
Iterates matching archetypes instead of individual entities.
Definition query.h:6239
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:3959
GAIA_NODISCARD bool can_run_parallel(QueryImpl &other)
Returns whether this query can run concurrently with another query based on declared scheduling metad...
Definition query.h:1177
static GAIA_NODISCARD constexpr Constraints iter_mode_constraints()
Maps an iteration-mode tag to the runtime row constraints used by iterators.
Definition query.h:2200
ExecPayloadKind
Runtime payload layout required by generic chunk-batch execution.
Definition query.h:2094
@ Plain
Plain batches without group ids, inherited data, sorted slices, or barrier metadata.
@ Grouped
Batches carry group ids but do not require sorted slices or inherited/barrier metadata.
@ NonTrivial
Batches require non-trivial side payload such as sorted slices, inherited data, or barriers.
void each_runtime_erased(QueryExecType execType, void *pFunc, void(*invoke)(void *, Iter &), Constraints constraints)
Runs a type-erased public iterator callback through generic query execution.
Definition query.h:3900
QueryImpl & any()
Adds an optional typed term.
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:5837
GAIA_NODISCARD const char * kind_error_str()
Returns a human-readable description of the current kind validation result.
Definition query.h:1240
GAIA_NODISCARD uint16_t cache_src_trav() const
Returns the traversed-source snapshot cap. 0 disables explicit traversed-source snapshot caching.
Definition query.h:1039
void groups(Container &out, bool sortGroups)
Collects active non-zero group ids for a grouped query. The ids can be fed back to group_id(....
Definition query.h:6093
GAIA_NODISCARD bool is_cached() const
Returns whether the query is stored in the query cache.
Definition query.h:5359
void run_query_on_chunks_runtime_direct_plain_impl(QueryInfo &queryInfo, const QueryPlan &plan, Constraints constraints, Func &func)
Runs a public Iter callback over cached chunks without creating chunk batches.
Definition query.h:3738
QueryImpl & all()
Adds a required typed term.
GAIA_NODISCARD OrderByTravView order_by(Entity relation, TravOrder order)
Iterates matching entities in an explicit relation traversal order.
Definition query.h:5978
QueryPlanFlags
Orthogonal flags attached to a prepared query plan.
Definition query.h:2146
@ QueryPlanFlag_Sorted
The plan may need sorted cache slices. Runners use them only with non-trivial payload.
Definition query.h:2158
@ QueryPlanFlag_Filtered
The query has per-chunk filters such as changed terms.
Definition query.h:2150
@ QueryPlanFlag_InheritedPayload
The query carries inherited component data into iterator payloads.
Definition query.h:2154
@ QueryPlanFlag_None
No additional query-plan properties are present.
Definition query.h:2148
@ QueryPlanFlag_BarrierCache
The plan must use the depth-order hierarchy barrier cache when checking archetype/row ranges.
Definition query.h:2160
@ QueryPlanFlag_Grouped
The query uses grouped payload/ranges or grouped cache ordering.
Definition query.h:2156
@ QueryPlanFlag_EntityFilter
The query has entity-filter terms that require per-entity rechecks.
Definition query.h:2152
QueryImpl & add(const char *str,...)
Creates a query from a null-terminated expression string.
Definition query.h:5406
QueryImpl & is(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds a semantic Is(entity) requirement.
Definition query.h:5698
QueryImpl & reads(Entity entity)
Declares an additional id read by this query callback.
Definition query.h:1102
QueryImpl & in(Entity entity, QueryTermOptions options=QueryTermOptions{})
Adds an inherited in(entity) requirement.
Definition query.h:5708
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:1020
void diag_bytecode()
Prints a textual dump of the generated query VM bytecode.
Definition query.h:6890
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:5874
QueryImpl & writes(Entity entity)
Declares an additional id written by this query callback.
Definition query.h:1123
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:3995
GAIA_NODISCARD std::span< const Entity > custom_writes() const
Returns explicitly declared write ids that are not query terms.
Definition query.h:1145
QueryImpl & depth_order(Entity relation=ChildOf)
Orders cached query entries by fragmenting relation depth so iteration runs breadth-first top-down....
Definition query.h:5999
GAIA_NODISCARD bool main_thread_required() const
Returns whether this query must run on the main thread/serial path.
Definition query.h:1086
QueryImpl & add(QueryInput item)
Adds a prebuilt query input item.
Definition query.h:5686
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:3979
QueryImpl & any(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds an optional entity or pair term.
Definition query.h:5743
void each_iter(Iter &it, Func func)
Runs a typed callback against an already prepared iterator. This is used by higher-level adapters tha...
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:5816
GAIA_NODISCARD bool can_process_archetype(const QueryInfo &queryInfo, const Archetype &archetype) const
Returns whether an archetype is eligible for query execution.
Definition query.h:1899
QueryImpl & group_dep()
Declares an explicit relation dependency for grouped cache invalidation. Useful for custom group_by c...
void each(Func func, Constraints constraints)
Iterates query matches with an iterator callback under the selected row constraints.
Definition query.h:6176
QueryImpl & ctx(void *pCtx)
Sets the user-owned context pointer visible through Iter::ctx() during iterator callbacks....
Definition query.h:1056
void each(Func func, QueryExecType execType)
Iterates query matches using the selected execution mode.
Definition query.h:6167
QueryImpl & var_name(Entity varEntity, const char *name)
Assigns a human-readable name to a query variable entity (Var0..Var7).
Definition query.h:5825
static GAIA_NODISCARD ExecPayloadKind exec_payload_kind(const QueryInfo &queryInfo, Constraints constraints)
Classifies the generic batch payload needed for a matched query under row constraints.
Definition query.h:2107
static GAIA_NODISCARD bool is_non_fragmenting_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:3947
void each(Func func)
Iterates query matches using the default execution mode.
Definition query.h:6151
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:3969
static GAIA_NODISCARD bool depth_order_hierarchy_barrier_prunes(const QueryInfo &queryInfo)
Checks whether cached depth-order barrier results can prune any matched archetype.
Definition query.h:1953
QueryImpl & match_prefab()
Makes the query include prefab entities in matches.
Definition query.h:1214
GAIA_NODISCARD std::span< const Entity > custom_reads() const
Returns explicitly declared read ids that are not query terms.
Definition query.h:1139
static GAIA_NODISCARD bool has_depth_order_hierarchy_enabled_barrier(const QueryInfo &queryInfo)
Checks whether depth-order grouping can prune disabled hierarchy subtrees.
Definition query.h:1915
void diag()
Run diagnostics.
Definition query.h:6867
QueryImpl & group_id(Entity entity)
Selects the group to iterate over.
Definition query.h:6075
void match_all(QueryInfo &queryInfo)
Matches the query against all relevant archetypes.
Definition query.h:953
void each_runtime_erased(QueryInfo &queryInfo, const QueryPlan &plan, QueryExecType execType, void *pFunc, void(*invoke)(void *, Iter &), Constraints constraints)
Runs a type-erased public iterator callback using an already prepared query cache and plan.
Definition query.h:3915
static GAIA_NODISCARD bool uses_potential_inherited_id_matching(const QueryTerm &term)
Returns whether a term could use inherited-id matching based on query shape alone....
Definition query.h:3987
QueryImpl & no()
Adds an excluded typed term.
void each_runtime_inter(Func func, Constraints constraints=Constraints::EnabledOnly)
Runs a public iterator callback through the fastest supported runtime path.
Definition query.h:3800
GAIA_NODISCARD QueryCacheScope scope() const
Returns the currently requested cache scope.
Definition query.h:1222
GAIA_NODISCARD std::span< const Entity > ordered_entities_walk(QueryInfo &queryInfo, Entity relation, TravOrder order, Constraints constraints=Constraints::EnabledOnly)
Builds and caches relation traversal order for the current query result.
Definition query.h:6493
QueryImpl & changed(Entity entity)
Marks a runtime component or pair for changed() filtering.
Definition query.h:5896
uint32_t count(Constraints constraints=Constraints::EnabledOnly)
Calculates the number of entities matching the query.
Definition query.h:6287
void destroy()
Destroys the current cached query state and local scratch data.
Definition query.h:5348
QueryImpl & group_id()
Selects the group to iterate over.
static GAIA_NODISCARD bool match_filters(const Chunk &chunk, const QueryInfo &queryInfo, uint32_t changedWorldVersion, std::span< const uint8_t > compIndices)
Returns whether a chunk passes the query's changed filters.
Definition query.h:1799
GAIA_NODISCARD bool conflicts_with(QueryImpl &other)
Returns whether this query conflicts with another query's effective access declarations.
Definition query.h:1167
bool empty(Constraints constraints=Constraints::EnabledOnly)
Returns true or false depending on whether there are any entities matching the query.
Definition query.h:6263
void each_entity_enabled(void *pCtx, void(*func)(void *, Entity))
Iterates matching enabled entities through a non-template erased callback.
Definition query.h:6302
void each_walk(Func func, Entity relation, TravOrder order=TravOrder::Down, Constraints constraints=Constraints::EnabledOnly)
Iterates entities matching the query in a requested relation traversal order. For relation R this tre...
Definition query.h:6838
QueryImpl & all(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds a required entity or pair term.
Definition query.h:5719
QueryImpl & changed()
Marks a typed term for changed() filtering.
GAIA_NODISCARD void * ctx() const
Returns the user-owned context pointer attached to this query.
Definition query.h:1063
QueryImpl & clear_vars()
Clears all runtime variable bindings.
Definition query.h:5886
QueryPlanMode
Prepared query runner mode shared by typed callbacks and public Iter callbacks.
Definition query.h:2122
@ General
Use the generic query execution path because no specialized runner is valid.
@ Sorted
Sorted payload execution that must preserve cache-provided chunk order.
@ SparseDense
Typed cached chunk iteration with compile-time sparse payload access.
@ DirectDense
Direct cached archetype/chunk iteration with query-term indices matching storage layout.
@ EntitySeed
Direct entity-seed evaluation over explicitly selected entities.
@ MappedDense
Typed dense cached archetype/chunk iteration using mapped component access. Public Iter callbacks use...
@ Empty
The selected group/range has no matching archetypes, so execution can return immediately.
@ Traversal
Traversal or inherited payload execution that requires the mapped generic path.
QueryImpl & no(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds an excluded entity or pair term.
Definition query.h:5792
QueryImpl & or_(const QueryTermOptions &options)
Adds an OR typed term.
QueryInfo & fetch()
Fetches the QueryInfo object. Creates or refreshes the backing QueryInfo if needed.
Definition query.h:899
GAIA_NODISCARD bool can_process_archetype_inter(const QueryInfo &queryInfo, const Archetype &archetype, Constraints constraints, int8_t barrierPasses=-1) const
Checks whether a matched archetype can be processed for the current row constraints.
Definition query.h:1993
static void run_query_arch_func(World *pWorld, Func func, ChunkBatch &batch, Constraints constraints)
Executes an archetype-level iterator callback for one prepared chunk batch. The iterator is initializ...
Definition query.h:2241
GAIA_NODISCARD QueryKindRes kind_error()
Returns the validation result for the current query shape and requested kind.
Definition query.h:1234
GAIA_NODISCARD bool valid()
Returns whether the current query shape satisfies the requested kind.
Definition query.h:1246
QueryImpl & depth_order()
Orders cached query entries by fragmenting relation depth so iteration runs breadth-first top-down.
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:6020
GAIA_NODISCARD bool match_one(QueryInfo &queryInfo, const Archetype &archetype, EntitySpan targetEntities)
Matches the query against a single archetype.
Definition query.h:979
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:5768
GAIA_NODISCARD OrderByTravView order_by(TravOrder order)
Iterates matching entities in an explicit typed relation traversal order.
QueryImpl & group_id(GroupId groupId)
Selects the group to iterate over.
Definition query.h:6067
GAIA_NODISCARD QueryCacheKind kind() const
Returns the currently requested cache kind.
Definition query.h:1228
void each_direct_inter(QueryInfo &queryInfo, Constraints constraints, void *pFunc, const TypedQueryExecState &state, void(*runDirectChunk)(QueryImpl &, Iter &, void *, const TypedQueryExecState &), bool needsInheritedArgIds, void(*invokeInherited)(World &, Entity, const Entity *, void *))
Runs an erased typed callback over entities selected by a direct sparse or target-term seed.
QueryImpl(World &world, QueryCache &queryCache, ArchetypeId &nextArchetypeId, uint32_t &worldVersion, const EntityToArchetypeMap &entityToArchetypeMap, const EntityToArchetypeVersionMap &entityToArchetypeMapVersions, const ArchetypeDArray &allArchetypes)
Creates a query bound to a world's query and archetype state.
Definition query.h:5299
QueryImpl & any(const QueryTermOptions &options)
Adds an optional typed term.
GAIA_NODISCARD QueryCachePolicy cache_policy()
Returns the effective cache policy chosen for the query.
Definition query.h:1007
GAIA_NODISCARD QueryId id() const
Returns the cache handle id of this query.
Definition query.h:5321
QueryImpl & writes()
Declares an additional component or pair type written by this query callback.
Definition query.h:1133
Wrapper for two Entities forming a relationship pair.
Definition id.h:614
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:262
Same API as ser_buffer_binary, but backed by fully dynamic storage.
Definition ser_buffer_binary.h:161
Compile-time serialization entry points.
Identifier of an entity or component instance in the world. Packs the entity index,...
Definition id.h:296
GAIA_NODISCARD constexpr bool pair() const noexcept
Whether this id refers to a relationship pair.
Definition id.h:377
GAIA_NODISCARD constexpr auto id() const noexcept
Entity index in the entity array.
Definition id.h:359
Explicit component/entity access declarations used for scheduling decisions.
Definition query_common.h:675
GAIA_NODISCARD QueryAccess access(Entity entity) const
Returns explicitly declared access for an id.
Definition query_common.h:722
GAIA_NODISCARD std::span< const Entity > writes_view() const
Returns the explicitly declared write ids.
Definition query_common.h:693
void add_read(Entity entity)
Declares that an id is read.
Definition query_common.h:699
GAIA_NODISCARD std::span< const Entity > reads_view() const
Returns the explicitly declared read ids.
Definition query_common.h:687
void add_write(Entity entity)
Declares that an id is written.
Definition query_common.h:710
Compact compiled query payload used by matching, identity, and cache maintenance.
Definition query_common.h:998
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:1152
TGroupByFunc groupByFunc
Function to use to perform the grouping.
Definition query_common.h:1133
Entity groupBy
Entity to group the archetypes by. EntityBad for no grouping.
Definition query_common.h:1131
GAIA_NODISCARD std::span< const uint8_t > changed_fields_view() const
Returns query-term indices matching changed-filter components.
Definition query_common.h:1194
GAIA_NODISCARD std::span< const QueryTerm > terms_view() const
Returns compiled terms in execution order.
Definition query_common.h:1270
TSortByFunc sortByFunc
Function to use to perform sorting.
Definition query_common.h:1129
uint16_t flags
Query flags.
Definition query_common.h:1154
GAIA_NODISCARD std::span< const Entity > changed_view() const
Returns changed-filter component ids.
Definition query_common.h:1188
Authored and compiled state defining query identity and execution behavior.
Definition query_common.h:885
struct gaia::ecs::QueryCtx::Data data
Compiled query payload.
@ OrderGroups
Grouped archetypes are ordered by group identifier during cache refresh.
Definition query_common.h:916
@ HasVariableTerms
Query contains variable-based lookup terms.
Definition query_common.h:910
@ HasSourceTerms
Query contains fixed-source lookup terms.
Definition query_common.h:908
@ DependencyHasSourceTerms
At least one term uses a fixed source entity.
Definition query_common.h:972
@ DependencyHasTraversalTerms
At least one source term traverses a relation.
Definition query_common.h:988
DirectTargetEvalKind
Specialized evaluation shape for concrete target entities.
Definition query_common.h:954
@ Generic
Uses the general compiled query evaluator.
@ SingleAllSemanticIs
Evaluates one required semantic Is term.
@ SingleAllInherited
Evaluates one required term through inherited component data.
@ SingleAllInIs
Evaluates one required inherited-inclusive Is term.
@ SingleAllDirect
Evaluates one required direct-storage term.
CachePolicy
Strategy used to maintain cached archetype matches.
Definition query_common.h:920
void init(World *pWorld)
Attaches the query context to a world and its component cache.
Definition query_common.h:1520
User-provided query input.
Definition query_common.h:485
Additional options for query terms. This can be used to configure source lookup, traversal and access...
Definition query_common.h:516
QueryTermOptions & write()
Requests mutable access to the term.
Definition query_common.h:648
Internal representation of QueryInput.
Definition query_common.h:732
Entity id
Queried id.
Definition query_common.h:734
QueryMatchKind matchKind
Match semantics for this term.
Definition query_common.h:744
Entity entTrav
Optional traversal relation for source lookups.
Definition query_common.h:738
Entity src
Source of where the queried id is looked up at.
Definition query_common.h:736
Tag selecting unconstrained row iteration for prepared query iteration.
Definition query.h:2194
Tag selecting disabled-only row constraints for prepared query iteration.
Definition query.h:2192
Tag selecting enabled-only row constraints for prepared query iteration.
Definition query.h:2190
Cache range selected by the query's optional group id filter.
Definition query.h:2178
bool hasSelectedGroup
True when m_groupIdSet narrowed the range to one cache group.
Definition query.h:2184
uint32_t idxTo
One-past-the-end cached archetype index to process.
Definition query.h:2182
bool valid
False when the selected group id is absent from the matched cache.
Definition query.h:2186
uint32_t idxFrom
First cached archetype index to process.
Definition query.h:2180
Prepared query execution metadata shared by typed callbacks and public Iter callbacks.
Definition query.h:2164
uint32_t idxTo
One-past-the-end cached archetype index to process.
Definition query.h:2174
QueryPlanMode mode
Runner family selected for the current matched query cache.
Definition query.h:2166
uint32_t idxFrom
First cached archetype index to process.
Definition query.h:2172
ExecPayloadKind payloadKind
Payload layout required by generic chunk-batch runners independent of sorted-cache availability.
Definition query.h:2170
uint8_t flags
Orthogonal plan properties such as filtering, entity filters, grouping, or payload requirements.
Definition query.h:2168
Detects whether a type is a relationship pair.
Definition id.h:285
static constexpr bool value
True when the type derives from the pair base.
Definition id.h:287
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:36
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:332