1#include "gaia/config/config.h"
5#include "gaia/ecs/chunk_iterator.h"
6#include "gaia/ecs/id.h"
7#include "gaia/ecs/observer.h"
9#if GAIA_OBSERVERS_ENABLED
12 inline void world_finish_write(World& world, Entity term, Entity entity);
14 static void observer_finish_iter_writes(Iter& it) {
15 auto* pChunk =
const_cast<Chunk*
>(it.chunk());
16 if (pChunk ==
nullptr)
19 for (
auto compIdx: it.touched_comp_indices())
20 pChunk->finish_write(compIdx, it.row_begin(), it.row_end());
22 auto terms = it.touched_terms();
26 auto& world = *it.world();
27 const auto entities = it.entity_rows();
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());
38 GAIA_FOR_(entities.size(), j) {
39 world_finish_write(world, term, entities[j]);
44 inline void ObserverRuntimeData::exec(Iter& iter, EntitySpan targets, ObserverEvent reportedEvent) {
45 const auto& queryInfo = query.fetch();
46 #if GAIA_ASSERT_ENABLED
47 iter.set_query_access(&queryInfo.ctx().data);
51 const auto name = entity_name(*queryInfo.world(), entity);
52 const char* pScopeName = !name.empty() ? name.data() : sc_observer_query_func_str;
53 GAIA_PROF_SCOPE2(pScopeName);
56 auto* pWorld = iter.world();
57 #if GAIA_OBSERVERS_ENABLED && GAIA_ASSERT_ENABLED
58 pWorld->observer_callback_enter();
60 const auto queryIdCnt = (uint32_t)plan.termCount;
61 iter.event(reportedEvent);
62 const auto& termIds = queryTermIds;
63 const auto terms = queryInfo.ctx().data.terms_view();
64 const QueryTerm* termsByField[MAX_ITEMS_IN_QUERY]{};
65 for (
const auto& term: terms)
66 termsByField[term.fieldIndex] = &term;
68 const Archetype* pCachedArchetype =
nullptr;
69 uint8_t cachedIndices[ChunkHeader::MAX_COMPONENTS];
70 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
71 cachedIndices[i] = 0xFF;
74 for (
auto e: targets) {
75 const auto& ec = pWorld->fetch(e);
76 if (pCachedArchetype != ec.pArchetype) {
77 pCachedArchetype = ec.pArchetype;
78 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
79 cachedIndices[i] = 0xFF;
82 auto indicesView = queryInfo.try_indices_mapping_view(ec.pArchetype);
83 if (!indicesView.empty()) {
84 GAIA_FOR(queryIdCnt) {
85 cachedIndices[i] = indicesView[i];
88 GAIA_FOR(queryIdCnt) {
89 const auto* pTerm = termsByField[i];
90 if (pTerm ==
nullptr || !query_term_maps_to_current_archetype(*pTerm))
93 const auto queryId = termIds[i];
94 auto compIdx = world_component_index_comp_idx(*pWorld, *ec.pArchetype, queryId);
95 if (compIdx == BadIndex || compIdx == ComponentIndexBad)
96 compIdx = core::get_index(ec.pArchetype->ids_view(), queryId);
98 (compIdx != BadIndex && compIdx != ComponentIndexBad && compIdx < ec.pArchetype->ids_view().size())
105 iter.set_archetype(ec.pArchetype);
106 iter.set_chunk(ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
107 iter.set_comp_indices(cachedIndices);
108 iter.set_term_ids(termIds.data());
109 iter.set_write_im(
false);
111 observer_finish_iter_writes(iter);
112 iter.clear_touched_writes();
114 #if GAIA_OBSERVERS_ENABLED && GAIA_ASSERT_ENABLED
115 pWorld->observer_callback_leave();
119 class ObserverBuilder {
124 GAIA_ASSERT(m_world.valid(m_entity));
128 auto ss = m_world.acc_mut(m_entity);
129 auto& sys = ss.smut<Observer_>();
133 const Observer_& data()
const {
134 auto ss = m_world.acc(m_entity);
135 const auto& sys = ss.get<Observer_>();
139 ObserverRuntimeData& runtime_data() {
140 return m_world.observers().data(m_entity);
143 const ObserverRuntimeData& runtime_data()
const {
144 return m_world.observers().data(m_entity);
148 cache_term_desc(ObserverRuntimeData& data, QueryOpKind op, Entity term,
const QueryTermOptions& options) {
149 GAIA_ASSERT(data.plan.termCount < MAX_ITEMS_IN_QUERY);
150 if (data.plan.termCount < MAX_ITEMS_IN_QUERY) {
151 data.queryTermIds[data.plan.termCount] = term;
152 data.queryTermOps[data.plan.termCount] = op;
153 data.queryTermMatchKinds[data.plan.termCount] = options.matchKind;
157 bool has_default_match_options(
const QueryTermOptions& options)
const {
160 return options.entSrc == EntityBad && options.entTrav == EntityBad;
163 bool is_complex_pair_term(Entity term)
const {
164 GAIA_ASSERT(term.pair());
168 if (is_wildcard(term))
170 if (term.id() == Is.
id())
172 if (is_variable(term.id()) || is_variable(term.gen()))
178 bool is_fast_path_eligible_term(Entity term,
const QueryTermOptions& options)
const {
182 if (term == EntityBad)
185 if (!has_default_match_options(options))
195 if (is_complex_pair_term(term))
201 bool requires_diff_dispatch(Entity term,
const QueryTermOptions& options)
const {
202 if (options.entSrc != EntityBad || options.entTrav != EntityBad)
205 if (term == EntityBad || term == All)
208 if (is_variable((EntityId)term.id()))
212 if (is_wildcard(term))
214 if (is_variable((EntityId)term.id()) || is_variable((EntityId)term.gen()))
221 void register_diff_term_index(
222 ObserverRuntimeData& data, QueryOpKind op, Entity term,
const QueryTermOptions& options) {
223 if (options.entTrav != EntityBad) {
224 bool hasRelation =
false;
225 GAIA_FOR(data.plan.diff.traversalRelationCount) {
226 if (data.plan.diff.traversalRelations[i] == options.entTrav) {
233 GAIA_ASSERT(data.plan.diff.traversalRelationCount < MAX_ITEMS_IN_QUERY);
234 if (data.plan.diff.traversalRelationCount < MAX_ITEMS_IN_QUERY)
235 data.plan.diff.traversalRelations[data.plan.diff.traversalRelationCount++] = options.entTrav;
238 update_diff_target_narrow_plan(data, op, term, options);
239 data.plan.refresh_exec_kind();
240 m_world.observers().add_diff_observer_term(m_world, m_entity, op, term, options);
243 void register_diff_term(ObserverRuntimeData& data, QueryOpKind op, Entity term,
const QueryTermOptions& options) {
244 if (data.plan.monitorsQuery) {
245 const bool compoundNegative = data.plan.hasNegativeTerm && data.plan.termCount > 1;
246 if (requires_diff_dispatch(term, options) || compoundNegative)
247 data.plan.diff.enabled =
true;
248 register_diff_term_index(data, op, term, options);
252 const bool compoundNegative = data.plan.hasNegativeTerm && data.plan.termCount > 1;
253 if (data.plan.diff.enabled) {
254 register_diff_term_index(data, op, term, options);
258 if (!requires_diff_dispatch(term, options) && !compoundNegative)
261 data.plan.diff.enabled =
true;
262 data.plan.refresh_exec_kind();
267 GAIA_FOR(data.plan.termCount) {
268 QueryTermOptions queryOptions{};
269 queryOptions.matchKind = data.queryTermMatchKinds[i];
270 if (i + 1 == data.plan.termCount)
271 queryOptions = options;
272 register_diff_term_index(data, data.queryTermOps[i], data.queryTermIds[i], queryOptions);
276 void update_diff_target_narrow_plan(
277 ObserverRuntimeData& data, QueryOpKind op, Entity term,
const QueryTermOptions& options) {
278 using DispatchKind = ObserverPlan::DiffPlan::DispatchKind;
279 auto& diff = data.plan.diff;
280 if (diff.dispatchKind == DispatchKind::GlobalFallback)
283 const auto mark_unsupported = [&] {
284 diff.dispatchKind = DispatchKind::GlobalFallback;
285 diff.bindingVar = EntityBad;
286 diff.bindingRelation = EntityBad;
287 diff.traversalRelation = EntityBad;
288 diff.travKind = QueryTravKind::None;
289 diff.travDepth = QueryTermOptions::TravDepthUnlimited;
290 diff.traversalTriggerTermCount = 0;
293 if (options.entSrc != EntityBad || options.entTrav != EntityBad) {
294 if (options.entSrc == EntityBad || options.entTrav == EntityBad || op != QueryOpKind::All ||
295 !query_trav_has(options.travKind, QueryTravKind::Up) ||
296 query_trav_has(options.travKind, QueryTravKind::Down)) {
301 if (diff.bindingVar == EntityBad)
302 diff.bindingVar = options.entSrc;
303 else if (diff.bindingVar != options.entSrc) {
308 if (diff.traversalRelation == EntityBad) {
309 diff.traversalRelation = options.entTrav;
310 diff.travKind = options.travKind;
311 diff.travDepth = options.travDepth;
313 diff.traversalRelation != options.entTrav || diff.travKind != options.travKind ||
314 diff.travDepth != options.travDepth) {
319 bool hasTerm =
false;
320 GAIA_FOR(diff.traversalTriggerTermCount) {
321 if (diff.traversalTriggerTerms[i] == term) {
327 if (diff.traversalTriggerTermCount >= MAX_ITEMS_IN_QUERY) {
331 diff.traversalTriggerTerms[diff.traversalTriggerTermCount++] = term;
334 if (diff.dispatchKind == DispatchKind::LocalTargets)
335 diff.dispatchKind = DispatchKind::PropagatedTraversal;
339 if (term.pair() && op == QueryOpKind::All && !is_wildcard(term) && !is_variable((EntityId)term.id()) &&
340 is_variable((EntityId)term.gen())) {
341 const auto bindingVar = entity_from_id(m_world, term.gen());
342 const auto bindingRelation = entity_from_id(m_world, term.id());
343 if (!m_world.valid(bindingRelation)) {
348 if (diff.bindingVar == EntityBad)
349 diff.bindingVar = bindingVar;
350 else if (diff.bindingVar != bindingVar) {
355 if (diff.bindingRelation == EntityBad)
356 diff.bindingRelation = bindingRelation;
357 else if (diff.bindingRelation != bindingRelation) {
368 if (!requires_diff_dispatch(term, options) &&
369 (!term.pair() || term.id() != Is.
id() || options.matchKind == QueryMatchKind::Direct))
375 void reg_term(ObserverRuntimeData& data, QueryOpKind op, Entity term,
const QueryTermOptions& options) {
376 cache_term_desc(data, op, term, options);
377 data.plan.add_term_desc(op, is_fast_path_eligible_term(term, options));
378 register_diff_term(data, op, term, options);
379 m_world.observers().add(m_world, term, m_entity, op, options.matchKind);
382 void rebuild_indices(ObserverRuntimeData& data) {
383 if (data.plan.termCount == 0)
388 const auto terms = data.query.fetch().ctx().data.terms_view();
389 for (
const auto& term: terms) {
390 QueryTermOptions options{};
391 options.entSrc = term.src;
392 options.entTrav = term.entTrav;
393 options.travKind = term.travKind;
394 options.travDepth = term.travDepth;
395 options.matchKind = term.matchKind;
397 m_world.observers().add(m_world, term.id, m_entity, term.op, term.matchKind);
398 if (data.plan.uses_diff_dispatch())
399 m_world.observers().add_diff_observer_term(m_world, m_entity, term.op, term.id, options);
404 ObserverBuilder(World& world, Entity entity): m_world(world), m_entity(entity) {}
412 ObserverBuilder& event(ObserverEvent event) {
414 auto& observer = data();
415 if (!observer.monitorsQuery && observer.event == event)
418 auto& runtime = runtime_data();
419 if (runtime.plan.termCount != 0)
420 m_world.observers().remove_observer_indices(m_world, m_entity);
422 observer.event = event;
423 observer.monitorsQuery =
false;
424 runtime.plan.monitorsQuery =
false;
425 runtime.plan.refresh_exec_kind();
426 rebuild_indices(runtime);
434 ObserverBuilder& monitor() {
436 auto& observer = data();
437 if (observer.monitorsQuery)
440 auto& runtime = runtime_data();
441 if (runtime.plan.termCount != 0)
442 m_world.observers().remove_observer_indices(m_world, m_entity);
444 observer.monitorsQuery =
true;
445 runtime.plan.monitorsQuery =
true;
446 runtime.plan.refresh_exec_kind();
447 rebuild_indices(runtime);
454 ObserverBuilder& kind(QueryCacheKind kind) {
456 runtime_data().query.kind(kind);
463 ObserverBuilder& scope(QueryCacheScope scope) {
465 runtime_data().query.scope(scope);
471 ObserverBuilder& add(QueryInput item) {
473 auto& data = runtime_data();
474 data.query.add(item);
476 QueryTermOptions options{};
477 options.entSrc = item.entSrc;
478 options.entTrav = item.entTrav;
479 options.travKind = item.travKind;
480 options.travDepth = item.travDepth;
481 options.access = item.access;
482 options.matchKind = item.matchKind;
484 cache_term_desc(data, item.op, item.id, options);
485 data.plan.add_term_desc(item.op, is_fast_path_eligible_term(item.id, options));
486 register_diff_term(data, item.op, item.id, options);
487 m_world.observers().add(m_world, item.id, m_entity, item.op, item.matchKind);
493 ObserverBuilder& is(Entity entity,
const QueryTermOptions& options = {}) {
494 return all(Pair(Is, entity), options);
499 ObserverBuilder& in(Entity entity, QueryTermOptions options = {}) {
501 return all(Pair(Is, entity), options);
506 ObserverBuilder& all(Entity entity,
const QueryTermOptions& options = {}) {
508 auto& data = runtime_data();
509 data.query.all(entity, options);
510 reg_term(data, QueryOpKind::All, entity, options);
514 ObserverBuilder& any(Entity entity,
const QueryTermOptions& options = {}) {
516 auto& data = runtime_data();
517 data.query.any(entity, options);
518 reg_term(data, QueryOpKind::Any, entity, options);
522 ObserverBuilder& or_(Entity entity,
const QueryTermOptions& options = {}) {
524 auto& data = runtime_data();
525 data.query.or_(entity, options);
526 reg_term(data, QueryOpKind::Or, entity, options);
530 ObserverBuilder& no(Entity entity,
const QueryTermOptions& options = {}) {
532 auto& data = runtime_data();
533 data.query.no(entity, options);
534 reg_term(data, QueryOpKind::Not, entity, options);
538 ObserverBuilder& match_prefab() {
540 runtime_data().query.match_prefab();
544 template <
typename T>
545 ObserverBuilder& all(
const QueryTermOptions& options);
547 template <
typename T>
548 ObserverBuilder& any(
const QueryTermOptions& options);
550 template <
typename T>
551 ObserverBuilder& or_(
const QueryTermOptions& options);
553 template <
typename T>
554 ObserverBuilder& no(
const QueryTermOptions& options);
558 template <
typename T>
559 ObserverBuilder& all();
561 template <
typename T>
562 ObserverBuilder& any();
564 template <
typename T>
565 ObserverBuilder& or_();
567 template <
typename T>
568 ObserverBuilder& no();
574 ObserverBuilder& depth_order(Entity relation = ChildOf) {
576 runtime_data().query.depth_order(relation);
582 template <
typename Rel>
583 ObserverBuilder& depth_order();
587 ObserverBuilder& name(
const char* name, uint32_t len = 0) {
588 m_world.name(m_entity, name, len);
592 ObserverBuilder& name_raw(
const char* name, uint32_t len = 0) {
593 m_world.name_raw(m_entity, name, len);
599 template <
typename Func, std::enable_if_t<std::is_invocable_v<Func, Iter&>,
int> = 0>
600 ObserverBuilder& on_each(Func func) {
603 auto& ctx = runtime_data();
604 ctx.on_each_func = [func](Iter& it) {
608 return (ObserverBuilder&)*
this;
611 template <
typename Func, std::enable_if_t<!std::is_invocable_v<Func, Iter&>,
int> = 0>
612 ObserverBuilder& on_each(Func func);
614 GAIA_NODISCARD Entity entity()
const {
618 void exec(Iter& iter, EntitySpan targets) {
619 auto& ctx = runtime_data();
620 ctx.exec(iter, targets, data().event);
627 #include "gaia/ecs/observer_typed.inl"
GAIA_NODISCARD constexpr auto id() const noexcept
Entity index in the entity array.
Definition id.h:295