diff --git a/flecs.c b/flecs.c index fe64996965..42dfca740b 100644 --- a/flecs.c +++ b/flecs.c @@ -61119,6 +61119,19 @@ ecs_var_id_t flecs_rule_add_var_for_term_id( return flecs_rule_add_var(rule, name, vars, kind); } +/* This function walks over terms to discover which variables are used in the + * query. It needs to provide the following functionality: + * - create table vars for all variables used as source + * - create entity vars for all variables not used as source + * - create entity vars for all non-$this vars + * - create anonymous vars to store the content of wildcards + * - create anonymous vars to store result of lookups (for $var.child_name) + * - create anonymous vars for resolving component inheritance + * - create array that stores the source variable for each field + * - ensure table vars for non-$this variables are anonymous + * - ensure variables created inside scopes are anonymous + * - place anonymous variables after public variables in vars array + */ static void flecs_rule_discover_vars( ecs_stage_t *stage, @@ -61187,7 +61200,9 @@ void flecs_rule_discover_vars( if (var_id == EcsVarNone || var_id == first_var_id) { var_id = flecs_rule_add_var( rule, var_name, vars, EcsVarEntity); + } + if (var_id != EcsVarNone) { /* Mark variable as one for which we need to create a table * variable. Don't create table variable now, so that we can * store it in the non-public part of the variable array. */ @@ -61198,10 +61213,8 @@ void flecs_rule_discover_vars( var->kind = EcsVarAny; anonymous_table_count ++; } - } - if (var_id != EcsVarNone) { - /* Track of which variable ids are used as field source */ + /* Track which variable ids are used as field source */ if (!rule->src_vars) { rule->src_vars = ecs_os_calloc_n(ecs_var_id_t, rule->filter.field_count); @@ -61415,13 +61428,6 @@ ecs_var_id_t flecs_rule_most_specific_var( return flecs_rule_find_var_id(rule, name, kind); } - ecs_var_id_t tvar = flecs_rule_find_var_id(rule, name, EcsVarTable); - if ((tvar != EcsVarNone) && !flecs_rule_is_written(tvar, ctx->written)) { - /* If variable of any kind is requested and variable hasn't been written - * yet, write to table variable */ - return tvar; - } - ecs_var_id_t evar = flecs_rule_find_var_id(rule, name, EcsVarEntity); if ((evar != EcsVarNone) && flecs_rule_is_written(evar, ctx->written)) { /* If entity variable is available and written to, it contains the most @@ -61429,6 +61435,13 @@ ecs_var_id_t flecs_rule_most_specific_var( return evar; } + ecs_var_id_t tvar = flecs_rule_find_var_id(rule, name, EcsVarTable); + if ((tvar != EcsVarNone) && !flecs_rule_is_written(tvar, ctx->written)) { + /* If variable of any kind is requested and variable hasn't been written + * yet, write to table variable */ + return tvar; + } + /* If table var is written, and entity var doesn't exist or is not written, * return table var */ if (tvar != EcsVarNone) { @@ -61896,8 +61909,8 @@ void flecs_rule_insert_contains( contains.kind = EcsRuleContain; contains.src.var = src_var; contains.first.var = other_var; - contains.flags |=(EcsRuleIsVar << EcsRuleSrc) | - (EcsRuleIsVar << EcsRuleFirst); + contains.flags |= (EcsRuleIsVar << EcsRuleSrc) | + (EcsRuleIsVar << EcsRuleFirst); flecs_rule_op_insert(&contains, ctx); } } @@ -62080,6 +62093,15 @@ void flecs_rule_compile_pop( ctx->cur = &ctx->ctrlflow[-- ctx->scope]; } +static +bool flecs_rule_term_is_or( + const ecs_filter_t *filter, + const ecs_term_t *term) +{ + bool first_term = term == filter->terms; + return (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); +} + static int flecs_rule_compile_term( ecs_world_t *world, @@ -62087,13 +62109,14 @@ int flecs_rule_compile_term( ecs_term_t *term, ecs_rule_compile_ctx_t *ctx) { - bool first_term = term == rule->filter.terms; + ecs_filter_t *filter = &rule->filter; + bool first_term = term == filter->terms; bool first_is_var = term->first.flags & EcsIsVariable; bool second_is_var = term->second.flags & EcsIsVariable; bool src_is_var = term->src.flags & EcsIsVariable; bool builtin_pred = flecs_rule_is_builtin_pred(term); bool is_not = (term->oper == EcsNot) && !builtin_pred; - bool is_or = (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); + bool is_or = flecs_rule_term_is_or(filter, term); bool first_or = false, last_or = false; bool cond_write = term->oper == EcsOptional || is_or; ecs_rule_op_t op = {0}; @@ -62152,7 +62175,7 @@ int flecs_rule_compile_term( * just matches against a source (vs. finding a source). */ op.kind = src_is_var ? EcsRuleAnd : EcsRuleWith; op.field_index = flecs_ito(int8_t, term->field_index); - op.term_index = flecs_ito(int8_t, term - rule->filter.terms); + op.term_index = flecs_ito(int8_t, term - filter->terms); /* If rule is transitive, use Trav(ersal) instruction */ if (term->flags & EcsTermTransitive) { @@ -62172,7 +62195,7 @@ int flecs_rule_compile_term( /* If term has fixed id, insert simpler instruction that skips dealing with * wildcard terms and variables */ - if (flecs_rule_term_fixed_id(&rule->filter, term)) { + if (flecs_rule_term_fixed_id(filter, term)) { if (op.kind == EcsRuleAnd) { op.kind = EcsRuleAndId; } @@ -62195,6 +62218,11 @@ int flecs_rule_compile_term( if (first_is_var) first_var = op.first.var; if (second_is_var) second_var = op.second.var; + flecs_rule_compile_term_id(world, rule, &op, &term->src, + &op.src, EcsRuleSrc, EcsVarAny, ctx, true); + if (src_is_var) src_var = op.src.var; + bool src_written = flecs_rule_is_written(src_var, ctx->written); + /* Insert each instructions for table -> entity variable if needed */ bool first_written, second_written; if (flecs_rule_compile_ensure_vars( @@ -62208,12 +62236,6 @@ int flecs_rule_compile_term( goto error; } - /* Do src last, in case it uses the same variable as first/second */ - flecs_rule_compile_term_id(world, rule, &op, &term->src, - &op.src, EcsRuleSrc, EcsVarAny, ctx, true); - if (src_is_var) src_var = op.src.var; - bool src_written = flecs_rule_is_written(src_var, ctx->written); - /* If the query starts with a Not or Optional term, insert an operation that * matches all entities. */ if (first_term && src_is_var && !src_written) { @@ -62368,7 +62390,7 @@ int flecs_rule_compile_term( * filtering out disabled/prefab entities is the default and this check is * cheap to perform on table flags, it's worth special casing. */ if (!src_written && src_var == 0) { - ecs_flags32_t filter_flags = rule->filter.flags; + ecs_flags32_t filter_flags = filter->flags; if (!(filter_flags & EcsFilterMatchDisabled) || !(filter_flags & EcsFilterMatchPrefab)) { @@ -62440,6 +62462,86 @@ int flecs_rule_compile_term( return -1; } +static +bool flecs_rule_term_is_unknown( + ecs_rule_t *rule, + ecs_term_t *term, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t dummy = {0}; + flecs_rule_compile_term_id(NULL, rule, &dummy, &term->first, + &dummy.first, EcsRuleFirst, EcsVarEntity, ctx, false); + flecs_rule_compile_term_id(NULL, rule, &dummy, &term->second, + &dummy.second, EcsRuleSecond, EcsVarEntity, ctx, false); + flecs_rule_compile_term_id(NULL, rule, &dummy, &term->second, + &dummy.src, EcsRuleSrc, EcsVarAny, ctx, false); + + bool has_vars = dummy.flags & + ((EcsRuleIsVar << EcsRuleFirst) | + (EcsRuleIsVar << EcsRuleSecond) | + (EcsRuleIsVar << EcsRuleSrc)); + if (!has_vars) { + /* If term has no variables (typically terms with a static src) there + * can't be anything that's unknown. */ + return false; + } + + if (dummy.flags & (EcsRuleIsVar << EcsRuleFirst)) { + if (ctx->written & (1 << dummy.first.var)) { + return false; + } + } + if (dummy.flags & (EcsRuleIsVar << EcsRuleSecond)) { + if (ctx->written & (1 << dummy.second.var)) { + return false; + } + } + if (dummy.flags & (EcsRuleIsVar << EcsRuleSrc)) { + if (ctx->written & (1 << dummy.src.var)) { + return false; + } + } + + return true; +} + +static +int32_t flecs_rule_term_next_known( + ecs_rule_t *rule, + ecs_rule_compile_ctx_t *ctx, + int32_t offset, + ecs_flags64_t compiled) +{ + ecs_filter_t *filter = &rule->filter; + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + + for (i = offset; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (compiled & (1 << i)) { + continue; + } + + /* Only evaluate And terms */ + if (term->oper != EcsAnd || flecs_rule_term_is_or(&rule->filter, term)){ + continue; + } + + /* Don't reorder terms before/after scopes */ + if (term->first.id == EcsScopeOpen || term->first.id == EcsScopeClose) { + return -1; + } + + if (flecs_rule_term_is_unknown(rule, term, ctx)) { + continue; + } + + return i; + } + + return -1; +} + int flecs_rule_compile( ecs_world_t *world, ecs_stage_t *stage, @@ -62486,11 +62588,42 @@ int flecs_rule_compile( } /* Compile query terms to instructions */ + ecs_flags64_t compiled = 0; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; + int32_t compile = i; + + if (compiled & (1 << i)) { + continue; /* Already compiled */ + } + + bool can_reorder = true; + if (term->oper != EcsAnd || flecs_rule_term_is_or(&rule->filter, term)){ + can_reorder = false; + } + + /* If variables have been written, but this term has no known variables, + * first try to resolve terms that have known variables. This can + * significantly reduce the search space. + * Only perform this optimization after at least one variable has been + * written to, as all terms are unknown otherwise. */ + if (can_reorder && ctx.written && + flecs_rule_term_is_unknown(rule, term, &ctx)) + { + int32_t term_index = flecs_rule_term_next_known( + rule, &ctx, i + 1, compiled); + if (term_index != -1) { + term = &rule->filter.terms[term_index]; + compile = term_index; + i --; /* Repeat current term */ + } + } + if (flecs_rule_compile_term(world, rule, term, &ctx)) { return -1; } + + compiled |= (1 << compile); } ecs_var_id_t this_id = flecs_rule_find_var_id(rule, "This", EcsVarEntity); diff --git a/src/addons/rules/compile.c b/src/addons/rules/compile.c index 1eacfb35c3..ae81c3fbe2 100644 --- a/src/addons/rules/compile.c +++ b/src/addons/rules/compile.c @@ -341,6 +341,19 @@ ecs_var_id_t flecs_rule_add_var_for_term_id( return flecs_rule_add_var(rule, name, vars, kind); } +/* This function walks over terms to discover which variables are used in the + * query. It needs to provide the following functionality: + * - create table vars for all variables used as source + * - create entity vars for all variables not used as source + * - create entity vars for all non-$this vars + * - create anonymous vars to store the content of wildcards + * - create anonymous vars to store result of lookups (for $var.child_name) + * - create anonymous vars for resolving component inheritance + * - create array that stores the source variable for each field + * - ensure table vars for non-$this variables are anonymous + * - ensure variables created inside scopes are anonymous + * - place anonymous variables after public variables in vars array + */ static void flecs_rule_discover_vars( ecs_stage_t *stage, @@ -409,7 +422,9 @@ void flecs_rule_discover_vars( if (var_id == EcsVarNone || var_id == first_var_id) { var_id = flecs_rule_add_var( rule, var_name, vars, EcsVarEntity); + } + if (var_id != EcsVarNone) { /* Mark variable as one for which we need to create a table * variable. Don't create table variable now, so that we can * store it in the non-public part of the variable array. */ @@ -420,10 +435,8 @@ void flecs_rule_discover_vars( var->kind = EcsVarAny; anonymous_table_count ++; } - } - if (var_id != EcsVarNone) { - /* Track of which variable ids are used as field source */ + /* Track which variable ids are used as field source */ if (!rule->src_vars) { rule->src_vars = ecs_os_calloc_n(ecs_var_id_t, rule->filter.field_count); @@ -637,13 +650,6 @@ ecs_var_id_t flecs_rule_most_specific_var( return flecs_rule_find_var_id(rule, name, kind); } - ecs_var_id_t tvar = flecs_rule_find_var_id(rule, name, EcsVarTable); - if ((tvar != EcsVarNone) && !flecs_rule_is_written(tvar, ctx->written)) { - /* If variable of any kind is requested and variable hasn't been written - * yet, write to table variable */ - return tvar; - } - ecs_var_id_t evar = flecs_rule_find_var_id(rule, name, EcsVarEntity); if ((evar != EcsVarNone) && flecs_rule_is_written(evar, ctx->written)) { /* If entity variable is available and written to, it contains the most @@ -651,6 +657,13 @@ ecs_var_id_t flecs_rule_most_specific_var( return evar; } + ecs_var_id_t tvar = flecs_rule_find_var_id(rule, name, EcsVarTable); + if ((tvar != EcsVarNone) && !flecs_rule_is_written(tvar, ctx->written)) { + /* If variable of any kind is requested and variable hasn't been written + * yet, write to table variable */ + return tvar; + } + /* If table var is written, and entity var doesn't exist or is not written, * return table var */ if (tvar != EcsVarNone) { @@ -1118,8 +1131,8 @@ void flecs_rule_insert_contains( contains.kind = EcsRuleContain; contains.src.var = src_var; contains.first.var = other_var; - contains.flags |=(EcsRuleIsVar << EcsRuleSrc) | - (EcsRuleIsVar << EcsRuleFirst); + contains.flags |= (EcsRuleIsVar << EcsRuleSrc) | + (EcsRuleIsVar << EcsRuleFirst); flecs_rule_op_insert(&contains, ctx); } } @@ -1302,6 +1315,15 @@ void flecs_rule_compile_pop( ctx->cur = &ctx->ctrlflow[-- ctx->scope]; } +static +bool flecs_rule_term_is_or( + const ecs_filter_t *filter, + const ecs_term_t *term) +{ + bool first_term = term == filter->terms; + return (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); +} + static int flecs_rule_compile_term( ecs_world_t *world, @@ -1309,13 +1331,14 @@ int flecs_rule_compile_term( ecs_term_t *term, ecs_rule_compile_ctx_t *ctx) { - bool first_term = term == rule->filter.terms; + ecs_filter_t *filter = &rule->filter; + bool first_term = term == filter->terms; bool first_is_var = term->first.flags & EcsIsVariable; bool second_is_var = term->second.flags & EcsIsVariable; bool src_is_var = term->src.flags & EcsIsVariable; bool builtin_pred = flecs_rule_is_builtin_pred(term); bool is_not = (term->oper == EcsNot) && !builtin_pred; - bool is_or = (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); + bool is_or = flecs_rule_term_is_or(filter, term); bool first_or = false, last_or = false; bool cond_write = term->oper == EcsOptional || is_or; ecs_rule_op_t op = {0}; @@ -1374,7 +1397,7 @@ int flecs_rule_compile_term( * just matches against a source (vs. finding a source). */ op.kind = src_is_var ? EcsRuleAnd : EcsRuleWith; op.field_index = flecs_ito(int8_t, term->field_index); - op.term_index = flecs_ito(int8_t, term - rule->filter.terms); + op.term_index = flecs_ito(int8_t, term - filter->terms); /* If rule is transitive, use Trav(ersal) instruction */ if (term->flags & EcsTermTransitive) { @@ -1394,7 +1417,7 @@ int flecs_rule_compile_term( /* If term has fixed id, insert simpler instruction that skips dealing with * wildcard terms and variables */ - if (flecs_rule_term_fixed_id(&rule->filter, term)) { + if (flecs_rule_term_fixed_id(filter, term)) { if (op.kind == EcsRuleAnd) { op.kind = EcsRuleAndId; } @@ -1417,6 +1440,11 @@ int flecs_rule_compile_term( if (first_is_var) first_var = op.first.var; if (second_is_var) second_var = op.second.var; + flecs_rule_compile_term_id(world, rule, &op, &term->src, + &op.src, EcsRuleSrc, EcsVarAny, ctx, true); + if (src_is_var) src_var = op.src.var; + bool src_written = flecs_rule_is_written(src_var, ctx->written); + /* Insert each instructions for table -> entity variable if needed */ bool first_written, second_written; if (flecs_rule_compile_ensure_vars( @@ -1430,12 +1458,6 @@ int flecs_rule_compile_term( goto error; } - /* Do src last, in case it uses the same variable as first/second */ - flecs_rule_compile_term_id(world, rule, &op, &term->src, - &op.src, EcsRuleSrc, EcsVarAny, ctx, true); - if (src_is_var) src_var = op.src.var; - bool src_written = flecs_rule_is_written(src_var, ctx->written); - /* If the query starts with a Not or Optional term, insert an operation that * matches all entities. */ if (first_term && src_is_var && !src_written) { @@ -1590,7 +1612,7 @@ int flecs_rule_compile_term( * filtering out disabled/prefab entities is the default and this check is * cheap to perform on table flags, it's worth special casing. */ if (!src_written && src_var == 0) { - ecs_flags32_t filter_flags = rule->filter.flags; + ecs_flags32_t filter_flags = filter->flags; if (!(filter_flags & EcsFilterMatchDisabled) || !(filter_flags & EcsFilterMatchPrefab)) { @@ -1662,6 +1684,86 @@ int flecs_rule_compile_term( return -1; } +static +bool flecs_rule_term_is_unknown( + ecs_rule_t *rule, + ecs_term_t *term, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t dummy = {0}; + flecs_rule_compile_term_id(NULL, rule, &dummy, &term->first, + &dummy.first, EcsRuleFirst, EcsVarEntity, ctx, false); + flecs_rule_compile_term_id(NULL, rule, &dummy, &term->second, + &dummy.second, EcsRuleSecond, EcsVarEntity, ctx, false); + flecs_rule_compile_term_id(NULL, rule, &dummy, &term->second, + &dummy.src, EcsRuleSrc, EcsVarAny, ctx, false); + + bool has_vars = dummy.flags & + ((EcsRuleIsVar << EcsRuleFirst) | + (EcsRuleIsVar << EcsRuleSecond) | + (EcsRuleIsVar << EcsRuleSrc)); + if (!has_vars) { + /* If term has no variables (typically terms with a static src) there + * can't be anything that's unknown. */ + return false; + } + + if (dummy.flags & (EcsRuleIsVar << EcsRuleFirst)) { + if (ctx->written & (1 << dummy.first.var)) { + return false; + } + } + if (dummy.flags & (EcsRuleIsVar << EcsRuleSecond)) { + if (ctx->written & (1 << dummy.second.var)) { + return false; + } + } + if (dummy.flags & (EcsRuleIsVar << EcsRuleSrc)) { + if (ctx->written & (1 << dummy.src.var)) { + return false; + } + } + + return true; +} + +static +int32_t flecs_rule_term_next_known( + ecs_rule_t *rule, + ecs_rule_compile_ctx_t *ctx, + int32_t offset, + ecs_flags64_t compiled) +{ + ecs_filter_t *filter = &rule->filter; + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + + for (i = offset; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (compiled & (1 << i)) { + continue; + } + + /* Only evaluate And terms */ + if (term->oper != EcsAnd || flecs_rule_term_is_or(&rule->filter, term)){ + continue; + } + + /* Don't reorder terms before/after scopes */ + if (term->first.id == EcsScopeOpen || term->first.id == EcsScopeClose) { + return -1; + } + + if (flecs_rule_term_is_unknown(rule, term, ctx)) { + continue; + } + + return i; + } + + return -1; +} + int flecs_rule_compile( ecs_world_t *world, ecs_stage_t *stage, @@ -1708,11 +1810,42 @@ int flecs_rule_compile( } /* Compile query terms to instructions */ + ecs_flags64_t compiled = 0; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; + int32_t compile = i; + + if (compiled & (1 << i)) { + continue; /* Already compiled */ + } + + bool can_reorder = true; + if (term->oper != EcsAnd || flecs_rule_term_is_or(&rule->filter, term)){ + can_reorder = false; + } + + /* If variables have been written, but this term has no known variables, + * first try to resolve terms that have known variables. This can + * significantly reduce the search space. + * Only perform this optimization after at least one variable has been + * written to, as all terms are unknown otherwise. */ + if (can_reorder && ctx.written && + flecs_rule_term_is_unknown(rule, term, &ctx)) + { + int32_t term_index = flecs_rule_term_next_known( + rule, &ctx, i + 1, compiled); + if (term_index != -1) { + term = &rule->filter.terms[term_index]; + compile = term_index; + i --; /* Repeat current term */ + } + } + if (flecs_rule_compile_term(world, rule, term, &ctx)) { return -1; } + + compiled |= (1 << compile); } ecs_var_id_t this_id = flecs_rule_find_var_id(rule, "This", EcsVarEntity); diff --git a/test/addons/project.json b/test/addons/project.json index 258e271e80..1d16542f11 100644 --- a/test/addons/project.json +++ b/test/addons/project.json @@ -790,7 +790,12 @@ "instanced_w_singleton", "instanced_w_base", "not_instanced_w_singleton", - "not_instanced_w_base" + "not_instanced_w_base", + "unknown_before_known", + "unknown_before_known_after_or", + "unknown_before_known_after_not", + "unknown_before_known_after_optional", + "unknown_before_known_after_scope" ] }, { "id": "RulesVariables", diff --git a/test/addons/src/RulesBasic.c b/test/addons/src/RulesBasic.c index 43c21151e7..34afdf378a 100644 --- a/test/addons/src/RulesBasic.c +++ b/test/addons/src/RulesBasic.c @@ -5990,3 +5990,213 @@ void RulesBasic_not_instanced_w_base(void) { ecs_fini(world); } + +void RulesBasic_unknown_before_known(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Foo); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "Foo, ChildOf($gc, $c), ChildOf($c, $this)" + }); + + test_assert(r != NULL); + + int c_var = ecs_rule_find_var(r, "c"); + test_assert(c_var != -1); + int gc_var = ecs_rule_find_var(r, "gc"); + test_assert(gc_var != -1); + + ecs_entity_t p = ecs_new(world, Foo); + ecs_entity_t c = ecs_new_w_pair(world, EcsChildOf, p); + /* ecs_entity_t c2 = */ ecs_new_w_pair(world, EcsChildOf, p); + ecs_entity_t gc = ecs_new_w_pair(world, EcsChildOf, c); + + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_int(1, it.count); + test_uint(p, it.entities[0]); + test_uint(Foo, ecs_field_id(&it, 1)); + test_uint(ecs_pair(EcsChildOf, c), ecs_field_id(&it, 2)); + test_uint(ecs_pair(EcsChildOf, p), ecs_field_id(&it, 3)); + test_uint(c, ecs_iter_get_var(&it, c_var)); + test_uint(gc, ecs_iter_get_var(&it, gc_var)); + test_assert(!ecs_rule_next(&it)); + + ecs_fini(world); +} + +void RulesBasic_unknown_before_known_after_or(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "Foo, ChildOf($gc, $c), Foo($gc) || Bar($gc), ChildOf($c, $this)" + }); + + test_assert(r != NULL); + + int c_var = ecs_rule_find_var(r, "c"); + test_assert(c_var != -1); + int gc_var = ecs_rule_find_var(r, "gc"); + test_assert(gc_var != -1); + + ecs_entity_t p = ecs_new(world, Foo); + ecs_entity_t c = ecs_new_w_pair(world, EcsChildOf, p); + /* ecs_entity_t c2 = */ ecs_new_w_pair(world, EcsChildOf, p); + ecs_entity_t gc1 = ecs_new_w_pair(world, EcsChildOf, c); + ecs_add(world, gc1, Foo); + ecs_entity_t gc2 = ecs_new_w_pair(world, EcsChildOf, c); + ecs_add(world, gc2, Bar); + /* ecs_entity_t gc3 = */ ecs_new_w_pair(world, EcsChildOf, c); + + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_int(1, it.count); + test_uint(p, it.entities[0]); + test_uint(Foo, ecs_field_id(&it, 1)); + test_uint(ecs_pair(EcsChildOf, c), ecs_field_id(&it, 2)); + test_uint(ecs_pair(EcsChildOf, p), ecs_field_id(&it, 4)); + test_uint(c, ecs_iter_get_var(&it, c_var)); + test_uint(gc1, ecs_iter_get_var(&it, gc_var)); + + test_bool(true, ecs_rule_next(&it)); + test_int(1, it.count); + test_uint(p, it.entities[0]); + test_uint(Foo, ecs_field_id(&it, 1)); + test_uint(ecs_pair(EcsChildOf, c), ecs_field_id(&it, 2)); + test_uint(ecs_pair(EcsChildOf, p), ecs_field_id(&it, 4)); + test_uint(c, ecs_iter_get_var(&it, c_var)); + test_uint(gc2, ecs_iter_get_var(&it, gc_var)); + test_assert(!ecs_rule_next(&it)); + + ecs_fini(world); +} + +void RulesBasic_unknown_before_known_after_not(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Foo); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "Foo, ChildOf($gc, $c), !Foo($gc), ChildOf($c, $this)" + }); + + test_assert(r != NULL); + + int c_var = ecs_rule_find_var(r, "c"); + test_assert(c_var != -1); + int gc_var = ecs_rule_find_var(r, "gc"); + test_assert(gc_var != -1); + + ecs_entity_t p = ecs_new(world, Foo); + ecs_entity_t c = ecs_new_w_pair(world, EcsChildOf, p); + /* ecs_entity_t c2 = */ ecs_new_w_pair(world, EcsChildOf, p); + ecs_entity_t gc1 = ecs_new_w_pair(world, EcsChildOf, c); + ecs_entity_t gc2 = ecs_new_w_pair(world, EcsChildOf, c); + ecs_add(world, gc2, Foo); + + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_int(1, it.count); + test_uint(p, it.entities[0]); + test_uint(Foo, ecs_field_id(&it, 1)); + test_uint(ecs_pair(EcsChildOf, c), ecs_field_id(&it, 2)); + test_uint(Foo, ecs_field_id(&it, 3)); + test_uint(ecs_pair(EcsChildOf, p), ecs_field_id(&it, 4)); + test_uint(c, ecs_iter_get_var(&it, c_var)); + test_uint(gc1, ecs_iter_get_var(&it, gc_var)); + test_assert(!ecs_rule_next(&it)); + + ecs_fini(world); +} + +void RulesBasic_unknown_before_known_after_optional(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Foo); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "Foo, ChildOf($gc, $c), ?Foo($gc), ChildOf($c, $this)" + }); + + test_assert(r != NULL); + + int c_var = ecs_rule_find_var(r, "c"); + test_assert(c_var != -1); + int gc_var = ecs_rule_find_var(r, "gc"); + test_assert(gc_var != -1); + + ecs_entity_t p = ecs_new(world, Foo); + ecs_entity_t c = ecs_new_w_pair(world, EcsChildOf, p); + /* ecs_entity_t c2 = */ ecs_new_w_pair(world, EcsChildOf, p); + ecs_entity_t gc1 = ecs_new_w_pair(world, EcsChildOf, c); + ecs_entity_t gc2 = ecs_new_w_pair(world, EcsChildOf, c); + ecs_add(world, gc2, Foo); + + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_int(1, it.count); + test_uint(p, it.entities[0]); + test_uint(Foo, ecs_field_id(&it, 1)); + test_uint(ecs_pair(EcsChildOf, c), ecs_field_id(&it, 2)); + test_uint(Foo, ecs_field_id(&it, 3)); + test_uint(ecs_pair(EcsChildOf, p), ecs_field_id(&it, 4)); + test_bool(false, ecs_field_is_set(&it, 3)); + test_uint(c, ecs_iter_get_var(&it, c_var)); + test_uint(gc1, ecs_iter_get_var(&it, gc_var)); + + test_bool(true, ecs_rule_next(&it)); + test_int(1, it.count); + test_uint(p, it.entities[0]); + test_uint(Foo, ecs_field_id(&it, 1)); + test_uint(ecs_pair(EcsChildOf, c), ecs_field_id(&it, 2)); + test_uint(Foo, ecs_field_id(&it, 3)); + test_uint(ecs_pair(EcsChildOf, p), ecs_field_id(&it, 4)); + test_bool(true, ecs_field_is_set(&it, 3)); + test_uint(c, ecs_iter_get_var(&it, c_var)); + test_uint(gc2, ecs_iter_get_var(&it, gc_var)); + test_assert(!ecs_rule_next(&it)); + + ecs_fini(world); +} + +void RulesBasic_unknown_before_known_after_scope(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "Foo, ChildOf($gc, $c), !{Foo($gc) || Bar($gc)}, ChildOf($c, $this)" + }); + + test_assert(r != NULL); + + int c_var = ecs_rule_find_var(r, "c"); + test_assert(c_var != -1); + int gc_var = ecs_rule_find_var(r, "gc"); + test_assert(gc_var != -1); + + ecs_entity_t p = ecs_new(world, Foo); + ecs_entity_t c = ecs_new_w_pair(world, EcsChildOf, p); + /* ecs_entity_t c2 = */ ecs_new_w_pair(world, EcsChildOf, p); + ecs_entity_t gc1 = ecs_new_w_pair(world, EcsChildOf, c); + ecs_entity_t gc2 = ecs_new_w_pair(world, EcsChildOf, c); + ecs_add(world, gc2, Foo); + ecs_entity_t gc3 = ecs_new_w_pair(world, EcsChildOf, c); + ecs_add(world, gc3, Bar); + + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_int(1, it.count); + test_uint(p, it.entities[0]); + test_uint(Foo, ecs_field_id(&it, 1)); + test_uint(c, ecs_iter_get_var(&it, c_var)); + test_uint(gc1, ecs_iter_get_var(&it, gc_var)); + test_assert(!ecs_rule_next(&it)); + + ecs_fini(world); +} diff --git a/test/addons/src/main.c b/test/addons/src/main.c index 7258cd736a..9379537c3d 100644 --- a/test/addons/src/main.c +++ b/test/addons/src/main.c @@ -776,6 +776,11 @@ void RulesBasic_instanced_w_singleton(void); void RulesBasic_instanced_w_base(void); void RulesBasic_not_instanced_w_singleton(void); void RulesBasic_not_instanced_w_base(void); +void RulesBasic_unknown_before_known(void); +void RulesBasic_unknown_before_known_after_or(void); +void RulesBasic_unknown_before_known_after_not(void); +void RulesBasic_unknown_before_known_after_optional(void); +void RulesBasic_unknown_before_known_after_scope(void); // Testsuite 'RulesVariables' void RulesVariables_1_ent_src_w_var(void); @@ -4795,6 +4800,26 @@ bake_test_case RulesBasic_testcases[] = { { "not_instanced_w_base", RulesBasic_not_instanced_w_base + }, + { + "unknown_before_known", + RulesBasic_unknown_before_known + }, + { + "unknown_before_known_after_or", + RulesBasic_unknown_before_known_after_or + }, + { + "unknown_before_known_after_not", + RulesBasic_unknown_before_known_after_not + }, + { + "unknown_before_known_after_optional", + RulesBasic_unknown_before_known_after_optional + }, + { + "unknown_before_known_after_scope", + RulesBasic_unknown_before_known_after_scope } }; @@ -8556,7 +8581,7 @@ static bake_test_suite suites[] = { "RulesBasic", NULL, NULL, - 125, + 130, RulesBasic_testcases }, {