Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
query_cache.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include "gaia/cnt/darray.h"
5#include "gaia/cnt/map.h"
6#include "gaia/cnt/paged_storage.h"
7#include "gaia/core/utility.h"
8#include "gaia/ecs/component.h"
9#include "gaia/ecs/id.h"
10#include "gaia/ecs/query_common.h"
11#include "gaia/ecs/query_info.h"
12
13namespace gaia {
14 namespace cnt {
15 template <>
16 struct to_page_storage_id<ecs::QueryInfo> {
17 static page_storage_id get(const ecs::QueryInfo& item) noexcept {
18 return item.idx;
19 }
20 };
21 } // namespace cnt
22
23 namespace ecs {
25 QueryLookupHash m_hash;
26 const QueryCtx* m_pCtx;
27
28 public:
29 static constexpr bool IsDirectHashKey = true;
30
31 QueryLookupKey(): m_hash({0}), m_pCtx(nullptr) {}
32 explicit QueryLookupKey(QueryLookupHash hash, const QueryCtx* pCtx): m_hash(hash), m_pCtx(pCtx) {}
33
34 size_t hash() const {
35 return (size_t)m_hash.hash;
36 }
37
38 bool operator==(const QueryLookupKey& other) const {
39 // Hash doesn't match we don't have a match.
40 // Hash collisions are expected to be very unlikely so optimize for this case.
41 if GAIA_LIKELY (m_hash != other.m_hash)
42 return false;
43
44 if (m_pCtx == other.m_pCtx)
45 return true;
46
47 const auto lhsReal = m_pCtx->q.handle.id() != QueryIdBad;
48 const auto rhsReal = other.m_pCtx->q.handle.id() != QueryIdBad;
49
50 // Two persisted cached-query keys with different stable context pointers cannot represent
51 // the same query.
52 if (lhsReal && rhsReal)
53 return false;
54
55 // At least one side is a temporary lookup key. Fall back to structural comparison so cached
56 // queries deduplicate correctly regardless of comparison direction.
57 return QueryCtx::equals_no_handle_assumption(*m_pCtx, *other.m_pCtx);
58 }
59 };
60
61 class QueryCache {
62 public:
63 enum class ChangeKind : uint8_t {
64 // Query membership may have changed due to structural world changes.
65 Structural,
66 // Only dynamic/source-driven results may have changed.
67 DynamicResult,
68 // Full query cache invalidation.
69 All,
70 };
71
72 private:
73 QueryInfo& register_query_info(QueryHandle handle, QueryInfo& info) {
74 // Add the entity->query pair
75 add_entity_to_query_pairs(info.ctx().data.ids_view(), handle);
76 add_rel_to_query_pairs(info.ctx(), handle);
77 add_sort_to_query_pairs(info.ctx(), handle);
78 add_sorted_query(info.ctx(), handle);
79 add_create_to_query_pairs(info.ctx(), handle);
80
81 return info;
82 }
83
86 struct CreateQueryCandidate {
87 QueryHandle handle;
88 Entity matchedSelector = EntityBad;
89 };
90
93 enum class CreateSelectorKind : uint8_t {
94 Other,
95 ExactPair,
96 RelWildcardPair,
97 TgtWildcardPair,
98 AnyPairWildcard,
99 Count
100 };
101
102 struct TrackedArchetypes {
104 uint32_t syncedRevision = 0;
105 };
106
110
118 cnt::darray<QueryHandle> m_sortedQueries;
126 cnt::darray<CreateQueryCandidate> m_createQueryHandleScratch;
128 cnt::darray<uint32_t> m_createQueryHandleStampById;
129 uint32_t m_createQueryHandleStamp = 1;
131 uint32_t m_createQuerySelectorCnt[(size_t)CreateSelectorKind::Count] = {};
132
133 public:
134 QueryCache() {
135 m_queryArr.reserve(256);
136 }
137
138 ~QueryCache() = default;
139
140 QueryCache(QueryCache&&) = delete;
141 QueryCache(const QueryCache&) = delete;
142 QueryCache& operator=(QueryCache&&) = delete;
143 QueryCache& operator=(const QueryCache&) = delete;
144
145 GAIA_NODISCARD bool valid(QueryHandle handle) const {
146 if (handle.id() == QueryIdBad)
147 return false;
148
149 if (!m_queryArr.has(handle.id()))
150 return false;
151
152 const auto& h = m_queryArr[handle.id()];
153 return h.idx == handle.id() && h.gen == handle.gen();
154 }
155
156 void clear() {
157 m_pCache.clear();
158 m_queryArr.clear();
159 m_entityToQuery.clear();
160 m_relationToQuery.clear();
161 m_sortEntityToQuery.clear();
162 m_sortedQueries.clear();
163 m_entityToCreateQuery.clear();
164 m_archetypeToQuery.clear();
165 m_queryToArchetype.clear();
166 m_createQueryHandleScratch.clear();
167 m_createQueryHandleStampById.clear();
168 m_createQueryHandleStamp = 1;
169 for (auto& cnt: m_createQuerySelectorCnt)
170 cnt = 0;
171 }
172
176 m_archetypeToQuery.clear();
177 m_queryToArchetype.clear();
178 }
179
184 if (!valid(handle))
185 return nullptr;
186
187 auto& info = m_queryArr[handle.id()];
188 GAIA_ASSERT(info.idx == handle.id());
189 GAIA_ASSERT(info.gen == handle.gen());
190 return &info;
191 }
192
197 GAIA_ASSERT(valid(handle));
198
199 auto& info = m_queryArr[handle.id()];
200 GAIA_ASSERT(info.idx == handle.id());
201 GAIA_ASSERT(info.gen == handle.gen());
202 return info;
203 }
204
210 QueryInfo&
211 add(QueryCtx&& ctx, //
212 const EntityToArchetypeMap& entityToArchetypeMap, //
213 std::span<const Archetype*> allArchetypes) {
214 GAIA_ASSERT(ctx.hashLookup.hash != 0);
215
216 // First check if the query cache record exists
217 auto ret = m_pCache.try_emplace(QueryLookupKey(ctx.hashLookup, &ctx), nullptr);
218 if (!ret.second) {
219 auto* pInfo = ret.first->second;
220 GAIA_ASSERT(pInfo != nullptr);
221 pInfo->add_ref();
222 return *pInfo;
223 }
224
225 // No record exists, let us create a new one
226 QueryInfoCreationCtx creationCtx{};
227 creationCtx.pQueryCtx = &ctx;
228 creationCtx.pEntityToArchetypeMap = &entityToArchetypeMap;
229 creationCtx.allArchetypes = allArchetypes;
230 auto handle = m_queryArr.alloc(&creationCtx);
231
232 // We are moving the rvalue to "ctx". As a result, the pointer stored in m_pCache.emplace above is no longer
233 // going to be valid. Therefore we swap the map key with a one with a valid pointer.
234 auto& info = get(handle);
235 info.add_ref();
236 ret.first->second = &info;
237 auto new_p = robin_hood::pair(std::make_pair(QueryLookupKey(ctx.hashLookup, &info.ctx()), &info));
238 ret.first->swap(new_p);
239
240 return register_query_info(handle, info);
241 }
242
245 QueryCtx&& ctx, //
246 const EntityToArchetypeMap& entityToArchetypeMap, //
247 std::span<const Archetype*> allArchetypes) {
248 QueryInfoCreationCtx creationCtx{};
249 creationCtx.pQueryCtx = &ctx;
250 creationCtx.pEntityToArchetypeMap = &entityToArchetypeMap;
251 creationCtx.allArchetypes = allArchetypes;
252 auto handle = m_queryArr.alloc(&creationCtx);
253
254 auto& info = get(handle);
255 info.add_ref();
256 return register_query_info(handle, info);
257 }
258
262 bool del(QueryHandle handle) {
263 auto* pInfo = try_get(handle);
264 if (pInfo == nullptr)
265 return false;
266
267 pInfo->dec_ref();
268 if (pInfo->refs() != 0)
269 return false;
270
271 unregister_query_archetypes(handle);
272
273 // If this was the last reference to the query, we can safely remove it
274 auto it = m_pCache.find(QueryLookupKey(pInfo->ctx().hashLookup, &pInfo->ctx()));
275 if (it != m_pCache.end())
276 m_pCache.erase(it);
277
278 // Remove the entity->query pair
279 del_entity_to_query_pairs(pInfo->ctx().data.ids_view(), handle);
280 del_rel_to_query_pairs(pInfo->ctx(), handle);
281 del_sort_to_query_pairs(pInfo->ctx(), handle);
282 del_sorted_query(pInfo->ctx(), handle);
283 del_create_to_query_pairs(pInfo->ctx(), handle);
284 m_queryArr.free(handle);
285
286 return true;
287 }
288
289 auto begin() {
290 return m_queryArr.begin();
291 }
292
293 auto end() {
294 return m_queryArr.end();
295 }
296
303 void invalidate_queries_for_entity(EntityLookupKey entityKey, ChangeKind changeKind) {
304 auto it = m_entityToQuery.find(entityKey);
305 if (it == m_entityToQuery.end())
306 return;
307
308 const auto& handles = it->second;
309 for (const auto& handle: handles) {
310 auto& info = get(handle);
311 // World mutations invalidate cached results, but they do not change query shape.
312 // Recomputing QueryCtx metadata here only adds overhead to the invalidation path.
313 info.invalidate(select_invalidation_kind(info, changeKind));
314 }
315 }
316
317 void invalidate_queries_for_rel(Entity relation, ChangeKind changeKind) {
318 auto it = m_relationToQuery.find(EntityLookupKey(relation));
319 if (it == m_relationToQuery.end())
320 return;
321
322 for (const auto handle: it->second) {
323 auto& info = get(handle);
324 // Relation changes affect dynamic freshness, not the query definition itself.
325 info.invalidate(select_invalidation_kind(info, changeKind));
326 }
327 }
328
329 void invalidate_sorted_queries_for_entity(Entity entity) {
330 auto it = m_sortEntityToQuery.find(EntityLookupKey(entity));
331 if (it == m_sortEntityToQuery.end())
332 return;
333
334 for (const auto handle: it->second) {
335 auto* pInfo = try_get(handle);
336 if (pInfo == nullptr || pInfo->refs() == 0)
337 continue;
338
339 pInfo->invalidate_sort();
340 }
341 }
342
345 for (const auto handle: m_sortedQueries) {
346 auto* pInfo = try_get(handle);
347 if (pInfo == nullptr || pInfo->refs() == 0)
348 continue;
349
350 pInfo->invalidate_sort();
351 }
352 }
353
354 void sync_archetype_cache(QueryInfo& queryInfo) {
355 const auto handle = QueryInfo::handle(queryInfo);
356 if (!valid(handle))
357 return;
358
359 const auto archetypes = queryInfo.cache_archetype_view();
360 const auto key = QueryHandleLookupKey(handle);
361 auto it = m_queryToArchetype.find(key);
362 if (it != m_queryToArchetype.end() && it->second.syncedRevision == queryInfo.reverse_index_revision())
363 return;
364
365 unregister_query_archetypes(handle);
366
367 if (archetypes.empty())
368 return;
369
370 auto [trackedIt, inserted] = m_queryToArchetype.try_emplace(key);
371 auto& tracked = trackedIt->second.archetypes;
372 if (!inserted)
373 tracked.clear();
374
375 tracked.reserve((uint32_t)archetypes.size());
376 for (const auto* pArchetype: archetypes) {
377 tracked.push_back(pArchetype);
378 add_archetype_query_pair(pArchetype, handle);
379 }
380 trackedIt->second.syncedRevision = queryInfo.reverse_index_revision();
381 }
382
383 void remove_archetype_from_queries(Archetype* pArchetype) {
384 const auto archetypeKey = ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash());
385 auto it = m_archetypeToQuery.find(archetypeKey);
386 if (it == m_archetypeToQuery.end())
387 return;
388
389 const auto handles = it->second;
390 for (const auto handle: handles) {
391 auto* pInfo = try_get(handle);
392 if (pInfo != nullptr && pInfo->refs() != 0)
393 pInfo->remove(pArchetype);
394
395 auto trackedIt = m_queryToArchetype.find(QueryHandleLookupKey(handle));
396 if (trackedIt == m_queryToArchetype.end())
397 continue;
398
399 auto& tracked = trackedIt->second.archetypes;
400 core::swap_erase(tracked, core::get_index(tracked, pArchetype));
401 if (trackedIt->second.archetypes.empty())
402 m_queryToArchetype.erase(trackedIt);
403 }
404
405 m_archetypeToQuery.erase(it);
406 }
407
408 void register_archetype_with_queries(const Archetype* pArchetype) {
409 auto& handles = prepare_create_query_handles();
410 const bool needsExactPairSelectors = has_create_selector_kind(CreateSelectorKind::ExactPair);
411 const bool needsRelWildcardSelectors = has_create_selector_kind(CreateSelectorKind::RelWildcardPair);
412 const bool needsTgtWildcardSelectors = has_create_selector_kind(CreateSelectorKind::TgtWildcardPair);
413 const bool needsAnyPairWildcardSelectors = has_create_selector_kind(CreateSelectorKind::AnyPairWildcard);
414 bool hasAnyPair = false;
415 cnt::darray_ext<Entity, 16> pairWildcardRelations;
416 for (const auto entity: pArchetype->ids_view()) {
417 if (!entity.pair()) {
418 add_create_query_handles(entity, handles);
419 continue;
420 }
421
422 hasAnyPair = true;
423 if (needsExactPairSelectors)
424 add_create_query_handles(entity, handles);
425
426 // Pair ids retain the relation/target ids plus their kind bits. That is enough to
427 // rebuild wildcard pair lookup keys without touching the world record storage.
428 const auto relKind = entity.entity() ? EntityKind::EK_Uni : EntityKind::EK_Gen;
429 const auto rel = Entity((EntityId)entity.id(), 0, false, false, relKind);
430 const auto tgt = Entity((EntityId)entity.gen(), 0, false, false, entity.kind());
431 if (needsTgtWildcardSelectors)
432 add_create_query_handles(Pair(All, tgt), handles);
433 if (needsRelWildcardSelectors && !core::has(pairWildcardRelations, rel)) {
434 pairWildcardRelations.push_back(rel);
435 add_create_query_handles(Pair(rel, All), handles);
436 }
437 }
438
439 if (hasAnyPair && needsAnyPairWildcardSelectors)
440 add_create_query_handles(Pair(All, All), handles);
441
442 for (const auto& candidate: handles) {
443 auto* pInfo = try_get(candidate.handle);
444 if (pInfo == nullptr || pInfo->refs() == 0)
445 continue;
446
447 if (!pInfo->register_archetype(*pArchetype, candidate.matchedSelector, true))
448 continue;
449
450 register_query_archetype(candidate.handle, pArchetype, pInfo->reverse_index_revision());
451 }
452 }
453
454 private:
456 static CreateSelectorKind classify_create_selector(Entity entity) {
457 if (!entity.pair())
458 return CreateSelectorKind::Other;
459 if (is_wildcard(entity.id()))
460 return is_wildcard(entity.gen()) ? CreateSelectorKind::AnyPairWildcard : CreateSelectorKind::TgtWildcardPair;
461 if (is_wildcard(entity.gen()))
462 return CreateSelectorKind::RelWildcardPair;
463 return CreateSelectorKind::ExactPair;
464 }
465
466 GAIA_NODISCARD static constexpr uint32_t selector_kind_idx(CreateSelectorKind kind) {
467 return (uint32_t)kind;
468 }
469
470 GAIA_NODISCARD bool has_create_selector_kind(CreateSelectorKind kind) const {
471 return m_createQuerySelectorCnt[selector_kind_idx(kind)] != 0;
472 }
473
475 void track_create_selector(Entity entity) {
476 const auto kind = classify_create_selector(entity);
477 ++m_createQuerySelectorCnt[selector_kind_idx(kind)];
478 }
479
481 void untrack_create_selector(Entity entity) {
482 const auto kind = classify_create_selector(entity);
483 auto& cnt = m_createQuerySelectorCnt[selector_kind_idx(kind)];
484 GAIA_ASSERT(cnt != 0);
485 --cnt;
486 }
487
488 static QueryInfo::InvalidationKind select_invalidation_kind(const QueryInfo& info, ChangeKind changeKind) {
489 switch (changeKind) {
490 case ChangeKind::DynamicResult:
492 case ChangeKind::All:
494 case ChangeKind::Structural:
495 // Structural changes invalidate seed caches for structural queries.
496 // Dynamic queries reuse structural compilation state and only need their
497 // final result refreshed on the next read.
498 return (info.ctx().data.deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms) ||
499 info.ctx().data.deps.has_dep_flag(QueryCtx::DependencyHasVariableTerms))
502 }
503
504 GAIA_ASSERT(false);
506 }
507
511 void add_entity_query_pair(Entity entity, QueryHandle handle) {
512 EntityLookupKey entityKey(entity);
513 const auto it = m_entityToQuery.find(entityKey);
514 if (it == m_entityToQuery.end()) {
515 m_entityToQuery.try_emplace(entityKey, cnt::darray<QueryHandle>{handle});
516 return;
517 }
518
519 auto& handles = it->second;
520 if (!core::has(handles, handle))
521 handles.push_back(handle);
522 }
523
527 void del_entity_query_pair(Entity entity, QueryHandle handle) {
528 auto it = m_entityToQuery.find(EntityLookupKey(entity));
529 if (it == m_entityToQuery.end())
530 return;
531
532 auto& handles = it->second;
533 const auto idx = core::get_index_unsafe(handles, handle);
534 core::swap_erase_unsafe(handles, idx);
535
536 // Remove the mapping if there are no more matches
537 if (handles.empty())
538 m_entityToQuery.erase(it);
539 }
540
543 void add_entity_to_query_pairs(EntitySpan entities, QueryHandle handle) {
544 for (auto entity: entities) {
545 add_entity_query_pair(entity, handle);
546 }
547 }
548
551 void del_entity_to_query_pairs(EntitySpan entities, QueryHandle handle) {
552 for (auto entity: entities) {
553 del_entity_query_pair(entity, handle);
554 }
555 }
556
557 void add_create_to_query_pair(Entity entity, QueryHandle handle) {
558 EntityLookupKey entityKey(entity);
559 const auto it = m_entityToCreateQuery.find(entityKey);
560 if (it == m_entityToCreateQuery.end()) {
561 m_entityToCreateQuery.try_emplace(entityKey, cnt::darray<QueryHandle>{handle});
562 track_create_selector(entity);
563 return;
564 }
565
566 auto& handles = it->second;
567 if (!core::has(handles, handle)) {
568 handles.push_back(handle);
569 track_create_selector(entity);
570 }
571 }
572
573 void add_sort_to_query_pair(Entity entity, QueryHandle handle) {
574 auto it = m_sortEntityToQuery.find(EntityLookupKey(entity));
575 if (it == m_sortEntityToQuery.end()) {
576 m_sortEntityToQuery.try_emplace(EntityLookupKey(entity), cnt::darray<QueryHandle>{handle});
577 return;
578 }
579
580 auto& handles = it->second;
581 if (!core::has(handles, handle))
582 handles.push_back(handle);
583 }
584
585 void del_sort_to_query_pair(Entity entity, QueryHandle handle) {
586 auto it = m_sortEntityToQuery.find(EntityLookupKey(entity));
587 if (it == m_sortEntityToQuery.end())
588 return;
589
590 auto& handles = it->second;
591 core::swap_erase(handles, core::get_index(handles, handle));
592 if (handles.empty())
593 m_sortEntityToQuery.erase(it);
594 }
595
596 void add_sort_to_query_pairs(const QueryCtx& ctx, QueryHandle handle) {
597 if (ctx.data.sortByFunc == nullptr || ctx.data.sortBy == EntityBad)
598 return;
599
600 add_sort_to_query_pair(ctx.data.sortBy, handle);
601 }
602
603 void del_sort_to_query_pairs(const QueryCtx& ctx, QueryHandle handle) {
604 if (ctx.data.sortByFunc == nullptr || ctx.data.sortBy == EntityBad)
605 return;
606
607 del_sort_to_query_pair(ctx.data.sortBy, handle);
608 }
609
610 void add_sorted_query(const QueryCtx& ctx, QueryHandle handle) {
611 if (ctx.data.sortByFunc == nullptr)
612 return;
613
614 m_sortedQueries.push_back(handle);
615 }
616
617 void del_sorted_query(const QueryCtx& ctx, QueryHandle handle) {
618 if (ctx.data.sortByFunc == nullptr)
619 return;
620
621 const auto idx = core::get_index(m_sortedQueries, handle);
622 GAIA_ASSERT(idx != BadIndex);
623 if (idx != BadIndex)
624 core::swap_erase(m_sortedQueries, idx);
625 }
626
627 void del_create_to_query_pair(Entity entity, QueryHandle handle) {
628 auto it = m_entityToCreateQuery.find(EntityLookupKey(entity));
629 if (it == m_entityToCreateQuery.end())
630 return;
631
632 auto& handles = it->second;
633 core::swap_erase(handles, core::get_index(handles, handle));
634 untrack_create_selector(entity);
635 if (handles.empty())
636 m_entityToCreateQuery.erase(it);
637 }
638
639 void add_create_to_query_pairs(const QueryCtx& ctx, QueryHandle handle) {
640 if (ctx.data.cachePolicy != QueryCtx::CachePolicy::Immediate)
641 return;
642
643 // Only structural queries with positive selector dependencies are tracked here.
644 // Dependency metadata is refreshed together with cache policy classification so
645 // create-time propagation can consume it without re-deriving query shape here.
646 for (const auto entity: ctx.data.deps.create_selectors_view())
647 add_create_to_query_pair(entity, handle);
648 }
649
650 void del_create_to_query_pairs(const QueryCtx& ctx, QueryHandle handle) {
651 if (ctx.data.cachePolicy != QueryCtx::CachePolicy::Immediate)
652 return;
653
654 for (const auto entity: ctx.data.deps.create_selectors_view())
655 del_create_to_query_pair(entity, handle);
656 }
657
658 void add_create_query_handles(Entity selector, cnt::darray<CreateQueryCandidate>& handles) {
659 const auto it = m_entityToCreateQuery.find(EntityLookupKey(selector));
660 if (it == m_entityToCreateQuery.end())
661 return;
662
663 for (const auto handle: it->second) {
664 if (mark_create_query_handle(handle))
665 handles.push_back(CreateQueryCandidate{handle, selector});
666 }
667 }
668
669 cnt::darray<CreateQueryCandidate>& prepare_create_query_handles() {
670 m_createQueryHandleScratch.clear();
671
672 // Archetype creation can fan out through many positive selector ids. Use a monotonic stamp table
673 // keyed by query-handle id so duplicate candidates do not devolve into repeated linear scans.
674 ++m_createQueryHandleStamp;
675 if (m_createQueryHandleStamp == 0) {
676 m_createQueryHandleStampById = {};
677 m_createQueryHandleStamp = 1;
678 }
679
680 return m_createQueryHandleScratch;
681 }
682
683 GAIA_NODISCARD bool mark_create_query_handle(QueryHandle handle) {
684 const auto handleId = (uint32_t)handle.id();
685 if (handleId >= m_createQueryHandleStampById.size())
686 m_createQueryHandleStampById.resize(handleId + 1);
687
688 auto& stamp = m_createQueryHandleStampById[handleId];
689 if (stamp == m_createQueryHandleStamp)
690 return false;
691
692 stamp = m_createQueryHandleStamp;
693 return true;
694 }
695
696 void add_archetype_query_pair(const Archetype* pArchetype, QueryHandle handle) {
697 const auto archetypeKey = ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash());
698 const auto it = m_archetypeToQuery.find(archetypeKey);
699 if (it == m_archetypeToQuery.end()) {
700 m_archetypeToQuery.try_emplace(archetypeKey, cnt::darray<QueryHandle>{handle});
701 return;
702 }
703
704 auto& handles = it->second;
705 // Callers only register a <query, archetype> edge after they proved that edge is new.
706 GAIA_ASSERT(!core::has(handles, handle));
707 handles.push_back(handle);
708 }
709
710 void del_archetype_query_pair(const Archetype* pArchetype, QueryHandle handle) {
711 auto it = m_archetypeToQuery.find(ArchetypeIdLookupKey(pArchetype->id(), pArchetype->id_hash()));
712 if (it == m_archetypeToQuery.end())
713 return;
714
715 auto& handles = it->second;
716 const auto idx = core::get_index(handles, handle);
717 GAIA_ASSERT(idx != BadIndex);
718 core::swap_erase(handles, idx);
719 if (handles.empty())
720 m_archetypeToQuery.erase(it);
721 }
722
723 void unregister_query_archetypes(QueryHandle handle) {
724 auto it = m_queryToArchetype.find(QueryHandleLookupKey(handle));
725 if (it == m_queryToArchetype.end())
726 return;
727
728 const auto& tracked = it->second.archetypes;
729 for (const auto* pArchetype: tracked)
730 del_archetype_query_pair(pArchetype, handle);
731
732 m_queryToArchetype.erase(it);
733 }
734
735 void register_query_archetype(QueryHandle handle, const Archetype* pArchetype, uint32_t syncedRevision) {
736 auto [trackedIt, inserted] = m_queryToArchetype.try_emplace(QueryHandleLookupKey(handle));
737 auto& tracked = trackedIt->second.archetypes;
738
739 // Newly-created archetypes and sync_archetype_cache() both route through a deduplicated edge set,
740 // so reverse-index registration can append directly instead of re-scanning tracked archetypes.
741 GAIA_ASSERT(inserted || !core::has(tracked, pArchetype));
742 tracked.push_back(pArchetype);
743 trackedIt->second.syncedRevision = syncedRevision;
744 add_archetype_query_pair(pArchetype, handle);
745 }
746
747 void add_rel_query_pair(Entity relation, QueryHandle handle) {
748 const auto key = EntityLookupKey(relation);
749 const auto it = m_relationToQuery.find(key);
750 if (it == m_relationToQuery.end()) {
751 m_relationToQuery.try_emplace(key, cnt::darray<QueryHandle>{handle});
752 return;
753 }
754
755 auto& handles = it->second;
756 if (!core::has(handles, handle))
757 handles.push_back(handle);
758 }
759
760 void del_rel_query_pair(Entity relation, QueryHandle handle) {
761 auto it = m_relationToQuery.find(EntityLookupKey(relation));
762 if (it == m_relationToQuery.end())
763 return;
764
765 auto& handles = it->second;
766 core::swap_erase(handles, core::get_index(handles, handle));
767 if (handles.empty())
768 m_relationToQuery.erase(it);
769 }
770
771 void add_rel_to_query_pairs(const QueryCtx& ctx, QueryHandle handle) {
772 for (const auto relation: ctx.data.deps.relations_view())
773 add_rel_query_pair(relation, handle);
774 }
775
776 void del_rel_to_query_pairs(const QueryCtx& ctx, QueryHandle handle) {
777 for (const auto relation: ctx.data.deps.relations_view())
778 del_rel_query_pair(relation, handle);
779 }
780 };
781 } // namespace ecs
782} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Definition span_impl.h:99
Definition query_cache.h:61
void clear_archetype_tracking()
Clears only the reverse indices that keep raw archetype pointers alive. Used during world shutdown be...
Definition query_cache.h:175
QueryInfo & add(QueryCtx &&ctx, const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes)
Registers the provided query lookup context ctx. If it already exists it is returned.
Definition query_cache.h:211
QueryInfo & get(QueryHandle handle)
Returns a QueryInfo object associated with handle.
Definition query_cache.h:196
bool del(QueryHandle handle)
Deletes an existing QueryInfo object given the provided query handle.
Definition query_cache.h:262
QueryInfo * try_get(QueryHandle handle)
Returns a QueryInfo object associated with handle.
Definition query_cache.h:183
QueryInfo & add_local(QueryCtx &&ctx, const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes)
Registers a cached query without deduplicating against the shared lookup map.
Definition query_cache.h:244
void invalidate_sorted_queries()
Invalidates all cached sorted queries after chunk row order changes.
Definition query_cache.h:344
void invalidate_queries_for_entity(EntityLookupKey entityKey, ChangeKind changeKind)
Invalidates all cached queries that work with the given entity This covers the following kinds of que...
Definition query_cache.h:303
Definition query_info.h:86
InvalidationKind
Definition query_info.h:132
@ Result
Only the final result cache is stale. Structural seed matches remain valid and can be reused.
@ All
Full invalidation of all query cache state.
@ Seed
Structural seed matches are stale. This also implies the final result cache must be rebuilt.
uint32_t idx
Allocated items: index in the query slot list. Deleted items: index of the next deleted item in the s...
Definition query_info.h:90
uint32_t gen
Generation ID of the query slot.
Definition query_info.h:92
Definition query_cache.h:24
Definition robin_hood.h:720
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Paged implicit list with page-local slot metadata and lazily allocated page payloads....
Definition ilist.h:475
GAIA_NODISCARD TItemHandle alloc(void *ctx)
Allocates a new item in the list.
Definition ilist.h:919
GAIA_NODISCARD iterator begin() noexcept
Returns an iterator over live payload objects only.
Definition ilist.h:830
Definition paged_storage.h:37
Hashmap lookup structure used for Entity.
Definition id.h:439
Definition id.h:241
Definition query_common.h:471
QueryIdentity q
Query identity.
Definition query_common.h:479
Hashmap lookup structure used for Entity.
Definition query_common.h:174
Definition query_common.h:125
QueryHandle handle
Query id.
Definition query_common.h:459
Definition query_info.h:80
Definition robin_hood.h:418