Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
query.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cstdarg>
5#include <cstdint>
6#include <type_traits>
7
8#include "gaia/cnt/darray.h"
9#include "gaia/cnt/map.h"
10#include "gaia/cnt/sarray_ext.h"
11#include "gaia/config/profiler.h"
12#include "gaia/core/hashing_policy.h"
13#include "gaia/core/string.h"
14#include "gaia/core/utility.h"
15#include "gaia/ecs/api.h"
16#include "gaia/ecs/archetype.h"
17#include "gaia/ecs/archetype_common.h"
18#include "gaia/ecs/chunk.h"
19#include "gaia/ecs/chunk_iterator.h"
20#include "gaia/ecs/common.h"
21#include "gaia/ecs/component.h"
22#include "gaia/ecs/component_cache.h"
23#include "gaia/ecs/id.h"
24#include "gaia/ecs/query_cache.h"
25#include "gaia/ecs/query_common.h"
26#include "gaia/ecs/query_info.h"
27#include "gaia/mt/threadpool.h"
28#include "gaia/ser/ser_buffer_binary.h"
29#include "gaia/ser/ser_ct.h"
30
31namespace gaia {
32 namespace ecs {
33 class World;
34
35 enum class QueryExecType : uint32_t {
36 // Main thread
37 Serial,
38 // Parallel, any core
39 Parallel,
40 // Parallel, perf cores only
41 ParallelPerf,
42 // Parallel, efficiency cores only
43 ParallelEff,
44 // Default execution type
45 Default = Serial,
46 };
47
48 namespace detail {
50 enum QueryCmdType : uint8_t { ADD_ITEM, ADD_FILTER, SORT_BY, GROUP_BY, SET_GROUP };
51
53 static constexpr QueryCmdType Id = QueryCmdType::ADD_ITEM;
54 static constexpr bool InvalidatesHash = true;
55
56 QueryInput item;
57
58 void exec(QueryCtx& ctx) const {
59 auto& ctxData = ctx.data;
60
61#if GAIA_DEBUG
62 // Unique component ids only
63 GAIA_ASSERT(!core::has(ctxData.ids_view(), item.id));
64
65 // There's a limit to the amount of query items which we can store
66 if (ctxData.idsCnt >= MAX_ITEMS_IN_QUERY) {
67 GAIA_ASSERT2(false, "Trying to create a query with too many components!");
68
69 const auto* name = ctx.cc->get(item.id).name.str();
70 GAIA_LOG_E("Trying to add component '%s' to an already full ECS query!", name);
71 return;
72 }
73#endif
74
75 // Build the read-write mask.
76 // This will be used to determine what kind of access the user wants for a given component.
77 const uint8_t isReadWrite = uint8_t(item.access == QueryAccess::Write);
78 ctxData.readWriteMask |= (isReadWrite << ctxData.idsCnt);
79
80 // The query engine is going to reorder the query items as necessary.
81 // Remapping is used so the user can still identify the items according the order in which
82 // they defined them when building the query.
83 ctxData._remapping[ctxData.idsCnt] = ctxData.idsCnt;
84
85 ctxData._ids[ctxData.idsCnt] = item.id;
86 ctxData._terms[ctxData.idsCnt] = {item.id, item.src, nullptr, item.op};
87 ++ctxData.idsCnt;
88 }
89 };
90
92 static constexpr QueryCmdType Id = QueryCmdType::ADD_FILTER;
93 static constexpr bool InvalidatesHash = true;
94
95 Entity comp;
96
97 void exec(QueryCtx& ctx) const {
98 auto& ctxData = ctx.data;
99
100#if GAIA_DEBUG
101 GAIA_ASSERT(core::has(ctxData.ids_view(), comp));
102 GAIA_ASSERT(!core::has(ctxData.changed_view(), comp));
103
104 // There's a limit to the amount of components which we can store
105 if (ctxData.changedCnt >= MAX_ITEMS_IN_QUERY) {
106 GAIA_ASSERT2(false, "Trying to create an filter query with too many components!");
107
108 const auto* compName = ctx.cc->get(comp).name.str();
109 GAIA_LOG_E("Trying to add component %s to an already full filter query!", compName);
110 return;
111 }
112
113 uint32_t compIdx = 0;
114 for (; compIdx < ctxData.idsCnt; ++compIdx)
115 if (ctxData._ids[compIdx] == comp)
116 break;
117
118 // NOTE: Code bellow does the same as this commented piece.
119 // However, compilers can't quite optimize it as well because it does some more
120 // calculations. This is used often so go with the custom code.
121 // const auto compIdx = core::get_index_unsafe(ids, comp);
122
123 // Component has to be present in anyList or allList.
124 // NoneList makes no sense because we skip those in query processing anyway.
125 GAIA_ASSERT2(ctxData._terms[compIdx].op != QueryOpKind::Not, "Filtering by NOT doesn't make sense!");
126 if (ctxData._terms[compIdx].op != QueryOpKind::Not) {
127 ctxData._changed[ctxData.changedCnt++] = comp;
128 return;
129 }
130
131 const auto* compName = ctx.cc->get(comp).name.str();
132 GAIA_LOG_E("SetChangeFilter trying to filter component %s but it's not a part of the query!", compName);
133#else
134 ctxData._changed[ctxData.changedCnt++] = comp;
135#endif
136 }
137 };
138
140 static constexpr QueryCmdType Id = QueryCmdType::SORT_BY;
141 static constexpr bool InvalidatesHash = true;
142
143 Entity sortBy;
144 TSortByFunc func;
145
146 void exec(QueryCtx& ctx) const {
147 auto& ctxData = ctx.data;
148 ctxData.sortBy = sortBy;
149 GAIA_ASSERT(func != nullptr);
150 ctxData.sortByFunc = func;
151 }
152 };
153
155 static constexpr QueryCmdType Id = QueryCmdType::GROUP_BY;
156 static constexpr bool InvalidatesHash = true;
157
158 Entity groupBy;
159 TGroupByFunc func;
160
161 void exec(QueryCtx& ctx) const {
162 auto& ctxData = ctx.data;
163 ctxData.groupBy = groupBy;
164 GAIA_ASSERT(func != nullptr);
165 ctxData.groupByFunc = func; // group_by_func_default;
166 }
167 };
168
170 static constexpr QueryCmdType Id = QueryCmdType::SET_GROUP;
171 static constexpr bool InvalidatesHash = false;
172
173 GroupId groupId;
174
175 void exec(QueryCtx& ctx) const {
176 auto& ctxData = ctx.data;
177 ctxData.groupIdSet = groupId;
178 }
179 };
180
181 template <bool Cached>
183 World* m_world{};
188 bool m_destroyed = false;
189
190 public:
191 QueryImplStorage() = default;
193 if (try_del_from_cache())
194 ser_buffer_reset();
195 }
196
197 QueryImplStorage(QueryImplStorage&& other) {
198 m_world = other.m_world;
199 m_queryCache = other.m_queryCache;
200 m_q = other.m_q;
201 m_destroyed = other.m_destroyed;
202
203 // Make sure old instance is invalidated
204 other.m_q = {};
205 other.m_destroyed = false;
206 }
207 QueryImplStorage& operator=(QueryImplStorage&& other) {
208 GAIA_ASSERT(core::addressof(other) != this);
209
210 m_world = other.m_world;
211 m_queryCache = other.m_queryCache;
212 m_q = other.m_q;
213 m_destroyed = other.m_destroyed;
214
215 // Make sure old instance is invalidated
216 other.m_q = {};
217 other.m_destroyed = false;
218
219 return *this;
220 }
221
222 QueryImplStorage(const QueryImplStorage& other) {
223 m_world = other.m_world;
224 m_queryCache = other.m_queryCache;
225 m_q = other.m_q;
226 m_destroyed = other.m_destroyed;
227
228 // Make sure to update the ref count of the cached query so
229 // it doesn't get deleted by accident.
230 if (!m_destroyed) {
231 auto* pInfo = m_queryCache->try_get(m_q.handle);
232 if (pInfo != nullptr)
233 pInfo->add_ref();
234 }
235 }
236 QueryImplStorage& operator=(const QueryImplStorage& other) {
237 GAIA_ASSERT(core::addressof(other) != this);
238
239 m_world = other.m_world;
240 m_queryCache = other.m_queryCache;
241 m_q = other.m_q;
242 m_destroyed = other.m_destroyed;
243
244 // Make sure to update the ref count of the cached query so
245 // it doesn't get deleted by accident.
246 if (!m_destroyed) {
247 auto* pInfo = m_queryCache->try_get(m_q.handle);
248 if (pInfo != nullptr)
249 pInfo->add_ref();
250 }
251
252 return *this;
253 }
254
255 GAIA_NODISCARD World* world() {
256 return m_world;
257 }
258
259 GAIA_NODISCARD QuerySerBuffer& ser_buffer() {
260 return m_q.ser_buffer(m_world);
261 }
262 void ser_buffer_reset() {
263 return m_q.ser_buffer_reset(m_world);
264 }
265
266 void init(World* world, QueryCache* queryCache) {
267 m_world = world;
268 m_queryCache = queryCache;
269 }
270
272 void reset() {
273 auto& info = m_queryCache->get(m_q.handle);
274 info.reset();
275 }
276
277 void allow_to_destroy_again() {
278 m_destroyed = false;
279 }
280
282 GAIA_NODISCARD bool try_del_from_cache() {
283 if (!m_destroyed)
285
286 // Don't allow multiple calls to destroy to break the reference counter.
287 // One object is only allowed to destroy once.
288 m_destroyed = true;
289 return false;
290 }
291
293 void invalidate() {
294 m_q.handle = {};
295 }
296
298 GAIA_NODISCARD bool is_cached() const {
299 auto* pInfo = m_queryCache->try_get(m_q.handle);
300 return pInfo != nullptr;
301 }
302
304 GAIA_NODISCARD bool is_initialized() const {
305 return m_world != nullptr && m_queryCache != nullptr;
306 }
307 };
308
309 template <>
310 struct QueryImplStorage<false> {
311 QueryInfo m_queryInfo;
312
314 m_queryInfo.idx = QueryIdBad;
315 m_queryInfo.data.gen = QueryIdBad;
316 }
317
318 GAIA_NODISCARD World* world() {
319 return m_queryInfo.world();
320 }
321
322 GAIA_NODISCARD QuerySerBuffer& ser_buffer() {
323 return m_queryInfo.ser_buffer();
324 }
325 void ser_buffer_reset() {
326 m_queryInfo.ser_buffer_reset();
327 }
328
329 void init(World* world) {
330 m_queryInfo.init(world);
331 }
332
334 void reset() {
335 m_queryInfo.reset();
336 }
337
339 GAIA_NODISCARD bool try_del_from_cache() {
340 return false;
341 }
342
344 void invalidate() {}
345
347 GAIA_NODISCARD bool is_cached() const {
348 return false;
349 }
350
352 GAIA_NODISCARD bool is_initialized() const {
353 return true;
354 }
355 };
356
357 template <bool UseCaching = true>
358 class QueryImpl {
359 static constexpr uint32_t ChunkBatchSize = 32;
360
361 struct ChunkBatch {
362 const Archetype* pArchetype;
363 Chunk* pChunk;
364 const uint8_t* pIndicesMapping;
365 GroupId groupId;
366 uint16_t from;
367 uint16_t to;
368 };
369
370 using ChunkSpan = std::span<const Chunk*>;
371 using ChunkSpanMut = std::span<Chunk*>;
373 using CmdFunc = void (*)(QuerySerBuffer& buffer, QueryCtx& ctx);
374
375 private:
376 static constexpr CmdFunc CommandBufferRead[] = {
377 // Add item
378 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
380 ser::load(buffer, cmd);
381 cmd.exec(ctx);
382 },
383 // Add filter
384 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
386 ser::load(buffer, cmd);
387 cmd.exec(ctx);
388 },
389 // SortBy
390 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
391 QueryCmd_SortBy cmd;
392 ser::load(buffer, cmd);
393 cmd.exec(ctx);
394 },
395 // GroupBy
396 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
398 ser::load(buffer, cmd);
399 cmd.exec(ctx);
400 },
401 // SetGroupId
402 [](QuerySerBuffer& buffer, QueryCtx& ctx) {
404 ser::load(buffer, cmd);
405 cmd.exec(ctx);
406 } //
407 }; // namespace detail
408
412 ArchetypeId* m_nextArchetypeId{};
414 uint32_t* m_worldVersion{};
416 const ArchetypeMapById* m_archetypes{};
418 const EntityToArchetypeMap* m_entityToArchetypeMap{};
420 const ArchetypeDArray* m_allArchetypes{};
423 cnt::darray<ChunkBatch> m_batches;
424
425 //--------------------------------------------------------------------------------
426 public:
430 if constexpr (UseCaching) {
431 GAIA_PROF_SCOPE(query::fetch);
432
433 // Make sure the query was created by World::query()
434 GAIA_ASSERT(m_storage.is_initialized());
435
436 // If queryId is set it means QueryInfo was already created.
437 // This is the common case for cached queries.
438 if GAIA_LIKELY (m_storage.m_q.handle.id() != QueryIdBad) {
439 auto* pQueryInfo = m_storage.m_queryCache->try_get(m_storage.m_q.handle);
440
441 // The only time when this can be nullptr is just once after Query::destroy is called.
442 if GAIA_LIKELY (pQueryInfo != nullptr) {
443 recommit(pQueryInfo->ctx());
444 pQueryInfo->match(*m_entityToArchetypeMap, *m_allArchetypes, last_archetype_id());
445 return *pQueryInfo;
446 }
447
448 m_storage.invalidate();
449 }
450
451 // No queryId is set which means QueryInfo needs to be created
452 QueryCtx ctx;
453 ctx.init(m_storage.world());
454 commit(ctx);
455 auto& queryInfo = m_storage.m_queryCache->add(GAIA_MOV(ctx), *m_entityToArchetypeMap, *m_allArchetypes);
456 m_storage.m_q.handle = QueryInfo::handle(queryInfo);
457 m_storage.allow_to_destroy_again();
458 queryInfo.match(*m_entityToArchetypeMap, *m_allArchetypes, last_archetype_id());
459 return queryInfo;
460 } else {
461 GAIA_PROF_SCOPE(query::fetchu);
462
463 if GAIA_UNLIKELY (m_storage.m_queryInfo.ctx().q.handle.id() == QueryIdBad) {
464 QueryCtx ctx;
465 ctx.init(m_storage.world());
466 commit(ctx);
467 m_storage.m_queryInfo =
468 QueryInfo::create(QueryId{}, GAIA_MOV(ctx), *m_entityToArchetypeMap, *m_allArchetypes);
469 }
470 m_storage.m_queryInfo.match(*m_entityToArchetypeMap, *m_allArchetypes, last_archetype_id());
471 return m_storage.m_queryInfo;
472 }
473 }
474
475 //--------------------------------------------------------------------------------
476 private:
477 ArchetypeId last_archetype_id() const {
478 return *m_nextArchetypeId - 1;
479 }
480
481 template <typename T>
482 void add_cmd(T& cmd) {
483 // Make sure to invalidate if necessary.
484 if constexpr (T::InvalidatesHash)
485 m_storage.invalidate();
486
487 auto& serBuffer = m_storage.ser_buffer();
488 ser::save(serBuffer, T::Id);
489 ser::save(serBuffer, T::InvalidatesHash);
490 ser::save(serBuffer, cmd);
491 }
492
493 void add_inter(QueryInput item) {
494 // When excluding make sure the access type is None.
495 GAIA_ASSERT(item.op != QueryOpKind::Not || item.access == QueryAccess::None);
496
497 QueryCmd_AddItem cmd{item};
498 add_cmd(cmd);
499 }
500
501 template <typename T>
502 void add_inter(QueryOpKind op) {
503 Entity e;
504
505 if constexpr (is_pair<T>::value) {
506 // Make sure the components are always registered
507 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
508 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
509
510 e = Pair(desc_rel.entity, desc_tgt.entity);
511 } else {
512 // Make sure the component is always registered
513 const auto& desc = comp_cache_add<T>(*m_storage.world());
514 e = desc.entity;
515 }
516
517 // Determine the access type
518 QueryAccess access = QueryAccess::None;
519 if (op != QueryOpKind::Not) {
520 constexpr auto isReadWrite = core::is_mut_v<T>;
521 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
522 }
523
524 add_inter({op, access, e});
525 }
526
527 template <typename Rel, typename Tgt>
528 void add_inter(QueryOpKind op) {
529 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
530 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
531 static_assert(core::is_raw_v<UO_Rel>, "Use add() with raw types only");
532 static_assert(core::is_raw_v<UO_Tgt>, "Use add() with raw types only");
533
534 // Make sure the component is always registered
535 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
536 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
537
538 // Determine the access type
539 QueryAccess access = QueryAccess::None;
540 if (op != QueryOpKind::Not) {
541 constexpr auto isReadWrite = core::is_mut_v<UO_Rel> || core::is_mut_v<UO_Tgt>;
542 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
543 }
544
545 add_inter({op, access, {descRel.entity, descTgt.entity}});
546 }
547
548 //--------------------------------------------------------------------------------
549
550 void changed_inter(Entity entity) {
551 QueryCmd_AddFilter cmd{entity};
552 add_cmd(cmd);
553 }
554
555 template <typename T>
556 void changed_inter() {
557 using UO = typename component_type_t<T>::TypeOriginal;
558 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
559
560 // Make sure the component is always registered
561 const auto& desc = comp_cache_add<T>(*m_storage.world());
562 changed_inter(desc.entity);
563 }
564
565 template <typename Rel, typename Tgt>
566 void changed_inter() {
567 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
568 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
569 static_assert(core::is_raw_v<UO_Rel>, "Use changed() with raw types only");
570 static_assert(core::is_raw_v<UO_Tgt>, "Use changed() with raw types only");
571
572 // Make sure the component is always registered
573 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
574 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
575 changed_inter({descRel.entity, descTgt.entity});
576 }
577
578 //--------------------------------------------------------------------------------
579
580 void sort_by_inter(Entity entity, TSortByFunc func) {
581 QueryCmd_SortBy cmd{entity, func};
582 add_cmd(cmd);
583 }
584
585 template <typename T>
586 void sort_by_inter(TSortByFunc func) {
587 using UO = typename component_type_t<T>::TypeOriginal;
588 if constexpr (std::is_same_v<UO, Entity>) {
589 sort_by_inter(EntityBad, func);
590 } else {
591 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
592
593 // Make sure the component is always registered
594 const auto& desc = comp_cache_add<T>(*m_storage.world());
595
596 sort_by_inter(desc.entity, func);
597 }
598 }
599
600 template <typename Rel, typename Tgt>
601 void sort_by_inter(TSortByFunc func) {
602 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
603 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
604 static_assert(core::is_raw_v<UO_Rel>, "Use group_by() with raw types only");
605 static_assert(core::is_raw_v<UO_Tgt>, "Use group_by() with raw types only");
606
607 // Make sure the component is always registered
608 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
609 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
610
611 sort_by_inter({descRel.entity, descTgt.entity}, func);
612 }
613
614 //--------------------------------------------------------------------------------
615
616 void group_by_inter(Entity entity, TGroupByFunc func) {
617 QueryCmd_GroupBy cmd{entity, func};
618 add_cmd(cmd);
619 }
620
621 template <typename T>
622 void group_by_inter(Entity entity, TGroupByFunc func) {
623 using UO = typename component_type_t<T>::TypeOriginal;
624 static_assert(core::is_raw_v<UO>, "Use changed() with raw types only");
625
626 group_by_inter(entity, func);
627 }
628
629 template <typename Rel, typename Tgt>
630 void group_by_inter(TGroupByFunc func) {
631 using UO_Rel = typename component_type_t<Rel>::TypeOriginal;
632 using UO_Tgt = typename component_type_t<Tgt>::TypeOriginal;
633 static_assert(core::is_raw_v<UO_Rel>, "Use group_by() with raw types only");
634 static_assert(core::is_raw_v<UO_Tgt>, "Use group_by() with raw types only");
635
636 // Make sure the component is always registered
637 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
638 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
639
640 group_by_inter({descRel.entity, descTgt.entity}, func);
641 }
642
643 //--------------------------------------------------------------------------------
644
645 void set_group_id_inter(GroupId groupId) {
646 // Dummy usage of GroupIdMax to avoid warning about unused constant
647 (void)GroupIdMax;
648
649 QueryCmd_SetGroupId cmd{groupId};
650 add_cmd(cmd);
651 }
652
653 void set_group_id_inter(Entity groupId) {
654 set_group_id_inter(groupId.value());
655 }
656
657 template <typename T>
658 void set_group_id_inter() {
659 using UO = typename component_type_t<T>::TypeOriginal;
660 static_assert(core::is_raw_v<UO>, "Use group_id() with raw types only");
661
662 // Make sure the component is always registered
663 const auto& desc = comp_cache_add<T>(*m_storage.world());
664 set_group_inter(desc.entity);
665 }
666
667 //--------------------------------------------------------------------------------
668
669 void commit(QueryCtx& ctx) {
670 GAIA_PROF_SCOPE(query::commit);
671
672#if GAIA_ASSERT_ENABLED
673 if constexpr (UseCaching) {
674 GAIA_ASSERT(m_storage.m_q.handle.id() == QueryIdBad);
675 } else {
676 GAIA_ASSERT(m_storage.m_queryInfo.idx == QueryIdBad);
677 }
678#endif
679
680 auto& serBuffer = m_storage.ser_buffer();
681
682 // Read data from buffer and execute the command stored in it
683 serBuffer.seek(0);
684 while (serBuffer.tell() < serBuffer.bytes()) {
685 QueryCmdType id{};
686 bool invalidatesHash = false;
687 ser::load(serBuffer, id);
688 ser::load(serBuffer, invalidatesHash);
689 (void)invalidatesHash; // We don't care about this during commit
690 CommandBufferRead[id](serBuffer, ctx);
691 }
692
693 // Calculate the lookup hash from the provided context
694 if constexpr (UseCaching) {
695 calc_lookup_hash(ctx);
696 }
697
698 // We can free all temporary data now
699 m_storage.ser_buffer_reset();
700
701 // Refresh the context
702 ctx.refresh();
703 }
704
705 void recommit(QueryCtx& ctx) {
706 GAIA_PROF_SCOPE(query::recommit);
707
708 auto& serBuffer = m_storage.ser_buffer();
709
710 // Read data from buffer and execute the command stored in it
711 serBuffer.seek(0);
712 while (serBuffer.tell() < serBuffer.bytes()) {
713 QueryCmdType id{};
714 bool invalidatesHash = false;
715 ser::load(serBuffer, id);
716 ser::load(serBuffer, invalidatesHash);
717 // Hash recalculation is not accepted here
718 GAIA_ASSERT(!invalidatesHash);
719 if (invalidatesHash)
720 return;
721 CommandBufferRead[id](serBuffer, ctx);
722 }
723
724 // We can free all temporary data now
725 m_storage.ser_buffer_reset();
726 }
727
728 //--------------------------------------------------------------------------------
729 public:
730#if GAIA_ASSERT_ENABLED
732 template <typename... T>
733 GAIA_NODISCARD bool unpack_args_into_query_has_all(
734 const QueryInfo& queryInfo, [[maybe_unused]] core::func_type_list<T...> types) const {
735 if constexpr (sizeof...(T) > 0)
736 return queryInfo.template has_all<T...>();
737 else
738 return true;
739 }
740#endif
741
742 //--------------------------------------------------------------------------------
743
744 GAIA_NODISCARD static bool match_filters(const Chunk& chunk, const QueryInfo& queryInfo) {
745 GAIA_ASSERT(!chunk.empty() && "match_filters called on an empty chunk");
746
747 const auto queryVersion = queryInfo.world_version();
748 const auto& filtered = queryInfo.ctx().data.changed_view();
749
750 // Skip unchanged chunks
751 if (filtered.empty())
752 return false;
753
754 // See if any component has changed
755 uint32_t lastIdx = 0;
756 for (const auto comp: filtered) {
757 // Components are sorted. Therefore, we don't need to search from 0 all the time.
758 // We can search from the last found index.
759 const auto compIdx = chunk.comp_idx(comp, lastIdx);
760 if (chunk.changed(queryVersion, compIdx))
761 return true;
762
763 lastIdx = compIdx;
764 }
765
766 // If the component hasn't been modified, the entity itself still might have been moved.
767 // For that reason we also need to check the entity version.
768 return chunk.changed(queryInfo.world_version());
769 }
770
771 GAIA_NODISCARD bool can_process_archetype(const Archetype& archetype) const {
772 // Archetypes requested for deletion are skipped for processing
773 return !archetype.is_req_del();
774 }
775
776 //--------------------------------------------------------------------------------
777
779 template <typename Func, typename TIter>
780 static void run_query_func(World* pWorld, Func func, ChunkBatch& batch) {
781 TIter it;
782 it.set_world(pWorld);
783 it.set_archetype(batch.pArchetype);
784 it.set_chunk(batch.pChunk, batch.from, batch.to);
785 it.set_group_id(batch.groupId);
786 it.set_remapping_indices(batch.pIndicesMapping);
787 func(it);
788 }
789
791 template <typename Func, typename TIter>
792 static void run_query_func(World* pWorld, Func func, std::span<ChunkBatch> batches) {
793 GAIA_PROF_SCOPE(query::run_query_func);
794
795 const auto chunkCnt = batches.size();
796 GAIA_ASSERT(chunkCnt > 0);
797
798 // We only have one chunk to process
799 if GAIA_UNLIKELY (chunkCnt == 1) {
800 run_query_func<Func, TIter>(pWorld, func, batches[0]);
801 return;
802 }
803
804 // We have many chunks to process.
805 // Chunks might be located at different memory locations. Not even in the same memory page.
806 // Therefore, to make it easier for the CPU we give it a hint that we want to prefetch data
807 // for the next chunk explicitly so we do not end up stalling later.
808 // Note, this is a micro optimization and on average it brings no performance benefit. It only
809 // helps with edge cases.
810 // Let us be conservative for now and go with T2. That means we will try to keep our data at
811 // least in L3 cache or higher.
813 run_query_func<Func, TIter>(pWorld, func, batches[0]);
814
815 uint32_t chunkIdx = 1;
816 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
817 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
818 run_query_func<Func, TIter>(pWorld, func, batches[chunkIdx]);
819 }
820
821 run_query_func<Func, TIter>(pWorld, func, batches[chunkIdx]);
822 }
823
824 template <bool HasFilters, typename TIter, typename Func>
825 void run_query_batch_no_group_id(
826 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
827 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
828
829 // We are batching by chunks. Some of them might contain only few items but this state is only
830 // temporary because defragmentation runs constantly and keeps things clean.
831 ChunkBatchArray chunkBatches;
832
833 auto cacheView = queryInfo.cache_archetype_view();
834 auto sortView = queryInfo.cache_sort_view();
835
836 lock(*m_storage.world());
837
838 if (!sortView.empty()) {
839 for (const auto& view: sortView) {
840 const auto chunkEntitiesCnt = TIter::size(view.pChunk);
841 if GAIA_UNLIKELY (chunkEntitiesCnt == 0)
842 continue;
843
844 const auto viewFrom = view.startRow;
845 const auto viewTo = (uint16_t)(view.startRow + view.count);
846
847 const auto minStartRow = TIter::start_index(view.pChunk);
848 const auto minEndRow = TIter::end_index(view.pChunk);
849 const auto startRow = core::get_max(minStartRow, viewFrom);
850 const auto endRow = core::get_min(minEndRow, viewTo);
851 const auto totalRows = endRow - startRow;
852 if (totalRows == 0)
853 continue;
854
855 if constexpr (HasFilters) {
856 if (!match_filters(*view.pChunk, queryInfo))
857 continue;
858 }
859
860 auto* pArchetype = cacheView[view.archetypeIdx];
861 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
862
863 chunkBatches.push_back(ChunkBatch{pArchetype, view.pChunk, indicesView.data(), 0U, startRow, endRow});
864
865 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
866 run_query_func<Func, TIter>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
867 chunkBatches.clear();
868 }
869 }
870 } else {
871 for (uint32_t i = idxFrom; i < idxTo; ++i) {
872 auto* pArchetype = cacheView[i];
873 if GAIA_UNLIKELY (!can_process_archetype(*pArchetype))
874 continue;
875
876 auto indicesView = queryInfo.indices_mapping_view(i);
877 const auto& chunks = pArchetype->chunks();
878
879 uint32_t chunkOffset = 0;
880 uint32_t itemsLeft = chunks.size();
881 while (itemsLeft > 0) {
882 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
883 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
884
885 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
886 for (auto* pChunk: chunkSpan) {
887 if GAIA_UNLIKELY (TIter::size(pChunk) == 0)
888 continue;
889
890 if constexpr (HasFilters) {
891 if (!match_filters(*pChunk, queryInfo))
892 continue;
893 }
894
895 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), 0, 0, 0});
896 }
897
898 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
899 run_query_func<Func, TIter>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
900 chunkBatches.clear();
901 }
902
903 itemsLeft -= batchSize;
904 chunkOffset += batchSize;
905 }
906 }
907 }
908
909 // Take care of any leftovers not processed during run_query
910 if (!chunkBatches.empty())
911 run_query_func<Func, TIter>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
912
913 unlock(*m_storage.world());
914 // Commit the command buffer.
915 // TODO: Smart handling necessary
916 commit_cmd_buffer_st(*m_storage.world());
917 commit_cmd_buffer_mt(*m_storage.world());
918 }
919
920 template <bool HasFilters, typename TIter, typename Func, QueryExecType ExecType>
921 void run_query_batch_no_group_id_par(
922 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
923 static_assert(ExecType != QueryExecType::Default);
924 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
925
926 auto cacheView = queryInfo.cache_archetype_view();
927 auto sortView = queryInfo.cache_sort_view();
928
929 if (!sortView.empty()) {
930 for (const auto& view: sortView) {
931 const auto chunkEntitiesCnt = TIter::size(view.pChunk);
932 if GAIA_UNLIKELY (chunkEntitiesCnt == 0)
933 continue;
934
935 const auto viewFrom = view.startRow;
936 const auto viewTo = (uint16_t)(view.startRow + view.count);
937
938 const auto minStartRow = TIter::start_index(view.pChunk);
939 const auto minEndRow = TIter::end_index(view.pChunk);
940 const auto startRow = core::get_max(minStartRow, viewFrom);
941 const auto endRow = core::get_min(minEndRow, viewTo);
942 const auto totalRows = endRow - startRow;
943 if (totalRows == 0)
944 continue;
945
946 if constexpr (HasFilters) {
947 if (!match_filters(*view.pChunk, queryInfo))
948 continue;
949 }
950
951 const auto* pArchetype = cacheView[view.archetypeIdx];
952 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
953
954 m_batches.push_back(ChunkBatch{pArchetype, view.pChunk, indicesView.data(), 0U, startRow, endRow});
955 }
956 } else {
957 for (uint32_t i = idxFrom; i < idxTo; ++i) {
958 const auto* pArchetype = cacheView[i];
959 if GAIA_UNLIKELY (!can_process_archetype(*pArchetype))
960 continue;
961
962 auto indicesView = queryInfo.indices_mapping_view(i);
963 const auto& chunks = pArchetype->chunks();
964 for (auto* pChunk: chunks) {
965 if GAIA_UNLIKELY (TIter::size(pChunk) == 0)
966 continue;
967
968 if constexpr (HasFilters) {
969 if (!match_filters(*pChunk, queryInfo))
970 continue;
971 }
972
973 m_batches.push_back({pArchetype, pChunk, indicesView.data(), 0, 0, 0});
974 }
975 }
976 }
977
978 if (m_batches.empty())
979 return;
980
981 lock(*m_storage.world());
982
983 mt::JobParallel j;
984
985 // Use efficiency cores for low-level priority jobs
986 if constexpr (ExecType == QueryExecType::ParallelEff)
987 j.priority = mt::JobPriority::Low;
988
989 j.func = [&](const mt::JobArgs& args) {
990 run_query_func<Func, TIter>(
991 m_storage.world(), func, std::span(&m_batches[args.idxStart], args.idxEnd - args.idxStart));
992 };
993
994 auto& tp = mt::ThreadPool::get();
995 auto jobHandle = tp.sched_par(j, m_batches.size(), 0);
996 tp.wait(jobHandle);
997 m_batches.clear();
998
999 unlock(*m_storage.world());
1000 // Commit the command buffer.
1001 // TODO: Smart handling necessary
1002 commit_cmd_buffer_st(*m_storage.world());
1003 commit_cmd_buffer_mt(*m_storage.world());
1004 }
1005
1006 template <bool HasFilters, typename TIter, typename Func>
1007 void run_query_batch_with_group_id(
1008 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
1009 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
1010
1011 ChunkBatchArray chunkBatches;
1012
1013 auto cacheView = queryInfo.cache_archetype_view();
1014 auto dataView = queryInfo.cache_data_view();
1015
1016 lock(*m_storage.world());
1017
1018 for (uint32_t i = idxFrom; i < idxTo; ++i) {
1019 const auto* pArchetype = cacheView[i];
1020 if GAIA_UNLIKELY (!can_process_archetype(*pArchetype))
1021 continue;
1022
1023 auto indicesView = queryInfo.indices_mapping_view(i);
1024 const auto& chunks = pArchetype->chunks();
1025 const auto& data = dataView[i];
1026
1027#if GAIA_ASSERT_ENABLED
1028 GAIA_ASSERT(
1029 // ... or no groupId is set...
1030 queryInfo.ctx().data.groupIdSet == 0 ||
1031 // ... or the groupId must match the requested one
1032 data.groupId == queryInfo.ctx().data.groupIdSet);
1033#endif
1034
1035 uint32_t chunkOffset = 0;
1036 uint32_t itemsLeft = chunks.size();
1037 while (itemsLeft > 0) {
1038 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
1039 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
1040
1041 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
1042 for (auto* pChunk: chunkSpan) {
1043 if GAIA_UNLIKELY (TIter::size(pChunk) == 0)
1044 continue;
1045
1046 if constexpr (HasFilters) {
1047 if (!match_filters(*pChunk, queryInfo))
1048 continue;
1049 }
1050
1051 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), data.groupId, 0, 0});
1052 }
1053
1054 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
1055 run_query_func<Func, TIter>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
1056 chunkBatches.clear();
1057 }
1058
1059 itemsLeft -= batchSize;
1060 chunkOffset += batchSize;
1061 }
1062 }
1063
1064 // Take care of any leftovers not processed during run_query
1065 if (!chunkBatches.empty())
1066 run_query_func<Func, TIter>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
1067
1068 unlock(*m_storage.world());
1069 // Commit the command buffer.
1070 // TODO: Smart handling necessary
1071 commit_cmd_buffer_st(*m_storage.world());
1072 commit_cmd_buffer_mt(*m_storage.world());
1073 }
1074
1075 template <bool HasFilters, typename TIter, typename Func, QueryExecType ExecType>
1076 void run_query_batch_with_group_id_par(
1077 const QueryInfo& queryInfo, const uint32_t idxFrom, const uint32_t idxTo, Func func) {
1078 static_assert(ExecType != QueryExecType::Default);
1079 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
1080
1081 ChunkBatchArray chunkBatch;
1082
1083 auto cacheView = queryInfo.cache_archetype_view();
1084 auto dataView = queryInfo.cache_data_view();
1085
1086#if GAIA_ASSERT_ENABLED
1087 for (uint32_t i = idxFrom; i < idxTo; ++i) {
1088 auto* pArchetype = cacheView[i];
1089 if GAIA_UNLIKELY (!can_process_archetype(*pArchetype))
1090 continue;
1091
1092 const auto& data = dataView[i];
1093 GAIA_ASSERT(
1094 // ... or no groupId is set...
1095 queryInfo.ctx().data.groupIdSet == 0 ||
1096 // ... or the groupId must match the requested one
1097 data.groupId == queryInfo.ctx().data.groupIdSet);
1098 }
1099#endif
1100
1101 for (uint32_t i = idxFrom; i < idxTo; ++i) {
1102 const Archetype* pArchetype = cacheView[i];
1103 if GAIA_UNLIKELY (!can_process_archetype(*pArchetype))
1104 continue;
1105
1106 auto indicesView = queryInfo.indices_mapping_view(i);
1107 const auto& data = dataView[i];
1108 const auto& chunks = pArchetype->chunks();
1109 for (auto* pChunk: chunks) {
1110 if GAIA_UNLIKELY (TIter::size(pChunk) == 0)
1111 continue;
1112
1113 if constexpr (HasFilters) {
1114 if (!match_filters(*pChunk, queryInfo))
1115 continue;
1116 }
1117
1118 m_batches.push_back({pArchetype, pChunk, indicesView.data(), data.groupId, 0, 0});
1119 }
1120 }
1121
1122 if (m_batches.empty())
1123 return;
1124
1125 lock(*m_storage.world());
1126
1127 mt::JobParallel j;
1128
1129 // Use efficiency cores for low-level priority jobs
1130 if constexpr (ExecType == QueryExecType::ParallelEff)
1131 j.priority = mt::JobPriority::Low;
1132
1133 j.func = [&](const mt::JobArgs& args) {
1134 run_query_func<Func, TIter>(
1135 m_storage.world(), func, std::span(&m_batches[args.idxStart], args.idxEnd - args.idxStart));
1136 };
1137
1138 auto& tp = mt::ThreadPool::get();
1139 auto jobHandle = tp.sched_par(j, m_batches.size(), 0);
1140 tp.wait(jobHandle);
1141 m_batches.clear();
1142
1143 unlock(*m_storage.world());
1144 // Commit the command buffer.
1145 // TODO: Smart handling necessary
1146 commit_cmd_buffer_st(*m_storage.world());
1147 commit_cmd_buffer_mt(*m_storage.world());
1148 }
1149
1150 template <bool HasFilters, QueryExecType ExecType, typename TIter, typename Func>
1151 void run_query(const QueryInfo& queryInfo, Func func) {
1152 GAIA_PROF_SCOPE(query::run_query);
1153
1154 // TODO: Have archetype cache as double-linked list with pointers only.
1155 // Have chunk cache as double-linked list with pointers only.
1156 // Make it so only valid pointers are linked together.
1157 // This means one less indirection + we won't need to call can_process_archetype()
1158 // or pChunk.size()==0 in run_query_batch functions.
1159 auto cache_view = queryInfo.cache_archetype_view();
1160 if (cache_view.empty())
1161 return;
1162
1163 const bool isGroupBy = queryInfo.ctx().data.groupBy != EntityBad;
1164 const bool isGroupSet = queryInfo.ctx().data.groupIdSet != 0;
1165 if (!isGroupBy || !isGroupSet) {
1166 // No group requested or group filtering is currently turned off
1167 const auto idxFrom = 0;
1168 const auto idxTo = (uint32_t)cache_view.size();
1169 if constexpr (ExecType != QueryExecType::Default)
1170 run_query_batch_no_group_id_par<HasFilters, TIter, Func, ExecType>(queryInfo, idxFrom, idxTo, func);
1171 else
1172 run_query_batch_no_group_id<HasFilters, TIter, Func>(queryInfo, idxFrom, idxTo, func);
1173 } else {
1174 // We wish to iterate only a certain group
1175 // TODO: Cache the indices so we don't have to iterate. In situations with many
1176 // groups this could save a bit of performance.
1177 auto group_data_view = queryInfo.group_data_view();
1178 const auto cnt = group_data_view.size();
1179 GAIA_FOR(cnt) {
1180 if (group_data_view[i].groupId != queryInfo.ctx().data.groupIdSet)
1181 continue;
1182
1183 const auto idxFrom = group_data_view[i].idxFirst;
1184 const auto idxTo = group_data_view[i].idxLast + 1;
1185 if constexpr (ExecType != QueryExecType::Default)
1186 run_query_batch_with_group_id_par<HasFilters, TIter, Func, ExecType>(queryInfo, idxFrom, idxTo, func);
1187 else
1188 run_query_batch_with_group_id<HasFilters, TIter, Func>(queryInfo, idxFrom, idxTo, func);
1189 return;
1190 }
1191 }
1192 }
1193
1194 template <QueryExecType ExecType, typename TIter, typename Func>
1195 void run_query_on_chunks(QueryInfo& queryInfo, Func func) {
1196 // Update the world version
1197 ::gaia::ecs::update_version(*m_worldVersion);
1198
1199 const bool hasFilters = queryInfo.has_filters();
1200 if (hasFilters)
1201 run_query<true, ExecType, TIter>(queryInfo, func);
1202 else
1203 run_query<false, ExecType, TIter>(queryInfo, func);
1204
1205 // Update the query version with the current world's version
1206 queryInfo.set_world_version(*m_worldVersion);
1207 }
1208
1209 template <typename TIter, typename Func, typename... T>
1210 GAIA_FORCEINLINE void
1211 run_query_on_chunk(TIter& it, Func func, [[maybe_unused]] core::func_type_list<T...> types) {
1212 const auto cnt = it.size();
1213
1214 if constexpr (sizeof...(T) > 0) {
1215 // Pointers to the respective component types in the chunk, e.g
1216 // q.each([&](Position& p, const Velocity& v) {...}
1217 // Translates to:
1218 // auto p = it.view_mut_inter<Position, true>();
1219 // auto v = it.view_inter<Velocity>();
1220 auto dataPointerTuple = std::make_tuple(it.template view_auto<T>()...);
1221
1222 // Iterate over each entity in the chunk.
1223 // Translates to:
1224 // GAIA_FOR(0, cnt) func(p[i], v[i]);
1225
1226 GAIA_FOR(cnt) {
1227 func(std::get<decltype(it.template view_auto<T>())>(dataPointerTuple)[it.template acc_index<T>(i)]...);
1228 }
1229 } else {
1230 // No functor parameters. Do an empty loop.
1231 GAIA_FOR(cnt) func();
1232 }
1233 }
1234
1235 template <QueryExecType ExecType, typename Func>
1236 void each_inter(QueryInfo& queryInfo, Func func) {
1237 using InputArgs = decltype(core::func_args(&Func::operator()));
1238
1239#if GAIA_ASSERT_ENABLED
1240 // Make sure we only use components specified in the query.
1241 // Constness is respected. Therefore, if a type is const when registered to query,
1242 // it has to be const (or immutable) also in each().
1243 // in query.
1244 // Example 1:
1245 // auto q = w.query().all<MyType>(); // immutable access requested
1246 // q.each([](MyType val)) {}); // okay
1247 // q.each([](const MyType& val)) {}); // okay
1248 // q.each([](MyType& val)) {}); // error
1249 // Example 2:
1250 // auto q = w.query().all<MyType&>(); // mutable access requested
1251 // q.each([](MyType val)) {}); // error
1252 // q.each([](const MyType& val)) {}); // error
1253 // q.each([](MyType& val)) {}); // okay
1254 GAIA_ASSERT(unpack_args_into_query_has_all(queryInfo, InputArgs{}));
1255#endif
1256
1257 run_query_on_chunks<ExecType, Iter>(queryInfo, [&](Iter& it) {
1258 GAIA_PROF_SCOPE(query_func);
1259 run_query_on_chunk(it, func, InputArgs{});
1260 });
1261 }
1262
1263 template <
1264 QueryExecType ExecType, typename Func, bool FuncEnabled = UseCaching,
1265 typename std::enable_if<FuncEnabled>::type* = nullptr>
1266 void each_inter(QueryId queryId, Func func) {
1267 // Make sure the query was created by World.query()
1268 GAIA_ASSERT(m_storage.m_queryCache != nullptr);
1269 GAIA_ASSERT(queryId != QueryIdBad);
1270
1271 auto& queryInfo = m_storage.m_queryCache->get(queryId);
1272 each_inter(queryInfo, func);
1273 }
1274
1275 template <QueryExecType ExecType, typename Func>
1276 void each_inter(Func func) {
1277 auto& queryInfo = fetch();
1278
1279 if constexpr (std::is_invocable_v<Func, IterAll&>) {
1280 run_query_on_chunks<ExecType, IterAll>(queryInfo, [&](IterAll& it) {
1281 GAIA_PROF_SCOPE(query_func);
1282 func(it);
1283 });
1284 } else if constexpr (std::is_invocable_v<Func, Iter&>) {
1285 run_query_on_chunks<ExecType, Iter>(queryInfo, [&](Iter& it) {
1286 GAIA_PROF_SCOPE(query_func);
1287 func(it);
1288 });
1289 } else if constexpr (std::is_invocable_v<Func, IterDisabled&>) {
1290 run_query_on_chunks<ExecType, IterDisabled>(queryInfo, [&](IterDisabled& it) {
1291 GAIA_PROF_SCOPE(query_func);
1292 func(it);
1293 });
1294 } else
1295 each_inter<ExecType>(queryInfo, func);
1296 }
1297
1298 template <bool UseFilters, typename TIter>
1299 GAIA_NODISCARD bool empty_inter(const QueryInfo& queryInfo) const {
1300 for (auto* pArchetype: queryInfo) {
1301 if GAIA_UNLIKELY (!can_process_archetype(*pArchetype))
1302 continue;
1303
1304 GAIA_PROF_SCOPE(query::empty);
1305
1306 const auto& chunks = pArchetype->chunks();
1307 TIter it;
1308 it.set_world(queryInfo.world());
1309 it.set_archetype(pArchetype);
1310
1311 const bool isNotEmpty = core::has_if(chunks, [&](Chunk* pChunk) {
1312 it.set_chunk(pChunk);
1313 if constexpr (UseFilters)
1314 return it.size() > 0 && match_filters(*pChunk, queryInfo);
1315 else
1316 return it.size() > 0;
1317 });
1318
1319 if (isNotEmpty)
1320 return false;
1321 }
1322
1323 return true;
1324 }
1325
1326 template <bool UseFilters, typename TIter>
1327 GAIA_NODISCARD uint32_t count_inter(const QueryInfo& queryInfo) const {
1328 uint32_t cnt = 0;
1329 TIter it;
1330 it.set_world(queryInfo.world());
1331
1332 for (auto* pArchetype: queryInfo) {
1333 if GAIA_UNLIKELY (!can_process_archetype(*pArchetype))
1334 continue;
1335
1336 GAIA_PROF_SCOPE(query::count);
1337
1338 it.set_archetype(pArchetype);
1339
1340 // No mapping for count(). It doesn't need to access data cache.
1341 // auto indicesView = queryInfo.indices_mapping_view(aid);
1342
1343 const auto& chunks = pArchetype->chunks();
1344 for (auto* pChunk: chunks) {
1345 it.set_chunk(pChunk);
1346
1347 const auto entityCnt = it.size();
1348 if (entityCnt == 0)
1349 continue;
1350
1351 // Filters
1352 if constexpr (UseFilters) {
1353 if (!match_filters(*pChunk, queryInfo))
1354 continue;
1355 }
1356
1357 // Entity count
1358 cnt += entityCnt;
1359 }
1360 }
1361
1362 return cnt;
1363 }
1364
1365 template <bool UseFilters, typename TIter, typename ContainerOut>
1366 void arr_inter(QueryInfo& queryInfo, ContainerOut& outArray) {
1367 using ContainerItemType = typename ContainerOut::value_type;
1368 TIter it;
1369 it.set_world(queryInfo.world());
1370
1371 for (auto* pArchetype: queryInfo) {
1372 if GAIA_UNLIKELY (!can_process_archetype(*pArchetype))
1373 continue;
1374
1375 GAIA_PROF_SCOPE(query::arr);
1376
1377 it.set_archetype(pArchetype);
1378
1379 // No mapping for arr(). It doesn't need to access data cache.
1380 // auto indicesView = queryInfo.indices_mapping_view(aid);
1381
1382 const auto& chunks = pArchetype->chunks();
1383 for (auto* pChunk: chunks) {
1384 it.set_chunk(pChunk);
1385
1386 const auto cnt = it.size();
1387 if (cnt == 0)
1388 continue;
1389
1390 // Filters
1391 if constexpr (UseFilters) {
1392 if (!match_filters(*pChunk, queryInfo))
1393 continue;
1394 }
1395
1396 const auto dataView = it.template view<ContainerItemType>();
1397 GAIA_FOR(cnt) {
1398 const auto idx = it.template acc_index<ContainerItemType>(i);
1399 auto tmp = dataView[idx];
1400 outArray.push_back(tmp);
1401 }
1402 }
1403 }
1404 }
1405
1406 public:
1407 QueryImpl() = default;
1408 ~QueryImpl() = default;
1409
1410 template <bool FuncEnabled = UseCaching>
1411 QueryImpl(
1412 World& world, QueryCache& queryCache, ArchetypeId& nextArchetypeId, uint32_t& worldVersion,
1413 const ArchetypeMapById& archetypes, const EntityToArchetypeMap& entityToArchetypeMap,
1414 const ArchetypeDArray& allArchetypes):
1415 m_nextArchetypeId(&nextArchetypeId), m_worldVersion(&worldVersion), m_archetypes(&archetypes),
1416 m_entityToArchetypeMap(&entityToArchetypeMap), m_allArchetypes(&allArchetypes) {
1417 m_storage.init(&world, &queryCache);
1418 }
1419
1420 template <bool FuncEnabled = !UseCaching>
1421 QueryImpl(
1422 World& world, ArchetypeId& nextArchetypeId, uint32_t& worldVersion, const ArchetypeMapById& archetypes,
1423 const EntityToArchetypeMap& entityToArchetypeMap, const ArchetypeDArray& allArchetypes):
1424 m_nextArchetypeId(&nextArchetypeId), m_worldVersion(&worldVersion), m_archetypes(&archetypes),
1425 m_entityToArchetypeMap(&entityToArchetypeMap), m_allArchetypes(&allArchetypes) {
1426 m_storage.init(&world);
1427 }
1428
1429 GAIA_NODISCARD QueryId id() const {
1430 static_assert(UseCaching, "id() can be used only with cached queries");
1431 return m_storage.m_q.handle.id();
1432 }
1433
1434 GAIA_NODISCARD uint32_t gen() const {
1435 static_assert(UseCaching, "gen() can be used only with cached queries");
1436 return m_storage.m_q.handle.gen();
1437 }
1438
1439 //------------------------------------------------
1440
1442 void reset() {
1443 m_storage.reset();
1444 }
1445
1446 void destroy() {
1447 (void)m_storage.try_del_from_cache();
1448 }
1449
1451 GAIA_NODISCARD bool is_cached() const {
1452 return m_storage.is_cached();
1453 }
1454
1455 //------------------------------------------------
1456
1486 QueryImpl& add(const char* str, ...) {
1487 GAIA_ASSERT(str != nullptr);
1488 if (str == nullptr)
1489 return *this;
1490
1491 va_list args{};
1492 va_start(args, str);
1493
1494 uint32_t pos = 0;
1495 uint32_t exp0 = 0;
1496
1497 auto process = [&]() {
1498 std::span<const char> exprRaw(&str[exp0], pos - exp0);
1499 exp0 = ++pos;
1500
1501 auto expr = core::trim(exprRaw);
1502 if (expr.empty())
1503 return true;
1504
1505 if (expr[0] == '+') {
1506 uint32_t off = 1;
1507 if (expr[1] == '&')
1508 off = 2;
1509
1510 auto var = expr.subspan(off);
1511 auto entity = expr_to_entity((const World&)*m_storage.world(), args, var);
1512 if (entity == EntityBad)
1513 return false;
1514
1515 any(entity, off != 0);
1516 } else if (expr[0] == '!') {
1517 auto var = expr.subspan(1);
1518 auto entity = expr_to_entity((const World&)*m_storage.world(), args, var);
1519 if (entity == EntityBad)
1520 return false;
1521
1522 no(entity);
1523 } else {
1524 auto entity = expr_to_entity((const World&)*m_storage.world(), args, expr);
1525 if (entity == EntityBad)
1526 return false;
1527
1528 all(entity);
1529 }
1530
1531 return true;
1532 };
1533
1534 for (; str[pos] != 0; ++pos) {
1535 if (str[pos] == ';' && !process())
1536 goto add_end;
1537 }
1538 process();
1539
1540 add_end:
1541 va_end(args);
1542 return *this;
1543 }
1544
1545 QueryImpl& add(QueryInput item) {
1546 // Add commands to the command buffer
1547 add_inter(item);
1548 return *this;
1549 }
1550
1551 //------------------------------------------------
1552
1553 QueryImpl& all(Entity entity, bool isReadWrite = false) {
1554 if (entity.pair())
1555 add({QueryOpKind::All, QueryAccess::None, entity});
1556 else
1557 add({QueryOpKind::All, isReadWrite ? QueryAccess::Write : QueryAccess::Read, entity});
1558 return *this;
1559 }
1560
1561 QueryImpl& all(Entity entity, Entity src, bool isReadWrite = false) {
1562 if (entity.pair())
1563 add({QueryOpKind::All, QueryAccess::None, entity, src});
1564 else
1565 add({QueryOpKind::All, isReadWrite ? QueryAccess::Write : QueryAccess::Read, entity, src});
1566 return *this;
1567 }
1568
1569#if GAIA_USE_VARIADIC_API
1570 template <typename... T>
1571 QueryImpl& all() {
1572 // Add commands to the command buffer
1573 (add_inter<T>(QueryOpKind::All), ...);
1574 return *this;
1575 }
1576#else
1577 template <typename T>
1578 QueryImpl& all() {
1579 // Add commands to the command buffer
1580 add_inter<T>(QueryOpKind::All);
1581 return *this;
1582 }
1583#endif
1584
1585 //------------------------------------------------
1586
1587 QueryImpl& any(Entity entity, bool isReadWrite = false) {
1588 if (entity.pair())
1589 add({QueryOpKind::Any, QueryAccess::None, entity});
1590 else
1591 add({QueryOpKind::Any, isReadWrite ? QueryAccess::Write : QueryAccess::Read, entity});
1592 return *this;
1593 }
1594
1595#if GAIA_USE_VARIADIC_API
1596 template <typename... T>
1597 QueryImpl& any() {
1598 // Add commands to the command buffer
1599 (add_inter<T>(QueryOpKind::Any), ...);
1600 return *this;
1601 }
1602#else
1603 template <typename T>
1604 QueryImpl& any() {
1605 // Add commands to the command buffer
1606 add_inter<T>(QueryOpKind::Any);
1607 return *this;
1608 }
1609#endif
1610
1611 //------------------------------------------------
1612
1613 QueryImpl& no(Entity entity) {
1614 add({QueryOpKind::Not, QueryAccess::None, entity});
1615 return *this;
1616 }
1617
1618#if GAIA_USE_VARIADIC_API
1619 template <typename... T>
1620 QueryImpl& no() {
1621 // Add commands to the command buffer
1622 (add_inter<T>(QueryOpKind::Not), ...);
1623 return *this;
1624 }
1625#else
1626 template <typename T>
1627 QueryImpl& no() {
1628 // Add commands to the command buffer
1629 add_inter<T>(QueryOpKind::Not);
1630 return *this;
1631 }
1632#endif
1633
1634 //------------------------------------------------
1635
1636 QueryImpl& changed(Entity entity) {
1637 changed_inter(entity);
1638 return *this;
1639 }
1640
1641#if GAIA_USE_VARIADIC_API
1642 template <typename... T>
1643 QueryImpl& changed() {
1644 // Add commands to the command buffer
1645 (changed_inter<T>(), ...);
1646 return *this;
1647 }
1648#else
1649 template <typename T>
1650 QueryImpl& changed() {
1651 // Add commands to the command buffer
1652 changed_inter<T>();
1653 return *this;
1654 }
1655#endif
1656
1657 //------------------------------------------------
1658
1661 // or anything else to sort by components.
1664 QueryImpl& sort_by(Entity entity, TSortByFunc func) {
1665 sort_by_inter(entity, func);
1666 return *this;
1667 }
1668
1673 template <typename T>
1674 QueryImpl& sort_by(TSortByFunc func) {
1675 sort_by_inter<T>(func);
1676 return *this;
1677 }
1678
1684 template <typename Rel, typename Tgt>
1685 QueryImpl& sort_by(TSortByFunc func) {
1686 sort_by_inter<Rel, Tgt>(func);
1687 return *this;
1688 }
1689
1690 //------------------------------------------------
1691
1695 QueryImpl& group_by(Entity entity, TGroupByFunc func = group_by_func_default) {
1696 group_by_inter(entity, func);
1697 return *this;
1698 }
1699
1703 template <typename T>
1704 QueryImpl& group_by(TGroupByFunc func = group_by_func_default) {
1705 group_by_inter<T>(func);
1706 return *this;
1707 }
1708
1713 template <typename Rel, typename Tgt>
1714 QueryImpl& group_by(TGroupByFunc func = group_by_func_default) {
1715 group_by_inter<Rel, Tgt>(func);
1716 return *this;
1717 }
1718
1719 //------------------------------------------------
1720
1723 QueryImpl& group_id(GroupId groupId) {
1724 set_group_id_inter(groupId);
1725 return *this;
1726 }
1727
1731 GAIA_ASSERT(!entity.pair());
1732 set_group_id_inter(entity.id());
1733 return *this;
1734 }
1735
1738 template <typename T>
1740 set_group_id_inter<T>();
1741 return *this;
1742 }
1743
1744 //------------------------------------------------
1745
1746 template <typename Func>
1747 void each(Func func) {
1748 each_inter<QueryExecType::Default, Func>(func);
1749 }
1750
1751 template <typename Func>
1752 void each(Func func, QueryExecType execType) {
1753 switch (execType) {
1754 case QueryExecType::Parallel:
1755 each_inter<QueryExecType::Parallel, Func>(func);
1756 break;
1757 case QueryExecType::ParallelPerf:
1758 each_inter<QueryExecType::ParallelPerf, Func>(func);
1759 break;
1760 case QueryExecType::ParallelEff:
1761 each_inter<QueryExecType::ParallelEff, Func>(func);
1762 break;
1763 default:
1764 each_inter<QueryExecType::Default, Func>(func);
1765 break;
1766 }
1767 }
1768
1769 //------------------------------------------------
1770
1777 bool empty(Constraints constraints = Constraints::EnabledOnly) {
1778 auto& queryInfo = fetch();
1779 const bool hasFilters = queryInfo.has_filters();
1780
1781 if (hasFilters) {
1782 switch (constraints) {
1783 case Constraints::EnabledOnly:
1784 return empty_inter<true, Iter>(queryInfo);
1785 case Constraints::DisabledOnly:
1786 return empty_inter<true, IterDisabled>(queryInfo);
1787 case Constraints::AcceptAll:
1788 return empty_inter<true, IterAll>(queryInfo);
1789 }
1790 } else {
1791 switch (constraints) {
1792 case Constraints::EnabledOnly:
1793 return empty_inter<false, Iter>(queryInfo);
1794 case Constraints::DisabledOnly:
1795 return empty_inter<false, IterDisabled>(queryInfo);
1796 case Constraints::AcceptAll:
1797 return empty_inter<false, IterAll>(queryInfo);
1798 }
1799 }
1800
1801 return true;
1802 }
1803
1809 uint32_t count(Constraints constraints = Constraints::EnabledOnly) {
1810 auto& queryInfo = fetch();
1811 uint32_t entCnt = 0;
1812
1813 const bool hasFilters = queryInfo.has_filters();
1814 if (hasFilters) {
1815 switch (constraints) {
1816 case Constraints::EnabledOnly: {
1817 entCnt += count_inter<true, Iter>(queryInfo);
1818 } break;
1819 case Constraints::DisabledOnly: {
1820 entCnt += count_inter<true, IterDisabled>(queryInfo);
1821 } break;
1822 case Constraints::AcceptAll: {
1823 entCnt += count_inter<true, IterAll>(queryInfo);
1824 } break;
1825 }
1826 } else {
1827 switch (constraints) {
1828 case Constraints::EnabledOnly: {
1829 entCnt += count_inter<false, Iter>(queryInfo);
1830 } break;
1831 case Constraints::DisabledOnly: {
1832 entCnt += count_inter<false, IterDisabled>(queryInfo);
1833 } break;
1834 case Constraints::AcceptAll: {
1835 entCnt += count_inter<false, IterAll>(queryInfo);
1836 } break;
1837 }
1838 }
1839
1840 return entCnt;
1841 }
1842
1847 template <typename Container>
1848 void arr(Container& outArray, Constraints constraints = Constraints::EnabledOnly) {
1849 const auto entCnt = count();
1850 if (entCnt == 0)
1851 return;
1852
1853 outArray.reserve(entCnt);
1854 auto& queryInfo = fetch();
1855
1856 const bool hasFilters = queryInfo.has_filters();
1857 if (hasFilters) {
1858 switch (constraints) {
1859 case Constraints::EnabledOnly:
1860 arr_inter<true, Iter>(queryInfo, outArray);
1861 break;
1862 case Constraints::DisabledOnly:
1863 arr_inter<true, IterDisabled>(queryInfo, outArray);
1864 break;
1865 case Constraints::AcceptAll:
1866 arr_inter<true, IterAll>(queryInfo, outArray);
1867 break;
1868 }
1869 } else {
1870 switch (constraints) {
1871 case Constraints::EnabledOnly:
1872 arr_inter<false, Iter>(queryInfo, outArray);
1873 break;
1874 case Constraints::DisabledOnly:
1875 arr_inter<false, IterDisabled>(queryInfo, outArray);
1876 break;
1877 case Constraints::AcceptAll:
1878 arr_inter<false, IterAll>(queryInfo, outArray);
1879 break;
1880 }
1881 }
1882 }
1883
1885 void diag() {
1886 // Make sure matching happened
1887 auto& info = fetch();
1888 GAIA_LOG_N("DIAG Query %u.%u [%c]", id(), gen(), UseCaching ? 'C' : 'U');
1889 for (const auto* pArchetype: info)
1890 Archetype::diag_basic_info(*m_storage.world(), *pArchetype);
1891 GAIA_LOG_N("END DIAG Query");
1892 }
1893 };
1894 } // namespace detail
1895
1896 using Query = typename detail::QueryImpl<true>;
1897 using QueryUncached = typename detail::QueryImpl<false>;
1898 } // namespace ecs
1899} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Array of elements of type.
Definition sarray_ext_impl.h:27
Definition span_impl.h:99
Definition archetype.h:82
Definition chunk.h:29
GAIA_NODISCARD const ComponentCacheItem & get(detail::ComponentDescId compDescId) const noexcept
Returns the component cache item given the compDescId.
Definition component_cache.h:156
Definition query_cache.h:47
QueryInfo & add(QueryCtx &&ctx, const EntityToArchetypeMap &entityToArchetypeMap, const ArchetypeDArray &allArchetypes)
Registers the provided query lookup context ctx. If it already exists it is returned.
Definition query_cache.h:120
QueryInfo & get(QueryHandle handle)
Returns a QueryInfo object associated with handle.
Definition query_cache.h:105
bool del(QueryHandle handle)
Deletes an existing QueryInfo object given the provided query handle.
Definition query_cache.h:158
QueryInfo * try_get(QueryHandle handle)
Returns a QueryInfo object associated with handle.
Definition query_cache.h:92
Definition query_info.h:37
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:714
Definition world.h:48
Definition query.h:358
QueryImpl & sort_by(Entity entity, TSortByFunc func)
Sorts the query by the specified entity and function.
Definition query.h:1664
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:1695
static void run_query_func(World *pWorld, Func func, std::span< ChunkBatch > batches)
Execute the functor in batches.
Definition query.h:792
void diag()
Run diagnostics.
Definition query.h:1885
QueryImpl & group_by(TGroupByFunc func=group_by_func_default)
Organizes matching archetypes into groups according to the grouping function.
Definition query.h:1704
void reset()
Release any data allocated by the query.
Definition query.h:1442
uint32_t count(Constraints constraints=Constraints::EnabledOnly)
Calculates the number of entities matching the query.
Definition query.h:1809
QueryImpl & sort_by(TSortByFunc func)
Sorts the query by the specified pair and function.
Definition query.h:1685
bool empty(Constraints constraints=Constraints::EnabledOnly)
Returns true or false depending on whether there are any entities matching the query.
Definition query.h:1777
QueryInfo & fetch()
Fetches the QueryInfo object.
Definition query.h:429
QueryImpl & group_id()
Selects the group to iterate over.
Definition query.h:1739
QueryImpl & add(const char *str,...)
Creates a query from a null-terminated expression string.
Definition query.h:1486
void arr(Container &outArray, Constraints constraints=Constraints::EnabledOnly)
Appends all components or entities matching the query to the output array.
Definition query.h:1848
GAIA_NODISCARD bool is_cached() const
Returns true if the query is stored in the query cache.
Definition query.h:1451
QueryImpl & group_id(GroupId groupId)
Selects the group to iterate over.
Definition query.h:1723
QueryImpl & group_by(TGroupByFunc func=group_by_func_default)
Organizes matching archetypes into groups according to the grouping function.
Definition query.h:1714
QueryImpl & group_id(Entity entity)
Selects the group to iterate over.
Definition query.h:1730
QueryImpl & sort_by(TSortByFunc func)
Sorts the query by the specified component and function.
Definition query.h:1674
static void run_query_func(World *pWorld, Func func, ChunkBatch &batch)
Execute the functor for a given chunk batch.
Definition query.h:780
void seek(uint32_t pos)
Changes the current position in the buffer.
Definition ser_buffer_binary.h:69
Definition ser_buffer_binary.h:154
Definition robin_hood.h:720
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
@ PREFETCH_HINT_T2
Temporal data with respect to second level cache misses — prefetch data into L3 cache and higher,...
Definition config_core_end.h:127
GAIA_FORCEINLINE void prefetch(const void *x, int hint)
Prefetch intrinsic.
Definition config_core_end.h:134
SymbolLookupKey name
Component name.
Definition component_cache_item.h:54
Definition id.h:225
Entity sortBy
Entity to sort the archetypes by. EntityBad for no sorting.
Definition query_common.h:237
Entity groupBy
Entity to group the archetypes by. EntityBad for no grouping.
Definition query_common.h:241
GroupId groupIdSet
Iteration will be restricted only to target Group.
Definition query_common.h:233
Definition query_common.h:197
ComponentCache * cc
Component cache.
Definition query_common.h:201
Definition query_common.h:183
QueryHandle handle
Query id.
Definition query_common.h:185
User-provided query input.
Definition query_common.h:147
Entity id
Entity/Component/Pair to query.
Definition query_common.h:153
QueryAccess access
Access type.
Definition query_common.h:151
Entity src
Source entity to query the id on. If id==EntityBad the source is fixed. If id!=src the source is vari...
Definition query_common.h:157
QueryOpKind op
Operation to perform with the input.
Definition query_common.h:149
void reset()
Release any data allocated by the query.
Definition query.h:334
void invalidate()
Does nothing for uncached queries.
Definition query.h:344
GAIA_NODISCARD bool is_cached() const
Does nothing for uncached queries.
Definition query.h:347
GAIA_NODISCARD bool try_del_from_cache()
Does nothing for uncached queries.
Definition query.h:339
GAIA_NODISCARD bool is_initialized() const
Returns true. Uncached queries are always considered initialized.
Definition query.h:352
QueryIdentity m_q
Query identity.
Definition query.h:187
GAIA_NODISCARD bool try_del_from_cache()
Try delete the query from query cache.
Definition query.h:282
void reset()
Release any data allocated by the query.
Definition query.h:272
QueryCache * m_queryCache
QueryImpl cache (stable pointer to parent world's query cache)
Definition query.h:185
GAIA_NODISCARD bool is_initialized() const
Returns true if the query is ready to be used.
Definition query.h:304
void invalidate()
Invalidates the query handle.
Definition query.h:293
GAIA_NODISCARD bool is_cached() const
Returns true if the query is found in the query cache.
Definition query.h:298