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 auto access = item.access;
168 if (item.op != QueryOpKind::Not && access != QueryAccess::Match) {
169 // Resolve payload-free ids once, before access metadata is cached.
170 // Wildcard and variable endpoints may resolve to data-bearing components later.
171 const auto id = item.id;
172 if (!is_wildcard(id.id()) && !is_variable((EntityId)id.id()) &&
173 (!id.pair() || (!is_wildcard(id.gen()) && !is_variable((EntityId)id.gen())))) {
174 const auto* pItem = id.pair() ? ctx.cc->find_pair_payload(id) : ctx.cc->find(id);
175 if (pItem == nullptr || pItem->comp.size() == 0)
176 access = QueryAccess::Match;
177 }
178 if (access == QueryAccess::None)
179 access = QueryAccess::Read;
180 }
181
182 // Build the read-write mask.
183 // This will be used to determine what kind of access the user wants for a given component.
184 const uint16_t isReadWrite = uint16_t(access == QueryAccess::Write);
185 ctxData.readWriteMask |= (isReadWrite << ctxData.idsCnt);
186 ctxData.flags |= uint16_t(access == QueryAccess::Match) * QueryCtx::QueryFlags::HasMatchTerms;
187 ctxData.flags |= uint16_t(item.id.pair()) * QueryCtx::QueryFlags::HasPairTerms;
188
189 ctxData.ids[ctxData.idsCnt] = item.id;
190 ctxData.terms[ctxData.idsCnt] = {
191 item.id, item.entSrc, item.entTrav, item.travKind, item.travDepth,
192 item.matchKind, nullptr, item.op, (uint8_t)ctxData.idsCnt, access};
193 ++ctxData.idsCnt;
194 }
195 };
196
197 struct QueryCmd_AddFilter {
198 static constexpr QueryCmdType Id = QueryCmdType::ADD_FILTER;
199 static constexpr bool InvalidatesHash = true;
200
201 Entity comp;
202
203 void exec(QueryCtx& ctx) const {
204 auto& ctxData = ctx.data;
205
206#if GAIA_DEBUG
207 GAIA_ASSERT(core::has(ctxData.ids_view(), comp));
208 GAIA_ASSERT(!core::has(ctxData.changed_view(), comp));
209
210 // There's a limit to the amount of components which we can store
211 if (ctxData.changedCnt >= MAX_ITEMS_IN_QUERY) {
212 GAIA_ASSERT2(false, "Trying to create an filter query with too many components!");
213
214 const auto compName = ctx.cc->get(comp).symbol_name();
215 GAIA_LOG_E(
216 "Trying to add component %.*s to an already full filter query!", (int)compName.size(), compName.data());
217 return;
218 }
219
220 uint32_t compIdx = 0;
221 for (; compIdx < ctxData.idsCnt; ++compIdx)
222 if (ctxData.ids[compIdx] == comp)
223 break;
224
225 // NOTE: Code bellow does the same as this commented piece.
226 // However, compilers can't quite optimize it as well because it does some more
227 // calculations. This is used often so go with the custom code.
228 // const auto compIdx = core::get_index_unsafe(ids, comp);
229
230 // Component has to be present in all/or lists.
231 // Filtering by NOT/ANY doesn't make sense because those are not hard requirements.
232 GAIA_ASSERT2(
233 ctxData.terms[compIdx].op != QueryOpKind::Not && ctxData.terms[compIdx].op != QueryOpKind::Any,
234 "Filtering by NOT/ANY doesn't make sense!");
235 if (ctxData.terms[compIdx].op != QueryOpKind::Not && ctxData.terms[compIdx].op != QueryOpKind::Any) {
236 ctxData.changed[ctxData.changedCnt++] = comp;
237 return;
238 }
239
240 const auto compName = ctx.cc->get(comp).symbol_name();
241 GAIA_LOG_E(
242 "SetChangeFilter trying to filter component %.*s but it's not a part of the query!", (int)compName.size(),
243 compName.data());
244#else
245 ctxData.changed[ctxData.changedCnt++] = comp;
246#endif
247 }
248 };
249
250 struct QueryCmd_SortBy {
251 static constexpr QueryCmdType Id = QueryCmdType::SORT_BY;
252 static constexpr bool InvalidatesHash = true;
253
254 Entity sortBy;
255 TSortByFunc func;
256
257 void exec(QueryCtx& ctx) const {
258 auto& ctxData = ctx.data;
259 ctxData.sortBy = sortBy;
260 GAIA_ASSERT(func != nullptr);
261 ctxData.sortByFunc = func;
262 }
263 };
264
265 struct QueryCmd_GroupBy {
266 static constexpr QueryCmdType Id = QueryCmdType::GROUP_BY;
267 static constexpr bool InvalidatesHash = true;
268
269 Entity groupBy;
270 TGroupByFunc func;
271 uint16_t flags;
272
273 void exec(QueryCtx& ctx) const {
274 auto& ctxData = ctx.data;
275 ctxData.groupBy = groupBy;
276 GAIA_ASSERT(func != nullptr);
277 ctxData.groupByFunc = func; // group_by_func_default;
278 if ((flags & QueryCtx::QueryFlags::OrderGroups) != 0)
279 ctxData.flags |= QueryCtx::QueryFlags::OrderGroups;
280 else
281 ctxData.flags &= (QueryCtx::QueryFlags)(~(uint16_t)QueryCtx::QueryFlags::OrderGroups);
282 }
283 };
284
285 struct QueryCmd_GroupDep {
286 static constexpr QueryCmdType Id = QueryCmdType::GROUP_DEP;
287 static constexpr bool InvalidatesHash = true;
288
289 Entity relation;
290
291 void exec(QueryCtx& ctx) const {
292 auto& ctxData = ctx.data;
293 GAIA_ASSERT(!relation.pair());
294 ctxData.add_group_dep(relation);
295 }
296 };
297
298 struct QueryCmd_MatchPrefab {
299 static constexpr QueryCmdType Id = QueryCmdType::MATCH_PREFAB;
300 static constexpr bool InvalidatesHash = true;
301
302 void exec(QueryCtx& ctx) const {
303 ctx.data.flags |= QueryCtx::QueryFlags::MatchPrefab;
304 }
305 };
306
307 struct QueryImplStorage {
308 World* m_world = nullptr;
310 QueryCache* m_pCache = nullptr;
312 uint64_t m_epoch = 0;
314 QueryInfo* m_pInfo = nullptr;
316 QueryInfo* m_pOwnedInfo = nullptr;
318 QueryIdentity m_identity{};
319 bool m_destroyed = false;
320
321 public:
322 QueryImplStorage() = default;
323 ~QueryImplStorage() {
324 (void)try_del_from_cache();
325 delete m_pOwnedInfo;
326 }
327
328 QueryImplStorage(QueryImplStorage&& other) {
329 m_world = other.m_world;
330 m_pCache = other.m_pCache;
331 m_epoch = other.m_epoch;
332 m_pInfo = other.m_pInfo;
333 m_pOwnedInfo = other.m_pOwnedInfo;
334 m_identity = other.m_identity;
335 m_destroyed = other.m_destroyed;
336
337 // Make sure old instance is invalidated
338 other.m_pInfo = nullptr;
339 other.m_pOwnedInfo = nullptr;
340 other.m_identity = {};
341 other.m_destroyed = false;
342 }
343 QueryImplStorage& operator=(QueryImplStorage&& other) {
344 GAIA_ASSERT(core::addressof(other) != this);
345
346 (void)try_del_from_cache();
347 delete m_pOwnedInfo;
348
349 m_world = other.m_world;
350 m_pCache = other.m_pCache;
351 m_epoch = other.m_epoch;
352 m_pInfo = other.m_pInfo;
353 m_pOwnedInfo = other.m_pOwnedInfo;
354 m_identity = other.m_identity;
355 m_destroyed = other.m_destroyed;
356
357 // Make sure old instance is invalidated
358 other.m_pInfo = nullptr;
359 other.m_pOwnedInfo = nullptr;
360 other.m_identity = {};
361 other.m_destroyed = false;
362
363 return *this;
364 }
365
366 QueryImplStorage(const QueryImplStorage& other) {
367 m_world = other.m_world;
368 m_pCache = other.m_pCache;
369 m_epoch = other.m_epoch;
370 m_pInfo = other.m_pInfo;
371 if (other.m_pOwnedInfo != nullptr && other.is_current())
372 m_pOwnedInfo = new QueryInfo(*other.m_pOwnedInfo);
373 m_identity = other.m_identity;
374 m_destroyed = other.m_destroyed;
375
376 // Make sure to update the ref count of the cached query so
377 // it doesn't get deleted by accident.
378 if (!m_destroyed && is_current()) {
379 auto* pInfo = try_query_info_fast();
380 if (pInfo == nullptr)
381 pInfo = m_pCache->try_get(m_identity.handle);
382 if (pInfo != nullptr)
383 pInfo->add_ref();
384 }
385 }
386 QueryImplStorage& operator=(const QueryImplStorage& other) {
387 GAIA_ASSERT(core::addressof(other) != this);
388
389 (void)try_del_from_cache();
390 delete m_pOwnedInfo;
391 m_pOwnedInfo = nullptr;
392
393 m_world = other.m_world;
394 m_pCache = other.m_pCache;
395 m_epoch = other.m_epoch;
396 m_pInfo = other.m_pInfo;
397 if (other.m_pOwnedInfo != nullptr && other.is_current())
398 m_pOwnedInfo = new QueryInfo(*other.m_pOwnedInfo);
399 m_identity = other.m_identity;
400 m_destroyed = other.m_destroyed;
401
402 // Make sure to update the ref count of the cached query so
403 // it doesn't get deleted by accident.
404 if (!m_destroyed && is_current()) {
405 auto* pInfo = try_query_info_fast();
406 if (pInfo == nullptr)
407 pInfo = m_pCache->try_get(m_identity.handle);
408 if (pInfo != nullptr)
409 pInfo->add_ref();
410 }
411
412 return *this;
413 }
414
417 GAIA_NODISCARD World* world() {
418 return m_world;
419 }
420
423 GAIA_NODISCARD QuerySerBuffer& ser_buffer() {
424 return m_identity.ser_buffer(m_world);
425 }
426
428 void ser_buffer_reset() {
429 return m_identity.ser_buffer_reset(m_world);
430 }
431
435 void init(World* world, QueryCache* queryCache) {
436 m_world = world;
437 m_pCache = queryCache;
438 m_epoch = queryCache->epoch();
439 m_pInfo = nullptr;
440 }
441
444 GAIA_NODISCARD bool is_current() const {
445 return m_pCache != nullptr && m_epoch == m_pCache->epoch();
446 }
447
449 void reset() {
450 if (auto* pInfo = try_query_info_fast(); pInfo != nullptr)
451 pInfo->reset();
452 if (m_pOwnedInfo != nullptr)
453 m_pOwnedInfo->reset();
454 }
455
457 void allow_to_destroy_again() {
458 m_destroyed = false;
459 }
460
463 GAIA_NODISCARD bool try_del_from_cache() {
464 if (!m_destroyed && m_identity.handle.id() != QueryIdBad && is_current())
465 m_pCache->del(m_identity.handle);
466
467 // Don't allow multiple calls to destroy to break the reference counter.
468 // One object is only allowed to destroy once.
469 m_pInfo = nullptr;
470 m_destroyed = true;
471 return false;
472 }
473
475 void invalidate() {
476 m_pInfo = nullptr;
477 m_identity.handle = {};
478 delete m_pOwnedInfo;
479 m_pOwnedInfo = nullptr;
480 }
481
484 GAIA_NODISCARD QueryInfo* try_query_info_fast() const {
485 if (m_pInfo == nullptr || m_identity.handle.id() == QueryIdBad || !is_current())
486 return nullptr;
487
488 auto* pInfo = m_pCache->try_get(m_identity.handle);
489 return pInfo == m_pInfo ? pInfo : nullptr;
490 }
491
494 void cache_query_info(QueryInfo& queryInfo) {
495 m_pInfo = &queryInfo;
496 }
497
500 GAIA_NODISCARD bool has_owned_query_info() const {
501 return m_pOwnedInfo != nullptr;
502 }
503
506 GAIA_NODISCARD QueryInfo& owned_query_info() {
507 GAIA_ASSERT(m_pOwnedInfo != nullptr);
508 return *m_pOwnedInfo;
509 }
510
513 void init_owned_query_info(QueryInfo&& queryInfo) {
514 if (m_pOwnedInfo == nullptr)
515 m_pOwnedInfo = new QueryInfo(GAIA_MOV(queryInfo));
516 else
517 *m_pOwnedInfo = GAIA_MOV(queryInfo);
518 }
519
522 GAIA_NODISCARD bool is_cached() const {
523 if (!is_current())
524 return false;
525 auto* pInfo = try_query_info_fast();
526 if (pInfo == nullptr)
527 pInfo = m_pCache->try_get(m_identity.handle);
528 return pInfo != nullptr;
529 }
530
533 GAIA_NODISCARD bool is_initialized() const {
534 return m_world != nullptr && m_pCache != nullptr;
535 }
536 };
539 class QueryImpl {
540 static constexpr uint32_t ChunkBatchSize = 32;
541 friend class SystemBuilder;
542
543 struct ChunkBatch {
544 const Archetype* pArchetype;
545 Chunk* pChunk;
546 const uint8_t* pCompIndices;
547 InheritedTermDataView inheritedData;
548 GroupId groupId;
549 uint16_t from;
550 uint16_t to;
551 };
552
553 using ChunkSpan = std::span<const Chunk*>;
554 using ChunkSpanMut = std::span<Chunk*>;
556 using CmdFunc = void (*)(QuerySerBuffer& buffer, QueryCtx& ctx);
557
560 struct DirectQueryScratch {
562 cnt::darray<Entity> entities;
563 cnt::darray<Entity> termEntities;
564 cnt::darray<Entity> bucketEntities;
566 uint32_t seenVersion = 1;
568 DirectQueryScratch* pNext = nullptr;
569 bool inUse = false;
570
571 ~DirectQueryScratch() {
572 GAIA_ASSERT(!inUse);
573 delete pNext;
574 }
575 };
576
577 private:
578 GAIA_NODISCARD bool uses_query_cache_storage() const {
579 return m_cacheKind != QueryCacheKind::None;
580 }
581
582 GAIA_NODISCARD bool uses_shared_cache_layer() const {
583 return uses_query_cache_storage() && m_cacheScope == QueryCacheScope::Shared;
584 }
585
586 void invalidate_query_storage() {
587 if (uses_query_cache_storage())
588 (void)m_storage.try_del_from_cache();
589 m_storage.invalidate();
590 }
591
593 GAIA_NOINLINE static DirectQueryScratch& nested_direct_query_scratch(DirectQueryScratch& scratch) {
594 auto* pScratch = &scratch;
595 do {
596 if (pScratch->pNext == nullptr)
597 pScratch->pNext = new DirectQueryScratch;
598 pScratch = pScratch->pNext;
599 } while (pScratch->inUse);
600 return *pScratch;
601 }
602
605 GAIA_NODISCARD static DirectQueryScratch& direct_query_scratch() {
606 static thread_local DirectQueryScratch scratch;
607 if GAIA_UNLIKELY (scratch.inUse)
608 return nested_direct_query_scratch(scratch);
609 return scratch;
610 }
611
613 struct DirectQueryScratchScope {
614 DirectQueryScratch& scratch;
615
616 DirectQueryScratchScope(): scratch(direct_query_scratch()) {
617 scratch.inUse = true;
618 }
619 ~DirectQueryScratchScope() {
620 scratch.inUse = false;
621 }
622 DirectQueryScratchScope(const DirectQueryScratchScope&) = delete;
623 DirectQueryScratchScope& operator=(const DirectQueryScratchScope&) = delete;
624 };
625
629 static void ensure_direct_query_count_capacity(DirectQueryScratch& scratch, uint32_t entityId) {
630 if (entityId < scratch.counts.size())
631 return;
632
633 const auto doubledSize = (uint32_t)scratch.counts.size() * 2U;
634 const auto minSize = doubledSize > 64U ? doubledSize : 64U;
635 const auto newSize = (entityId + 1U) > minSize ? (entityId + 1U) : minSize;
636 scratch.counts.resize(newSize, 0);
637 }
638
642 GAIA_NODISCARD static uint32_t next_direct_query_seen_version(DirectQueryScratch& scratch) {
643 update_version(scratch.seenVersion);
644 if (scratch.seenVersion == 0) {
645 scratch.seenVersion = 1;
646 core::fill(scratch.counts.begin(), scratch.counts.end(), 0U);
647 }
648
649 return scratch.seenVersion;
650 }
651
652 static constexpr CmdFunc CommandBufferRead[] = {
653 // Add item
654 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
655 QueryCmd_AddItem cmd;
656 ser::load(buffer, cmd);
657 cmd.exec(ctx);
658 },
659 // Add filter
660 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
661 QueryCmd_AddFilter cmd;
662 ser::load(buffer, cmd);
663 cmd.exec(ctx);
664 },
665 // SortBy
666 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
667 QueryCmd_SortBy cmd;
668 ser::load(buffer, cmd);
669 cmd.exec(ctx);
670 },
671 // GroupBy
672 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
673 QueryCmd_GroupBy cmd;
674 ser::load(buffer, cmd);
675 cmd.exec(ctx);
676 },
677 // GroupDep
678 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
679 QueryCmd_GroupDep cmd;
680 ser::load(buffer, cmd);
681 cmd.exec(ctx);
682 },
683 // MatchPrefab
684 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
685 QueryCmd_MatchPrefab cmd;
686 ser::load(buffer, cmd);
687 cmd.exec(ctx);
688 } //
689 }; // namespace detail
690
692 QueryImplStorage m_storage;
694 ArchetypeId* m_nextArchetypeId{};
696 uint32_t* m_worldVersion{};
698 const EntityToArchetypeMap* m_entityToArchetypeMap{};
700 const EntityToArchetypeVersionMap* m_entityToArchetypeMapVersions{};
702 const ArchetypeDArray* m_allArchetypes{};
706 uint8_t m_varNamesMask = 0;
708 cnt::sarray<Entity, MaxVarCnt> m_varBindings;
710 uint8_t m_varBindingsMask = 0;
712 GroupId m_groupIdSet = 0;
714 uint32_t m_changedWorldVersion = 0;
717 cnt::darray<ChunkBatch> m_batches;
719 QueryCacheKind m_cacheKind = QueryCacheKind::Default;
721 QueryCacheScope m_cacheScope = QueryCacheScope::Local;
723 uint16_t m_cacheSrcTrav = 0;
725 void* m_ctx = nullptr;
727 bool m_mainThread = false;
729 QueryAccessSet m_access;
730
732 struct EachWalkData {
734 cnt::darray<Entity> cachedInput;
736 cnt::darray<Entity> cachedOutput;
740 Entity cachedRelation = EntityBad;
742 TravOrder cachedOrder = TravOrder::Down;
744 Constraints cachedConstraints = Constraints::EnabledOnly;
746 uint32_t cachedRelationVersion = 0;
748 uint32_t cachedEntityVersion = 0;
750 uint32_t cachedResultCacheRevision = 0;
752 cnt::darray<const Chunk*> cachedChunks;
754 bool cacheValid = false;
756 cnt::darray<Entity> scratchEntities;
758 cnt::darray<const Chunk*> scratchChunks;
760 cnt::darray<uint32_t> scratchIndegree;
762 cnt::darray<uint32_t> scratchOutdegree;
764 cnt::darray<uint32_t> scratchOffsets;
766 cnt::darray<uint32_t> scratchWriteCursor;
768 cnt::darray<uint32_t> scratchEdges;
770 cnt::darray<uint32_t> scratchCurrLevel;
772 cnt::darray<uint32_t> scratchNextLevel;
773 };
774
775 template <typename T>
776 struct OnDemandDataHolder {
777 T* pData = nullptr;
778
779 OnDemandDataHolder() = default;
780
781 ~OnDemandDataHolder() {
782 delete pData;
783 }
784
785 OnDemandDataHolder(const OnDemandDataHolder& other) {
786 if (other.pData != nullptr)
787 pData = new T(*other.pData);
788 }
789
790 OnDemandDataHolder& operator=(const OnDemandDataHolder& other) {
791 if (core::addressof(other) == this)
792 return *this;
793
794 if (other.pData == nullptr) {
795 delete pData;
796 pData = nullptr;
797 return *this;
798 }
799
800 if (pData == nullptr)
801 pData = new T(*other.pData);
802 else
803 *pData = *other.pData;
804
805 return *this;
806 }
807
808 OnDemandDataHolder(OnDemandDataHolder&& other) noexcept: pData(other.pData) {
809 other.pData = nullptr;
810 }
811
812 OnDemandDataHolder& operator=(OnDemandDataHolder&& other) noexcept {
813 if (core::addressof(other) == this)
814 return *this;
815
816 delete pData;
817 pData = other.pData;
818 other.pData = nullptr;
819 return *this;
820 }
821
822 GAIA_NODISCARD T* get() {
823 return pData;
824 }
825
826 GAIA_NODISCARD const T* get() const {
827 return pData;
828 }
829
830 GAIA_NODISCARD T& ensure() {
831 if (pData == nullptr)
832 pData = new T();
833 return *pData;
834 }
835
836 void reset() {
837 delete pData;
838 pData = nullptr;
839 }
840 };
841
843 OnDemandDataHolder<EachWalkData> m_eachWalkData;
844
846 struct DirectSeedRunData {
847 cnt::darray<Entity> cachedEntities;
848 cnt::darray<Entity> cachedChunkOrderedEntities;
850 Entity cachedSeedTerm = EntityBad;
851 QueryMatchKind cachedSeedMatchKind = QueryMatchKind::Semantic;
852 Constraints cachedConstraints = Constraints::EnabledOnly;
853 uint32_t cachedRelVersion = 0;
854 uint32_t cachedWorldVersion = 0;
855 bool cacheValid = false;
856 };
857
858 OnDemandDataHolder<DirectSeedRunData> m_directSeedRunData;
859
861 void refresh_world_lifetime() {
862 if GAIA_LIKELY (m_storage.is_current())
863 return;
864 reset_world_lifetime();
865 }
866
868 GAIA_NOINLINE void reset_world_lifetime() {
869 GAIA_ASSERT(m_storage.is_initialized());
870 QueryImpl fresh(
871 *m_storage.world(), *m_storage.m_pCache, *m_nextArchetypeId, *m_worldVersion, *m_entityToArchetypeMap,
872 *m_entityToArchetypeMapVersions, *m_allArchetypes);
873 fresh.m_cacheKind = m_cacheKind;
874 fresh.m_cacheScope = m_cacheScope;
875 fresh.m_cacheSrcTrav = m_cacheSrcTrav;
876 fresh.m_ctx = m_ctx;
877 fresh.m_mainThread = m_mainThread;
878 *this = GAIA_MOV(fresh);
879 }
880
885 GAIA_NODISCARD static QueryAccess merge_access(QueryAccess lhs, QueryAccess rhs) {
886 if (lhs == QueryAccess::Write || rhs == QueryAccess::Write)
887 return QueryAccess::Write;
888 if (lhs == QueryAccess::Read || rhs == QueryAccess::Read)
889 return QueryAccess::Read;
890 return QueryAccess::None;
891 }
892
897 GAIA_NODISCARD static bool access_ids_overlap(Entity left, Entity right) {
898 if (left == right)
899 return true;
900 if (!left.pair() || !right.pair())
901 return false;
902 const auto endpointOverlaps = [](EntityId a, EntityId b) {
903 return a == b || is_wildcard(a) || is_wildcard(b) || is_variable(a) || is_variable(b);
904 };
905 return endpointOverlaps((EntityId)left.id(), (EntityId)right.id()) &&
906 endpointOverlaps((EntityId)left.gen(), (EntityId)right.gen());
907 }
908
914 template <bool MatchPairs>
915 GAIA_NODISCARD GAIA_FORCEINLINE static QueryAccess term_access(const QueryCtx::Data& data, Entity entity) {
916 if (entity == EntityBad)
917 return QueryAccess::None;
918
919 QueryAccess access = QueryAccess::None;
920 const auto terms = data.terms_view();
921 GAIA_FOR((uint32_t)terms.size()) {
922 const auto& term = terms[i];
923 const bool matches = MatchPairs ? access_ids_overlap(term.id, entity) : term.id == entity;
924 if (!matches ||
925 (term.op != QueryOpKind::All && term.op != QueryOpKind::Or && term.op != QueryOpKind::Any) ||
926 term.access == QueryAccess::Match)
927 continue;
928
929 if (term.access == QueryAccess::Write)
930 return QueryAccess::Write;
931 access = QueryAccess::Read;
932 }
933
934 return access;
935 }
936
943 template <bool MatchPairs>
944 GAIA_NODISCARD GAIA_FORCEINLINE static QueryAccess
945 effective_access(const QueryCtx::Data& data, const QueryAccessSet& accessSet, Entity entity) {
946 const auto access = term_access<MatchPairs>(data, entity);
947 if constexpr (!MatchPairs)
948 return merge_access(access, accessSet.access(entity));
949 else {
950 if (!entity.pair())
951 return merge_access(access, accessSet.access(entity));
952 if (access == QueryAccess::Write)
953 return access;
954 for (const auto id: accessSet.writes_view()) {
955 if (access_ids_overlap(id, entity))
956 return QueryAccess::Write;
957 }
958 if (access == QueryAccess::Read)
959 return access;
960 for (const auto id: accessSet.reads_view()) {
961 if (access_ids_overlap(id, entity))
962 return QueryAccess::Read;
963 }
964 return QueryAccess::None;
965 }
966 }
967
972 GAIA_NODISCARD static bool access_conflicts(QueryAccess lhs, QueryAccess rhs) {
973 return (lhs == QueryAccess::Write && rhs != QueryAccess::None) ||
974 (rhs == QueryAccess::Write && lhs != QueryAccess::None);
975 }
976
984 template <bool MatchPairs>
985 GAIA_NODISCARD static bool conflicts_one_way(
986 const QueryCtx::Data& leftData, const QueryAccessSet& leftAccess, const QueryCtx::Data& rightData,
987 const QueryAccessSet& rightAccess) {
988 const auto terms = leftData.terms_view();
989 GAIA_FOR((uint32_t)terms.size()) {
990 const auto& term = terms[i];
991 const auto id = term.id;
992 if (id == EntityBad ||
993 (term.op != QueryOpKind::All && term.op != QueryOpKind::Or && term.op != QueryOpKind::Any) ||
994 term.access == QueryAccess::Match)
995 continue;
996 // This term's access is already known. Duplicate ids are still checked individually.
997 const auto access = term.access == QueryAccess::Write ? QueryAccess::Write : QueryAccess::Read;
998 if (access_conflicts(access, effective_access<MatchPairs>(rightData, rightAccess, id)))
999 return true;
1000 }
1001
1002 for (const auto id: leftAccess.reads_view()) {
1003 if (access_conflicts(QueryAccess::Read, effective_access<MatchPairs>(rightData, rightAccess, id)))
1004 return true;
1005 }
1006 for (const auto id: leftAccess.writes_view()) {
1007 if (access_conflicts(QueryAccess::Write, effective_access<MatchPairs>(rightData, rightAccess, id)))
1008 return true;
1009 }
1010
1011 return false;
1012 }
1013
1017 template <typename T>
1018 GAIA_NODISCARD Entity access_entity_inter() {
1019 if constexpr (is_pair<T>::value) {
1020 const auto& descRel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1021 const auto& descTgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1022 return Pair(descRel.entity, descTgt.entity);
1023 } else {
1024 using UO = typename component_type_t<T>::TypeOriginal;
1025 static_assert(core::is_raw_v<UO>, "Use reads()/writes() with raw types only");
1026 const auto& desc = comp_cache_add<T>(*m_storage.world());
1027 return desc.entity;
1028 }
1029 }
1030
1031 //--------------------------------------------------------------------------------
1032 public:
1034 static inline bool SilenceInvalidCacheKindAssertions = false;
1035
1040 GAIA_PROF_SCOPE(query::fetch);
1041 refresh_world_lifetime();
1042
1043 // Make sure the query was created by World::query()
1044 GAIA_ASSERT(m_storage.is_initialized());
1045
1046 if (!uses_query_cache_storage()) {
1047 if GAIA_UNLIKELY (!m_storage.has_owned_query_info()) {
1048 QueryCtx ctx;
1049 ctx.init(m_storage.world());
1050 commit(ctx);
1051 m_storage.init_owned_query_info(
1052 QueryInfo::create(QueryId{}, GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view()));
1053 } else if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad) {
1054 recommit(m_storage.owned_query_info().ctx());
1055 }
1056
1057 return m_storage.owned_query_info();
1058 }
1059
1060 // If queryId is set it means QueryInfo was already created.
1061 // This is the common case for cached queries.
1062 if GAIA_LIKELY (m_storage.m_identity.handle.id() != QueryIdBad) {
1063 auto* pQueryInfo = m_storage.try_query_info_fast();
1064 if GAIA_UNLIKELY (pQueryInfo == nullptr)
1065 pQueryInfo = m_storage.m_pCache->try_get(m_storage.m_identity.handle);
1066
1067 // The only time when this can be nullptr is just once after Query::destroy is called.
1068 if GAIA_LIKELY (pQueryInfo != nullptr) {
1069 m_storage.cache_query_info(*pQueryInfo);
1070 if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad)
1071 recommit(pQueryInfo->ctx());
1072 return *pQueryInfo;
1073 }
1074
1075 m_storage.invalidate();
1076 }
1077
1078 // No queryId is set which means QueryInfo needs to be created
1079 QueryCtx ctx;
1080 ctx.init(m_storage.world());
1081 commit(ctx);
1082 auto& queryInfo =
1083 uses_shared_cache_layer()
1084 ? m_storage.m_pCache->add(GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view())
1085 : m_storage.m_pCache->add_local(GAIA_MOV(ctx), *m_entityToArchetypeMap, all_archetypes_view());
1086 m_storage.m_identity.handle = QueryInfo::handle(queryInfo);
1087 m_storage.cache_query_info(queryInfo);
1088 m_storage.allow_to_destroy_again();
1089 return queryInfo;
1090 }
1091
1094 void match_all(QueryInfo& queryInfo) {
1095 const auto kindError = validate_kind(queryInfo.ctx());
1096 if (kindError != QueryKindRes::OK) {
1097 GAIA_ASSERT2(SilenceInvalidCacheKindAssertions, kind_error_str(kindError));
1098 queryInfo.reset();
1099 return;
1100 }
1101
1102 if (!uses_query_cache_storage()) {
1103 queryInfo.ensure_matches_transient(
1104 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, m_varBindings,
1105 m_varBindingsMask);
1106 return;
1107 }
1108
1109 queryInfo.ensure_matches(
1110 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, last_archetype_id(),
1111 m_varBindings, m_varBindingsMask);
1112 m_storage.m_pCache->sync_archetype_cache(queryInfo);
1113 }
1114
1120 GAIA_NODISCARD bool match_one(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
1121 if (!uses_query_cache_storage()) {
1122 return queryInfo.ensure_matches_one_transient(archetype, targetEntities, m_varBindings, m_varBindingsMask);
1123 }
1124
1125 return queryInfo.ensure_matches_one(archetype, targetEntities, m_varBindings, m_varBindingsMask);
1126 }
1127
1133 GAIA_NODISCARD bool matches_any(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
1134 const auto kindError = validate_kind(queryInfo.ctx());
1135 if (kindError != QueryKindRes::OK) {
1136 GAIA_ASSERT2(SilenceInvalidCacheKindAssertions, kind_error_str(kindError));
1137 queryInfo.reset();
1138 return false;
1139 }
1140
1141 return matches_target_entities(queryInfo, archetype, targetEntities);
1142 }
1143
1144 //--------------------------------------------------------------------------------
1145
1149 return fetch().cache_policy();
1150 }
1151
1161 QueryImpl& cache_src_trav(uint16_t maxItems) {
1162 if (m_cacheSrcTrav == maxItems)
1163 return *this;
1164
1165 if (maxItems > MaxCacheSrcTrav) {
1166 GAIA_ASSERT(false && "cache_src_trav should be a value smaller than MaxCacheSrcTrav");
1167 maxItems = MaxCacheSrcTrav;
1168 }
1169
1170 invalidate_each_walk_cache();
1171 invalidate_direct_seed_run_cache();
1172 invalidate_query_storage();
1173 m_cacheSrcTrav = maxItems;
1174 return *this;
1175 }
1176
1180 GAIA_NODISCARD uint16_t cache_src_trav() const {
1181 return m_cacheSrcTrav;
1182 }
1183
1189
1201 QueryImpl& ctx(void* pCtx) {
1202 m_ctx = pCtx;
1203 return *this;
1204 }
1205
1208 GAIA_NODISCARD void* ctx() const {
1209 return m_ctx;
1210 }
1212
1216
1224 QueryImpl& main_thread(bool required = true) {
1225 m_mainThread = required;
1226 return *this;
1227 }
1228
1231 GAIA_NODISCARD bool main_thread_required() const {
1232 return m_mainThread;
1233 }
1235
1239
1249 refresh_world_lifetime();
1250 m_access.add_read(entity);
1251 return *this;
1252 }
1253
1258 template <typename T>
1260 return reads(access_entity_inter<T>());
1261 }
1262
1272 refresh_world_lifetime();
1273 m_access.add_write(entity);
1274 return *this;
1275 }
1276
1281 template <typename T>
1283 return writes(access_entity_inter<T>());
1284 }
1285
1288 GAIA_NODISCARD std::span<const Entity> custom_reads() const {
1289 return m_storage.is_current() ? m_access.reads_view() : std::span<const Entity>{};
1290 }
1291
1294 GAIA_NODISCARD std::span<const Entity> custom_writes() const {
1295 return m_storage.is_current() ? m_access.writes_view() : std::span<const Entity>{};
1296 }
1297
1304 GAIA_NODISCARD QueryAccess access(Entity entity) {
1305 const auto& data = fetch().ctx().data;
1306 return entity.pair() ? effective_access<true>(data, m_access, entity)
1307 : effective_access<false>(data, m_access, entity);
1308 }
1309
1317 GAIA_NODISCARD bool conflicts_with(QueryImpl& other) {
1318 const auto& leftData = fetch().ctx().data;
1319 const auto& rightData = other.fetch().ctx().data;
1320 // Select pair-pattern matching once, keeping the exact-id loops free of its extra work.
1322 if (((leftData.deps.flags | rightData.deps.flags) & patternFlags) != 0 || m_access.hasPairPatterns ||
1323 other.m_access.hasPairPatterns)
1324 return conflicts_one_way<true>(leftData, m_access, rightData, other.m_access);
1325 return conflicts_one_way<false>(leftData, m_access, rightData, other.m_access);
1326 }
1327
1331 GAIA_NODISCARD bool can_run_parallel(QueryImpl& other) {
1332 return !m_mainThread && !other.m_mainThread && !conflicts_with(other);
1333 }
1335
1339 QueryImpl& kind(QueryCacheKind cacheKind) {
1340 if (m_cacheKind == cacheKind)
1341 return *this;
1342
1343 invalidate_each_walk_cache();
1344 invalidate_direct_seed_run_cache();
1345 invalidate_query_storage();
1346 m_cacheKind = cacheKind;
1347
1348 return *this;
1349 }
1350
1354 QueryImpl& scope(QueryCacheScope cacheScope) {
1355 if (m_cacheScope == cacheScope)
1356 return *this;
1357
1358 invalidate_each_walk_cache();
1359 invalidate_direct_seed_run_cache();
1360 invalidate_query_storage();
1361 m_cacheScope = cacheScope;
1362
1363 return *this;
1364 }
1365
1369 QueryCmd_MatchPrefab cmd{};
1370 add_cmd(cmd);
1371 return *this;
1372 }
1373
1376 GAIA_NODISCARD QueryCacheScope scope() const {
1377 return m_cacheScope;
1378 }
1379
1382 GAIA_NODISCARD QueryCacheKind kind() const {
1383 return m_cacheKind;
1384 }
1385
1388 GAIA_NODISCARD QueryKindRes kind_error() {
1389 return validate_kind(fetch().ctx());
1390 }
1391
1394 GAIA_NODISCARD const char* kind_error_str() {
1395 return kind_error_str(kind_error());
1396 }
1397
1400 GAIA_NODISCARD bool valid() {
1401 return kind_error() == QueryKindRes::OK;
1402 }
1403
1404 //--------------------------------------------------------------------------------
1405 private:
1409 GAIA_NODISCARD bool uses_manual_src_trav_cache(const QueryCtx& ctx) const {
1410 return m_cacheSrcTrav != 0 && //
1411 ctx.data.deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms) && //
1412 ctx.data.deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms);
1413 }
1414
1418 GAIA_NODISCARD static bool uses_im_cache(const QueryCtx& ctx) {
1419 return ctx.data.cachePolicy == QueryCachePolicy::Immediate;
1420 }
1421
1425 GAIA_NODISCARD static bool uses_lazy_cache(const QueryCtx& ctx) {
1426 return ctx.data.cachePolicy == QueryCachePolicy::Lazy;
1427 }
1428
1432 GAIA_NODISCARD static bool uses_dyn_cache(const QueryCtx& ctx) {
1433 return ctx.data.cachePolicy == QueryCachePolicy::Dynamic;
1434 }
1435
1439 GAIA_NODISCARD QueryKindRes validate_kind(const QueryCtx& ctx) const {
1440 if (m_cacheKind == QueryCacheKind::Auto) {
1441 if (uses_manual_src_trav_cache(ctx))
1442 return QueryKindRes::AutoSrcTrav;
1443 }
1444
1445 if (m_cacheKind == QueryCacheKind::All) {
1446 if (uses_manual_src_trav_cache(ctx))
1447 return QueryKindRes::AllSrcTrav;
1448 if (!uses_im_cache(ctx))
1449 return QueryKindRes::AllNotIm;
1450 }
1451
1452 return QueryKindRes::OK;
1453 }
1454
1458 GAIA_NODISCARD static const char* kind_error_str(QueryKindRes error) {
1459 switch (error) {
1460 case QueryKindRes::OK:
1461 return "OK";
1462 case QueryKindRes::AutoSrcTrav:
1463 return "QueryCacheKind::Auto rejects explicit traversed-source snapshot caching";
1464 case QueryKindRes::AllNotIm:
1465 return "QueryCacheKind::All requires a fully immediate structural cache";
1466 case QueryKindRes::AllSrcTrav:
1467 return "QueryCacheKind::All rejects explicit traversed-source snapshot caching";
1468 default:
1469 return "Unknown query kind validation error";
1470 }
1471 }
1472
1475 GAIA_NODISCARD EachWalkData* each_walk_data() {
1476 return m_eachWalkData.get();
1477 }
1478
1481 GAIA_NODISCARD const EachWalkData* each_walk_data() const {
1482 return m_eachWalkData.get();
1483 }
1484
1487 GAIA_NODISCARD EachWalkData& ensure_each_walk_data() {
1488 return m_eachWalkData.ensure();
1489 }
1490
1492 void invalidate_each_walk_cache() {
1493 auto* pWalkData = each_walk_data();
1494 if (pWalkData != nullptr)
1495 pWalkData->cacheValid = false;
1496 }
1497
1500 GAIA_NODISCARD DirectSeedRunData* direct_seed_run_data() {
1501 return m_directSeedRunData.get();
1502 }
1503
1506 GAIA_NODISCARD const DirectSeedRunData* direct_seed_run_data() const {
1507 return m_directSeedRunData.get();
1508 }
1509
1512 GAIA_NODISCARD DirectSeedRunData& ensure_direct_seed_run_data() {
1513 return m_directSeedRunData.ensure();
1514 }
1515
1517 void invalidate_direct_seed_run_cache() {
1518 auto* pRunData = direct_seed_run_data();
1519 if (pRunData != nullptr)
1520 pRunData->cacheValid = false;
1521 }
1522
1524 void reset_changed_filter_state() {
1525 m_changedWorldVersion = 0;
1526 }
1527
1530 ArchetypeId last_archetype_id() const {
1531 return *m_nextArchetypeId - 1;
1532 }
1533
1536 GAIA_NODISCARD std::span<const Archetype*> all_archetypes_view() const {
1537 GAIA_ASSERT(m_allArchetypes != nullptr);
1538 return {(const Archetype**)m_allArchetypes->data(), m_allArchetypes->size()};
1539 }
1540
1541 GAIA_NODISCARD static bool is_query_var_entity(Entity entity) {
1542 return is_variable((EntityId)entity.id());
1543 }
1544
1545 GAIA_NODISCARD static uint32_t query_var_idx(Entity entity) {
1546 GAIA_ASSERT(is_query_var_entity(entity));
1547 return (uint32_t)(entity.id() - Var0.id());
1548 }
1549
1550 GAIA_NODISCARD Entity query_var_entity(uint32_t idx) {
1551 GAIA_ASSERT(idx < 8);
1552 return entity_from_id((const World&)*m_storage.world(), (EntityId)(Var0.id() + idx));
1553 }
1554
1555 GAIA_NODISCARD static util::str_view normalize_var_name(util::str_view name) {
1556 auto trimmed = util::trim(name);
1557 if (trimmed.empty())
1558 return {};
1559
1560 if (trimmed.data()[0] == '$') {
1561 if (trimmed.size() == 1)
1562 return {};
1563 trimmed = util::str_view(trimmed.data() + 1, trimmed.size() - 1);
1564 }
1565
1566 return util::trim(trimmed);
1567 }
1568
1569 GAIA_NODISCARD static bool is_reserved_var_name(util::str_view varName) {
1570 return varName == "this";
1571 }
1572
1573 GAIA_NODISCARD Entity find_var_by_name(util::str_view rawName) {
1574 const auto varName = normalize_var_name(rawName);
1575 if (varName.empty() || is_reserved_var_name(varName))
1576 return EntityBad;
1577
1578 GAIA_FOR(8) {
1579 const auto bit = (uint8_t(1) << i);
1580 if ((m_varNamesMask & bit) == 0)
1581 continue;
1582 if (m_varNames[i] == varName)
1583 return query_var_entity(i);
1584 }
1585
1586 return EntityBad;
1587 }
1588
1589 bool set_var_name_internal(Entity varEntity, util::str_view rawName) {
1590 if (!is_query_var_entity(varEntity))
1591 return false;
1592
1593 const auto varName = normalize_var_name(rawName);
1594 if (varName.empty() || is_reserved_var_name(varName))
1595 return false;
1596
1597 const auto idx = query_var_idx(varEntity);
1598 const auto bit = (uint8_t(1) << idx);
1599
1600 GAIA_FOR(8) {
1601 if (i == idx)
1602 continue;
1603
1604 const auto otherBit = (uint8_t(1) << i);
1605 if ((m_varNamesMask & otherBit) == 0)
1606 continue;
1607 if (!(m_varNames[i] == varName))
1608 continue;
1609
1610 GAIA_ASSERT2(false, "Variable name is already assigned to a different query variable");
1611 return false;
1612 }
1613
1614 m_varNames[idx].assign(varName);
1615 m_varNamesMask |= bit;
1616 return true;
1617 }
1618
1619 template <typename T>
1620 void add_cmd(T& cmd) {
1621 refresh_world_lifetime();
1622 invalidate_each_walk_cache();
1623
1624 // Make sure to invalidate if necessary.
1625 if constexpr (T::InvalidatesHash) {
1626 reset_changed_filter_state();
1627 m_storage.invalidate();
1628 }
1629
1630 auto& serBuffer = m_storage.ser_buffer();
1631 ser::save(serBuffer, T::Id);
1632 ser::save(serBuffer, T::InvalidatesHash);
1633 ser::save(serBuffer, cmd);
1634 }
1635
1636 void add_inter(QueryInput item) {
1637#if GAIA_ASSERT_ENABLED
1638 GAIA_ASSERT(
1639 item.access != QueryAccess::Match || item.op == QueryOpKind::All || item.op == QueryOpKind::Or ||
1640 item.op == QueryOpKind::Any);
1641 // Exclusions have no payload access.
1642 GAIA_ASSERT(item.op != QueryOpKind::Not || item.access == QueryAccess::None);
1643#endif
1644
1645 QueryCmd_AddItem cmd{item};
1646 add_cmd(cmd);
1647 }
1648
1649 GAIA_NODISCARD static QueryAccess normalize_access(QueryOpKind op, QueryAccess access) {
1650#if GAIA_ASSERT_ENABLED
1651 GAIA_ASSERT(
1652 access != QueryAccess::Match || op == QueryOpKind::All || op == QueryOpKind::Or ||
1653 op == QueryOpKind::Any);
1654#endif
1655 if (op == QueryOpKind::Not)
1656 return QueryAccess::None;
1657
1658 // Payload-free terms are normalized to Match when the query is compiled.
1659 if (access == QueryAccess::None)
1660 return QueryAccess::Read;
1661
1662 return access;
1663 }
1664
1665 void add_entity_term(QueryOpKind op, Entity entity, const QueryTermOptions& options) {
1666 const auto access = normalize_access(op, options.access);
1667 add(
1668 {op, access, entity, options.entSrc, options.entTrav, options.travKind, options.travDepth,
1669 options.matchKind});
1670 }
1671
1672 template <typename T>
1673 void add_inter(QueryOpKind op) {
1674 Entity e;
1675
1676 if constexpr (is_pair<T>::value) {
1677 // Make sure the components are always registered
1678 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1679 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1680
1681 e = Pair(desc_rel.entity, desc_tgt.entity);
1682 } else {
1683 // Make sure the component is always registered
1684 const auto& desc = comp_cache_add<T>(*m_storage.world());
1685 e = desc.entity;
1686 }
1687
1688 // Determine the access type
1689 QueryAccess access = QueryAccess::None;
1690 if (op != QueryOpKind::Not) {
1691 constexpr auto isReadWrite = core::is_mut_v<T>;
1692 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1693 }
1694
1695 add_inter({op, access, e});
1696 }
1697
1698 template <typename T>
1699 void add_inter(QueryOpKind op, const QueryTermOptions& options) {
1700 Entity e;
1701
1702 if constexpr (is_pair<T>::value) {
1703 // Make sure the components are always registered
1704 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1705 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1706
1707 e = Pair(desc_rel.entity, desc_tgt.entity);
1708 } else {
1709 // Make sure the component is always registered
1710 const auto& desc = comp_cache_add<T>(*m_storage.world());
1711 e = desc.entity;
1712 }
1713
1714 QueryAccess access = options.access;
1715 if (op != QueryOpKind::Not && access == QueryAccess::None) {
1716 constexpr auto isReadWrite = core::is_mut_v<T>;
1717 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1718 }
1719
1720 add_inter(
1721 {op, normalize_access(op, access), e, options.entSrc, options.entTrav, options.travKind,
1722 options.travDepth, options.matchKind});
1723 }
1724
1725 template <typename Rel, typename Tgt>
1726 void add_inter(QueryOpKind op) {
1727 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1728 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1729 static_assert(core::is_raw_v<UO_Rel>, "Use add() with raw types only");
1730 static_assert(core::is_raw_v<UO_Tgt>, "Use add() with raw types only");
1731
1732 // Make sure the component is always registered
1733 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1734 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1735
1736 // Determine the access type
1737 QueryAccess access = QueryAccess::None;
1738 if (op != QueryOpKind::Not) {
1739 constexpr auto isReadWrite = core::is_mut_v<UO_Rel> || core::is_mut_v<UO_Tgt>;
1740 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1741 }
1742
1743 add_inter({op, access, {descRel.entity, descTgt.entity}});
1744 }
1745
1746 //--------------------------------------------------------------------------------
1747
1748 void changed_inter(Entity entity) {
1749 QueryCmd_AddFilter cmd{entity};
1750 add_cmd(cmd);
1751 }
1752
1753 template <typename T>
1754 void changed_inter() {
1755 using UO = typename component_type_t<T>::TypeOriginal;
1756 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1757
1758 // Make sure the component is always registered
1759 const auto& desc = comp_cache_add<T>(*m_storage.world());
1760 changed_inter(desc.entity);
1761 }
1762
1763 template <typename Rel, typename Tgt>
1764 void changed_inter() {
1765 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1766 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1767 static_assert(core::is_raw_v<UO_Rel>, "Use changed() with raw types only");
1768 static_assert(core::is_raw_v<UO_Tgt>, "Use changed() with raw types only");
1769
1770 // Make sure the component is always registered
1771 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1772 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1773 changed_inter({descRel.entity, descTgt.entity});
1774 }
1775
1776 //--------------------------------------------------------------------------------
1777
1778 void sort_by_inter(Entity entity, TSortByFunc func) {
1779 QueryCmd_SortBy cmd{entity, func};
1780 add_cmd(cmd);
1781 }
1782
1783 template <typename T>
1784 void sort_by_inter(TSortByFunc func) {
1785 using UO = typename component_type_t<T>::TypeOriginal;
1786 if constexpr (std::is_same_v<UO, Entity>) {
1787 sort_by_inter(EntityBad, func);
1788 } else {
1789 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1790
1791 // Make sure the component is always registered
1792 const auto& desc = comp_cache_add<T>(*m_storage.world());
1793
1794 sort_by_inter(desc.entity, func);
1795 }
1796 }
1797
1798 template <typename Rel, typename Tgt>
1799 void sort_by_inter(TSortByFunc func) {
1800 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1801 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1802 static_assert(core::is_raw_v<UO_Rel>, "Use group_by() with raw types only");
1803 static_assert(core::is_raw_v<UO_Tgt>, "Use group_by() with raw types only");
1804
1805 // Make sure the component is always registered
1806 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1807 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1808
1809 sort_by_inter({descRel.entity, descTgt.entity}, func);
1810 }
1811
1812 //--------------------------------------------------------------------------------
1813
1814 void group_by_inter(Entity entity, TGroupByFunc func, bool orderGroups = false) {
1815 QueryCmd_GroupBy cmd{entity, func, orderGroups ? (uint16_t)QueryCtx::QueryFlags::OrderGroups : (uint16_t)0};
1816 add_cmd(cmd);
1817 }
1818
1819 template <typename T>
1820 void group_by_inter(Entity entity, TGroupByFunc func) {
1821 using UO = typename component_type_t<T>::TypeOriginal;
1822 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
1823
1824 group_by_inter(entity, func);
1825 }
1826
1827 template <typename Rel, typename Tgt>
1828 void group_by_inter(TGroupByFunc func) {
1829 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
1830 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
1831 static_assert(core::is_raw_v<UO_Rel>, "Use group_by() with raw types only");
1832 static_assert(core::is_raw_v<UO_Tgt>, "Use group_by() with raw types only");
1833
1834 // Make sure the component is always registered
1835 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1836 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1837
1838 group_by_inter({descRel.entity, descTgt.entity}, func);
1839 }
1840
1841 //--------------------------------------------------------------------------------
1842
1843 void group_dep_inter(Entity relation) {
1844 GAIA_ASSERT(!relation.pair());
1845 QueryCmd_GroupDep cmd{relation};
1846 add_cmd(cmd);
1847 }
1848
1849 template <typename T>
1850 void group_dep_inter() {
1851 using UO = typename component_type_t<T>::TypeOriginal;
1852 static_assert(core::is_raw_v<UO>, "Use group_dep() with raw types only");
1853
1854 const auto& desc = comp_cache_add<T>(*m_storage.world());
1855 group_dep_inter(desc.entity);
1856 }
1857
1858 //--------------------------------------------------------------------------------
1859
1860 void set_group_id_inter(GroupId groupId) {
1861 refresh_world_lifetime();
1862 // Dummy usage of GroupIdMax to avoid warning about unused constant
1863 (void)GroupIdMax;
1864
1865 invalidate_each_walk_cache();
1866 m_groupIdSet = groupId;
1867 }
1868
1869 void set_group_id_inter(Entity groupId) {
1870 set_group_id_inter(groupId.id());
1871 }
1872
1873 template <typename T>
1874 void set_group_id_inter() {
1875 using UO = typename component_type_t<T>::TypeOriginal;
1876 static_assert(core::is_raw_v<UO>, "Use group_id() with raw types only");
1877
1878 // Make sure the component is always registered
1879 const auto& desc = comp_cache_add<T>(*m_storage.world());
1880 set_group_id_inter(desc.entity);
1881 }
1882
1883 //--------------------------------------------------------------------------------
1884
1885 void commit(QueryCtx& ctx) {
1886 GAIA_PROF_SCOPE(query::commit);
1887
1888#if GAIA_ASSERT_ENABLED
1889 GAIA_ASSERT(m_storage.m_identity.handle.id() == QueryIdBad);
1890#endif
1891
1892 auto& serBuffer = m_storage.ser_buffer();
1893
1894 // Read data from buffer and execute the command stored in it
1895 serBuffer.seek(0);
1896 while (serBuffer.tell() < serBuffer.bytes()) {
1897 QueryCmdType id{};
1898 bool invalidatesHash = false;
1899 ser::load(serBuffer, id);
1900 ser::load(serBuffer, invalidatesHash);
1901 (void)invalidatesHash; // We don't care about this during commit
1902 CommandBufferRead[id](serBuffer, ctx);
1903 }
1904
1905 // Calculate the lookup hash from the provided context
1906 if (uses_query_cache_storage()) {
1907 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1908 normalize_cache_src_trav(ctx);
1909 }
1910 if (uses_shared_cache_layer()) {
1911 auto& ctxData = ctx.data;
1912 if (ctxData.changedCnt > 1) {
1913 core::sort(ctxData.changed.data(), ctxData.changed.data() + ctxData.changedCnt, SortComponentCond{});
1914 }
1915 }
1916
1917 // We can free all temporary data now
1918 m_storage.ser_buffer_reset();
1919
1920 // Refresh the context
1921 ctx.refresh();
1922 if (uses_shared_cache_layer())
1923 calc_lookup_hash(ctx);
1924 }
1925
1926 void recommit(QueryCtx& ctx) {
1927 GAIA_PROF_SCOPE(query::recommit);
1928
1929 auto& serBuffer = m_storage.ser_buffer();
1930
1931 // Read data from buffer and execute the command stored in it
1932 serBuffer.seek(0);
1933 while (serBuffer.tell() < serBuffer.bytes()) {
1934 QueryCmdType id{};
1935 bool invalidatesHash = false;
1936 ser::load(serBuffer, id);
1937 ser::load(serBuffer, invalidatesHash);
1938 // Hash recalculation is not accepted here
1939 GAIA_ASSERT(!invalidatesHash);
1940 if (invalidatesHash)
1941 return;
1942 CommandBufferRead[id](serBuffer, ctx);
1943 }
1944 if (uses_query_cache_storage()) {
1945 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1946 normalize_cache_src_trav(ctx);
1947 }
1948
1949 // We can free all temporary data now
1950 m_storage.ser_buffer_reset();
1951 }
1952
1953 //--------------------------------------------------------------------------------
1954 public:
1961 GAIA_NODISCARD static bool match_filters(
1962 const Chunk& chunk, const QueryInfo& queryInfo, uint32_t changedWorldVersion,
1963 std::span<const uint8_t> compIndices) {
1964 GAIA_ASSERT(!chunk.empty() && "match_filters called on an empty chunk");
1965
1966 const auto queryVersion = changedWorldVersion;
1967 const auto& data = queryInfo.ctx().data;
1969 return match_filters(chunk, queryInfo, changedWorldVersion);
1970
1971 const auto changedFields = data.changed_fields_view();
1972
1973 if (changedFields.empty())
1974 return false;
1975
1976 const auto changedCnt = (uint32_t)changedFields.size();
1977 if (changedCnt == 1) {
1978 const auto fieldIdx = changedFields[0];
1979 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1980 if (compIdx == (uint8_t)0xFF)
1981 return match_filters(chunk, queryInfo, changedWorldVersion);
1982 if (chunk.changed(queryVersion, compIdx))
1983 return true;
1984
1985 return chunk.entity_order_changed(changedWorldVersion);
1986 }
1987
1988 GAIA_FOR(changedCnt) {
1989 const auto fieldIdx = changedFields[i];
1990 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1991 if (compIdx == (uint8_t)0xFF)
1992 return match_filters(chunk, queryInfo, changedWorldVersion);
1993 if (chunk.changed(queryVersion, compIdx))
1994 return true;
1995 }
1996
1997 return chunk.entity_order_changed(changedWorldVersion);
1998 }
1999
2005 GAIA_NODISCARD static bool
2006 match_filters(const Chunk& chunk, const QueryInfo& queryInfo, uint32_t changedWorldVersion) {
2007 GAIA_ASSERT(!chunk.empty() && "match_filters called on an empty chunk");
2008
2009 const auto queryVersion = changedWorldVersion;
2010 const auto& filtered = queryInfo.ctx().data.changed_view();
2011
2012 // Skip unchanged chunks
2013 if (filtered.empty())
2014 return false;
2015
2016 const auto filteredCnt = (uint32_t)filtered.size();
2017 auto ids = chunk.ids_view();
2018
2019 // This is the hot path for most change-filter queries.
2020 if (filteredCnt == 1) {
2021 const auto compIdx = core::get_index(ids, filtered[0]);
2022 if (compIdx != BadIndex && chunk.changed(queryVersion, compIdx))
2023 return true;
2024
2025 return chunk.entity_order_changed(changedWorldVersion);
2026 }
2027
2028 // See if any component has changed
2029 uint32_t lastIdx = 0;
2030 for (const auto comp: filtered) {
2031 uint32_t compIdx = BadIndex;
2032 if (lastIdx < (uint32_t)ids.size()) {
2033 const auto suffixIdx =
2034 core::get_index(std::span<const Entity>(ids.data() + lastIdx, ids.size() - lastIdx), comp);
2035 if (suffixIdx != BadIndex)
2036 compIdx = lastIdx + suffixIdx;
2037 }
2038
2039 // Fallback for queries where change-filters are not monotonic in chunk column order
2040 // (e.g. OR-driven layouts).
2041 if (compIdx == BadIndex)
2042 compIdx = core::get_index(ids, comp);
2043 if (compIdx == BadIndex)
2044 continue;
2045
2046 if (chunk.changed(queryVersion, compIdx))
2047 return true;
2048
2049 lastIdx = compIdx;
2050 }
2051
2052 // If none of the tracked components changed, row movement can still make the
2053 // filtered query observable because newly added or moved entities must be seen.
2054 return chunk.entity_order_changed(changedWorldVersion);
2055 }
2056
2061 GAIA_NODISCARD bool can_process_archetype(const QueryInfo& queryInfo, const Archetype& archetype) const {
2062 // Archetypes requested for deletion are skipped for processing.
2063 if (archetype.is_req_del())
2064 return false;
2065
2066 // Prefabs are excluded from query results by default unless the query opted in
2067 // explicitly or it mentions Prefab directly.
2068 if (!queryInfo.matches_prefab_entities() && archetype.has(Prefab))
2069 return false;
2070
2071 return true;
2072 }
2073
2077 GAIA_NODISCARD static bool has_depth_order_hierarchy_enabled_barrier(const QueryInfo& queryInfo) {
2078 const auto& data = queryInfo.ctx().data;
2079 return data.groupByFunc == group_by_func_depth_order &&
2080 world_relation_depth_order_prunes_disabled_subtrees(*queryInfo.world(), data.groupBy);
2081 }
2082
2087 GAIA_NODISCARD static bool
2088 needs_depth_order_hierarchy_barrier_cache(const QueryInfo& queryInfo, Constraints constraints) {
2089 return constraints != Constraints::AcceptAll && has_depth_order_hierarchy_enabled_barrier(queryInfo);
2090 }
2091
2100 Chunk* pChunk, Constraints constraints, bool needsBarrierCache, bool barrierPasses, uint16_t& from,
2101 uint16_t& to) noexcept {
2102 if (needsBarrierCache && constraints == Constraints::DisabledOnly && !barrierPasses) {
2103 from = 0;
2104 to = pChunk->size();
2105 return;
2106 }
2107
2108 from = detail::ChunkIterImpl::start_index(pChunk, constraints);
2109 to = detail::ChunkIterImpl::end_index(pChunk, constraints);
2110 }
2111
2115 GAIA_NODISCARD static bool depth_order_hierarchy_barrier_prunes(const QueryInfo& queryInfo) {
2116 return has_depth_order_hierarchy_enabled_barrier(queryInfo) && queryInfo.barrier_may_prune();
2117 }
2118
2128 GAIA_NODISCARD static bool
2131 return true;
2132
2133 const auto& world = *queryInfo.world();
2134 const auto relation = queryInfo.ctx().data.groupBy;
2135 auto ids = archetype.ids_view();
2136
2137 for (auto idsIdx: archetype.pair_rel_indices(relation)) {
2138 const auto pair = ids[idsIdx];
2139 const auto parent = world_pair_target_if_alive(world, pair);
2140 if (parent == EntityBad)
2141 return false;
2142 if (!world_entity_enabled_hierarchy(world, parent, relation))
2143 return false;
2144 }
2145
2146 return true;
2147 }
2148
2155 GAIA_NODISCARD bool can_process_archetype_inter(
2156 const QueryInfo& queryInfo, const Archetype& archetype, Constraints constraints,
2157 int8_t barrierPasses = -1) const {
2158 if (!can_process_archetype(queryInfo, archetype))
2159 return false;
2160 if (constraints == Constraints::EnabledOnly) {
2162 if (barrierPasses >= 0)
2163 return barrierPasses != 0;
2164 if (!survives_cascade_hierarchy_enabled_barrier(queryInfo, archetype))
2165 return false;
2166 }
2167 }
2168 return true;
2169 }
2170
2178 class ParallelScope final {
2179 World* m_pWorld;
2180
2181 public:
2182 ParallelScope(World& world, uint32_t itemCount): m_pWorld(&world) {
2183 world_defer_parallel_begin(*m_pWorld, itemCount);
2184 }
2185 ~ParallelScope() {
2186 world_defer_parallel_end(*m_pWorld);
2187 }
2188
2189 ParallelScope(ParallelScope&&) = delete;
2190 ParallelScope(const ParallelScope&) = delete;
2191 ParallelScope& operator=(ParallelScope&&) = delete;
2192 ParallelScope& operator=(const ParallelScope&) = delete;
2193 };
2194
2200 class ParallelSlot final {
2201 DeferSlotScope m_scope;
2202
2203 public:
2204 explicit ParallelSlot(uint32_t idxStart): m_scope(idxStart) {}
2205
2206 ParallelSlot(ParallelSlot&&) = delete;
2207 ParallelSlot(const ParallelSlot&) = delete;
2208 ParallelSlot& operator=(ParallelSlot&&) = delete;
2209 ParallelSlot& operator=(const ParallelSlot&) = delete;
2210 };
2211
2213 template <typename TIter>
2214 static void finish_iter_writes(TIter& it) {
2215 if (it.chunk() == nullptr)
2216 return;
2217
2218 auto compIndices = it.touched_comp_indices();
2219 for (auto compIdx: compIndices)
2220 const_cast<Chunk*>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
2221
2222 auto terms = it.touched_terms();
2223 if (terms.empty())
2224 return;
2225
2226 auto entities = it.entity_rows();
2227 auto& world = *it.world();
2228 GAIA_EACH(terms) {
2229 const auto term = terms[i];
2230 if (!world_component_uses_sparse_storage(world, term)) {
2231 const auto compIdx = core::get_index(it.chunk()->ids_view(), term);
2232 if (compIdx != BadIndex) {
2233 const_cast<Chunk*>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
2234 continue;
2235 }
2236 }
2237
2238 GAIA_FOR_(entities.size(), j) {
2239 world_finish_write(world, term, entities[j]);
2240 }
2241 }
2242 }
2243
2244 static void finish_typed_chunk_writes_runtime(
2245 World& world, Chunk* pChunk, uint16_t from, uint16_t to, const Entity* pArgIds, const bool* pWriteFlags,
2246 uint32_t argCnt, uint32_t firstWriteArg, void* const* pSparseStores = nullptr);
2247
2248 template <typename... T>
2249 static void finish_typed_chunk_writes(World& world, Chunk* pChunk, uint16_t from, uint16_t to);
2250
2251 static void finish_typed_iter_writes_runtime(
2252 Iter& it, const Entity* pArgIds, const bool* pWriteFlags, uint32_t argCnt, uint32_t firstWriteArg);
2254
2256 enum class ExecPayloadKind : uint8_t {
2258 Plain,
2260 Grouped,
2263 };
2264
2269 GAIA_NODISCARD static ExecPayloadKind exec_payload_kind(const QueryInfo& queryInfo, Constraints constraints) {
2270 if (queryInfo.has_sorted_payload())
2272 if (!queryInfo.has_grouped_payload())
2274 if (needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints))
2277 }
2278
2284 enum class QueryPlanMode : uint8_t {
2286 Empty,
2288 General,
2290 EntitySeed,
2299 Sorted,
2301 Traversal
2302 };
2303
2324
2338
2340 struct QueryCacheRange final {
2342 uint32_t idxFrom = 0;
2344 uint32_t idxTo = 0;
2346 bool hasSelectedGroup = false;
2348 bool valid = true;
2349 };
2350
2352 struct IterModeEnabled final {};
2354 struct IterModeDisabledOnly final {};
2356 struct IterModeAcceptAll final {};
2357
2361 template <typename TMode>
2362 GAIA_NODISCARD static constexpr Constraints iter_mode_constraints() {
2363 if constexpr (std::is_same_v<TMode, IterModeDisabledOnly>)
2364 return Constraints::DisabledOnly;
2365 else if constexpr (std::is_same_v<TMode, IterModeAcceptAll>)
2366 return Constraints::AcceptAll;
2367 else
2368 return Constraints::EnabledOnly;
2369 }
2370
2371 //--------------------------------------------------------------------------------
2372
2380 template <typename Func, typename TMode>
2381 static void run_query_func(World* pWorld, Func func, ChunkBatch& batch) {
2382 Iter it;
2383 it.init_query_state(pWorld, iter_mode_constraints<TMode>(), false);
2384 it.set_archetype(batch.pArchetype);
2385 it.set_chunk(batch.pChunk, batch.from, batch.to);
2386 it.set_group_id(batch.groupId);
2387 it.set_comp_indices(batch.pCompIndices);
2388 it.set_inherited_data(batch.inheritedData);
2389 func(it);
2390 finish_iter_writes(it);
2391 it.clear_touched_writes();
2392 }
2393
2402 template <typename Func>
2403 static void run_query_arch_func(World* pWorld, Func func, ChunkBatch& batch, Constraints constraints) {
2404 Iter it;
2405 it.init_query_state(pWorld, constraints, false);
2406 it.set_archetype(batch.pArchetype);
2407 // it.set_chunk(nullptr, 0, 0); We do not need this, and calling it would assert
2408 it.set_group_id(batch.groupId);
2409 it.set_comp_indices(batch.pCompIndices);
2410 it.set_inherited_data(batch.inheritedData);
2411 func(it);
2412 it.clear_touched_writes();
2413 }
2414
2422 template <typename Func, typename TMode>
2423 static void run_query_func(World* pWorld, Func func, std::span<ChunkBatch> batches) {
2424 GAIA_PROF_SCOPE(query::run_query_func);
2425
2426 const auto chunkCnt = batches.size();
2427 GAIA_ASSERT(chunkCnt > 0);
2428
2429 Iter it;
2430 it.init_query_state(pWorld, iter_mode_constraints<TMode>(), false);
2431
2432 const Archetype* pLastArchetype = nullptr;
2433 const uint8_t* pLastIndices = nullptr;
2434 InheritedTermDataView lastInheritedData{};
2435 GroupId lastGroupId = GroupIdMax;
2436
2437 const auto apply_batch = [&](const ChunkBatch& batch) {
2438 if (batch.pArchetype != pLastArchetype) {
2439 it.set_archetype(batch.pArchetype);
2440 pLastArchetype = batch.pArchetype;
2441 }
2442
2443 if (batch.pCompIndices != pLastIndices) {
2444 it.set_comp_indices(batch.pCompIndices);
2445 pLastIndices = batch.pCompIndices;
2446 }
2447
2448 if (batch.inheritedData.data() != lastInheritedData.data()) {
2449 it.set_inherited_data(batch.inheritedData);
2450 lastInheritedData = batch.inheritedData;
2451 }
2452
2453 if (batch.groupId != lastGroupId) {
2454 it.set_group_id(batch.groupId);
2455 lastGroupId = batch.groupId;
2456 }
2457
2458 it.set_chunk(batch.pChunk, batch.from, batch.to);
2459 func(it);
2460 finish_iter_writes(it);
2461 it.clear_touched_writes();
2462 };
2463
2464 // We only have one chunk to process.
2465 if GAIA_UNLIKELY (chunkCnt == 1) {
2466 apply_batch(batches[0]);
2467 return;
2468 }
2469
2470 // We have many chunks to process.
2471 // Chunks might be located at different memory locations. Not even in the same memory page.
2472 // Therefore, to make it easier for the CPU we give it a hint that we want to prefetch data
2473 // for the next chunk explicitly so we do not end up stalling later.
2474 // Note, this is a micro optimization and on average it brings no performance benefit. It only
2475 // helps with edge cases.
2476 // Let us be conservative for now and go with T2. That means we will try to keep our data at
2477 // least in L3 cache or higher.
2478 gaia::prefetch(batches[1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
2479 apply_batch(batches[0]);
2480
2481 uint32_t chunkIdx = 1;
2482 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
2483 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
2484 apply_batch(batches[chunkIdx]);
2485 }
2486
2487 apply_batch(batches[chunkIdx]);
2488 }
2489
2490 //------------------------------------------------
2491
2493 template <typename Func, typename TMode>
2494 struct QueryJobCtx {
2495 QueryImpl* pSelf = nullptr;
2496 World* pWorld = nullptr;
2498 Func func;
2499
2500 GAIA_USE_SMALLBLOCK(QueryJobCtx)
2501 };
2502
2503 template <typename Func>
2504 struct QueryTaskJobCtx {
2505 QueryImpl* pSelf = nullptr;
2506 Func func;
2507 QueryExecType execType = QueryExecType::Default;
2508
2509 GAIA_USE_SMALLBLOCK(QueryTaskJobCtx)
2510 };
2511
2514 template <typename Func>
2515 struct IterJobCallback {
2517 QueryImpl* pSelf = nullptr;
2519 Func func;
2520
2523 void operator()(Iter& it) {
2524 it.ctx(pSelf->ctx());
2525#if GAIA_ASSERT_ENABLED
2526 it.set_query_access(&pSelf->fetch().ctx().data);
2527#endif
2528 func(it);
2529 }
2530 };
2531
2534 template <typename Func>
2535 struct TypedJobCallback {
2537 QueryImpl* pSelf = nullptr;
2539 Func func;
2540
2543 void operator()(Iter& it) {
2544 pSelf->each_iter(it, func);
2545 }
2546 };
2547
2548 template <typename Func>
2549 static void invoke_query_task_job(void* pCtx) {
2550 auto& ctx = *reinterpret_cast<QueryTaskJobCtx<Func>*>(pCtx);
2551 ctx.pSelf->each(ctx.func, ctx.execType);
2552 }
2553
2554 template <typename Func>
2555 static void cleanup_query_task_job(void* pCtx) {
2556 auto* pJobCtx = reinterpret_cast<QueryTaskJobCtx<Func>*>(pCtx);
2557 if (pJobCtx == nullptr)
2558 return;
2559 delete pJobCtx;
2560 }
2561
2562 template <typename Func, typename TMode>
2563 static void cleanup_query_job(void* pCtx) {
2564 auto* pJobCtx = reinterpret_cast<QueryJobCtx<Func, TMode>*>(pCtx);
2565 if (pJobCtx == nullptr)
2566 return;
2567
2568 auto* pWorld = pJobCtx->pWorld;
2569 if (pWorld != nullptr) {
2570 unlock(*pWorld);
2571 // Apply the sorted-query invalidations and deliver the OnSet notifications workers
2572 // recorded. This runs once the job finished, on the thread that waited for it,
2573 // with the world already unlocked.
2574 world_defer_parallel_end(*pWorld);
2575 commit_cmd_buffer_st(*pWorld);
2576 commit_cmd_buffer_mt(*pWorld);
2577 if (pJobCtx->pSelf != nullptr)
2578 pJobCtx->pSelf->m_changedWorldVersion = *pJobCtx->pSelf->m_worldVersion;
2579 }
2580
2581 delete pJobCtx;
2582 }
2583
2584 template <typename Func, typename TMode, QueryExecType ExecType>
2585 GAIA_NODISCARD SchedJob add_parallel_query_job(Func func) {
2586 static_assert(ExecType != QueryExecType::Default);
2587 if (m_batches.empty()) {
2588 m_changedWorldVersion = *m_worldVersion;
2589 return {};
2590 }
2591
2592 auto* pWorld = m_storage.world();
2593 lock(*pWorld);
2594
2595 auto* pCtx = new QueryJobCtx<Func, TMode>{this, pWorld, {}, GAIA_MOV(func)};
2596 pCtx->batches.resize(m_batches.size());
2597 GAIA_EACH(m_batches) pCtx->batches[i] = m_batches[i];
2598 m_batches.clear();
2599
2600 SchedParDesc desc{};
2601 desc.pCtx = pCtx;
2602 desc.itemCount = (uint32_t)pCtx->batches.size();
2603 desc.groupSize = 0;
2604 desc.execType = ExecType;
2605 desc.invoke = [](void* pInvokeCtx, uint32_t idxStart, uint32_t idxEnd) {
2606 auto& ctx = *reinterpret_cast<QueryJobCtx<Func, TMode>*>(pInvokeCtx);
2607 ParallelSlot slot(idxStart);
2608 run_query_func<Func, TMode>(ctx.pWorld, ctx.func, std::span(&ctx.batches[idxStart], idxEnd - idxStart));
2609 };
2610
2611 // Matched by world_defer_parallel_end() in cleanup_query_job(), which runs after
2612 // the job completed and the world was unlocked.
2613 world_defer_parallel_begin(*pWorld, desc.itemCount);
2614
2615 return sched_add_par(world_sched(*pWorld), desc, pCtx, &cleanup_query_job<Func, TMode>);
2616 }
2617
2618 template <bool HasFilters>
2619 void
2620 collect_runtime_parallel_batches(const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints) {
2621 auto cacheView = queryInfo.cache_archetype_view();
2622 const bool hasSortedPlanPayload =
2623 plan.payloadKind == ExecPayloadKind::NonTrivial && (plan.flags & QueryPlanFlag_Sorted) != 0;
2624 const auto sortView =
2625 hasSortedPlanPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
2626 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
2627 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
2628 if (needsBarrierCache)
2629 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2630
2631 if (!sortView.empty()) {
2632 for (const auto& view: sortView) {
2633 const auto* pArchetype = cacheView[view.archetypeIdx];
2634 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2635 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2636 continue;
2637
2638 const auto viewFrom = view.startRow;
2639 const auto viewTo = (uint16_t)(view.startRow + view.count);
2640 uint16_t minStartRow = 0;
2641 uint16_t minEndRow = 0;
2642 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2643 const auto startRow = core::get_max(minStartRow, viewFrom);
2644 const auto endRow = core::get_min(minEndRow, viewTo);
2645 if (endRow == startRow)
2646 continue;
2647
2648 if constexpr (HasFilters) {
2649 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2650 continue;
2651 }
2652
2653 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2654 const auto inheritedDataView =
2655 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2656 push_matching_chunk_batch(
2657 m_batches, queryInfo, pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow,
2658 endRow);
2659 }
2660 return;
2661 }
2662
2663 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
2664 const auto* pArchetype = cacheView[i];
2665 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2666 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2667 continue;
2668
2669 auto indicesView = queryInfo.indices_mapping_view(i);
2670 const auto inheritedDataView =
2671 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2672 const auto& chunks = pArchetype->chunks();
2673 for (auto* pChunk: chunks) {
2674 uint16_t from = 0;
2675 uint16_t to = 0;
2676 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2677 if GAIA_UNLIKELY (from == to)
2678 continue;
2679
2680 if constexpr (HasFilters) {
2681 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2682 continue;
2683 }
2684
2685 push_matching_chunk_batch(
2686 m_batches, queryInfo, pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to);
2687 }
2688 }
2689 }
2690
2691 template <typename Func>
2692 GAIA_NODISCARD SchedJob add_query_task_job(Func func, QueryExecType execType) {
2693 auto* pCtx = new QueryTaskJobCtx<Func>{this, GAIA_MOV(func), execType};
2694
2695 SchedTaskDesc desc{};
2696 desc.pCtx = pCtx;
2697 desc.invoke = &invoke_query_task_job<Func>;
2698 desc.execType = execType;
2699
2700 return sched_add(world_sched(*m_storage.world()), desc, pCtx, &cleanup_query_task_job<Func>);
2701 }
2702
2703 template <typename Func, QueryExecType ExecType>
2704 GAIA_NODISCARD SchedJob add_iter_parallel_job(Func func) {
2705 static_assert(ExecType != QueryExecType::Default);
2706
2707 auto& queryInfo = fetch();
2708 match_all(queryInfo);
2709 const auto constraints = Constraints::EnabledOnly;
2710 const auto plan = prepare_query_plan(queryInfo, constraints);
2711 if (plan.mode == QueryPlanMode::Empty || plan.idxFrom >= plan.idxTo)
2712 return {};
2713 if (plan.mode == QueryPlanMode::EntitySeed)
2714 return add_query_task_job(GAIA_MOV(func), ExecType);
2715
2716 const auto cacheRange = selected_query_cache_range(queryInfo);
2717 if (cacheRange.hasSelectedGroup)
2718 return add_query_task_job(GAIA_MOV(func), ExecType);
2719
2720 ::gaia::ecs::update_version(*m_worldVersion);
2721 m_batches.clear();
2722 if ((plan.flags & QueryPlanFlag_Filtered) != 0)
2723 collect_runtime_parallel_batches<true>(queryInfo, plan, constraints);
2724 else
2725 collect_runtime_parallel_batches<false>(queryInfo, plan, constraints);
2726
2727 using JobFunc = IterJobCallback<Func>;
2728 return add_parallel_query_job<JobFunc, IterModeEnabled, ExecType>(JobFunc{this, GAIA_MOV(func)});
2729 }
2730
2731 //------------------------------------------------
2732
2733 template <bool HasFilters, typename TMode, typename Func>
2734 void run_query_batch_no_group_id(
2735 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2736 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
2737
2738 auto cacheView = queryInfo.cache_archetype_view();
2739 constexpr auto constraints = iter_mode_constraints<TMode>();
2740 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2741 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2742 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2743 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2744 const bool hasSortedBatchPayload =
2745 payloadKind == ExecPayloadKind::NonTrivial && (queryInfo.has_sorted_payload() || needsBarrierCache);
2746 const auto sortView =
2747 hasSortedBatchPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
2748 if (needsBarrierCache)
2749 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2750
2751 lock(*m_storage.world());
2752
2753 // We are batching by chunks. Some of them might contain only few items but this state is only
2754 // temporary because defragmentation runs constantly and keeps things clean.
2755 ChunkBatchArray chunkBatches;
2756
2757 if (!sortView.empty()) {
2758 for (const auto& view: sortView) {
2759 auto* pArchetype = const_cast<Archetype*>(cacheView[view.archetypeIdx]);
2760 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2761 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2762 continue;
2763
2764 const auto viewFrom = view.startRow;
2765 const auto viewTo = (uint16_t)(view.startRow + view.count);
2766 uint16_t minStartRow = 0;
2767 uint16_t minEndRow = 0;
2768 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2769 const auto startRow = core::get_max(minStartRow, viewFrom);
2770 const auto endRow = core::get_min(minEndRow, viewTo);
2771 const auto totalRows = endRow - startRow;
2772 if (totalRows == 0)
2773 continue;
2774
2775 if constexpr (HasFilters) {
2776 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2777 continue;
2778 }
2779
2780 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2781 const auto inheritedDataView =
2782 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2783
2784 chunkBatches.push_back(
2785 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2786
2787 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2788 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2789 chunkBatches.clear();
2790 }
2791 }
2792 } else {
2793 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2794 auto* pArchetype = const_cast<Archetype*>(cacheView[i]);
2795 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2796 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2797 continue;
2798
2799 auto indicesView = queryInfo.indices_mapping_view(i);
2800 const auto inheritedDataView =
2801 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2802 const auto& chunks = pArchetype->chunks();
2803 uint32_t chunkOffset = 0;
2804 uint32_t itemsLeft = chunks.size();
2805 while (itemsLeft > 0) {
2806 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
2807 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
2808
2809 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
2810 for (auto* pChunk: chunkSpan) {
2811 uint16_t from = 0;
2812 uint16_t to = 0;
2813 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2814 if GAIA_UNLIKELY (from == to)
2815 continue;
2816
2817 if constexpr (HasFilters) {
2818 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2819 continue;
2820 }
2821
2822 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2823 }
2824
2825 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2826 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2827 chunkBatches.clear();
2828 }
2829
2830 itemsLeft -= batchSize;
2831 chunkOffset += batchSize;
2832 }
2833 }
2834 }
2835
2836 // Take care of any leftovers not processed during run_query
2837 if (!chunkBatches.empty())
2838 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2839
2840 unlock(*m_storage.world());
2841 // Commit the command buffer.
2842 // TODO: Smart handling necessary
2843 commit_cmd_buffer_st(*m_storage.world());
2844 commit_cmd_buffer_mt(*m_storage.world());
2845 }
2846
2847 template <bool HasFilters, typename TMode, typename Func, QueryExecType ExecType>
2848 void run_query_batch_no_group_id_par(
2849 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2850 static_assert(ExecType != QueryExecType::Default);
2851 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
2852
2853 auto cacheView = queryInfo.cache_archetype_view();
2854 constexpr auto constraints = iter_mode_constraints<TMode>();
2855 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2856 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2857 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2858 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2859 const bool hasSortedBatchPayload =
2860 payloadKind == ExecPayloadKind::NonTrivial && (queryInfo.has_sorted_payload() || needsBarrierCache);
2861 const auto sortView =
2862 hasSortedBatchPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
2863 if (needsBarrierCache)
2864 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2865
2866 if (!sortView.empty()) {
2867 for (const auto& view: sortView) {
2868 const auto* pArchetype = cacheView[view.archetypeIdx];
2869 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2870 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2871 continue;
2872
2873 const auto viewFrom = view.startRow;
2874 const auto viewTo = (uint16_t)(view.startRow + view.count);
2875 uint16_t minStartRow = 0;
2876 uint16_t minEndRow = 0;
2877 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2878 const auto startRow = core::get_max(minStartRow, viewFrom);
2879 const auto endRow = core::get_min(minEndRow, viewTo);
2880 const auto totalRows = endRow - startRow;
2881 if (totalRows == 0)
2882 continue;
2883
2884 if constexpr (HasFilters) {
2885 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2886 continue;
2887 }
2888
2889 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2890 const auto inheritedDataView =
2891 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2892
2893 m_batches.push_back(
2894 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2895 }
2896 } else {
2897 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2898 const auto* pArchetype = cacheView[i];
2899 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2900 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2901 continue;
2902
2903 auto indicesView = queryInfo.indices_mapping_view(i);
2904 const auto inheritedDataView =
2905 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2906 const auto& chunks = pArchetype->chunks();
2907 for (auto* pChunk: chunks) {
2908 uint16_t from = 0;
2909 uint16_t to = 0;
2910 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
2911 if GAIA_UNLIKELY (from == to)
2912 continue;
2913
2914 if constexpr (HasFilters) {
2915 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2916 continue;
2917 }
2918
2919 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2920 }
2921 }
2922 }
2923
2924 if (m_batches.empty())
2925 return;
2926
2927 lock(*m_storage.world());
2928
2929 struct ParallelQueryBatchCtx {
2930 QueryImpl* pSelf;
2931 Func* pFunc;
2932 };
2933 ParallelQueryBatchCtx ctx{this, &func};
2934 SchedParDesc desc{};
2935 desc.pCtx = &ctx;
2936 desc.itemCount = (uint32_t)m_batches.size();
2937 desc.groupSize = 0;
2938 desc.execType = ExecType;
2939 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
2940 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
2941 ParallelSlot slot(idxStart);
2942 run_query_func<Func, TMode>(
2943 ctx.pSelf->m_storage.world(), *ctx.pFunc,
2944 std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
2945 };
2946
2947 {
2948 // Observers recorded by workers are dispatched when this scope ends, after the
2949 // world is unlocked again, so callbacks see the same world state as on the
2950 // serial path.
2951 ParallelScope scope(*m_storage.world(), desc.itemCount);
2952
2953 const auto& sched = world_sched(*m_storage.world());
2954 const auto token = sched_par(sched, desc);
2955 sched_wait(sched, token);
2956 sched_del(sched, token);
2957 m_batches.clear();
2958
2959 unlock(*m_storage.world());
2960 }
2961
2962 // Commit the command buffer.
2963 // TODO: Smart handling necessary
2964 commit_cmd_buffer_st(*m_storage.world());
2965 commit_cmd_buffer_mt(*m_storage.world());
2966 }
2967
2968 template <bool HasFilters, typename TMode, typename Func>
2969 void run_query_batch_with_group_id(
2970 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
2971 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
2972
2973 ChunkBatchArray chunkBatches;
2974
2975 auto cacheView = queryInfo.cache_archetype_view();
2976 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2977 constexpr auto constraints = iter_mode_constraints<TMode>();
2978 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
2979 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
2980 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
2981 if (needsBarrierCache)
2982 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2983
2984 lock(*m_storage.world());
2985
2986 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2987 const auto* pArchetype = cacheView[i];
2988 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2989 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
2990 continue;
2991
2992 auto indicesView = queryInfo.indices_mapping_view(i);
2993 const auto inheritedDataView =
2994 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2995 const auto& chunks = pArchetype->chunks();
2996 const auto groupId = queryInfo.group_id(i);
2997
2998#if GAIA_ASSERT_ENABLED
2999 GAIA_ASSERT(
3000 // ... or no groupId is set...
3001 m_groupIdSet == 0 ||
3002 // ... or the groupId must match the requested one
3003 groupId == m_groupIdSet);
3004#endif
3005
3006 uint32_t chunkOffset = 0;
3007 uint32_t itemsLeft = chunks.size();
3008 while (itemsLeft > 0) {
3009 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3010 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3011
3012 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3013 for (auto* pChunk: chunkSpan) {
3014 uint16_t from = 0;
3015 uint16_t to = 0;
3016 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3017 if GAIA_UNLIKELY (from == to)
3018 continue;
3019
3020 if constexpr (HasFilters) {
3021 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3022 continue;
3023 }
3024
3025 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3026 }
3027
3028 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3029 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
3030 chunkBatches.clear();
3031 }
3032
3033 itemsLeft -= batchSize;
3034 chunkOffset += batchSize;
3035 }
3036 }
3037
3038 // Take care of any leftovers not processed during run_query
3039 if (!chunkBatches.empty())
3040 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
3041
3042 unlock(*m_storage.world());
3043 // Commit the command buffer.
3044 // TODO: Smart handling necessary
3045 commit_cmd_buffer_st(*m_storage.world());
3046 commit_cmd_buffer_mt(*m_storage.world());
3047 }
3048
3049 template <bool HasFilters, typename TMode, typename Func, QueryExecType ExecType>
3050 void run_query_batch_with_group_id_par(
3051 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
3052 static_assert(ExecType != QueryExecType::Default);
3053 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
3054
3055 ChunkBatchArray chunkBatch;
3056
3057 auto cacheView = queryInfo.cache_archetype_view();
3058 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
3059 constexpr auto constraints = iter_mode_constraints<TMode>();
3060 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
3061 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
3062 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
3063 if (needsBarrierCache)
3064 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3065
3066#if GAIA_ASSERT_ENABLED
3067 for (uint32_t i = idxFrom; i < idxTo; ++i) {
3068 const auto* pArchetype = cacheView[i];
3069 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3070 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3071 continue;
3072
3073 const auto groupId = queryInfo.group_id(i);
3074 GAIA_ASSERT(
3075 // ... or no groupId is set...
3076 m_groupIdSet == 0 ||
3077 // ... or the groupId must match the requested one
3078 groupId == m_groupIdSet);
3079 }
3080#endif
3081
3082 for (uint32_t i = idxFrom; i < idxTo; ++i) {
3083 const Archetype* pArchetype = cacheView[i];
3084 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3085 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3086 continue;
3087
3088 auto indicesView = queryInfo.indices_mapping_view(i);
3089 const auto inheritedDataView =
3090 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3091 const auto groupId = queryInfo.group_id(i);
3092 const auto& chunks = pArchetype->chunks();
3093 for (auto* pChunk: chunks) {
3094 uint16_t from = 0;
3095 uint16_t to = 0;
3096 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3097 if GAIA_UNLIKELY (from == to)
3098 continue;
3099
3100 if constexpr (HasFilters) {
3101 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3102 continue;
3103 }
3104
3105 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3106 }
3107 }
3108
3109 if (m_batches.empty())
3110 return;
3111
3112 lock(*m_storage.world());
3113
3114 struct ParallelQueryBatchCtx {
3115 QueryImpl* pSelf;
3116 Func* pFunc;
3117 };
3118 ParallelQueryBatchCtx ctx{this, &func};
3119 SchedParDesc desc{};
3120 desc.pCtx = &ctx;
3121 desc.itemCount = (uint32_t)m_batches.size();
3122 desc.groupSize = 0;
3123 desc.execType = ExecType;
3124 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3125 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
3126 ParallelSlot slot(idxStart);
3127 run_query_func<Func, TMode>(
3128 ctx.pSelf->m_storage.world(), *ctx.pFunc,
3129 std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
3130 };
3131
3132 {
3133 ParallelScope scope(*m_storage.world(), desc.itemCount);
3134
3135 const auto& sched = world_sched(*m_storage.world());
3136 const auto token = sched_par(sched, desc);
3137 sched_wait(sched, token);
3138 sched_del(sched, token);
3139 m_batches.clear();
3140
3141 unlock(*m_storage.world());
3142 }
3143
3144 // Commit the command buffer.
3145 // TODO: Smart handling necessary
3146 commit_cmd_buffer_st(*m_storage.world());
3147 commit_cmd_buffer_mt(*m_storage.world());
3148 }
3149
3150 //------------------------------------------------
3151
3152 template <bool HasFilters, QueryExecType ExecType, typename TMode, typename Func>
3153 void run_query(const QueryInfo& queryInfo, Func func) {
3154 GAIA_PROF_SCOPE(query::run_query);
3155
3156 // TODO: Have archetype cache as double-linked list with pointers only.
3157 // Have chunk cache as double-linked list with pointers only.
3158 // Make it so only valid pointers are linked together.
3159 // This means one less indirection + we won't need to call can_process_archetype()
3160 // or pChunk.size()==0 in run_query_batch functions.
3161 auto cache_view = queryInfo.cache_archetype_view();
3162 if (cache_view.empty())
3163 return;
3164
3165 const auto cacheRange = selected_query_cache_range(queryInfo);
3166 if (!cacheRange.hasSelectedGroup) {
3167 // No group requested or group filtering is currently turned off
3168 if constexpr (ExecType != QueryExecType::Default)
3169 run_query_batch_no_group_id_par<HasFilters, TMode, Func, ExecType>(
3170 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3171 else
3172 run_query_batch_no_group_id<HasFilters, TMode, Func>(
3173 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3174 } else {
3175 // We wish to iterate only a certain group
3176 if (!cacheRange.valid)
3177 return;
3178
3179 if constexpr (ExecType != QueryExecType::Default)
3180 run_query_batch_with_group_id_par<HasFilters, TMode, Func, ExecType>(
3181 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3182 else
3183 run_query_batch_with_group_id<HasFilters, TMode, Func>(
3184 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3185 }
3186 }
3187
3188 //------------------------------------------------
3189
3190 template <QueryExecType ExecType, typename Func>
3191 void run_query_on_archetypes(QueryInfo& queryInfo, Func func, Constraints constraints) {
3192 // Update the world version
3193 // We do read-only access. No need to update the version
3194 //::gaia::ecs::update_version(*m_worldVersion);
3195 lock(*m_storage.world());
3196
3197 {
3198 GAIA_PROF_SCOPE(query::run_query_a);
3199
3200 // TODO: Have archetype cache as double-linked list with pointers only.
3201 // Have chunk cache as double-linked list with pointers only.
3202 // Make it so only valid pointers are linked together.
3203 // This means one less indirection + we won't need to call can_process_archetype().
3204 auto cache_view = queryInfo.cache_archetype_view();
3205 const auto payloadKind = exec_payload_kind(queryInfo, constraints);
3206 const bool needsBarrierCache = payloadKind == ExecPayloadKind::NonTrivial &&
3207 needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
3208 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
3209 if (needsBarrierCache)
3210 queryInfo.ensure_depth_order_hierarchy_barrier_cache();
3211 GAIA_EACH(cache_view) {
3212 const auto* pArchetype = cache_view[i];
3213 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3214 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3215 continue;
3216
3217 auto indicesView = queryInfo.indices_mapping_view(i);
3218 const auto inheritedDataView =
3219 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3220 ChunkBatch batch{pArchetype, nullptr, indicesView.data(), inheritedDataView, 0, 0, 0};
3221 run_query_arch_func(m_storage.world(), func, batch, constraints);
3222 }
3223 }
3224
3225 unlock(*m_storage.world());
3226 // Changed-filter state is instance-local for cached queries.
3227 }
3228
3229 //------------------------------------------------
3230
3231 template <QueryExecType ExecType, typename TMode, typename Func>
3232 void run_query_on_chunks(QueryInfo& queryInfo, Func func) {
3233 // Update the world version
3234 ::gaia::ecs::update_version(*m_worldVersion);
3235
3236 const bool hasFilters = queryInfo.has_filters();
3237 if (hasFilters)
3238 run_query<true, ExecType, TMode>(queryInfo, func);
3239 else
3240 run_query<false, ExecType, TMode>(queryInfo, func);
3241
3242 // Changed-filter state is instance-local for cached queries.
3243 m_changedWorldVersion = *m_worldVersion;
3244 }
3245
3246 template <typename Func>
3247 static void
3248 run_query_func_runtime(World* pWorld, Func func, std::span<ChunkBatch> batches, Constraints constraints) {
3249 GAIA_PROF_SCOPE(query::run_query_func);
3250
3251 const auto chunkCnt = batches.size();
3252 GAIA_ASSERT(chunkCnt > 0);
3253
3254 Iter it;
3255 it.init_query_state(pWorld, constraints, false);
3256
3257 const Archetype* pLastArchetype = nullptr;
3258 const uint8_t* pLastIndices = nullptr;
3259 InheritedTermDataView lastInheritedData{};
3260 GroupId lastGroupId = GroupIdMax;
3261
3262 const auto apply_batch = [&](const ChunkBatch& batch) {
3263 if (batch.pArchetype != pLastArchetype) {
3264 it.set_archetype(batch.pArchetype);
3265 pLastArchetype = batch.pArchetype;
3266 }
3267
3268 if (batch.pCompIndices != pLastIndices) {
3269 it.set_comp_indices(batch.pCompIndices);
3270 pLastIndices = batch.pCompIndices;
3271 }
3272
3273 if (batch.inheritedData.data() != lastInheritedData.data()) {
3274 it.set_inherited_data(batch.inheritedData);
3275 lastInheritedData = batch.inheritedData;
3276 }
3277
3278 if (batch.groupId != lastGroupId) {
3279 it.set_group_id(batch.groupId);
3280 lastGroupId = batch.groupId;
3281 }
3282
3283 it.set_chunk(batch.pChunk, batch.from, batch.to);
3284 func(it);
3285 finish_iter_writes(it);
3286 it.clear_touched_writes();
3287 };
3288
3289 if GAIA_UNLIKELY (chunkCnt == 1) {
3290 apply_batch(batches[0]);
3291 return;
3292 }
3293
3294 gaia::prefetch(batches[1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
3295 apply_batch(batches[0]);
3296
3297 uint32_t chunkIdx = 1;
3298 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
3299 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
3300 apply_batch(batches[chunkIdx]);
3301 }
3302
3303 apply_batch(batches[chunkIdx]);
3304 }
3305
3306 template <bool HasFilters, typename Func>
3307 void run_query_batch_no_group_id_runtime(
3308 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3309 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
3310
3311 auto cacheView = queryInfo.cache_archetype_view();
3312 const bool hasSortedPlanPayload =
3313 plan.payloadKind == ExecPayloadKind::NonTrivial && (plan.flags & QueryPlanFlag_Sorted) != 0;
3314 const auto sortView =
3315 hasSortedPlanPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
3316 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3317 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3318 if (needsBarrierCache)
3319 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3320
3321 lock(*m_storage.world());
3322 ChunkBatchArray chunkBatches;
3323 const auto flush_chunk_batches = [&]() {
3324 if (chunkBatches.empty())
3325 return;
3326 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3327 chunkBatches.clear();
3328 };
3329
3330 if (!sortView.empty()) {
3331 for (const auto& view: sortView) {
3332 auto* pArchetype = const_cast<Archetype*>(cacheView[view.archetypeIdx]);
3333 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3334 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3335 continue;
3336
3337 const auto viewFrom = view.startRow;
3338 const auto viewTo = (uint16_t)(view.startRow + view.count);
3339 uint16_t minStartRow = 0;
3340 uint16_t minEndRow = 0;
3341 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3342 const auto startRow = core::get_max(minStartRow, viewFrom);
3343 const auto endRow = core::get_min(minEndRow, viewTo);
3344 const auto totalRows = endRow - startRow;
3345 if (totalRows == 0)
3346 continue;
3347
3348 if constexpr (HasFilters) {
3349 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3350 continue;
3351 }
3352
3353 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3354 const auto inheritedDataView =
3355 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3356
3357 push_matching_chunk_batch(
3358 chunkBatches, queryInfo, pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow,
3359 endRow, flush_chunk_batches);
3360
3361 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3362 run_query_func_runtime(
3363 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3364 chunkBatches.clear();
3365 }
3366 }
3367 } else {
3368 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3369 auto* pArchetype = const_cast<Archetype*>(cacheView[i]);
3370 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3371 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3372 continue;
3373
3374 auto indicesView = queryInfo.indices_mapping_view(i);
3375 const auto inheritedDataView =
3376 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3377 const auto& chunks = pArchetype->chunks();
3378 uint32_t chunkOffset = 0;
3379 uint32_t itemsLeft = chunks.size();
3380 while (itemsLeft > 0) {
3381 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3382 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3383
3384 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3385 for (auto* pChunk: chunkSpan) {
3386 uint16_t from = 0;
3387 uint16_t to = 0;
3388 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3389 if GAIA_UNLIKELY (from == to)
3390 continue;
3391
3392 if constexpr (HasFilters) {
3393 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3394 continue;
3395 }
3396
3397 push_matching_chunk_batch(
3398 chunkBatches, queryInfo, pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to,
3399 flush_chunk_batches);
3400 }
3401
3402 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3403 run_query_func_runtime(
3404 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3405 chunkBatches.clear();
3406 }
3407
3408 itemsLeft -= batchSize;
3409 chunkOffset += batchSize;
3410 }
3411 }
3412 }
3413
3414 if (!chunkBatches.empty())
3415 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3416
3417 unlock(*m_storage.world());
3418 commit_cmd_buffer_st(*m_storage.world());
3419 commit_cmd_buffer_mt(*m_storage.world());
3420 }
3421
3422 template <bool HasFilters, typename Func, QueryExecType ExecType>
3423 void run_query_batch_no_group_id_runtime_par(
3424 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3425 static_assert(ExecType != QueryExecType::Default);
3426 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
3427
3428 auto cacheView = queryInfo.cache_archetype_view();
3429 const bool hasSortedPlanPayload =
3430 plan.payloadKind == ExecPayloadKind::NonTrivial && (plan.flags & QueryPlanFlag_Sorted) != 0;
3431 const auto sortView =
3432 hasSortedPlanPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
3433 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3434 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3435 if (needsBarrierCache)
3436 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3437
3438 if (!sortView.empty()) {
3439 for (const auto& view: sortView) {
3440 const auto* pArchetype = cacheView[view.archetypeIdx];
3441 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3442 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3443 continue;
3444
3445 const auto viewFrom = view.startRow;
3446 const auto viewTo = (uint16_t)(view.startRow + view.count);
3447 uint16_t minStartRow = 0;
3448 uint16_t minEndRow = 0;
3449 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3450 const auto startRow = core::get_max(minStartRow, viewFrom);
3451 const auto endRow = core::get_min(minEndRow, viewTo);
3452 const auto totalRows = endRow - startRow;
3453 if (totalRows == 0)
3454 continue;
3455
3456 if constexpr (HasFilters) {
3457 if (!match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3458 continue;
3459 }
3460
3461 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3462 const auto inheritedDataView =
3463 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3464
3465 push_matching_chunk_batch(
3466 m_batches, queryInfo, pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow,
3467 endRow);
3468 }
3469 } else {
3470 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3471 const auto* pArchetype = cacheView[i];
3472 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3473 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3474 continue;
3475
3476 auto indicesView = queryInfo.indices_mapping_view(i);
3477 const auto inheritedDataView =
3478 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3479 const auto& chunks = pArchetype->chunks();
3480 for (auto* pChunk: chunks) {
3481 uint16_t from = 0;
3482 uint16_t to = 0;
3483 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3484 if GAIA_UNLIKELY (from == to)
3485 continue;
3486
3487 if constexpr (HasFilters) {
3488 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3489 continue;
3490 }
3491
3492 push_matching_chunk_batch(
3493 m_batches, queryInfo, pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to);
3494 }
3495 }
3496 }
3497
3498 if (m_batches.empty())
3499 return;
3500
3501 lock(*m_storage.world());
3502
3503 struct ParallelQueryBatchCtx {
3504 QueryImpl* pSelf;
3505 Func* pFunc;
3506 Constraints constraints;
3507 };
3508 ParallelQueryBatchCtx ctx{this, &func, constraints};
3509 SchedParDesc desc{};
3510 desc.pCtx = &ctx;
3511 desc.itemCount = (uint32_t)m_batches.size();
3512 desc.groupSize = 0;
3513 desc.execType = ExecType;
3514 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3515 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
3516 ParallelSlot slot(idxStart);
3517 run_query_func_runtime(
3518 ctx.pSelf->m_storage.world(), *ctx.pFunc, std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3519 ctx.constraints);
3520 };
3521
3522 {
3523 ParallelScope scope(*m_storage.world(), desc.itemCount);
3524
3525 const auto& sched = world_sched(*m_storage.world());
3526 const auto token = sched_par(sched, desc);
3527 sched_wait(sched, token);
3528 sched_del(sched, token);
3529 m_batches.clear();
3530
3531 unlock(*m_storage.world());
3532 }
3533
3534 commit_cmd_buffer_st(*m_storage.world());
3535 commit_cmd_buffer_mt(*m_storage.world());
3536 }
3537
3538 template <bool HasFilters, typename Func>
3539 void run_query_batch_with_group_id_runtime(
3540 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3541 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
3542
3543 ChunkBatchArray chunkBatches;
3544 const auto flush_chunk_batches = [&]() {
3545 if (chunkBatches.empty())
3546 return;
3547 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3548 chunkBatches.clear();
3549 };
3550 auto cacheView = queryInfo.cache_archetype_view();
3551 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3552 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3553 if (needsBarrierCache)
3554 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3555
3556 lock(*m_storage.world());
3557
3558 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3559 const auto* pArchetype = cacheView[i];
3560 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3561 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3562 continue;
3563
3564 auto indicesView = queryInfo.indices_mapping_view(i);
3565 const auto inheritedDataView =
3566 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3567 const auto& chunks = pArchetype->chunks();
3568 const auto groupId = queryInfo.group_id(i);
3569
3570 uint32_t chunkOffset = 0;
3571 uint32_t itemsLeft = chunks.size();
3572 while (itemsLeft > 0) {
3573 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3574 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3575
3576 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3577 for (auto* pChunk: chunkSpan) {
3578 uint16_t from = 0;
3579 uint16_t to = 0;
3580 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3581 if GAIA_UNLIKELY (from == to)
3582 continue;
3583
3584 if constexpr (HasFilters) {
3585 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3586 continue;
3587 }
3588
3589 push_matching_chunk_batch(
3590 chunkBatches, queryInfo, pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from,
3591 to, flush_chunk_batches);
3592 }
3593
3594 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3595 run_query_func_runtime(
3596 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3597 chunkBatches.clear();
3598 }
3599
3600 itemsLeft -= batchSize;
3601 chunkOffset += batchSize;
3602 }
3603 }
3604
3605 if (!chunkBatches.empty())
3606 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3607
3608 unlock(*m_storage.world());
3609 commit_cmd_buffer_st(*m_storage.world());
3610 commit_cmd_buffer_mt(*m_storage.world());
3611 }
3612
3613 template <bool HasFilters, typename Func, QueryExecType ExecType>
3614 void run_query_batch_with_group_id_runtime_par(
3615 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3616 static_assert(ExecType != QueryExecType::Default);
3617 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
3618
3619 ChunkBatchArray chunkBatch;
3620 auto cacheView = queryInfo.cache_archetype_view();
3621 const bool hasInheritedData = (plan.flags & QueryPlanFlag_InheritedPayload) != 0;
3622 const bool needsBarrierCache = (plan.flags & QueryPlanFlag_BarrierCache) != 0;
3623 if (needsBarrierCache)
3624 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3625
3626 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3627 const auto* pArchetype = cacheView[i];
3628 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3629 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
3630 continue;
3631
3632 auto indicesView = queryInfo.indices_mapping_view(i);
3633 const auto inheritedDataView =
3634 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3635 const auto groupId = queryInfo.group_id(i);
3636 const auto& chunks = pArchetype->chunks();
3637 for (auto* pChunk: chunks) {
3638 uint16_t from = 0;
3639 uint16_t to = 0;
3640 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
3641 if GAIA_UNLIKELY (from == to)
3642 continue;
3643
3644 if constexpr (HasFilters) {
3645 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3646 continue;
3647 }
3648
3649 push_matching_chunk_batch(
3650 m_batches, queryInfo, pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to);
3651 }
3652 }
3653
3654 if (m_batches.empty())
3655 return;
3656
3657 lock(*m_storage.world());
3658
3659 struct ParallelQueryBatchCtx {
3660 QueryImpl* pSelf;
3661 Func* pFunc;
3662 Constraints constraints;
3663 };
3664 ParallelQueryBatchCtx ctx{this, &func, constraints};
3665 SchedParDesc desc{};
3666 desc.pCtx = &ctx;
3667 desc.itemCount = (uint32_t)m_batches.size();
3668 desc.groupSize = 0;
3669 desc.execType = ExecType;
3670 desc.invoke = [](void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3671 auto& ctx = *reinterpret_cast<ParallelQueryBatchCtx*>(pCtx);
3672 ParallelSlot slot(idxStart);
3673 run_query_func_runtime(
3674 ctx.pSelf->m_storage.world(), *ctx.pFunc, std::span(&ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3675 ctx.constraints);
3676 };
3677
3678 {
3679 ParallelScope scope(*m_storage.world(), desc.itemCount);
3680
3681 const auto& sched = world_sched(*m_storage.world());
3682 const auto token = sched_par(sched, desc);
3683 sched_wait(sched, token);
3684 sched_del(sched, token);
3685 m_batches.clear();
3686
3687 unlock(*m_storage.world());
3688 }
3689
3690 commit_cmd_buffer_st(*m_storage.world());
3691 commit_cmd_buffer_mt(*m_storage.world());
3692 }
3693
3694 template <bool HasFilters, QueryExecType ExecType, typename Func>
3695 void run_query_runtime_planned(
3696 const QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3697 GAIA_PROF_SCOPE(query::run_query);
3698 if (plan.mode == QueryPlanMode::Empty || plan.idxFrom >= plan.idxTo)
3699 return;
3700
3701 const auto cacheRange = selected_query_cache_range(queryInfo);
3702 if (!cacheRange.hasSelectedGroup) {
3703 if constexpr (ExecType != QueryExecType::Default)
3704 run_query_batch_no_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3705 else
3706 run_query_batch_no_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3707 } else {
3708 if constexpr (ExecType != QueryExecType::Default)
3709 run_query_batch_with_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3710 else
3711 run_query_batch_with_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3712 }
3713 }
3714
3722 template <QueryExecType ExecType, typename Func>
3723 void run_query_on_chunks_runtime_planned(
3724 QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func func) {
3725 if (plan.mode == QueryPlanMode::Empty)
3726 return;
3727
3728 ::gaia::ecs::update_version(*m_worldVersion);
3729 if ((plan.flags & QueryPlanFlag_Filtered) != 0)
3730 run_query_runtime_planned<true, ExecType>(queryInfo, plan, constraints, func);
3731 else
3732 run_query_runtime_planned<false, ExecType>(queryInfo, plan, constraints, func);
3733
3734 m_changedWorldVersion = *m_worldVersion;
3735 }
3736
3741 GAIA_NODISCARD bool can_use_direct_chunk_iteration_fastpath(const QueryInfo& queryInfo) const {
3742 const auto& data = queryInfo.ctx().data;
3743 return data.sortByFunc == nullptr &&
3744 (!has_depth_order_hierarchy_enabled_barrier(queryInfo) || !queryInfo.barrier_may_prune());
3745 }
3746
3750 GAIA_NODISCARD QueryCacheRange selected_query_cache_range(const QueryInfo& queryInfo) const {
3751 QueryCacheRange range{};
3752 range.idxTo = (uint32_t)queryInfo.cache_archetype_view().size();
3753
3754 const auto& data = queryInfo.ctx().data;
3755 if (data.groupBy == EntityBad || m_groupIdSet == 0)
3756 return range;
3757
3758 range.hasSelectedGroup = true;
3759 const auto* pGroupData = queryInfo.selected_group_data(m_groupIdSet);
3760 if (pGroupData == nullptr) {
3761 range.idxFrom = 0;
3762 range.idxTo = 0;
3763 range.valid = false;
3764 return range;
3765 }
3766
3767 range.idxFrom = pGroupData->idxFirst;
3768 range.idxTo = pGroupData->idxLast + 1;
3769 return range;
3770 }
3771
3776 GAIA_NODISCARD QueryPlan prepare_query_plan(const QueryInfo& queryInfo, const TypedQueryExecState& state) const;
3777
3778 template <bool HasFilters, typename Func, typename... T>
3779 void run_query_on_chunks_direct_typed(
3780 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, Func& func,
3781 core::func_type_list<T...>);
3782
3783 template <bool HasFilters, typename Func, typename... T>
3784 void run_query_on_chunks_sparse_typed(
3785 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, Func& func,
3786 core::func_type_list<T...>);
3787
3788 template <typename Func, typename... T>
3789 void run_query_on_sparse_entities_typed(
3790 QueryInfo& queryInfo, const TypedQueryExecState& state, Func& func, core::func_type_list<T...>);
3791
3792 void run_query_on_chunks_direct(
3793 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, void* pFunc,
3794 void (*runChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&));
3795
3796 void run_query_on_chunks_direct_iter(
3797 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, void* pFunc,
3798 void (*runChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&));
3799
3800 struct TypedQueryErasedOps {
3801 void (*runSparsePlan)(QueryImpl&, QueryInfo&, const QueryPlan&, void*, const TypedQueryExecState&) = nullptr;
3802 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&) = nullptr;
3803 void (*runDirectChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&) = nullptr;
3804 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&) = nullptr;
3805 void (*invokeInherited)(World&, Entity, const Entity*, void*) = nullptr;
3806 bool needsInheritedArgIds = false;
3807 };
3808
3809 template <QueryExecType ExecType>
3810 void each_inter(
3811 QueryInfo& queryInfo, const QueryPlan& plan, void* pFunc, const TypedQueryExecState& state,
3812 const TypedQueryErasedOps& ops);
3813
3814 void each_typed_erased(
3815 QueryExecType execType, void* pFunc, const TypedQueryExecState& state, const TypedQueryErasedOps& ops);
3816
3817 template <QueryExecType ExecType, typename Func>
3818 void each_typed_inter(QueryInfo& queryInfo, Func func);
3819
3820 template <QueryExecType ExecType>
3821 void each_iter_inter_erased(
3822 QueryInfo& queryInfo, const QueryPlan& plan, void* pFunc, const TypedQueryExecState& state,
3823 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
3824 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
3825
3826 void each_walk_inter(
3827 QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints, void* pFunc,
3828 const TypedQueryExecState& state,
3829 void (*runChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
3831
3836 GAIA_NODISCARD QueryPlan prepare_query_plan(const QueryInfo& queryInfo, Constraints constraints) const {
3837 QueryPlan plan{};
3838 const auto cacheRange = selected_query_cache_range(queryInfo);
3839 plan.idxFrom = cacheRange.idxFrom;
3840 plan.idxTo = cacheRange.idxTo;
3841 const bool hasFilters = queryInfo.has_filters();
3842 const bool hasSortedPayload = queryInfo.has_sorted_payload();
3843 const bool hasDepthOrderBarrier = has_depth_order_hierarchy_enabled_barrier(queryInfo);
3844 bool hasConstrainedDepthOrderBarrier = constraints != Constraints::AcceptAll && hasDepthOrderBarrier;
3845 if (hasFilters)
3846 plan.flags |= QueryPlanFlag_Filtered;
3847 if (queryInfo.has_entity_filter_terms())
3848 plan.flags |= QueryPlanFlag_EntityFilter;
3849 if (queryInfo.has_inherited_data_payload())
3850 plan.flags |= QueryPlanFlag_InheritedPayload;
3851 if (queryInfo.has_grouped_payload())
3852 plan.flags |= QueryPlanFlag_Grouped;
3853 if (hasSortedPayload || hasDepthOrderBarrier)
3854 plan.flags |= QueryPlanFlag_Sorted;
3855 plan.payloadKind = exec_payload_kind(queryInfo, constraints);
3856
3857 if (cacheRange.hasSelectedGroup) {
3858 plan.flags |= QueryPlanFlag_Grouped;
3859 plan.payloadKind = ExecPayloadKind::Grouped;
3860 if (!cacheRange.valid) {
3861 plan.mode = QueryPlanMode::Empty;
3862 return plan;
3863 }
3864 }
3865
3866 if ((plan.flags & QueryPlanFlag_Filtered) == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
3867 plan.mode = QueryPlanMode::EntitySeed;
3868 return plan;
3869 }
3870
3871 if (plan.idxFrom >= plan.idxTo) {
3872 plan.mode = QueryPlanMode::Empty;
3873 return plan;
3874 }
3875
3876 if (hasConstrainedDepthOrderBarrier && !depth_order_hierarchy_barrier_prunes(queryInfo)) {
3877 hasConstrainedDepthOrderBarrier = false;
3878 if (!hasSortedPayload && (plan.flags & QueryPlanFlag_InheritedPayload) == 0)
3879 plan.payloadKind = queryInfo.has_grouped_payload() ? ExecPayloadKind::Grouped : ExecPayloadKind::Plain;
3880 }
3881 if (hasConstrainedDepthOrderBarrier)
3882 plan.flags |= QueryPlanFlag_BarrierCache;
3883
3884 if (hasSortedPayload) {
3885 plan.mode = QueryPlanMode::Sorted;
3886 return plan;
3887 }
3888
3889 if (hasConstrainedDepthOrderBarrier || (plan.flags & QueryPlanFlag_InheritedPayload) != 0) {
3890 plan.mode = QueryPlanMode::Traversal;
3891 return plan;
3892 }
3893
3894 // DirectDense exposes whole cached chunk windows. Entity-filter terms such as DontFragment
3895 // are not archetype identity, so those windows must be split by match_entity_filters in the
3896 // generic batch runner. Keep General mode here.
3897 if ((plan.flags & QueryPlanFlag_EntityFilter) != 0)
3898 return plan;
3899
3900 if (plan.payloadKind != ExecPayloadKind::Plain) {
3901 if (plan.payloadKind != ExecPayloadKind::Grouped || !hasDepthOrderBarrier)
3902 return plan;
3903 if (hasConstrainedDepthOrderBarrier)
3904 return plan;
3905 if (!can_use_direct_chunk_iteration_fastpath(queryInfo))
3906 return plan;
3907
3908 plan.mode = QueryPlanMode::DirectDense;
3909 return plan;
3910 }
3911
3912 if (!can_use_direct_chunk_iteration_fastpath(queryInfo))
3913 return plan;
3914
3915 plan.mode = QueryPlanMode::DirectDense;
3916 return plan;
3917 }
3918
3927 template <bool HasFilters, bool HasGroups, typename Func>
3929 QueryInfo& queryInfo, const QueryPlan& plan, Constraints constraints, Func& func) {
3930 ::gaia::ecs::update_version(*m_worldVersion);
3931
3932 auto cacheView = queryInfo.cache_archetype_view();
3933 lock(*m_storage.world());
3934
3935 Iter it;
3936 it.init_query_state(queryInfo.world(), constraints, false);
3937#if GAIA_ASSERT_ENABLED
3938 it.set_query_access(&queryInfo.ctx().data);
3939#endif
3940
3943 const bool canSkipProcessCheck =
3945
3946 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3947 auto* pArchetype = const_cast<Archetype*>(cacheView[i]);
3948 if (canSkipProcessCheck) {
3949 if GAIA_UNLIKELY (pArchetype->is_req_del())
3950 continue;
3951 } else if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints))
3952 continue;
3953
3954 auto indicesView = queryInfo.indices_mapping_view(i);
3955 const auto* pIndices = indicesView.data();
3956 const auto groupId = HasGroups ? queryInfo.group_id(i) : GroupId(0);
3957 const auto& chunks = pArchetype->chunks();
3958 for (auto* pChunk: chunks) {
3959 const auto from = detail::ChunkIterImpl::start_index(pChunk, constraints);
3960 const auto to = detail::ChunkIterImpl::end_index(pChunk, constraints);
3961 if GAIA_UNLIKELY (from == to)
3962 continue;
3963 if constexpr (HasFilters) {
3964 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion, indicesView))
3965 continue;
3966 }
3967
3968 it.set_query_chunk(pArchetype, pIndices, pChunk, from, to);
3969 if constexpr (HasGroups)
3970 it.set_group_id(groupId);
3971 it.ctx(m_ctx);
3972 {
3973 GAIA_PROF_SCOPE(query_func);
3974 func(it);
3975 }
3976 finish_iter_writes(it);
3977 it.clear_touched_writes();
3978 }
3979 }
3980
3981 unlock(*m_storage.world());
3982 commit_cmd_buffer_st(*m_storage.world());
3983 commit_cmd_buffer_mt(*m_storage.world());
3984 m_changedWorldVersion = *m_worldVersion;
3985 }
3986
3992 template <QueryExecType ExecType, typename Func>
3993 void each_runtime_inter(Func func, Constraints constraints = Constraints::EnabledOnly) {
3994 if constexpr (ExecType == QueryExecType::Default) {
3995 auto& queryInfo = fetch();
3996 match_all(queryInfo);
3997 const auto plan = prepare_query_plan(queryInfo, constraints);
3998 if (plan.mode == QueryPlanMode::DirectDense) {
3999 const bool hasGroups = (plan.flags & QueryPlanFlag_Grouped) != 0;
4000 if ((plan.flags & QueryPlanFlag_Filtered) != 0) {
4001 if (hasGroups)
4002 run_query_on_chunks_runtime_direct_plain_impl<true, true>(queryInfo, plan, constraints, func);
4003 else
4004 run_query_on_chunks_runtime_direct_plain_impl<true, false>(queryInfo, plan, constraints, func);
4005 } else {
4006 if (hasGroups)
4007 run_query_on_chunks_runtime_direct_plain_impl<false, true>(queryInfo, plan, constraints, func);
4008 else
4009 run_query_on_chunks_runtime_direct_plain_impl<false, false>(queryInfo, plan, constraints, func);
4010 }
4011 return;
4012 }
4013
4015 queryInfo, plan, ExecType, static_cast<void*>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
4016 } else {
4017 each_runtime_erased(ExecType, static_cast<void*>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
4018 }
4019 }
4020
4026 template <typename Func, typename TIter>
4027 static void invoke_runtime_iter(void* pFunc, TIter& it) {
4028 auto& func = *static_cast<Func*>(pFunc);
4029 func(it);
4030 }
4031
4033 struct RuntimeIterCallback {
4034 void* pFunc;
4035 void* pCtx;
4036 void (*invoke)(void*, Iter&);
4037#if GAIA_ASSERT_ENABLED
4039 const QueryCtx::Data* pQueryAccess = nullptr;
4040#endif
4041
4042 void operator()(Iter& it) const {
4043 GAIA_PROF_SCOPE(query_func);
4044 it.ctx(pCtx);
4045#if GAIA_ASSERT_ENABLED
4046 it.set_query_access(pQueryAccess);
4047#endif
4048 invoke(pFunc, it);
4049 }
4050 };
4051
4052 struct TypedDirectChunkCallback {
4053 QueryImpl* pSelf;
4054 void* pFunc;
4055 const TypedQueryExecState* pState;
4056 void (*runChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&);
4057
4058 void operator()(Iter& it) const {
4059 GAIA_PROF_SCOPE(query_func);
4060 it.ctx(pSelf->ctx());
4061 runChunk(*pSelf, it, pFunc, *pState);
4062 }
4063 };
4064
4065 struct TypedMappedChunkCallback {
4066 QueryImpl* pSelf;
4067 const QueryInfo* pQueryInfo;
4068 void* pFunc;
4069 const TypedQueryExecState* pState;
4070 void (*runChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&);
4071
4072 void operator()(Iter& it) const {
4073 GAIA_PROF_SCOPE(query_func);
4074 it.ctx(pSelf->ctx());
4075 runChunk(*pSelf, *pQueryInfo, it, pFunc, *pState);
4076 }
4077 };
4078
4079 struct TypedIterErasedCallback {
4080 QueryImpl* pSelf;
4081 void* pFunc;
4082 const TypedQueryExecState* pState;
4083 void (*runDirect)(QueryImpl&, Iter&, void*, const TypedQueryExecState&);
4084 void (*runChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&);
4085
4086 void operator()(Iter& it) const {
4087 GAIA_PROF_SCOPE(query_func);
4088 it.ctx(pSelf->ctx());
4089 pSelf->each_iter_erased(it, pFunc, *pState, runDirect, runChunk);
4090 }
4091 };
4093
4100 QueryExecType execType, void* pFunc, void (*invoke)(void*, Iter&), Constraints constraints) {
4101 auto& queryInfo = fetch();
4102 match_all(queryInfo);
4103 const auto plan = prepare_query_plan(queryInfo, constraints);
4104 each_runtime_erased(queryInfo, plan, execType, pFunc, invoke, constraints);
4105 }
4106
4115 QueryInfo& queryInfo, const QueryPlan& plan, QueryExecType execType, void* pFunc,
4116 void (*invoke)(void*, Iter&), Constraints constraints) {
4117 RuntimeIterCallback cb{pFunc, m_ctx, invoke};
4118#if GAIA_ASSERT_ENABLED
4119 cb.pQueryAccess = &queryInfo.ctx().data;
4120#endif
4121
4122 if (plan.mode == QueryPlanMode::EntitySeed) {
4123 each_direct_iter_inter(queryInfo, constraints, cb);
4124 return;
4125 }
4126
4127 switch (execType) {
4128 case QueryExecType::Parallel:
4129 run_query_on_chunks_runtime_planned<QueryExecType::Parallel>(queryInfo, plan, constraints, cb);
4130 break;
4131 case QueryExecType::ParallelPerf:
4132 run_query_on_chunks_runtime_planned<QueryExecType::ParallelPerf>(queryInfo, plan, constraints, cb);
4133 break;
4134 case QueryExecType::ParallelEff:
4135 run_query_on_chunks_runtime_planned<QueryExecType::ParallelEff>(queryInfo, plan, constraints, cb);
4136 break;
4137 default:
4138 run_query_on_chunks_runtime_planned<QueryExecType::Default>(queryInfo, plan, constraints, cb);
4139 break;
4140 }
4141 }
4142
4143 //------------------------------------------------
4144
4149 GAIA_NODISCARD static bool is_non_fragmenting_direct_term(const World& world, const QueryTerm& term) {
4150 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4151 return false;
4152
4153 const auto id = term.id;
4154 return (id.pair() && world_relation_uses_non_fragmenting_storage(world, pair_rel(world, id))) ||
4155 (!id.pair() && world_component_is_non_fragmenting(world, id));
4156 }
4157
4161 GAIA_NODISCARD static bool uses_semantic_is_matching(const QueryTerm& term) {
4162 const auto id = term.id;
4163 return term.matchKind == QueryMatchKind::Semantic && term.src == EntityBad && term.entTrav == EntityBad &&
4164 !term_has_variables(term) && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
4165 !is_variable((EntityId)id.gen());
4166 }
4167
4171 GAIA_NODISCARD static bool uses_in_is_matching(const QueryTerm& term) {
4172 const auto id = term.id;
4173 return term.matchKind == QueryMatchKind::In && term.src == EntityBad && term.entTrav == EntityBad &&
4174 !term_has_variables(term) && id.pair() && id.id() == Is.id() && !is_wildcard(id.gen()) &&
4175 !is_variable((EntityId)id.gen());
4176 }
4177
4181 GAIA_NODISCARD static bool uses_non_direct_is_matching(const QueryTerm& term) {
4182 return uses_semantic_is_matching(term) || uses_in_is_matching(term);
4183 }
4184
4189 GAIA_NODISCARD static bool uses_potential_inherited_id_matching(const QueryTerm& term) {
4190 return query_term_uses_potential_inherited_id_matching(term);
4191 }
4192
4197 GAIA_NODISCARD static bool uses_inherited_id_matching(const World& world, const QueryTerm& term) {
4198 return uses_potential_inherited_id_matching(term) && world_term_uses_inherit_policy(world, term.id);
4199 }
4200
4203 GAIA_NODISCARD static bool match_entity_term(const World& world, Entity entity, const QueryTerm& term) {
4204 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
4205 return world_has_entity_term(world, entity, term.id);
4206 if (uses_in_is_matching(term))
4207 return world_has_entity_term_in(world, entity, term.id);
4208
4209 return world_has_entity_term_direct(world, entity, term.id);
4210 }
4211
4212 GAIA_NODISCARD static uint32_t count_direct_term_entities(const World& world, const QueryTerm& term) {
4213 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
4214 return world_count_direct_term_entities(world, term.id);
4215 if (uses_in_is_matching(term))
4216 return world_count_in_term_entities(world, term.id);
4217
4218 return world_count_direct_term_entities_direct(world, term.id);
4219 }
4220
4221 static void collect_direct_term_entities(const World& world, const QueryTerm& term, cnt::darray<Entity>& out) {
4222 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term)) {
4223 world_collect_direct_term_entities(world, term.id, out);
4224 return;
4225 }
4226 if (uses_in_is_matching(term)) {
4227 world_collect_in_term_entities(world, term.id, out);
4228 return;
4229 }
4230
4231 world_collect_direct_term_entities_direct(world, term.id, out);
4232 }
4233
4234 template <typename Func>
4235 GAIA_NODISCARD static bool each_direct_term_entity(const World& world, const QueryTerm& term, Func&& func) {
4236 struct Visitor {
4237 Func& func;
4238 static bool thunk(void* ctx, Entity entity) {
4239 return static_cast<Visitor*>(ctx)->func(entity);
4240 }
4241 };
4242
4243 Visitor visitor{func};
4244 if (uses_semantic_is_matching(term) || uses_inherited_id_matching(world, term))
4245 return world_each_direct_term_entity(world, term.id, &visitor, &Visitor::thunk);
4246 if (uses_in_is_matching(term))
4247 return world_each_in_term_entity(world, term.id, &visitor, &Visitor::thunk);
4248
4249 return world_each_direct_term_entity_direct(world, term.id, &visitor, &Visitor::thunk);
4250 }
4251
4253 GAIA_NODISCARD static bool can_use_direct_entity_seed_eval(const QueryInfo& queryInfo) {
4254 if (!queryInfo.can_direct_entity_seed_eval_shape())
4255 return false;
4256
4257 const auto& world = *queryInfo.world();
4258 bool hasSeedableTerm = false;
4259 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4260 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Or)
4261 continue;
4262 if (uses_non_direct_is_matching(term) || uses_inherited_id_matching(world, term) ||
4264 hasSeedableTerm = true;
4265 }
4266
4267 return hasSeedableTerm;
4268 }
4269
4271 GAIA_NODISCARD static bool can_use_direct_target_eval(const QueryInfo& queryInfo) {
4272 return queryInfo.can_direct_target_eval();
4273 }
4274
4276 GAIA_NODISCARD static bool has_only_direct_or_terms(const QueryInfo& queryInfo) {
4277 return queryInfo.has_only_direct_or_terms();
4278 }
4279
4280 static void
4281 add_chunk_run(cnt::darray<detail::BfsChunkRun>& runs, const EntityContainer& ec, uint32_t entityOffset) {
4282 if (runs.empty()) {
4283 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
4284 return;
4285 }
4286
4287 auto& run = runs.back();
4288 if (ec.pChunk == run.pChunk && ec.row == run.to) {
4289 run.to = (uint16_t)(run.to + 1);
4290 return;
4291 }
4292
4293 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
4294 }
4295
4296 struct DirectEntitySeedInfo {
4297 Entity seededAllTerm = EntityBad;
4298 QueryMatchKind seededAllMatchKind = QueryMatchKind::Semantic;
4299 bool seededFromAll = false;
4300 bool seededFromOr = false;
4301 };
4302
4304 struct DirectEntitySeedPlan {
4305 Entity bestAllTerm = EntityBad;
4306 uint32_t bestAllTermCount = UINT32_MAX;
4307 QueryMatchKind bestAllTermMatchKind = QueryMatchKind::Semantic;
4308 bool hasAllTerms = false;
4309 bool hasOrTerms = false;
4310 bool preferOrSeed = false;
4311 };
4312
4313 struct DirectEntitySeedEvalPlan {
4316 const QueryTerm* pSingleAllTerm = nullptr;
4318 bool alwaysMatch = false;
4319 };
4320
4322 GAIA_NODISCARD static bool should_prefer_direct_seed_term(
4323 const World& world, const QueryTerm& candidate, uint32_t candidateCount, const DirectEntitySeedPlan& plan) {
4324 const bool candidateIsSemanticIs = uses_non_direct_is_matching(candidate);
4325 const bool bestIsSemanticIs = plan.bestAllTermMatchKind != QueryMatchKind::Direct &&
4326 plan.bestAllTerm.pair() && plan.bestAllTerm.id() == Is.id() &&
4327 !is_wildcard(plan.bestAllTerm.gen()) &&
4328 !is_variable((EntityId)plan.bestAllTerm.gen());
4329 const auto adjustedCandidateCount = candidateCount - (candidateIsSemanticIs && candidateCount > 0 ? 1U : 0U);
4330 const auto adjustedBestCount =
4331 plan.bestAllTermCount - (bestIsSemanticIs && plan.bestAllTermCount > 0 ? 1U : 0U);
4332 if (adjustedCandidateCount < adjustedBestCount)
4333 return true;
4334 if (adjustedCandidateCount > adjustedBestCount)
4335 return false;
4336 if (plan.bestAllTerm == EntityBad)
4337 return true;
4338
4339 if (candidateIsSemanticIs != bestIsSemanticIs)
4340 return candidateIsSemanticIs;
4341
4342 const bool candidateUsesInherited = uses_inherited_id_matching(world, candidate);
4343 const bool bestUsesInherited = plan.bestAllTermMatchKind == QueryMatchKind::Semantic &&
4344 !is_wildcard(plan.bestAllTerm) &&
4345 !is_variable((EntityId)plan.bestAllTerm.id()) &&
4346 (!plan.bestAllTerm.pair() || !is_variable((EntityId)plan.bestAllTerm.gen())) &&
4347 world_term_uses_inherit_policy(world, plan.bestAllTerm);
4348 if (candidateUsesInherited != bestUsesInherited)
4349 return !candidateUsesInherited;
4350
4351 return false;
4352 }
4353
4354 GAIA_NODISCARD static DirectEntitySeedPlan
4355 direct_entity_seed_plan(const World& world, const QueryInfo& queryInfo) {
4356 DirectEntitySeedPlan plan;
4357 uint32_t totalOrTermCount = 0;
4358
4359 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4360 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4361 continue;
4362 if (term.op == QueryOpKind::All) {
4363 plan.hasAllTerms = true;
4364 const auto cnt = count_direct_term_entities(world, term);
4365 if (should_prefer_direct_seed_term(world, term, cnt, plan)) {
4366 plan.bestAllTermCount = cnt;
4367 plan.bestAllTerm = term.id;
4368 plan.bestAllTermMatchKind = term.matchKind;
4369 }
4370 } else if (term.op == QueryOpKind::Or) {
4371 plan.hasOrTerms = true;
4372 totalOrTermCount += count_direct_term_entities(world, term);
4373 }
4374 }
4375
4376 plan.preferOrSeed = plan.hasOrTerms && (!plan.hasAllTerms || totalOrTermCount < plan.bestAllTermCount);
4377 return plan;
4378 }
4379
4381 GAIA_NODISCARD static bool match_direct_entity_terms(
4382 const World& world, Entity entity, const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
4383 bool hasOrTerms = false;
4384 bool anyOrMatched = false;
4385
4386 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4387 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4388 continue;
4389 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4390 term.matchKind == seedInfo.seededAllMatchKind)
4391 continue;
4392 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4393 continue;
4394
4395 const bool present = match_entity_term(world, entity, term);
4396 switch (term.op) {
4397 case QueryOpKind::All:
4398 if (!present)
4399 return false;
4400 break;
4401 case QueryOpKind::Or:
4402 hasOrTerms = true;
4403 anyOrMatched |= present;
4404 break;
4405 case QueryOpKind::Not:
4406 if (present)
4407 return false;
4408 break;
4409 case QueryOpKind::Any:
4410 case QueryOpKind::Count:
4411 break;
4412 }
4413 }
4414
4415 return !hasOrTerms || anyOrMatched;
4416 }
4417
4419 GAIA_NODISCARD static bool match_remaining_direct_storage_terms(
4420 const World& world, Entity entity, const QueryInfo& queryInfo, Entity seedTerm) {
4421 bool hasOrTerms = false;
4422 bool anyOrMatched = false;
4423 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4424 if (term.op == QueryOpKind::All && term.id == seedTerm && term.matchKind == QueryMatchKind::Direct)
4425 continue;
4426
4427 const bool present = world_has_entity_term_direct(world, entity, term.id);
4428 switch (term.op) {
4429 case QueryOpKind::All:
4430 if (!present)
4431 return false;
4432 break;
4433 case QueryOpKind::Or:
4434 hasOrTerms = true;
4435 anyOrMatched |= present;
4436 break;
4437 case QueryOpKind::Not:
4438 if (present)
4439 return false;
4440 break;
4441 case QueryOpKind::Any:
4442 case QueryOpKind::Count:
4443 break;
4444 }
4445 }
4446
4447 return !hasOrTerms || anyOrMatched;
4448 }
4449
4451 GAIA_NODISCARD static bool match_specialized_direct_target_terms(
4452 const World& world, Entity entity, const QueryInfo& queryInfo, Entity termId,
4455 return world_has_entity_term_direct(world, entity, termId);
4456
4457 DirectEntitySeedInfo seedInfo{};
4458 switch (kind) {
4461 return world_has_entity_term(world, entity, termId);
4463 return world_has_entity_term_in(world, entity, termId);
4465 if (!world_has_entity_term_direct(world, entity, termId))
4466 return false;
4467 return match_remaining_direct_storage_terms(world, entity, queryInfo, termId);
4469 if (!world_has_entity_term_direct(world, entity, termId))
4470 return false;
4471 seedInfo.seededAllMatchKind = QueryMatchKind::Direct;
4472 break;
4474 if (!world_has_entity_term_in(world, entity, termId))
4475 return false;
4476 seedInfo.seededAllMatchKind = QueryMatchKind::In;
4477 break;
4480 if (!world_has_entity_term(world, entity, termId))
4481 return false;
4482 seedInfo.seededAllMatchKind = QueryMatchKind::Semantic;
4483 break;
4486 return false;
4487 }
4488
4489 seedInfo.seededAllTerm = termId;
4490 seedInfo.seededFromAll = true;
4491 return match_direct_entity_terms(world, entity, queryInfo, seedInfo);
4492 }
4493
4494 GAIA_NODISCARD static const QueryTerm*
4495 find_direct_all_seed_term(const QueryInfo& queryInfo, const DirectEntitySeedPlan& plan) {
4496 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4497 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4498 continue;
4499 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4500 term.matchKind != plan.bestAllTermMatchKind)
4501 continue;
4502 return &term;
4503 }
4504
4505 return nullptr;
4506 }
4507
4508 GAIA_NODISCARD static DirectEntitySeedEvalPlan
4509 direct_all_seed_eval_plan(const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
4510 DirectEntitySeedEvalPlan plan{};
4511
4512 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4513 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4514 return {};
4515 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4516 term.matchKind == seedInfo.seededAllMatchKind)
4517 continue;
4518
4519 if (term.op == QueryOpKind::All) {
4520 if (plan.pSingleAllTerm != nullptr)
4521 return {};
4522 plan.pSingleAllTerm = &term;
4523 continue;
4524 }
4525
4526 return {};
4527 }
4528
4529 plan.alwaysMatch = plan.pSingleAllTerm == nullptr;
4530 return plan;
4531 }
4532
4538 GAIA_NODISCARD static bool
4539 can_use_direct_seed_run_cache(const World& world, const QueryInfo& queryInfo, const QueryTerm& seedTerm) {
4540 if (!(uses_non_direct_is_matching(seedTerm) || uses_inherited_id_matching(world, seedTerm)))
4541 return false;
4542
4543 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4544 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4545 return false;
4546 if (term.op == QueryOpKind::Any || term.op == QueryOpKind::Count || term.op == QueryOpKind::Or)
4547 return false;
4548 if (term.op == QueryOpKind::All && term.id == seedTerm.id && term.matchKind == seedTerm.matchKind)
4549 continue;
4550 if (is_non_fragmenting_direct_term(world, term))
4551 return false;
4552 }
4553
4554 return true;
4555 }
4556
4557 GAIA_NODISCARD std::span<const detail::BfsChunkRun> cached_direct_seed_runs(
4558 QueryInfo& queryInfo, const QueryTerm& seedTerm, const DirectEntitySeedInfo& seedInfo,
4559 Constraints constraints) {
4560 auto& runData = ensure_direct_seed_run_data();
4561 auto& world = *queryInfo.world();
4562 const auto cachedConstraints = constraints;
4563 const auto relVersion = world_rel_version(world, Is);
4564 const auto worldVersion = ::gaia::ecs::world_version(world);
4565
4566 if (runData.cacheValid && runData.cachedSeedTerm == seedTerm.id &&
4567 runData.cachedSeedMatchKind == seedTerm.matchKind && runData.cachedConstraints == cachedConstraints &&
4568 runData.cachedRelVersion == relVersion && runData.cachedWorldVersion == worldVersion) {
4569 return {runData.cachedRuns.data(), runData.cachedRuns.size()};
4570 }
4571
4572 auto& runs = runData.cachedRuns;
4573 auto& entities = runData.cachedEntities;
4574 auto& chunkOrderedEntities = runData.cachedChunkOrderedEntities;
4575 runs.clear();
4576 entities.clear();
4577 chunkOrderedEntities.clear();
4578
4579 (void)each_direct_term_entity(world, seedTerm, [&](Entity entity) {
4580 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4581 return true;
4582
4583 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4584 return true;
4585
4586 entities.push_back(entity);
4587 return true;
4588 });
4589
4590 chunkOrderedEntities = entities;
4591 core::sort(chunkOrderedEntities, [&](Entity left, Entity right) {
4592 const auto& ecLeft = ::gaia::ecs::fetch(world, left);
4593 const auto& ecRight = ::gaia::ecs::fetch(world, right);
4594 if (ecLeft.pArchetype != ecRight.pArchetype)
4595 return ecLeft.pArchetype->id() < ecRight.pArchetype->id();
4596 if (ecLeft.pChunk != ecRight.pChunk)
4597 return ecLeft.pChunk < ecRight.pChunk;
4598 return ecLeft.row < ecRight.row;
4599 });
4600
4601 uint32_t entityOffset = 0;
4602 for (const auto entity: chunkOrderedEntities) {
4603 const auto& ec = ::gaia::ecs::fetch(world, entity);
4604 add_chunk_run(runs, ec, entityOffset++);
4605 }
4606
4607 runData.cachedSeedTerm = seedTerm.id;
4608 runData.cachedSeedMatchKind = seedTerm.matchKind;
4609 runData.cachedConstraints = cachedConstraints;
4610 runData.cachedRelVersion = relVersion;
4611 runData.cachedWorldVersion = worldVersion;
4612 runData.cacheValid = true;
4613 return {runs.data(), runs.size()};
4614 }
4615
4616 GAIA_NODISCARD std::span<const Entity> cached_direct_seed_chunk_entities(
4617 QueryInfo& queryInfo, const QueryTerm& seedTerm, const DirectEntitySeedInfo& seedInfo,
4618 Constraints constraints) {
4619 (void)cached_direct_seed_runs(queryInfo, seedTerm, seedInfo, constraints);
4620 auto& runData = ensure_direct_seed_run_data();
4621 return {runData.cachedChunkOrderedEntities.data(), runData.cachedChunkOrderedEntities.size()};
4622 }
4623
4624 template <typename Func>
4625 GAIA_NODISCARD static bool each_direct_all_seed(
4626 const World& world, const QueryInfo& queryInfo, const DirectEntitySeedPlan& plan, Constraints constraints,
4627 Func&& func) {
4628 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
4629 GAIA_ASSERT(pSeedTerm != nullptr);
4630 if (pSeedTerm == nullptr)
4631 return true;
4632
4633 DirectEntitySeedInfo seedInfo{};
4634 seedInfo.seededAllTerm = pSeedTerm->id;
4635 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
4636 seedInfo.seededFromAll = true;
4637 const auto evalPlan = direct_all_seed_eval_plan(queryInfo, seedInfo);
4638 const Archetype* pLastSingleAllArchetype = nullptr;
4639 bool lastSingleAllMatch = false;
4640 bool seedImpliesSingleAllTerm = false;
4641 if (evalPlan.pSingleAllTerm != nullptr && uses_non_direct_is_matching(*pSeedTerm) &&
4642 (uses_non_direct_is_matching(*evalPlan.pSingleAllTerm) ||
4643 uses_inherited_id_matching(world, *evalPlan.pSingleAllTerm))) {
4644 const auto seedTarget = pair_tgt(world, pSeedTerm->id);
4645 if (seedTarget != EntityBad)
4646 seedImpliesSingleAllTerm = match_entity_term(world, seedTarget, *evalPlan.pSingleAllTerm);
4647 }
4648
4649 // Stream the chosen ALL seed term directly. This avoids materializing a temporary
4650 // entity array for the common `all<T>().is(base)` shape.
4651 return each_direct_term_entity(world, *pSeedTerm, [&](Entity entity) {
4652 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4653 return true;
4654
4655 if (evalPlan.alwaysMatch)
4656 return func(entity);
4657 if (evalPlan.pSingleAllTerm != nullptr) {
4658 if (seedImpliesSingleAllTerm)
4659 return func(entity);
4660 if (uses_non_direct_is_matching(*evalPlan.pSingleAllTerm) ||
4661 uses_inherited_id_matching(world, *evalPlan.pSingleAllTerm)) {
4662 const auto* pArchetype = world_entity_archetype(world, entity);
4663 if (pArchetype != pLastSingleAllArchetype) {
4664 lastSingleAllMatch = match_entity_term(world, entity, *evalPlan.pSingleAllTerm);
4665 pLastSingleAllArchetype = pArchetype;
4666 }
4667 if (!lastSingleAllMatch)
4668 return true;
4669 } else if (!match_entity_term(world, entity, *evalPlan.pSingleAllTerm)) {
4670 return true;
4671 }
4672 return func(entity);
4673 }
4674 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4675 return true;
4676
4677 return func(entity);
4678 });
4679 }
4680
4682 GAIA_NODISCARD static bool match_direct_entity_constraints(
4683 const World& world, const QueryInfo& queryInfo, Entity entity, Constraints constraints) {
4684 if (!queryInfo.matches_prefab_entities() && world_entity_prefab(world, entity))
4685 return false;
4686
4687 if (constraints == Constraints::EnabledOnly)
4688 return world_entity_enabled(world, entity);
4689 if (constraints == Constraints::DisabledOnly)
4690 return !world_entity_enabled(world, entity);
4691 return true;
4692 }
4693
4695 GAIA_NODISCARD static bool can_use_archetype_bucket_count(
4696 const World& world, const QueryInfo& queryInfo, const DirectEntitySeedInfo& seedInfo) {
4697 if (!seedInfo.seededFromAll && !seedInfo.seededFromOr)
4698 return false;
4699
4700 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4701 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4702 return false;
4703 if (seedInfo.seededFromAll && term.id == seedInfo.seededAllTerm && term.op == QueryOpKind::All)
4704 continue;
4705 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4706 continue;
4707 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Not)
4708 return false;
4709 if (is_non_fragmenting_direct_term(world, term))
4710 return false;
4711 if (uses_inherited_id_matching(world, term))
4712 return false;
4713 }
4714
4715 return true;
4716 }
4717
4719 GAIA_NODISCARD static uint32_t count_direct_entity_seed_by_archetype(
4720 const World& world, const QueryInfo& queryInfo, const cnt::darray<Entity>& seedEntities,
4721 const DirectEntitySeedInfo& seedInfo, Constraints constraints, DirectQueryScratch& scratch) {
4722 scratch.archetypes.clear();
4723 scratch.bucketEntities.clear();
4724 scratch.counts.clear();
4725
4726 for (const auto entity: seedEntities) {
4727 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4728 continue;
4729
4730 const auto* pArchetype = world_entity_archetype(world, entity);
4731 const auto idx = core::get_index(scratch.archetypes, pArchetype);
4732 if (idx == BadIndex) {
4733 scratch.archetypes.push_back(pArchetype);
4734 scratch.bucketEntities.push_back(entity);
4735 scratch.counts.push_back(1);
4736 } else {
4737 ++scratch.counts[idx];
4738 }
4739 }
4740
4741 uint32_t cnt = 0;
4742 const auto archetypeCnt = (uint32_t)scratch.archetypes.size();
4743 GAIA_FOR(archetypeCnt) {
4744 if (match_direct_entity_terms(world, scratch.bucketEntities[i], queryInfo, seedInfo))
4745 cnt += scratch.counts[i];
4746 }
4747
4748 return cnt;
4749 }
4750
4752 GAIA_NODISCARD static uint32_t
4753 count_direct_or_union(const World& world, const QueryInfo& queryInfo, Constraints constraints) {
4754 DirectQueryScratchScope scratchScope;
4755 auto& scratch = scratchScope.scratch;
4756 const auto seenVersion = next_direct_query_seen_version(scratch);
4757 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
4758
4759 uint32_t cnt = 0;
4760 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4761 if (term.op != QueryOpKind::Or)
4762 continue;
4763
4764 (void)each_direct_term_entity(world, term, [&](Entity entity) {
4765 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4766 return true;
4767
4768 const auto entityId = (uint32_t)entity.id();
4769 ensure_direct_query_count_capacity(scratch, entityId);
4770
4771 if (scratch.counts[entityId] == seenVersion)
4772 return true;
4773 scratch.counts[entityId] = seenVersion;
4774
4775 bool rejected = false;
4776 if (hasDirectNotTerms) {
4777 for (const auto& notTerm: queryInfo.ctx().data.terms_view()) {
4778 if (notTerm.op != QueryOpKind::Not)
4779 continue;
4780 if (match_entity_term(world, entity, notTerm)) {
4781 rejected = true;
4782 break;
4783 }
4784 }
4785 }
4786
4787 if (!rejected)
4788 ++cnt;
4789 return true;
4790 });
4791 }
4792
4793 return cnt;
4794 }
4795
4801 GAIA_NODISCARD static bool
4802 is_empty_direct_or_union(const World& world, const QueryInfo& queryInfo, Constraints constraints) {
4803 DirectQueryScratchScope scratchScope;
4804 auto& scratch = scratchScope.scratch;
4805 const auto seenVersion = next_direct_query_seen_version(scratch);
4806 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
4807
4808 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4809 if (term.op != QueryOpKind::Or)
4810 continue;
4811
4812 const bool completed = each_direct_term_entity(world, term, [&](Entity entity) {
4813 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4814 return true;
4815
4816 const auto entityId = (uint32_t)entity.id();
4817 ensure_direct_query_count_capacity(scratch, entityId);
4818
4819 if (scratch.counts[entityId] == seenVersion)
4820 return true;
4821 scratch.counts[entityId] = seenVersion;
4822
4823 bool rejected = false;
4824 if (hasDirectNotTerms) {
4825 for (const auto& notTerm: queryInfo.ctx().data.terms_view()) {
4826 if (notTerm.op != QueryOpKind::Not)
4827 continue;
4828 if (match_entity_term(world, entity, notTerm)) {
4829 rejected = true;
4830 break;
4831 }
4832 }
4833 }
4834
4835 if (!rejected)
4836 return false;
4837 return true;
4838 });
4839
4840 if (!completed)
4841 return false;
4842 }
4843
4844 return true;
4845 }
4846
4848 static DirectEntitySeedInfo
4849 build_direct_entity_seed(const World& world, const QueryInfo& queryInfo, DirectQueryScratch& scratch) {
4850 auto& out = scratch.entities;
4851 out.clear();
4852 DirectEntitySeedInfo seedInfo{};
4853 const auto plan = direct_entity_seed_plan(world, queryInfo);
4854
4855 if (plan.hasAllTerms && !plan.preferOrSeed) {
4856 if (plan.bestAllTerm != EntityBad) {
4857 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4858 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4859 continue;
4860 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4861 term.matchKind != plan.bestAllTermMatchKind)
4862 continue;
4863 collect_direct_term_entities(world, term, out);
4864 seedInfo.seededAllMatchKind = term.matchKind;
4865 break;
4866 }
4867 seedInfo.seededFromAll = true;
4868 seedInfo.seededAllTerm = plan.bestAllTerm;
4869 }
4870 return seedInfo;
4871 }
4872
4873 const auto seenVersion = next_direct_query_seen_version(scratch);
4874
4875 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4876 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4877 continue;
4878 if (term.op != QueryOpKind::Or)
4879 continue;
4880
4881 scratch.termEntities.clear();
4882 collect_direct_term_entities(world, term, scratch.termEntities);
4883 for (const auto entity: scratch.termEntities) {
4884 const auto entityId = (uint32_t)entity.id();
4885 ensure_direct_query_count_capacity(scratch, entityId);
4886
4887 if (scratch.counts[entityId] == seenVersion)
4888 continue;
4889 scratch.counts[entityId] = seenVersion;
4890 out.push_back(entity);
4891 }
4892 }
4893
4894 seedInfo.seededFromOr = true;
4895 return seedInfo;
4896 }
4897
4901 GAIA_NODISCARD static bool has_direct_not_terms(const QueryInfo& queryInfo) {
4902 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4903 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4904 continue;
4905 if (term.op == QueryOpKind::Not)
4906 return true;
4907 }
4908
4909 return false;
4910 }
4911
4919 template <typename Func>
4920 GAIA_FORCEINLINE void
4921 each_direct_or_union(World& world, const QueryInfo& queryInfo, Constraints constraints, Func&& func) {
4922 DirectQueryScratchScope scratchScope;
4923 auto& scratch = scratchScope.scratch;
4924 const auto seenVersion = next_direct_query_seen_version(scratch);
4925 DirectEntitySeedInfo seedInfo{};
4926 seedInfo.seededFromOr = true;
4927
4928 for (const auto& term: queryInfo.ctx().data.terms_view()) {
4929 if (term.op != QueryOpKind::Or)
4930 continue;
4931
4932 (void)each_direct_term_entity(world, term, [&](Entity entity) {
4933 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4934 return true;
4935
4936 const auto entityId = (uint32_t)entity.id();
4937 ensure_direct_query_count_capacity(scratch, entityId);
4938
4939 if (scratch.counts[entityId] == seenVersion)
4940 return true;
4941 scratch.counts[entityId] = seenVersion;
4942
4943 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4944 return true;
4945
4946 func(entity);
4947 return true;
4948 });
4949 }
4950 }
4951
4958 template <bool UseFilters>
4959 GAIA_NODISCARD bool empty_inter(const QueryInfo& queryInfo, Constraints constraints) const {
4960 const auto cacheRange = selected_query_cache_range(queryInfo);
4961
4962 if constexpr (!UseFilters) {
4963 if (!cacheRange.hasSelectedGroup && can_use_direct_entity_seed_eval(queryInfo)) {
4964 if (has_only_direct_or_terms(queryInfo))
4965 return is_empty_direct_or_union(*queryInfo.world(), queryInfo, constraints);
4966
4967 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
4968 bool empty = true;
4969 (void)each_direct_all_seed(*queryInfo.world(), queryInfo, plan, constraints, [&](Entity) {
4970 empty = false;
4971 return false;
4972 });
4973 return empty;
4974 }
4975 }
4976
4977 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
4978 const auto cacheView = queryInfo.cache_archetype_view();
4979 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
4980 if (needsBarrierCache)
4981 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
4982 if (!cacheRange.valid)
4983 return true;
4984 const auto idxFrom = cacheRange.idxFrom;
4985 const auto idxTo = cacheRange.idxTo;
4986
4987 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
4988 const auto* pArchetype = cacheView[qi];
4989 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
4990 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
4991 continue;
4992
4993 GAIA_PROF_SCOPE(query::empty);
4994
4995 const auto& chunks = pArchetype->chunks();
4996 Iter it;
4997 it.init_query_state(queryInfo.world(), constraints, false);
4998#if GAIA_ASSERT_ENABLED
4999 it.set_query_access(&queryInfo.ctx().data);
5000#endif
5001 it.set_archetype(pArchetype);
5002
5003 if (!hasEntityFilters) {
5004 for (auto* pChunk: chunks) {
5005 uint16_t from = 0;
5006 uint16_t to = 0;
5007 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
5008 if (from == to)
5009 continue;
5010 it.set_chunk(pChunk, from, to);
5011 if constexpr (UseFilters) {
5012 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
5013 continue;
5014 }
5015 return false;
5016 }
5017 continue;
5018 }
5019
5020 const bool isNotEmpty = core::has_if(chunks, [&](Chunk* pChunk) {
5021 uint16_t from = 0;
5022 uint16_t to = 0;
5023 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
5024 if (from == to)
5025 return false;
5026 it.set_chunk(pChunk, from, to);
5027 if constexpr (UseFilters)
5028 if (it.size() == 0 || !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
5029 return false;
5030 if (!hasEntityFilters)
5031 return it.size() > 0;
5032
5033 const auto entities = it.template view<Entity>();
5034 const auto cnt = it.size();
5035 GAIA_FOR(cnt) {
5036 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
5037 return true;
5038 }
5039 return false;
5040 });
5041
5042 if (isNotEmpty)
5043 return false;
5044 }
5045
5046 return true;
5047 }
5048
5059 template <typename Emit>
5060 static void
5061 each_matching_iter_range(const QueryInfo& queryInfo, Chunk* pChunk, uint16_t from, uint16_t to, Emit&& emit) {
5062 if (pChunk == nullptr || from >= to)
5063 return;
5064 if (!queryInfo.has_entity_filter_terms()) {
5065 emit(from, to);
5066 return;
5067 }
5068
5069 const auto entities = pChunk->entity_view();
5070 uint16_t rangeFrom = from;
5071 bool inRange = false;
5072 for (uint16_t row = from; row < to; ++row) {
5073 const bool match = match_entity_filters(*queryInfo.world(), entities[row], queryInfo);
5074 if (match) {
5075 if (!inRange) {
5076 rangeFrom = row;
5077 inRange = true;
5078 }
5079 } else if (inRange) {
5080 emit(rangeFrom, row);
5081 inRange = false;
5082 }
5083 }
5084 if (inRange)
5085 emit(rangeFrom, to);
5086 }
5087
5100 template <typename Batches>
5101 static void push_matching_chunk_batch(
5102 Batches& batches, const QueryInfo& queryInfo, const Archetype* pArchetype, Chunk* pChunk,
5103 const uint8_t* pCompIndices, InheritedTermDataView inheritedData, GroupId groupId, uint16_t from,
5104 uint16_t to) {
5105 each_matching_iter_range(queryInfo, pChunk, from, to, [&](uint16_t rangeFrom, uint16_t rangeTo) {
5106 batches.push_back({pArchetype, pChunk, pCompIndices, inheritedData, groupId, rangeFrom, rangeTo});
5107 });
5108 }
5109
5116 template <typename Flush>
5117 static void push_matching_chunk_batch(
5118 ChunkBatchArray& batches, const QueryInfo& queryInfo, const Archetype* pArchetype, Chunk* pChunk,
5119 const uint8_t* pCompIndices, InheritedTermDataView inheritedData, GroupId groupId, uint16_t from,
5120 uint16_t to, Flush&& flush) {
5121 each_matching_iter_range(queryInfo, pChunk, from, to, [&](uint16_t rangeFrom, uint16_t rangeTo) {
5122 if (batches.size() == batches.max_size())
5123 flush();
5124 batches.push_back({pArchetype, pChunk, pCompIndices, inheritedData, groupId, rangeFrom, rangeTo});
5125 });
5126 }
5127
5129 GAIA_NODISCARD static bool match_entity_filters(const World& world, Entity entity, const QueryInfo& queryInfo) {
5130 bool hasOrTerms = false;
5131 bool anyOrMatched = false;
5132 const bool hasEntityFilterTerms = queryInfo.has_entity_filter_terms();
5133
5134 for (const auto& term: queryInfo.ctx().data.terms_view()) {
5135 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
5136 continue;
5137
5138 const auto id = term.id;
5139 const bool isDirectIsTerm = uses_non_direct_is_matching(term);
5140 const bool isInheritedTerm = uses_inherited_id_matching(world, term);
5141 const bool isNonFragmentingTerm =
5142 (id.pair() && world_relation_uses_non_fragmenting_storage(world, pair_rel(world, id))) ||
5143 (!id.pair() && world_component_is_non_fragmenting(world, id));
5144 const bool needsEntityFilter = isNonFragmentingTerm || isDirectIsTerm || isInheritedTerm ||
5145 (hasEntityFilterTerms && term.op == QueryOpKind::Or);
5146 if (!needsEntityFilter)
5147 continue;
5148
5149 const bool present = match_entity_term(world, entity, term);
5150 switch (term.op) {
5151 case QueryOpKind::All:
5152 if (!present)
5153 return false;
5154 break;
5155 case QueryOpKind::Or:
5156 hasOrTerms = true;
5157 anyOrMatched |= present;
5158 break;
5159 case QueryOpKind::Not:
5160 if (present)
5161 return false;
5162 break;
5163 case QueryOpKind::Any:
5164 case QueryOpKind::Count:
5165 break;
5166 }
5167 }
5168
5169 return !hasOrTerms || anyOrMatched;
5170 }
5171
5177 GAIA_NODISCARD bool
5178 matches_target_entities(QueryInfo& queryInfo, const Archetype& archetype, EntitySpan targetEntities) {
5179 if (targetEntities.empty())
5180 return false;
5181
5182 const auto& world = *queryInfo.world();
5183
5184 if (can_use_direct_target_eval(queryInfo)) {
5185 const auto directTargetEvalKind = queryInfo.direct_target_eval_kind();
5186 if (directTargetEvalKind != QueryCtx::DirectTargetEvalKind::Generic) {
5187 const auto termId = queryInfo.direct_target_eval_id();
5188 if (targetEntities.size() == 1) {
5189 const auto entity = targetEntities[0];
5190 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
5191 return false;
5192 return match_specialized_direct_target_terms(world, entity, queryInfo, termId, directTargetEvalKind);
5193 }
5194
5195 for (const auto entity: targetEntities) {
5196 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
5197 continue;
5198 if (match_specialized_direct_target_terms(world, entity, queryInfo, termId, directTargetEvalKind))
5199 return true;
5200 }
5201
5202 return false;
5203 }
5204
5205 const DirectEntitySeedInfo seedInfo{};
5206 if (targetEntities.size() == 1) {
5207 const auto entity = targetEntities[0];
5208 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
5209 return false;
5210 return match_direct_entity_terms(world, entity, queryInfo, seedInfo);
5211 }
5212
5213 for (const auto entity: targetEntities) {
5214 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
5215 continue;
5216 if (match_direct_entity_terms(world, entity, queryInfo, seedInfo))
5217 return true;
5218 }
5219
5220 return false;
5221 }
5222
5223 if (!match_one(queryInfo, archetype, targetEntities))
5224 return false;
5225
5226 if (!queryInfo.has_entity_filter_terms())
5227 return true;
5228
5229 for (const auto entity: targetEntities) {
5230 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
5231 continue;
5232 if (match_entity_filters(world, entity, queryInfo))
5233 return true;
5234 }
5235
5236 return false;
5237 }
5238
5245 template <bool UseFilters>
5246 GAIA_NODISCARD uint32_t count_inter(const QueryInfo& queryInfo, Constraints constraints) const {
5247 const auto cacheRange = selected_query_cache_range(queryInfo);
5248
5249 if constexpr (!UseFilters) {
5250 if (!cacheRange.hasSelectedGroup && can_use_direct_entity_seed_eval(queryInfo)) {
5251 if (has_only_direct_or_terms(queryInfo))
5252 return count_direct_or_union(*queryInfo.world(), queryInfo, constraints);
5253
5254 DirectQueryScratchScope scratchScope;
5255 auto& scratch = scratchScope.scratch;
5256
5257 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
5258 const auto seedInfo = build_direct_entity_seed(*queryInfo.world(), queryInfo, scratch);
5259
5260 if (can_use_archetype_bucket_count(*queryInfo.world(), queryInfo, seedInfo))
5261 return count_direct_entity_seed_by_archetype(
5262 *queryInfo.world(), queryInfo, scratch.entities, seedInfo, constraints, scratch);
5263
5264 uint32_t cnt = 0;
5265 (void)each_direct_all_seed(*queryInfo.world(), queryInfo, plan, constraints, [&](Entity) {
5266 ++cnt;
5267 return true;
5268 });
5269
5270 return cnt;
5271 }
5272 }
5273
5274 uint32_t cnt = 0;
5275 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
5276 const auto cacheView = queryInfo.cache_archetype_view();
5277 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
5278 if (needsBarrierCache)
5279 const_cast<QueryInfo&>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
5280
5281 if (!cacheRange.valid)
5282 return 0;
5283 const auto idxFrom = cacheRange.idxFrom;
5284 const auto idxTo = cacheRange.idxTo;
5285
5286 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
5287 const auto* pArchetype = cacheView[qi];
5288 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
5289 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
5290 continue;
5291
5292 GAIA_PROF_SCOPE(query::count);
5293
5294 const auto& chunks = pArchetype->chunks();
5295 Iter it;
5296 it.init_query_state(queryInfo.world(), constraints, false);
5297#if GAIA_ASSERT_ENABLED
5298 it.set_query_access(&queryInfo.ctx().data);
5299#endif
5300 it.set_archetype(pArchetype);
5301
5302 if (!hasEntityFilters) {
5303 for (auto* pChunk: chunks) {
5304 uint16_t from = 0;
5305 uint16_t to = 0;
5306 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
5307 const uint16_t entityCnt = to - from;
5308 if (entityCnt == 0)
5309 continue;
5310 it.set_chunk(pChunk, from, to);
5311
5312 if constexpr (UseFilters) {
5313 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
5314 continue;
5315 }
5316
5317 cnt += entityCnt;
5318 }
5319 continue;
5320 }
5321 for (auto* pChunk: chunks) {
5322 uint16_t from = 0;
5323 uint16_t to = 0;
5324 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
5325 const uint16_t entityCnt = to - from;
5326 if (entityCnt == 0)
5327 continue;
5328 it.set_chunk(pChunk, from, to);
5329
5330 // Filters
5331 if constexpr (UseFilters) {
5332 if (!match_filters(*pChunk, queryInfo, m_changedWorldVersion))
5333 continue;
5334 }
5335
5336 if (hasEntityFilters) {
5337 const auto entities = it.template view<Entity>();
5338 GAIA_FOR(entityCnt) {
5339 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
5340 ++cnt;
5341 }
5342 continue;
5343 }
5344
5345 // Entity count
5346 cnt += entityCnt;
5347 }
5348 }
5349
5350 return cnt;
5351 }
5352
5353 static void init_direct_entity_iter(
5354 const QueryInfo& queryInfo, const World& world, const EntityContainer& ec, Iter& it, uint8_t* pIndices,
5355 Entity* pTermIds, const Archetype*& pLastArchetype) {
5356 GAIA_ASSERT(ec.pArchetype != nullptr);
5357 GAIA_ASSERT(ec.pChunk != nullptr);
5358 GAIA_ASSERT(ec.row < ec.pChunk->size());
5359#if GAIA_ASSERT_ENABLED
5360 it.set_query_access(&queryInfo.ctx().data);
5361#endif
5362
5363 if (ec.pArchetype != pLastArchetype) {
5364 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
5365 pIndices[i] = 0xFF;
5366 pTermIds[i] = EntityBad;
5367 }
5368
5369 const auto terms = queryInfo.ctx().data.terms_view();
5370 const auto queryIdCnt = (uint32_t)terms.size();
5371 auto indicesView = queryInfo.try_indices_mapping_view(ec.pArchetype);
5372 GAIA_FOR(queryIdCnt) {
5373 const auto& term = terms[i];
5374 const auto fieldIdx = term.fieldIndex;
5375 const auto queryId = term.id;
5376 pTermIds[fieldIdx] = queryId;
5377 if (!indicesView.empty()) {
5378 pIndices[fieldIdx] = indicesView[fieldIdx];
5379 continue;
5380 }
5381 if (!query_term_maps_to_current_archetype(term))
5382 continue;
5383
5384 if (world_component_uses_sparse_storage(world, queryId)) {
5385 pIndices[fieldIdx] = 0xFF;
5386 continue;
5387 }
5388
5389 auto compIdx = world_component_index_comp_idx(world, *ec.pArchetype, queryId);
5390 if (compIdx == BadIndex || compIdx == ComponentIndexBad)
5391 compIdx = core::get_index(ec.pArchetype->ids_view(), queryId);
5392 pIndices[fieldIdx] =
5393 (compIdx != BadIndex && compIdx != ComponentIndexBad && compIdx < ec.pArchetype->ids_view().size())
5394 ? (uint8_t)compIdx
5395 : (uint8_t)0xFF;
5396 }
5397
5398 it.set_archetype(ec.pArchetype);
5399 it.set_comp_indices(pIndices);
5400 const auto inheritedDataView = queryInfo.inherited_data_view(ec.pArchetype);
5401 it.set_inherited_data(inheritedDataView);
5402 it.set_term_ids(pTermIds);
5403 pLastArchetype = ec.pArchetype;
5404 }
5405
5406 it.set_chunk(ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
5407 it.set_group_id(0);
5408 }
5409
5410 static void init_direct_entity_iter(
5411 const QueryInfo& queryInfo, const World& world, Entity entity, Iter& it, uint8_t* pIndices,
5412 Entity* pTermIds) {
5413 const auto& ec = ::gaia::ecs::fetch(world, entity);
5414 const Archetype* pLastArchetype = nullptr;
5415 it.set_world(&world);
5416 init_direct_entity_iter(queryInfo, world, ec, it, pIndices, pTermIds, pLastArchetype);
5417 }
5418
5419 template <typename Func>
5420 void each_chunk_runs_iter(
5421 QueryInfo& queryInfo, std::span<const detail::BfsChunkRun> runs, Constraints constraints, Func func) {
5422 auto& world = *queryInfo.world();
5423 Iter it;
5424 it.init_query_state(&world, constraints, false);
5425 const Archetype* pLastArchetype = nullptr;
5426 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5427 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5428
5429 for (const auto& run: runs) {
5430 const auto& ec = ::gaia::ecs::fetch(world, run.pChunk->entity_view()[run.from]);
5431 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
5432 it.set_chunk(run.pChunk, run.from, run.to);
5433 it.set_group_id(0);
5434 it.ctx(m_ctx);
5435 func(it);
5436 finish_iter_writes(it);
5437 it.clear_touched_writes();
5438 }
5439 }
5440
5441 struct DirectChunkArgEvalDesc {
5442 Entity id = EntityBad;
5443 bool isEntity = false;
5444 bool isPair = false;
5445 bool usesSparseStorage = false;
5446 };
5447
5453 GAIA_NODISCARD static bool can_use_direct_chunk_term_eval_arg(
5454 World& world, const QueryInfo& queryInfo, const DirectChunkArgEvalDesc& desc) {
5455 if (desc.isEntity)
5456 return true;
5457 if (desc.isPair)
5458 return false;
5459 if (world_component_uses_sparse_storage(world, desc.id))
5460 return false;
5461
5462 for (const auto& term: queryInfo.ctx().data.terms_view()) {
5463 if (term.id != desc.id)
5464 continue;
5465 if (!query_term_maps_to_current_archetype(term))
5466 return false;
5467 if (uses_non_direct_is_matching(term) || uses_inherited_id_matching(world, term) ||
5468 is_non_fragmenting_direct_term(world, term))
5469 return false;
5470 return true;
5471 }
5472
5473 return false;
5474 }
5475
5476 GAIA_NODISCARD static bool can_use_direct_chunk_term_eval_descs(
5477 World& world, const QueryInfo& queryInfo, const DirectChunkArgEvalDesc* pDescs, uint32_t descCnt) {
5478 if (queryInfo.has_entity_filter_terms())
5479 return false;
5480
5481 GAIA_FOR(descCnt) {
5482 if (!can_use_direct_chunk_term_eval_arg(world, queryInfo, pDescs[i]))
5483 return false;
5484 }
5485
5486 return true;
5487 }
5488
5495 GAIA_NODISCARD static bool can_use_sparse_chunk_term_eval_descs(
5496 World& world, const QueryInfo& queryInfo, const DirectChunkArgEvalDesc* pDescs, uint32_t descCnt) {
5497 if (queryInfo.has_entity_filter_terms())
5498 return false;
5499
5500 GAIA_FOR(descCnt) {
5501 const auto& desc = pDescs[i];
5502 if (!desc.usesSparseStorage) {
5503 if (!can_use_direct_chunk_term_eval_arg(world, queryInfo, desc))
5504 return false;
5505 continue;
5506 }
5507 bool found = false;
5508 for (const auto& term: queryInfo.ctx().data.terms_view()) {
5509 if (term.id != desc.id)
5510 continue;
5511 found = true;
5512 if (!query_term_maps_to_current_archetype(term) || uses_non_direct_is_matching(term) ||
5514 return false;
5515 break;
5516 }
5517 if (!found)
5518 return false;
5519 }
5520 return true;
5521 }
5522
5530 template <typename Func>
5531 void each_direct_entities_iter(
5532 QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints, Func func) {
5533 auto& world = *queryInfo.world();
5534 auto& walkData = ensure_each_walk_data();
5535 Iter it;
5536 it.init_query_state(&world, constraints, false);
5537 if (!walkData.cachedRuns.empty()) {
5538 each_chunk_runs_iter(queryInfo, walkData.cachedRuns, constraints, func);
5539 return;
5540 }
5541
5542 const Archetype* pLastArchetype = nullptr;
5543 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5544 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5545 for (const auto entity: entities) {
5546 const auto& ec = ::gaia::ecs::fetch(world, entity);
5547 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
5548 it.ctx(m_ctx);
5549 func(it);
5550 finish_iter_writes(it);
5551 it.clear_touched_writes();
5552 }
5553 }
5554
5560 void finish_unlocked_each() {
5561 auto* pWorld = m_storage.world();
5562 commit_cmd_buffer_st(*pWorld);
5563 commit_cmd_buffer_mt(*pWorld);
5564 m_changedWorldVersion = *m_worldVersion;
5565 }
5566
5573 template <typename Func>
5574 void each_direct_iter_inter(QueryInfo& queryInfo, Constraints constraints, Func func) {
5575 auto& world = *queryInfo.world();
5576 const bool hasWriteTerms = queryInfo.ctx().data.readWriteMask != 0;
5577 const auto plan = direct_entity_seed_plan(world, queryInfo);
5578
5579 auto exec_entity = [&](Entity entity) {
5580 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5581 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5582 Iter it;
5583 it.set_constraints(constraints);
5584 init_direct_entity_iter(queryInfo, world, entity, it, indices, termIds);
5585 it.set_write_im(false);
5586 it.ctx(m_ctx);
5587 func(it);
5588 finish_iter_writes(it);
5589 };
5590
5591 if (hasWriteTerms) {
5592 DirectQueryScratchScope scratchScope;
5593 auto& scratch = scratchScope.scratch;
5594 // Writable callbacks may add local overrides and reshuffle direct-term indices,
5595 // so direct-seeded execution must iterate a stable snapshot.
5596 const auto seedInfo = build_direct_entity_seed(world, queryInfo, scratch);
5597 for (const auto entity: scratch.entities) {
5598 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
5599 continue;
5600 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
5601 continue;
5602 exec_entity(entity);
5603 }
5604 } else if (plan.preferOrSeed) {
5605 each_direct_or_union(world, queryInfo, constraints, exec_entity);
5606 } else {
5607 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
5608 if (pSeedTerm != nullptr && can_use_direct_seed_run_cache(world, queryInfo, *pSeedTerm)) {
5609 DirectEntitySeedInfo seedInfo{};
5610 seedInfo.seededAllTerm = pSeedTerm->id;
5611 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
5612 seedInfo.seededFromAll = true;
5613 each_chunk_runs_iter(
5614 queryInfo, cached_direct_seed_runs(queryInfo, *pSeedTerm, seedInfo, constraints), constraints, func);
5615 } else {
5616 (void)each_direct_all_seed(world, queryInfo, plan, constraints, [&](Entity entity) {
5617 exec_entity(entity);
5618 return true;
5619 });
5620 }
5621 }
5622
5623 finish_unlocked_each();
5624 }
5625
5638 QueryInfo& queryInfo, Constraints constraints, void* pFunc, const TypedQueryExecState& state,
5639 void (*runDirectChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&), bool needsInheritedArgIds,
5640 void (*invokeInherited)(World&, Entity, const Entity*, void*));
5641
5643 template <bool UseFilters, typename ContainerOut>
5644 void arr_inter(QueryInfo& queryInfo, ContainerOut& outArray, Constraints constraints);
5646
5647 public:
5648 QueryImpl() = default;
5649 ~QueryImpl() = default;
5650
5660 World& world, QueryCache& queryCache, ArchetypeId& nextArchetypeId, uint32_t& worldVersion,
5661 const EntityToArchetypeMap& entityToArchetypeMap,
5662 const EntityToArchetypeVersionMap& entityToArchetypeMapVersions, const ArchetypeDArray& allArchetypes):
5663 m_nextArchetypeId(&nextArchetypeId), m_worldVersion(&worldVersion),
5664 m_entityToArchetypeMap(&entityToArchetypeMap),
5665 m_entityToArchetypeMapVersions(&entityToArchetypeMapVersions), m_allArchetypes(&allArchetypes) {
5666 m_storage.init(&world, &queryCache);
5667 }
5668
5669#if GAIA_ECS_TEST_HOOKS
5670 template <typename Func>
5671 GAIA_NODISCARD QueryPlan test_typed_plan(Func func);
5672
5676 GAIA_NODISCARD QueryPlan test_iter_plan(Constraints constraints = Constraints::EnabledOnly);
5677#endif
5678
5681 GAIA_NODISCARD QueryId id() const {
5682 if (!uses_query_cache_storage() || !m_storage.is_current())
5683 return QueryIdBad;
5684 return m_storage.m_identity.handle.id();
5685 }
5686
5689 GAIA_NODISCARD uint32_t gen() const {
5690 if (!uses_query_cache_storage() || !m_storage.is_current())
5691 return QueryIdBad;
5692 return m_storage.m_identity.handle.gen();
5693 }
5694
5695 //------------------------------------------------
5696
5698 void reset() {
5699 m_storage.reset();
5700 m_eachWalkData.reset();
5701 m_directSeedRunData.reset();
5702 reset_changed_filter_state();
5703 invalidate_each_walk_cache();
5704 invalidate_direct_seed_run_cache();
5705 }
5706
5708 void destroy() {
5709 (void)m_storage.try_del_from_cache();
5710 m_eachWalkData.reset();
5711 m_directSeedRunData.reset();
5712 reset_changed_filter_state();
5713 invalidate_each_walk_cache();
5714 invalidate_direct_seed_run_cache();
5715 }
5716
5719 GAIA_NODISCARD bool is_cached() const {
5720 return uses_query_cache_storage() && m_storage.is_cached();
5721 }
5722
5723 //------------------------------------------------
5724
5766 QueryImpl& add(const char* str, ...) {
5767 refresh_world_lifetime();
5768 GAIA_ASSERT(str != nullptr);
5769 if (str == nullptr)
5770 return *this;
5771
5772 va_list args{};
5773 va_start(args, str);
5774
5775 uint32_t pos = 0;
5776 uint32_t exp0 = 0;
5777 uint32_t parentDepth = 0;
5778
5780 uint32_t varNamesCnt = 0;
5781 auto is_this_expr = [](std::span<const char> exprRaw) {
5782 auto expr = util::trim(exprRaw);
5783 return expr.size() == 5 && expr[0] == '$' && expr[1] == 't' && expr[2] == 'h' && expr[3] == 'i' &&
5784 expr[4] == 's';
5785 };
5786
5787 auto find_or_alloc_var = [&](std::span<const char> varExpr) -> Entity {
5788 auto varNameSpan = util::trim(varExpr);
5789 if (varNameSpan.empty())
5790 return EntityBad;
5791
5792 const util::str_view varName{varNameSpan.data(), (uint32_t)varNameSpan.size()};
5793 if (is_reserved_var_name(varName)) {
5794 GAIA_ASSERT2(false, "$this is reserved and can only be used as a source expression: Id($this)");
5795 return EntityBad;
5796 }
5797
5798 const auto namedVar = find_var_by_name(varName);
5799 if (namedVar != EntityBad)
5800 return namedVar;
5801
5802 GAIA_FOR(varNamesCnt) {
5803 if (varNames[i].size() != varName.size())
5804 continue;
5805 if (varNames[i].size() > 0 && memcmp(varNames[i].data(), varName.data(), varName.size()) != 0)
5806 continue;
5807 return query_var_entity(i);
5808 }
5809
5810 if (varNamesCnt >= varNames.size()) {
5811 GAIA_ASSERT2(false, "Too many query variables in expression");
5812 return EntityBad;
5813 }
5814
5815 const auto idx = varNamesCnt++;
5816 varNames[idx] = varName;
5817
5818 const auto varEntity = query_var_entity(idx);
5819 (void)set_var_name_internal(varEntity, varName);
5820 return varEntity;
5821 };
5822
5823 auto parse_entity_expr = [&](auto&& self, std::span<const char> exprRaw) -> Entity {
5824 auto expr = util::trim(exprRaw);
5825 if (expr.empty())
5826 return EntityBad;
5827
5828 if (expr[0] == '$')
5829 return find_or_alloc_var(expr.subspan(1));
5830
5831 if (expr[0] == '(') {
5832 if (expr.back() != ')') {
5833 GAIA_ASSERT2(false, "Expression '(' not terminated");
5834 return EntityBad;
5835 }
5836
5837 const auto idStr = expr.subspan(1, expr.size() - 2);
5838 const auto commaIdx = core::get_index(idStr, ',');
5839 if (commaIdx == BadIndex) {
5840 GAIA_ASSERT2(false, "Pair expression does not contain ','");
5841 return EntityBad;
5842 }
5843
5844 const auto first = self(self, idStr.subspan(0, commaIdx));
5845 if (first == EntityBad)
5846 return EntityBad;
5847 const auto second = self(self, idStr.subspan(commaIdx + 1));
5848 if (second == EntityBad)
5849 return EntityBad;
5850
5851 return ecs::Pair(first, second);
5852 }
5853
5854 return expr_to_entity((const World&)*m_storage.world(), args, expr);
5855 };
5856
5857 auto parse_src_expr = [&](std::span<const char> srcExprRaw, Entity& srcOut) -> bool {
5858 auto srcExpr = util::trim(srcExprRaw);
5859 if (srcExpr.empty())
5860 return false;
5861
5862 // `$this` explicitly means the default source for the term.
5863 if (is_this_expr(srcExpr)) {
5864 srcOut = EntityBad;
5865 return true;
5866 }
5867
5868 srcOut = parse_entity_expr(parse_entity_expr, srcExpr);
5869 return srcOut != EntityBad;
5870 };
5871
5872 auto parse_term_expr = [&](std::span<const char> exprRaw, Entity& id, QueryTermOptions& options) -> bool {
5873 auto expr = util::trim(exprRaw);
5874 if (expr.empty())
5875 return false;
5876
5877 if (expr.back() == ')') {
5878 int32_t depth = 0;
5879 int32_t openIdx = -1;
5880 for (int32_t i = (int32_t)expr.size() - 1; i >= 0; --i) {
5881 if (expr[(uint32_t)i] == ')')
5882 ++depth;
5883 else if (expr[(uint32_t)i] == '(') {
5884 --depth;
5885 if (depth == 0) {
5886 openIdx = i;
5887 break;
5888 }
5889 }
5890 }
5891
5892 // `Id(src)` form. Keep `(Rel,Tgt)` intact by requiring a non-empty prefix.
5893 if (openIdx > 0) {
5894 auto idExpr = util::trim(expr.subspan(0, (uint32_t)openIdx));
5895 auto srcExpr = util::trim(expr.subspan((uint32_t)openIdx + 1, expr.size() - (uint32_t)openIdx - 2));
5896 if (!idExpr.empty() && !srcExpr.empty()) {
5897 id = parse_entity_expr(parse_entity_expr, idExpr);
5898 if (id == EntityBad)
5899 return false;
5900
5901 Entity src = EntityBad;
5902 if (!parse_src_expr(srcExpr, src))
5903 return false;
5904
5905 options.src(src);
5906 return true;
5907 }
5908 }
5909 }
5910
5911 id = parse_entity_expr(parse_entity_expr, expr);
5912 return id != EntityBad;
5913 };
5914
5915 auto add_term = [&](QueryOpKind op, std::span<const char> exprRaw) {
5916 auto expr = util::trim(exprRaw);
5917 if (expr.empty())
5918 return false;
5919
5920 bool isReadWrite = false;
5921 if (!expr.empty() && expr[0] == '&') {
5922 isReadWrite = true;
5923 expr = util::trim(expr.subspan(1));
5924 }
5925
5926 QueryTermOptions options{};
5927 if (isReadWrite)
5928 options.write();
5929
5930 Entity entity = EntityBad;
5931 if (!parse_term_expr(expr, entity, options))
5932 return false;
5933
5934 switch (op) {
5935 case QueryOpKind::All:
5936 all(entity, options);
5937 break;
5938 case QueryOpKind::Or:
5939 or_(entity, options);
5940 break;
5941 case QueryOpKind::Not:
5942 no(entity, options);
5943 break;
5944 case QueryOpKind::Any:
5945 any(entity, options);
5946 break;
5947 default:
5948 GAIA_ASSERT(false);
5949 return false;
5950 }
5951
5952 return true;
5953 };
5954
5955 auto process = [&]() {
5956 std::span<const char> exprRaw(&str[exp0], pos - exp0);
5957 exp0 = ++pos;
5958
5959 auto expr = util::trim(exprRaw);
5960 if (expr.empty())
5961 return true;
5962
5963 // OR-chain at top level maps to query::or_ terms.
5964 bool hasOrChain = false;
5965 {
5966 uint32_t depth = 0;
5967 const auto cnt = (uint32_t)expr.size();
5968 for (uint32_t i = 0; i + 1 < cnt; ++i) {
5969 const auto ch = expr[i];
5970 if (ch == '(')
5971 ++depth;
5972 else if (ch == ')') {
5973 GAIA_ASSERT(depth > 0);
5974 --depth;
5975 } else if (depth == 0 && ch == '|' && expr[i + 1] == '|') {
5976 hasOrChain = true;
5977 break;
5978 }
5979 }
5980 }
5981
5982 if (hasOrChain) {
5983 uint32_t depth = 0;
5984 uint32_t partBeg = 0;
5985 const auto cnt = (uint32_t)expr.size();
5986 for (uint32_t i = 0; i < cnt; ++i) {
5987 const auto ch = expr[i];
5988 if (ch == '(')
5989 ++depth;
5990 else if (ch == ')') {
5991 GAIA_ASSERT(depth > 0);
5992 --depth;
5993 }
5994
5995 const bool isOr = i + 1 < cnt && depth == 0 && ch == '|' && expr[i + 1] == '|';
5996 const bool isEnd = i + 1 == cnt;
5997 if (!isOr && !isEnd)
5998 continue;
5999
6000 const auto partEnd = isOr ? i : (i + 1);
6001 auto partExpr = expr.subspan(partBeg, partEnd - partBeg);
6002 if (!add_term(QueryOpKind::Or, partExpr))
6003 return false;
6004
6005 if (isOr) {
6006 partBeg = i + 2;
6007 ++i;
6008 }
6009 }
6010
6011 return true;
6012 }
6013
6014 QueryOpKind op = QueryOpKind::All;
6015 if (expr[0] == '?') {
6016 op = QueryOpKind::Any;
6017 expr = util::trim(expr.subspan(1));
6018 } else if (expr[0] == '!') {
6019 op = QueryOpKind::Not;
6020 expr = util::trim(expr.subspan(1));
6021 }
6022
6023 return add_term(op, expr);
6024 };
6025
6026 for (; str[pos] != 0; ++pos) {
6027 if (str[pos] == '(')
6028 ++parentDepth;
6029 else if (str[pos] == ')') {
6030 GAIA_ASSERT(parentDepth > 0);
6031 --parentDepth;
6032 } else if (str[pos] == ',' && parentDepth == 0) {
6033 if (!process())
6034 goto add_end;
6035 }
6036 }
6037 process();
6038
6039 add_end:
6040 va_end(args);
6041 return *this;
6042 }
6043
6048 // Add commands to the command buffer
6049 add_inter(item);
6050 return *this;
6051 }
6052
6053 //------------------------------------------------
6054
6059 QueryImpl& is(Entity entity, const QueryTermOptions& options = QueryTermOptions{}) {
6060 return all(Pair(Is, entity), options);
6061 }
6062
6063 //------------------------------------------------
6064
6070 options.in();
6071 return all(Pair(Is, entity), options);
6072 }
6073
6074 //------------------------------------------------
6075
6081 refresh_world_lifetime();
6082#if GAIA_ASSERT_ENABLED
6083 GAIA_ASSERT(m_storage.m_identity.serId != QueryIdBad && "no_access() requires a pending query term");
6084#endif
6085 if (m_storage.m_identity.serId == QueryIdBad)
6086 return *this;
6087 auto& buffer = m_storage.ser_buffer();
6088 const auto end = buffer.bytes();
6089 const uint32_t commandSizes[] = {ser::bytes(QueryCmd_AddItem{}), ser::bytes(QueryCmd_AddFilter{}),
6090 ser::bytes(QueryCmd_SortBy{}), ser::bytes(QueryCmd_GroupBy{}),
6091 ser::bytes(QueryCmd_GroupDep{}), ser::bytes(QueryCmd_MatchPrefab{})};
6092#if GAIA_ASSERT_ENABLED
6093 static_assert(
6094 sizeof(commandSizes) / sizeof(commandSizes[0]) ==
6095 sizeof(CommandBufferRead) / sizeof(CommandBufferRead[0]),
6096 "Every query command needs a serialized size");
6097#endif
6098 uint32_t termOffset = BadIndex;
6099 buffer.seek(0);
6100 while (buffer.tell() < end) {
6101 QueryCmdType id{};
6102 bool invalidatesHash = false;
6103 ser::load(buffer, id);
6104 ser::load(buffer, invalidatesHash);
6105 (void)invalidatesHash;
6106 if (id == QueryCmdType::ADD_ITEM)
6107 termOffset = buffer.tell();
6108 buffer.skip(commandSizes[id]);
6109 }
6110#if GAIA_ASSERT_ENABLED
6111 GAIA_ASSERT(termOffset != BadIndex && "no_access() requires a preceding all(), or_(), or any() term");
6112#endif
6113 buffer.seek(termOffset);
6114 QueryCmd_AddItem cmd{};
6115 ser::load(buffer, cmd);
6116#if GAIA_ASSERT_ENABLED
6117 GAIA_ASSERT(
6118 cmd.item.op == QueryOpKind::All || cmd.item.op == QueryOpKind::Or || cmd.item.op == QueryOpKind::Any);
6119#endif
6120 cmd.item.access = QueryAccess::Match;
6121 buffer.seek(termOffset);
6122 ser::save(buffer, cmd);
6123 buffer.seek(end);
6124 return *this;
6125 }
6126
6132 add_entity_term(QueryOpKind::All, entity, options);
6133 return *this;
6134 }
6135
6140 template <typename T>
6142
6146 template <typename T>
6148
6149 //------------------------------------------------
6150
6156 add_entity_term(QueryOpKind::Any, entity, options);
6157 return *this;
6158 }
6159
6164 template <typename T>
6166
6170 template <typename T>
6172
6173 //------------------------------------------------
6174
6181 add_entity_term(QueryOpKind::Or, entity, options);
6182 return *this;
6183 }
6184
6189 template <typename T>
6191
6195 template <typename T>
6197
6198 //------------------------------------------------
6199
6204 QueryImpl& no(Entity entity, const QueryTermOptions& options = QueryTermOptions{}) {
6205 add_entity_term(QueryOpKind::Not, entity, options);
6206 return *this;
6207 }
6208
6213 template <typename T>
6215
6219 template <typename T>
6221
6229 refresh_world_lifetime();
6230 [[maybe_unused]] const bool ok = set_var_name_internal(varEntity, name);
6231 GAIA_ASSERT(ok);
6232 return *this;
6233 }
6238 QueryImpl& var_name(Entity varEntity, const char* name) {
6239 GAIA_ASSERT(name != nullptr);
6240 if (name == nullptr)
6241 return *this;
6242 return var_name(varEntity, util::str_view{name, (uint32_t)GAIA_STRLEN(name, 256)});
6243 }
6244
6250 QueryImpl& set_var(Entity varEntity, Entity value) {
6251 refresh_world_lifetime();
6252 const bool ok = is_query_var_entity(varEntity);
6253 GAIA_ASSERT(ok);
6254 if (!ok)
6255 return *this;
6256
6257 const auto idx = query_var_idx(varEntity);
6258 m_varBindings[idx] = value;
6259 m_varBindingsMask |= (uint8_t(1) << idx);
6260 return *this;
6261 }
6267 refresh_world_lifetime();
6268 const auto varEntity = find_var_by_name(name);
6269 GAIA_ASSERT(varEntity != EntityBad);
6270 if (varEntity == EntityBad)
6271 return *this;
6272 return set_var(varEntity, value);
6273 }
6278 QueryImpl& set_var(const char* name, Entity value) {
6279 GAIA_ASSERT(name != nullptr);
6280 if (name == nullptr)
6281 return *this;
6282 return set_var(util::str_view{name, (uint32_t)GAIA_STRLEN(name, 256)}, value);
6283 }
6284
6290 const bool ok = is_query_var_entity(varEntity);
6291 GAIA_ASSERT(ok);
6292 if (!ok)
6293 return *this;
6294
6295 const auto idx = query_var_idx(varEntity);
6296 m_varBindingsMask &= (uint8_t)~(uint8_t(1) << idx);
6297 return *this;
6298 }
6302 m_varBindingsMask = 0;
6303 return *this;
6304 }
6305
6306 //------------------------------------------------
6307
6312 changed_inter(entity);
6313 return *this;
6314 }
6315
6319 template <typename T>
6321
6322 //------------------------------------------------
6323
6330 QueryImpl& sort_by(Entity entity, TSortByFunc func) {
6331 sort_by_inter(entity, func);
6332 return *this;
6333 }
6334
6340 template <typename T>
6341 QueryImpl& sort_by(TSortByFunc func);
6342
6349 template <typename Rel, typename Tgt>
6350 QueryImpl& sort_by(TSortByFunc func);
6351
6352 //------------------------------------------------
6353
6355 class OrderByTravView final {
6356 QueryImpl* m_query = nullptr;
6357 Entity m_relation = EntityBad;
6358 TravOrder m_order = TravOrder::Down;
6359
6360 public:
6365 OrderByTravView(QueryImpl& query, Entity relation, TravOrder order):
6366 m_query(&query), m_relation(relation), m_order(order) {}
6367
6371 template <typename Func>
6372 void each(Func func) {
6373 m_query->each_walk(func, m_relation, m_order);
6374 }
6375 };
6376
6377 //------------------------------------------------
6378
6393 GAIA_NODISCARD OrderByTravView order_by(Entity relation, TravOrder order) {
6394 return OrderByTravView(*this, relation, order);
6395 }
6396
6402 template <typename Rel>
6403 GAIA_NODISCARD OrderByTravView order_by(TravOrder order);
6404
6405 //------------------------------------------------
6406
6414 QueryImpl& depth_order(Entity relation = ChildOf) {
6415 GAIA_ASSERT(!relation.pair());
6416 GAIA_ASSERT(world_relation_supports_depth_order(*m_storage.world(), relation));
6417 group_by_inter(relation, group_by_func_depth_order, true);
6418 return *this;
6419 }
6420
6424 template <typename Rel>
6426
6427 //------------------------------------------------
6428
6435 QueryImpl& group_by(Entity entity, TGroupByFunc func = group_by_func_default) {
6436 group_by_inter(entity, func);
6437 return *this;
6438 }
6439
6446 template <typename T>
6447 QueryImpl& group_by(TGroupByFunc func = group_by_func_default);
6448
6456 template <typename Rel, typename Tgt>
6457 QueryImpl& group_by(TGroupByFunc func = group_by_func_default);
6458
6459 //------------------------------------------------
6460
6466 group_dep_inter(relation);
6467 return *this;
6468 }
6469
6474 template <typename Rel>
6476
6477 //------------------------------------------------
6478
6482 QueryImpl& group_id(GroupId groupId) {
6483 set_group_id_inter(groupId);
6484 return *this;
6485 }
6486
6491 GAIA_ASSERT(!entity.pair());
6492 set_group_id_inter(entity.id());
6493 return *this;
6494 }
6495
6499 template <typename T>
6501
6507 template <typename Container>
6508 void groups(Container& out, bool sortGroups) {
6509 auto& queryInfo = fetch();
6510 match_all(queryInfo);
6511 queryInfo.group_ids(out, sortGroups);
6512 }
6513
6514 //------------------------------------------------
6515
6529 template <typename Func>
6530 GAIA_NODISCARD SchedJob job(Func func, QueryExecType execType) {
6531#if GAIA_ASSERT_ENABLED
6532 if constexpr (!detail::is_query_iter_callback_v<Func>)
6533 validate_typed_access<Func>();
6534#endif
6535 if constexpr (detail::is_query_iter_callback_v<Func>) {
6536 switch (execType) {
6537 case QueryExecType::Parallel:
6538 return add_iter_parallel_job<Func, QueryExecType::Parallel>(GAIA_MOV(func));
6539 case QueryExecType::ParallelPerf:
6540 return add_iter_parallel_job<Func, QueryExecType::ParallelPerf>(GAIA_MOV(func));
6541 case QueryExecType::ParallelEff:
6542 return add_iter_parallel_job<Func, QueryExecType::ParallelEff>(GAIA_MOV(func));
6543 default:
6544 break;
6545 }
6546 } else {
6547 switch (execType) {
6548 case QueryExecType::Parallel:
6549 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::Parallel>(
6550 TypedJobCallback<Func>{this, GAIA_MOV(func)});
6551 case QueryExecType::ParallelPerf:
6552 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelPerf>(
6553 TypedJobCallback<Func>{this, GAIA_MOV(func)});
6554 case QueryExecType::ParallelEff:
6555 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelEff>(
6556 TypedJobCallback<Func>{this, GAIA_MOV(func)});
6557 default:
6558 break;
6559 }
6560 }
6561
6562 return add_query_task_job(GAIA_MOV(func), execType);
6563 }
6564
6565#if GAIA_ASSERT_ENABLED
6568 template <typename Func>
6569 void validate_typed_access();
6570#endif
6571
6580 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
6581 void each(Func func) {
6582 each_runtime_inter<QueryExecType::Default, Func>(func, Constraints::EnabledOnly);
6583 }
6584
6588 template <typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>, int> = 0>
6589 void each(Func func);
6590
6596 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
6597 void each(Func func, QueryExecType execType) {
6598 each(func, execType, Constraints::EnabledOnly);
6599 }
6600
6605 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
6606 void each(Func func, Constraints constraints) {
6607 each(func, QueryExecType::Default, constraints);
6608 }
6609
6615 template <typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>, int> = 0>
6616 void each(Func func, QueryExecType execType, Constraints constraints) {
6617 switch (execType) {
6618 case QueryExecType::Parallel:
6619 each_runtime_inter<QueryExecType::Parallel, Func>(func, constraints);
6620 break;
6621 case QueryExecType::ParallelPerf:
6622 each_runtime_inter<QueryExecType::ParallelPerf, Func>(func, constraints);
6623 break;
6624 case QueryExecType::ParallelEff:
6625 each_runtime_inter<QueryExecType::ParallelEff, Func>(func, constraints);
6626 break;
6627 default:
6628 each_runtime_inter<QueryExecType::Default, Func>(func, constraints);
6629 break;
6630 }
6631 }
6632
6637 template <typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>, int> = 0>
6638 void each(Func func, QueryExecType execType);
6639
6646 template <typename Func>
6647 void each_iter(Iter& it, Func func);
6648
6650 void each_iter_erased(
6651 QueryExecType execType, void* pFunc, const TypedQueryExecState& state,
6652 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
6653 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
6654
6655 void each_iter_erased(
6656 Iter& it, void* pFunc, const TypedQueryExecState& state,
6657 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
6658 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&));
6660
6661 //------------------------------------------------
6662
6668 template <typename Func>
6669 void each_arch(Func func, Constraints constraints = Constraints::EnabledOnly) {
6670 auto& queryInfo = fetch();
6671 match_all(queryInfo);
6672 run_query_on_archetypes<QueryExecType::Default>(
6673 queryInfo,
6674 [&](Iter& it) {
6675 GAIA_PROF_SCOPE(query_func_a);
6676 it.ctx(m_ctx);
6677#if GAIA_ASSERT_ENABLED
6678 it.set_query_access(&queryInfo.ctx().data);
6679#endif
6680 func(it);
6681 },
6682 constraints);
6683 }
6684
6685 //------------------------------------------------
6686
6696 bool empty(Constraints constraints = Constraints::EnabledOnly) {
6697 auto& queryInfo = fetch();
6698 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6699 return empty_inter<false>(queryInfo, constraints);
6700 }
6701
6702 match_all(queryInfo);
6703
6704 const bool hasFilters = queryInfo.has_filters();
6705 if (hasFilters) {
6706 return empty_inter<true>(queryInfo, constraints);
6707 } else {
6708 return empty_inter<false>(queryInfo, constraints);
6709 }
6710 }
6711
6720 uint32_t count(Constraints constraints = Constraints::EnabledOnly) {
6721 auto& queryInfo = fetch();
6722 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6723 return count_inter<false>(queryInfo, constraints);
6724 }
6725
6726 match_all(queryInfo);
6727
6728 const bool hasFilters = queryInfo.has_filters();
6729 return hasFilters ? count_inter<true>(queryInfo, constraints) : count_inter<false>(queryInfo, constraints);
6730 }
6731
6735 void each_entity_enabled(void* pCtx, void (*func)(void*, Entity)) {
6736 auto& queryInfo = fetch();
6737 match_all(queryInfo);
6738 ::gaia::ecs::update_version(*m_worldVersion);
6739
6740 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6741 auto& world = *queryInfo.world();
6742 if (has_only_direct_or_terms(queryInfo)) {
6743 each_direct_or_union(world, queryInfo, Constraints::EnabledOnly, [&](Entity entity) {
6744 func(pCtx, entity);
6745 return true;
6746 });
6747 } else {
6748 const auto plan = direct_entity_seed_plan(world, queryInfo);
6749 (void)each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](Entity entity) {
6750 func(pCtx, entity);
6751 return true;
6752 });
6753 }
6754
6755 m_changedWorldVersion = *m_worldVersion;
6756 return;
6757 }
6758
6759 const bool hasFilters = queryInfo.has_filters();
6760 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
6761 const auto cacheView = queryInfo.cache_archetype_view();
6762 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, Constraints::EnabledOnly);
6763 if (needsBarrierCache)
6765
6766 const auto cacheRange = selected_query_cache_range(queryInfo);
6767 if (!cacheRange.valid) {
6768 m_changedWorldVersion = *m_worldVersion;
6769 return;
6770 }
6771 const auto idxFrom = cacheRange.idxFrom;
6772 const auto idxTo = cacheRange.idxTo;
6773
6774 Iter it;
6775 it.init_query_state(queryInfo.world(), Constraints::EnabledOnly, false);
6776 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6777 const auto* pArchetype = cacheView[qi];
6778 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
6779 if GAIA_UNLIKELY (!can_process_archetype_inter(
6780 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6781 continue;
6782
6783 const auto& chunks = pArchetype->chunks();
6784 if (!hasEntityFilters) {
6785 for (auto* pChunk: chunks) {
6786 const auto from = Iter::start_index(pChunk);
6787 const auto to = Iter::end_index(pChunk);
6788 if (from == to)
6789 continue;
6790 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6791 continue;
6792
6793 const auto entityCnt = (uint32_t)(to - from);
6794 const auto entities = pChunk->entity_view();
6795 GAIA_FOR(entityCnt) {
6796 func(pCtx, entities[from + i]);
6797 }
6798 }
6799 continue;
6800 }
6801
6802 it.set_archetype(pArchetype);
6803 for (auto* pChunk: chunks) {
6804 it.set_chunk(pChunk);
6805 const auto entityCnt = it.size();
6806 if (entityCnt == 0)
6807 continue;
6808 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6809 continue;
6810
6811 const auto entities = it.view<Entity>();
6812 GAIA_FOR(entityCnt) {
6813 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
6814 func(pCtx, entities[i]);
6815 }
6816 }
6817 }
6818
6819 m_changedWorldVersion = *m_worldVersion;
6820 }
6821
6823 void collect_entities_enabled(cnt::darray<Entity>& out) {
6824 auto& queryInfo = fetch();
6825 match_all(queryInfo);
6826 ::gaia::ecs::update_version(*m_worldVersion);
6827
6828 if (!queryInfo.has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6829 auto& world = *queryInfo.world();
6830 if (has_only_direct_or_terms(queryInfo)) {
6831 each_direct_or_union(world, queryInfo, Constraints::EnabledOnly, [&](Entity entity) {
6832 out.push_back(entity);
6833 return true;
6834 });
6835 } else {
6836 const auto plan = direct_entity_seed_plan(world, queryInfo);
6837 (void)each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](Entity entity) {
6838 out.push_back(entity);
6839 return true;
6840 });
6841 }
6842
6843 m_changedWorldVersion = *m_worldVersion;
6844 return;
6845 }
6846
6847 const bool hasFilters = queryInfo.has_filters();
6848 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
6849 const auto cacheView = queryInfo.cache_archetype_view();
6850 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, Constraints::EnabledOnly);
6851 if (needsBarrierCache)
6853
6854 const auto cacheRange = selected_query_cache_range(queryInfo);
6855 if (!cacheRange.valid) {
6856 m_changedWorldVersion = *m_worldVersion;
6857 return;
6858 }
6859 const auto idxFrom = cacheRange.idxFrom;
6860 const auto idxTo = cacheRange.idxTo;
6861
6862 Iter it;
6863 it.init_query_state(queryInfo.world(), Constraints::EnabledOnly, false);
6864 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6865 const auto* pArchetype = cacheView[qi];
6866 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
6867 if GAIA_UNLIKELY (!can_process_archetype_inter(
6868 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6869 continue;
6870
6871 const auto& chunks = pArchetype->chunks();
6872 if (!hasEntityFilters) {
6873 for (auto* pChunk: chunks) {
6874 const auto from = Iter::start_index(pChunk);
6875 const auto to = Iter::end_index(pChunk);
6876 if (from == to)
6877 continue;
6878 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6879 continue;
6880
6881 const auto oldSize = out.size();
6882 const auto entityCnt = (uint32_t)(to - from);
6883 const auto entities = pChunk->entity_view();
6884 out.resize(oldSize + entityCnt, cnt::ResizePolicy::Scale);
6885 GAIA_FOR(entityCnt) {
6886 out[oldSize + i] = entities[from + i];
6887 }
6888 }
6889 continue;
6890 }
6891
6892 it.set_archetype(pArchetype);
6893 for (auto* pChunk: chunks) {
6894 it.set_chunk(pChunk);
6895 const auto entityCnt = it.size();
6896 if (entityCnt == 0)
6897 continue;
6898 if (hasFilters && !match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6899 continue;
6900
6901 const auto entities = it.view<Entity>();
6902 GAIA_FOR(entityCnt) {
6903 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
6904 out.push_back(entities[i]);
6905 }
6906 }
6907 }
6908
6909 m_changedWorldVersion = *m_worldVersion;
6910 }
6911
6917 template <typename Container>
6918 void arr(Container& outArray, Constraints constraints = Constraints::EnabledOnly);
6919
6926 GAIA_NODISCARD std::span<const Entity> ordered_entities_walk(
6927 QueryInfo& queryInfo, Entity relation, TravOrder order,
6928 Constraints constraints = Constraints::EnabledOnly) {
6929 struct OrderedWalkTargetCtx {
6930 const cnt::darray<Entity>* pEntities = nullptr;
6931 uint32_t cnt = 0;
6932 uint32_t dependentIdx = 0;
6933 cnt::darray<uint32_t>* pIndegree = nullptr;
6934 cnt::darray<uint32_t>* pOutdegree = nullptr;
6935 cnt::darray<uint32_t>* pWriteCursor = nullptr;
6936 cnt::darray<uint32_t>* pEdges = nullptr;
6937 uint32_t* pEdgeCnt = nullptr;
6938
6939 GAIA_NODISCARD static uint32_t
6940 find_entity_idx(const cnt::darray<Entity>& entities, uint32_t cnt, Entity entity) {
6941 const auto targetId = entity.id();
6942 uint32_t low = 0;
6943 uint32_t high = cnt;
6944 while (low < high) {
6945 const uint32_t mid = low + ((high - low) >> 1);
6946 if (entities[mid].id() < targetId)
6947 low = mid + 1;
6948 else
6949 high = mid;
6950 }
6951
6952 if (low < cnt && entities[low].id() == targetId)
6953 return low;
6954 return cnt;
6955 }
6956
6957 static void count_edge(void* rawCtx, Entity dependency) {
6958 auto& ctx = *static_cast<OrderedWalkTargetCtx*>(rawCtx);
6959 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6960 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6961 return;
6962
6963 ++(*ctx.pOutdegree)[dependencyIdx];
6964 ++(*ctx.pIndegree)[ctx.dependentIdx];
6965 ++*ctx.pEdgeCnt;
6966 }
6967
6968 static void write_edge(void* rawCtx, Entity dependency) {
6969 auto& ctx = *static_cast<OrderedWalkTargetCtx*>(rawCtx);
6970 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6971 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6972 return;
6973
6974 (*ctx.pEdges)[(*ctx.pWriteCursor)[dependencyIdx]++] = ctx.dependentIdx;
6975 }
6976 };
6977
6978 auto& walkData = ensure_each_walk_data();
6979 auto& world = *m_storage.world();
6980 const uint32_t relationVersion = world_rel_version(world, relation);
6981 const uint32_t worldVersion = ::gaia::ecs::world_version(world);
6982 const uint32_t resultCacheRevision = queryInfo.result_cache_rev();
6983
6984 const bool needsTraversalBarrierState =
6985 constraints == Constraints::EnabledOnly && ::gaia::ecs::valid(world, relation);
6986 auto survives_disabled_barrier = [&](Entity entity) {
6987 if (!needsTraversalBarrierState)
6988 return true;
6989
6990 auto curr = entity;
6991 GAIA_FOR(MAX_TRAV_DEPTH) {
6992 const auto next = target(world, curr, relation);
6993 if (next == EntityBad || next == curr)
6994 break;
6995 if (!world_entity_enabled(world, next))
6996 return false;
6997 curr = next;
6998 }
6999
7000 return true;
7001 };
7002
7003 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
7004 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
7005 walkData.cachedEntityVersion == worldVersion &&
7006 walkData.cachedResultCacheRevision == resultCacheRevision && !queryInfo.has_filters()) {
7007 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
7008 }
7009
7010 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
7011 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
7012 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
7013 !queryInfo.has_filters()) {
7014 auto& chunks = walkData.scratchChunks;
7015 chunks.clear();
7016
7017 bool chunkChanged = false;
7018 for (auto* pArchetype: queryInfo) {
7019 if (pArchetype == nullptr || !can_process_archetype(queryInfo, *pArchetype))
7020 continue;
7021
7022 for (const auto* pChunk: pArchetype->chunks()) {
7023 if (pChunk == nullptr)
7024 continue;
7025
7026 chunks.push_back(pChunk);
7027 if (!chunkChanged && pChunk->changed(walkData.cachedEntityVersion))
7028 chunkChanged = true;
7029 }
7030 }
7031
7032 bool sameChunks = chunks.size() == walkData.cachedChunks.size();
7033 if (sameChunks) {
7034 for (uint32_t i = 0; i < (uint32_t)chunks.size(); ++i) {
7035 if (chunks[i] != walkData.cachedChunks[i]) {
7036 sameChunks = false;
7037 break;
7038 }
7039 }
7040 }
7041
7042 if (sameChunks && !chunkChanged) {
7043 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
7044 }
7045 }
7046
7047 auto& entities = walkData.scratchEntities;
7048 entities.clear();
7049 arr(entities, constraints);
7050 if (entities.empty())
7051 return {};
7052
7053 if (needsTraversalBarrierState) {
7054 uint32_t writeIdx = 0;
7055 const auto cnt = (uint32_t)entities.size();
7056 GAIA_FOR(cnt) {
7057 const auto entity = entities[i];
7058 if (!survives_disabled_barrier(entity))
7059 continue;
7060 entities[writeIdx++] = entity;
7061 }
7062 entities.resize(writeIdx);
7063 if (entities.empty())
7064 return {};
7065 }
7066
7067 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
7068 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
7069 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
7070 entities.size() == walkData.cachedInput.size()) {
7071 bool sameInput = true;
7072 for (uint32_t i = 0; i < (uint32_t)entities.size(); ++i) {
7073 if (entities[i] != walkData.cachedInput[i]) {
7074 sameInput = false;
7075 break;
7076 }
7077 }
7078
7079 if (sameInput) {
7080 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
7081 }
7082 }
7083
7084 auto& ordered = walkData.cachedOutput;
7085 walkData.cachedInput = entities;
7086 ordered.clear();
7087 if (!::gaia::ecs::valid(world, relation)) {
7088 core::sort(entities, SortEntityIdCond{});
7089 ordered = entities;
7090 } else {
7091 // Keep execution deterministic regardless of archetype iteration order.
7092 core::sort(entities, SortEntityIdCond{});
7093
7094 const auto cnt = (uint32_t)entities.size();
7095
7096 auto& indegree = walkData.scratchIndegree;
7097 indegree.resize(cnt);
7098 auto& outdegree = walkData.scratchOutdegree;
7099 outdegree.resize(cnt);
7100 for (uint32_t i = 0; i < cnt; ++i) {
7101 indegree[i] = 0;
7102 outdegree[i] = 0;
7103 }
7104
7105 uint32_t edgeCnt = 0;
7106 OrderedWalkTargetCtx edgeCtx;
7107 edgeCtx.pEntities = &entities;
7108 edgeCtx.cnt = cnt;
7109 edgeCtx.pIndegree = &indegree;
7110 edgeCtx.pOutdegree = &outdegree;
7111 edgeCtx.pEdgeCnt = &edgeCnt;
7112 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
7113 const auto dependent = entities[dependentIdx];
7114 edgeCtx.dependentIdx = dependentIdx;
7115 world_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::count_edge);
7116 }
7117
7118 auto& offsets = walkData.scratchOffsets;
7119 offsets.resize(cnt + 1);
7120 offsets[0] = 0;
7121 for (uint32_t i = 0; i < cnt; ++i)
7122 offsets[i + 1] = offsets[i] + outdegree[i];
7123
7124 auto& writeCursor = walkData.scratchWriteCursor;
7125 writeCursor.resize(cnt);
7126 for (uint32_t i = 0; i < cnt; ++i)
7127 writeCursor[i] = offsets[i];
7128
7129 auto& edges = walkData.scratchEdges;
7130 edges.resize(edgeCnt);
7131 edgeCtx.pWriteCursor = &writeCursor;
7132 edgeCtx.pEdges = &edges;
7133 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
7134 const auto dependent = entities[dependentIdx];
7135 edgeCtx.dependentIdx = dependentIdx;
7136 world_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::write_edge);
7137 }
7138
7139 ordered.reserve(cnt);
7140
7141 const bool isUp = order == TravOrder::Up || order == TravOrder::ReverseUp;
7142 const bool needsReverse = order == TravOrder::ReverseUp || order == TravOrder::ReverseDown;
7143
7144 auto& visited = walkData.scratchWriteCursor;
7145 visited.resize(cnt);
7146 for (uint32_t i = 0; i < cnt; ++i)
7147 visited[i] = 0;
7148
7149 auto& stack = walkData.scratchCurrLevel;
7150 stack.clear();
7151 auto& cursorStack = walkData.scratchNextLevel;
7152 cursorStack.clear();
7153
7154 auto append_from_root = [&](uint32_t rootIdx) {
7155 stack.push_back(rootIdx);
7156 cursorStack.push_back(offsets[rootIdx]);
7157
7158 while (!stack.empty()) {
7159 const auto idx = stack.back();
7160 if (visited[idx] == 0) {
7161 visited[idx] = 1;
7162 if (!isUp)
7163 ordered.push_back(entities[idx]);
7164 }
7165
7166 bool pushedChild = false;
7167 auto& cursor = cursorStack.back();
7168 while (cursor < offsets[idx + 1]) {
7169 const auto childIdx = edges[cursor++];
7170 if (visited[childIdx] != 0)
7171 continue;
7172
7173 stack.push_back(childIdx);
7174 cursorStack.push_back(offsets[childIdx]);
7175 pushedChild = true;
7176 break;
7177 }
7178
7179 if (pushedChild)
7180 continue;
7181
7182 if (isUp)
7183 ordered.push_back(entities[idx]);
7184 visited[idx] = 2;
7185 stack.pop_back();
7186 cursorStack.pop_back();
7187 }
7188 };
7189
7190 for (uint32_t i = 0; i < cnt; ++i) {
7191 if (indegree[i] == 0 && visited[i] == 0)
7192 append_from_root(i);
7193 }
7194
7195 // Cycles are invalid relationship data, but keep traversal deterministic by visiting leftovers by id.
7196 for (uint32_t i = 0; i < cnt; ++i) {
7197 if (visited[i] == 0)
7198 append_from_root(i);
7199 }
7200
7201 if (needsReverse) {
7202 const auto orderedCnt = (uint32_t)ordered.size();
7203 for (uint32_t i = 0; i < orderedCnt / 2; ++i)
7204 core::swap(ordered[i], ordered[orderedCnt - i - 1]);
7205 }
7206 }
7207
7208 walkData.cachedRelation = relation;
7209 walkData.cachedOrder = order;
7210 walkData.cachedConstraints = constraints;
7211 walkData.cachedRelationVersion = relationVersion;
7212 walkData.cachedEntityVersion = ::gaia::ecs::world_version(world);
7213 walkData.cachedResultCacheRevision = resultCacheRevision;
7214 walkData.cachedRuns.clear();
7215
7216 {
7217 const auto orderedCnt = (uint32_t)ordered.size();
7218 if (orderedCnt != 0) {
7219 for (uint32_t i = 0; i < orderedCnt; ++i) {
7220 const auto& ec = ::gaia::ecs::fetch(world, ordered[i]);
7221 if (walkData.cachedRuns.empty()) {
7222 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
7223 continue;
7224 }
7225
7226 auto& run = walkData.cachedRuns.back();
7227 if (ec.pChunk == run.pChunk && ec.row == run.to) {
7228 run.to = (uint16_t)(run.to + 1);
7229 } else {
7230 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
7231 }
7232 }
7233 }
7234 }
7235
7236 if (!queryInfo.has_filters()) {
7237 auto& chunks = walkData.scratchChunks;
7238 chunks.clear();
7239 for (auto* pArchetype: queryInfo) {
7240 if (pArchetype == nullptr || !can_process_archetype(queryInfo, *pArchetype))
7241 continue;
7242
7243 for (const auto* pChunk: pArchetype->chunks()) {
7244 if (pChunk == nullptr)
7245 continue;
7246 chunks.push_back(pChunk);
7247 }
7248 }
7249 walkData.cachedChunks = chunks;
7250 } else
7251 walkData.cachedChunks.clear();
7252 walkData.cacheValid = true;
7253
7254 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
7255 }
7256
7266 template <typename Func, std::enable_if_t<detail::is_query_walk_core_callback_v<Func>, int> = 0>
7268 Func func, Entity relation, TravOrder order = TravOrder::Down,
7269 Constraints constraints = Constraints::EnabledOnly) {
7270 auto& queryInfo = fetch();
7271 match_all(queryInfo);
7272 const auto ordered = ordered_entities_walk(queryInfo, relation, order, constraints);
7273
7274 if constexpr (std::is_invocable_v<Func, Iter&>) {
7275 each_direct_entities_iter(queryInfo, ordered, constraints, func);
7276 } else if constexpr (std::is_invocable_v<Func, const Entity&> || std::is_invocable_v<Func, Entity>) {
7277 for (const auto entity: ordered)
7278 func(entity);
7279 }
7280 }
7281
7288 template <typename Func, std::enable_if_t<!detail::is_query_walk_core_callback_v<Func>, int> = 0>
7290 Func func, Entity relation, TravOrder order = TravOrder::Down,
7291 Constraints constraints = Constraints::EnabledOnly);
7292
7293 //------------------------------------------------
7294
7296 void diag() {
7297 // Make sure matching happened
7298 auto& queryInfo = fetch();
7299 match_all(queryInfo);
7300 if (uses_shared_cache_layer())
7301 GAIA_LOG_N("BEG DIAG Query %u.%u [S]", id(), gen());
7302 else if (uses_query_cache_storage())
7303 GAIA_LOG_N("BEG DIAG Query %u.%u [L]", id(), gen());
7304 else
7305 GAIA_LOG_N("BEG DIAG Query [U]");
7306 for (const auto* pArchetype: queryInfo)
7307 Archetype::diag_basic_info(*m_storage.world(), *pArchetype);
7308 GAIA_LOG_N("END DIAG Query");
7309 }
7310
7313 GAIA_NODISCARD util::str bytecode() {
7314 auto& queryInfo = fetch();
7315 return queryInfo.bytecode();
7316 }
7317
7320 const auto dump = bytecode();
7321 if (uses_shared_cache_layer())
7322 GAIA_LOG_N("BEG DIAG Query Bytecode %u.%u [S]", id(), gen());
7323 else if (uses_query_cache_storage())
7324 GAIA_LOG_N("BEG DIAG Query Bytecode %u.%u [L]", id(), gen());
7325 else
7326 GAIA_LOG_N("BEG DIAG Query Bytecode [U]");
7327 GAIA_LOG_N("%.*s", (int)dump.size(), dump.data());
7328 GAIA_LOG_N("END DIAG Query");
7329 }
7330 };
7331 } // namespace detail
7332
7334 using Query = detail::QueryImpl;
7335 } // namespace ecs
7336} // namespace gaia
7337
7338#include "gaia/ecs/query_builder_typed.inl"
7339#include "gaia/ecs/query_typed.inl"
Array of elements of type.
Definition darray_ext_impl.h:31
GAIA_NODISCARD size_type size() const noexcept
Returns the number of elements.
Definition darray_ext_impl.h:561
void push_back(const T &arg)
Appends an element.
Definition darray_ext_impl.h:360
void resize(size_type count, ResizePolicy policy=ResizePolicy::Exact)
Changes the number of elements.
Definition darray_ext_impl.h:289
void clear() noexcept
Removes all elements.
Definition darray_ext_impl.h:518
GAIA_NODISCARD bool empty() const noexcept
Checks whether the container has no elements.
Definition darray_ext_impl.h:567
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:800
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:1326
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:861
GAIA_NODISCARD bool is_req_del() const
Returns true if this archetype is requested to be deleted.
Definition archetype.h:1231
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:847
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:797
GAIA_NODISCARD EntitySpan ids_view() const
Span over the component and entity identifiers held by this chunk.
Definition chunk.h:809
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:1793
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:1772
GAIA_NODISCARD bool empty() const
Checks is there are any entities in the chunk.
Definition chunk.h:1744
Binds the calling thread to a work-item deferral slot for the lifetime of the scope.
Definition api.h:197
Iterator for iterating entity subsets selected by Constraints. Disabled entities always precede enabl...
Definition chunk_iterator.h:2077
static GAIA_NODISCARD uint16_t size(Chunk *pChunk) noexcept
Returns the number of enabled rows in a chunk.
Definition chunk_iterator.h:2123
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:2116
static GAIA_NODISCARD uint16_t start_index(Chunk *pChunk) noexcept
Returns the first enabled row in a chunk.
Definition chunk_iterator.h:2109
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:2651
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:2670
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:2660
GAIA_NODISCARD util::str bytecode() const
Returns a textual dump of the compiled VM bytecode.
Definition query_info.h:2455
GAIA_NODISCARD std::span< const Archetype * > cache_archetype_view() const
Returns the cached result archetypes as a span.
Definition query_info.h:2713
GAIA_NODISCARD bool has_inherited_data_payload() const
Returns true when query iteration needs cached inherited data payloads.
Definition query_info.h:1951
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:2524
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:2473
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:2421
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:2480
GAIA_NODISCARD QueryCtx & ctx()
Returns the mutable compiled query context.
Definition query_info.h:2444
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:2573
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:2621
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:6355
OrderByTravView(QueryImpl &query, Entity relation, TravOrder order)
Creates a query traversal view.
Definition query.h:6365
void each(Func func)
Iterates the query result through the requested relation order.
Definition query.h:6372
Records OnSet notifications and sorted-query invalidations produced by a parallel region instead of a...
Definition query.h:2178
Binds a parallel work-item range to its shared deferred-notification slot for the lifetime of the sco...
Definition query.h:2200
Builds, caches, and executes a Gaia-ECS query.
Definition query.h:539
QueryImpl & sort_by(Entity entity, TSortByFunc func)
Sorts the query by the specified entity and function.
Definition query.h:6330
static void run_query_func(World *pWorld, Func func, ChunkBatch &batch)
Executes an iterator callback for one prepared chunk batch.
Definition query.h:2381
static void invoke_runtime_iter(void *pFunc, TIter &it)
Invokes a type-erased public iterator callback.
Definition query.h:4027
QueryImpl & scope(QueryCacheScope cacheScope)
Sets the cache scope used by cached queries.
Definition query.h:1354
QueryImpl & main_thread(bool required=true)
Marks whether this query must run on the main thread/serial path.
Definition query.h:1224
GAIA_NODISCARD QueryAccess access(Entity entity)
Returns the effective read/write access for an id.
Definition query.h:1304
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:2006
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:2099
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:5698
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:3836
QueryImpl & set_var(util::str_view name, Entity value)
Binds a named query variable to a concrete entity value.
Definition query.h:6266
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:2088
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:7313
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:6616
static bool SilenceInvalidCacheKindAssertions
Suppresses assertions when an invalid cache-kind combination is queried for diagnostics.
Definition query.h:1034
QueryImpl & group_dep(Entity relation)
Declares an explicit relation dependency for grouped cache invalidation. Useful for custom group_by c...
Definition query.h:6465
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:2423
QueryImpl & reads()
Declares an additional component or pair type read by this query callback.
Definition query.h:1259
GAIA_NODISCARD uint32_t gen() const
Returns the cache handle generation of this query.
Definition query.h:5689
GAIA_NODISCARD SchedJob job(Func func, QueryExecType execType)
Adds a query execution job without submitting it.
Definition query.h:6530
QueryImpl & kind(QueryCacheKind cacheKind)
Sets the hard cache-kind requirement for the query.
Definition query.h:1339
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:1133
QueryImpl & set_var(const char *name, Entity value)
Binds a named query variable to a concrete entity value.
Definition query.h:6278
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:2129
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:6669
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:4161
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:1331
static GAIA_NODISCARD constexpr Constraints iter_mode_constraints()
Maps an iteration-mode tag to the runtime row constraints used by iterators.
Definition query.h:2362
ExecPayloadKind
Runtime payload layout required by generic chunk-batch execution.
Definition query.h:2256
@ 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:4099
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:6250
GAIA_NODISCARD const char * kind_error_str()
Returns a human-readable description of the current kind validation result.
Definition query.h:1394
GAIA_NODISCARD uint16_t cache_src_trav() const
Returns the traversed-source snapshot cap. 0 disables explicit traversed-source snapshot caching.
Definition query.h:1180
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:6508
GAIA_NODISCARD bool is_cached() const
Returns whether the query is stored in the query cache.
Definition query.h:5719
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:3928
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:6393
QueryPlanFlags
Orthogonal flags attached to a prepared query plan.
Definition query.h:2308
@ QueryPlanFlag_Sorted
The plan may need sorted cache slices. Runners use them only with non-trivial payload.
Definition query.h:2320
@ QueryPlanFlag_Filtered
The query has per-chunk filters such as changed terms.
Definition query.h:2312
@ QueryPlanFlag_InheritedPayload
The query carries inherited component data into iterator payloads.
Definition query.h:2316
@ QueryPlanFlag_None
No additional query-plan properties are present.
Definition query.h:2310
@ QueryPlanFlag_BarrierCache
The plan must use the depth-order hierarchy barrier cache when checking archetype/row ranges.
Definition query.h:2322
@ QueryPlanFlag_Grouped
The query uses grouped payload/ranges or grouped cache ordering.
Definition query.h:2318
@ QueryPlanFlag_EntityFilter
The query has entity-filter terms that require per-entity rechecks.
Definition query.h:2314
QueryImpl & add(const char *str,...)
Creates a query from a null-terminated expression string.
Definition query.h:5766
QueryImpl & is(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds a semantic Is(entity) requirement.
Definition query.h:6059
QueryImpl & reads(Entity entity)
Declares an additional id read by this query callback.
Definition query.h:1248
QueryImpl & in(Entity entity, QueryTermOptions options=QueryTermOptions{})
Adds an inherited in(entity) requirement.
Definition query.h:6069
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:1161
void diag_bytecode()
Prints a textual dump of the generated query VM bytecode.
Definition query.h:7319
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:6289
QueryImpl & writes(Entity entity)
Declares an additional id written by this query callback.
Definition query.h:1271
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:4197
GAIA_NODISCARD std::span< const Entity > custom_writes() const
Returns explicitly declared write ids that are not query terms.
Definition query.h:1294
QueryImpl & depth_order(Entity relation=ChildOf)
Orders cached query entries by fragmenting relation depth so iteration runs breadth-first top-down....
Definition query.h:6414
GAIA_NODISCARD bool main_thread_required() const
Returns whether this query must run on the main thread/serial path.
Definition query.h:1231
QueryImpl & add(QueryInput item)
Adds a prebuilt query input item.
Definition query.h:6047
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:4181
QueryImpl & any(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds an optional entity or pair term.
Definition query.h:6155
QueryImpl & no_access()
Makes the preceding all(), or_(), or any() term presence-only. Call while building the query,...
Definition query.h:6080
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:6228
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:2061
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:6606
QueryImpl & ctx(void *pCtx)
Sets the user-owned context pointer visible through Iter::ctx() during iterator callbacks....
Definition query.h:1201
void each(Func func, QueryExecType execType)
Iterates query matches using the selected execution mode.
Definition query.h:6597
QueryImpl & var_name(Entity varEntity, const char *name)
Assigns a human-readable name to a query variable entity (Var0..Var7).
Definition query.h:6238
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:2269
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:4149
void each(Func func)
Iterates query matches using the default execution mode. Each callback receives a contiguous chunk wi...
Definition query.h:6581
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:4171
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:2115
QueryImpl & match_prefab()
Makes the query include prefab entities in matches.
Definition query.h:1368
GAIA_NODISCARD std::span< const Entity > custom_reads() const
Returns explicitly declared read ids that are not query terms.
Definition query.h:1288
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:2077
void diag()
Run diagnostics.
Definition query.h:7296
QueryImpl & group_id(Entity entity)
Selects the group to iterate over.
Definition query.h:6490
void match_all(QueryInfo &queryInfo)
Matches the query against all relevant archetypes.
Definition query.h:1094
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:4114
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:4189
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:3993
GAIA_NODISCARD QueryCacheScope scope() const
Returns the currently requested cache scope.
Definition query.h:1376
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:6926
QueryImpl & changed(Entity entity)
Marks a runtime component or pair for changed() filtering.
Definition query.h:6311
uint32_t count(Constraints constraints=Constraints::EnabledOnly)
Calculates the number of entities matching the query.
Definition query.h:6720
void destroy()
Destroys the current cached query state and local scratch data.
Definition query.h:5708
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:1961
GAIA_NODISCARD bool conflicts_with(QueryImpl &other)
Returns whether this query conflicts with another query's effective access declarations.
Definition query.h:1317
bool empty(Constraints constraints=Constraints::EnabledOnly)
Returns whether no entities match the query.
Definition query.h:6696
void each_entity_enabled(void *pCtx, void(*func)(void *, Entity))
Iterates matching enabled entities through a non-template erased callback.
Definition query.h:6735
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:7267
QueryImpl & all(Entity entity, const QueryTermOptions &options=QueryTermOptions{})
Adds a required entity or pair term.
Definition query.h:6131
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:1208
QueryImpl & clear_vars()
Clears all runtime variable bindings.
Definition query.h:6301
QueryPlanMode
Prepared query runner mode shared by typed callbacks and public Iter callbacks.
Definition query.h:2284
@ 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:6204
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:1039
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:2155
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:2403
GAIA_NODISCARD QueryKindRes kind_error()
Returns the validation result for the current query shape and requested kind.
Definition query.h:1388
GAIA_NODISCARD bool valid()
Returns whether the current query shape satisfies the requested kind.
Definition query.h:1400
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:6435
GAIA_NODISCARD bool match_one(QueryInfo &queryInfo, const Archetype &archetype, EntitySpan targetEntities)
Matches the query against a single archetype.
Definition query.h:1120
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:6180
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:6482
GAIA_NODISCARD QueryCacheKind kind() const
Returns the currently requested cache kind.
Definition query.h:1382
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:5659
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:1148
GAIA_NODISCARD QueryId id() const
Returns the cache handle id of this query.
Definition query.h:5681
QueryImpl & writes()
Declares an additional component or pair type written by this query callback.
Definition query.h:1282
Wrapper for two Entities forming a relationship pair. Conversion to Entity stores only id() of each e...
Definition id.h:559
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:197
Same API as ser_buffer_binary, but backed by fully dynamic storage.
Definition ser_buffer_binary.h:164
Compile-time serialization entry points.
Identifier of an entity or component instance in the world. Packs the entity index,...
Definition id.h:231
GAIA_NODISCARD constexpr auto gen() const noexcept
Generation index of the entity.
Definition id.h:301
GAIA_NODISCARD constexpr bool pair() const noexcept
Whether this id refers to a relationship pair.
Definition id.h:313
GAIA_NODISCARD constexpr auto id() const noexcept
Entity index in the entity array.
Definition id.h:295
Explicit component/entity access declarations used for scheduling decisions.
Definition query_common.h:677
GAIA_NODISCARD QueryAccess access(Entity entity) const
Returns explicitly declared access for an id.
Definition query_common.h:732
GAIA_NODISCARD std::span< const Entity > writes_view() const
Returns the explicitly declared write ids.
Definition query_common.h:697
void add_read(Entity entity)
Declares that an id is read.
Definition query_common.h:703
GAIA_NODISCARD std::span< const Entity > reads_view() const
Returns the explicitly declared read ids.
Definition query_common.h:691
bool hasPairPatterns
At least one custom scheduling id has wildcard or variable pair endpoints.
Definition query_common.h:687
void add_write(Entity entity)
Declares that an id is written.
Definition query_common.h:717
DependencyFlags flags
Combined DependencyFlags describing the compiled query shape.
Definition query_common.h:1046
Compact compiled query payload used by matching, identity, and cache maintenance.
Definition query_common.h:1024
TGroupByFunc groupByFunc
Function to use to perform the grouping.
Definition query_common.h:1159
Entity groupBy
Entity to group the archetypes by. EntityBad for no grouping.
Definition query_common.h:1157
GAIA_NODISCARD std::span< const uint8_t > changed_fields_view() const
Returns query-term indices matching changed-filter components.
Definition query_common.h:1226
GAIA_NODISCARD std::span< const QueryTerm > terms_view() const
Returns compiled terms in execution order.
Definition query_common.h:1312
Dependencies deps
Explicit dependency metadata derived from query shape.
Definition query_common.h:1196
TSortByFunc sortByFunc
Function to use to perform sorting.
Definition query_common.h:1155
uint16_t flags
Query flags.
Definition query_common.h:1180
GAIA_NODISCARD std::span< const Entity > changed_view() const
Returns changed-filter component ids.
Definition query_common.h:1220
Authored and compiled state defining query identity and execution behavior.
Definition query_common.h:897
struct gaia::ecs::QueryCtx::Data data
Compiled query payload.
@ OrderGroups
Grouped archetypes are ordered by group identifier during cache refresh.
Definition query_common.h:928
@ HasVariableTerms
Query contains variable-based lookup terms.
Definition query_common.h:922
@ HasSourceTerms
Query contains fixed-source lookup terms.
Definition query_common.h:920
@ DependencyHasWildcardTerms
At least one wildcard id or pair term is present.
Definition query_common.h:1008
@ DependencyHasSourceTerms
At least one term uses a fixed source entity.
Definition query_common.h:998
@ DependencyHasVariableTerms
At least one term contains a runtime variable.
Definition query_common.h:1000
@ DependencyHasTraversalTerms
At least one source term traverses a relation.
Definition query_common.h:1014
DirectTargetEvalKind
Specialized evaluation shape for concrete target entities.
Definition query_common.h:970
@ SingleAllSemanticIsMixed
Evaluates one required semantic Is term before remaining direct terms.
@ SingleAllDirectMixed
Evaluates one required direct-storage term before remaining direct terms.
@ SingleAllDirectMixedDirect
Evaluates one required direct-storage term before remaining direct-storage terms.
@ SingleAllInheritedMixed
Evaluates one required inherited-data term before remaining direct terms.
@ Generic
Uses the general compiled query evaluator.
@ SingleAllSemanticIs
Evaluates one required semantic Is term.
@ SingleAllInherited
Evaluates one required term through inherited component data.
@ SingleAllInIsMixed
Evaluates one required inherited-inclusive Is term before remaining direct terms.
@ 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:936
void init(World *pWorld)
Attaches the query context to a world and its component cache.
Definition query_common.h:1578
User-provided query input.
Definition query_common.h:478
Additional options for query terms. This can be used to configure source lookup, traversal and access...
Definition query_common.h:509
QueryTermOptions & write()
Requests mutable access to the term.
Definition query_common.h:648
Internal representation of QueryInput.
Definition query_common.h:742
Entity id
Queried id.
Definition query_common.h:744
QueryMatchKind matchKind
Match semantics for this term.
Definition query_common.h:754
Entity entTrav
Optional traversal relation for source lookups.
Definition query_common.h:748
Entity src
Source of where the queried id is looked up at.
Definition query_common.h:746
Orders entities by id only, ignoring the generation and the other packed bits. Sorts that need this o...
Definition id.h:369
Tag selecting unconstrained row iteration for prepared query iteration.
Definition query.h:2356
Tag selecting disabled-only row constraints for prepared query iteration.
Definition query.h:2354
Tag selecting enabled-only row constraints for prepared query iteration.
Definition query.h:2352
Cache range selected by the query's optional group id filter.
Definition query.h:2340
bool hasSelectedGroup
True when m_groupIdSet narrowed the range to one cache group.
Definition query.h:2346
uint32_t idxTo
One-past-the-end cached archetype index to process.
Definition query.h:2344
bool valid
False when the selected group id is absent from the matched cache.
Definition query.h:2348
uint32_t idxFrom
First cached archetype index to process.
Definition query.h:2342
Prepared query execution metadata shared by typed callbacks and public Iter callbacks.
Definition query.h:2326
uint32_t idxTo
One-past-the-end cached archetype index to process.
Definition query.h:2336
QueryPlanMode mode
Runner family selected for the current matched query cache.
Definition query.h:2328
uint32_t idxFrom
First cached archetype index to process.
Definition query.h:2334
ExecPayloadKind payloadKind
Payload layout required by generic chunk-batch runners independent of sorted-cache availability.
Definition query.h:2332
uint8_t flags
Orthogonal plan properties such as filtering, entity filters, grouping, or payload requirements.
Definition query.h:2330
Detects whether a type is a relationship pair.
Definition id.h:220
static constexpr bool value
True when the type derives from the pair base.
Definition id.h:222
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