2#include "gaia/config/config.h"
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"
11#if GAIA_OBSERVERS_ENABLED
16 class ObserverBuilder;
19 class ObserverRegistry {
20 friend class ObserverBuilder;
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;
40 GAIA_NODISCARD
bool empty()
const {
41 return direct.empty() && sourceTerm.empty() && traversalRelation.empty() && pairRelation.empty() &&
42 pairTarget.empty() && all.empty() && global.empty();
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;
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);
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;
73 struct PropagatedTargetCacheEntry {
74 uint32_t bindingRelationVersion = 0;
75 uint32_t traversalRelationVersion = 0;
76 cnt::darray<Entity> targets;
80 struct DiffDispatcher {
83 Entity observer = EntityBad;
85 ObserverEvent
event = ObserverEvent::OnAdd;
87 bool monitorsQuery =
false;
89 uint32_t matchesBeforeIdx = UINT32_MAX;
92 struct MatchCacheEntry {
93 ObserverRuntimeData* pObsRepresentative =
nullptr;
94 QueryInfo* pQueryInfoRepresentative =
nullptr;
95 uint64_t queryHash = 0;
96 cnt::darray<Entity> matches;
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;
111 ObserverEvent
event = ObserverEvent::OnAdd;
112 cnt::darray<Snapshot> observers;
113 cnt::darray<MatchCacheEntry> matchesBeforeCache;
114 cnt::darray<Entity> targets;
116 bool targeted =
false;
117 bool targetsAddedAfterPrepare =
false;
119 bool targetsRemovedAfterPrepare =
false;
120 bool resetTraversalCaches =
false;
128 static void collect_query_matches(World& world, ObserverRuntimeData& obs, cnt::darray<Entity>& out);
135 static void collect_query_target_matches(
136 World& world, ObserverRuntimeData& obs, EntitySpan targets, cnt::darray<Entity>& out);
142 static void add_valid_targets(World& world, cnt::darray<Entity>& out, EntitySpan targets);
147 static void copy_target_narrow_plan(
const ObserverRuntimeData& obs, TargetNarrowCacheEntry& entry);
153 GAIA_NODISCARD
static bool
154 same_target_narrow_plan(
const ObserverRuntimeData& obs,
const TargetNarrowCacheEntry& entry);
158 static void normalize_targets(cnt::darray<Entity>& targets);
163 GAIA_NODISCARD
static uint64_t query_hash(ObserverRuntimeData& obs);
169 GAIA_NODISCARD
static bool same_query_ctx(
const QueryCtx& left,
const QueryCtx& right);
175 template <
typename TMatchCache>
176 GAIA_NODISCARD
static int32_t find_match_cache_entry(TMatchCache& cache, ObserverRuntimeData& obs);
185 GAIA_NODISCARD
static Context prepare(
186 ObserverRegistry& registry, World& world, ObserverEvent event, EntitySpan terms,
187 EntitySpan targetEntities = {});
194 GAIA_NODISCARD
static Context prepare_add_new(ObserverRegistry& registry, World& world, EntitySpan terms);
200 static void add_targets(World& world, Context& ctx, EntitySpan targets);
205 static void finish(World& world, Context&& ctx);
208 struct DirectDispatcher {
216 ObserverRegistry& registry, World& world,
const Archetype& archetype, EntitySpan entsAdded,
226 ObserverRegistry& registry, World& world,
const Archetype& archetype, EntitySpan entsRemoved,
234 static void on_set(ObserverRegistry& registry, World& world, Entity term, EntitySpan targets);
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,
260 template <
typename TObserverList>
261 static void collect_diff_from_list(
262 ObserverRegistry& registry, World& world,
const cnt::darray<Entity>& observers, uint64_t matchStamp,
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())
287 template <
typename TObserverMap>
288 GAIA_NODISCARD
static bool has_pair_relations(World& world,
const TObserverMap& map, EntitySpan terms);
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,
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,
323 template <
typename Func>
324 static void each_inherited_term(World& world, Entity baseEntity, Func&& func);
332 template <
typename TObserverMap>
333 GAIA_NODISCARD
static bool has_semantic_is_terms(World& world,
const TObserverMap& map, EntitySpan terms);
341 template <
typename TObserverMap>
342 GAIA_NODISCARD
static bool has_inherited_terms(World& world,
const TObserverMap& map, EntitySpan terms);
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,
363 static void execute_targets(World& world, ObserverRuntimeData& obs, EntitySpan targets, ObserverEvent event);
371 GAIA_NODISCARD
static bool matches_direct_targets(
372 ObserverRuntimeData& obs,
const Archetype& archetype, EntitySpan targets, QueryInfo* pQueryInfo =
nullptr);
375 using DiffDispatchCtx = DiffDispatcher::Context;
379 struct ObserverRuntimeSlot {
382 ObserverRuntimeData runtime;
387 ObserverRuntimeSlot(uint32_t index, uint32_t generation): idx(index), gen(generation) {}
394 GAIA_NODISCARD
static ObserverRuntimeSlot
395 create(uint32_t index, uint32_t generation, [[maybe_unused]]
void* pContext) {
396 return ObserverRuntimeSlot(index, generation);
402 GAIA_NODISCARD
static Entity handle(
const ObserverRuntimeSlot& slot) {
403 return Entity(slot.idx, slot.gen,
false,
false);
407 using ObserverRuntimeSlots = cnt::paged_ilist<ObserverRuntimeSlot, Entity>;
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;
445 class CandidateScope final {
446 ObserverRegistry& m_registry;
447 cnt::darray<ObserverRuntimeData*>* m_pCandidates =
nullptr;
452 explicit CandidateScope(ObserverRegistry& registry): m_registry(registry) {
453 const auto depth = m_registry.m_candidate_depth++;
455 m_pCandidates = &m_registry.m_candidate_observers;
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];
462 m_pCandidates->clear();
467 GAIA_NODISCARD cnt::darray<ObserverRuntimeData*>& candidates() {
468 return *m_pCandidates;
473 GAIA_ASSERT(m_registry.m_candidate_depth > 0);
474 --m_registry.m_candidate_depth;
477 CandidateScope(CandidateScope&&) =
delete;
478 CandidateScope(
const CandidateScope&) =
delete;
479 CandidateScope& operator=(CandidateScope&&) =
delete;
480 CandidateScope& operator=(
const CandidateScope&) =
delete;
484 class DispatchScope final {
485 ObserverRegistry& m_registry;
490 explicit DispatchScope(ObserverRegistry& registry): m_registry(registry) {
491 ++m_registry.m_dispatch_depth;
496 GAIA_ASSERT(m_registry.m_dispatch_depth > 0);
497 if (--m_registry.m_dispatch_depth == 0)
498 m_registry.release_retired_observer_data();
501 DispatchScope(DispatchScope&&) =
delete;
502 DispatchScope(
const DispatchScope&) =
delete;
503 DispatchScope& operator=(DispatchScope&&) =
delete;
504 DispatchScope& operator=(
const DispatchScope&) =
delete;
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();
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);
521 m_retired_observer_data.push_back(handle);
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;
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;
544 GAIA_NODISCARD
static ObserverEvent structural_event(ObserverEvent event, QueryOpKind op) {
545 if (op != QueryOpKind::Not || event == ObserverEvent::OnSet || event == ObserverEvent::None)
547 return event == ObserverEvent::OnAdd ? ObserverEvent::OnDel : ObserverEvent::OnAdd;
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);
564 GAIA_NODISCARD
static bool can_mark_term_observed(World& world, Entity term);
570 GAIA_NODISCARD
static bool is_semantic_is_term(Entity term, QueryMatchKind matchKind = QueryMatchKind::Semantic);
576 void mark_term_observed(World& world, Entity term,
bool observed);
581 void remove_observer_indices(World& world, Entity observer);
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);
594 map.emplace(entityKey, cnt::darray<Entity>{observer});
596 it->second.push_back(observer);
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);
609 map.emplace(entityKey, cnt::darray<Entity>{observer});
611 add_observer_to_list(it->second, observer);
617 static void add_observer_to_list(cnt::darray<Entity>& list, Entity observer) {
618 if (core::has(list, observer))
620 list.push_back(observer);
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);
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);
645 for (
const auto observer: it->second) {
646 if (m_observer_data.find(EntityLookupKey(observer)) != m_observer_data.end())
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);
670 static PropagatedTargetCacheEntry& ensure_propagated_targets_cached(
671 ObserverRegistry& registry, World& world,
const ObserverRuntimeData& obs, Entity changedSource);
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);
691 static void add_propagated_targets_cached(
692 ObserverRegistry& registry, World& world,
const ObserverRuntimeData& obs, Entity changedSource,
693 cnt::darray<Entity>& outTargets);
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);
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);
724 GAIA_NODISCARD
static bool
725 observer_uses_changed_traversal_relation(World& world,
const ObserverRuntimeData& obs, EntitySpan changedTerms);
730 GAIA_NODISCARD
static bool is_dynamic_pair_endpoint(EntityId endpoint) {
731 return is_wildcard(endpoint) || is_variable(endpoint);
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);
747 GAIA_NODISCARD
static bool is_observer_term_globally_dynamic(Entity term) {
748 if (term == EntityBad || term == All)
752 return is_variable((EntityId)term.id());
754 const bool relDynamic = is_dynamic_pair_endpoint(term.id());
755 const bool tgtDynamic = is_dynamic_pair_endpoint(term.gen());
756 return relDynamic && tgtDynamic;
763 GAIA_NODISCARD
bool has_observers(Entity term)
const {
764 return has_observers_for_term(term);
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();
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();
782 GAIA_NODISCARD
bool has_on_set_observers(Entity term)
const {
783 if (!m_hasOnSetObservers)
786 if (observer_map_has_observers(m_observer_map_set, EntityLookupKey(term)))
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)));
804 void add_diff_observer_term(
805 World& world, Entity observer, QueryOpKind op, Entity term,
const QueryTermOptions& options);
813 GAIA_NODISCARD DiffDispatchCtx
814 prepare_diff(World& world, ObserverEvent event, EntitySpan terms, EntitySpan targetEntities = {});
820 GAIA_NODISCARD DiffDispatchCtx prepare_diff_add_new(World& world, EntitySpan terms);
826 void add_diff_targets(World& world, DiffDispatchCtx& ctx, EntitySpan targets);
831 void finish_diff(World& world, DiffDispatchCtx&& ctx);
835 for (
auto& slot: m_observer_runtime_slots) {
836 auto& obs = slot.runtime;
837 obs.on_each_func = {};
840 obs.lastMatchStamp = 0;
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)
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 = {};
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;
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;
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))
884 return &m_observer_runtime_slots[it->second.id()].runtime;
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))
894 return &m_observer_runtime_slots[it->second.id()].runtime;
900 GAIA_NODISCARD ObserverRuntimeData& data(Entity observer) {
901 auto* pData = data_try(observer);
902 GAIA_ASSERT(pData !=
nullptr);
909 GAIA_NODISCARD
const ObserverRuntimeData& data(Entity observer)
const {
910 const auto* pData = data_try(observer);
911 GAIA_ASSERT(pData !=
nullptr);
918 void try_mark_term_observed(World& world, Entity term);
927 add(World& world, Entity term, Entity observer, QueryOpKind op,
928 QueryMatchKind matchKind = QueryMatchKind::Semantic);
933 void del(World& world, Entity term);
940 void on_add(World& world,
const Archetype& archetype, EntitySpan entsAdded, EntitySpan targets);
947 void on_del(World& world,
const Archetype& archetype, EntitySpan entsRemoved, EntitySpan targets);
953 void on_set(World& world, Entity term, EntitySpan targets);