Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
observer.inl
1#include "gaia/config/config.h"
2
3#include <cinttypes>
4
5#include "gaia/ecs/chunk_iterator.h"
6#include "gaia/ecs/id.h"
7#include "gaia/ecs/observer.h"
8
9#if GAIA_OBSERVERS_ENABLED
10namespace gaia {
11 namespace ecs {
12 inline void world_finish_write(World& world, Entity term, Entity entity);
13
14 static void observer_finish_iter_writes(Iter& it) {
15 auto* pChunk = const_cast<Chunk*>(it.chunk());
16 if (pChunk == nullptr)
17 return;
18
19 for (auto compIdx: it.touched_comp_indices())
20 pChunk->finish_write(compIdx, it.row_begin(), it.row_end());
21
22 auto terms = it.touched_terms();
23 if (terms.empty())
24 return;
25
26 auto& world = *it.world();
27 const auto entities = it.entity_rows();
28 GAIA_EACH(terms) {
29 const auto term = terms[i];
30 if (!world_component_uses_sparse_storage(world, term)) {
31 const auto compIdx = core::get_index(it.chunk()->ids_view(), term);
32 if (compIdx != BadIndex) {
33 pChunk->finish_write(compIdx, it.row_begin(), it.row_end());
34 continue;
35 }
36 }
37
38 GAIA_FOR_(entities.size(), j) {
39 world_finish_write(world, term, entities[j]);
40 }
41 }
42 }
43
44 inline void ObserverRuntimeData::exec(Iter& iter, EntitySpan targets) {
45 const auto& queryInfo = query.fetch();
46
47 #if GAIA_PROFILER_CPU
48 const auto name = entity_name(*queryInfo.world(), entity);
49 const char* pScopeName = !name.empty() ? name.data() : sc_observer_query_func_str;
50 GAIA_PROF_SCOPE2(pScopeName);
51 #endif
52
53 auto* pWorld = iter.world();
54 #if GAIA_OBSERVERS_ENABLED && GAIA_ASSERT_ENABLED
55 pWorld->observer_callback_enter();
56 #endif
57 const auto queryIdCnt = (uint32_t)plan.termCount;
58 const auto& termIds = queryTermIds;
59 const auto terms = queryInfo.ctx().data.terms_view();
60 const QueryTerm* termsByField[MAX_ITEMS_IN_QUERY]{};
61 for (const auto& term: terms)
62 termsByField[term.fieldIndex] = &term;
63
64 const Archetype* pCachedArchetype = nullptr;
65 uint8_t cachedIndices[ChunkHeader::MAX_COMPONENTS];
66 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
67 cachedIndices[i] = 0xFF;
68 }
69
70 for (auto e: targets) {
71 const auto& ec = pWorld->fetch(e);
72 if (pCachedArchetype != ec.pArchetype) {
73 pCachedArchetype = ec.pArchetype;
74 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
75 cachedIndices[i] = 0xFF;
76 }
77
78 auto indicesView = queryInfo.try_indices_mapping_view(ec.pArchetype);
79 if (!indicesView.empty()) {
80 GAIA_FOR(queryIdCnt) {
81 cachedIndices[i] = indicesView[i];
82 }
83 } else {
84 GAIA_FOR(queryIdCnt) {
85 const auto* pTerm = termsByField[i];
86 if (pTerm == nullptr || !query_term_maps_to_current_archetype(*pTerm))
87 continue;
88
89 const auto queryId = termIds[i];
90 auto compIdx = world_component_index_comp_idx(*pWorld, *ec.pArchetype, queryId);
91 if (compIdx == BadIndex)
92 compIdx = core::get_index(ec.pArchetype->ids_view(), queryId);
93 cachedIndices[i] = (uint8_t)compIdx;
94 }
95 }
96 }
97
98 iter.set_archetype(ec.pArchetype);
99 iter.set_chunk(ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
100 iter.set_comp_indices(cachedIndices);
101 iter.set_term_ids(termIds.data());
102 iter.set_write_im(false);
103 on_each_func(iter);
104 observer_finish_iter_writes(iter);
105 iter.clear_touched_writes();
106 }
107 #if GAIA_OBSERVERS_ENABLED && GAIA_ASSERT_ENABLED
108 pWorld->observer_callback_leave();
109 #endif
110 }
111
112 class ObserverBuilder {
113 World& m_world;
114 Entity m_entity;
115
116 void validate() {
117 GAIA_ASSERT(m_world.valid(m_entity));
118 }
119
120 Observer_& data() {
121 auto ss = m_world.acc_mut(m_entity);
122 auto& sys = ss.smut<Observer_>();
123 return sys;
124 }
125
126 const Observer_& data() const {
127 auto ss = m_world.acc(m_entity);
128 const auto& sys = ss.get<Observer_>();
129 return sys;
130 }
131
132 ObserverRuntimeData& runtime_data() {
133 return m_world.observers().data(m_entity);
134 }
135
136 const ObserverRuntimeData& runtime_data() const {
137 return m_world.observers().data(m_entity);
138 }
139
140 static void cache_term_id(ObserverRuntimeData& data, Entity term) {
141 GAIA_ASSERT(data.plan.termCount < MAX_ITEMS_IN_QUERY);
142 if (data.plan.termCount < MAX_ITEMS_IN_QUERY)
143 data.queryTermIds[data.plan.termCount] = term;
144 }
145
146 bool has_default_match_options(const QueryTermOptions& options) const {
147 // Access mode (read/write) does not change membership, only access semantics.
148 // Source/traversal options can change membership and must stay on generic matcher.
149 return options.entSrc == EntityBad && options.entTrav == EntityBad;
150 }
151
152 bool is_complex_pair_term(Entity term) const {
153 GAIA_ASSERT(term.pair());
154
155 // Wildcards, Is-relations and variable-like endpoints can have dynamic semantics.
156 // Keep these on the generic matcher.
157 if (is_wildcard(term))
158 return true;
159 if (term.id() == Is.id())
160 return true;
161 if (is_variable(term.id()) || is_variable(term.gen()))
162 return true;
163
164 return false;
165 }
166
167 bool is_fast_path_eligible_term(Entity term, const QueryTermOptions& options) const {
168 // Pair/traversal/source terms can carry non-trivial semantics (e.g. IsA-like expressions).
169 // Also exclude wildcard-style terms (All), which are not fixed direct term matches.
170 // Keep these on the generic matcher for correctness.
171 if (term == EntityBad)
172 return false;
173
174 if (!has_default_match_options(options))
175 return false;
176
177 if (term == All)
178 return false;
179
180 if (!term.pair())
181 return true;
182
183 // Pair fast-path only supports fixed direct pairs.
184 if (is_complex_pair_term(term))
185 return false;
186
187 return true;
188 }
189
190 bool requires_diff_dispatch(Entity term, const QueryTermOptions& options) const {
191 if (options.entSrc != EntityBad || options.entTrav != EntityBad)
192 return true;
193
194 if (term == EntityBad || term == All)
195 return true;
196
197 if (is_variable((EntityId)term.id()))
198 return true;
199
200 if (term.pair()) {
201 if (is_wildcard(term))
202 return true;
203 if (is_variable((EntityId)term.id()) || is_variable((EntityId)term.gen()))
204 return true;
205 }
206
207 return false;
208 }
209
210 void register_diff_term(ObserverRuntimeData& data, QueryOpKind op, Entity term, const QueryTermOptions& options) {
211 if (!requires_diff_dispatch(term, options))
212 return;
213
214 data.plan.diff.enabled = true;
215 data.plan.refresh_exec_kind();
216 if (options.entTrav != EntityBad) {
217 bool hasRelation = false;
218 GAIA_FOR(data.plan.diff.traversalRelationCount) {
219 if (data.plan.diff.traversalRelations[i] == options.entTrav) {
220 hasRelation = true;
221 break;
222 }
223 }
224
225 if (!hasRelation) {
226 GAIA_ASSERT(data.plan.diff.traversalRelationCount < MAX_ITEMS_IN_QUERY);
227 if (data.plan.diff.traversalRelationCount < MAX_ITEMS_IN_QUERY)
228 data.plan.diff.traversalRelations[data.plan.diff.traversalRelationCount++] = options.entTrav;
229 }
230 }
231 update_diff_target_narrow_plan(data, op, term, options);
232 data.plan.refresh_exec_kind();
233 m_world.observers().add_diff_observer_term(m_world, m_entity, term, options);
234 }
235
236 void update_diff_target_narrow_plan(
237 ObserverRuntimeData& data, QueryOpKind op, Entity term, const QueryTermOptions& options) {
238 using DispatchKind = ObserverPlan::DiffPlan::DispatchKind;
239 auto& diff = data.plan.diff;
240 if (diff.dispatchKind == DispatchKind::GlobalFallback)
241 return;
242
243 const auto mark_unsupported = [&] {
244 diff.dispatchKind = DispatchKind::GlobalFallback;
245 diff.bindingVar = EntityBad;
246 diff.bindingRelation = EntityBad;
247 diff.traversalRelation = EntityBad;
248 diff.travKind = QueryTravKind::None;
249 diff.travDepth = QueryTermOptions::TravDepthUnlimited;
250 diff.traversalTriggerTermCount = 0;
251 };
252
253 if (options.entSrc != EntityBad || options.entTrav != EntityBad) {
254 if (options.entSrc == EntityBad || options.entTrav == EntityBad || op != QueryOpKind::All ||
255 !query_trav_has(options.travKind, QueryTravKind::Up) ||
256 query_trav_has(options.travKind, QueryTravKind::Down)) {
257 mark_unsupported();
258 return;
259 }
260
261 if (diff.bindingVar == EntityBad)
262 diff.bindingVar = options.entSrc;
263 else if (diff.bindingVar != options.entSrc) {
264 mark_unsupported();
265 return;
266 }
267
268 if (diff.traversalRelation == EntityBad) {
269 diff.traversalRelation = options.entTrav;
270 diff.travKind = options.travKind;
271 diff.travDepth = options.travDepth;
272 } else if (
273 diff.traversalRelation != options.entTrav || diff.travKind != options.travKind ||
274 diff.travDepth != options.travDepth) {
275 mark_unsupported();
276 return;
277 }
278
279 bool hasTerm = false;
280 GAIA_FOR(diff.traversalTriggerTermCount) {
281 if (diff.traversalTriggerTerms[i] == term) {
282 hasTerm = true;
283 break;
284 }
285 }
286 if (!hasTerm) {
287 if (diff.traversalTriggerTermCount >= MAX_ITEMS_IN_QUERY) {
288 mark_unsupported();
289 return;
290 }
291 diff.traversalTriggerTerms[diff.traversalTriggerTermCount++] = term;
292 }
293
294 if (diff.dispatchKind == DispatchKind::LocalTargets)
295 diff.dispatchKind = DispatchKind::PropagatedTraversal;
296 return;
297 }
298
299 if (term.pair() && op == QueryOpKind::All && !is_wildcard(term) && !is_variable((EntityId)term.id()) &&
300 is_variable((EntityId)term.gen())) {
301 const auto bindingVar = entity_from_id(m_world, term.gen());
302 const auto bindingRelation = entity_from_id(m_world, term.id());
303 if (!m_world.valid(bindingRelation)) {
304 mark_unsupported();
305 return;
306 }
307
308 if (diff.bindingVar == EntityBad)
309 diff.bindingVar = bindingVar;
310 else if (diff.bindingVar != bindingVar) {
311 mark_unsupported();
312 return;
313 }
314
315 if (diff.bindingRelation == EntityBad)
316 diff.bindingRelation = bindingRelation;
317 else if (diff.bindingRelation != bindingRelation) {
318 mark_unsupported();
319 return;
320 }
321
322 return;
323 }
324
325 mark_unsupported();
326 }
327
328 void reg_term(ObserverRuntimeData& data, QueryOpKind op, Entity term, const QueryTermOptions& options) {
329 cache_term_id(data, term);
330 data.plan.add_term_descriptor(op, is_fast_path_eligible_term(term, options));
331 register_diff_term(data, op, term, options);
332 m_world.observers().add(m_world, term, m_entity, options.matchKind);
333 }
334
335 public:
336 ObserverBuilder(World& world, Entity entity): m_world(world), m_entity(entity) {}
337
338 //------------------------------------------------
339
340 ObserverBuilder& event(ObserverEvent event) {
341 validate();
342 data().event = event;
343 return *this;
344 }
345
349 ObserverBuilder& kind(QueryCacheKind kind) {
350 validate();
351 runtime_data().query.kind(kind);
352 return *this;
353 }
354
358 ObserverBuilder& scope(QueryCacheScope scope) {
359 validate();
360 runtime_data().query.scope(scope);
361 return *this;
362 }
363
364 //------------------------------------------------
365
366 ObserverBuilder& add(QueryInput item) {
367 validate();
368 auto& data = runtime_data();
369 data.query.add(item);
370
371 QueryTermOptions options{};
372 options.entSrc = item.entSrc;
373 options.entTrav = item.entTrav;
374 options.travKind = item.travKind;
375 options.travDepth = item.travDepth;
376 options.access = item.access;
377 options.matchKind = item.matchKind;
378
379 cache_term_id(data, item.id);
380 data.plan.add_term_descriptor(item.op, is_fast_path_eligible_term(item.id, options));
381 register_diff_term(data, item.op, item.id, options);
382 m_world.observers().add(m_world, item.id, m_entity, item.matchKind);
383 return *this;
384 }
385
386 //------------------------------------------------
387
388 ObserverBuilder& is(Entity entity, const QueryTermOptions& options = {}) {
389 return all(Pair(Is, entity), options);
390 }
391
392 //------------------------------------------------
393
394 ObserverBuilder& in(Entity entity, QueryTermOptions options = {}) {
395 options.in();
396 return all(Pair(Is, entity), options);
397 }
398
399 //------------------------------------------------
400
401 ObserverBuilder& all(Entity entity, const QueryTermOptions& options = {}) {
402 validate();
403 auto& data = runtime_data();
404 data.query.all(entity, options);
405 reg_term(data, QueryOpKind::All, entity, options);
406 return *this;
407 }
408
409 ObserverBuilder& any(Entity entity, const QueryTermOptions& options = {}) {
410 validate();
411 auto& data = runtime_data();
412 data.query.any(entity, options);
413 reg_term(data, QueryOpKind::Any, entity, options);
414 return *this;
415 }
416
417 ObserverBuilder& or_(Entity entity, const QueryTermOptions& options = {}) {
418 validate();
419 auto& data = runtime_data();
420 data.query.or_(entity, options);
421 reg_term(data, QueryOpKind::Or, entity, options);
422 return *this;
423 }
424
425 ObserverBuilder& no(Entity entity, const QueryTermOptions& options = {}) {
426 validate();
427 auto& data = runtime_data();
428 data.query.no(entity, options);
429 reg_term(data, QueryOpKind::Not, entity, options);
430 return *this;
431 }
432
433 ObserverBuilder& match_prefab() {
434 validate();
435 runtime_data().query.match_prefab();
436 return *this;
437 }
438
439 template <typename T>
440 ObserverBuilder& all(const QueryTermOptions& options);
441
442 template <typename T>
443 ObserverBuilder& any(const QueryTermOptions& options);
444
445 template <typename T>
446 ObserverBuilder& or_(const QueryTermOptions& options);
447
448 template <typename T>
449 ObserverBuilder& no(const QueryTermOptions& options);
450
451 //------------------------------------------------
452
453 template <typename T>
454 ObserverBuilder& all();
455
456 template <typename T>
457 ObserverBuilder& any();
458
459 template <typename T>
460 ObserverBuilder& or_();
461
462 template <typename T>
463 ObserverBuilder& no();
464
465 //------------------------------------------------
466
469 ObserverBuilder& depth_order(Entity relation = ChildOf) {
470 validate();
471 runtime_data().query.depth_order(relation);
472 return *this;
473 }
474
477 template <typename Rel>
478 ObserverBuilder& depth_order();
479
480 //------------------------------------------------
481
482 ObserverBuilder& name(const char* name, uint32_t len = 0) {
483 m_world.name(m_entity, name, len);
484 return *this;
485 }
486
487 ObserverBuilder& name_raw(const char* name, uint32_t len = 0) {
488 m_world.name_raw(m_entity, name, len);
489 return *this;
490 }
491
492 //------------------------------------------------
493
494 template <typename Func, std::enable_if_t<std::is_invocable_v<Func, Iter&>, int> = 0>
495 ObserverBuilder& on_each(Func func) {
496 validate();
497
498 auto& ctx = runtime_data();
499 ctx.on_each_func = [func](Iter& it) {
500 func(it);
501 };
502
503 return (ObserverBuilder&)*this;
504 }
505
506 template <typename Func, std::enable_if_t<!std::is_invocable_v<Func, Iter&>, int> = 0>
507 ObserverBuilder& on_each(Func func);
508
509 GAIA_NODISCARD Entity entity() const {
510 return m_entity;
511 }
512
513 void exec(Iter& iter, EntitySpan targets) {
514 auto& ctx = runtime_data();
515 ctx.exec(iter, targets);
516 }
517 };
518
519 } // namespace ecs
520} // namespace gaia
521
522 #include "gaia/ecs/observer_typed.inl"
523
524#endif
GAIA_NODISCARD constexpr auto id() const noexcept
Entity index in the entity array.
Definition id.h:359