/** * @file query/engine/eval_iter.c * @brief Query iterator. */ #include "../../private_api.h" static void flecs_query_iter_run_ctx_init( ecs_iter_t *it, ecs_query_run_ctx_t *ctx) { ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, it->query); ctx->world = it->real_world; ctx->query = impl; ctx->it = it; ctx->vars = qit->vars; ctx->query_vars = qit->query_vars; ctx->written = qit->written; ctx->op_ctx = qit->op_ctx; ctx->qit = qit; } void flecs_query_iter_constrain( ecs_iter_t *it) { ecs_query_run_ctx_t ctx; flecs_query_iter_run_ctx_init(it, &ctx); ecs_assert(ctx.written != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_query_impl_t *query = ctx.query; const ecs_query_t *q = &query->pub; ecs_flags64_t it_written = it->constrained_vars; ctx.written[0] = it_written; if (it_written && ctx.query->src_vars) { /* If variables were constrained, check if there are any table * variables that have a constrained entity variable. */ ecs_var_t *vars = ctx.vars; int32_t i, count = q->field_count; for (i = 0; i < count; i ++) { ecs_var_id_t var_id = query->src_vars[i]; ecs_query_var_t *var = &query->vars[var_id]; if (!(it_written & (1ull << var_id)) || (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) { continue; } /* Initialize table variable with constrained entity variable */ ecs_var_t *tvar = &vars[var->table_id]; tvar->range = flecs_range_from_entity(ctx.world, vars[var_id].entity); ctx.written[0] |= (1ull << var->table_id); /* Mark as written */ } } /* This function can be called multiple times when setting variables, so * reset flags before setting them. */ it->flags &= ~(EcsIterTrivialTest|EcsIterTrivialCached| EcsIterTrivialSearch); /* Figure out whether this query can utilize specialized iterator modes for * improved performance. */ ecs_flags32_t flags = q->flags; ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; ecs_query_cache_t *cache = query->cache; if (it_written) { /* When we're testing against an entity or table, set the $this * variable in advance since it won't change later on. This * initializes it.count, it.entities and it.table. */ flecs_query_set_iter_this(it, &ctx); if (!cache) { if ((flags & (trivial_flags)) == trivial_flags) { if (!(flags & EcsQueryMatchWildcards)) { it->flags |= EcsIterTrivialTest; flecs_query_setids(NULL, false, &ctx); } } } else if (flags & EcsQueryIsCacheable) { if (!cache->order_by_callback && (cache->query->flags & EcsQueryTrivialCache && !(query->pub.flags & EcsQueryHasChangeDetection))) { it->flags |= EcsIterTrivialTest|EcsIterTrivialCached| EcsIterTrivialChangeDetection; it->ids = cache->query->ids; it->sources = cache->sources; it->set_fields = flecs_uto(uint32_t, (1llu << it->field_count) - 1); } else { it->flags |= EcsIterTrivialTest|EcsIterCached; } } } else { if (!cache) { if ((flags & (trivial_flags)) == trivial_flags) { if (!(flags & EcsQueryMatchWildcards)) { it->flags |= EcsIterTrivialSearch| EcsIterTrivialChangeDetection; flecs_query_setids(NULL, false, &ctx); } } } else if (flags & EcsQueryIsCacheable) { if (!cache->order_by_callback && (cache->query->flags & EcsQueryTrivialCache && !(query->pub.flags & EcsQueryHasChangeDetection))) { it->flags |= EcsIterTrivialSearch|EcsIterTrivialCached| EcsIterTrivialChangeDetection; it->ids = cache->query->ids; it->sources = cache->sources; it->set_fields = flecs_uto(uint32_t, (1llu << it->field_count) - 1); } else { it->flags |= EcsIterTrivialSearch|EcsIterCached; } } } } static void flecs_query_change_detection( ecs_iter_t *it, ecs_query_iter_t *qit, ecs_query_impl_t *impl) { /* Change detection */ if (!(it->flags & EcsIterSkip)) { /* Mark table columns that are written to dirty */ flecs_query_mark_fields_dirty(impl, it); if (qit->elem) { if (impl->pub.flags & EcsQueryHasChangeDetection) { /* If this query uses change detection, synchronize the * monitor for the iterated table with the query */ flecs_query_sync_match_monitor(impl, qit->elem); } } } } static void flecs_query_self_change_detection( ecs_iter_t *it, ecs_query_iter_t *qit, ecs_query_impl_t *impl) { if (!it->table->dirty_state) { return; } flecs_query_change_detection(it, qit, impl); } bool ecs_query_next( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(it->next == ecs_query_next || it->next == flecs_query_trivial_cached_next || it->next == flecs_default_next_callback, ECS_INVALID_PARAMETER, NULL); ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, it->query); ecs_assert(impl != NULL, ECS_INVALID_OPERATION, "cannot call ecs_query_next on invalid iterator"); ecs_query_run_ctx_t ctx; flecs_query_iter_run_ctx_init(it, &ctx); bool redo = it->flags & EcsIterIsValid; if (redo) { if (it->flags & EcsIterTrivialChangeDetection) { flecs_query_self_change_detection(it, qit, impl); } else { flecs_query_change_detection(it, qit, impl); } } it->flags &= ~(EcsIterSkip); it->flags |= EcsIterIsValid; it->frame_offset += it->count; /* Specialized iterator modes. When a query doesn't use any advanced * features, it can call specialized iterator functions directly instead of * going through the dispatcher of the query engine. * The iterator mode is set during iterator initialization. Besides being * determined by the query, there are different modes for searching and * testing, where searching returns all matches for a query, whereas testing * tests a single table or table range against the query. */ if (it->flags & EcsIterTrivialCached) { /* Trivial cache iterator. Only supported for search */ ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); if (it->flags & EcsIterTrivialSearch) { if (flecs_query_is_trivial_cache_search(&ctx)) { return true; } } else if (it->flags & EcsIterTrivialTest) { if (flecs_query_is_trivial_cache_test(&ctx, redo)) { return true; } } } else if (it->flags & EcsIterCached) { /* Cached iterator modes */ if (it->flags & EcsIterTrivialSearch) { ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_query_is_cache_search(&ctx)) { goto trivial_search_yield; } } else if (it->flags & EcsIterTrivialTest) { ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_query_is_cache_test(&ctx, redo)) { goto yield; } } } else { /* Uncached iterator modes */ if (it->flags & EcsIterTrivialSearch) { ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; if (flecs_query_is_trivial_search(&ctx, op_ctx, redo)) { goto yield; } } else if (it->flags & EcsIterTrivialTest) { ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); int32_t fields = ctx.query->pub.term_count; ecs_flags64_t mask = (2llu << (fields - 1)) - 1; if (flecs_query_trivial_test(&ctx, redo, mask)) { goto yield; } } else { const ecs_query_op_t *ops = qit->ops; /* Default iterator mode. This enters the query VM dispatch loop. */ if (flecs_query_run_until( redo, &ctx, ops, -1, qit->op, impl->op_count - 1)) { ecs_assert(ops[ctx.op_index].kind == EcsQueryYield, ECS_INTERNAL_ERROR, NULL); flecs_query_set_iter_this(it, &ctx); ecs_assert(it->count >= 0, ECS_INTERNAL_ERROR, NULL); qit->op = flecs_itolbl(ctx.op_index - 1); goto yield; } } } /* Done iterating */ flecs_query_mark_fixed_fields_dirty(impl, it); if (ctx.query->monitor) { flecs_query_update_fixed_monitor( ECS_CONST_CAST(ecs_query_impl_t*, ctx.query)); } it->flags |= EcsIterSkip; /* Prevent change detection on fini */ ecs_iter_fini(it); ecs_os_linc(&it->real_world->info.queries_ran_total); return false; trivial_search_yield: it->table = ctx.vars[0].range.table; it->count = ecs_table_count(it->table); it->entities = ecs_table_entities(it->table); yield: return true; } bool flecs_query_trivial_cached_next( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, it->query); ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_run_ctx_t ctx; flecs_query_iter_run_ctx_init(it, &ctx); bool redo = it->flags & EcsIterIsValid; if (redo) { flecs_query_self_change_detection(it, qit, impl); } it->flags &= ~(EcsIterSkip); it->flags |= EcsIterIsValid; it->frame_offset += it->count; ecs_assert(it->flags & EcsIterTrivialCached, ECS_INVALID_OPERATION, "query does not have trivial cache, use ecs_query_next instead"); ecs_assert(it->flags & EcsIterTrivialSearch, ECS_INVALID_OPERATION, "iterator has constrained variables, use ecs_query_next instead"); ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_query_is_trivial_cache_search(&ctx)) { return true; } it->flags |= EcsIterSkip; /* Prevent change detection on fini */ ecs_iter_fini(it); ecs_os_linc(&it->real_world->info.queries_ran_total); return false; } static void flecs_query_iter_fini_ctx( ecs_iter_t *it, ecs_query_iter_t *qit) { const ecs_query_impl_t *query = flecs_query_impl(it->query); int32_t i, count = query->op_count; ecs_query_op_t *ops = query->ops; ecs_query_op_ctx_t *ctx = qit->op_ctx; ecs_allocator_t *a = flecs_query_get_allocator(it); for (i = 0; i < count; i ++) { ecs_query_op_t *op = &ops[i]; switch(op->kind) { case EcsQueryTrav: flecs_query_trav_cache_fini(a, &ctx[i].is.trav.cache); break; case EcsQueryUp: case EcsQuerySelfUp: case EcsQuerySparseUp: case EcsQuerySparseSelfUp: { ecs_query_up_ctx_t *op_ctx = &ctx[i].is.up; ecs_query_up_impl_t *impl = op_ctx->impl; if (impl) { ecs_trav_up_cache_t *cache = &impl->cache; if (cache->dir == EcsTravDown) { flecs_query_down_cache_fini(a, cache); } else { flecs_query_up_cache_fini(cache); } flecs_free_t(a, ecs_query_up_impl_t, impl); } break; } default: break; } } } static void flecs_query_iter_fini( ecs_iter_t *it) { ecs_query_iter_t *qit = &it->priv_.iter.query; const ecs_query_t *q = it->query; ecs_assert(q != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_assert(q, ecs_query_t); int32_t op_count = flecs_query_impl(q)->op_count; int32_t var_count = flecs_query_impl(q)->var_count; if (it->flags & EcsIterIsValid) { flecs_query_change_detection(it, qit, flecs_query_impl(q)); } #ifdef FLECS_DEBUG if (it->flags & EcsIterProfile) { char *str = ecs_query_plan_w_profile(q, it); printf("%s\n", str); ecs_os_free(str); } flecs_iter_free_n(qit->profile, ecs_query_op_profile_t, op_count); #endif flecs_query_iter_fini_ctx(it, qit); flecs_iter_free_n(qit->vars, ecs_var_t, var_count); flecs_iter_free_n(qit->written, ecs_write_flags_t, op_count); flecs_iter_free_n(qit->op_ctx, ecs_query_op_ctx_t, op_count); qit->vars = NULL; qit->written = NULL; qit->op_ctx = NULL; it->query = NULL; } static void flecs_query_validate_final_fields( const ecs_query_t *q) { (void)q; #ifdef FLECS_DEBUG ecs_query_impl_t *impl = flecs_query_impl(q); ecs_world_t *world = q->real_world; if (!impl->final_terms) { return; } if (!world->cr_isa_wildcard) { return; } int32_t i, count = impl->pub.term_count; ecs_term_t *terms = impl->pub.terms; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; ecs_id_t id = term->id; if (!(impl->final_terms & (1ull << i))) { continue; } if (ECS_IS_PAIR(id)) { id = ECS_PAIR_FIRST(id); } if (ecs_id_is_wildcard(id)) { continue; } if (flecs_components_get(world, ecs_pair(EcsIsA, id))) { char *query_str = ecs_query_str(q); char *id_str = ecs_id_str(world, id); ecs_abort(ECS_INVALID_OPERATION, "query '%s' was created before '(IsA, %s)' relationship, " "create query after adding inheritance relationship " "or add 'Inheritable' trait to '%s'", query_str, id_str, id_str); ecs_os_free(id_str); ecs_os_free(query_str); } } #endif } ecs_iter_t flecs_query_iter( const ecs_world_t *world, const ecs_query_t *q) { ecs_iter_t it = {0}; ecs_query_iter_t *qit = &it.priv_.iter.query; ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); #ifdef FLECS_DEBUG flecs_check_exclusive_world_access_write(q->real_world); #endif flecs_query_validate_final_fields(q); flecs_poly_assert(q, ecs_query_t); ecs_query_impl_t *impl = flecs_query_impl(q); int32_t i, var_count = impl->var_count; int32_t op_count = impl->op_count ? impl->op_count : 1; it.world = ECS_CONST_CAST(ecs_world_t*, world); /* If world passed to iterator is the real world, but query was created from * a stage, stage takes precedence. */ if (flecs_poly_is(it.world, ecs_world_t) && flecs_poly_is(q->world, ecs_stage_t)) { it.world = ECS_CONST_CAST(ecs_world_t*, q->world); } it.real_world = q->real_world; ecs_assert(flecs_poly_is(it.real_world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); ecs_check(!(it.real_world->flags & EcsWorldMultiThreaded) || it.world != it.real_world, ECS_INVALID_PARAMETER, "create iterator for stage when world is in multithreaded mode"); it.query = q; it.system = q->entity; it.next = ecs_query_next; it.fini = flecs_query_iter_fini; it.field_count = q->field_count; it.sizes = q->sizes; it.set_fields = q->set_fields; it.ref_fields = q->fixed_fields | q->row_fields | q->var_fields; it.row_fields = q->row_fields; it.up_fields = 0; flecs_query_apply_iter_flags(&it, q); flecs_iter_init(it.world, &it, !impl->cache || !(q->flags & EcsQueryIsCacheable)); qit->query_vars = impl->vars; qit->ops = impl->ops; if (q->flags & EcsQueryMatchEmptyTables) { it.flags |= EcsIterMatchEmptyTables; } flecs_query_cache_iter_init(&it, qit, impl); if (var_count) { qit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); } if (op_count) { qit->written = flecs_iter_calloc_n(&it, ecs_write_flags_t, op_count); qit->op_ctx = flecs_iter_calloc_n(&it, ecs_query_op_ctx_t, op_count); } #ifdef FLECS_DEBUG qit->profile = flecs_iter_calloc_n(&it, ecs_query_op_profile_t, op_count); #endif for (i = 1; i < var_count; i ++) { qit->vars[i].entity = EcsWildcard; } /* Set flags for unconstrained query iteration. Can be reinitialized when * variables are constrained on iterator. */ flecs_query_iter_constrain(&it); error: return it; } ecs_iter_t ecs_query_iter( const ecs_world_t *world, const ecs_query_t *q) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); /* Ok, only for stats */ ecs_os_linc(&ECS_CONST_CAST(ecs_query_t*, q)->eval_count); ecs_query_impl_t *impl = flecs_query_impl(q); ecs_query_cache_t *cache = impl->cache; if (cache) { /* If monitors changed, do query rematching */ ecs_flags32_t flags = q->flags; if (!(ecs_world_get_flags(world) & EcsWorldReadonly) && (flags & EcsQueryHasRefs)) { flecs_eval_component_monitors(q->world); } } return flecs_query_iter(world, q); }