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 template <typename T>
13 inline Entity world_query_arg_id(World& world);
14
15 template <typename T>
16 inline decltype(auto) world_query_entity_arg_by_id(World& world, Entity entity, Entity id);
17
18 template <typename T>
19 inline decltype(auto) world_query_entity_arg_by_id_raw(World& world, Entity entity, Entity id);
20
21 inline void world_finish_write(World& world, Entity term, Entity entity);
22
23 template <typename T>
24 static Entity observer_inherited_arg_id(World& world) {
25 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
26 if constexpr (std::is_same_v<Arg, Entity>)
27 return EntityBad;
28 else
29 return world_query_arg_id<Arg>(world);
30 }
31
32 template <typename T>
33 static decltype(auto) observer_inherited_entity_arg_by_id(World& world, Entity entity, Entity termId) {
34 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
35 if constexpr (std::is_same_v<Arg, Entity>)
36 return entity;
37 else if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>)
38 return world_query_entity_arg_by_id_raw<T>(world, entity, termId);
39 else
40 return world_query_entity_arg_by_id<T>(world, entity, termId);
41 }
42
43 template <typename... T, typename Func, size_t... I>
44 static void observer_invoke_inherited_args_by_id(
45 World& world, Entity entity, const Entity* pArgIds, Func& func, std::index_sequence<I...>) {
46 func(observer_inherited_entity_arg_by_id<T>(world, entity, pArgIds[I])...);
47 }
48
49 template <typename T>
50 GAIA_NODISCARD static constexpr bool observer_is_write_arg() {
51 using Arg = std::remove_cv_t<std::remove_reference_t<T>>;
52 return std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>> &&
53 !std::is_same_v<Arg, Entity>;
54 }
55
56 template <typename... T, size_t... I>
57 static void
58 observer_finish_args_by_id(World& world, Entity entity, const Entity* pArgIds, std::index_sequence<I...>) {
59 Entity seenTerms[sizeof...(T) > 0 ? sizeof...(T) : 1]{};
60 uint32_t seenCnt = 0;
61 const auto finish_term = [&](Entity term) {
62 GAIA_FOR(seenCnt) {
63 if (seenTerms[i] == term)
64 return;
65 }
66
67 seenTerms[seenCnt++] = term;
68 world_finish_write(world, term, entity);
69 };
70
71 (
72 [&] {
73 if constexpr (observer_is_write_arg<T>())
74 finish_term(pArgIds[I]);
75 }(),
76 ...);
77 }
78
79 static void observer_finish_iter_writes(Iter& it) {
80 auto* pChunk = const_cast<Chunk*>(it.chunk());
81 if (pChunk == nullptr)
82 return;
83
84 for (auto compIdx: it.touched_comp_indices())
85 pChunk->finish_write(compIdx, it.row_begin(), it.row_end());
86
87 auto terms = it.touched_terms();
88 if (terms.empty())
89 return;
90
91 auto& world = *it.world();
92 const auto entities = it.entity_rows();
93 GAIA_EACH(terms) {
94 const auto term = terms[i];
95 if (!world_is_out_of_line_component(world, term)) {
96 const auto compIdx = core::get_index(it.chunk()->ids_view(), term);
97 if (compIdx != BadIndex) {
98 pChunk->finish_write(compIdx, it.row_begin(), it.row_end());
99 continue;
100 }
101 }
102
103 GAIA_FOR_(entities.size(), j) {
104 world_finish_write(world, term, entities[j]);
105 }
106 }
107 }
108
109 template <typename... T>
110 static bool observer_has_inherited_query_terms(Query& query, World& world, core::func_type_list<T...>) {
111 constexpr bool needsInheritedArgIds =
112 (!std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, Entity> || ... || false);
113 if constexpr (!needsInheritedArgIds)
114 return false;
115 else {
116 auto& queryInfo = query.fetch();
117 for (const auto& term: queryInfo.ctx().data.terms_view()) {
118 if (Query::uses_inherited_id_matching(world, term))
119 return true;
120 }
121 return false;
122 }
123 }
124
125 template <typename... T>
126 static void observer_init_inherited_arg_ids(Entity* pArgIds, World& world, core::func_type_list<T...>) {
127 if constexpr (sizeof...(T) > 0) {
128 const Entity ids[] = {observer_inherited_arg_id<T>(world)...};
129 GAIA_FOR(sizeof...(T)) pArgIds[i] = ids[i];
130 }
131 }
132
133 template <typename Func, typename... T>
134 static void observer_run_typed_on_entity(
135 ObserverRuntimeData& obs, World& world, Entity entity, Iter& it, Func& func, core::func_type_list<T...>,
136 bool hasInheritedTerms, const Entity* pInheritedArgIds) {
137 constexpr bool needsInheritedArgIds =
138 (!std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, Entity> || ... || false);
139 if constexpr (!needsInheritedArgIds)
140 obs.query.run_query_on_chunk(it, func, core::func_type_list<T...>{});
141 else {
142 if (hasInheritedTerms) {
143 observer_invoke_inherited_args_by_id<T...>(
144 world, entity, pInheritedArgIds, func, std::index_sequence_for<T...>{});
145 observer_finish_args_by_id<T...>(world, entity, pInheritedArgIds, std::index_sequence_for<T...>{});
146 return;
147 }
148
149 obs.query.run_query_on_chunk(it, func, core::func_type_list<T...>{});
150 }
151 }
152
153 inline void ObserverRuntimeData::exec(Iter& iter, EntitySpan targets) {
154 const auto& queryInfo = query.fetch();
155
156 #if GAIA_PROFILER_CPU
157 const auto name = entity_name(*queryInfo.world(), entity);
158 const char* pScopeName = !name.empty() ? name.data() : sc_observer_query_func_str;
159 GAIA_PROF_SCOPE2(pScopeName);
160 #endif
161
162 auto* pWorld = iter.world();
163 const auto queryIdCnt = (uint32_t)plan.termCount;
164 const auto& termIds = queryTermIds;
165
166 const Archetype* pCachedArchetype = nullptr;
167 uint8_t cachedIndices[ChunkHeader::MAX_COMPONENTS];
168 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
169 cachedIndices[i] = 0xFF;
170 }
171
172 for (auto e: targets) {
173 const auto& ec = pWorld->fetch(e);
174 if (pCachedArchetype != ec.pArchetype) {
175 pCachedArchetype = ec.pArchetype;
176 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
177 cachedIndices[i] = 0xFF;
178 }
179
180 auto indicesView = queryInfo.try_indices_mapping_view(ec.pArchetype);
181 if (!indicesView.empty()) {
182 GAIA_FOR(queryIdCnt) {
183 cachedIndices[i] = indicesView[i];
184 }
185 } else {
186 GAIA_FOR(queryIdCnt) {
187 const auto queryId = termIds[i];
188 auto compIdx = world_component_index_comp_idx(*pWorld, *ec.pArchetype, queryId);
189 if (compIdx == BadIndex)
190 compIdx = core::get_index(ec.pArchetype->ids_view(), queryId);
191 cachedIndices[i] = (uint8_t)compIdx;
192 }
193 }
194 }
195
196 iter.set_archetype(ec.pArchetype);
197 iter.set_chunk(ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
198 iter.set_comp_indices(cachedIndices);
199 iter.set_term_ids(termIds.data());
200 iter.set_write_im(false);
201 on_each_func(iter);
202 observer_finish_iter_writes(iter);
203 iter.clear_touched_writes();
204 }
205 }
206
207 class ObserverBuilder {
208 World& m_world;
209 Entity m_entity;
210
211 void validate() {
212 GAIA_ASSERT(m_world.valid(m_entity));
213 }
214
215 Observer_& data() {
216 auto ss = m_world.acc_mut(m_entity);
217 auto& sys = ss.smut<Observer_>();
218 return sys;
219 }
220
221 const Observer_& data() const {
222 auto ss = m_world.acc(m_entity);
223 const auto& sys = ss.get<Observer_>();
224 return sys;
225 }
226
227 ObserverRuntimeData& runtime_data() {
228 return m_world.observers().data(m_entity);
229 }
230
231 const ObserverRuntimeData& runtime_data() const {
232 return m_world.observers().data(m_entity);
233 }
234
235 static void cache_term_id(ObserverRuntimeData& data, Entity term) {
236 GAIA_ASSERT(data.plan.termCount < MAX_ITEMS_IN_QUERY);
237 if (data.plan.termCount < MAX_ITEMS_IN_QUERY)
238 data.queryTermIds[data.plan.termCount] = term;
239 }
240
241 bool has_default_match_options(const QueryTermOptions& options) const {
242 // Access mode (read/write) does not change membership, only access semantics.
243 // Source/traversal options can change membership and must stay on generic matcher.
244 return options.entSrc == EntityBad && options.entTrav == EntityBad;
245 }
246
247 bool is_complex_pair_term(Entity term) const {
248 GAIA_ASSERT(term.pair());
249
250 // Wildcards, Is-relations and variable-like endpoints can have dynamic semantics.
251 // Keep these on the generic matcher.
252 if (is_wildcard(term))
253 return true;
254 if (term.id() == Is.id())
255 return true;
256 if (is_variable(term.id()) || is_variable(term.gen()))
257 return true;
258
259 return false;
260 }
261
262 bool is_fast_path_eligible_term(Entity term, const QueryTermOptions& options) const {
263 // Pair/traversal/source terms can carry non-trivial semantics (e.g. IsA-like expressions).
264 // Also exclude wildcard-style terms (All), which are not fixed direct term matches.
265 // Keep these on the generic matcher for correctness.
266 if (term == EntityBad)
267 return false;
268
269 if (!has_default_match_options(options))
270 return false;
271
272 if (term == All)
273 return false;
274
275 if (!term.pair())
276 return true;
277
278 // Pair fast-path only supports fixed direct pairs.
279 if (is_complex_pair_term(term))
280 return false;
281
282 return true;
283 }
284
285 bool requires_diff_dispatch(Entity term, const QueryTermOptions& options) const {
286 if (options.entSrc != EntityBad || options.entTrav != EntityBad)
287 return true;
288
289 if (term == EntityBad || term == All)
290 return true;
291
292 if (is_variable((EntityId)term.id()))
293 return true;
294
295 if (term.pair()) {
296 if (is_wildcard(term))
297 return true;
298 if (is_variable((EntityId)term.id()) || is_variable((EntityId)term.gen()))
299 return true;
300 }
301
302 return false;
303 }
304
305 void register_diff_term(ObserverRuntimeData& data, QueryOpKind op, Entity term, const QueryTermOptions& options) {
306 if (!requires_diff_dispatch(term, options))
307 return;
308
309 data.plan.diff.enabled = true;
310 data.plan.refresh_exec_kind();
311 if (options.entTrav != EntityBad) {
312 bool hasRelation = false;
313 GAIA_FOR(data.plan.diff.traversalRelationCount) {
314 if (data.plan.diff.traversalRelations[i] == options.entTrav) {
315 hasRelation = true;
316 break;
317 }
318 }
319
320 if (!hasRelation) {
321 GAIA_ASSERT(data.plan.diff.traversalRelationCount < MAX_ITEMS_IN_QUERY);
322 if (data.plan.diff.traversalRelationCount < MAX_ITEMS_IN_QUERY)
323 data.plan.diff.traversalRelations[data.plan.diff.traversalRelationCount++] = options.entTrav;
324 }
325 }
326 update_diff_target_narrow_plan(data, op, term, options);
327 data.plan.refresh_exec_kind();
328 m_world.observers().add_diff_observer_term(m_world, m_entity, term, options);
329 }
330
331 void update_diff_target_narrow_plan(
332 ObserverRuntimeData& data, QueryOpKind op, Entity term, const QueryTermOptions& options) {
333 using DispatchKind = ObserverPlan::DiffPlan::DispatchKind;
334 auto& diff = data.plan.diff;
335 if (diff.dispatchKind == DispatchKind::GlobalFallback)
336 return;
337
338 const auto mark_unsupported = [&] {
339 diff.dispatchKind = DispatchKind::GlobalFallback;
340 diff.bindingVar = EntityBad;
341 diff.bindingRelation = EntityBad;
342 diff.traversalRelation = EntityBad;
343 diff.travKind = QueryTravKind::None;
344 diff.travDepth = QueryTermOptions::TravDepthUnlimited;
345 diff.traversalTriggerTermCount = 0;
346 };
347
348 if (options.entSrc != EntityBad || options.entTrav != EntityBad) {
349 if (options.entSrc == EntityBad || options.entTrav == EntityBad || op != QueryOpKind::All ||
350 !query_trav_has(options.travKind, QueryTravKind::Up) ||
351 query_trav_has(options.travKind, QueryTravKind::Down)) {
352 mark_unsupported();
353 return;
354 }
355
356 if (diff.bindingVar == EntityBad)
357 diff.bindingVar = options.entSrc;
358 else if (diff.bindingVar != options.entSrc) {
359 mark_unsupported();
360 return;
361 }
362
363 if (diff.traversalRelation == EntityBad) {
364 diff.traversalRelation = options.entTrav;
365 diff.travKind = options.travKind;
366 diff.travDepth = options.travDepth;
367 } else if (
368 diff.traversalRelation != options.entTrav || diff.travKind != options.travKind ||
369 diff.travDepth != options.travDepth) {
370 mark_unsupported();
371 return;
372 }
373
374 bool hasTerm = false;
375 GAIA_FOR(diff.traversalTriggerTermCount) {
376 if (diff.traversalTriggerTerms[i] == term) {
377 hasTerm = true;
378 break;
379 }
380 }
381 if (!hasTerm) {
382 if (diff.traversalTriggerTermCount >= MAX_ITEMS_IN_QUERY) {
383 mark_unsupported();
384 return;
385 }
386 diff.traversalTriggerTerms[diff.traversalTriggerTermCount++] = term;
387 }
388
389 if (diff.dispatchKind == DispatchKind::LocalTargets)
390 diff.dispatchKind = DispatchKind::PropagatedTraversal;
391 return;
392 }
393
394 if (term.pair() && op == QueryOpKind::All && !is_wildcard(term) && !is_variable((EntityId)term.id()) &&
395 is_variable((EntityId)term.gen())) {
396 const auto bindingVar = entity_from_id(m_world, term.gen());
397 const auto bindingRelation = entity_from_id(m_world, term.id());
398 if (!m_world.valid(bindingRelation)) {
399 mark_unsupported();
400 return;
401 }
402
403 if (diff.bindingVar == EntityBad)
404 diff.bindingVar = bindingVar;
405 else if (diff.bindingVar != bindingVar) {
406 mark_unsupported();
407 return;
408 }
409
410 if (diff.bindingRelation == EntityBad)
411 diff.bindingRelation = bindingRelation;
412 else if (diff.bindingRelation != bindingRelation) {
413 mark_unsupported();
414 return;
415 }
416
417 return;
418 }
419
420 mark_unsupported();
421 }
422
423 template <QueryOpKind Op, typename T>
424 void reg_typed_term(ObserverRuntimeData& data) {
425 const auto term = m_world.template reg_comp<T>().entity;
426 cache_term_id(data, term);
427 data.plan.add_term_descriptor(Op, is_fast_path_eligible_term(term, QueryTermOptions{}));
428 register_diff_term(data, Op, term, QueryTermOptions{});
429 m_world.observers().add(m_world, term, m_entity, QueryMatchKind::Semantic);
430 }
431
432 template <QueryOpKind Op, typename T>
433 void reg_typed_term(ObserverRuntimeData& data, const QueryTermOptions& options) {
434 const auto term = m_world.template reg_comp<T>().entity;
435 cache_term_id(data, term);
436 data.plan.add_term_descriptor(Op, is_fast_path_eligible_term(term, options));
437 register_diff_term(data, Op, term, options);
438 m_world.observers().add(m_world, term, m_entity, options.matchKind);
439 }
440
441 public:
442 ObserverBuilder(World& world, Entity entity): m_world(world), m_entity(entity) {}
443
444 //------------------------------------------------
445
446 ObserverBuilder& event(ObserverEvent event) {
447 validate();
448 data().event = event;
449 return *this;
450 }
451
455 ObserverBuilder& kind(QueryCacheKind kind) {
456 validate();
457 runtime_data().query.kind(kind);
458 return *this;
459 }
460
464 ObserverBuilder& scope(QueryCacheScope scope) {
465 validate();
466 runtime_data().query.scope(scope);
467 return *this;
468 }
469
470 //------------------------------------------------
471
472 ObserverBuilder& add(QueryInput item) {
473 validate();
474 auto& data = runtime_data();
475 data.query.add(item);
476
477 QueryTermOptions options{};
478 options.entSrc = item.entSrc;
479 options.entTrav = item.entTrav;
480 options.travKind = item.travKind;
481 options.travDepth = item.travDepth;
482 options.access = item.access;
483 options.matchKind = item.matchKind;
484
485 cache_term_id(data, item.id);
486 data.plan.add_term_descriptor(item.op, is_fast_path_eligible_term(item.id, options));
487 register_diff_term(data, item.op, item.id, options);
488 m_world.observers().add(m_world, item.id, m_entity, item.matchKind);
489 return *this;
490 }
491
492 //------------------------------------------------
493
494 ObserverBuilder& is(Entity entity, const QueryTermOptions& options = {}) {
495 return all(Pair(Is, entity), options);
496 }
497
498 //------------------------------------------------
499
500 ObserverBuilder& in(Entity entity, QueryTermOptions options = {}) {
501 options.in();
502 return all(Pair(Is, entity), options);
503 }
504
505 //------------------------------------------------
506
507 ObserverBuilder& all(Entity entity, const QueryTermOptions& options = {}) {
508 validate();
509 auto& data = runtime_data();
510 data.query.all(entity, options);
511 cache_term_id(data, entity);
512 data.plan.add_term_descriptor(QueryOpKind::All, is_fast_path_eligible_term(entity, options));
513 register_diff_term(data, QueryOpKind::All, entity, options);
514 m_world.observers().add(m_world, entity, m_entity, options.matchKind);
515 return *this;
516 }
517
518 ObserverBuilder& any(Entity entity, const QueryTermOptions& options = {}) {
519 validate();
520 auto& data = runtime_data();
521 data.query.any(entity, options);
522 cache_term_id(data, entity);
523 data.plan.add_term_descriptor(QueryOpKind::Any, is_fast_path_eligible_term(entity, options));
524 register_diff_term(data, QueryOpKind::Any, entity, options);
525 m_world.observers().add(m_world, entity, m_entity, options.matchKind);
526 return *this;
527 }
528
529 ObserverBuilder& or_(Entity entity, const QueryTermOptions& options = {}) {
530 validate();
531 auto& data = runtime_data();
532 data.query.or_(entity, options);
533 cache_term_id(data, entity);
534 data.plan.add_term_descriptor(QueryOpKind::Or, is_fast_path_eligible_term(entity, options));
535 register_diff_term(data, QueryOpKind::Or, entity, options);
536 m_world.observers().add(m_world, entity, m_entity, options.matchKind);
537 return *this;
538 }
539
540 ObserverBuilder& no(Entity entity, const QueryTermOptions& options = {}) {
541 validate();
542 auto& data = runtime_data();
543 data.query.no(entity, options);
544 cache_term_id(data, entity);
545 data.plan.add_term_descriptor(QueryOpKind::Not, is_fast_path_eligible_term(entity, options));
546 register_diff_term(data, QueryOpKind::Not, entity, options);
547 m_world.observers().add(m_world, entity, m_entity, options.matchKind);
548 return *this;
549 }
550
551 ObserverBuilder& match_prefab() {
552 validate();
553 runtime_data().query.match_prefab();
554 return *this;
555 }
556
557 template <typename T>
558 ObserverBuilder& all(const QueryTermOptions& options) {
559 validate();
560 auto& data = runtime_data();
561 data.query.template all<T>(options);
562 reg_typed_term<QueryOpKind::All, T>(data, options);
563 return *this;
564 }
565
566 template <typename T>
567 ObserverBuilder& any(const QueryTermOptions& options) {
568 validate();
569 auto& data = runtime_data();
570 data.query.template any<T>(options);
571 reg_typed_term<QueryOpKind::Any, T>(data, options);
572 return *this;
573 }
574
575 template <typename T>
576 ObserverBuilder& or_(const QueryTermOptions& options) {
577 validate();
578 auto& data = runtime_data();
579 data.query.template or_<T>(options);
580 reg_typed_term<QueryOpKind::Or, T>(data, options);
581 return *this;
582 }
583
584 template <typename T>
585 ObserverBuilder& no(const QueryTermOptions& options) {
586 validate();
587 auto& data = runtime_data();
588 data.query.template no<T>(options);
589 reg_typed_term<QueryOpKind::Not, T>(data, options);
590 return *this;
591 }
592
593 //------------------------------------------------
594
595 template <typename T>
596 ObserverBuilder& all() {
597 validate();
598 auto& data = runtime_data();
599 data.query.all<T>();
600 reg_typed_term<QueryOpKind::All, T>(data);
601 return *this;
602 }
603
604 template <typename T>
605 ObserverBuilder& any() {
606 validate();
607 auto& data = runtime_data();
608 data.query.any<T>();
609 reg_typed_term<QueryOpKind::Any, T>(data);
610 return *this;
611 }
612
613 template <typename T>
614 ObserverBuilder& or_() {
615 validate();
616 auto& data = runtime_data();
617 data.query.or_<T>();
618 reg_typed_term<QueryOpKind::Or, T>(data);
619 return *this;
620 }
621
622 template <typename T>
623 ObserverBuilder& no() {
624 validate();
625 auto& data = runtime_data();
626 data.query.no<T>();
627 reg_typed_term<QueryOpKind::Not, T>(data);
628 return *this;
629 }
630
631 //------------------------------------------------
632
635 ObserverBuilder& depth_order(Entity relation = ChildOf) {
636 validate();
637 runtime_data().query.depth_order(relation);
638 return *this;
639 }
640
643 template <typename Rel>
644 ObserverBuilder& depth_order() {
645 validate();
646 runtime_data().query.template depth_order<Rel>();
647 return *this;
648 }
649
650 //------------------------------------------------
651
652 ObserverBuilder& name(const char* name, uint32_t len = 0) {
653 m_world.name(m_entity, name, len);
654 return *this;
655 }
656
657 ObserverBuilder& name_raw(const char* name, uint32_t len = 0) {
658 m_world.name_raw(m_entity, name, len);
659 return *this;
660 }
661
662 //------------------------------------------------
663
664 template <typename Func>
665 ObserverBuilder& on_each(Func func) {
666 validate();
667
668 auto& ctx = runtime_data();
669 if constexpr (std::is_invocable_v<Func, Iter&>) {
670 ctx.on_each_func = [func](Iter& it) {
671 func(it);
672 };
673 } else {
674 using InputArgs = decltype(core::func_args(&Func::operator()));
675
676 const bool hasInheritedTerms = observer_has_inherited_query_terms(ctx.query, m_world, InputArgs{});
677 Entity inheritedArgIds[ChunkHeader::MAX_COMPONENTS] = {};
678 observer_init_inherited_arg_ids(inheritedArgIds, m_world, InputArgs{});
679
680 #if GAIA_ASSERT_ENABLED
681 // Make sure we only use components specified in the query.
682 // Constness is respected. Therefore, if a type is const when registered to query,
683 // it has to be const (or immutable) also in each().
684 auto& queryInfo = ctx.query.fetch();
685 ctx.query.match_all(queryInfo);
686 GAIA_ASSERT(ctx.query.unpack_args_into_query_has_all(queryInfo, InputArgs{}));
687 #endif
688
689 ctx.on_each_func = [e = m_entity, func, hasInheritedTerms, inheritedArgIds](Iter& it) mutable {
690 auto& obs = it.world()->observers().data(e);
691 auto& world = *it.world();
692 const auto entity = it.view<Entity>()[0];
693 observer_run_typed_on_entity(obs, world, entity, it, func, InputArgs{}, hasInheritedTerms, inheritedArgIds);
694 };
695 }
696
697 return (ObserverBuilder&)*this;
698 }
699
700 GAIA_NODISCARD Entity entity() const {
701 return m_entity;
702 }
703
704 void exec(Iter& iter, EntitySpan targets) {
705 auto& ctx = runtime_data();
706 ctx.exec(iter, targets);
707 }
708 };
709
710 } // namespace ecs
711} // namespace gaia
712
713#endif
static GAIA_NODISCARD bool uses_inherited_id_matching(const World &world, const QueryTerm &term)
Returns whether a term uses semantic inherited-id matching rather than direct storage matching.
Definition query.h:2645
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9