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/map.h"
6#include "gaia/cnt/set.h"
7#include "gaia/ecs/observer.h"
8
9#if GAIA_OBSERVERS_ENABLED
10namespace gaia {
11 namespace ecs {
12 class World;
13 class Archetype;
14
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;
32
34 GAIA_NODISCARD bool empty() const {
35 return direct.empty() && sourceTerm.empty() && traversalRelation.empty() && pairRelation.empty() &&
36 pairTarget.empty() && all.empty() && global.empty();
37 }
38 };
39
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;
46
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);
53 return seed;
54 }
55
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;
59 }
60 };
61
62 struct PropagatedTargetCacheEntry {
63 uint32_t bindingRelationVersion = 0;
64 uint32_t traversalRelationVersion = 0;
65 cnt::darray<Entity> targets;
66 };
67
68 public:
69 struct DiffDispatcher {
70 struct Snapshot {
71 ObserverRuntimeData* pObs = nullptr;
72 uint32_t matchesBeforeIdx = UINT32_MAX;
73 };
74
75 struct MatchCacheEntry {
76 ObserverRuntimeData* pObsRepresentative = nullptr;
77 QueryInfo* pQueryInfoRepresentative = nullptr;
78 uint64_t queryHash = 0;
79 cnt::darray<Entity> matches;
80 };
81
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;
91 };
92
93 struct Context {
94 ObserverEvent event = ObserverEvent::OnAdd;
95 cnt::darray<Snapshot> observers;
96 cnt::darray<MatchCacheEntry> matchesBeforeCache;
97 cnt::darray<Entity> targets;
98 bool active = false;
99 bool targeted = false;
100 bool targetsAddedAfterPrepare = false;
101 bool resetTraversalCaches = false;
102 };
103
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);
122 };
123
124 struct DirectDispatcher {
125 static void on_add(
126 ObserverRegistry& registry, World& world, const Archetype& archetype, EntitySpan entsAdded,
127 EntitySpan targets);
128 static void on_del(
129 ObserverRegistry& registry, World& world, const Archetype& archetype, EntitySpan entsRemoved,
130 EntitySpan targets);
131 static void on_set(ObserverRegistry& registry, World& world, Entity term, EntitySpan targets);
132 };
133
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);
138
139 static void collect_diff_from_list(
140 ObserverRegistry& registry, World& world, const cnt::darray<Entity>& observers, uint64_t matchStamp);
141
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())
147 return true;
148 }
149
150 return false;
151 }
152
153 template <typename TObserverMap>
154 GAIA_NODISCARD static bool has_pair_relations(World& world, const TObserverMap& map, EntitySpan terms);
155
156 template <typename TObserverMap>
157 static void collect_for_event_term(
158 ObserverRegistry& registry, World& world, const TObserverMap& map, Entity term, uint64_t matchStamp);
159
160 template <typename TObserverMap>
161 static void collect_for_is_target(
162 ObserverRegistry& registry, World& world, const TObserverMap& map, Entity target, uint64_t matchStamp);
163
164 template <typename Func>
165 static void for_each_inherited_term(World& world, Entity baseEntity, Func&& func);
166
167 template <typename TObserverMap>
168 GAIA_NODISCARD static bool has_semantic_is_terms(World& world, const TObserverMap& map, EntitySpan terms);
169
170 template <typename TObserverMap>
171 GAIA_NODISCARD static bool has_inherited_terms(World& world, const TObserverMap& map, EntitySpan terms);
172
173 template <typename TObserverMap>
174 static void collect_for_inherited_terms(
175 ObserverRegistry& registry, World& world, const TObserverMap& map, Entity baseEntity, uint64_t matchStamp);
176
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);
180 };
181
182 using DiffDispatchCtx = DiffDispatcher::Context;
183
184 private:
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;
209
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;
213 }
214
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;
218 }
219
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);
225 }
226
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);
230
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);
235 if (it == map.end())
236 map.emplace(entityKey, cnt::darray<Entity>{observer});
237 else
238 it->second.push_back(observer);
239 }
240
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);
245 if (it == map.end())
246 map.emplace(entityKey, cnt::darray<Entity>{observer});
247 else
248 add_observer_to_list(it->second, observer);
249 }
250
251 static void add_observer_to_list(cnt::darray<Entity>& list, Entity observer) {
252 if (core::has(list, observer))
253 return;
254 list.push_back(observer);
255 }
256
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);
261 else
262 ++i;
263 }
264 }
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);
268 if (it == map.end())
269 return false;
270
271 for (const auto observer: it->second) {
272 if (m_observer_data.find(EntityLookupKey(observer)) != m_observer_data.end())
273 return true;
274 }
275 return false;
276 }
277
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);
297
298 GAIA_NODISCARD static bool is_dynamic_pair_endpoint(EntityId endpoint) {
299 return is_wildcard(endpoint) || is_variable(endpoint);
300 }
301
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());
311 }
312
313 GAIA_NODISCARD static bool is_observer_term_globally_dynamic(Entity term) {
314 if (term == EntityBad || term == All)
315 return true;
316
317 if (!term.pair())
318 return is_variable((EntityId)term.id());
319
320 const bool relDynamic = is_dynamic_pair_endpoint(term.id());
321 const bool tgtDynamic = is_dynamic_pair_endpoint(term.gen());
322 return relDynamic && tgtDynamic;
323 }
324
325 public:
326 GAIA_NODISCARD bool has_observers(Entity term) const {
327 return has_observers_for_term(term);
328 }
329
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();
334 }
335
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();
340 }
341
345 GAIA_NODISCARD bool has_on_set_observers(Entity term) const {
346 if (!m_hasOnSetObservers)
347 return false;
348
349 if (observer_map_has_observers(m_observer_map_set, EntityLookupKey(term)))
350 return true;
351 if (!term.pair())
352 return false;
353
354 Entity rel, tgt;
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)));
359 }
360
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);
367
368 void teardown() {
369 for (auto& it: m_observer_data) {
370 auto& obs = it.second;
371 obs.on_each_func = {};
372 obs.query = {};
373 obs.plan = {};
374 obs.lastMatchStamp = 0;
375 }
376
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 = {};
388 }
389
390 ObserverRuntimeData& data_add(Entity observer) {
391 return m_observer_data[EntityLookupKey(observer)];
392 }
393
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())
397 return nullptr;
398 return &it->second;
399 }
400
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())
404 return nullptr;
405 return &it->second;
406 }
407
408 GAIA_NODISCARD ObserverRuntimeData& data(Entity observer) {
409 auto* pData = data_try(observer);
410 GAIA_ASSERT(pData != nullptr);
411 return *pData;
412 }
413
414 GAIA_NODISCARD const ObserverRuntimeData& data(Entity observer) const {
415 const auto* pData = data_try(observer);
416 GAIA_ASSERT(pData != nullptr);
417 return *pData;
418 }
419
420 void try_mark_term_observed(World& world, Entity term);
421
427 void add(World& world, Entity term, Entity observer, QueryMatchKind matchKind = QueryMatchKind::Semantic);
428
432 void del(World& world, Entity term);
433
439 void on_add(World& world, const Archetype& archetype, EntitySpan entsAdded, EntitySpan targets);
440
446 void on_del(World& world, const Archetype& archetype, EntitySpan entsRemoved, EntitySpan targets);
447
452 void on_set(World& world, Entity term, EntitySpan targets);
453 };
454 } // namespace ecs
455} // namespace gaia
456#endif