From 39446dd794230e09cf2ffeb47199c3840f466b7d Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 27 Jul 2023 20:40:01 -0700 Subject: [PATCH] Make variable writes in OR chains conditional --- flecs.c | 110 +++++++- src/addons/rules/api.c | 1 + src/addons/rules/compile.c | 91 +++++- src/addons/rules/engine.c | 15 + src/addons/rules/rules.h | 3 + test/addons/project.json | 6 + test/addons/src/RulesOperators.c | 456 +++++++++++++++++++++++++++++++ test/addons/src/main.c | 32 ++- 8 files changed, 695 insertions(+), 19 deletions(-) diff --git a/flecs.c b/flecs.c index 7e553166e..6f8e4612f 100644 --- a/flecs.c +++ b/flecs.c @@ -40392,6 +40392,7 @@ typedef enum { EcsRuleIdsLeft, /* Find ids in use that match (*, T) wildcard */ EcsRuleEach, /* Iterate entities in table, populate entity variable */ EcsRuleStore, /* Store table or entity in variable */ + EcsRuleReset, /* Reset value of variable to wildcard (*) */ EcsRuleUnion, /* Combine output of multiple operations */ EcsRuleEnd, /* Used to denote end of EcsRuleUnion block */ EcsRuleNot, /* Sets iterator state after term was not matched */ @@ -40533,6 +40534,8 @@ typedef struct { ecs_rule_lbl_t lbl_or; ecs_rule_lbl_t lbl_none; ecs_rule_lbl_t lbl_prev; /* If set, use this as default value for prev */ + ecs_write_flags_t cond_written_or; /* Cond written flags at start of or chain */ + bool in_or; /* Whether we're in an or chain */ } ecs_rule_compile_ctrlflow_t; /* Rule compiler state */ @@ -40729,9 +40732,6 @@ void flecs_rule_write_ctx( if (cond_write) { flecs_rule_write(var_id, &ctx->cond_written); } - if (ctx->scope != 0) { - - } } } @@ -41386,6 +41386,14 @@ void flecs_rule_begin_cond_eval( cond_mask |= (1ull << src_var); } + /* Variables set in an OR chain are marked as conditional writes. However, + * writes from previous terms in the current OR chain shouldn't be treated + * as variables that are conditionally set, so instead use the write mask + * from before the chain started. */ + if (ctx->ctrlflow->in_or) { + cond_write_state = ctx->ctrlflow->cond_written_or; + } + /* If this term uses conditionally set variables, insert instruction that * jumps over the term if the variables weren't set yet. */ if (cond_mask & cond_write_state) { @@ -41430,20 +41438,58 @@ void flecs_rule_end_cond_eval( ops[count - 2].next = flecs_itolbl(count); } +static +void flecs_rule_insert_reset_after_or( + ecs_rule_compile_ctx_t *ctx) +{ + /* Scan which variables were conditionally written in the OR chain and + * reset instructions after the OR chain. If a variable is set in part one + * of a chain but not part two, there would be nothing writing to the + * variable in part two, leaving it to the previous value. To address this + * a reset is inserted that resets the variable value on redo. */ + int32_t i; + for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { + ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); + ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); + + if (!prev && cur) { + ecs_rule_op_t reset_op = {0}; + reset_op.kind = EcsRuleReset; + reset_op.flags |= (EcsRuleIsVar << EcsRuleSrc); + reset_op.src.var = flecs_itovar(i); + flecs_rule_op_insert(&reset_op, ctx); + } + } +} + static void flecs_rule_next_or( ecs_rule_compile_ctx_t *ctx) { ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); int32_t count = ecs_vec_count(ctx->ops); - ops[count - 1].next = FlecsRuleOrMarker; + ecs_rule_op_t *op = &ops[count - 1]; + + if (op->kind == EcsRuleNot) { + /* If the engine reaches a Not operation, it means that the term in the + * chain was negated. This can happen when part(s) of the term have + * conditionally set variables: if a variable was not set this will + * cause the term to automatically evaluate to false. + * In this case, the Not operation should proceed to the next term in + * the chain, whereas the preceding operation should upon success skip + * to the end of the chain, which is what the marker is replaced with. */ + ops[count - 2].next = FlecsRuleOrMarker; + } else { + ops[count - 1].next = FlecsRuleOrMarker; + } } static void flecs_rule_begin_or( - ecs_rule_compile_ctx_t *ctx) + ecs_rule_compile_ctx_t *ctx, + ecs_rule_lbl_t lbl_start) { - ctx->cur->lbl_or = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); + ctx->cur->lbl_or = flecs_itolbl(lbl_start); flecs_rule_next_or(ctx); } @@ -41456,6 +41502,7 @@ void flecs_rule_end_or( ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); int32_t i, count = ecs_vec_count(ctx->ops); int32_t prev_or = -2; + for (i = ctx->cur->lbl_or; i < count; i ++) { if (ops[i].next == FlecsRuleOrMarker) { if (prev_or != -2) { @@ -41466,11 +41513,25 @@ void flecs_rule_end_or( } } - ops[count - 1].prev = flecs_itolbl(ctx->cur->lbl_or - 1); + ecs_rule_op_t *op = &ops[count - 1]; + op->prev = flecs_itolbl(ctx->cur->lbl_or - 1); + + if (op->kind == EcsRuleNot) { + /* If last op is a Not operation it means that this was a dependent term + * which should be ignored if one or more of its variables weren't set + * yet. In addition to setting the prev label of the Not operation, also + * set the prev label of the actual operation to the start of the OR + * chain. */ + op[-1].prev = flecs_itolbl(ctx->cur->lbl_or - 1); + } /* Set prev of next instruction to before the start of the OR chain */ ctx->cur->lbl_prev = flecs_itolbl(ctx->cur->lbl_or - 1); ctx->cur->lbl_or = -1; + + flecs_rule_insert_reset_after_or(ctx); + + ctx->ctrlflow->in_or = false; } static @@ -41503,6 +41564,10 @@ void flecs_rule_end_union( ops[next].prev = flecs_itolbl(i); ops[i].next = next; + + flecs_rule_insert_reset_after_or(ctx); + + ctx->ctrlflow->in_or = false; } static @@ -41821,11 +41886,17 @@ int flecs_rule_compile_term( bool first_is_var = term->first.flags & EcsIsVariable; bool second_is_var = term->second.flags & EcsIsVariable; bool src_is_var = term->src.flags & EcsIsVariable; - bool cond_write = term->oper == EcsOptional; bool builtin_pred = flecs_rule_is_builtin_pred(term); bool is_not = (term->oper == EcsNot) && !builtin_pred; + bool is_or = (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); + bool cond_write = term->oper == EcsOptional || is_or; ecs_rule_op_t op = {0}; + if (is_or && (first_term || term[-1].oper != EcsOr)) { + ctx->ctrlflow->cond_written_or = ctx->cond_written; + ctx->ctrlflow->in_or = true; + } + if (!term->src.id && term->src.flags & EcsIsEntity) { /* If the term has a 0 source, check if it's a scope open/close */ if (term->first.id == EcsScopeOpen) { @@ -42016,6 +42087,9 @@ int flecs_rule_compile_term( } } + /* Track label before inserting operations for current */ + ecs_rule_lbl_t lbl_start = flecs_itolbl(ecs_vec_count(ctx->ops)); + /* Check if this term has variables that have been conditionally written, * like variables written by an optional term. */ if (ctx->cond_written) { @@ -42076,7 +42150,7 @@ int flecs_rule_compile_term( } else { if (first_term || term[-1].oper != EcsOr) { if (ctx->cur->lbl_union == -1) { - flecs_rule_begin_or(ctx); + flecs_rule_begin_or(ctx, lbl_start); } } else if (term->oper == EcsOr) { flecs_rule_next_or(ctx); @@ -42110,6 +42184,8 @@ int flecs_rule_compile( ctx.cur->lbl_prev = -1; ctx.cur->lbl_not = -1; ctx.cur->lbl_none = -1; + ctx.cur->lbl_or = -1; + ctx.cur->lbl_union = -1; ecs_vec_clear(ctx.ops); /* Find all variables defined in query */ @@ -42289,6 +42365,7 @@ const char* flecs_rule_op_str( case EcsRuleIdsLeft: return "idsl "; case EcsRuleEach: return "each "; case EcsRuleStore: return "store "; + case EcsRuleReset: return "reset "; case EcsRuleUnion: return "union "; case EcsRuleEnd: return "end "; case EcsRuleNot: return "not "; @@ -43849,6 +43926,20 @@ bool flecs_rule_store( } } +static +bool flecs_rule_reset( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + if (!redo) { + return true; + } else { + flecs_rule_var_reset(op->src.var, ctx); + return false; + } +} + static bool flecs_rule_union( const ecs_rule_op_t *op, @@ -44472,6 +44563,7 @@ bool flecs_rule_run( case EcsRuleIdsLeft: return flecs_rule_idsleft(op, redo, ctx); case EcsRuleEach: return flecs_rule_each(op, redo, ctx); case EcsRuleStore: return flecs_rule_store(op, redo, ctx); + case EcsRuleReset: return flecs_rule_reset(op, redo, ctx); 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); diff --git a/src/addons/rules/api.c b/src/addons/rules/api.c index bec6a8c97..5e4f0f834 100644 --- a/src/addons/rules/api.c +++ b/src/addons/rules/api.c @@ -32,6 +32,7 @@ const char* flecs_rule_op_str( case EcsRuleIdsLeft: return "idsl "; case EcsRuleEach: return "each "; case EcsRuleStore: return "store "; + case EcsRuleReset: return "reset "; case EcsRuleUnion: return "union "; case EcsRuleEnd: return "end "; case EcsRuleNot: return "not "; diff --git a/src/addons/rules/compile.c b/src/addons/rules/compile.c index 5d0368c64..937dc32e4 100644 --- a/src/addons/rules/compile.c +++ b/src/addons/rules/compile.c @@ -86,9 +86,6 @@ void flecs_rule_write_ctx( if (cond_write) { flecs_rule_write(var_id, &ctx->cond_written); } - if (ctx->scope != 0) { - - } } } @@ -743,6 +740,14 @@ void flecs_rule_begin_cond_eval( cond_mask |= (1ull << src_var); } + /* Variables set in an OR chain are marked as conditional writes. However, + * writes from previous terms in the current OR chain shouldn't be treated + * as variables that are conditionally set, so instead use the write mask + * from before the chain started. */ + if (ctx->ctrlflow->in_or) { + cond_write_state = ctx->ctrlflow->cond_written_or; + } + /* If this term uses conditionally set variables, insert instruction that * jumps over the term if the variables weren't set yet. */ if (cond_mask & cond_write_state) { @@ -787,20 +792,58 @@ void flecs_rule_end_cond_eval( ops[count - 2].next = flecs_itolbl(count); } +static +void flecs_rule_insert_reset_after_or( + ecs_rule_compile_ctx_t *ctx) +{ + /* Scan which variables were conditionally written in the OR chain and + * reset instructions after the OR chain. If a variable is set in part one + * of a chain but not part two, there would be nothing writing to the + * variable in part two, leaving it to the previous value. To address this + * a reset is inserted that resets the variable value on redo. */ + int32_t i; + for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { + ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); + ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); + + if (!prev && cur) { + ecs_rule_op_t reset_op = {0}; + reset_op.kind = EcsRuleReset; + reset_op.flags |= (EcsRuleIsVar << EcsRuleSrc); + reset_op.src.var = flecs_itovar(i); + flecs_rule_op_insert(&reset_op, ctx); + } + } +} + static void flecs_rule_next_or( ecs_rule_compile_ctx_t *ctx) { ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); int32_t count = ecs_vec_count(ctx->ops); - ops[count - 1].next = FlecsRuleOrMarker; + ecs_rule_op_t *op = &ops[count - 1]; + + if (op->kind == EcsRuleNot) { + /* If the engine reaches a Not operation, it means that the term in the + * chain was negated. This can happen when part(s) of the term have + * conditionally set variables: if a variable was not set this will + * cause the term to automatically evaluate to false. + * In this case, the Not operation should proceed to the next term in + * the chain, whereas the preceding operation should upon success skip + * to the end of the chain, which is what the marker is replaced with. */ + ops[count - 2].next = FlecsRuleOrMarker; + } else { + ops[count - 1].next = FlecsRuleOrMarker; + } } static void flecs_rule_begin_or( - ecs_rule_compile_ctx_t *ctx) + ecs_rule_compile_ctx_t *ctx, + ecs_rule_lbl_t lbl_start) { - ctx->cur->lbl_or = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); + ctx->cur->lbl_or = flecs_itolbl(lbl_start); flecs_rule_next_or(ctx); } @@ -813,6 +856,7 @@ void flecs_rule_end_or( ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); int32_t i, count = ecs_vec_count(ctx->ops); int32_t prev_or = -2; + for (i = ctx->cur->lbl_or; i < count; i ++) { if (ops[i].next == FlecsRuleOrMarker) { if (prev_or != -2) { @@ -823,11 +867,25 @@ void flecs_rule_end_or( } } - ops[count - 1].prev = flecs_itolbl(ctx->cur->lbl_or - 1); + ecs_rule_op_t *op = &ops[count - 1]; + op->prev = flecs_itolbl(ctx->cur->lbl_or - 1); + + if (op->kind == EcsRuleNot) { + /* If last op is a Not operation it means that this was a dependent term + * which should be ignored if one or more of its variables weren't set + * yet. In addition to setting the prev label of the Not operation, also + * set the prev label of the actual operation to the start of the OR + * chain. */ + op[-1].prev = flecs_itolbl(ctx->cur->lbl_or - 1); + } /* Set prev of next instruction to before the start of the OR chain */ ctx->cur->lbl_prev = flecs_itolbl(ctx->cur->lbl_or - 1); ctx->cur->lbl_or = -1; + + flecs_rule_insert_reset_after_or(ctx); + + ctx->ctrlflow->in_or = false; } static @@ -860,6 +918,10 @@ void flecs_rule_end_union( ops[next].prev = flecs_itolbl(i); ops[i].next = next; + + flecs_rule_insert_reset_after_or(ctx); + + ctx->ctrlflow->in_or = false; } static @@ -1178,11 +1240,17 @@ int flecs_rule_compile_term( bool first_is_var = term->first.flags & EcsIsVariable; bool second_is_var = term->second.flags & EcsIsVariable; bool src_is_var = term->src.flags & EcsIsVariable; - bool cond_write = term->oper == EcsOptional; bool builtin_pred = flecs_rule_is_builtin_pred(term); bool is_not = (term->oper == EcsNot) && !builtin_pred; + bool is_or = (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); + bool cond_write = term->oper == EcsOptional || is_or; ecs_rule_op_t op = {0}; + if (is_or && (first_term || term[-1].oper != EcsOr)) { + ctx->ctrlflow->cond_written_or = ctx->cond_written; + ctx->ctrlflow->in_or = true; + } + if (!term->src.id && term->src.flags & EcsIsEntity) { /* If the term has a 0 source, check if it's a scope open/close */ if (term->first.id == EcsScopeOpen) { @@ -1373,6 +1441,9 @@ int flecs_rule_compile_term( } } + /* Track label before inserting operations for current */ + ecs_rule_lbl_t lbl_start = flecs_itolbl(ecs_vec_count(ctx->ops)); + /* Check if this term has variables that have been conditionally written, * like variables written by an optional term. */ if (ctx->cond_written) { @@ -1433,7 +1504,7 @@ int flecs_rule_compile_term( } else { if (first_term || term[-1].oper != EcsOr) { if (ctx->cur->lbl_union == -1) { - flecs_rule_begin_or(ctx); + flecs_rule_begin_or(ctx, lbl_start); } } else if (term->oper == EcsOr) { flecs_rule_next_or(ctx); @@ -1467,6 +1538,8 @@ int flecs_rule_compile( ctx.cur->lbl_prev = -1; ctx.cur->lbl_not = -1; ctx.cur->lbl_none = -1; + ctx.cur->lbl_or = -1; + ctx.cur->lbl_union = -1; ecs_vec_clear(ctx.ops); /* Find all variables defined in query */ diff --git a/src/addons/rules/engine.c b/src/addons/rules/engine.c index 1ff1e7cc3..46b86dc32 100644 --- a/src/addons/rules/engine.c +++ b/src/addons/rules/engine.c @@ -1040,6 +1040,20 @@ bool flecs_rule_store( } } +static +bool flecs_rule_reset( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + if (!redo) { + return true; + } else { + flecs_rule_var_reset(op->src.var, ctx); + return false; + } +} + static bool flecs_rule_union( const ecs_rule_op_t *op, @@ -1663,6 +1677,7 @@ bool flecs_rule_run( case EcsRuleIdsLeft: return flecs_rule_idsleft(op, redo, ctx); case EcsRuleEach: return flecs_rule_each(op, redo, ctx); case EcsRuleStore: return flecs_rule_store(op, redo, ctx); + case EcsRuleReset: return flecs_rule_reset(op, redo, ctx); 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); diff --git a/src/addons/rules/rules.h b/src/addons/rules/rules.h index 6b701342f..74096648d 100644 --- a/src/addons/rules/rules.h +++ b/src/addons/rules/rules.h @@ -44,6 +44,7 @@ typedef enum { EcsRuleIdsLeft, /* Find ids in use that match (*, T) wildcard */ EcsRuleEach, /* Iterate entities in table, populate entity variable */ EcsRuleStore, /* Store table or entity in variable */ + EcsRuleReset, /* Reset value of variable to wildcard (*) */ EcsRuleUnion, /* Combine output of multiple operations */ EcsRuleEnd, /* Used to denote end of EcsRuleUnion block */ EcsRuleNot, /* Sets iterator state after term was not matched */ @@ -185,6 +186,8 @@ typedef struct { ecs_rule_lbl_t lbl_or; ecs_rule_lbl_t lbl_none; ecs_rule_lbl_t lbl_prev; /* If set, use this as default value for prev */ + ecs_write_flags_t cond_written_or; /* Cond written flags at start of or chain */ + bool in_or; /* Whether we're in an or chain */ } ecs_rule_compile_ctrlflow_t; /* Rule compiler state */ diff --git a/test/addons/project.json b/test/addons/project.json index da5be3369..b0dd80144 100644 --- a/test/addons/project.json +++ b/test/addons/project.json @@ -885,6 +885,12 @@ "3_or_written_w_tgt_var", "2_or_chains", "2_or_chains_written", + "2_or_dependent", + "2_or_dependent_reverse", + "2_or_dependent_2_vars", + "2_or_written_dependent", + "2_or_written_dependent_2_vars", + "2_or_w_dependent", "2_not_first", "2_optional_first", "root_entities_empty", diff --git a/test/addons/src/RulesOperators.c b/test/addons/src/RulesOperators.c index a5abf87d9..6f85c9f18 100644 --- a/test/addons/src/RulesOperators.c +++ b/test/addons/src/RulesOperators.c @@ -4593,6 +4593,462 @@ void RulesOperators_2_or_chains() { ecs_fini(world); } +void RulesOperators_2_or_dependent() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, RelC); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelB($this) || RelA($this, $tgt), RelC($tgt)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(false, ecs_rule_next(&it)); + } + + int tgt_var = ecs_rule_find_var(r, "tgt"); + test_assert(tgt_var != -1); + + ecs_entity_t tgt = ecs_new(world, RelC); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + ecs_entity_t e3 = ecs_new_id(world); + + ecs_add(world, e1, RelB); + ecs_add_pair(world, e2, RelA, tgt); + ecs_add(world, e3, RelB); + ecs_add(world, e3, Foo); + + { + 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(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(false, ecs_field_is_set(&it, 2)); + test_uint(e1, it.entities[0]); + test_uint(EcsWildcard, ecs_iter_get_var(&it, tgt_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelB, ecs_field_id(&it, 1)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(false, ecs_field_is_set(&it, 2)); + test_uint(e3, it.entities[0]); + test_uint(EcsWildcard, ecs_iter_get_var(&it, tgt_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(ecs_pair(RelA, tgt), ecs_field_id(&it, 1)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 2)); + test_uint(e2, it.entities[0]); + test_uint(tgt, ecs_iter_get_var(&it, tgt_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesOperators_2_or_dependent_reverse() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, RelC); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this, $tgt) || RelB($this), RelC($tgt)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(false, ecs_rule_next(&it)); + } + + int tgt_var = ecs_rule_find_var(r, "tgt"); + test_assert(tgt_var != -1); + + ecs_entity_t tgt = ecs_new(world, RelC); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + ecs_entity_t e3 = ecs_new_id(world); + + ecs_add(world, e1, RelB); + ecs_add_pair(world, e2, RelA, tgt); + ecs_add(world, e3, RelB); + ecs_add(world, e3, Foo); + + { + 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, tgt), ecs_field_id(&it, 1)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 2)); + test_uint(e2, it.entities[0]); + test_uint(tgt, ecs_iter_get_var(&it, tgt_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelB, ecs_field_id(&it, 1)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(false, ecs_field_is_set(&it, 2)); + test_uint(e1, it.entities[0]); + test_uint(EcsWildcard, ecs_iter_get_var(&it, tgt_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(RelB, ecs_field_id(&it, 1)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(false, ecs_field_is_set(&it, 2)); + test_uint(e3, it.entities[0]); + test_uint(EcsWildcard, ecs_iter_get_var(&it, tgt_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesOperators_2_or_dependent_2_vars() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, RelC); + ECS_TAG(world, RelD); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "RelA($this, $x) || RelB($this, $y), RelC($x), RelD($y)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(false, ecs_rule_next(&it)); + } + + 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_entity_t tgt_a = ecs_new(world, RelC); + ecs_entity_t tgt_b = ecs_new(world, RelD); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + ecs_entity_t e3 = ecs_new_id(world); + ecs_entity_t e4 = ecs_new_id(world); + + ecs_add_pair(world, e1, RelA, tgt_a); + ecs_add_pair(world, e2, RelB, tgt_b); + + ecs_add_pair(world, e3, RelA, tgt_b); + ecs_add_pair(world, e4, RelB, tgt_a); + + { + 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, tgt_a), ecs_field_id(&it, 1)); + test_uint(RelC, ecs_field_id(&it, 2)); + test_uint(RelD, ecs_field_id(&it, 3)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 2)); + test_bool(false, ecs_field_is_set(&it, 3)); + test_uint(e1, it.entities[0]); + test_uint(tgt_a, ecs_iter_get_var(&it, x_var)); + test_uint(EcsWildcard, ecs_iter_get_var(&it, y_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(ecs_pair(RelB, tgt_b), ecs_field_id(&it, 1)); + test_uint(RelC, ecs_field_id(&it, 2)); + test_uint(RelD, ecs_field_id(&it, 3)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(false, ecs_field_is_set(&it, 2)); + test_bool(true, ecs_field_is_set(&it, 3)); + test_uint(e2, it.entities[0]); + test_uint(EcsWildcard, ecs_iter_get_var(&it, x_var)); + test_uint(tgt_b, ecs_iter_get_var(&it, y_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesOperators_2_or_written_dependent() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Foo); + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, RelC); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "Tag, RelA($this, $tgt) || RelB($this), RelC($tgt)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(false, ecs_rule_next(&it)); + } + + int tgt_var = ecs_rule_find_var(r, "tgt"); + test_assert(tgt_var != -1); + + ecs_entity_t tgt = ecs_new(world, RelC); + + ecs_entity_t e1 = ecs_new(world, Tag); + ecs_entity_t e2 = ecs_new(world, Tag); + ecs_entity_t e3 = ecs_new(world, Tag); + + ecs_add(world, e1, RelB); + ecs_add_pair(world, e2, RelA, tgt); + ecs_add(world, e3, RelB); + ecs_add(world, e3, Foo); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(Tag, ecs_field_id(&it, 1)); + test_uint(RelB, ecs_field_id(&it, 2)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 2)); + test_bool(false, ecs_field_is_set(&it, 3)); + test_uint(e1, it.entities[0]); + test_uint(EcsWildcard, ecs_iter_get_var(&it, tgt_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(Tag, ecs_field_id(&it, 1)); + test_uint(ecs_pair(RelA, tgt), ecs_field_id(&it, 2)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 2)); + test_bool(true, ecs_field_is_set(&it, 3)); + test_uint(e2, it.entities[0]); + test_uint(tgt, ecs_iter_get_var(&it, tgt_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(Tag, ecs_field_id(&it, 1)); + test_uint(RelB, ecs_field_id(&it, 2)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 2)); + test_bool(false, ecs_field_is_set(&it, 3)); + test_uint(e3, it.entities[0]); + test_uint(EcsWildcard, ecs_iter_get_var(&it, tgt_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesOperators_2_or_written_dependent_2_vars() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Foo); + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, RelC); + ECS_TAG(world, RelD); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "Tag, RelA($this, $x) || RelB($this, $y), RelC($x), RelD($y)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(false, ecs_rule_next(&it)); + } + + 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_entity_t tgt_a = ecs_new(world, RelC); + ecs_entity_t tgt_b = ecs_new(world, RelD); + + ecs_entity_t e1 = ecs_new(world, Tag); + ecs_entity_t e2 = ecs_new(world, Tag); + ecs_entity_t e3 = ecs_new(world, Tag); + ecs_entity_t e4 = ecs_new(world, Tag); + + ecs_add_pair(world, e1, RelA, tgt_a); + ecs_add_pair(world, e2, RelB, tgt_b); + + ecs_add_pair(world, e3, RelA, tgt_b); + ecs_add_pair(world, e4, RelB, tgt_a); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(Tag, ecs_field_id(&it, 1)); + test_uint(ecs_pair(RelA, tgt_a), ecs_field_id(&it, 2)); + test_uint(RelC, ecs_field_id(&it, 3)); + test_uint(RelD, ecs_field_id(&it, 4)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 2)); + test_bool(true, ecs_field_is_set(&it, 3)); + test_bool(false, ecs_field_is_set(&it, 4)); + test_uint(e1, it.entities[0]); + test_uint(tgt_a, ecs_iter_get_var(&it, x_var)); + test_uint(EcsWildcard, ecs_iter_get_var(&it, y_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(Tag, ecs_field_id(&it, 1)); + test_uint(ecs_pair(RelB, tgt_b), ecs_field_id(&it, 2)); + test_uint(RelC, ecs_field_id(&it, 3)); + test_uint(RelD, ecs_field_id(&it, 4)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 2)); + test_bool(false, ecs_field_is_set(&it, 3)); + test_bool(true, ecs_field_is_set(&it, 4)); + test_uint(e2, it.entities[0]); + test_uint(EcsWildcard, ecs_iter_get_var(&it, x_var)); + test_uint(tgt_b, ecs_iter_get_var(&it, y_var)); + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + +void RulesOperators_2_or_w_dependent() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Foo); + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, RelC); + ECS_TAG(world, RelD); + + ecs_rule_t *r = ecs_rule(world, { + .expr = "Tag, ?RelA($this, $x), RelB($x) || RelC($x)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(false, ecs_rule_next(&it)); + } + + int x_var = ecs_rule_find_var(r, "x"); + test_assert(x_var != -1); + + ecs_entity_t tgt_a = ecs_new(world, RelB); + ecs_entity_t tgt_b = ecs_new(world, RelC); + ecs_entity_t tgt_c = ecs_new(world, RelD); + + ecs_entity_t e1 = ecs_new(world, Tag); + ecs_entity_t e2 = ecs_new(world, Tag); + ecs_entity_t e3 = ecs_new(world, Tag); + ecs_entity_t e4 = ecs_new(world, Tag); + + ecs_add_pair(world, e2, RelA, tgt_a); + ecs_add_pair(world, e3, RelA, tgt_b); + ecs_add_pair(world, e4, RelA, tgt_c); + + { + ecs_iter_t it = ecs_rule_iter(world, r); + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(Tag, ecs_field_id(&it, 1)); + test_uint(ecs_pair(RelA, EcsWildcard), ecs_field_id(&it, 2)); + // test_uint(RelC, ecs_field_id(&it, 3)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(false, ecs_field_is_set(&it, 2)); + test_bool(false, ecs_field_is_set(&it, 3)); + test_uint(e1, it.entities[0]); + test_uint(EcsWildcard, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(Tag, ecs_field_id(&it, 1)); + test_uint(ecs_pair(RelA, tgt_a), ecs_field_id(&it, 2)); + test_uint(RelB, ecs_field_id(&it, 3)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 2)); + test_bool(true, ecs_field_is_set(&it, 3)); + test_uint(e2, it.entities[0]); + test_uint(tgt_a, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_rule_next(&it)); + test_uint(1, it.count); + test_uint(Tag, ecs_field_id(&it, 1)); + test_uint(ecs_pair(RelA, tgt_b), ecs_field_id(&it, 2)); + test_uint(RelC, ecs_field_id(&it, 3)); + test_uint(0, ecs_field_src(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 2)); + test_bool(true, ecs_field_is_set(&it, 3)); + test_uint(e3, it.entities[0]); + test_uint(tgt_b, ecs_iter_get_var(&it, x_var)); + + + test_bool(false, ecs_rule_next(&it)); + } + + ecs_rule_fini(r); + + ecs_fini(world); +} + void RulesOperators_2_not_first() { ecs_world_t *world = ecs_init(); diff --git a/test/addons/src/main.c b/test/addons/src/main.c index e7c9943f4..849eb82f6 100644 --- a/test/addons/src/main.c +++ b/test/addons/src/main.c @@ -866,6 +866,12 @@ void RulesOperators_2_or_written_w_rel_tgt_same_var(void); void RulesOperators_3_or_written_w_tgt_var(void); void RulesOperators_2_or_chains(void); void RulesOperators_2_or_chains_written(void); +void RulesOperators_2_or_dependent(void); +void RulesOperators_2_or_dependent_reverse(void); +void RulesOperators_2_or_dependent_2_vars(void); +void RulesOperators_2_or_written_dependent(void); +void RulesOperators_2_or_written_dependent_2_vars(void); +void RulesOperators_2_or_w_dependent(void); void RulesOperators_2_not_first(void); void RulesOperators_2_optional_first(void); void RulesOperators_root_entities_empty(void); @@ -4915,6 +4921,30 @@ bake_test_case RulesOperators_testcases[] = { "2_or_chains_written", RulesOperators_2_or_chains_written }, + { + "2_or_dependent", + RulesOperators_2_or_dependent + }, + { + "2_or_dependent_reverse", + RulesOperators_2_or_dependent_reverse + }, + { + "2_or_dependent_2_vars", + RulesOperators_2_or_dependent_2_vars + }, + { + "2_or_written_dependent", + RulesOperators_2_or_written_dependent + }, + { + "2_or_written_dependent_2_vars", + RulesOperators_2_or_written_dependent_2_vars + }, + { + "2_or_w_dependent", + RulesOperators_2_or_w_dependent + }, { "2_not_first", RulesOperators_2_not_first @@ -7420,7 +7450,7 @@ static bake_test_suite suites[] = { "RulesOperators", NULL, NULL, - 87, + 93, RulesOperators_testcases }, {