diff --git a/flecs.c b/flecs.c index f22aa1ac57..e74066ca48 100644 --- a/flecs.c +++ b/flecs.c @@ -469,6 +469,7 @@ struct ecs_table_t { int32_t refcount; /* Increased when used as storage table */ int32_t lock; /* Prevents modifications */ + int32_t observed_count; /* Number of observed entities in table */ uint16_t record_count; /* Table record count including wildcards */ }; @@ -3237,6 +3238,8 @@ void fini_data( if (deactivate && count) { flecs_table_set_empty(world, table); } + + table->observed_count = 0; } /* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ @@ -4542,6 +4545,8 @@ void flecs_table_merge( flecs_table_set_empty(world, dst_table); } flecs_table_set_empty(world, src_table); + dst_table->observed_count += src_table->observed_count; + src_table->observed_count = 0; } check_table_sanity(src_table); @@ -4791,6 +4796,12 @@ void ecs_table_swap_rows( flecs_table_swap(world, table, row_1, row_2); } +int32_t flecs_table_observed_count( + const ecs_table_t *table) +{ + return table->observed_count; +} + static const char* mixin_kind_str[] = { [EcsMixinBase] = "base (should never be requested by application)", @@ -6181,7 +6192,15 @@ void flecs_commit( { ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); - ecs_table_t *src_table = record ? record->table : NULL; + ecs_table_t *src_table = NULL; + uint32_t row_flags = 0; + bool observed = false; + if (record) { + src_table = record->table; + row_flags = record->row & ECS_ROW_FLAGS_MASK; + observed = row_flags & EcsEntityObservedAcyclic; + } + if (src_table == dst_table) { /* If source and destination table are the same no action is needed * * However, if a component was added in the process of traversing a @@ -6198,7 +6217,8 @@ void flecs_commit( } if (src_table) { - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + dst_table->observed_count += observed; if (dst_table->type.count) { move_entity(world, entity, record, dst_table, diff, @@ -6206,12 +6226,15 @@ void flecs_commit( } else { delete_entity(world, record, diff); record->table = NULL; - } + } + + src_table->observed_count -= observed; } else { + dst_table->observed_count += observed; if (dst_table->type.count) { - record = new_entity(world, entity, record, dst_table, diff, + new_entity(world, entity, record, dst_table, diff, construct, notify_on_set); - } + } } /* If the entity is being watched, it is being monitored for changes and @@ -6219,7 +6242,7 @@ void flecs_commit( * ensures that systems that rely on components from containers or prefabs * update the matched tables when the application adds or removes a * component from, for example, a container. */ - if (record->row & ECS_ROW_FLAGS_MASK) { + if (row_flags) { update_component_monitors(world, &diff->added, &diff->removed); } @@ -6630,6 +6653,14 @@ void flecs_add_flag( ecs_record_t new_record = {.row = flag, .table = NULL}; flecs_entities_set(world, entity, &new_record); } else { + if (flag == EcsEntityObservedAcyclic) { + if (!(record->row & flag)) { + ecs_table_t *table = record->table; + if (table) { + table->observed_count ++; + } + } + } record->row |= flag; } } @@ -7438,6 +7469,10 @@ void ecs_clear( ecs_table_t *table = r->table; if (table) { + if (r->row & EcsEntityObservedAcyclic) { + table->observed_count --; + } + ecs_table_diff_t diff = { .removed = table->type, .un_set = { table->storage_ids, table->storage_count } @@ -7914,7 +7949,14 @@ void ecs_delete( ecs_record_t *r = flecs_entities_get(world, entity); if (r) { ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); + ecs_table_t *table; if (row_flags) { + if (row_flags & EcsEntityObservedAcyclic) { + table = r->table; + if (table) { + table->observed_count --; + } + } if (row_flags & EcsEntityObservedId) { flecs_on_delete(world, entity, 0); flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0); @@ -7929,7 +7971,7 @@ void ecs_delete( ecs_defer_begin(world); } - ecs_table_t *table = r->table; + table = r->table; /* If entity has components, remove them. Check if table is still alive, * as delete actions could have deleted the table already. */ @@ -37665,6 +37707,10 @@ void notify_subset( flecs_set_observers_notify(it, observable, ids, event, ecs_pair(rel, EcsWildcard)); + if (!table->observed_count) { + continue; + } + ecs_entity_t *entities = ecs_storage_first(&table->data.entities); ecs_record_t **records = ecs_storage_first(&table->data.records); @@ -37672,7 +37718,7 @@ void notify_subset( uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[e]->row); if (flags & EcsEntityObservedAcyclic) { /* Only notify for entities that are used in pairs with - * acyclic relationships */ + * acyclic relationships */ notify_subset(world, it, observable, entities[e], event, ids); } } @@ -37730,14 +37776,17 @@ void flecs_emit( } if (count && !desc->table_event) { + if (!table->observed_count) { + return; + } + ecs_record_t **recs = ecs_storage_get_t( &table->data.records, ecs_record_t*, row); - for (i = 0; i < count; i ++) { ecs_record_t *r = recs[i]; if (!r) { /* If the event is emitted after a bulk operation, it's possible - * that it hasn't been populate with entities yet. */ + * that it hasn't been populated with entities yet. */ continue; } @@ -39705,16 +39754,18 @@ bool flecs_term_iter_next( if (!table) { if (!(tr = flecs_term_iter_next_table(iter))) { if (iter->cur != iter->set_index && iter->set_index != NULL) { - iter->cur = iter->set_index; - if (iter->empty_tables) { - flecs_table_cache_all_iter( - &iter->set_index->cache, &iter->it); - } else { - flecs_table_cache_iter( - &iter->set_index->cache, &iter->it); + if (iter->observed_table_count != 0) { + iter->cur = iter->set_index; + if (iter->empty_tables) { + flecs_table_cache_all_iter( + &iter->set_index->cache, &iter->it); + } else { + flecs_table_cache_iter( + &iter->set_index->cache, &iter->it); + } + iter->index = 0; + tr = flecs_term_iter_next_table(iter); } - iter->index = 0; - tr = flecs_term_iter_next_table(iter); } if (!tr) { @@ -39723,6 +39774,9 @@ bool flecs_term_iter_next( } table = tr->hdr.table; + if (table->observed_count) { + iter->observed_table_count ++; + } if (!match_prefab && (table->flags & EcsTableIsPrefab)) { continue; diff --git a/flecs.h b/flecs.h index 8fe7a54eb3..62e28cc9c0 100644 --- a/flecs.h +++ b/flecs.h @@ -2709,6 +2709,7 @@ typedef struct ecs_term_iter_t { ecs_id_record_t *cur; ecs_table_cache_iter_t it; int32_t index; + int32_t observed_table_count; ecs_table_t *table; int32_t cur_match; @@ -2961,6 +2962,10 @@ char* ecs_asprintf( const char *fmt, ...); +FLECS_DBG_API +int32_t flecs_table_observed_count( + const ecs_table_t *table); + /** Calculate offset from address */ #ifdef __cplusplus #define ECS_OFFSET(o, offset) reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(o)) + (static_cast<uintptr_t>(offset))) diff --git a/include/flecs/private/api_support.h b/include/flecs/private/api_support.h index a09a1ca683..3f886c524b 100644 --- a/include/flecs/private/api_support.h +++ b/include/flecs/private/api_support.h @@ -63,6 +63,10 @@ char* ecs_asprintf( const char *fmt, ...); +FLECS_DBG_API +int32_t flecs_table_observed_count( + const ecs_table_t *table); + /** Calculate offset from address */ #ifdef __cplusplus #define ECS_OFFSET(o, offset) reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(o)) + (static_cast<uintptr_t>(offset))) diff --git a/include/flecs/private/api_types.h b/include/flecs/private/api_types.h index 44dacf7791..30d2053135 100644 --- a/include/flecs/private/api_types.h +++ b/include/flecs/private/api_types.h @@ -117,6 +117,7 @@ typedef struct ecs_term_iter_t { ecs_id_record_t *cur; ecs_table_cache_iter_t it; int32_t index; + int32_t observed_table_count; ecs_table_t *table; int32_t cur_match; diff --git a/src/entity.c b/src/entity.c index 9046cd237e..3238c3ba14 100644 --- a/src/entity.c +++ b/src/entity.c @@ -965,7 +965,15 @@ void flecs_commit( { ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); - ecs_table_t *src_table = record ? record->table : NULL; + ecs_table_t *src_table = NULL; + uint32_t row_flags = 0; + bool observed = false; + if (record) { + src_table = record->table; + row_flags = record->row & ECS_ROW_FLAGS_MASK; + observed = row_flags & EcsEntityObservedAcyclic; + } + if (src_table == dst_table) { /* If source and destination table are the same no action is needed * * However, if a component was added in the process of traversing a @@ -982,7 +990,8 @@ void flecs_commit( } if (src_table) { - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + dst_table->observed_count += observed; if (dst_table->type.count) { move_entity(world, entity, record, dst_table, diff, @@ -990,12 +999,15 @@ void flecs_commit( } else { delete_entity(world, record, diff); record->table = NULL; - } + } + + src_table->observed_count -= observed; } else { + dst_table->observed_count += observed; if (dst_table->type.count) { - record = new_entity(world, entity, record, dst_table, diff, + new_entity(world, entity, record, dst_table, diff, construct, notify_on_set); - } + } } /* If the entity is being watched, it is being monitored for changes and @@ -1003,7 +1015,7 @@ void flecs_commit( * ensures that systems that rely on components from containers or prefabs * update the matched tables when the application adds or removes a * component from, for example, a container. */ - if (record->row & ECS_ROW_FLAGS_MASK) { + if (row_flags) { update_component_monitors(world, &diff->added, &diff->removed); } @@ -1414,6 +1426,14 @@ void flecs_add_flag( ecs_record_t new_record = {.row = flag, .table = NULL}; flecs_entities_set(world, entity, &new_record); } else { + if (flag == EcsEntityObservedAcyclic) { + if (!(record->row & flag)) { + ecs_table_t *table = record->table; + if (table) { + table->observed_count ++; + } + } + } record->row |= flag; } } @@ -2222,6 +2242,10 @@ void ecs_clear( ecs_table_t *table = r->table; if (table) { + if (r->row & EcsEntityObservedAcyclic) { + table->observed_count --; + } + ecs_table_diff_t diff = { .removed = table->type, .un_set = { table->storage_ids, table->storage_count } @@ -2698,7 +2722,14 @@ void ecs_delete( ecs_record_t *r = flecs_entities_get(world, entity); if (r) { ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); + ecs_table_t *table; if (row_flags) { + if (row_flags & EcsEntityObservedAcyclic) { + table = r->table; + if (table) { + table->observed_count --; + } + } if (row_flags & EcsEntityObservedId) { flecs_on_delete(world, entity, 0); flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0); @@ -2713,7 +2744,7 @@ void ecs_delete( ecs_defer_begin(world); } - ecs_table_t *table = r->table; + table = r->table; /* If entity has components, remove them. Check if table is still alive, * as delete actions could have deleted the table already. */ diff --git a/src/filter.c b/src/filter.c index 495a3cb31f..7ac41c8c0c 100644 --- a/src/filter.c +++ b/src/filter.c @@ -1943,16 +1943,18 @@ bool flecs_term_iter_next( if (!table) { if (!(tr = flecs_term_iter_next_table(iter))) { if (iter->cur != iter->set_index && iter->set_index != NULL) { - iter->cur = iter->set_index; - if (iter->empty_tables) { - flecs_table_cache_all_iter( - &iter->set_index->cache, &iter->it); - } else { - flecs_table_cache_iter( - &iter->set_index->cache, &iter->it); + if (iter->observed_table_count != 0) { + iter->cur = iter->set_index; + if (iter->empty_tables) { + flecs_table_cache_all_iter( + &iter->set_index->cache, &iter->it); + } else { + flecs_table_cache_iter( + &iter->set_index->cache, &iter->it); + } + iter->index = 0; + tr = flecs_term_iter_next_table(iter); } - iter->index = 0; - tr = flecs_term_iter_next_table(iter); } if (!tr) { @@ -1961,6 +1963,9 @@ bool flecs_term_iter_next( } table = tr->hdr.table; + if (table->observed_count) { + iter->observed_table_count ++; + } if (!match_prefab && (table->flags & EcsTableIsPrefab)) { continue; diff --git a/src/observable.c b/src/observable.c index f6b3a82df1..c8d6f04cc3 100644 --- a/src/observable.c +++ b/src/observable.c @@ -69,6 +69,10 @@ void notify_subset( flecs_set_observers_notify(it, observable, ids, event, ecs_pair(rel, EcsWildcard)); + if (!table->observed_count) { + continue; + } + ecs_entity_t *entities = ecs_storage_first(&table->data.entities); ecs_record_t **records = ecs_storage_first(&table->data.records); @@ -76,7 +80,7 @@ void notify_subset( uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[e]->row); if (flags & EcsEntityObservedAcyclic) { /* Only notify for entities that are used in pairs with - * acyclic relationships */ + * acyclic relationships */ notify_subset(world, it, observable, entities[e], event, ids); } } @@ -134,14 +138,17 @@ void flecs_emit( } if (count && !desc->table_event) { + if (!table->observed_count) { + return; + } + ecs_record_t **recs = ecs_storage_get_t( &table->data.records, ecs_record_t*, row); - for (i = 0; i < count; i ++) { ecs_record_t *r = recs[i]; if (!r) { /* If the event is emitted after a bulk operation, it's possible - * that it hasn't been populate with entities yet. */ + * that it hasn't been populated with entities yet. */ continue; } diff --git a/src/private_types.h b/src/private_types.h index 75cc4ad619..21b3b1e37e 100644 --- a/src/private_types.h +++ b/src/private_types.h @@ -189,6 +189,7 @@ struct ecs_table_t { int32_t refcount; /* Increased when used as storage table */ int32_t lock; /* Prevents modifications */ + int32_t observed_count; /* Number of observed entities in table */ uint16_t record_count; /* Table record count including wildcards */ }; diff --git a/src/table.c b/src/table.c index c5f98c0eeb..3bf3a9b6b9 100644 --- a/src/table.c +++ b/src/table.c @@ -961,6 +961,8 @@ void fini_data( if (deactivate && count) { flecs_table_set_empty(world, table); } + + table->observed_count = 0; } /* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ @@ -2266,6 +2268,8 @@ void flecs_table_merge( flecs_table_set_empty(world, dst_table); } flecs_table_set_empty(world, src_table); + dst_table->observed_count += src_table->observed_count; + src_table->observed_count = 0; } check_table_sanity(src_table); @@ -2514,3 +2518,9 @@ void ecs_table_swap_rows( { flecs_table_swap(world, table, row_1, row_2); } + +int32_t flecs_table_observed_count( + const ecs_table_t *table) +{ + return table->observed_count; +} diff --git a/test/api/project.json b/test/api/project.json index a8c4963287..085406ce31 100644 --- a/test/api/project.json +++ b/test/api/project.json @@ -2236,7 +2236,13 @@ "create_65k_tables", "no_duplicate_root_table_id", "override_os_api_w_addon", - "records_resize_on_override" + "records_resize_on_override", + "table_observed_after_add", + "table_observed_after_remove", + "table_observed_after_clear", + "table_observed_after_delete", + "table_observed_after_on_remove", + "table_observed_after_entity_flag" ] }, { "id": "Error", diff --git a/test/api/src/Internals.c b/test/api/src/Internals.c index b84d8fb54d..657782166e 100644 --- a/test/api/src/Internals.c +++ b/test/api/src/Internals.c @@ -318,3 +318,177 @@ void Internals_records_resize_on_override() { ecs_fini(world); } + +void Internals_table_observed_after_add() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t p = ecs_new_id(world); + ecs_entity_t c = ecs_new_w_pair(world, EcsChildOf, p); + ecs_table_t *pt = ecs_get_table(world, p); + ecs_table_t *ct = ecs_get_table(world, c); + test_assert(pt == NULL); + test_assert(ct != NULL); + test_int(flecs_table_observed_count(ct), 0); + + ecs_add(world, p, TagA); + ecs_table_t *pt_a = ecs_get_table(world, p); + test_assert(pt_a != NULL); + test_int(flecs_table_observed_count(pt_a), 1); + + ecs_add(world, p, TagB); + ecs_table_t *pt_b = ecs_get_table(world, p); + test_assert(pt_b != NULL); + test_int(flecs_table_observed_count(pt_a), 0); + test_int(flecs_table_observed_count(pt_b), 1); + + ecs_fini(world); +} + +void Internals_table_observed_after_remove() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t p = ecs_new_id(world); + ecs_entity_t c = ecs_new_w_pair(world, EcsChildOf, p); + ecs_table_t *pt = ecs_get_table(world, p); + ecs_table_t *ct = ecs_get_table(world, c); + test_assert(pt == NULL); + test_assert(ct != NULL); + test_int(flecs_table_observed_count(ct), 0); + + ecs_add(world, p, TagA); + ecs_table_t *pt_a = ecs_get_table(world, p); + test_assert(pt_a != NULL); + test_int(flecs_table_observed_count(pt_a), 1); + + ecs_add(world, p, TagB); + ecs_table_t *pt_b = ecs_get_table(world, p); + test_assert(pt_b != NULL); + test_int(flecs_table_observed_count(pt_a), 0); + test_int(flecs_table_observed_count(pt_b), 1); + + ecs_remove(world, p, TagB); + test_int(flecs_table_observed_count(pt_a), 1); + test_int(flecs_table_observed_count(pt_b), 0); + + ecs_remove(world, p, TagA); + test_int(flecs_table_observed_count(pt_a), 0); + test_int(flecs_table_observed_count(pt_b), 0); + + ecs_fini(world); +} + +void Internals_table_observed_after_clear() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + + ecs_entity_t p = ecs_new_id(world); + ecs_entity_t c = ecs_new_w_pair(world, EcsChildOf, p); + ecs_table_t *pt = ecs_get_table(world, p); + ecs_table_t *ct = ecs_get_table(world, c); + test_assert(pt == NULL); + test_assert(ct != NULL); + test_int(flecs_table_observed_count(ct), 0); + + ecs_add(world, p, TagA); + ecs_table_t *pt_a = ecs_get_table(world, p); + test_assert(pt_a != NULL); + test_int(flecs_table_observed_count(pt_a), 1); + + ecs_clear(world, p); + test_int(flecs_table_observed_count(pt_a), 0); + + ecs_add(world, p, TagA); + test_int(flecs_table_observed_count(pt_a), 1); + + ecs_fini(world); +} + +void Internals_table_observed_after_delete() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + + ecs_entity_t p = ecs_new_id(world); + ecs_entity_t c = ecs_new_w_pair(world, EcsChildOf, p); + ecs_table_t *pt = ecs_get_table(world, p); + ecs_table_t *ct = ecs_get_table(world, c); + test_assert(pt == NULL); + test_assert(ct != NULL); + test_int(flecs_table_observed_count(ct), 0); + + ecs_add(world, p, TagA); + ecs_table_t *pt_a = ecs_get_table(world, p); + test_assert(pt_a != NULL); + test_int(flecs_table_observed_count(pt_a), 1); + + ecs_delete(world, p); + test_int(flecs_table_observed_count(pt_a), 0); + + ecs_fini(world); +} + +void Internals_table_observed_after_on_remove() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + + ecs_entity_t p = ecs_new_id(world); + ecs_entity_t c = ecs_new_w_pair(world, EcsChildOf, p); + ecs_table_t *pt = ecs_get_table(world, p); + ecs_table_t *ct = ecs_get_table(world, c); + test_assert(pt == NULL); + test_assert(ct != NULL); + test_int(flecs_table_observed_count(ct), 0); + + ecs_entity_t t = ecs_new_id(world); + ecs_add_id(world, p, t); + ecs_table_t *pt_t = ecs_get_table(world, p); + test_assert(pt_t != NULL); + test_int(flecs_table_observed_count(pt_t), 1); + + ecs_add(world, p, TagA); + ecs_table_t *pt_ta = ecs_get_table(world, p); + test_assert(pt_ta != NULL); + test_int(flecs_table_observed_count(pt_t), 0); + test_int(flecs_table_observed_count(pt_ta), 1); + + ecs_delete(world, t); + test_assert(!ecs_has_id(world, p, t)); + test_assert(ecs_has(world, p, TagA)); + + ecs_table_t *p_a = ecs_get_table(world, p); + test_assert(p_a != NULL); + test_int(flecs_table_observed_count(p_a), 1); + + ecs_fini(world); +} + +void Internals_table_observed_after_entity_flag() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t p = ecs_new_id(world); + + ecs_add(world, p, TagA); + ecs_table_t *p_a = ecs_get_table(world, p); + test_assert(p_a != NULL); + test_int(flecs_table_observed_count(p_a), 0); + + ecs_entity_t c = ecs_new_w_pair(world, EcsChildOf, p); + ecs_table_t *ct = ecs_get_table(world, c); + test_assert(ct != NULL); + test_int(flecs_table_observed_count(ct), 0); + test_int(flecs_table_observed_count(p_a), 1); + + ecs_fini(world); + +} diff --git a/test/api/src/main.c b/test/api/src/main.c index c2d82b2181..4e8e728ad0 100644 --- a/test/api/src/main.c +++ b/test/api/src/main.c @@ -2140,6 +2140,12 @@ void Internals_create_65k_tables(void); void Internals_no_duplicate_root_table_id(void); void Internals_override_os_api_w_addon(void); void Internals_records_resize_on_override(void); +void Internals_table_observed_after_add(void); +void Internals_table_observed_after_remove(void); +void Internals_table_observed_after_clear(void); +void Internals_table_observed_after_delete(void); +void Internals_table_observed_after_on_remove(void); +void Internals_table_observed_after_entity_flag(void); // Testsuite 'Error' void Error_setup(void); @@ -10394,6 +10400,30 @@ bake_test_case Internals_testcases[] = { { "records_resize_on_override", Internals_records_resize_on_override + }, + { + "table_observed_after_add", + Internals_table_observed_after_add + }, + { + "table_observed_after_remove", + Internals_table_observed_after_remove + }, + { + "table_observed_after_clear", + Internals_table_observed_after_clear + }, + { + "table_observed_after_delete", + Internals_table_observed_after_delete + }, + { + "table_observed_after_on_remove", + Internals_table_observed_after_on_remove + }, + { + "table_observed_after_entity_flag", + Internals_table_observed_after_entity_flag } }; @@ -10775,7 +10805,7 @@ static bake_test_suite suites[] = { "Internals", Internals_setup, NULL, - 11, + 17, Internals_testcases }, {