2#include "gaia/config/config.h"
4#include "gaia/cnt/darray.h"
5#include "gaia/cnt/map.h"
6#include "gaia/cnt/set.h"
7#include "gaia/ecs/observer.h"
9#if GAIA_OBSERVERS_ENABLED
16 class ObserverRegistry {
17 struct DiffObserverIndex {
19 cnt::map<EntityLookupKey, cnt::darray<Entity>> direct;
21 cnt::map<EntityLookupKey, cnt::darray<Entity>> sourceTerm;
23 cnt::map<EntityLookupKey, cnt::darray<Entity>> traversalRelation;
25 cnt::map<EntityLookupKey, cnt::darray<Entity>> pairRelation;
27 cnt::map<EntityLookupKey, cnt::darray<Entity>> pairTarget;
29 cnt::darray<Entity> all;
31 cnt::darray<Entity> global;
34 GAIA_NODISCARD
bool empty()
const {
35 return direct.empty() && sourceTerm.empty() && traversalRelation.empty() && pairRelation.empty() &&
36 pairTarget.empty() && all.empty() && global.empty();
40 struct PropagatedTargetCacheKey {
41 Entity bindingRelation = EntityBad;
42 Entity traversalRelation = EntityBad;
43 Entity rootTarget = EntityBad;
44 QueryTravKind travKind = QueryTravKind::None;
45 uint8_t travDepth = QueryTermOptions::TravDepthUnlimited;
47 GAIA_NODISCARD
size_t hash()
const {
48 size_t seed = EntityLookupKey(bindingRelation).hash();
49 seed ^= EntityLookupKey(traversalRelation).hash() + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
50 seed ^= EntityLookupKey(rootTarget).hash() + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
51 seed ^= size_t(travKind) + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
52 seed ^= size_t(travDepth) + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
56 GAIA_NODISCARD
bool operator==(
const PropagatedTargetCacheKey& other)
const {
57 return bindingRelation == other.bindingRelation && traversalRelation == other.traversalRelation &&
58 rootTarget == other.rootTarget && travKind == other.travKind && travDepth == other.travDepth;
62 struct PropagatedTargetCacheEntry {
63 uint32_t bindingRelationVersion = 0;
64 uint32_t traversalRelationVersion = 0;
65 cnt::darray<Entity> targets;
69 struct DiffDispatcher {
71 ObserverRuntimeData* pObs =
nullptr;
72 uint32_t matchesBeforeIdx = UINT32_MAX;
75 struct MatchCacheEntry {
76 ObserverRuntimeData* pObsRepresentative =
nullptr;
77 QueryInfo* pQueryInfoRepresentative =
nullptr;
78 uint64_t queryHash = 0;
79 cnt::darray<Entity> matches;
82 struct TargetNarrowCacheEntry {
83 ObserverPlan::DiffPlan::DispatchKind kind = ObserverPlan::DiffPlan::DispatchKind::LocalTargets;
84 Entity bindingRelation = EntityBad;
85 Entity traversalRelation = EntityBad;
86 QueryTravKind travKind = QueryTravKind::None;
87 uint8_t travDepth = QueryTermOptions::TravDepthUnlimited;
88 QueryEntityArray triggerTerms{};
89 uint8_t triggerTermCount = 0;
90 cnt::darray<Entity> targets;
94 ObserverEvent
event = ObserverEvent::OnAdd;
95 cnt::darray<Snapshot> observers;
96 cnt::darray<MatchCacheEntry> matchesBeforeCache;
97 cnt::darray<Entity> targets;
99 bool targeted =
false;
100 bool targetsAddedAfterPrepare =
false;
101 bool resetTraversalCaches =
false;
104 static void collect_query_matches(World& world, ObserverRuntimeData& obs, cnt::darray<Entity>& out);
105 static void collect_query_target_matches(
106 World& world, ObserverRuntimeData& obs, EntitySpan targets, cnt::darray<Entity>& out);
107 static void add_valid_targets(World& world, cnt::darray<Entity>& out, EntitySpan targets);
108 static void copy_target_narrow_plan(
const ObserverRuntimeData& obs, TargetNarrowCacheEntry& entry);
109 GAIA_NODISCARD
static bool
110 same_target_narrow_plan(
const ObserverRuntimeData& obs,
const TargetNarrowCacheEntry& entry);
111 static void normalize_targets(cnt::darray<Entity>& targets);
112 GAIA_NODISCARD
static uint64_t query_hash(ObserverRuntimeData& obs);
113 GAIA_NODISCARD
static bool same_query_ctx(
const QueryCtx& left,
const QueryCtx& right);
114 GAIA_NODISCARD
static int32_t
115 find_match_cache_entry(cnt::darray<MatchCacheEntry>& cache, ObserverRuntimeData& obs);
116 GAIA_NODISCARD
static Context prepare(
117 ObserverRegistry& registry, World& world, ObserverEvent event, EntitySpan terms,
118 EntitySpan targetEntities = {});
119 GAIA_NODISCARD
static Context prepare_add_new(ObserverRegistry& registry, World& world, EntitySpan terms);
120 static void add_targets(World& world, Context& ctx, EntitySpan targets);
121 static void finish(World& world, Context&& ctx);
124 struct DirectDispatcher {
126 ObserverRegistry& registry, World& world,
const Archetype& archetype, EntitySpan entsAdded,
129 ObserverRegistry& registry, World& world,
const Archetype& archetype, EntitySpan entsRemoved,
131 static void on_set(ObserverRegistry& registry, World& world, Entity term, EntitySpan targets);
134 struct SharedDispatch {
135 template <
bool DiffOnly,
typename TObserverMap>
136 static void collect_from_map(
137 ObserverRegistry& registry, World& world,
const TObserverMap& map, Entity term, uint64_t matchStamp);
139 static void collect_diff_from_list(
140 ObserverRegistry& registry, World& world,
const cnt::darray<Entity>& observers, uint64_t matchStamp);
142 template <
typename TObserverMap>
143 GAIA_NODISCARD
static bool has_terms(
const TObserverMap& map, EntitySpan terms) {
144 for (
auto term: terms) {
145 const auto it = map.find(EntityLookupKey(term));
146 if (it != map.end() && !it->second.empty())
153 template <
typename TObserverMap>
154 GAIA_NODISCARD
static bool has_pair_relations(World& world,
const TObserverMap& map, EntitySpan terms);
156 template <
typename TObserverMap>
157 static void collect_for_event_term(
158 ObserverRegistry& registry, World& world,
const TObserverMap& map, Entity term, uint64_t matchStamp);
160 template <
typename TObserverMap>
161 static void collect_for_is_target(
162 ObserverRegistry& registry, World& world,
const TObserverMap& map, Entity target, uint64_t matchStamp);
164 template <
typename Func>
165 static void for_each_inherited_term(World& world, Entity baseEntity, Func&& func);
167 template <
typename TObserverMap>
168 GAIA_NODISCARD
static bool has_semantic_is_terms(World& world,
const TObserverMap& map, EntitySpan terms);
170 template <
typename TObserverMap>
171 GAIA_NODISCARD
static bool has_inherited_terms(World& world,
const TObserverMap& map, EntitySpan terms);
173 template <
typename TObserverMap>
174 static void collect_for_inherited_terms(
175 ObserverRegistry& registry, World& world,
const TObserverMap& map, Entity baseEntity, uint64_t matchStamp);
177 static void execute_targets(World& world, ObserverRuntimeData& obs, EntitySpan targets);
178 GAIA_NODISCARD
static bool matches_direct_targets(
179 ObserverRuntimeData& obs,
const Archetype& archetype, EntitySpan targets, QueryInfo* pQueryInfo =
nullptr);
182 using DiffDispatchCtx = DiffDispatcher::Context;
186 cnt::darray<ObserverRuntimeData*> m_relevant_observers_tmp;
188 cnt::map<EntityLookupKey, ObserverRuntimeData> m_observer_data;
190 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_add;
192 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_del;
194 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_set;
196 bool m_hasOnSetObservers =
false;
198 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_add_is;
200 cnt::map<EntityLookupKey, cnt::darray<Entity>> m_observer_map_del_is;
202 DiffObserverIndex m_diff_index_add;
204 DiffObserverIndex m_diff_index_del;
206 cnt::map<PropagatedTargetCacheKey, PropagatedTargetCacheEntry> m_propagated_target_cache;
208 uint64_t m_current_match_stamp = 0;
210 GAIA_NODISCARD DiffObserverIndex& diff_index(ObserverEvent event) {
211 GAIA_ASSERT(event == ObserverEvent::OnAdd || event == ObserverEvent::OnDel);
212 return event == ObserverEvent::OnAdd ? m_diff_index_add : m_diff_index_del;
215 GAIA_NODISCARD
const DiffObserverIndex& diff_index(ObserverEvent event)
const {
216 GAIA_ASSERT(event == ObserverEvent::OnAdd || event == ObserverEvent::OnDel);
217 return event == ObserverEvent::OnAdd ? m_diff_index_add : m_diff_index_del;
220 GAIA_NODISCARD
bool has_observers_for_term(Entity term)
const {
221 const auto termKey = EntityLookupKey(term);
222 return observer_map_has_observers(m_observer_map_add, termKey) ||
223 observer_map_has_observers(m_observer_map_del, termKey) ||
224 observer_map_has_observers(m_observer_map_set, termKey);
227 GAIA_NODISCARD
static bool can_mark_term_observed(World& world, Entity term);
228 GAIA_NODISCARD
static bool is_semantic_is_term(Entity term, QueryMatchKind matchKind = QueryMatchKind::Semantic);
229 void mark_term_observed(World& world, Entity term,
bool observed);
231 template <
typename TObserverMap>
232 static void add_observer_to_map(TObserverMap& map, Entity term, Entity observer) {
233 const auto entityKey = EntityLookupKey(term);
234 const auto it = map.find(entityKey);
236 map.emplace(entityKey, cnt::darray<Entity>{observer});
238 it->second.push_back(observer);
241 template <
typename TObserverMap>
242 static void add_observer_to_map_unique(TObserverMap& map, Entity term, Entity observer) {
243 const auto entityKey = EntityLookupKey(term);
244 const auto it = map.find(entityKey);
246 map.emplace(entityKey, cnt::darray<Entity>{observer});
248 add_observer_to_list(it->second, observer);
251 static void add_observer_to_list(cnt::darray<Entity>& list, Entity observer) {
252 if (core::has(list, observer))
254 list.push_back(observer);
257 static void remove_observer_from_list(cnt::darray<Entity>& list, Entity observer) {
258 for (uint32_t i = 0; i < list.size();) {
259 if (list[i] == observer)
260 core::swap_erase_unsafe(list, i);
265 template <
typename TObserverMap>
266 GAIA_NODISCARD
bool observer_map_has_observers(
const TObserverMap& map,
const EntityLookupKey& termKey)
const {
267 const auto it = map.find(termKey);
271 for (
const auto observer: it->second) {
272 if (m_observer_data.find(EntityLookupKey(observer)) != m_observer_data.end())
278 static void collect_traversal_descendants(
279 World& world, Entity relation, Entity root, QueryTravKind travKind, uint8_t travDepth, uint64_t visitStamp,
280 cnt::darray<Entity>& outTargets);
281 static PropagatedTargetCacheEntry& ensure_propagated_targets_cached(
282 ObserverRegistry& registry, World& world,
const ObserverRuntimeData& obs, Entity changedSource);
283 static void collect_propagated_targets_cached(
284 ObserverRegistry& registry, World& world,
const ObserverRuntimeData& obs, Entity changedSource,
285 uint64_t visitStamp, cnt::set<EntityLookupKey>& visitedPairs, cnt::darray<Entity>& outTargets);
286 static void add_propagated_targets_cached(
287 ObserverRegistry& registry, World& world,
const ObserverRuntimeData& obs, Entity changedSource,
288 cnt::darray<Entity>& outTargets);
289 GAIA_NODISCARD
static bool collect_source_traversal_diff_targets(
290 ObserverRegistry& registry, World& world, ObserverRuntimeData& obs, EntitySpan changedTerms,
291 EntitySpan changedSources, cnt::darray<Entity>& outTargets);
292 GAIA_NODISCARD
static bool collect_diff_targets_for_observer(
293 ObserverRegistry& registry, World& world, ObserverRuntimeData& obs, EntitySpan changedTerms,
294 EntitySpan changedTargets, cnt::darray<Entity>& outTargets);
295 GAIA_NODISCARD
static bool
296 observer_uses_changed_traversal_relation(World& world,
const ObserverRuntimeData& obs, EntitySpan changedTerms);
298 GAIA_NODISCARD
static bool is_dynamic_pair_endpoint(EntityId endpoint) {
299 return is_wildcard(endpoint) || is_variable(endpoint);
306 static void pair_endpoint_entities(Entity term, Entity& rel, Entity& tgt) {
307 GAIA_ASSERT(term.pair());
308 const auto relKind = term.entity() ? EntityKind::EK_Uni : EntityKind::EK_Gen;
309 rel = Entity((EntityId)term.id(), 0,
false,
false, relKind);
310 tgt = Entity((EntityId)term.gen(), 0,
false,
false, term.kind());
313 GAIA_NODISCARD
static bool is_observer_term_globally_dynamic(Entity term) {
314 if (term == EntityBad || term == All)
318 return is_variable((EntityId)term.id());
320 const bool relDynamic = is_dynamic_pair_endpoint(term.id());
321 const bool tgtDynamic = is_dynamic_pair_endpoint(term.gen());
322 return relDynamic && tgtDynamic;
326 GAIA_NODISCARD
bool has_observers(Entity term)
const {
327 return has_observers_for_term(term);
332 GAIA_NODISCARD
bool has_on_add_observers()
const {
333 return !m_observer_map_add.empty() || !m_observer_map_add_is.empty() || !m_diff_index_add.empty();
338 GAIA_NODISCARD
bool has_on_del_observers()
const {
339 return !m_observer_map_del.empty() || !m_observer_map_del_is.empty() || !m_diff_index_del.empty();
345 GAIA_NODISCARD
bool has_on_set_observers(Entity term)
const {
346 if (!m_hasOnSetObservers)
349 if (observer_map_has_observers(m_observer_map_set, EntityLookupKey(term)))
355 pair_endpoint_entities(term, rel, tgt);
356 return observer_map_has_observers(m_observer_map_set, EntityLookupKey(Pair(rel, All))) ||
357 observer_map_has_observers(m_observer_map_set, EntityLookupKey(Pair(All, tgt))) ||
358 observer_map_has_observers(m_observer_map_set, EntityLookupKey(Pair(All, All)));
361 void add_diff_observer_term(World& world, Entity observer, Entity term,
const QueryTermOptions& options);
362 GAIA_NODISCARD DiffDispatchCtx
363 prepare_diff(World& world, ObserverEvent event, EntitySpan terms, EntitySpan targetEntities = {});
364 GAIA_NODISCARD DiffDispatchCtx prepare_diff_add_new(World& world, EntitySpan terms);
365 void add_diff_targets(World& world, DiffDispatchCtx& ctx, EntitySpan targets);
366 void finish_diff(World& world, DiffDispatchCtx&& ctx);
369 for (
auto& it: m_observer_data) {
370 auto& obs = it.second;
371 obs.on_each_func = {};
374 obs.lastMatchStamp = 0;
377 m_relevant_observers_tmp = {};
378 m_observer_data = {};
379 m_observer_map_add = {};
380 m_observer_map_del = {};
381 m_observer_map_set = {};
382 m_hasOnSetObservers =
false;
383 m_observer_map_add_is = {};
384 m_observer_map_del_is = {};
385 m_diff_index_add = {};
386 m_diff_index_del = {};
387 m_propagated_target_cache = {};
390 ObserverRuntimeData& data_add(Entity observer) {
391 return m_observer_data[EntityLookupKey(observer)];
394 GAIA_NODISCARD ObserverRuntimeData* data_try(Entity observer) {
395 const auto it = m_observer_data.find(EntityLookupKey(observer));
396 if (it == m_observer_data.end())
401 GAIA_NODISCARD
const ObserverRuntimeData* data_try(Entity observer)
const {
402 const auto it = m_observer_data.find(EntityLookupKey(observer));
403 if (it == m_observer_data.end())
408 GAIA_NODISCARD ObserverRuntimeData& data(Entity observer) {
409 auto* pData = data_try(observer);
410 GAIA_ASSERT(pData !=
nullptr);
414 GAIA_NODISCARD
const ObserverRuntimeData& data(Entity observer)
const {
415 const auto* pData = data_try(observer);
416 GAIA_ASSERT(pData !=
nullptr);
420 void try_mark_term_observed(World& world, Entity term);
427 void add(World& world, Entity term, Entity observer, QueryMatchKind matchKind = QueryMatchKind::Semantic);
432 void del(World& world, Entity term);
439 void on_add(World& world,
const Archetype& archetype, EntitySpan entsAdded, EntitySpan targets);
446 void on_del(World& world,
const Archetype& archetype, EntitySpan entsRemoved, EntitySpan targets);
452 void on_set(World& world, Entity term, EntitySpan targets);