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