diff --git a/docs/Queries.md b/docs/Queries.md index 08dd6f91b7..159ed255a5 100644 --- a/docs/Queries.md +++ b/docs/Queries.md @@ -1041,15 +1041,18 @@ Position, [in] Velocity The following operators are supported by queries: -| Name | DSL operator | C identifier | C++ identifier | Description | -|----------|----------------|---------------|-------------------|-------------| -| And | `,` | `EcsAnd` | `flecs::And` | Match at least once with term | -| Or | `\|\|` | `EcsOr` | `flecs::Or` | Match at least once with one of the OR terms | -| Not | `!` | `EcsNot` | `flecs::Not` | Must not match with term | -| Optional | `?` | `EcsOptional` | `flecs::Optional` | May match with term | -| AndFrom | `AND \|` | `EcsAndFrom` | `flecs::AndFrom` | Match all components from id at least once | -| OrFrom | `OR \|` | `EcsOrFrom` | `flecs::OrFrom` | Match at least one component from id at least once | -| NotFrom | `NOT \|` | `EcsNotFrom` | `flecs::NotFrom` | Don't match any components from id | +| Name | DSL operator | C identifier | C++ identifier | Description | +|-----------|----------------|---------------|-------------------|-------------| +| And | `,` | `EcsAnd` | `flecs::And` | Match at least once with term | +| Or | `\|\|` | `EcsOr` | `flecs::Or` | Match at least once with one of the OR terms | +| Not | `!` | `EcsNot` | `flecs::Not` | Must not match with term | +| Optional | `?` | `EcsOptional` | `flecs::Optional` | May match with term | +| Equal | `==` | `EcsPredEq` | `flecs::PredEq` | Equals entity/entity name | +| Not equal | `!=` | `EcsPredNeq` | `flecs::PredNeq` | Not equals entity/entity name | +| Match | `~=` | `EcsPredMatch`| `flecs::PredMatch`| Match entity name with substring | +| AndFrom | `AND \|` | `EcsAndFrom` | `flecs::AndFrom` | Match all components from id at least once | +| OrFrom | `OR \|` | `EcsOrFrom` | `flecs::OrFrom` | Match at least one component from id at least once | +| NotFrom | `NOT \|` | `EcsNotFrom` | `flecs::NotFrom` | Don't match any components from id | ### And Operator > *Supported by: filters, cached queries, rules* @@ -1252,6 +1255,14 @@ The `Optional` operator optionally matches with a component. While this operator A note on performance: just like the `Not` operator `Optional` terms are efficient to evaluate when combined with other terms, but queries that only have `Optional` terms can be expensive. Because the `Optional` operator does not restrict query results, a query that only has `Optional` terms will match all entities. +When an optional operator is used in a rule, and a variable written by the optional term is read by a subsequent term, the subsequent term becomes a _dependent term_. This means that if the optional term does not match, the dependent term will be ignored. For example: + +``` +SpaceShip, ?(DockedTo, $planet), Planet($planet) +``` + +Because the second term is optional, the variable `$planet` may or may not be set depending on whether the term was matched. As a result the third term becomes dependent: if `$planet` was not set, the term will be ignored. + The following sections show how to use the `Optional` operator in the different language bindings. The code examples use filter queries, but also apply to queries and rules. #### Query Descriptor (C) @@ -1314,6 +1325,75 @@ To create a query with `Optional` terms, use the `?` symbol: Position, ?Velocity ``` +### Equality operators +> *Supported by: rules* + +Equality operators (`==`, `!=`, `~=`) allow a query to ensure that a variable equals a specific entity, that the entity it stores has a specific name, or that the entity name partially matches a substring. + +The left hand side of an equality operator must be a variable. The right hand side of an operator can be an entity identifier or a string for the `==` and `!=` operators, and must be a string in case of the `~=` operator. For example: + +Test if variable `$this` equals `Foo` (`Foo` must exist at query creation time): + +```js +$this == Foo +``` + +Test if variable `$this` equals entity with name `Foo` (`Foo` does not need to exist at query creation time): + +```js +$this == "Foo" +``` + +Test if variable `$this` stores an entity with a name that has substring `Fo`: + +```js +$this ~= "Fo" +``` + +When the equals operator (`==`) is used with a variable that has not yet been initialized, the right-hand side of the operator will be assigned to the variable. + +Other than regular operators, equality operators are set as `first`, with the left hand being `src` and the right hand being `second`. Equality operators can be combined with `And`, `Not` and `Or` terms. + +Terms with equality operators return no data. + +#### Query Descriptor (C) +```c +ecs_rule_t *r = ecs_rule(world, { + .terms = { + // $this == Foo + { .first.id = EcsPredEq, .second.id = Foo }, + // $this != Bar + { .first.id = EcsPredEq, .second.id = Bar, .oper = EcsNot }, + // $this == "Foo" + { .first.id = EcsPredEq, .second = { .name = "Foo", .flags = EcsIsName }}, + // $this ~= "Fo" + { .first.id = EcsPredMatch, .second = { .name = "Fo", .flags = EcsIsName }}, + } +}); +``` + +#### Query Builder (C++) +```cpp +world.rule_builder() + // $this == Foo + .with(flecs::PredEq, Foo) + // $this != Foo + .without(flecs::PredEq, Bar) + // $this == "Foo" + .with(flecs::PredEq).second("Foo").flags(EcsIsName) + // $this ~= "Fo" + .with(flecs::PredMatch).second("Fo").flags(EcsIsName) + .build(); +``` + +#### Query DSL +```js +$this == Foo +$this != Foo +$this == "Foo" +$this != "Fo" +``` + ### AndFrom, OrFrom, NotFrom Operators > *Supported by: filters, cached queries* diff --git a/docs/Quickstart.md b/docs/Quickstart.md index 4dd1c9ed0b..c05b2a4684 100644 --- a/docs/Quickstart.md +++ b/docs/Quickstart.md @@ -53,7 +53,7 @@ When building for emscripten, add the following command line options to the `emc ```bash -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_RUNTIME_METHODS=cwrap --s MODULARIZE=true +-s MODULARIZE=1 -s EXPORT_NAME="my_app" ``` diff --git a/flecs.c b/flecs.c index 2376935a79..51ceebdda4 100644 --- a/flecs.c +++ b/flecs.c @@ -35045,6 +35045,12 @@ typedef enum { EcsRuleUnion, /* Combine output of multiple operations */ EcsRuleEnd, /* Used to denote end of EcsRuleUnion block */ EcsRuleNot, /* Sets iterator state after term was not matched */ + EcsRulePredEq, /* Test if variable is equal to, or assign to if not set */ + EcsRulePredNeq, /* Test if variable is not equal to */ + EcsRulePredEqName, /* Same as EcsRulePredEq but with matching by name */ + EcsRulePredNeqName, /* Same as EcsRulePredNeq but with matching by name */ + EcsRulePredEqMatch, /* Same as EcsRulePredEq but with fuzzy matching by name */ + EcsRulePredNeqMatch, /* Same as EcsRulePredNeq but with fuzzy matching by name */ EcsRuleSetVars, /* Populate it.sources from variables */ EcsRuleSetThis, /* Populate This entity variable */ EcsRuleSetFixed, /* Set fixed source entity ids */ @@ -35079,6 +35085,7 @@ typedef struct ecs_rule_op_t { uint8_t kind; /* Instruction kind */ ecs_flags8_t flags; /* Flags storing whether 1st/2nd are variables */ int8_t field_index; /* Query field corresponding with operation */ + int8_t term_index; /* Query term corresponding with operation */ ecs_rule_lbl_t prev; /* Backtracking label (no data) */ ecs_rule_lbl_t next; /* Forwarding label. Must come after prev */ ecs_rule_lbl_t other; /* Misc register used for control flow */ @@ -35097,16 +35104,6 @@ typedef struct { int16_t remaining; } ecs_rule_and_ctx_t; - /* Each context */ -typedef struct { - int32_t row; -} ecs_rule_each_ctx_t; - - /* Setthis context */ -typedef struct { - ecs_table_range_t range; -} ecs_rule_setthis_ctx_t; - /* Cache for storing results of downward traversal */ typedef struct { ecs_entity_t entity; @@ -35131,12 +35128,30 @@ typedef struct { bool yield_reflexive; } ecs_rule_trav_ctx_t; -/* Trav context */ + /* Eq context */ +typedef struct { + ecs_table_range_t range; + int32_t index; + int16_t name_col; + bool redo; +} ecs_rule_eq_ctx_t; + + /* Each context */ +typedef struct { + int32_t row; +} ecs_rule_each_ctx_t; + + /* Setthis context */ +typedef struct { + ecs_table_range_t range; +} ecs_rule_setthis_ctx_t; + +/* Ids context */ typedef struct { ecs_id_record_t *cur; } ecs_rule_ids_ctx_t; -/* End context (used with Union) */ +/* Ctrlflow context (used with Union) */ typedef struct { ecs_rule_lbl_t lbl; } ecs_rule_ctrlflow_ctx_t; @@ -35151,6 +35166,7 @@ typedef struct ecs_rule_op_ctx_t { ecs_rule_and_ctx_t and; ecs_rule_trav_ctx_t trav; ecs_rule_ids_ctx_t ids; + ecs_rule_eq_ctx_t eq; ecs_rule_each_ctx_t each; ecs_rule_setthis_ctx_t setthis; ecs_rule_ctrlflow_ctx_t ctrlflow; @@ -35242,7 +35258,7 @@ bool flecs_ref_is_written( uint64_t written); /* Compile filter to list of operations */ -void flecs_rule_compile( +int flecs_rule_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_rule_t *rule); @@ -35308,6 +35324,19 @@ ecs_var_id_t flecs_utovar(uint64_t val) { #define flecs_set_var_label(var, lbl) #endif +static +bool flecs_rule_is_builtin_pred( + ecs_term_t *term) +{ + if (term->first.flags & EcsIsEntity) { + ecs_entity_t id = term->first.id; + if (id == EcsPredEq || id == EcsPredMatch || id == EcsPredLookup) { + return true; + } + } + return false; +} + bool flecs_rule_is_written( ecs_var_id_t var_id, uint64_t written) @@ -35357,6 +35386,7 @@ bool flecs_ref_is_written( { ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, kind); if (flags & EcsRuleIsEntity) { + ecs_assert(!(flags & EcsRuleIsVar), ECS_INTERNAL_ERROR, NULL); if (ref->entity) { return true; } @@ -35633,6 +35663,10 @@ void flecs_rule_discover_vars( anonymous_count ++; } } + } else if ((src->flags & EcsIsVariable) && (src->id == EcsThis)) { + if (flecs_rule_is_builtin_pred(term) && term->oper == EcsOr) { + flecs_rule_add_var(rule, EcsThisName, vars, EcsVarEntity); + } } if (flecs_rule_add_var_for_term_id( @@ -36302,7 +36336,48 @@ bool flecs_rule_term_fixed_id( } static -void flecs_rule_compile_term( +int flecs_rule_compile_builtin_pred( + ecs_term_t *term, + ecs_rule_op_t *op, + ecs_write_flags_t write_state) +{ + ecs_entity_t id = term->first.id; + + ecs_rule_op_kind_t eq[] = {EcsRulePredEq, EcsRulePredNeq}; + ecs_rule_op_kind_t eq_name[] = {EcsRulePredEqName, EcsRulePredNeqName}; + ecs_rule_op_kind_t eq_match[] = {EcsRulePredEqMatch, EcsRulePredNeqMatch}; + + ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + + if (id == EcsPredEq) { + if (term->second.flags & EcsIsName) { + op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); + } else { + op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); + } + } else if (id == EcsPredMatch) { + op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); + } + + op->first = op->src; + op->src = (ecs_rule_ref_t){0}; + op->flags &= (ecs_flags8_t)~((EcsRuleIsEntity|EcsRuleIsVar) << EcsRuleSrc); + op->flags &= (ecs_flags8_t)~((EcsRuleIsEntity|EcsRuleIsVar) << EcsRuleFirst); + op->flags |= EcsRuleIsVar << EcsRuleFirst; + + if (flags_2nd & EcsRuleIsVar) { + if (!(write_state & (1ull << op->second.var))) { + ecs_err("uninitialized variable '%s' on right-hand side of " + "equality operator", term->second.name); + return -1; + } + } + + return 0; +} + +static +int flecs_rule_compile_term( ecs_world_t *world, ecs_rule_t *rule, ecs_term_t *term, @@ -36313,6 +36388,8 @@ void flecs_rule_compile_term( bool second_is_var = term->second.flags & EcsIsVariable; bool src_is_var = term->src.flags & EcsIsVariable; bool cond_write = term->oper == EcsOptional; + bool builtin_pred = flecs_rule_is_builtin_pred(term); + bool is_not = (term->oper == EcsNot) && !builtin_pred; ecs_rule_op_t op = {0}; /* Default instruction for And operators. If the source is fixed (like for @@ -36320,6 +36397,7 @@ void 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); /* If rule is transitive, use Trav(ersal) instruction */ if (term->flags & EcsTermTransitive) { @@ -36342,6 +36420,7 @@ void flecs_rule_compile_term( /* Save write state at start of term so we can use it to reliably track * variables got written by this term. */ ecs_write_flags_t cond_write_state = ctx->cond_written; + ecs_write_flags_t write_state = ctx->written; /* Resolve component inheritance if necessary */ ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; @@ -36376,13 +36455,23 @@ void flecs_rule_compile_term( /* 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) { - if (term->oper == EcsNot || term->oper == EcsOptional) { + bool pred_match = builtin_pred && term->first.id == EcsPredMatch; + if (term->oper == EcsNot || term->oper == EcsOptional || pred_match) { ecs_rule_op_t match_any = {0}; match_any.kind = EcsAnd; match_any.flags = EcsRuleIsSelf | (EcsRuleIsEntity << EcsRuleFirst); match_any.flags |= (EcsRuleIsVar << EcsRuleSrc); match_any.src = op.src; - match_any.first.entity = EcsAny; + match_any.field_index = -1; + if (!pred_match) { + match_any.first.entity = EcsAny; + } else { + /* If matching by name, instead of finding all tables, just find + * the ones with a name. */ + match_any.first.entity = ecs_id(EcsIdentifier); + match_any.second.entity = EcsName; + match_any.flags |= (EcsRuleIsEntity << EcsRuleSecond); + } match_any.written = (1ull << src_var); flecs_rule_op_insert(&match_any, ctx); flecs_rule_write_ctx(op.src.var, ctx, false); @@ -36392,6 +36481,22 @@ void flecs_rule_compile_term( } } + /* A bit of special logic for OR expressions and equality predicates. If the + * left-hand of an equality operator is a table, and there are multiple + * operators in an Or expression, the Or chain should match all entities in + * the table that match the right hand sides of the operator expressions. + * For this to work, the src variable needs to be resolved as entity, as an + * Or chain would otherwise only yield the first match from a table. */ + if (src_is_var && src_written && builtin_pred && term->oper == EcsOr) { + /* Or terms are required to have the same source, so we don't have to + * worry about the last term in the chain. */ + if (rule->vars[src_var].kind == EcsVarTable) { + flecs_rule_compile_term_id(world, rule, &op, &term->src, + &op.src, EcsRuleSrc, EcsVarEntity, ctx); + src_var = op.src.var; + } + } + flecs_rule_compile_ensure_vars(rule, &op, &op.src, EcsRuleSrc, ctx, cond_write); /* If source is Any (_) and first and/or second are unconstrained, insert an @@ -36426,7 +36531,7 @@ void flecs_rule_compile_term( /* If term has component inheritance enabled, insert instruction to walk * down the relationship tree of the id. */ if (term->flags & EcsTermIdInherited) { - if (term->oper == EcsNot) { + if (is_not) { /* Ensure that term only matches if none of the inherited ids match * with the source. */ flecs_rule_begin_none(ctx); @@ -36435,7 +36540,7 @@ void flecs_rule_compile_term( } /* Handle Not, Optional, Or operators */ - if (term->oper == EcsNot) { + if (is_not) { flecs_rule_begin_not(ctx); } else if (term->oper == EcsOptional) { flecs_rule_begin_option(ctx); @@ -36465,6 +36570,12 @@ void flecs_rule_compile_term( op.flags |= EcsRuleIsSelf; } + if (builtin_pred) { + if (flecs_rule_compile_builtin_pred(term, &op, write_state)) { + return -1; + } + } + flecs_rule_op_insert(&op, ctx); /* Handle self-references between src and first/second variables */ @@ -36488,7 +36599,7 @@ void flecs_rule_compile_term( } /* Handle closing of Not, Optional and Or operators */ - if (term->oper == EcsNot) { + if (is_not) { /* Restore original first id in case it got replaced with a variable */ op.first = prev_first; op.flags = prev_op_flags; @@ -36516,9 +36627,11 @@ void flecs_rule_compile_term( } } } + + return 0; } -void flecs_rule_compile( +int flecs_rule_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_rule_t *rule) @@ -36565,13 +36678,15 @@ void flecs_rule_compile( /* Compile query terms to instructions */ for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; - flecs_rule_compile_term(world, rule, term, &ctx); + if (flecs_rule_compile_term(world, rule, term, &ctx)) { + return -1; + } } /* If This variable has been written as entity, insert an operation to * assign it to it.entities for consistency. */ ecs_var_id_t this_id = flecs_rule_find_var_id(rule, "This", EcsVarEntity); - if (this_id != EcsVarNone) { + if (this_id != EcsVarNone && (ctx.written & (1ull << this_id))) { ecs_rule_op_t set_this = {0}; set_this.kind = EcsRuleSetThis; set_this.flags |= (EcsRuleIsVar << EcsRuleFirst); @@ -36657,6 +36772,8 @@ void flecs_rule_compile( ecs_rule_op_t *rule_ops = ecs_vec_first_t(ctx.ops, ecs_rule_op_t); ecs_os_memcpy_n(rule->ops, rule_ops, ecs_rule_op_t, op_count); } + + return 0; } #endif @@ -36697,6 +36814,12 @@ const char* flecs_rule_op_str( case EcsRuleUnion: return "union "; case EcsRuleEnd: return "end "; case EcsRuleNot: return "not "; + case EcsRulePredEq: return "eq "; + case EcsRulePredNeq: return "neq "; + case EcsRulePredEqName: return "eq_nm "; + case EcsRulePredNeqName: return "neq_nm "; + case EcsRulePredEqMatch: return "eq_m "; + case EcsRulePredNeqMatch: return "neq_m "; case EcsRuleSetVars: return "setvars "; case EcsRuleSetThis: return "setthis "; case EcsRuleSetFixed: return "setfix "; @@ -36776,7 +36899,9 @@ ecs_rule_t* ecs_rule_init( result->iterable.init = flecs_rule_iter_mixin_init; /* Compile filter to operations */ - flecs_rule_compile(world, stage, result); + if (flecs_rule_compile(world, stage, result)) { + goto error; + } ecs_entity_t entity = const_desc->entity; result->dtor = (ecs_poly_dtor_t)flecs_rule_fini; @@ -36912,6 +37037,20 @@ char* ecs_rule_str_w_profile( if (second_flags) { ecs_strbuf_appendstr(&buf, ", "); flecs_rule_op_ref_str(rule, &op->second, second_flags, &buf); + } else { + switch (op->kind) { + case EcsRulePredEqName: + case EcsRulePredNeqName: + case EcsRulePredEqMatch: + case EcsRulePredNeqMatch: { + int8_t term_index = op->term_index; + ecs_strbuf_appendstr(&buf, ", #[yellow]\""); + ecs_strbuf_appendstr(&buf, rule->filter.terms[term_index].second.name); + ecs_strbuf_appendstr(&buf, "\"#[reset]"); + } + default: + break; + } } ecs_strbuf_appendch(&buf, ')'); @@ -37285,6 +37424,28 @@ ecs_table_t* flecs_rule_get_table( } } +static +ecs_table_range_t flecs_rule_get_range( + const ecs_rule_op_t *op, + const ecs_rule_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); + if (flags & EcsRuleIsEntity) { + ecs_assert(!(flags & EcsRuleIsVar), ECS_INTERNAL_ERROR, NULL); + return flecs_range_from_entity(ref->entity, ctx); + } else { + ecs_var_t *var = &ctx->vars[ref->var]; + if (var->range.table) { + return ctx->vars[ref->var].range; + } else if (var->entity) { + return flecs_range_from_entity(var->entity, ctx); + } + } + return (ecs_table_range_t){0}; +} + static ecs_entity_t flecs_rule_var_get_entity( ecs_var_id_t var_id, @@ -38251,43 +38412,351 @@ bool flecs_rule_not( } int32_t field = op->field_index; - if (field != -1) { - ecs_iter_t *it = ctx->it; + if (field == -1) { + return true; + } - /* Not terms return no data */ - it->columns[field] = 0; + ecs_iter_t *it = ctx->it; - /* Ignore variables written by Not operation */ - uint64_t *written = ctx->written; - uint64_t written_cur = written[ctx->op_index] = written[op->prev + 1]; - ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); - ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + /* Not terms return no data */ + it->columns[field] = 0; - /* Overwrite id with cleared out variables */ - ecs_id_t id = flecs_rule_op_get_id(op, ctx); - if (id) { - it->ids[field] = id; + /* Ignore variables written by Not operation */ + uint64_t *written = ctx->written; + uint64_t written_cur = written[ctx->op_index] = written[op->prev + 1]; + ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); + ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + + /* Overwrite id with cleared out variables */ + ecs_id_t id = flecs_rule_op_get_id(op, ctx); + if (id) { + it->ids[field] = id; + } + + /* Reset variables */ + if (flags_1st & EcsRuleIsVar) { + if (!flecs_ref_is_written(op, &op->first, EcsRuleFirst, written_cur)){ + flecs_rule_var_reset(op->first.var, ctx); + } + } + if (flags_2nd & EcsRuleIsVar) { + if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written_cur)){ + flecs_rule_var_reset(op->second.var, ctx); } + } - /* Reset variables */ - if (flags_1st & EcsRuleIsVar) { - if (!flecs_ref_is_written(op, &op->first, EcsRuleFirst, written_cur)){ - flecs_rule_var_reset(op->first.var, ctx); - } + /* If term has entity src, set it because no other instruction might */ + if (op->flags & (EcsRuleIsEntity << EcsRuleSrc)) { + it->sources[field] = op->src.entity; + } + + return true; /* Flip result */ +} + +static +const char* flecs_rule_name_arg( + const ecs_rule_op_t *op, + ecs_rule_run_ctx_t *ctx) +{ + int8_t term_index = op->term_index; + ecs_term_t *term = &ctx->rule->filter.terms[term_index]; + return term->second.name; +} + +static +bool flecs_rule_compare_range( + const ecs_table_range_t *l, + const ecs_table_range_t *r) +{ + if (l->table != r->table) { + return false; + } + + if (l->count) { + int32_t l_end = l->offset + l->count; + int32_t r_end = r->offset + r->count; + if (r->offset < l->offset) { + return false; } - if (flags_2nd & EcsRuleIsVar) { - if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written_cur)){ - flecs_rule_var_reset(op->second.var, ctx); - } + if (r_end > l_end) { + return false; } + } else { + /* Entire table is matched */ + } + + return true; +} + +static +bool flecs_rule_pred_eq_w_range( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx, + ecs_table_range_t r) +{ + if (redo) { + return false; + } + + uint64_t written = ctx->written[ctx->op_index]; + ecs_var_id_t first_var = op->first.var; + if (!(written & (1ull << first_var))) { + /* left = unknown, right = known. Assign right-hand value to left */ + ecs_var_id_t l = first_var; + ctx->vars[l].range = r; + return true; + } else { + ecs_table_range_t l = flecs_rule_get_range( + op, &op->first, EcsRuleFirst, ctx); - /* If term has entity src, set it because no other instruction might */ - if (op->flags & (EcsRuleIsEntity << EcsRuleSrc)) { - it->sources[field] = op->src.entity; + if (!flecs_rule_compare_range(&l, &r)) { + return false; } + + ctx->vars[first_var].range.offset = r.offset; + ctx->vars[first_var].range.count = r.count; + return true; } +} - return true; /* Flip result */ +static +bool flecs_rule_pred_eq( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsRuleSecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized eq operand"); + + ecs_table_range_t r = flecs_rule_get_range( + op, &op->second, EcsRuleSecond, ctx); + return flecs_rule_pred_eq_w_range(op, redo, ctx, r); +} + +static +bool flecs_rule_pred_eq_name( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + const char *name = flecs_rule_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup_fullpath(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return false; + } + + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_rule_pred_eq_w_range(op, redo, ctx, r); +} + +static +bool flecs_rule_pred_neq_w_range( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx, + ecs_table_range_t r) +{ + ecs_rule_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + ecs_var_id_t first_var = op->first.var; + ecs_table_range_t l = flecs_rule_get_range( + op, &op->first, EcsRuleFirst, ctx); + + /* If tables don't match, neq always returns once */ + if (l.table != r.table) { + return true && !redo; + } + + int32_t l_offset; + int32_t l_count; + if (!redo) { + /* Make sure we're working with the correct table count */ + if (!l.count && l.table) { + l.count = ecs_table_count(l.table); + } + + l_offset = l.offset; + l_count = l.count; + + /* Cache old value */ + op_ctx->range = l; + } else { + l_offset = op_ctx->range.offset; + l_count = op_ctx->range.count; + } + + /* If the table matches, a Neq returns twice: once for the slice before the + * excluded slice, once for the slice after the excluded slice. If the right + * hand range starts & overlaps with the left hand range, there is only + * one slice. */ + ecs_var_t *var = &ctx->vars[first_var]; + if (!redo && r.offset > l_offset) { + int32_t end = r.offset; + if (end > l_count) { + end = l_count; + } + + /* Return first slice */ + var->range.table = l.table; + var->range.offset = l_offset; + var->range.count = end - l_offset; + op_ctx->redo = false; + return true; + } else if (!op_ctx->redo) { + int32_t l_end = op_ctx->range.offset + l_count; + int32_t r_end = r.offset + r.count; + + if (l_end <= r_end) { + /* If end of existing range falls inside the excluded range, there's + * nothing more to return */ + var->range = l; + return false; + } + + /* Return second slice */ + var->range.table = l.table; + var->range.offset = r_end; + var->range.count = l_end - r_end; + + /* Flag so we know we're done the next redo */ + op_ctx->redo = true; + return true; + } else { + /* Restore previous value */ + var->range = l; + return false; + } +} + +static +bool flecs_rule_pred_match( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx, + bool is_neq) +{ + ecs_rule_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + uint64_t written = ctx->written[ctx->op_index]; + ecs_assert(flecs_ref_is_written(op, &op->first, EcsRuleFirst, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized match operand"); + (void)written; + + ecs_var_id_t first_var = op->first.var; + const char *match = flecs_rule_name_arg(op, ctx); + ecs_table_range_t l; + if (!redo) { + l = flecs_rule_get_range(op, &op->first, EcsRuleFirst, ctx); + if (!l.table) { + return false; + } + + if (!l.count) { + l.count = ecs_table_count(l.table); + } + + op_ctx->range = l; + op_ctx->index = l.offset; + op_ctx->name_col = flecs_ito(int16_t, + ecs_table_get_index(ctx->world, l.table, + ecs_pair(ecs_id(EcsIdentifier), EcsName))); + if (op_ctx->name_col == -1) { + return is_neq; + } + op_ctx->name_col = flecs_ito(int16_t, + l.table->storage_map[op_ctx->name_col]); + ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); + } else { + if (op_ctx->name_col == -1) { + /* Table has no name */ + return false; + } + + l = op_ctx->range; + } + + const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].array; + int32_t count = l.offset + l.count, offset = -1; + for (; op_ctx->index < count; op_ctx->index ++) { + const char *name = names[op_ctx->index].value; + bool result = strstr(name, match); + if (is_neq) { + result = !result; + } + + if (!result) { + if (offset != -1) { + break; + } + } else { + if (offset == -1) { + offset = op_ctx->index; + } + } + } + + if (offset == -1) { + ctx->vars[first_var].range = op_ctx->range; + return false; + } + + ctx->vars[first_var].range.offset = offset; + ctx->vars[first_var].range.count = (op_ctx->index - offset); + return true; +} + +static +bool flecs_rule_pred_eq_match( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + return flecs_rule_pred_match(op, redo, ctx, false); +} + +static +bool flecs_rule_pred_neq_match( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + return flecs_rule_pred_match(op, redo, ctx, true); +} + +static +bool flecs_rule_pred_neq( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsRuleSecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized neq operand"); + + ecs_table_range_t r = flecs_rule_get_range( + op, &op->second, EcsRuleSecond, ctx); + return flecs_rule_pred_neq_w_range(op, redo, ctx, r); +} + +static +bool flecs_rule_pred_neq_name( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + const char *name = flecs_rule_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup_fullpath(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return true && !redo; + } + + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_rule_pred_neq_w_range(op, redo, ctx, r); } static @@ -38521,6 +38990,12 @@ bool flecs_rule_run( case EcsRuleUnion: return flecs_rule_union(op, redo, ctx); case EcsRuleEnd: return flecs_rule_end(op, redo, ctx); case EcsRuleNot: return flecs_rule_not(op, redo, ctx); + case EcsRulePredEq: return flecs_rule_pred_eq(op, redo, ctx); + case EcsRulePredNeq: return flecs_rule_pred_neq(op, redo, ctx); + case EcsRulePredEqName: return flecs_rule_pred_eq_name(op, redo, ctx); + case EcsRulePredNeqName: return flecs_rule_pred_neq_name(op, redo, ctx); + case EcsRulePredEqMatch: return flecs_rule_pred_eq_match(op, redo, ctx); + case EcsRulePredNeqMatch: return flecs_rule_pred_neq_match(op, redo, ctx); case EcsRuleSetVars: return flecs_rule_setvars(op, redo, ctx); case EcsRuleSetThis: return flecs_rule_setthis(op, redo, ctx); case EcsRuleSetFixed: return flecs_rule_setfixed(op, redo, ctx); @@ -38950,6 +39425,10 @@ void FlecsDocImport( #define TOK_VARIABLE '$' #define TOK_PAREN_OPEN '(' #define TOK_PAREN_CLOSE ')' +#define TOK_EQ "==" +#define TOK_NEQ "!=" +#define TOK_MATCH "~=" +#define TOK_EXPR_STRING '"' #define TOK_SELF "self" #define TOK_UP "up" @@ -38958,7 +39437,6 @@ void FlecsDocImport( #define TOK_PARENT "parent" #define TOK_OVERRIDE "OVERRIDE" - #define TOK_ROLE_AND "AND" #define TOK_ROLE_OR "OR" #define TOK_ROLE_NOT "NOT" @@ -39168,7 +39646,7 @@ const char* ecs_parse_identifier( const char *ptr, char *token_out) { - if (!flecs_valid_identifier_start_char(ptr[0])) { + if (!flecs_valid_identifier_start_char(ptr[0]) && (ptr[0] != '"')) { ecs_parser_error(name, expr, (ptr - expr), "expected start of identifier"); return NULL; @@ -39189,9 +39667,27 @@ int flecs_parse_identifier( out->flags |= EcsIsVariable; tptr ++; } + if (tptr[0] == TOK_EXPR_STRING && tptr[1]) { + out->flags |= EcsIsName; + tptr ++; + if (tptr[0] == TOK_NOT) { + /* Already parsed */ + tptr ++; + } + } out->name = ecs_os_strdup(tptr); + ecs_size_t len = ecs_os_strlen(out->name); + if (out->flags & EcsIsName) { + if (out->name[len - 1] != TOK_EXPR_STRING) { + ecs_parser_error(NULL, token, 0, "missing '\"' at end of string"); + return -1; + } else { + out->name[len - 1] = '\0'; + } + } + return 0; } @@ -39611,6 +40107,15 @@ const char* flecs_parse_term( } ptr = ecs_parse_ws(ptr + 1); + } else if (!ecs_os_strncmp(ptr, TOK_EQ, 2)) { + ptr = ecs_parse_ws(ptr + 2); + goto parse_eq; + } else if (!ecs_os_strncmp(ptr, TOK_NEQ, 2)) { + ptr = ecs_parse_ws(ptr + 2); + goto parse_neq; + } else if (!ecs_os_strncmp(ptr, TOK_MATCH, 2)) { + ptr = ecs_parse_ws(ptr + 2); + goto parse_match; } else { ptr = ecs_parse_ws(ptr); } @@ -39632,6 +40137,58 @@ const char* flecs_parse_term( goto parse_done; +parse_eq: + term.src = term.first; + term.first = (ecs_term_id_t){0}; + term.first.id = EcsPredEq; + goto parse_right_operand; + +parse_neq: + term.src = term.first; + term.first = (ecs_term_id_t){0}; + term.first.id = EcsPredEq; + if (term.oper != EcsAnd) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid operator combination"); + goto error; + } + term.oper = EcsNot; + goto parse_right_operand; + +parse_match: + term.src = term.first; + term.first = (ecs_term_id_t){0}; + term.first.id = EcsPredMatch; + goto parse_right_operand; + +parse_right_operand: + if (flecs_valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } + + if (term.first.id == EcsPredMatch) { + if (token[0] == '"' && token[1] == '!') { + term.oper = EcsNot; + } + } + + if (flecs_parse_identifier(token, &term.second)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } + + term.src.flags &= ~EcsTraverseFlags; + term.src.flags |= EcsSelf; + term.inout = EcsInOutNone; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier"); + goto error; + } + goto parse_done; parse_pair: ptr = ecs_parse_identifier(name, expr, ptr + 1, token); if (!ptr) { @@ -39800,7 +40357,7 @@ char* ecs_parse_term( } /* Check for $() notation */ - if (!ecs_os_strcmp(term->first.name, "$")) { + if (term->first.name && !ecs_os_strcmp(term->first.name, "$")) { if (term->src.name) { ecs_os_free(term->first.name); @@ -39844,7 +40401,7 @@ char* ecs_parse_term( /* If the term just contained a 0, the expression has nothing. Ensure * that after the 0 nothing else follows */ - if (!ecs_os_strcmp(term->first.name, "0")) { + if (term->first.name && !ecs_os_strcmp(term->first.name, "0")) { if (ptr[0]) { ecs_parser_error(name, expr, (ptr - expr), "unexpected term after 0"); @@ -40942,7 +41499,12 @@ const ecs_entity_t EcsDelete = ECS_HI_COMPONENT_ID + 51; const ecs_entity_t EcsPanic = ECS_HI_COMPONENT_ID + 52; /* Misc */ -const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 55; +const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 53; + +/* Builtin predicate ids (used by rule engine) */ +const ecs_entity_t EcsPredEq = ECS_HI_COMPONENT_ID + 54; +const ecs_entity_t EcsPredMatch = ECS_HI_COMPONENT_ID + 55; +const ecs_entity_t EcsPredLookup = ECS_HI_COMPONENT_ID + 56; /* Systems */ const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61; @@ -44208,7 +44770,7 @@ int flecs_term_id_finalize_flags( return -1; } - if (!(term_id->flags & EcsIsEntity) && !(term_id->flags & EcsIsVariable)) { + if (!(term_id->flags & (EcsIsEntity|EcsIsVariable|EcsIsName))) { if (term_id->id || term_id->name) { if (term_id->id == EcsThis || term_id->id == EcsWildcard || @@ -44265,6 +44827,8 @@ int flecs_term_id_lookup( term_id->name = NULL; } return 0; + } else if (term_id->flags & EcsIsName) { + return 0; } ecs_assert(term_id->flags & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); @@ -44536,6 +45100,56 @@ int flecs_term_populate_from_id( return 0; } +static +int flecs_term_verify_eq_pred( + const ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) +{ + ecs_entity_t first_id = term->first.id; + const ecs_term_id_t *second = &term->second; + const ecs_term_id_t *src = &term->src; + + if (term->oper != EcsAnd && term->oper != EcsNot && term->oper != EcsOr) { + flecs_filter_error(ctx, "invalid operator combination"); + goto error; + } + + if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { + flecs_filter_error(ctx, "both sides of operator cannot be a name"); + goto error; + } + + if ((src->flags & EcsIsEntity) && (second->flags & EcsIsEntity)) { + flecs_filter_error(ctx, "both sides of operator cannot be an entity"); + goto error; + } + + if (!(src->flags & EcsIsVariable)) { + flecs_filter_error(ctx, "left-hand of operator must be a variable"); + goto error; + } + + if (first_id == EcsPredMatch && !(second->flags & EcsIsName)) { + flecs_filter_error(ctx, "right-hand of match operator must be a string"); + goto error; + } + + if ((src->flags & EcsIsVariable) && (second->flags & EcsIsVariable)) { + if (src->id && src->id == second->id) { + flecs_filter_error(ctx, "both sides of operator are equal"); + goto error; + } + if (src->name && second->name && !ecs_os_strcmp(src->name, second->name)) { + flecs_filter_error(ctx, "both sides of operator are equal"); + goto error; + } + } + + return 0; +error: + return -1; +} + static int flecs_term_verify( const ecs_world_t *world, @@ -44549,6 +45163,11 @@ int flecs_term_verify( ecs_id_t role = term->id_flags; ecs_id_t id = term->id; + if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { + flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); + return -1; + } + if (first->flags & EcsIsEntity) { first_id = first->id; } @@ -44556,6 +45175,10 @@ int flecs_term_verify( second_id = second->id; } + if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { + return flecs_term_verify_eq_pred(term, ctx); + } + if (role != (id & ECS_ID_FLAGS_MASK)) { flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); return -1; @@ -55116,6 +55739,11 @@ void flecs_bootstrap( flecs_bootstrap_tag(world, EcsDefaultChildComponent); + /* Builtin predicates */ + flecs_bootstrap_tag(world, EcsPredEq); + flecs_bootstrap_tag(world, EcsPredMatch); + flecs_bootstrap_tag(world, EcsPredLookup); + /* Builtin relationships */ flecs_bootstrap_tag(world, EcsIsA); flecs_bootstrap_tag(world, EcsChildOf); diff --git a/flecs.h b/flecs.h index 94c66390eb..d61fb995bc 100644 --- a/flecs.h +++ b/flecs.h @@ -2631,15 +2631,16 @@ typedef enum ecs_oper_kind_t { } ecs_oper_kind_t; /* Term id flags */ -#define EcsSelf (1u << 1) /**< Match on self */ -#define EcsUp (1u << 2) /**< Match by traversing upwards */ -#define EcsDown (1u << 3) /**< Match by traversing downwards (derived, cannot be set) */ -#define EcsTraverseAll (1u << 4) /**< Match all entities encountered through traversal */ -#define EcsCascade (1u << 5) /**< Sort results breadth first */ -#define EcsParent (1u << 6) /**< Short for up(ChildOf) */ -#define EcsIsVariable (1u << 7) /**< Term id is a variable */ -#define EcsIsEntity (1u << 8) /**< Term id is an entity */ -#define EcsFilter (1u << 9) /**< Prevent observer from triggering on term */ +#define EcsSelf (1u << 1) /**< Match on self */ +#define EcsUp (1u << 2) /**< Match by traversing upwards */ +#define EcsDown (1u << 3) /**< Match by traversing downwards (derived, cannot be set) */ +#define EcsTraverseAll (1u << 4) /**< Match all entities encountered through traversal */ +#define EcsCascade (1u << 5) /**< Sort results breadth first */ +#define EcsParent (1u << 6) /**< Short for up(ChildOf) */ +#define EcsIsVariable (1u << 7) /**< Term id is a variable */ +#define EcsIsEntity (1u << 8) /**< Term id is an entity */ +#define EcsIsName (1u << 9) /**< Term id is a name (don't attempt to lookup as entity) */ +#define EcsFilter (1u << 10) /**< Prevent observer from triggering on term */ #define EcsTraverseFlags (EcsUp|EcsDown|EcsTraverseAll|EcsSelf|EcsCascade|EcsParent) /* Term flags discovered & set during filter creation. */ @@ -4005,6 +4006,11 @@ FLECS_API extern const ecs_entity_t EcsPanic; * component does not change the behavior of core ECS operations. */ FLECS_API extern const ecs_entity_t EcsDefaultChildComponent; +/* Builtin predicates for comparing entity ids in queries. Only supported by rules */ +FLECS_API extern const ecs_entity_t EcsPredEq; +FLECS_API extern const ecs_entity_t EcsPredMatch; +FLECS_API extern const ecs_entity_t EcsPredLookup; + /** Tag used to indicate query is empty */ FLECS_API extern const ecs_entity_t EcsEmpty; @@ -14473,6 +14479,14 @@ static const flecs::entity_t Remove = EcsRemove; static const flecs::entity_t Delete = EcsDelete; static const flecs::entity_t Panic = EcsPanic; +/* Misc */ +static const flecs::entity_t EcsDefaultChildComponent = EcsDefaultChildComponent; + +/* Builtin predicates for comparing entity ids in queries. Only supported by rules */ +static const flecs::entity_t PredEq = EcsPredEq; +static const flecs::entity_t PredMatch = EcsPredMatch; +static const flecs::entity_t PredLookup = EcsPredLookup; + /** @} */ } @@ -24546,6 +24560,13 @@ struct term_id_builder_i { return *this; } + /* Override term id flags */ + Base& flags(flecs::flags32_t flags) { + this->assert_term_id(); + m_term_id->flags = flags; + return *this; + } + ecs_term_id_t *m_term_id; protected: diff --git a/include/flecs.h b/include/flecs.h index 45a673ceea..4b13845870 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -491,15 +491,16 @@ typedef enum ecs_oper_kind_t { } ecs_oper_kind_t; /* Term id flags */ -#define EcsSelf (1u << 1) /**< Match on self */ -#define EcsUp (1u << 2) /**< Match by traversing upwards */ -#define EcsDown (1u << 3) /**< Match by traversing downwards (derived, cannot be set) */ -#define EcsTraverseAll (1u << 4) /**< Match all entities encountered through traversal */ -#define EcsCascade (1u << 5) /**< Sort results breadth first */ -#define EcsParent (1u << 6) /**< Short for up(ChildOf) */ -#define EcsIsVariable (1u << 7) /**< Term id is a variable */ -#define EcsIsEntity (1u << 8) /**< Term id is an entity */ -#define EcsFilter (1u << 9) /**< Prevent observer from triggering on term */ +#define EcsSelf (1u << 1) /**< Match on self */ +#define EcsUp (1u << 2) /**< Match by traversing upwards */ +#define EcsDown (1u << 3) /**< Match by traversing downwards (derived, cannot be set) */ +#define EcsTraverseAll (1u << 4) /**< Match all entities encountered through traversal */ +#define EcsCascade (1u << 5) /**< Sort results breadth first */ +#define EcsParent (1u << 6) /**< Short for up(ChildOf) */ +#define EcsIsVariable (1u << 7) /**< Term id is a variable */ +#define EcsIsEntity (1u << 8) /**< Term id is an entity */ +#define EcsIsName (1u << 9) /**< Term id is a name (don't attempt to lookup as entity) */ +#define EcsFilter (1u << 10) /**< Prevent observer from triggering on term */ #define EcsTraverseFlags (EcsUp|EcsDown|EcsTraverseAll|EcsSelf|EcsCascade|EcsParent) /* Term flags discovered & set during filter creation. */ @@ -1286,6 +1287,11 @@ FLECS_API extern const ecs_entity_t EcsPanic; * component does not change the behavior of core ECS operations. */ FLECS_API extern const ecs_entity_t EcsDefaultChildComponent; +/* Builtin predicates for comparing entity ids in queries. Only supported by rules */ +FLECS_API extern const ecs_entity_t EcsPredEq; +FLECS_API extern const ecs_entity_t EcsPredMatch; +FLECS_API extern const ecs_entity_t EcsPredLookup; + /** Tag used to indicate query is empty */ FLECS_API extern const ecs_entity_t EcsEmpty; diff --git a/include/flecs/addons/cpp/c_types.hpp b/include/flecs/addons/cpp/c_types.hpp index 9a7302714a..e0c36b8e7b 100644 --- a/include/flecs/addons/cpp/c_types.hpp +++ b/include/flecs/addons/cpp/c_types.hpp @@ -135,6 +135,14 @@ static const flecs::entity_t Remove = EcsRemove; static const flecs::entity_t Delete = EcsDelete; static const flecs::entity_t Panic = EcsPanic; +/* Misc */ +static const flecs::entity_t EcsDefaultChildComponent = EcsDefaultChildComponent; + +/* Builtin predicates for comparing entity ids in queries. Only supported by rules */ +static const flecs::entity_t PredEq = EcsPredEq; +static const flecs::entity_t PredMatch = EcsPredMatch; +static const flecs::entity_t PredLookup = EcsPredLookup; + /** @} */ } diff --git a/include/flecs/addons/cpp/mixins/term/builder_i.hpp b/include/flecs/addons/cpp/mixins/term/builder_i.hpp index d34ea0c154..82c04a309c 100644 --- a/include/flecs/addons/cpp/mixins/term/builder_i.hpp +++ b/include/flecs/addons/cpp/mixins/term/builder_i.hpp @@ -116,6 +116,13 @@ struct term_id_builder_i { return *this; } + /* Override term id flags */ + Base& flags(flecs::flags32_t flags) { + this->assert_term_id(); + m_term_id->flags = flags; + return *this; + } + ecs_term_id_t *m_term_id; protected: diff --git a/src/addons/parser.c b/src/addons/parser.c index cc767b93f2..90be5dbe40 100644 --- a/src/addons/parser.c +++ b/src/addons/parser.c @@ -25,6 +25,10 @@ #define TOK_VARIABLE '$' #define TOK_PAREN_OPEN '(' #define TOK_PAREN_CLOSE ')' +#define TOK_EQ "==" +#define TOK_NEQ "!=" +#define TOK_MATCH "~=" +#define TOK_EXPR_STRING '"' #define TOK_SELF "self" #define TOK_UP "up" @@ -33,7 +37,6 @@ #define TOK_PARENT "parent" #define TOK_OVERRIDE "OVERRIDE" - #define TOK_ROLE_AND "AND" #define TOK_ROLE_OR "OR" #define TOK_ROLE_NOT "NOT" @@ -243,7 +246,7 @@ const char* ecs_parse_identifier( const char *ptr, char *token_out) { - if (!flecs_valid_identifier_start_char(ptr[0])) { + if (!flecs_valid_identifier_start_char(ptr[0]) && (ptr[0] != '"')) { ecs_parser_error(name, expr, (ptr - expr), "expected start of identifier"); return NULL; @@ -264,9 +267,27 @@ int flecs_parse_identifier( out->flags |= EcsIsVariable; tptr ++; } + if (tptr[0] == TOK_EXPR_STRING && tptr[1]) { + out->flags |= EcsIsName; + tptr ++; + if (tptr[0] == TOK_NOT) { + /* Already parsed */ + tptr ++; + } + } out->name = ecs_os_strdup(tptr); + ecs_size_t len = ecs_os_strlen(out->name); + if (out->flags & EcsIsName) { + if (out->name[len - 1] != TOK_EXPR_STRING) { + ecs_parser_error(NULL, token, 0, "missing '\"' at end of string"); + return -1; + } else { + out->name[len - 1] = '\0'; + } + } + return 0; } @@ -686,6 +707,15 @@ const char* flecs_parse_term( } ptr = ecs_parse_ws(ptr + 1); + } else if (!ecs_os_strncmp(ptr, TOK_EQ, 2)) { + ptr = ecs_parse_ws(ptr + 2); + goto parse_eq; + } else if (!ecs_os_strncmp(ptr, TOK_NEQ, 2)) { + ptr = ecs_parse_ws(ptr + 2); + goto parse_neq; + } else if (!ecs_os_strncmp(ptr, TOK_MATCH, 2)) { + ptr = ecs_parse_ws(ptr + 2); + goto parse_match; } else { ptr = ecs_parse_ws(ptr); } @@ -707,6 +737,58 @@ const char* flecs_parse_term( goto parse_done; +parse_eq: + term.src = term.first; + term.first = (ecs_term_id_t){0}; + term.first.id = EcsPredEq; + goto parse_right_operand; + +parse_neq: + term.src = term.first; + term.first = (ecs_term_id_t){0}; + term.first.id = EcsPredEq; + if (term.oper != EcsAnd) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid operator combination"); + goto error; + } + term.oper = EcsNot; + goto parse_right_operand; + +parse_match: + term.src = term.first; + term.first = (ecs_term_id_t){0}; + term.first.id = EcsPredMatch; + goto parse_right_operand; + +parse_right_operand: + if (flecs_valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } + + if (term.first.id == EcsPredMatch) { + if (token[0] == '"' && token[1] == '!') { + term.oper = EcsNot; + } + } + + if (flecs_parse_identifier(token, &term.second)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } + + term.src.flags &= ~EcsTraverseFlags; + term.src.flags |= EcsSelf; + term.inout = EcsInOutNone; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier"); + goto error; + } + goto parse_done; parse_pair: ptr = ecs_parse_identifier(name, expr, ptr + 1, token); if (!ptr) { @@ -875,7 +957,7 @@ char* ecs_parse_term( } /* Check for $() notation */ - if (!ecs_os_strcmp(term->first.name, "$")) { + if (term->first.name && !ecs_os_strcmp(term->first.name, "$")) { if (term->src.name) { ecs_os_free(term->first.name); @@ -919,7 +1001,7 @@ char* ecs_parse_term( /* If the term just contained a 0, the expression has nothing. Ensure * that after the 0 nothing else follows */ - if (!ecs_os_strcmp(term->first.name, "0")) { + if (term->first.name && !ecs_os_strcmp(term->first.name, "0")) { if (ptr[0]) { ecs_parser_error(name, expr, (ptr - expr), "unexpected term after 0"); diff --git a/src/addons/rules/api.c b/src/addons/rules/api.c index f66dd73c26..4a98187418 100644 --- a/src/addons/rules/api.c +++ b/src/addons/rules/api.c @@ -35,6 +35,12 @@ const char* flecs_rule_op_str( case EcsRuleUnion: return "union "; case EcsRuleEnd: return "end "; case EcsRuleNot: return "not "; + case EcsRulePredEq: return "eq "; + case EcsRulePredNeq: return "neq "; + case EcsRulePredEqName: return "eq_nm "; + case EcsRulePredNeqName: return "neq_nm "; + case EcsRulePredEqMatch: return "eq_m "; + case EcsRulePredNeqMatch: return "neq_m "; case EcsRuleSetVars: return "setvars "; case EcsRuleSetThis: return "setthis "; case EcsRuleSetFixed: return "setfix "; @@ -114,7 +120,9 @@ ecs_rule_t* ecs_rule_init( result->iterable.init = flecs_rule_iter_mixin_init; /* Compile filter to operations */ - flecs_rule_compile(world, stage, result); + if (flecs_rule_compile(world, stage, result)) { + goto error; + } ecs_entity_t entity = const_desc->entity; result->dtor = (ecs_poly_dtor_t)flecs_rule_fini; @@ -250,6 +258,20 @@ char* ecs_rule_str_w_profile( if (second_flags) { ecs_strbuf_appendstr(&buf, ", "); flecs_rule_op_ref_str(rule, &op->second, second_flags, &buf); + } else { + switch (op->kind) { + case EcsRulePredEqName: + case EcsRulePredNeqName: + case EcsRulePredEqMatch: + case EcsRulePredNeqMatch: { + int8_t term_index = op->term_index; + ecs_strbuf_appendstr(&buf, ", #[yellow]\""); + ecs_strbuf_appendstr(&buf, rule->filter.terms[term_index].second.name); + ecs_strbuf_appendstr(&buf, "\"#[reset]"); + } + default: + break; + } } ecs_strbuf_appendch(&buf, ')'); diff --git a/src/addons/rules/compile.c b/src/addons/rules/compile.c index cf2484924e..0f7da99fde 100644 --- a/src/addons/rules/compile.c +++ b/src/addons/rules/compile.c @@ -40,6 +40,19 @@ ecs_var_id_t flecs_utovar(uint64_t val) { #define flecs_set_var_label(var, lbl) #endif +static +bool flecs_rule_is_builtin_pred( + ecs_term_t *term) +{ + if (term->first.flags & EcsIsEntity) { + ecs_entity_t id = term->first.id; + if (id == EcsPredEq || id == EcsPredMatch || id == EcsPredLookup) { + return true; + } + } + return false; +} + bool flecs_rule_is_written( ecs_var_id_t var_id, uint64_t written) @@ -89,6 +102,7 @@ bool flecs_ref_is_written( { ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, kind); if (flags & EcsRuleIsEntity) { + ecs_assert(!(flags & EcsRuleIsVar), ECS_INTERNAL_ERROR, NULL); if (ref->entity) { return true; } @@ -365,6 +379,10 @@ void flecs_rule_discover_vars( anonymous_count ++; } } + } else if ((src->flags & EcsIsVariable) && (src->id == EcsThis)) { + if (flecs_rule_is_builtin_pred(term) && term->oper == EcsOr) { + flecs_rule_add_var(rule, EcsThisName, vars, EcsVarEntity); + } } if (flecs_rule_add_var_for_term_id( @@ -1034,7 +1052,48 @@ bool flecs_rule_term_fixed_id( } static -void flecs_rule_compile_term( +int flecs_rule_compile_builtin_pred( + ecs_term_t *term, + ecs_rule_op_t *op, + ecs_write_flags_t write_state) +{ + ecs_entity_t id = term->first.id; + + ecs_rule_op_kind_t eq[] = {EcsRulePredEq, EcsRulePredNeq}; + ecs_rule_op_kind_t eq_name[] = {EcsRulePredEqName, EcsRulePredNeqName}; + ecs_rule_op_kind_t eq_match[] = {EcsRulePredEqMatch, EcsRulePredNeqMatch}; + + ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + + if (id == EcsPredEq) { + if (term->second.flags & EcsIsName) { + op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); + } else { + op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); + } + } else if (id == EcsPredMatch) { + op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); + } + + op->first = op->src; + op->src = (ecs_rule_ref_t){0}; + op->flags &= (ecs_flags8_t)~((EcsRuleIsEntity|EcsRuleIsVar) << EcsRuleSrc); + op->flags &= (ecs_flags8_t)~((EcsRuleIsEntity|EcsRuleIsVar) << EcsRuleFirst); + op->flags |= EcsRuleIsVar << EcsRuleFirst; + + if (flags_2nd & EcsRuleIsVar) { + if (!(write_state & (1ull << op->second.var))) { + ecs_err("uninitialized variable '%s' on right-hand side of " + "equality operator", term->second.name); + return -1; + } + } + + return 0; +} + +static +int flecs_rule_compile_term( ecs_world_t *world, ecs_rule_t *rule, ecs_term_t *term, @@ -1045,6 +1104,8 @@ void flecs_rule_compile_term( bool second_is_var = term->second.flags & EcsIsVariable; bool src_is_var = term->src.flags & EcsIsVariable; bool cond_write = term->oper == EcsOptional; + bool builtin_pred = flecs_rule_is_builtin_pred(term); + bool is_not = (term->oper == EcsNot) && !builtin_pred; ecs_rule_op_t op = {0}; /* Default instruction for And operators. If the source is fixed (like for @@ -1052,6 +1113,7 @@ void 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); /* If rule is transitive, use Trav(ersal) instruction */ if (term->flags & EcsTermTransitive) { @@ -1074,6 +1136,7 @@ void flecs_rule_compile_term( /* Save write state at start of term so we can use it to reliably track * variables got written by this term. */ ecs_write_flags_t cond_write_state = ctx->cond_written; + ecs_write_flags_t write_state = ctx->written; /* Resolve component inheritance if necessary */ ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; @@ -1108,13 +1171,23 @@ void flecs_rule_compile_term( /* 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) { - if (term->oper == EcsNot || term->oper == EcsOptional) { + bool pred_match = builtin_pred && term->first.id == EcsPredMatch; + if (term->oper == EcsNot || term->oper == EcsOptional || pred_match) { ecs_rule_op_t match_any = {0}; match_any.kind = EcsAnd; match_any.flags = EcsRuleIsSelf | (EcsRuleIsEntity << EcsRuleFirst); match_any.flags |= (EcsRuleIsVar << EcsRuleSrc); match_any.src = op.src; - match_any.first.entity = EcsAny; + match_any.field_index = -1; + if (!pred_match) { + match_any.first.entity = EcsAny; + } else { + /* If matching by name, instead of finding all tables, just find + * the ones with a name. */ + match_any.first.entity = ecs_id(EcsIdentifier); + match_any.second.entity = EcsName; + match_any.flags |= (EcsRuleIsEntity << EcsRuleSecond); + } match_any.written = (1ull << src_var); flecs_rule_op_insert(&match_any, ctx); flecs_rule_write_ctx(op.src.var, ctx, false); @@ -1124,6 +1197,22 @@ void flecs_rule_compile_term( } } + /* A bit of special logic for OR expressions and equality predicates. If the + * left-hand of an equality operator is a table, and there are multiple + * operators in an Or expression, the Or chain should match all entities in + * the table that match the right hand sides of the operator expressions. + * For this to work, the src variable needs to be resolved as entity, as an + * Or chain would otherwise only yield the first match from a table. */ + if (src_is_var && src_written && builtin_pred && term->oper == EcsOr) { + /* Or terms are required to have the same source, so we don't have to + * worry about the last term in the chain. */ + if (rule->vars[src_var].kind == EcsVarTable) { + flecs_rule_compile_term_id(world, rule, &op, &term->src, + &op.src, EcsRuleSrc, EcsVarEntity, ctx); + src_var = op.src.var; + } + } + flecs_rule_compile_ensure_vars(rule, &op, &op.src, EcsRuleSrc, ctx, cond_write); /* If source is Any (_) and first and/or second are unconstrained, insert an @@ -1158,7 +1247,7 @@ void flecs_rule_compile_term( /* If term has component inheritance enabled, insert instruction to walk * down the relationship tree of the id. */ if (term->flags & EcsTermIdInherited) { - if (term->oper == EcsNot) { + if (is_not) { /* Ensure that term only matches if none of the inherited ids match * with the source. */ flecs_rule_begin_none(ctx); @@ -1167,7 +1256,7 @@ void flecs_rule_compile_term( } /* Handle Not, Optional, Or operators */ - if (term->oper == EcsNot) { + if (is_not) { flecs_rule_begin_not(ctx); } else if (term->oper == EcsOptional) { flecs_rule_begin_option(ctx); @@ -1197,6 +1286,12 @@ void flecs_rule_compile_term( op.flags |= EcsRuleIsSelf; } + if (builtin_pred) { + if (flecs_rule_compile_builtin_pred(term, &op, write_state)) { + return -1; + } + } + flecs_rule_op_insert(&op, ctx); /* Handle self-references between src and first/second variables */ @@ -1220,7 +1315,7 @@ void flecs_rule_compile_term( } /* Handle closing of Not, Optional and Or operators */ - if (term->oper == EcsNot) { + if (is_not) { /* Restore original first id in case it got replaced with a variable */ op.first = prev_first; op.flags = prev_op_flags; @@ -1248,9 +1343,11 @@ void flecs_rule_compile_term( } } } + + return 0; } -void flecs_rule_compile( +int flecs_rule_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_rule_t *rule) @@ -1297,13 +1394,15 @@ void flecs_rule_compile( /* Compile query terms to instructions */ for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; - flecs_rule_compile_term(world, rule, term, &ctx); + if (flecs_rule_compile_term(world, rule, term, &ctx)) { + return -1; + } } /* If This variable has been written as entity, insert an operation to * assign it to it.entities for consistency. */ ecs_var_id_t this_id = flecs_rule_find_var_id(rule, "This", EcsVarEntity); - if (this_id != EcsVarNone) { + if (this_id != EcsVarNone && (ctx.written & (1ull << this_id))) { ecs_rule_op_t set_this = {0}; set_this.kind = EcsRuleSetThis; set_this.flags |= (EcsRuleIsVar << EcsRuleFirst); @@ -1389,6 +1488,8 @@ void flecs_rule_compile( ecs_rule_op_t *rule_ops = ecs_vec_first_t(ctx.ops, ecs_rule_op_t); ecs_os_memcpy_n(rule->ops, rule_ops, ecs_rule_op_t, op_count); } + + return 0; } #endif diff --git a/src/addons/rules/engine.c b/src/addons/rules/engine.c index 83c522846a..be2428e603 100644 --- a/src/addons/rules/engine.c +++ b/src/addons/rules/engine.c @@ -100,6 +100,28 @@ ecs_table_t* flecs_rule_get_table( } } +static +ecs_table_range_t flecs_rule_get_range( + const ecs_rule_op_t *op, + const ecs_rule_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); + if (flags & EcsRuleIsEntity) { + ecs_assert(!(flags & EcsRuleIsVar), ECS_INTERNAL_ERROR, NULL); + return flecs_range_from_entity(ref->entity, ctx); + } else { + ecs_var_t *var = &ctx->vars[ref->var]; + if (var->range.table) { + return ctx->vars[ref->var].range; + } else if (var->entity) { + return flecs_range_from_entity(var->entity, ctx); + } + } + return (ecs_table_range_t){0}; +} + static ecs_entity_t flecs_rule_var_get_entity( ecs_var_id_t var_id, @@ -1066,43 +1088,351 @@ bool flecs_rule_not( } int32_t field = op->field_index; - if (field != -1) { - ecs_iter_t *it = ctx->it; + if (field == -1) { + return true; + } - /* Not terms return no data */ - it->columns[field] = 0; + ecs_iter_t *it = ctx->it; - /* Ignore variables written by Not operation */ - uint64_t *written = ctx->written; - uint64_t written_cur = written[ctx->op_index] = written[op->prev + 1]; - ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); - ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + /* Not terms return no data */ + it->columns[field] = 0; - /* Overwrite id with cleared out variables */ - ecs_id_t id = flecs_rule_op_get_id(op, ctx); - if (id) { - it->ids[field] = id; + /* Ignore variables written by Not operation */ + uint64_t *written = ctx->written; + uint64_t written_cur = written[ctx->op_index] = written[op->prev + 1]; + ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); + ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + + /* Overwrite id with cleared out variables */ + ecs_id_t id = flecs_rule_op_get_id(op, ctx); + if (id) { + it->ids[field] = id; + } + + /* Reset variables */ + if (flags_1st & EcsRuleIsVar) { + if (!flecs_ref_is_written(op, &op->first, EcsRuleFirst, written_cur)){ + flecs_rule_var_reset(op->first.var, ctx); } + } + if (flags_2nd & EcsRuleIsVar) { + if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written_cur)){ + flecs_rule_var_reset(op->second.var, ctx); + } + } - /* Reset variables */ - if (flags_1st & EcsRuleIsVar) { - if (!flecs_ref_is_written(op, &op->first, EcsRuleFirst, written_cur)){ - flecs_rule_var_reset(op->first.var, ctx); - } + /* If term has entity src, set it because no other instruction might */ + if (op->flags & (EcsRuleIsEntity << EcsRuleSrc)) { + it->sources[field] = op->src.entity; + } + + return true; /* Flip result */ +} + +static +const char* flecs_rule_name_arg( + const ecs_rule_op_t *op, + ecs_rule_run_ctx_t *ctx) +{ + int8_t term_index = op->term_index; + ecs_term_t *term = &ctx->rule->filter.terms[term_index]; + return term->second.name; +} + +static +bool flecs_rule_compare_range( + const ecs_table_range_t *l, + const ecs_table_range_t *r) +{ + if (l->table != r->table) { + return false; + } + + if (l->count) { + int32_t l_end = l->offset + l->count; + int32_t r_end = r->offset + r->count; + if (r->offset < l->offset) { + return false; } - if (flags_2nd & EcsRuleIsVar) { - if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written_cur)){ - flecs_rule_var_reset(op->second.var, ctx); - } + if (r_end > l_end) { + return false; } + } else { + /* Entire table is matched */ + } + + return true; +} + +static +bool flecs_rule_pred_eq_w_range( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx, + ecs_table_range_t r) +{ + if (redo) { + return false; + } + + uint64_t written = ctx->written[ctx->op_index]; + ecs_var_id_t first_var = op->first.var; + if (!(written & (1ull << first_var))) { + /* left = unknown, right = known. Assign right-hand value to left */ + ecs_var_id_t l = first_var; + ctx->vars[l].range = r; + return true; + } else { + ecs_table_range_t l = flecs_rule_get_range( + op, &op->first, EcsRuleFirst, ctx); - /* If term has entity src, set it because no other instruction might */ - if (op->flags & (EcsRuleIsEntity << EcsRuleSrc)) { - it->sources[field] = op->src.entity; + if (!flecs_rule_compare_range(&l, &r)) { + return false; } + + ctx->vars[first_var].range.offset = r.offset; + ctx->vars[first_var].range.count = r.count; + return true; } +} - return true; /* Flip result */ +static +bool flecs_rule_pred_eq( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsRuleSecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized eq operand"); + + ecs_table_range_t r = flecs_rule_get_range( + op, &op->second, EcsRuleSecond, ctx); + return flecs_rule_pred_eq_w_range(op, redo, ctx, r); +} + +static +bool flecs_rule_pred_eq_name( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + const char *name = flecs_rule_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup_fullpath(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return false; + } + + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_rule_pred_eq_w_range(op, redo, ctx, r); +} + +static +bool flecs_rule_pred_neq_w_range( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx, + ecs_table_range_t r) +{ + ecs_rule_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + ecs_var_id_t first_var = op->first.var; + ecs_table_range_t l = flecs_rule_get_range( + op, &op->first, EcsRuleFirst, ctx); + + /* If tables don't match, neq always returns once */ + if (l.table != r.table) { + return true && !redo; + } + + int32_t l_offset; + int32_t l_count; + if (!redo) { + /* Make sure we're working with the correct table count */ + if (!l.count && l.table) { + l.count = ecs_table_count(l.table); + } + + l_offset = l.offset; + l_count = l.count; + + /* Cache old value */ + op_ctx->range = l; + } else { + l_offset = op_ctx->range.offset; + l_count = op_ctx->range.count; + } + + /* If the table matches, a Neq returns twice: once for the slice before the + * excluded slice, once for the slice after the excluded slice. If the right + * hand range starts & overlaps with the left hand range, there is only + * one slice. */ + ecs_var_t *var = &ctx->vars[first_var]; + if (!redo && r.offset > l_offset) { + int32_t end = r.offset; + if (end > l_count) { + end = l_count; + } + + /* Return first slice */ + var->range.table = l.table; + var->range.offset = l_offset; + var->range.count = end - l_offset; + op_ctx->redo = false; + return true; + } else if (!op_ctx->redo) { + int32_t l_end = op_ctx->range.offset + l_count; + int32_t r_end = r.offset + r.count; + + if (l_end <= r_end) { + /* If end of existing range falls inside the excluded range, there's + * nothing more to return */ + var->range = l; + return false; + } + + /* Return second slice */ + var->range.table = l.table; + var->range.offset = r_end; + var->range.count = l_end - r_end; + + /* Flag so we know we're done the next redo */ + op_ctx->redo = true; + return true; + } else { + /* Restore previous value */ + var->range = l; + return false; + } +} + +static +bool flecs_rule_pred_match( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx, + bool is_neq) +{ + ecs_rule_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + uint64_t written = ctx->written[ctx->op_index]; + ecs_assert(flecs_ref_is_written(op, &op->first, EcsRuleFirst, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized match operand"); + (void)written; + + ecs_var_id_t first_var = op->first.var; + const char *match = flecs_rule_name_arg(op, ctx); + ecs_table_range_t l; + if (!redo) { + l = flecs_rule_get_range(op, &op->first, EcsRuleFirst, ctx); + if (!l.table) { + return false; + } + + if (!l.count) { + l.count = ecs_table_count(l.table); + } + + op_ctx->range = l; + op_ctx->index = l.offset; + op_ctx->name_col = flecs_ito(int16_t, + ecs_table_get_index(ctx->world, l.table, + ecs_pair(ecs_id(EcsIdentifier), EcsName))); + if (op_ctx->name_col == -1) { + return is_neq; + } + op_ctx->name_col = flecs_ito(int16_t, + l.table->storage_map[op_ctx->name_col]); + ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); + } else { + if (op_ctx->name_col == -1) { + /* Table has no name */ + return false; + } + + l = op_ctx->range; + } + + const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].array; + int32_t count = l.offset + l.count, offset = -1; + for (; op_ctx->index < count; op_ctx->index ++) { + const char *name = names[op_ctx->index].value; + bool result = strstr(name, match); + if (is_neq) { + result = !result; + } + + if (!result) { + if (offset != -1) { + break; + } + } else { + if (offset == -1) { + offset = op_ctx->index; + } + } + } + + if (offset == -1) { + ctx->vars[first_var].range = op_ctx->range; + return false; + } + + ctx->vars[first_var].range.offset = offset; + ctx->vars[first_var].range.count = (op_ctx->index - offset); + return true; +} + +static +bool flecs_rule_pred_eq_match( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + return flecs_rule_pred_match(op, redo, ctx, false); +} + +static +bool flecs_rule_pred_neq_match( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + return flecs_rule_pred_match(op, redo, ctx, true); +} + +static +bool flecs_rule_pred_neq( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsRuleSecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized neq operand"); + + ecs_table_range_t r = flecs_rule_get_range( + op, &op->second, EcsRuleSecond, ctx); + return flecs_rule_pred_neq_w_range(op, redo, ctx, r); +} + +static +bool flecs_rule_pred_neq_name( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + const char *name = flecs_rule_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup_fullpath(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return true && !redo; + } + + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_rule_pred_neq_w_range(op, redo, ctx, r); } static @@ -1336,6 +1666,12 @@ bool flecs_rule_run( case EcsRuleUnion: return flecs_rule_union(op, redo, ctx); case EcsRuleEnd: return flecs_rule_end(op, redo, ctx); case EcsRuleNot: return flecs_rule_not(op, redo, ctx); + case EcsRulePredEq: return flecs_rule_pred_eq(op, redo, ctx); + case EcsRulePredNeq: return flecs_rule_pred_neq(op, redo, ctx); + case EcsRulePredEqName: return flecs_rule_pred_eq_name(op, redo, ctx); + case EcsRulePredNeqName: return flecs_rule_pred_neq_name(op, redo, ctx); + case EcsRulePredEqMatch: return flecs_rule_pred_eq_match(op, redo, ctx); + case EcsRulePredNeqMatch: return flecs_rule_pred_neq_match(op, redo, ctx); case EcsRuleSetVars: return flecs_rule_setvars(op, redo, ctx); case EcsRuleSetThis: return flecs_rule_setthis(op, redo, ctx); case EcsRuleSetFixed: return flecs_rule_setfixed(op, redo, ctx); diff --git a/src/addons/rules/rules.h b/src/addons/rules/rules.h index 147dbf69da..ce725f7bf7 100644 --- a/src/addons/rules/rules.h +++ b/src/addons/rules/rules.h @@ -46,6 +46,12 @@ typedef enum { EcsRuleUnion, /* Combine output of multiple operations */ EcsRuleEnd, /* Used to denote end of EcsRuleUnion block */ EcsRuleNot, /* Sets iterator state after term was not matched */ + EcsRulePredEq, /* Test if variable is equal to, or assign to if not set */ + EcsRulePredNeq, /* Test if variable is not equal to */ + EcsRulePredEqName, /* Same as EcsRulePredEq but with matching by name */ + EcsRulePredNeqName, /* Same as EcsRulePredNeq but with matching by name */ + EcsRulePredEqMatch, /* Same as EcsRulePredEq but with fuzzy matching by name */ + EcsRulePredNeqMatch, /* Same as EcsRulePredNeq but with fuzzy matching by name */ EcsRuleSetVars, /* Populate it.sources from variables */ EcsRuleSetThis, /* Populate This entity variable */ EcsRuleSetFixed, /* Set fixed source entity ids */ @@ -80,6 +86,7 @@ typedef struct ecs_rule_op_t { uint8_t kind; /* Instruction kind */ ecs_flags8_t flags; /* Flags storing whether 1st/2nd are variables */ int8_t field_index; /* Query field corresponding with operation */ + int8_t term_index; /* Query term corresponding with operation */ ecs_rule_lbl_t prev; /* Backtracking label (no data) */ ecs_rule_lbl_t next; /* Forwarding label. Must come after prev */ ecs_rule_lbl_t other; /* Misc register used for control flow */ @@ -98,16 +105,6 @@ typedef struct { int16_t remaining; } ecs_rule_and_ctx_t; - /* Each context */ -typedef struct { - int32_t row; -} ecs_rule_each_ctx_t; - - /* Setthis context */ -typedef struct { - ecs_table_range_t range; -} ecs_rule_setthis_ctx_t; - /* Cache for storing results of downward traversal */ typedef struct { ecs_entity_t entity; @@ -132,12 +129,30 @@ typedef struct { bool yield_reflexive; } ecs_rule_trav_ctx_t; -/* Trav context */ + /* Eq context */ +typedef struct { + ecs_table_range_t range; + int32_t index; + int16_t name_col; + bool redo; +} ecs_rule_eq_ctx_t; + + /* Each context */ +typedef struct { + int32_t row; +} ecs_rule_each_ctx_t; + + /* Setthis context */ +typedef struct { + ecs_table_range_t range; +} ecs_rule_setthis_ctx_t; + +/* Ids context */ typedef struct { ecs_id_record_t *cur; } ecs_rule_ids_ctx_t; -/* End context (used with Union) */ +/* Ctrlflow context (used with Union) */ typedef struct { ecs_rule_lbl_t lbl; } ecs_rule_ctrlflow_ctx_t; @@ -152,6 +167,7 @@ typedef struct ecs_rule_op_ctx_t { ecs_rule_and_ctx_t and; ecs_rule_trav_ctx_t trav; ecs_rule_ids_ctx_t ids; + ecs_rule_eq_ctx_t eq; ecs_rule_each_ctx_t each; ecs_rule_setthis_ctx_t setthis; ecs_rule_ctrlflow_ctx_t ctrlflow; @@ -243,7 +259,7 @@ bool flecs_ref_is_written( uint64_t written); /* Compile filter to list of operations */ -void flecs_rule_compile( +int flecs_rule_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_rule_t *rule); diff --git a/src/bootstrap.c b/src/bootstrap.c index d9587fa786..af6d75eb33 100644 --- a/src/bootstrap.c +++ b/src/bootstrap.c @@ -800,6 +800,11 @@ void flecs_bootstrap( flecs_bootstrap_tag(world, EcsDefaultChildComponent); + /* Builtin predicates */ + flecs_bootstrap_tag(world, EcsPredEq); + flecs_bootstrap_tag(world, EcsPredMatch); + flecs_bootstrap_tag(world, EcsPredLookup); + /* Builtin relationships */ flecs_bootstrap_tag(world, EcsIsA); flecs_bootstrap_tag(world, EcsChildOf); diff --git a/src/filter.c b/src/filter.c index 7da2206642..8963cfb9ca 100644 --- a/src/filter.c +++ b/src/filter.c @@ -74,7 +74,7 @@ int flecs_term_id_finalize_flags( return -1; } - if (!(term_id->flags & EcsIsEntity) && !(term_id->flags & EcsIsVariable)) { + if (!(term_id->flags & (EcsIsEntity|EcsIsVariable|EcsIsName))) { if (term_id->id || term_id->name) { if (term_id->id == EcsThis || term_id->id == EcsWildcard || @@ -131,6 +131,8 @@ int flecs_term_id_lookup( term_id->name = NULL; } return 0; + } else if (term_id->flags & EcsIsName) { + return 0; } ecs_assert(term_id->flags & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); @@ -402,6 +404,56 @@ int flecs_term_populate_from_id( return 0; } +static +int flecs_term_verify_eq_pred( + const ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) +{ + ecs_entity_t first_id = term->first.id; + const ecs_term_id_t *second = &term->second; + const ecs_term_id_t *src = &term->src; + + if (term->oper != EcsAnd && term->oper != EcsNot && term->oper != EcsOr) { + flecs_filter_error(ctx, "invalid operator combination"); + goto error; + } + + if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { + flecs_filter_error(ctx, "both sides of operator cannot be a name"); + goto error; + } + + if ((src->flags & EcsIsEntity) && (second->flags & EcsIsEntity)) { + flecs_filter_error(ctx, "both sides of operator cannot be an entity"); + goto error; + } + + if (!(src->flags & EcsIsVariable)) { + flecs_filter_error(ctx, "left-hand of operator must be a variable"); + goto error; + } + + if (first_id == EcsPredMatch && !(second->flags & EcsIsName)) { + flecs_filter_error(ctx, "right-hand of match operator must be a string"); + goto error; + } + + if ((src->flags & EcsIsVariable) && (second->flags & EcsIsVariable)) { + if (src->id && src->id == second->id) { + flecs_filter_error(ctx, "both sides of operator are equal"); + goto error; + } + if (src->name && second->name && !ecs_os_strcmp(src->name, second->name)) { + flecs_filter_error(ctx, "both sides of operator are equal"); + goto error; + } + } + + return 0; +error: + return -1; +} + static int flecs_term_verify( const ecs_world_t *world, @@ -415,6 +467,11 @@ int flecs_term_verify( ecs_id_t role = term->id_flags; ecs_id_t id = term->id; + if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { + flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); + return -1; + } + if (first->flags & EcsIsEntity) { first_id = first->id; } @@ -422,6 +479,10 @@ int flecs_term_verify( second_id = second->id; } + if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { + return flecs_term_verify_eq_pred(term, ctx); + } + if (role != (id & ECS_ID_FLAGS_MASK)) { flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); return -1; diff --git a/src/world.c b/src/world.c index 742daa3123..081747d65e 100644 --- a/src/world.c +++ b/src/world.c @@ -91,7 +91,12 @@ const ecs_entity_t EcsDelete = ECS_HI_COMPONENT_ID + 51; const ecs_entity_t EcsPanic = ECS_HI_COMPONENT_ID + 52; /* Misc */ -const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 55; +const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 53; + +/* Builtin predicate ids (used by rule engine) */ +const ecs_entity_t EcsPredEq = ECS_HI_COMPONENT_ID + 54; +const ecs_entity_t EcsPredMatch = ECS_HI_COMPONENT_ID + 55; +const ecs_entity_t EcsPredLookup = ECS_HI_COMPONENT_ID + 56; /* Systems */ const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61; diff --git a/test/addons/project.json b/test/addons/project.json index 329dd62aa3..c92a9dd919 100644 --- a/test/addons/project.json +++ b/test/addons/project.json @@ -198,7 +198,37 @@ "pair_implicit_src_missing_rel", "pair_implicit_src_missing_obj", "pair_explicit_src_missing_src", - "pair_explicit_src_missing_obj" + "pair_explicit_src_missing_obj", + "eq_id", + "eq_id_var", + "eq_var_id", + "eq_var", + "neq_id", + "neq_id_var", + "neq_var_id", + "neq_var", + "eq_name", + "eq_name_var", + "eq_var_name", + "eq_var", + "neq_name", + "neq_name_var", + "neq_var_name", + "neq_var", + "match_name", + "match_name_var", + "match_var_name", + "match_var", + "nmatch_name", + "nmatch_name_var", + "nmatch_var_name", + "eq_same_var", + "neq_same_var", + "eq_same_var_this", + "neq_same_var_this", + "eq_w_optional", + "neq_w_optional", + "match_w_optional" ] }, { "id": "Plecs", @@ -886,6 +916,74 @@ "recycled_this_ent_var", "has_recycled_id_from_pair" ] + }, { + "id": "RulesBuiltinPredicates", + "testcases": [ + "this_eq_id", + "this_eq_name", + "this_eq_var", + "this_eq_id_written", + "this_eq_id_written_no_match", + "this_eq_name_written", + "this_eq_name_written_no_match", + "this_eq_var_written", + "var_eq_id", + "var_eq_name", + "var_eq_var", + "var_eq_id_written", + "var_eq_id_written_no_match", + "var_eq_name_written", + "var_eq_name_written_no_match", + "var_eq_var_written", + "this_neq_id", + "this_neq_name", + "this_neq_var", + "this_neq_id_written", + "this_neq_id_written_no_match", + "this_neq_name_written", + "this_neq_name_written_no_match", + "this_neq_var_written", + "var_neq_id", + "var_neq_name", + "var_neq_var", + "var_neq_id_written", + "var_neq_id_written_no_match", + "var_neq_name_written", + "var_neq_name_written_no_match", + "var_neq_var_written", + "this_2_neq_id", + "this_2_neq_name", + "var_2_neq_id", + "var_2_neq_name", + "this_2_neq_id_written", + "this_2_neq_name_written", + "var_2_neq_id_written", + "var_2_neq_name_written", + "this_2_or_id", + "this_2_or_name", + "var_2_or_id", + "var_2_or_name", + "this_2_or_id_written", + "this_2_or_name_written", + "var_2_or_id_written", + "var_2_or_name_written", + "this_match_eq", + "var_match_eq", + "this_match_eq_written", + "var_match_eq_written", + "this_match_neq", + "var_match_neq", + "this_match_neq_written", + "var_match_neq_written", + "this_match_2_neq", + "var_match_2_neq", + "this_match_2_neq_written", + "var_match_2_neq_written", + "this_match_2_or", + "this_match_2_or_written", + "this_match_3_or", + "this_match_3_or_written" + ] }, { "id": "SystemPeriodic", "testcases": [ diff --git a/test/addons/src/Parser.c b/test/addons/src/Parser.c index 781cc3ec9d..9e4804337b 100644 --- a/test/addons/src/Parser.c +++ b/test/addons/src/Parser.c @@ -4557,3 +4557,425 @@ void Parser_pair_explicit_src_missing_obj() { ecs_fini(world); } + +void Parser_eq_id() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, First); + ECS_TAG(world, Second); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "First == Second" + })); + + ecs_fini(world); +} + +void Parser_eq_id_var() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Second); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "$this == Second" + })); + + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsPredEq, EcsSelf|EcsIsEntity); + test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable); + test_second(terms[0], Second, EcsSelf|EcsIsEntity); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutNone); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_eq_var_id() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, First); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "First == $this" + })); + + ecs_fini(world); +} + +void Parser_eq_var() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, First); + ECS_TAG(world, Second); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "$x == $y" + })); + + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsPredEq, EcsSelf|EcsIsEntity); + test_src_var(terms[0], 0, EcsSelf, "x"); + test_second_var(terms[0], 0, EcsSelf, "y"); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutNone); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_neq_id() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, First); + ECS_TAG(world, Second); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "First != Second" + })); + + ecs_fini(world); +} + +void Parser_neq_id_var() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Second); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "$this != Second" + })); + + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsPredEq, EcsSelf|EcsIsEntity); + test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable); + test_second(terms[0], Second, EcsSelf|EcsIsEntity); + test_int(terms[0].oper, EcsNot); + test_int(terms[0].inout, EcsInOutNone); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_neq_var_id() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, First); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "First != $this" + })); + + ecs_fini(world); +} + +void Parser_neq_var() { + ecs_world_t *world = ecs_mini(); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "$x != $y" + })); + + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsPredEq, EcsSelf|EcsIsEntity); + test_src_var(terms[0], 0, EcsSelf, "x"); + test_second_var(terms[0], 0, EcsSelf, "y"); + test_int(terms[0].oper, EcsNot); + test_int(terms[0].inout, EcsInOutNone); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_eq_name() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "\"First\" == \"Second\"" + })); + + ecs_fini(world); +} + +void Parser_eq_name_var() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "\"First\" == $y" + })); + + ecs_fini(world); +} + +void Parser_eq_var_name() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "$x == \"Second\"" + })); + + ecs_fini(world); +} + +void Parser_neq_name() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "\"First\" != \"Second\"" + })); + + ecs_fini(world); +} + +void Parser_neq_name_var() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "\"First\" != $y" + })); + + ecs_fini(world); +} + +void Parser_neq_var_name() { + ecs_world_t *world = ecs_mini(); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "$x != \"Second\"" + })); + + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsPredEq, EcsSelf|EcsIsEntity); + test_uint(terms[0].src.flags, (EcsSelf|EcsIsVariable)); + test_str(terms[0].src.name, "x"); + test_uint(terms[0].src.id, 0); + test_uint(terms[0].second.flags, (EcsSelf|EcsIsName)); + test_str(terms[0].second.name, "Second"); + test_uint(terms[0].second.id, 0); + test_int(terms[0].oper, EcsNot); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_match_name() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "\"First\" ~= \"Second\"" + })); + + ecs_fini(world); +} + +void Parser_match_name_var() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "\"First\" ~= $y" + })); + + ecs_fini(world); +} + +void Parser_match_var_name() { + ecs_world_t *world = ecs_mini(); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "$x ~= \"Second\"" + })); + + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsPredMatch, EcsSelf|EcsIsEntity); + test_uint(terms[0].src.flags, (EcsSelf|EcsIsVariable)); + test_str(terms[0].src.name, "x"); + test_uint(terms[0].src.id, 0); + test_uint(terms[0].second.flags, (EcsSelf|EcsIsName)); + test_str(terms[0].second.name, "Second"); + test_uint(terms[0].second.id, 0); + test_int(terms[0].oper, EcsAnd); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_match_var() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "$x ~= $y" + })); + + ecs_fini(world); +} + +void Parser_nmatch_name() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "\"First\" ~= \"!Second\"" + })); + + ecs_fini(world); +} + +void Parser_nmatch_name_var() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "\"!First\" ~= $y" + })); + + ecs_fini(world); +} + +void Parser_nmatch_var_name() { + ecs_world_t *world = ecs_mini(); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "$x ~= \"!Second\"" + })); + + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsPredMatch, EcsSelf|EcsIsEntity); + test_uint(terms[0].src.flags, (EcsSelf|EcsIsVariable)); + test_str(terms[0].src.name, "x"); + test_uint(terms[0].src.id, 0); + test_uint(terms[0].second.flags, (EcsSelf|EcsIsName)); + test_str(terms[0].second.name, "Second"); + test_uint(terms[0].second.id, 0); + test_int(terms[0].oper, EcsNot); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_eq_same_var() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "$x == $x" + })); + + ecs_fini(world); +} + +void Parser_neq_same_var() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "$x != $x" + })); + + ecs_fini(world); +} + +void Parser_eq_same_var_this() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "$this == $this" + })); + + ecs_fini(world); +} + +void Parser_neq_same_var_this() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "$this != $this" + })); + + ecs_fini(world); +} + +void Parser_eq_w_optional() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Foo); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "?$this == Foo" + })); + + ecs_fini(world); +} + +void Parser_neq_w_optional() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Foo); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "?$this != Foo" + })); + + ecs_fini(world); +} + +void Parser_match_w_optional() { + ecs_world_t *world = ecs_mini(); + + ecs_log_set_level(-4); + test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){ + .expr = "?$this ~= \"Foo\"" + })); + + ecs_fini(world); +} diff --git a/test/addons/src/RulesBuiltinPredicates.c b/test/addons/src/RulesBuiltinPredicates.c new file mode 100644 index 0000000000..6f9d7e7688 --- /dev/null +++ b/test/addons/src/RulesBuiltinPredicates.c @@ -0,0 +1,2685 @@ +#include + +void RulesBuiltinPredicates_this_eq_id() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ent = ecs_new_entity(world, "ent"); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this == ent" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent, it.entities[0]); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_eq_name() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ent = ecs_new_entity(world, "ent"); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this == \"ent\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent, it.entities[0]); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_eq_var() { + ecs_world_t *world = ecs_init(); + + ecs_log_set_level(-4); + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this == $x" + }); + + test_assert(r == NULL); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_eq_id_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this == ent_2" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, it.entities[0]); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_eq_id_written_no_match() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelB); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelB($this), $this == ent_2" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_eq_name_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this == \"ent_2\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, it.entities[0]); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_eq_name_written_no_match() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelB); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelB($this), $this == \"ent_2\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_eq_var_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add_pair(world, ent_1, RelA, ent_2); + ecs_add_pair(world, ent_2, RelA, ent_2); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add_pair(world, ent_3, RelA, ent_2); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this, $x), $this == $x" + }); + + test_assert(r != NULL); + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(ecs_pair(RelA, ent_2), ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, ecs_iter_get_var(&it, x_var)); + test_uint(ent_2, it.entities[0]); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_eq_id() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ent = ecs_new_entity(world, "ent"); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x == ent" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent, ecs_iter_get_var(&it, x_var)); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_eq_name() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ent = ecs_new_entity(world, "ent"); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x == \"ent\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent, ecs_iter_get_var(&it, x_var)); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_eq_var() { + ecs_world_t *world = ecs_init(); + + ecs_log_set_level(-4); + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x == $y" + }); + + test_assert(r == NULL); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_eq_id_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x), $x == ent_2" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, ecs_iter_get_var(&it, x_var)); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_eq_id_written_no_match() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelB); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelB($x), $x == ent_2" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_eq_name_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x), $x == \"ent_2\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, ecs_iter_get_var(&it, x_var)); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_eq_name_written_no_match() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelB); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelB($x), $x == \"ent_2\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_eq_var_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add_pair(world, ent_1, RelA, ent_2); + ecs_add_pair(world, ent_2, RelA, ent_2); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add_pair(world, ent_3, RelA, ent_2); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x, $y), $x == $y" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + int y_var = ecs_rule_find_var(r, "y"); + test_assert(y_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(ecs_pair(RelA, ent_2), ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, ecs_iter_get_var(&it, x_var)); + test_uint(ent_2, ecs_iter_get_var(&it, y_var)); + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_neq_id() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this != ent_2" + }); + + test_assert(r != NULL); + + bool ent_1_found = false; + bool ent_3_found = false; + bool ent_4_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + for (int i = 0; i < it.count; i ++) { + test_assert(it.entities[i] != ent_2); + if (it.entities[i] == ent_1) { + ent_1_found = true; + } + if (it.entities[i] == ent_3) { + ent_3_found = true; + } + if (it.entities[i] == ent_4) { + ent_4_found = true; + } + } + } + + test_bool(ent_1_found, true); + test_bool(ent_3_found, true); + test_bool(ent_4_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_neq_name() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this != \"ent_2\"" + }); + + test_assert(r != NULL); + + bool ent_1_found = false; + bool ent_3_found = false; + bool ent_4_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + for (int i = 0; i < it.count; i ++) { + test_assert(it.entities[i] != ent_2); + if (it.entities[i] == ent_1) { + ent_1_found = true; + } + if (it.entities[i] == ent_3) { + ent_3_found = true; + } + if (it.entities[i] == ent_4) { + ent_4_found = true; + } + } + } + + test_bool(ent_1_found, true); + test_bool(ent_3_found, true); + test_bool(ent_4_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_neq_var() { + ecs_world_t *world = ecs_init(); + + ecs_log_set_level(-4); + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this != $x" + }); + + test_assert(r == NULL); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_neq_id_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this != ent_2" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_neq_id_written_no_match() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelB); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelB($this), $this != ent_2" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelB, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_4, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_neq_name_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this != \"ent_2\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_neq_name_written_no_match() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelB); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelB($this), $this != \"ent_2\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelB, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_4, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_neq_var_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add_pair(world, ent_1, RelA, ent_2); + ecs_add_pair(world, ent_2, RelA, ent_2); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add_pair(world, ent_3, RelA, ent_2); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this, $x), $this != $x" + }); + + test_assert(r != NULL); + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(ecs_pair(RelA, ent_2), ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, ecs_iter_get_var(&it, x_var)); + test_uint(ent_1, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(ecs_pair(RelA, ent_2), ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, ecs_iter_get_var(&it, x_var)); + test_uint(ent_3, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_neq_id() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x != ent_2" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + bool ent_1_found = false; + bool ent_3_found = false; + bool ent_4_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + test_uint(it.count, 0); + ecs_entity_t e = ecs_iter_get_var(&it, x_var); + test_assert(e != ent_2); + if (e == ent_1) { + ent_1_found = true; + } + if (e == ent_3) { + ent_3_found = true; + } + if (e == ent_4) { + ent_4_found = true; + } + } + + test_bool(ent_1_found, true); + test_bool(ent_3_found, true); + test_bool(ent_4_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_neq_name() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x != \"ent_2\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + bool ent_1_found = false; + bool ent_3_found = false; + bool ent_4_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + test_uint(it.count, 0); + ecs_entity_t e = ecs_iter_get_var(&it, x_var); + test_assert(e != ent_2); + if (e == ent_1) { + ent_1_found = true; + } + if (e == ent_3) { + ent_3_found = true; + } + if (e == ent_4) { + ent_4_found = true; + } + } + + test_bool(ent_1_found, true); + test_bool(ent_3_found, true); + test_bool(ent_4_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_neq_var() { + ecs_world_t *world = ecs_init(); + + ecs_log_set_level(-4); + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x != $y" + }); + + test_assert(r == NULL); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_neq_id_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x), $x != ent_2" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_neq_id_written_no_match() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelB); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelB($x), $x != ent_2" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelB, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_4, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_neq_name_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x), $x != \"ent_2\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_neq_name_written_no_match() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelB); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelB($x), $x != \"ent_2\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelB, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_4, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_neq_var_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add_pair(world, ent_1, RelA, ent_2); + ecs_add_pair(world, ent_2, RelA, ent_2); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add_pair(world, ent_3, RelA, ent_2); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x, $y), $x != $y" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + int y_var = ecs_rule_find_var(r, "y"); + test_assert(y_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(ecs_pair(RelA, ent_2), ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, ecs_iter_get_var(&it, x_var)); + test_uint(ent_2, ecs_iter_get_var(&it, y_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(ecs_pair(RelA, ent_2), ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, ecs_iter_get_var(&it, x_var)); + test_uint(ent_2, ecs_iter_get_var(&it, y_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_2_neq_id() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this != ent_2, $this != ent_3" + }); + + test_assert(r != NULL); + + bool ent_1_found = false; + bool ent_4_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + for (int i = 0; i < it.count; i ++) { + test_assert(it.entities[i] != ent_2); + test_assert(it.entities[i] != ent_3); + if (it.entities[i] == ent_1) { + ent_1_found = true; + } + if (it.entities[i] == ent_4) { + ent_4_found = true; + } + if (it.entities[i] == ent_5) { + ent_5_found = true; + } + } + } + + test_bool(ent_1_found, true); + test_bool(ent_4_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_2_neq_name() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this != \"ent_2\", $this != \"ent_3\"" + }); + + test_assert(r != NULL); + + bool ent_1_found = false; + bool ent_4_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + for (int i = 0; i < it.count; i ++) { + test_assert(it.entities[i] != ent_2); + test_assert(it.entities[i] != ent_3); + if (it.entities[i] == ent_1) { + ent_1_found = true; + } + if (it.entities[i] == ent_4) { + ent_4_found = true; + } + if (it.entities[i] == ent_5) { + ent_5_found = true; + } + } + } + + test_bool(ent_1_found, true); + test_bool(ent_4_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_2_neq_id() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x != ent_2, $x != ent_3" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + bool ent_1_found = false; + bool ent_4_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + test_uint(it.count, 0); + ecs_entity_t e = ecs_iter_get_var(&it, x_var); + test_assert(e != ent_2); + test_assert(e != ent_3); + if (e == ent_1) { + ent_1_found = true; + } + if (e == ent_4) { + ent_4_found = true; + } + if (e == ent_5) { + ent_5_found = true; + } + } + + test_bool(ent_1_found, true); + test_bool(ent_4_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_2_neq_name() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x != \"ent_2\", $x != \"ent_3\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + bool ent_1_found = false; + bool ent_4_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + test_uint(it.count, 0); + ecs_entity_t e = ecs_iter_get_var(&it, x_var); + test_assert(e != ent_2); + test_assert(e != ent_3); + if (e == ent_1) { + ent_1_found = true; + } + if (e == ent_4) { + ent_4_found = true; + } + if (e == ent_5) { + ent_5_found = true; + } + } + + test_bool(ent_1_found, true); + test_bool(ent_4_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_2_neq_id_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this != ent_2, $this != ent_3" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(2, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_4, it.entities[0]); + test_uint(ent_5, it.entities[1]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_2_neq_name_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this != \"ent_2\", $this != \"ent_3\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(2, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_4, it.entities[0]); + test_uint(ent_5, it.entities[1]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_2_neq_id_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x), $x != ent_2, $x != ent_3" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_4, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_5, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_2_neq_name_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x), $x != \"ent_2\", $x != \"ent_3\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_4, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_5, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_2_or_id() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + /* ecs_entity_t ent_1 = */ ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + /* ecs_entity_t ent_4 = */ ecs_new_entity(world, "ent_4"); + /* ecs_entity_t ent_5 = */ ecs_new_entity(world, "ent_5"); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this == ent_2 || $this == ent_3" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_2, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_3, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_2_or_name() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + /* ecs_entity_t ent_1 = */ ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + /* ecs_entity_t ent_4 = */ ecs_new_entity(world, "ent_4"); + /* ecs_entity_t ent_5 = */ ecs_new_entity(world, "ent_5"); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this == \"ent_2\" || $this == \"ent_3\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_2, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_3, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_2_or_id() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + /* ecs_entity_t ent_1 = */ ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + /* ecs_entity_t ent_4 = */ ecs_new_entity(world, "ent_4"); + /* ecs_entity_t ent_5 = */ ecs_new_entity(world, "ent_5"); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x == ent_2 || $x == ent_3" + }); + + test_assert(r != NULL); + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_2, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_3, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_2_or_name() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + /* ecs_entity_t ent_1 = */ ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + /* ecs_entity_t ent_4 = */ ecs_new_entity(world, "ent_4"); + /* ecs_entity_t ent_5 = */ ecs_new_entity(world, "ent_5"); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x == \"ent_2\" || $x == \"ent_3\"" + }); + + test_assert(r != NULL); + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_2, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_3, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_2_or_id_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this == ent_2 || $this == ent_3" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_2_or_name_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this == \"ent_2\" || $this == \"ent_3\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_2_or_id_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x), $x == ent_2 || $x == ent_3" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_2_or_name_written() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent_2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "ent_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent_5"); + ecs_add(world, ent_5, RelA); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x), $x == \"ent_2\" || $x == \"ent_3\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_2, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(RelA, ecs_field_id(&it, 1)); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_match_eq() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + /* ecs_entity_t ent_2 = */ ecs_new_entity(world, "ent2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, Tag); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this ~= \"nt_\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_1, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(2, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_3, it.entities[0]); + test_uint(ent_4, it.entities[1]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_6, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_match_eq() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + /* ecs_entity_t ent_2 = */ ecs_new_entity(world, "ent2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, Tag); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x ~= \"nt_\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_1, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_3, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_4, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_6, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_match_eq_written() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, RelA); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, RelA); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this ~= \"nt_\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(2, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, it.entities[0]); + test_uint(ent_4, it.entities[1]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_6, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_match_eq_written() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, RelA); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, RelA); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x), $x ~= \"nt_\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_4, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(0, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_6, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_match_neq() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, Tag); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this ~= \"!nt_\"" + }); + + test_assert(r != NULL); + + bool ent_2_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + for (int i = 0; i < it.count; i ++) { + test_assert(it.entities[i] != ent_1); + test_assert(it.entities[i] != ent_3); + test_assert(it.entities[i] != ent_4); + test_assert(it.entities[i] != ent_6); + if (it.entities[i] == ent_2) { + ent_2_found = true; + } + if (it.entities[i] == ent_5) { + ent_5_found = true; + } + } + } + + test_bool(ent_2_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_match_neq() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, Tag); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x ~= \"!nt_\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + bool ent_2_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + test_uint(it.count, 0); + ecs_entity_t e = ecs_iter_get_var(&it, x_var); + test_assert(e != ent_1); + test_assert(e != ent_3); + test_assert(e != ent_4); + test_assert(e != ent_6); + if (e == ent_2) { + ent_2_found = true; + } + if (e == ent_5) { + ent_5_found = true; + } + } + + test_bool(ent_2_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_match_neq_written() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, RelA); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, RelA); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this ~= \"!nt_\"" + }); + + test_assert(r != NULL); + + bool ent_2_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + for (int i = 0; i < it.count; i ++) { + test_assert(it.entities[i] != ent_1); + test_assert(it.entities[i] != ent_3); + test_assert(it.entities[i] != ent_4); + test_assert(it.entities[i] != ent_6); + if (it.entities[i] == ent_2) { + ent_2_found = true; + } + if (it.entities[i] == ent_5) { + ent_5_found = true; + } + } + } + + test_bool(ent_2_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_match_neq_written() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, RelA); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, RelA); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x ~= \"!nt_\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + bool ent_2_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + test_uint(it.count, 0); + ecs_entity_t e = ecs_iter_get_var(&it, x_var); + test_assert(e != ent_1); + test_assert(e != ent_3); + test_assert(e != ent_4); + test_assert(e != ent_6); + if (e == ent_2) { + ent_2_found = true; + } + if (e == ent_5) { + ent_5_found = true; + } + } + + test_bool(ent_2_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_match_2_neq() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, Tag); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this ~= \"!nt_\", $this ~= \"!_3\"" + }); + + test_assert(r != NULL); + + bool ent_2_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + for (int i = 0; i < it.count; i ++) { + test_assert(it.entities[i] != ent_1); + test_assert(it.entities[i] != ent_3); + test_assert(it.entities[i] != ent_4); + test_assert(it.entities[i] != ent_6); + if (it.entities[i] == ent_2) { + ent_2_found = true; + } + if (it.entities[i] == ent_5) { + ent_5_found = true; + } + } + } + + test_bool(ent_2_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_match_2_neq() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, Tag); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$x ~= \"!nt_\", $x ~= \"!_3\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + bool ent_2_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + ecs_entity_t e = ecs_iter_get_var(&it, x_var); + test_assert(e != ent_1); + test_assert(e != ent_3); + test_assert(e != ent_4); + test_assert(e != ent_6); + if (e == ent_2) { + ent_2_found = true; + } + if (e == ent_5) { + ent_5_found = true; + } + } + + test_bool(ent_2_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_match_2_neq_written() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, RelA); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, RelA); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this ~= \"!nt_\", $this ~= \"!_3\"" + }); + + test_assert(r != NULL); + + bool ent_2_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + for (int i = 0; i < it.count; i ++) { + test_assert(it.entities[i] != ent_1); + test_assert(it.entities[i] != ent_3); + test_assert(it.entities[i] != ent_4); + test_assert(it.entities[i] != ent_6); + if (it.entities[i] == ent_2) { + ent_2_found = true; + } + if (it.entities[i] == ent_5) { + ent_5_found = true; + } + } + } + + test_bool(ent_2_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_var_match_2_neq_written() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, RelA); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, RelA); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($x), $x ~= \"!nt_\", $x ~= \"!_3\"" + }); + + test_assert(r != NULL); + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + bool ent_2_found = false; + bool ent_5_found = false; + + ecs_iter_t it = ecs_rule_iter(world, r); + while (ecs_rule_next(&it)) { + ecs_entity_t e = ecs_iter_get_var(&it, x_var); + test_assert(e != ent_1); + test_assert(e != ent_3); + test_assert(e != ent_4); + test_assert(e != ent_6); + if (e == ent_2) { + ent_2_found = true; + } + if (e == ent_5) { + ent_5_found = true; + } + } + + test_bool(ent_2_found, true); + test_bool(ent_5_found, true); + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_match_2_or() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + /* ecs_entity_t ent_2 = */ ecs_new_entity(world, "ent2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + /* ecs_entity_t ent_4 = */ ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, Tag); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this ~= \"1\" || $this ~= \"_3\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_1, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_3, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_match_2_or_written() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, RelA); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, RelA); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this ~= \"1\" || $this ~= \"_3\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_match_3_or() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + /* ecs_entity_t ent_2 = */ ecs_new_entity(world, "ent2"); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + /* ecs_entity_t ent_4 = */ ecs_new_entity(world, "ent_4"); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, Tag); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "$this ~= \"1\" || $this ~= \"_3\" || $this ~= \"nt_6\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_1, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_3, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(false, ecs_field_is_set(&it, 1)); + test_uint(ent_6, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesBuiltinPredicates_this_match_3_or_written() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_TAG(world, RelA); + + ecs_entity_t ent_1 = ecs_new_entity(world, "ent_1"); + ecs_add(world, ent_1, RelA); + ecs_entity_t ent_2 = ecs_new_entity(world, "ent2"); + ecs_add(world, ent_2, RelA); + ecs_entity_t ent_3 = ecs_new_entity(world, "nt_3"); + ecs_add(world, ent_3, RelA); + ecs_entity_t ent_4 = ecs_new_entity(world, "ent_4"); + ecs_add(world, ent_4, RelA); + ecs_entity_t ent_5 = ecs_new_entity(world, "ent5"); + ecs_add(world, ent_5, RelA); + ecs_entity_t ent_6 = ecs_new_entity(world, "ent_6"); + ecs_add(world, ent_6, RelA); + ecs_add(world, ent_6, Tag); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this), $this ~= \"1\" || $this ~= \"_3\" || $this ~= \"nt_6\"" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_1, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_3, it.entities[0]); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(true, ecs_field_is_set(&it, 1)); + test_uint(false, ecs_field_is_set(&it, 2)); + test_uint(ent_6, it.entities[0]); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} diff --git a/test/addons/src/main.c b/test/addons/src/main.c index a7b1488224..d875fdd266 100644 --- a/test/addons/src/main.c +++ b/test/addons/src/main.c @@ -194,6 +194,36 @@ void Parser_pair_implicit_src_missing_rel(void); void Parser_pair_implicit_src_missing_obj(void); void Parser_pair_explicit_src_missing_src(void); void Parser_pair_explicit_src_missing_obj(void); +void Parser_eq_id(void); +void Parser_eq_id_var(void); +void Parser_eq_var_id(void); +void Parser_eq_var(void); +void Parser_neq_id(void); +void Parser_neq_id_var(void); +void Parser_neq_var_id(void); +void Parser_neq_var(void); +void Parser_eq_name(void); +void Parser_eq_name_var(void); +void Parser_eq_var_name(void); +void Parser_eq_var(void); +void Parser_neq_name(void); +void Parser_neq_name_var(void); +void Parser_neq_var_name(void); +void Parser_neq_var(void); +void Parser_match_name(void); +void Parser_match_name_var(void); +void Parser_match_var_name(void); +void Parser_match_var(void); +void Parser_nmatch_name(void); +void Parser_nmatch_name_var(void); +void Parser_nmatch_var_name(void); +void Parser_eq_same_var(void); +void Parser_neq_same_var(void); +void Parser_eq_same_var_this(void); +void Parser_neq_same_var_this(void); +void Parser_eq_w_optional(void); +void Parser_neq_w_optional(void); +void Parser_match_w_optional(void); // Testsuite 'Plecs' void Plecs_null(void); @@ -861,6 +891,72 @@ void RulesRecycled_recycled_pair_vars(void); void RulesRecycled_recycled_this_ent_var(void); void RulesRecycled_has_recycled_id_from_pair(void); +// Testsuite 'RulesBuiltinPredicates' +void RulesBuiltinPredicates_this_eq_id(void); +void RulesBuiltinPredicates_this_eq_name(void); +void RulesBuiltinPredicates_this_eq_var(void); +void RulesBuiltinPredicates_this_eq_id_written(void); +void RulesBuiltinPredicates_this_eq_id_written_no_match(void); +void RulesBuiltinPredicates_this_eq_name_written(void); +void RulesBuiltinPredicates_this_eq_name_written_no_match(void); +void RulesBuiltinPredicates_this_eq_var_written(void); +void RulesBuiltinPredicates_var_eq_id(void); +void RulesBuiltinPredicates_var_eq_name(void); +void RulesBuiltinPredicates_var_eq_var(void); +void RulesBuiltinPredicates_var_eq_id_written(void); +void RulesBuiltinPredicates_var_eq_id_written_no_match(void); +void RulesBuiltinPredicates_var_eq_name_written(void); +void RulesBuiltinPredicates_var_eq_name_written_no_match(void); +void RulesBuiltinPredicates_var_eq_var_written(void); +void RulesBuiltinPredicates_this_neq_id(void); +void RulesBuiltinPredicates_this_neq_name(void); +void RulesBuiltinPredicates_this_neq_var(void); +void RulesBuiltinPredicates_this_neq_id_written(void); +void RulesBuiltinPredicates_this_neq_id_written_no_match(void); +void RulesBuiltinPredicates_this_neq_name_written(void); +void RulesBuiltinPredicates_this_neq_name_written_no_match(void); +void RulesBuiltinPredicates_this_neq_var_written(void); +void RulesBuiltinPredicates_var_neq_id(void); +void RulesBuiltinPredicates_var_neq_name(void); +void RulesBuiltinPredicates_var_neq_var(void); +void RulesBuiltinPredicates_var_neq_id_written(void); +void RulesBuiltinPredicates_var_neq_id_written_no_match(void); +void RulesBuiltinPredicates_var_neq_name_written(void); +void RulesBuiltinPredicates_var_neq_name_written_no_match(void); +void RulesBuiltinPredicates_var_neq_var_written(void); +void RulesBuiltinPredicates_this_2_neq_id(void); +void RulesBuiltinPredicates_this_2_neq_name(void); +void RulesBuiltinPredicates_var_2_neq_id(void); +void RulesBuiltinPredicates_var_2_neq_name(void); +void RulesBuiltinPredicates_this_2_neq_id_written(void); +void RulesBuiltinPredicates_this_2_neq_name_written(void); +void RulesBuiltinPredicates_var_2_neq_id_written(void); +void RulesBuiltinPredicates_var_2_neq_name_written(void); +void RulesBuiltinPredicates_this_2_or_id(void); +void RulesBuiltinPredicates_this_2_or_name(void); +void RulesBuiltinPredicates_var_2_or_id(void); +void RulesBuiltinPredicates_var_2_or_name(void); +void RulesBuiltinPredicates_this_2_or_id_written(void); +void RulesBuiltinPredicates_this_2_or_name_written(void); +void RulesBuiltinPredicates_var_2_or_id_written(void); +void RulesBuiltinPredicates_var_2_or_name_written(void); +void RulesBuiltinPredicates_this_match_eq(void); +void RulesBuiltinPredicates_var_match_eq(void); +void RulesBuiltinPredicates_this_match_eq_written(void); +void RulesBuiltinPredicates_var_match_eq_written(void); +void RulesBuiltinPredicates_this_match_neq(void); +void RulesBuiltinPredicates_var_match_neq(void); +void RulesBuiltinPredicates_this_match_neq_written(void); +void RulesBuiltinPredicates_var_match_neq_written(void); +void RulesBuiltinPredicates_this_match_2_neq(void); +void RulesBuiltinPredicates_var_match_2_neq(void); +void RulesBuiltinPredicates_this_match_2_neq_written(void); +void RulesBuiltinPredicates_var_match_2_neq_written(void); +void RulesBuiltinPredicates_this_match_2_or(void); +void RulesBuiltinPredicates_this_match_2_or_written(void); +void RulesBuiltinPredicates_this_match_3_or(void); +void RulesBuiltinPredicates_this_match_3_or_written(void); + // Testsuite 'SystemPeriodic' void SystemPeriodic_1_type_1_component(void); void SystemPeriodic_1_type_3_component(void); @@ -1901,6 +1997,126 @@ bake_test_case Parser_testcases[] = { { "pair_explicit_src_missing_obj", Parser_pair_explicit_src_missing_obj + }, + { + "eq_id", + Parser_eq_id + }, + { + "eq_id_var", + Parser_eq_id_var + }, + { + "eq_var_id", + Parser_eq_var_id + }, + { + "eq_var", + Parser_eq_var + }, + { + "neq_id", + Parser_neq_id + }, + { + "neq_id_var", + Parser_neq_id_var + }, + { + "neq_var_id", + Parser_neq_var_id + }, + { + "neq_var", + Parser_neq_var + }, + { + "eq_name", + Parser_eq_name + }, + { + "eq_name_var", + Parser_eq_name_var + }, + { + "eq_var_name", + Parser_eq_var_name + }, + { + "eq_var", + Parser_eq_var + }, + { + "neq_name", + Parser_neq_name + }, + { + "neq_name_var", + Parser_neq_name_var + }, + { + "neq_var_name", + Parser_neq_var_name + }, + { + "neq_var", + Parser_neq_var + }, + { + "match_name", + Parser_match_name + }, + { + "match_name_var", + Parser_match_name_var + }, + { + "match_var_name", + Parser_match_var_name + }, + { + "match_var", + Parser_match_var + }, + { + "nmatch_name", + Parser_nmatch_name + }, + { + "nmatch_name_var", + Parser_nmatch_name_var + }, + { + "nmatch_var_name", + Parser_nmatch_var_name + }, + { + "eq_same_var", + Parser_eq_same_var + }, + { + "neq_same_var", + Parser_neq_same_var + }, + { + "eq_same_var_this", + Parser_eq_same_var_this + }, + { + "neq_same_var_this", + Parser_neq_same_var_this + }, + { + "eq_w_optional", + Parser_eq_w_optional + }, + { + "neq_w_optional", + Parser_neq_w_optional + }, + { + "match_w_optional", + Parser_match_w_optional } }; @@ -4518,6 +4734,265 @@ bake_test_case RulesRecycled_testcases[] = { } }; +bake_test_case RulesBuiltinPredicates_testcases[] = { + { + "this_eq_id", + RulesBuiltinPredicates_this_eq_id + }, + { + "this_eq_name", + RulesBuiltinPredicates_this_eq_name + }, + { + "this_eq_var", + RulesBuiltinPredicates_this_eq_var + }, + { + "this_eq_id_written", + RulesBuiltinPredicates_this_eq_id_written + }, + { + "this_eq_id_written_no_match", + RulesBuiltinPredicates_this_eq_id_written_no_match + }, + { + "this_eq_name_written", + RulesBuiltinPredicates_this_eq_name_written + }, + { + "this_eq_name_written_no_match", + RulesBuiltinPredicates_this_eq_name_written_no_match + }, + { + "this_eq_var_written", + RulesBuiltinPredicates_this_eq_var_written + }, + { + "var_eq_id", + RulesBuiltinPredicates_var_eq_id + }, + { + "var_eq_name", + RulesBuiltinPredicates_var_eq_name + }, + { + "var_eq_var", + RulesBuiltinPredicates_var_eq_var + }, + { + "var_eq_id_written", + RulesBuiltinPredicates_var_eq_id_written + }, + { + "var_eq_id_written_no_match", + RulesBuiltinPredicates_var_eq_id_written_no_match + }, + { + "var_eq_name_written", + RulesBuiltinPredicates_var_eq_name_written + }, + { + "var_eq_name_written_no_match", + RulesBuiltinPredicates_var_eq_name_written_no_match + }, + { + "var_eq_var_written", + RulesBuiltinPredicates_var_eq_var_written + }, + { + "this_neq_id", + RulesBuiltinPredicates_this_neq_id + }, + { + "this_neq_name", + RulesBuiltinPredicates_this_neq_name + }, + { + "this_neq_var", + RulesBuiltinPredicates_this_neq_var + }, + { + "this_neq_id_written", + RulesBuiltinPredicates_this_neq_id_written + }, + { + "this_neq_id_written_no_match", + RulesBuiltinPredicates_this_neq_id_written_no_match + }, + { + "this_neq_name_written", + RulesBuiltinPredicates_this_neq_name_written + }, + { + "this_neq_name_written_no_match", + RulesBuiltinPredicates_this_neq_name_written_no_match + }, + { + "this_neq_var_written", + RulesBuiltinPredicates_this_neq_var_written + }, + { + "var_neq_id", + RulesBuiltinPredicates_var_neq_id + }, + { + "var_neq_name", + RulesBuiltinPredicates_var_neq_name + }, + { + "var_neq_var", + RulesBuiltinPredicates_var_neq_var + }, + { + "var_neq_id_written", + RulesBuiltinPredicates_var_neq_id_written + }, + { + "var_neq_id_written_no_match", + RulesBuiltinPredicates_var_neq_id_written_no_match + }, + { + "var_neq_name_written", + RulesBuiltinPredicates_var_neq_name_written + }, + { + "var_neq_name_written_no_match", + RulesBuiltinPredicates_var_neq_name_written_no_match + }, + { + "var_neq_var_written", + RulesBuiltinPredicates_var_neq_var_written + }, + { + "this_2_neq_id", + RulesBuiltinPredicates_this_2_neq_id + }, + { + "this_2_neq_name", + RulesBuiltinPredicates_this_2_neq_name + }, + { + "var_2_neq_id", + RulesBuiltinPredicates_var_2_neq_id + }, + { + "var_2_neq_name", + RulesBuiltinPredicates_var_2_neq_name + }, + { + "this_2_neq_id_written", + RulesBuiltinPredicates_this_2_neq_id_written + }, + { + "this_2_neq_name_written", + RulesBuiltinPredicates_this_2_neq_name_written + }, + { + "var_2_neq_id_written", + RulesBuiltinPredicates_var_2_neq_id_written + }, + { + "var_2_neq_name_written", + RulesBuiltinPredicates_var_2_neq_name_written + }, + { + "this_2_or_id", + RulesBuiltinPredicates_this_2_or_id + }, + { + "this_2_or_name", + RulesBuiltinPredicates_this_2_or_name + }, + { + "var_2_or_id", + RulesBuiltinPredicates_var_2_or_id + }, + { + "var_2_or_name", + RulesBuiltinPredicates_var_2_or_name + }, + { + "this_2_or_id_written", + RulesBuiltinPredicates_this_2_or_id_written + }, + { + "this_2_or_name_written", + RulesBuiltinPredicates_this_2_or_name_written + }, + { + "var_2_or_id_written", + RulesBuiltinPredicates_var_2_or_id_written + }, + { + "var_2_or_name_written", + RulesBuiltinPredicates_var_2_or_name_written + }, + { + "this_match_eq", + RulesBuiltinPredicates_this_match_eq + }, + { + "var_match_eq", + RulesBuiltinPredicates_var_match_eq + }, + { + "this_match_eq_written", + RulesBuiltinPredicates_this_match_eq_written + }, + { + "var_match_eq_written", + RulesBuiltinPredicates_var_match_eq_written + }, + { + "this_match_neq", + RulesBuiltinPredicates_this_match_neq + }, + { + "var_match_neq", + RulesBuiltinPredicates_var_match_neq + }, + { + "this_match_neq_written", + RulesBuiltinPredicates_this_match_neq_written + }, + { + "var_match_neq_written", + RulesBuiltinPredicates_var_match_neq_written + }, + { + "this_match_2_neq", + RulesBuiltinPredicates_this_match_2_neq + }, + { + "var_match_2_neq", + RulesBuiltinPredicates_var_match_2_neq + }, + { + "this_match_2_neq_written", + RulesBuiltinPredicates_this_match_2_neq_written + }, + { + "var_match_2_neq_written", + RulesBuiltinPredicates_var_match_2_neq_written + }, + { + "this_match_2_or", + RulesBuiltinPredicates_this_match_2_or + }, + { + "this_match_2_or_written", + RulesBuiltinPredicates_this_match_2_or_written + }, + { + "this_match_3_or", + RulesBuiltinPredicates_this_match_3_or + }, + { + "this_match_3_or_written", + RulesBuiltinPredicates_this_match_3_or_written + } +}; + bake_test_case SystemPeriodic_testcases[] = { { "1_type_1_component", @@ -5609,7 +6084,7 @@ static bake_test_suite suites[] = { "Parser", NULL, NULL, - 185, + 215, Parser_testcases }, { @@ -5682,6 +6157,13 @@ static bake_test_suite suites[] = { 4, RulesRecycled_testcases }, + { + "RulesBuiltinPredicates", + NULL, + NULL, + 64, + RulesBuiltinPredicates_testcases + }, { "SystemPeriodic", NULL, @@ -5811,5 +6293,5 @@ static bake_test_suite suites[] = { }; int main(int argc, char *argv[]) { - return bake_test_run("addons", argc, argv, suites, 29); + return bake_test_run("addons", argc, argv, suites, 30); }