From fcd1f2dad6600dfeaa8fe97ebba0f977c8f4dedd Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 19 Jul 2023 22:02:31 -0700 Subject: [PATCH] Fix issues with query change detection and writing to shared terms --- flecs.c | 171 ++++++----- flecs.h | 6 +- include/flecs/private/api_flags.h | 3 +- src/query.c | 171 ++++++----- test/api/project.json | 10 + test/api/src/Query.c | 481 +++++++++++++++++++++++++++++- test/api/src/main.c | 52 +++- 7 files changed, 744 insertions(+), 150 deletions(-) diff --git a/flecs.c b/flecs.c index ace6099e64..2bbb269357 100644 --- a/flecs.c +++ b/flecs.c @@ -56087,44 +56087,52 @@ ecs_query_table_match_t* flecs_query_cache_add( typedef struct { ecs_table_t *table; - int32_t *dirty_state; int32_t column; -} table_dirty_state_t; +} flecs_table_column_t; static -void flecs_query_get_dirty_state( +void flecs_query_get_column_for_term( ecs_query_t *query, ecs_query_table_match_t *match, - int32_t term, - table_dirty_state_t *out) + int32_t t, + flecs_table_column_t *out) { - ecs_world_t *world = query->filter.world; - ecs_entity_t src = match->sources[term]; - ecs_table_t *table; + const ecs_filter_t *filter = &query->filter; + ecs_world_t *world = filter->world; + ecs_term_t *term = &filter->terms[t]; + int32_t field = term->field_index; + ecs_entity_t src = match->sources[t]; + ecs_table_t *table = NULL; int32_t column = -1; - if (!src) { - table = match->table; - column = match->storage_columns[term]; - } else { - const ecs_filter_t *filter = &query->filter; - table = ecs_get_table(world, src); - if (ecs_term_match_this(&filter->terms[term])) { - int32_t ref_index = -match->columns[term] - 1; - ecs_ref_t *ref = ecs_vec_get_t(&match->refs, ecs_ref_t, ref_index); - if (ref->id != 0) { - ecs_ref_update(world, ref); - column = ref->tr->column; - column = ecs_table_type_to_storage_index(table, column); + if (term->oper != EcsNot) { + if (!src) { + if (term->src.flags != EcsIsEntity) { + table = match->table; + column = match->storage_columns[field]; + if (column == -2) { + /* Shared field */ + column = -1; + } } } else { - column = match->columns[term]; + table = ecs_get_table(world, src); + if (ecs_term_match_this(term)) { + int32_t ref_index = -match->columns[field] - 1; + ecs_ref_t *ref = ecs_vec_get_t(&match->refs, ecs_ref_t, ref_index); + if (ref->id != 0) { + ecs_ref_update(world, ref); + column = ref->tr->column; + column = ecs_table_type_to_storage_index(table, column); + } + } else { + column = -(match->columns[field] + 1); + } } } out->table = table; out->column = column; - out->dirty_state = flecs_table_get_dirty_state(world, table); } /* Get match monitor. Monitors are used to keep track of whether components @@ -56144,18 +56152,18 @@ bool flecs_query_get_match_monitor( /* Mark terms that don't need to be monitored. This saves time when reading * and/or updating the monitor. */ const ecs_filter_t *f = &query->filter; - int32_t i, t = -1, term_count = f->term_count; - table_dirty_state_t cur_dirty_state; + int32_t i, field = -1, term_count = f->term_count; + flecs_table_column_t tc; for (i = 0; i < term_count; i ++) { - if (t == f->terms[i].field_index) { - if (monitor[t + 1] != -1) { + if (field == f->terms[i].field_index) { + if (monitor[field + 1] != -1) { continue; } } - t = f->terms[i].field_index; - monitor[t + 1] = -1; + field = f->terms[i].field_index; + monitor[field + 1] = -1; if (f->terms[i].inout != EcsIn && f->terms[i].inout != EcsInOut && @@ -56163,20 +56171,19 @@ bool flecs_query_get_match_monitor( continue; /* If term isn't read, don't monitor */ } - int32_t column = match->columns[t]; + int32_t column = match->columns[field]; if (column == 0) { continue; /* Don't track terms that aren't matched */ } - flecs_query_get_dirty_state(query, match, t, &cur_dirty_state); - if (cur_dirty_state.column == -1) { + flecs_query_get_column_for_term(query, match, i, &tc); + if (tc.column == -1) { continue; /* Don't track terms that aren't stored */ } - monitor[t + 1] = 0; + monitor[field + 1] = 0; } - /* If matched table needs entity filter, make sure to test fields that could * be matched by flattened parents. */ ecs_entity_filter_t *ef = match->entity_filter; @@ -56219,33 +56226,33 @@ void flecs_query_sync_match_monitor( monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ } - table_dirty_state_t cur; - int32_t i, term_count = query->filter.term_count; - for (i = 0; i < term_count; i ++) { - int32_t t = query->filter.terms[i].field_index; - if (monitor[t + 1] == -1) { - continue; - } + ecs_filter_t *filter = &query->filter; + { + flecs_table_column_t tc; + int32_t t, term_count = filter->term_count; + for (t = 0; t < term_count; t ++) { + int32_t field = filter->terms[t].field_index; + if (monitor[field + 1] == -1) { + continue; + } - flecs_query_get_dirty_state(query, match, t, &cur); - if (cur.column < 0) { - // continue; - cur.column = -(cur.column + 1); - } + flecs_query_get_column_for_term(query, match, t, &tc); - monitor[t + 1] = cur.dirty_state[cur.column + 1]; + monitor[field + 1] = flecs_table_get_dirty_state( + filter->world, tc.table)[tc.column + 1]; + } } ecs_entity_filter_t *ef = match->entity_filter; if (ef && ef->flat_tree_column != -1) { flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); - int32_t field_count = ecs_vec_count(&ef->ft_terms); - for (i = 0; i < field_count; i ++) { - flecs_flat_table_term_t *field = &fields[i]; + int32_t f, field_count = ecs_vec_count(&ef->ft_terms); + for (f = 0; f < field_count; f ++) { + flecs_flat_table_term_t *field = &fields[f]; flecs_flat_monitor_t *tgt_mon = ecs_vec_first(&field->monitor); - int32_t t, tgt_count = ecs_vec_count(&field->monitor); - for (t = 0; t < tgt_count; t ++) { - tgt_mon[t].monitor = tgt_mon[t].table_state; + int32_t tgt, tgt_count = ecs_vec_count(&field->monitor); + for (tgt = 0; tgt < tgt_count; tgt ++) { + tgt_mon[tgt].monitor = tgt_mon[tgt].table_state; } } } @@ -56284,11 +56291,12 @@ bool flecs_query_check_match_monitor_term( return false; } - table_dirty_state_t cur; - flecs_query_get_dirty_state(query, match, term - 1, &cur); + flecs_table_column_t cur; + flecs_query_get_column_for_term(query, match, term - 1, &cur); ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); - return monitor[term] != cur.dirty_state[cur.column + 1]; + return monitor[term] != flecs_table_get_dirty_state( + query->filter.world, cur.table)[cur.column + 1]; } /* Check if any term for match has changed */ @@ -57107,7 +57115,16 @@ int flecs_query_process_signature( "invalid usage of Filter for query"); if (inout != EcsIn && inout != EcsInOutNone) { - query->flags |= EcsQueryHasOutColumns; + /* Non-this terms default to EcsIn */ + if (ecs_term_match_this(term) || inout != EcsInOutDefault) { + query->flags |= EcsQueryHasOutTerms; + } + + bool match_non_this = !ecs_term_match_this(term) || + (term->src.flags & EcsUp); + if (match_non_this && inout != EcsInOutDefault) { + query->flags |= EcsQueryHasNonThisOutTerms; + } } if (src->flags & EcsCascade) { @@ -57994,31 +58011,41 @@ void flecs_query_mark_columns_dirty( ecs_query_table_match_t *qm) { ecs_table_t *table = qm->table; - if (!table) { - return; - } - - int32_t *dirty_state = table->dirty_state; - if (dirty_state) { - int32_t *storage_columns = qm->storage_columns; - ecs_filter_t *filter = &query->filter; + ecs_filter_t *filter = &query->filter; + if ((table && table->dirty_state) || (query->flags & EcsQueryHasNonThisOutTerms)) { ecs_term_t *terms = filter->terms; int32_t i, count = filter->term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; - if (term->inout == EcsIn || term->inout == EcsInOutNone) { + ecs_inout_kind_t inout = term->inout; + if (inout == EcsIn || inout == EcsInOutNone) { /* Don't mark readonly terms dirty */ continue; } - int32_t field = term->field_index; - int32_t column = storage_columns[field]; - if (column < 0) { + flecs_table_column_t tc; + flecs_query_get_column_for_term(query, qm, i, &tc); + + if (tc.column == -1) { + continue; + } + + ecs_assert(tc.table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *dirty_state = tc.table->dirty_state; + if (!dirty_state) { continue; } - dirty_state[column + 1] ++; + if (table != tc.table) { + if (inout == EcsInOutDefault) { + continue; + } + } + + ecs_assert(tc.column >= 0, ECS_INTERNAL_ERROR, NULL); + + dirty_state[tc.column + 1] ++; } } } @@ -58040,7 +58067,7 @@ bool ecs_query_next_table( if (query->flags & EcsQueryHasMonitor) { flecs_query_sync_match_monitor(query, prev); } - if (query->flags & EcsQueryHasOutColumns) { + if (query->flags & EcsQueryHasOutTerms) { if (it->count) { flecs_query_mark_columns_dirty(query, prev); } @@ -58231,7 +58258,7 @@ bool ecs_query_next_instanced( if (flags & EcsQueryHasMonitor) { flecs_query_sync_match_monitor(query, prev); } - if (flags & EcsQueryHasOutColumns) { + if (flags & EcsQueryHasOutTerms) { flecs_query_mark_columns_dirty(query, prev); } } diff --git a/flecs.h b/flecs.h index 67e5e76a00..6824782dbb 100644 --- a/flecs.h +++ b/flecs.h @@ -447,7 +447,8 @@ extern "C" { #define EcsQueryHasRefs (1u << 1u) /* Does query have references */ #define EcsQueryIsSubquery (1u << 2u) /* Is query a subquery */ #define EcsQueryIsOrphaned (1u << 3u) /* Is subquery orphaned */ -#define EcsQueryHasOutColumns (1u << 4u) /* Does query have out columns */ +#define EcsQueryHasOutTerms (1u << 4u) /* Does query have out terms */ +#define EcsQueryHasNonThisOutTerms (1u << 5u) /* Does query have non-this out terms */ #define EcsQueryHasMonitor (1u << 5u) /* Does query track changes */ #define EcsQueryTrivialIter (1u << 6u) /* Does the query require special features to iterate */ @@ -8600,6 +8601,9 @@ int ecs_value_move_ctor( #define ecs_singleton_get(world, comp)\ ecs_get(world, ecs_id(comp), comp) +#define ecs_singleton_set_ptr(world, comp, ptr)\ + ecs_set_ptr(world, ecs_id(comp), comp, ptr) + #define ecs_singleton_set(world, comp, ...)\ ecs_set(world, ecs_id(comp), comp, __VA_ARGS__) diff --git a/include/flecs/private/api_flags.h b/include/flecs/private/api_flags.h index 7a052819ab..b4ca6ec2f8 100644 --- a/include/flecs/private/api_flags.h +++ b/include/flecs/private/api_flags.h @@ -182,7 +182,8 @@ extern "C" { #define EcsQueryHasRefs (1u << 1u) /* Does query have references */ #define EcsQueryIsSubquery (1u << 2u) /* Is query a subquery */ #define EcsQueryIsOrphaned (1u << 3u) /* Is subquery orphaned */ -#define EcsQueryHasOutColumns (1u << 4u) /* Does query have out columns */ +#define EcsQueryHasOutTerms (1u << 4u) /* Does query have out terms */ +#define EcsQueryHasNonThisOutTerms (1u << 5u) /* Does query have non-this out terms */ #define EcsQueryHasMonitor (1u << 5u) /* Does query track changes */ #define EcsQueryTrivialIter (1u << 6u) /* Does the query require special features to iterate */ diff --git a/src/query.c b/src/query.c index ff0708d957..92eac056cf 100644 --- a/src/query.c +++ b/src/query.c @@ -406,44 +406,52 @@ ecs_query_table_match_t* flecs_query_cache_add( typedef struct { ecs_table_t *table; - int32_t *dirty_state; int32_t column; -} table_dirty_state_t; +} flecs_table_column_t; static -void flecs_query_get_dirty_state( +void flecs_query_get_column_for_term( ecs_query_t *query, ecs_query_table_match_t *match, - int32_t term, - table_dirty_state_t *out) + int32_t t, + flecs_table_column_t *out) { - ecs_world_t *world = query->filter.world; - ecs_entity_t src = match->sources[term]; - ecs_table_t *table; + const ecs_filter_t *filter = &query->filter; + ecs_world_t *world = filter->world; + ecs_term_t *term = &filter->terms[t]; + int32_t field = term->field_index; + ecs_entity_t src = match->sources[t]; + ecs_table_t *table = NULL; int32_t column = -1; - if (!src) { - table = match->table; - column = match->storage_columns[term]; - } else { - const ecs_filter_t *filter = &query->filter; - table = ecs_get_table(world, src); - if (ecs_term_match_this(&filter->terms[term])) { - int32_t ref_index = -match->columns[term] - 1; - ecs_ref_t *ref = ecs_vec_get_t(&match->refs, ecs_ref_t, ref_index); - if (ref->id != 0) { - ecs_ref_update(world, ref); - column = ref->tr->column; - column = ecs_table_type_to_storage_index(table, column); + if (term->oper != EcsNot) { + if (!src) { + if (term->src.flags != EcsIsEntity) { + table = match->table; + column = match->storage_columns[field]; + if (column == -2) { + /* Shared field */ + column = -1; + } } } else { - column = match->columns[term]; + table = ecs_get_table(world, src); + if (ecs_term_match_this(term)) { + int32_t ref_index = -match->columns[field] - 1; + ecs_ref_t *ref = ecs_vec_get_t(&match->refs, ecs_ref_t, ref_index); + if (ref->id != 0) { + ecs_ref_update(world, ref); + column = ref->tr->column; + column = ecs_table_type_to_storage_index(table, column); + } + } else { + column = -(match->columns[field] + 1); + } } } out->table = table; out->column = column; - out->dirty_state = flecs_table_get_dirty_state(world, table); } /* Get match monitor. Monitors are used to keep track of whether components @@ -463,18 +471,18 @@ bool flecs_query_get_match_monitor( /* Mark terms that don't need to be monitored. This saves time when reading * and/or updating the monitor. */ const ecs_filter_t *f = &query->filter; - int32_t i, t = -1, term_count = f->term_count; - table_dirty_state_t cur_dirty_state; + int32_t i, field = -1, term_count = f->term_count; + flecs_table_column_t tc; for (i = 0; i < term_count; i ++) { - if (t == f->terms[i].field_index) { - if (monitor[t + 1] != -1) { + if (field == f->terms[i].field_index) { + if (monitor[field + 1] != -1) { continue; } } - t = f->terms[i].field_index; - monitor[t + 1] = -1; + field = f->terms[i].field_index; + monitor[field + 1] = -1; if (f->terms[i].inout != EcsIn && f->terms[i].inout != EcsInOut && @@ -482,20 +490,19 @@ bool flecs_query_get_match_monitor( continue; /* If term isn't read, don't monitor */ } - int32_t column = match->columns[t]; + int32_t column = match->columns[field]; if (column == 0) { continue; /* Don't track terms that aren't matched */ } - flecs_query_get_dirty_state(query, match, t, &cur_dirty_state); - if (cur_dirty_state.column == -1) { + flecs_query_get_column_for_term(query, match, i, &tc); + if (tc.column == -1) { continue; /* Don't track terms that aren't stored */ } - monitor[t + 1] = 0; + monitor[field + 1] = 0; } - /* If matched table needs entity filter, make sure to test fields that could * be matched by flattened parents. */ ecs_entity_filter_t *ef = match->entity_filter; @@ -538,33 +545,33 @@ void flecs_query_sync_match_monitor( monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ } - table_dirty_state_t cur; - int32_t i, term_count = query->filter.term_count; - for (i = 0; i < term_count; i ++) { - int32_t t = query->filter.terms[i].field_index; - if (monitor[t + 1] == -1) { - continue; - } + ecs_filter_t *filter = &query->filter; + { + flecs_table_column_t tc; + int32_t t, term_count = filter->term_count; + for (t = 0; t < term_count; t ++) { + int32_t field = filter->terms[t].field_index; + if (monitor[field + 1] == -1) { + continue; + } - flecs_query_get_dirty_state(query, match, t, &cur); - if (cur.column < 0) { - // continue; - cur.column = -(cur.column + 1); - } + flecs_query_get_column_for_term(query, match, t, &tc); - monitor[t + 1] = cur.dirty_state[cur.column + 1]; + monitor[field + 1] = flecs_table_get_dirty_state( + filter->world, tc.table)[tc.column + 1]; + } } ecs_entity_filter_t *ef = match->entity_filter; if (ef && ef->flat_tree_column != -1) { flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); - int32_t field_count = ecs_vec_count(&ef->ft_terms); - for (i = 0; i < field_count; i ++) { - flecs_flat_table_term_t *field = &fields[i]; + int32_t f, field_count = ecs_vec_count(&ef->ft_terms); + for (f = 0; f < field_count; f ++) { + flecs_flat_table_term_t *field = &fields[f]; flecs_flat_monitor_t *tgt_mon = ecs_vec_first(&field->monitor); - int32_t t, tgt_count = ecs_vec_count(&field->monitor); - for (t = 0; t < tgt_count; t ++) { - tgt_mon[t].monitor = tgt_mon[t].table_state; + int32_t tgt, tgt_count = ecs_vec_count(&field->monitor); + for (tgt = 0; tgt < tgt_count; tgt ++) { + tgt_mon[tgt].monitor = tgt_mon[tgt].table_state; } } } @@ -603,11 +610,12 @@ bool flecs_query_check_match_monitor_term( return false; } - table_dirty_state_t cur; - flecs_query_get_dirty_state(query, match, term - 1, &cur); + flecs_table_column_t cur; + flecs_query_get_column_for_term(query, match, term - 1, &cur); ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); - return monitor[term] != cur.dirty_state[cur.column + 1]; + return monitor[term] != flecs_table_get_dirty_state( + query->filter.world, cur.table)[cur.column + 1]; } /* Check if any term for match has changed */ @@ -1426,7 +1434,16 @@ int flecs_query_process_signature( "invalid usage of Filter for query"); if (inout != EcsIn && inout != EcsInOutNone) { - query->flags |= EcsQueryHasOutColumns; + /* Non-this terms default to EcsIn */ + if (ecs_term_match_this(term) || inout != EcsInOutDefault) { + query->flags |= EcsQueryHasOutTerms; + } + + bool match_non_this = !ecs_term_match_this(term) || + (term->src.flags & EcsUp); + if (match_non_this && inout != EcsInOutDefault) { + query->flags |= EcsQueryHasNonThisOutTerms; + } } if (src->flags & EcsCascade) { @@ -2313,31 +2330,41 @@ void flecs_query_mark_columns_dirty( ecs_query_table_match_t *qm) { ecs_table_t *table = qm->table; - if (!table) { - return; - } - - int32_t *dirty_state = table->dirty_state; - if (dirty_state) { - int32_t *storage_columns = qm->storage_columns; - ecs_filter_t *filter = &query->filter; + ecs_filter_t *filter = &query->filter; + if ((table && table->dirty_state) || (query->flags & EcsQueryHasNonThisOutTerms)) { ecs_term_t *terms = filter->terms; int32_t i, count = filter->term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; - if (term->inout == EcsIn || term->inout == EcsInOutNone) { + ecs_inout_kind_t inout = term->inout; + if (inout == EcsIn || inout == EcsInOutNone) { /* Don't mark readonly terms dirty */ continue; } - int32_t field = term->field_index; - int32_t column = storage_columns[field]; - if (column < 0) { + flecs_table_column_t tc; + flecs_query_get_column_for_term(query, qm, i, &tc); + + if (tc.column == -1) { + continue; + } + + ecs_assert(tc.table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *dirty_state = tc.table->dirty_state; + if (!dirty_state) { continue; } - dirty_state[column + 1] ++; + if (table != tc.table) { + if (inout == EcsInOutDefault) { + continue; + } + } + + ecs_assert(tc.column >= 0, ECS_INTERNAL_ERROR, NULL); + + dirty_state[tc.column + 1] ++; } } } @@ -2359,7 +2386,7 @@ bool ecs_query_next_table( if (query->flags & EcsQueryHasMonitor) { flecs_query_sync_match_monitor(query, prev); } - if (query->flags & EcsQueryHasOutColumns) { + if (query->flags & EcsQueryHasOutTerms) { if (it->count) { flecs_query_mark_columns_dirty(query, prev); } @@ -2550,7 +2577,7 @@ bool ecs_query_next_instanced( if (flags & EcsQueryHasMonitor) { flecs_query_sync_match_monitor(query, prev); } - if (flags & EcsQueryHasOutColumns) { + if (flags & EcsQueryHasOutTerms) { flecs_query_mark_columns_dirty(query, prev); } } diff --git a/test/api/project.json b/test/api/project.json index 16147fa105..aeb7c15c91 100644 --- a/test/api/project.json +++ b/test/api/project.json @@ -1502,6 +1502,16 @@ "query_changed_w_singleton", "query_changed_w_only_singleton", "query_changed_w_only_singleton_after_set", + "query_changed_w_only_singleton_after_out_term", + "query_changed_w_only_singleton_after_singleton_out_term", + "query_changed_w_only_parent", + "query_changed_w_only_parent_after_set", + "query_changed_w_only_parent_after_out_term", + "query_changed_w_only_parent_after_parent_out_term", + "query_changed_tag", + "query_changed_no_source", + "query_changed_no_source_component", + "query_changed_w_not_out", "subquery_match_existing", "subquery_match_new", "subquery_inactive", diff --git a/test/api/src/Query.c b/test/api/src/Query.c index cf1f657265..e0f5457467 100644 --- a/test/api/src/Query.c +++ b/test/api/src/Query.c @@ -3045,7 +3045,7 @@ void Query_query_changed_or() { } }); test_assert(q != NULL); - + test_bool(true, ecs_query_changed(q, NULL)); ecs_iter_t it = ecs_query_iter(world, q); @@ -3075,6 +3075,112 @@ void Query_query_changed_or() { ecs_fini(world); } +void Query_query_changed_tag() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_add(world, e1, TagA); + + ecs_query_t *q = ecs_query(world, { + .filter.terms = { + { TagA } + } + }); + test_assert(q != NULL); + + test_bool(true, ecs_query_changed(q, NULL)); + + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(false, ecs_query_next(&it)); + test_bool(false, ecs_query_changed(q, NULL)); + + ecs_fini(world); +} + +void Query_query_changed_no_source() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_add(world, e1, TagA); + + ecs_query_t *q = ecs_query(world, { + .filter.terms = { + { TagA }, + { TagB, .src.flags = EcsIsEntity, .inout = EcsOut } + } + }); + test_assert(q != NULL); + + test_bool(true, ecs_query_changed(q, NULL)); + + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(false, ecs_query_next(&it)); + test_bool(false, ecs_query_changed(q, NULL)); + + ecs_fini(world); +} + +void Query_query_changed_no_source_component() { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_add(world, e1, Position); + + ecs_query_t *q = ecs_query(world, { + .filter.terms = { + { ecs_id(Position), .inout = EcsIn }, + { ecs_id(Velocity), .src.flags = EcsIsEntity, .inout = EcsOut } + } + }); + test_assert(q != NULL); + + test_bool(true, ecs_query_changed(q, NULL)); + + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(false, ecs_query_next(&it)); + test_bool(false, ecs_query_changed(q, NULL)); + + ecs_fini(world); +} + +void Query_query_changed_w_not_out() { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_add(world, e1, Position); + + ecs_query_t *q = ecs_query(world, { + .filter.terms = { + { ecs_id(Position), .inout = EcsIn }, + { ecs_id(Velocity), .inout = EcsOut, .oper = EcsNot } + } + }); + test_assert(q != NULL); + + test_bool(true, ecs_query_changed(q, NULL)); + + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(false, ecs_query_next(&it)); + test_bool(false, ecs_query_changed(q, NULL)); + + ecs_fini(world); +} + void Query_query_changed_w_singleton() { ecs_world_t *world = ecs_mini(); @@ -3157,8 +3263,7 @@ void Query_query_changed_w_only_singleton_after_set() { ecs_query_t *q = ecs_query(world, { .filter.terms = {{ - .id = ecs_id(Position), - .src.id = ecs_id(Position) + .id = ecs_id(Position), .src.id = ecs_id(Position) }} }); @@ -3200,6 +3305,376 @@ void Query_query_changed_w_only_singleton_after_set() { ecs_fini(world); } +void Query_query_changed_w_only_singleton_after_out_term() { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + + ecs_singleton_set(world, Position, {1, 2}); + + ecs_query_t *q = ecs_query(world, { + .filter.terms = {{ + .id = ecs_id(Position), .src.id = ecs_id(Position) + }} + }); + + ecs_query_t *q_write = ecs_query(world, { + .filter.terms = { + { ecs_id(Position), .inout = EcsOut } + } + }); + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(true, ecs_query_changed(NULL, &it)); + test_int(0, it.count); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(false, ecs_query_changed(NULL, &it)); + test_int(0, it.count); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q_write); + test_bool(true, ecs_query_next(&it)); + test_int(1, it.count); + Position *p = ecs_field(&it, Position, 1); + p->x = 3; + p->y = 4; + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(true, ecs_query_changed(NULL, &it)); + test_int(0, it.count); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 3); + test_int(p->y, 4); + test_bool(false, ecs_query_next(&it)); + } + + ecs_fini(world); +} + +void Query_query_changed_w_only_singleton_after_singleton_out_term() { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + + ecs_singleton_set(world, Position, {1, 2}); + + ecs_query_t *q = ecs_query(world, { + .filter.terms = {{ + .id = ecs_id(Position), .src.id = ecs_id(Position) + }} + }); + + ecs_query_t *q_write = ecs_query(world, { + .filter.terms = { + { ecs_id(Position), .src.id = ecs_id(Position), .inout = EcsOut } + } + }); + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(true, ecs_query_changed(NULL, &it)); + test_int(0, it.count); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(false, ecs_query_changed(NULL, &it)); + test_int(0, it.count); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q_write); + test_bool(true, ecs_query_next(&it)); + test_int(0, it.count); + Position *p = ecs_field(&it, Position, 1); + p->x = 3; + p->y = 4; + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(true, ecs_query_changed(NULL, &it)); + test_int(0, it.count); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 3); + test_int(p->y, 4); + test_bool(false, ecs_query_next(&it)); + } + + ecs_fini(world); +} + +void Query_query_changed_w_only_parent() { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent = ecs_set(world, 0, Position, {1, 2}); + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + + ecs_query_t *q = ecs_query(world, { + .filter.terms = {{ + .id = ecs_id(Position), + .src.flags = EcsParent + }} + }); + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(true, ecs_query_changed(NULL, &it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(false, ecs_query_changed(NULL, &it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + ecs_fini(world); +} + +void Query_query_changed_w_only_parent_after_set() { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent = ecs_set(world, 0, Position, {1, 2}); + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + + ecs_query_t *q = ecs_query(world, { + .filter.terms = {{ + .id = ecs_id(Position), + .src.flags = EcsParent + }} + }); + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(true, ecs_query_changed(NULL, &it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(false, ecs_query_changed(NULL, &it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + ecs_set(world, parent, Position, {3, 4}); + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(true, ecs_query_changed(NULL, &it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 3); + test_int(p->y, 4); + test_bool(false, ecs_query_next(&it)); + } + + ecs_fini(world); +} + +void Query_query_changed_w_only_parent_after_out_term() { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent = ecs_set(world, 0, Position, {1, 2}); + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + + ecs_query_t *q = ecs_query(world, { + .filter.terms = {{ + .id = ecs_id(Position), + .src.flags = EcsParent + }} + }); + + ecs_query_t *q_write = ecs_query(world, { + .filter.terms = { + { ecs_id(Position), .inout = EcsOut } + } + }); + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(true, ecs_query_changed(NULL, &it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(false, ecs_query_changed(NULL, &it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q_write); + test_bool(true, ecs_query_next(&it)); + test_int(1, it.count); + test_uint(parent, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + p->x = 3; + p->y = 4; + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(true, ecs_query_changed(NULL, &it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 3); + test_int(p->y, 4); + test_bool(false, ecs_query_next(&it)); + } + + ecs_fini(world); +} + +void Query_query_changed_w_only_parent_after_parent_out_term() { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent = ecs_set(world, 0, Position, {1, 2}); + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + + ecs_query_t *q = ecs_query(world, { + .filter.terms = {{ + .id = ecs_id(Position), + .src.flags = EcsParent + }} + }); + + ecs_query_t *q_write = ecs_query(world, { + .filter.terms = { + { ecs_id(Position), .src.flags = EcsParent, .inout = EcsOut } + } + }); + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(true, ecs_query_changed(NULL, &it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(false, ecs_query_changed(NULL, &it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 1); + test_int(p->y, 2); + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q_write); + test_bool(true, ecs_query_next(&it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + p->x = 3; + p->y = 4; + test_bool(false, ecs_query_next(&it)); + } + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_bool(true, ecs_query_changed(NULL, &it)); + test_int(1, it.count); + test_uint(child, it.entities[0]); + Position *p = ecs_field(&it, Position, 1); + test_int(p->x, 3); + test_int(p->y, 4); + test_bool(false, ecs_query_next(&it)); + } + + ecs_fini(world); +} + void Query_subquery_match_existing() { ecs_world_t *world = ecs_mini(); diff --git a/test/api/src/main.c b/test/api/src/main.c index 605fcb0cb5..b769bd8ab1 100644 --- a/test/api/src/main.c +++ b/test/api/src/main.c @@ -1439,6 +1439,16 @@ void Query_query_changed_or(void); void Query_query_changed_w_singleton(void); void Query_query_changed_w_only_singleton(void); void Query_query_changed_w_only_singleton_after_set(void); +void Query_query_changed_w_only_singleton_after_out_term(void); +void Query_query_changed_w_only_singleton_after_singleton_out_term(void); +void Query_query_changed_w_only_parent(void); +void Query_query_changed_w_only_parent_after_set(void); +void Query_query_changed_w_only_parent_after_out_term(void); +void Query_query_changed_w_only_parent_after_parent_out_term(void); +void Query_query_changed_tag(void); +void Query_query_changed_no_source(void); +void Query_query_changed_no_source_component(void); +void Query_query_changed_w_not_out(void); void Query_subquery_match_existing(void); void Query_subquery_match_new(void); void Query_subquery_inactive(void); @@ -8108,6 +8118,46 @@ bake_test_case Query_testcases[] = { "query_changed_w_only_singleton_after_set", Query_query_changed_w_only_singleton_after_set }, + { + "query_changed_w_only_singleton_after_out_term", + Query_query_changed_w_only_singleton_after_out_term + }, + { + "query_changed_w_only_singleton_after_singleton_out_term", + Query_query_changed_w_only_singleton_after_singleton_out_term + }, + { + "query_changed_w_only_parent", + Query_query_changed_w_only_parent + }, + { + "query_changed_w_only_parent_after_set", + Query_query_changed_w_only_parent_after_set + }, + { + "query_changed_w_only_parent_after_out_term", + Query_query_changed_w_only_parent_after_out_term + }, + { + "query_changed_w_only_parent_after_parent_out_term", + Query_query_changed_w_only_parent_after_parent_out_term + }, + { + "query_changed_tag", + Query_query_changed_tag + }, + { + "query_changed_no_source", + Query_query_changed_no_source + }, + { + "query_changed_no_source_component", + Query_query_changed_no_source_component + }, + { + "query_changed_w_not_out", + Query_query_changed_w_not_out + }, { "subquery_match_existing", Query_subquery_match_existing @@ -12666,7 +12716,7 @@ static bake_test_suite suites[] = { "Query", NULL, NULL, - 209, + 219, Query_testcases }, {