Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
observer_registry.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include "gaia/cnt/darray.h"
5#include "gaia/cnt/darray_ext.h"
6#include "gaia/cnt/ilist.h"
7#include "gaia/cnt/map.h"
8#include "gaia/cnt/set.h"
9#include "gaia/ecs/observer.h"
10
11#if GAIA_OBSERVERS_ENABLED
12namespace gaia {
13 namespace ecs {
14 class World;
15 class Archetype;
16 class ObserverBuilder;
17
19 class ObserverRegistry {
20 friend class ObserverBuilder;
21
22 struct DiffObserverIndex {
24 cnt::map<EntityLookupKey, cnt::darray<Entity>> direct;
26 cnt::map<EntityLookupKey, cnt::darray<Entity>> sourceTerm;
28 cnt::map<EntityLookupKey, cnt::darray<Entity>> traversalRelation;
30 cnt::map<EntityLookupKey, cnt::darray<Entity>> pairRelation;
32 cnt::map<EntityLookupKey, cnt::darray<Entity>> pairTarget;
34 cnt::darray<Entity> all;
36 cnt::darray<Entity> global;
37
40 GAIA_NODISCARD bool empty() const {
41 return direct.empty() && sourceTerm.empty() && traversalRelation.empty() && pairRelation.empty() &&
42 pairTarget.empty() && all.empty() && global.empty();
43 }
44 };
45
46 struct PropagatedTargetCacheKey {
47 Entity bindingRelation = EntityBad;
48 Entity traversalRelation = EntityBad;
49 Entity rootTarget = EntityBad;
50 QueryTravKind travKind = QueryTravKind::None;
51 uint8_t travDepth = QueryTermOptions::TravDepthUnlimited;
52
55 GAIA_NODISCARD size_t hash() const {
56 size_t seed = EntityLookupKey(bindingRelation).hash();
57 seed ^= EntityLookupKey(traversalRelation).hash() + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
58 seed ^= EntityLookupKey(rootTarget).hash() + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
59 seed ^= size_t(travKind) + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
60 seed ^= size_t(travDepth) + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
61 return seed;
62 }
63
67 GAIA_NODISCARD bool operator==(const PropagatedTargetCacheKey& other) const {
68 return bindingRelation == other.bindingRelation && traversalRelation == other.traversalRelation &&
69 rootTarget == other.rootTarget && travKind == other.travKind && travDepth == other.travDepth;
70 }
71 };
72
73 struct PropagatedTargetCacheEntry {
74 uint32_t bindingRelationVersion = 0;
75 uint32_t traversalRelationVersion = 0;
76 cnt::darray<Entity> targets;
77 };
78
79 public:
80 struct DiffDispatcher {
81 struct Snapshot {
83 Entity observer = EntityBad;
85 ObserverEvent event = ObserverEvent::OnAdd;
87 bool monitorsQuery = false;
89 uint32_t matchesBeforeIdx = UINT32_MAX;
90 };
91
92 struct MatchCacheEntry {
93 ObserverRuntimeData* pObsRepresentative = nullptr;
94 QueryInfo* pQueryInfoRepresentative = nullptr;
95 uint64_t queryHash = 0;
96 cnt::darray<Entity> matches;
97 };
98
99 struct TargetNarrowCacheEntry {
100 ObserverPlan::DiffPlan::DispatchKind kind = ObserverPlan::DiffPlan::DispatchKind::LocalTargets;
101 Entity bindingRelation = EntityBad;
102 Entity traversalRelation = EntityBad;
103 QueryTravKind travKind = QueryTravKind::None;
104 uint8_t travDepth = QueryTermOptions::TravDepthUnlimited;
105 QueryEntityArray triggerTerms{};
106 uint8_t triggerTermCount = 0;
107 cnt::darray<Entity> targets;
108 };
109
110 struct Context {
111 ObserverEvent event = ObserverEvent::OnAdd;
112 cnt::darray<Snapshot> observers;
113 cnt::darray<MatchCacheEntry> matchesBeforeCache;
114 cnt::darray<Entity> targets;
115 bool active = false;
116 bool targeted = false;
117 bool targetsAddedAfterPrepare = false;
119 bool targetsRemovedAfterPrepare = false;
120 bool resetTraversalCaches = false;
121 };
122
128 static void collect_query_matches(World& world, ObserverRuntimeData& obs, cnt::darray<Entity>& out);
129
135 static void collect_query_target_matches(
136 World& world, ObserverRuntimeData& obs, EntitySpan targets, cnt::darray<Entity>& out);
137
142 static void add_valid_targets(World& world, cnt::darray<Entity>& out, EntitySpan targets);
143
147 static void copy_target_narrow_plan(const ObserverRuntimeData& obs, TargetNarrowCacheEntry& entry);
148
153 GAIA_NODISCARD static bool
154 same_target_narrow_plan(const ObserverRuntimeData& obs, const TargetNarrowCacheEntry& entry);
155
158 static void normalize_targets(cnt::darray<Entity>& targets);
159
163 GAIA_NODISCARD static uint64_t query_hash(ObserverRuntimeData& obs);
164
169 GAIA_NODISCARD static bool same_query_ctx(const QueryCtx& left, const QueryCtx& right);
170
175 template <typename TMatchCache>
176 GAIA_NODISCARD static int32_t find_match_cache_entry(TMatchCache& cache, ObserverRuntimeData& obs);
177
185 GAIA_NODISCARD static Context prepare(
186 ObserverRegistry& registry, World& world, ObserverEvent event, EntitySpan terms,
187 EntitySpan targetEntities = {});
188
194 GAIA_NODISCARD static Context prepare_add_new(ObserverRegistry& registry, World& world, EntitySpan terms);
195
200 static void add_targets(World& world, Context& ctx, EntitySpan targets);
201
205 static void finish(World& world, Context&& ctx);
206 };
207
208 struct DirectDispatcher {
215 static void on_add(
216 ObserverRegistry& registry, World& world, const Archetype& archetype, EntitySpan entsAdded,
217 EntitySpan targets);
218
225 static void on_del(
226 ObserverRegistry& registry, World& world, const Archetype& archetype, EntitySpan entsRemoved,
227 EntitySpan targets);
228
234 static void on_set(ObserverRegistry& registry, World& world, Entity term, EntitySpan targets);
235 };
236
237 struct SharedDispatch {
248 template <bool DiffOnly, typename TObserverMap, typename TObserverList>
249 static void collect_from_map(
250 ObserverRegistry& registry, World& world, const TObserverMap& map, Entity term, uint64_t matchStamp,
251 TObserverList& out);
252
260 template <typename TObserverList>
261 static void collect_diff_from_list(
262 ObserverRegistry& registry, World& world, const cnt::darray<Entity>& observers, uint64_t matchStamp,
263 TObserverList& out);
264
270 template <typename TObserverMap>
271 GAIA_NODISCARD static bool has_terms(const TObserverMap& map, EntitySpan terms) {
272 for (auto term: terms) {
273 const auto it = map.find(EntityLookupKey(term));
274 if (it != map.end() && !it->second.empty())
275 return true;
276 }
277
278 return false;
279 }
280
287 template <typename TObserverMap>
288 GAIA_NODISCARD static bool has_pair_relations(World& world, const TObserverMap& map, EntitySpan terms);
289
299 template <typename TObserverMap, typename TObserverList>
300 static void collect_for_event_term(
301 ObserverRegistry& registry, World& world, const TObserverMap& map, Entity term, uint64_t matchStamp,
302 TObserverList& out);
303
313 template <typename TObserverMap, typename TObserverList>
314 static void collect_for_is_target(
315 ObserverRegistry& registry, World& world, const TObserverMap& map, Entity target, uint64_t matchStamp,
316 TObserverList& out);
317
323 template <typename Func>
324 static void each_inherited_term(World& world, Entity baseEntity, Func&& func);
325
332 template <typename TObserverMap>
333 GAIA_NODISCARD static bool has_semantic_is_terms(World& world, const TObserverMap& map, EntitySpan terms);
334
341 template <typename TObserverMap>
342 GAIA_NODISCARD static bool has_inherited_terms(World& world, const TObserverMap& map, EntitySpan terms);
343
353 template <typename TObserverMap, typename TObserverList>
354 static void collect_for_inherited_terms(
355 ObserverRegistry& registry, World& world, const TObserverMap& map, Entity baseEntity, uint64_t matchStamp,
356 TObserverList& out);
357
363 static void execute_targets(World& world, ObserverRuntimeData& obs, EntitySpan targets, ObserverEvent event);
364
371 GAIA_NODISCARD static bool matches_direct_targets(
372 ObserverRuntimeData& obs, const Archetype& archetype, EntitySpan targets, QueryInfo* pQueryInfo = nullptr);
373 };
374
375 using DiffDispatchCtx = DiffDispatcher::Context;
376
377 private:
379 struct ObserverRuntimeSlot {
380 uint32_t idx = 0;
381 uint32_t gen = 0;
382 ObserverRuntimeData runtime;
383
387 ObserverRuntimeSlot(uint32_t index, uint32_t generation): idx(index), gen(generation) {}
388
394 GAIA_NODISCARD static ObserverRuntimeSlot
395 create(uint32_t index, uint32_t generation, [[maybe_unused]] void* pContext) {
396 return ObserverRuntimeSlot(index, generation);
397 }
398
402 GAIA_NODISCARD static Entity handle(const ObserverRuntimeSlot& slot) {
403 return Entity(slot.idx, slot.gen, false, false);
404 }
405 };
406
407 using ObserverRuntimeSlots = cnt::paged_ilist<ObserverRuntimeSlot, Entity>;
408
410 cnt::darray<ObserverRuntimeData*> m_candidate_observers;
412 cnt::darray<cnt::darray<ObserverRuntimeData*>*> m_nested_candidate_observers;
414 uint32_t m_candidate_depth = 0;
416 ObserverRuntimeSlots m_observer_runtime_slots;
418 cnt::map<EntityLookupKey, Entity> m_observer_data;
420 cnt::darray<Entity> m_retired_observer_data;
422 uint32_t m_dispatch_depth = 0;
424 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_add;
426 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_del;
428 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_set;
430 bool m_hasOnSetObservers = false;
432 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_add_is;
434 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_del_is;
436 DiffObserverIndex m_diff_index_add;
438 DiffObserverIndex m_diff_index_del;
440 cnt::map<PropagatedTargetCacheKey, PropagatedTargetCacheEntry> m_propagated_target_cache;
442 uint64_t m_current_match_stamp = 0;
443
445 class CandidateScope final {
446 ObserverRegistry& m_registry;
447 cnt::darray<ObserverRuntimeData*>* m_pCandidates = nullptr;
448
449 public:
452 explicit CandidateScope(ObserverRegistry& registry): m_registry(registry) {
453 const auto depth = m_registry.m_candidate_depth++;
454 if (depth == 0) {
455 m_pCandidates = &m_registry.m_candidate_observers;
456 } else {
457 const auto nestedIdx = depth - 1;
458 if (nestedIdx == m_registry.m_nested_candidate_observers.size())
459 m_registry.m_nested_candidate_observers.push_back(new cnt::darray<ObserverRuntimeData*>());
460 m_pCandidates = m_registry.m_nested_candidate_observers[nestedIdx];
461 }
462 m_pCandidates->clear();
463 }
464
467 GAIA_NODISCARD cnt::darray<ObserverRuntimeData*>& candidates() {
468 return *m_pCandidates;
469 }
470
472 ~CandidateScope() {
473 GAIA_ASSERT(m_registry.m_candidate_depth > 0);
474 --m_registry.m_candidate_depth;
475 }
476
477 CandidateScope(CandidateScope&&) = delete;
478 CandidateScope(const CandidateScope&) = delete;
479 CandidateScope& operator=(CandidateScope&&) = delete;
480 CandidateScope& operator=(const CandidateScope&) = delete;
481 };
482
484 class DispatchScope final {
485 ObserverRegistry& m_registry;
486
487 public:
490 explicit DispatchScope(ObserverRegistry& registry): m_registry(registry) {
491 ++m_registry.m_dispatch_depth;
492 }
493
495 ~DispatchScope() {
496 GAIA_ASSERT(m_registry.m_dispatch_depth > 0);
497 if (--m_registry.m_dispatch_depth == 0)
498 m_registry.release_retired_observer_data();
499 }
500
501 DispatchScope(DispatchScope&&) = delete;
502 DispatchScope(const DispatchScope&) = delete;
503 DispatchScope& operator=(DispatchScope&&) = delete;
504 DispatchScope& operator=(const DispatchScope&) = delete;
505 };
506
508 void release_retired_observer_data() {
509 for (auto handle: m_retired_observer_data)
510 m_observer_runtime_slots.free(handle);
511 m_retired_observer_data.clear();
512 }
513
516 void retire_observer_data(Entity handle) {
517 GAIA_ASSERT(m_observer_runtime_slots.has(handle));
518 if (m_dispatch_depth == 0)
519 m_observer_runtime_slots.free(handle);
520 else
521 m_retired_observer_data.push_back(handle);
522 }
523
527 GAIA_NODISCARD DiffObserverIndex& diff_index(ObserverEvent event) {
528 GAIA_ASSERT(event == ObserverEvent::OnAdd || event == ObserverEvent::OnDel);
529 return event == ObserverEvent::OnAdd ? m_diff_index_add : m_diff_index_del;
530 }
531
535 GAIA_NODISCARD const DiffObserverIndex& diff_index(ObserverEvent event) const {
536 GAIA_ASSERT(event == ObserverEvent::OnAdd || event == ObserverEvent::OnDel);
537 return event == ObserverEvent::OnAdd ? m_diff_index_add : m_diff_index_del;
538 }
539
544 GAIA_NODISCARD static ObserverEvent structural_event(ObserverEvent event, QueryOpKind op) {
545 if (op != QueryOpKind::Not || event == ObserverEvent::OnSet || event == ObserverEvent::None)
546 return event;
547 return event == ObserverEvent::OnAdd ? ObserverEvent::OnDel : ObserverEvent::OnAdd;
548 }
549
553 GAIA_NODISCARD bool has_observers_for_term(Entity term) const {
554 const auto termKey = EntityLookupKey(term);
555 return observer_map_has_observers(m_observer_map_add, termKey) ||
556 observer_map_has_observers(m_observer_map_del, termKey) ||
557 observer_map_has_observers(m_observer_map_set, termKey);
558 }
559
564 GAIA_NODISCARD static bool can_mark_term_observed(World& world, Entity term);
565
570 GAIA_NODISCARD static bool is_semantic_is_term(Entity term, QueryMatchKind matchKind = QueryMatchKind::Semantic);
571
576 void mark_term_observed(World& world, Entity term, bool observed);
577
581 void remove_observer_indices(World& world, Entity observer);
582
589 template <typename TObserverMap>
590 static void add_observer_to_map(TObserverMap& map, Entity term, Entity observer) {
591 const auto entityKey = EntityLookupKey(term);
592 const auto it = map.find(entityKey);
593 if (it == map.end())
594 map.emplace(entityKey, cnt::darray<Entity>{observer});
595 else
596 it->second.push_back(observer);
597 }
598
604 template <typename TObserverMap>
605 static void add_observer_to_map_unique(TObserverMap& map, Entity term, Entity observer) {
606 const auto entityKey = EntityLookupKey(term);
607 const auto it = map.find(entityKey);
608 if (it == map.end())
609 map.emplace(entityKey, cnt::darray<Entity>{observer});
610 else
611 add_observer_to_list(it->second, observer);
612 }
613
617 static void add_observer_to_list(cnt::darray<Entity>& list, Entity observer) {
618 if (core::has(list, observer))
619 return;
620 list.push_back(observer);
621 }
622
626 static void remove_observer_from_list(cnt::darray<Entity>& list, Entity observer) {
627 for (uint32_t i = 0; i < list.size();) {
628 if (list[i] == observer)
629 core::swap_erase_unsafe(list, i);
630 else
631 ++i;
632 }
633 }
639 template <typename TObserverMap>
640 GAIA_NODISCARD bool observer_map_has_observers(const TObserverMap& map, const EntityLookupKey& termKey) const {
641 const auto it = map.find(termKey);
642 if (it == map.end())
643 return false;
644
645 for (const auto observer: it->second) {
646 if (m_observer_data.find(EntityLookupKey(observer)) != m_observer_data.end())
647 return true;
648 }
649 return false;
650 }
651
660 static void collect_traversal_descendants(
661 World& world, Entity relation, Entity root, QueryTravKind travKind, uint8_t travDepth, uint64_t visitStamp,
662 cnt::darray<Entity>& outTargets);
663
670 static PropagatedTargetCacheEntry& ensure_propagated_targets_cached(
671 ObserverRegistry& registry, World& world, const ObserverRuntimeData& obs, Entity changedSource);
672
681 static void collect_propagated_targets_cached(
682 ObserverRegistry& registry, World& world, const ObserverRuntimeData& obs, Entity changedSource,
683 uint64_t visitStamp, cnt::set<EntityLookupKey>& visitedPairs, cnt::darray<Entity>& outTargets);
684
691 static void add_propagated_targets_cached(
692 ObserverRegistry& registry, World& world, const ObserverRuntimeData& obs, Entity changedSource,
693 cnt::darray<Entity>& outTargets);
694
703 GAIA_NODISCARD static bool collect_source_traversal_diff_targets(
704 ObserverRegistry& registry, World& world, ObserverRuntimeData& obs, EntitySpan changedTerms,
705 EntitySpan changedSources, cnt::darray<Entity>& outTargets);
706
715 GAIA_NODISCARD static bool collect_diff_targets_for_observer(
716 ObserverRegistry& registry, World& world, ObserverRuntimeData& obs, EntitySpan changedTerms,
717 EntitySpan changedTargets, cnt::darray<Entity>& outTargets);
718
724 GAIA_NODISCARD static bool
725 observer_uses_changed_traversal_relation(World& world, const ObserverRuntimeData& obs, EntitySpan changedTerms);
726
730 GAIA_NODISCARD static bool is_dynamic_pair_endpoint(EntityId endpoint) {
731 return is_wildcard(endpoint) || is_variable(endpoint);
732 }
733
738 static void pair_endpoint_entities(Entity term, Entity& rel, Entity& tgt) {
739 GAIA_ASSERT(term.pair());
740 rel = Entity((EntityId)term.id(), 0, false, false);
741 tgt = Entity((EntityId)term.gen(), 0, false, false);
742 }
743
747 GAIA_NODISCARD static bool is_observer_term_globally_dynamic(Entity term) {
748 if (term == EntityBad || term == All)
749 return true;
750
751 if (!term.pair())
752 return is_variable((EntityId)term.id());
753
754 const bool relDynamic = is_dynamic_pair_endpoint(term.id());
755 const bool tgtDynamic = is_dynamic_pair_endpoint(term.gen());
756 return relDynamic && tgtDynamic;
757 }
758
759 public:
763 GAIA_NODISCARD bool has_observers(Entity term) const {
764 return has_observers_for_term(term);
765 }
766
769 GAIA_NODISCARD bool has_on_add_observers() const {
770 return !m_observer_map_add.empty() || !m_observer_map_add_is.empty() || !m_diff_index_add.empty();
771 }
772
775 GAIA_NODISCARD bool has_on_del_observers() const {
776 return !m_observer_map_del.empty() || !m_observer_map_del_is.empty() || !m_diff_index_del.empty();
777 }
778
782 GAIA_NODISCARD bool has_on_set_observers(Entity term) const {
783 if (!m_hasOnSetObservers)
784 return false;
785
786 if (observer_map_has_observers(m_observer_map_set, EntityLookupKey(term)))
787 return true;
788 if (!term.pair())
789 return false;
790
791 Entity rel, tgt;
792 pair_endpoint_entities(term, rel, tgt);
793 return observer_map_has_observers(m_observer_map_set, EntityLookupKey(Pair(rel, All))) ||
794 observer_map_has_observers(m_observer_map_set, EntityLookupKey(Pair(All, tgt))) ||
795 observer_map_has_observers(m_observer_map_set, EntityLookupKey(Pair(All, All)));
796 }
797
804 void add_diff_observer_term(
805 World& world, Entity observer, QueryOpKind op, Entity term, const QueryTermOptions& options);
806
813 GAIA_NODISCARD DiffDispatchCtx
814 prepare_diff(World& world, ObserverEvent event, EntitySpan terms, EntitySpan targetEntities = {});
815
820 GAIA_NODISCARD DiffDispatchCtx prepare_diff_add_new(World& world, EntitySpan terms);
821
826 void add_diff_targets(World& world, DiffDispatchCtx& ctx, EntitySpan targets);
827
831 void finish_diff(World& world, DiffDispatchCtx&& ctx);
832
834 void teardown() {
835 for (auto& slot: m_observer_runtime_slots) {
836 auto& obs = slot.runtime;
837 obs.on_each_func = {};
838 obs.query = {};
839 obs.plan = {};
840 obs.lastMatchStamp = 0;
841 }
842
843 m_observer_data = {};
844 m_observer_runtime_slots = {};
845 m_retired_observer_data = {};
846 m_dispatch_depth = 0;
847 m_candidate_observers = {};
848 for (auto* pCandidates: m_nested_candidate_observers)
849 delete pCandidates;
850 m_nested_candidate_observers = {};
851 m_candidate_depth = 0;
852 m_observer_map_add = {};
853 m_observer_map_del = {};
854 m_observer_map_set = {};
855 m_hasOnSetObservers = false;
856 m_observer_map_add_is = {};
857 m_observer_map_del_is = {};
858 m_diff_index_add = {};
859 m_diff_index_del = {};
860 m_propagated_target_cache = {};
861 }
862
866 ObserverRuntimeData& data_add(Entity observer) {
867 const auto key = EntityLookupKey(observer);
868 const auto it = m_observer_data.find(key);
869 if (it != m_observer_data.end())
870 return m_observer_runtime_slots[it->second.id()].runtime;
871
872 const auto handle = m_observer_runtime_slots.alloc(nullptr);
873 m_observer_data.emplace(key, handle);
874 return m_observer_runtime_slots[handle.id()].runtime;
875 }
876
880 GAIA_NODISCARD ObserverRuntimeData* data_try(Entity observer) {
881 const auto it = m_observer_data.find(EntityLookupKey(observer));
882 if (it == m_observer_data.end() || !m_observer_runtime_slots.has(it->second))
883 return nullptr;
884 return &m_observer_runtime_slots[it->second.id()].runtime;
885 }
886
890 GAIA_NODISCARD const ObserverRuntimeData* data_try(Entity observer) const {
891 const auto it = m_observer_data.find(EntityLookupKey(observer));
892 if (it == m_observer_data.end() || !m_observer_runtime_slots.has(it->second))
893 return nullptr;
894 return &m_observer_runtime_slots[it->second.id()].runtime;
895 }
896
900 GAIA_NODISCARD ObserverRuntimeData& data(Entity observer) {
901 auto* pData = data_try(observer);
902 GAIA_ASSERT(pData != nullptr);
903 return *pData;
904 }
905
909 GAIA_NODISCARD const ObserverRuntimeData& data(Entity observer) const {
910 const auto* pData = data_try(observer);
911 GAIA_ASSERT(pData != nullptr);
912 return *pData;
913 }
914
918 void try_mark_term_observed(World& world, Entity term);
919
926 void
927 add(World& world, Entity term, Entity observer, QueryOpKind op,
928 QueryMatchKind matchKind = QueryMatchKind::Semantic);
929
933 void del(World& world, Entity term);
934
940 void on_add(World& world, const Archetype& archetype, EntitySpan entsAdded, EntitySpan targets);
941
947 void on_del(World& world, const Archetype& archetype, EntitySpan entsRemoved, EntitySpan targets);
948
953 void on_set(World& world, Entity term, EntitySpan targets);
954 };
955 } // namespace ecs
956} // namespace gaia
957#endif