diff --git a/distr/flecs.c b/distr/flecs.c index a2d9ec9c9b..f1c8b99e68 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -2610,7 +2610,8 @@ void flecs_invoke_replace_hook( ecs_id_t id, const void *old_ptr, const void *new_ptr, - const ecs_type_info_t *ti); + const ecs_type_info_t *ti, + ecs_table_t *prev_table); /* Add action for sparse components. */ bool flecs_sparse_on_add( @@ -5995,7 +5996,7 @@ void* flecs_defer_set( /* Call on_replace hook before copying the new value. */ if (ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, ptr.ptr, value, ti); + world, r->table, entity, id, ptr.ptr, value, ti, r->table); } flecs_type_info_copy(ptr.ptr, value, 1, ti); @@ -6071,7 +6072,7 @@ void* flecs_defer_cpp_set( /* Call on_replace hook before copying the new value. */ if (ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, ptr.ptr, value, ti); + world, r->table, entity, id, ptr.ptr, value, ti, r->table); } } @@ -6105,7 +6106,7 @@ void* flecs_defer_cpp_assign( ecs_iter_action_t on_replace = ptr.ti->hooks.on_replace; if (on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, ptr.ptr, value, ptr.ti); + world, r->table, entity, id, ptr.ptr, value, ptr.ti, r->table); } ecs_cmd_t *cmd = flecs_cmd_new(stage); @@ -6474,9 +6475,8 @@ void flecs_cmd_batch_for_entity( void *ptr = cmd->is._1.value; const ecs_type_info_t *ti = dst.ti; if (ti->hooks.on_replace) { - ecs_table_t *prev_table = r->table; - flecs_invoke_replace_hook(world, prev_table, entity, - cmd->id, dst.ptr, ptr, ti); + flecs_invoke_replace_hook(world, r->table, entity, + cmd->id, dst.ptr, ptr, ti, start_table); if (!r->table) { /* Entity was deleted */ goto done; @@ -6996,7 +6996,8 @@ void flecs_invoke_replace_hook( ecs_id_t id, const void *old_ptr, const void *new_ptr, - const ecs_type_info_t *ti) + const ecs_type_info_t *ti, + ecs_table_t *prev_table) { int32_t defer = world->stages[0]->defer; if (defer < 0) { @@ -7029,6 +7030,8 @@ void flecs_invoke_replace_hook( it.count = 1; it.offset = 0; /* Don't set row because we don't want to offset ptrs */ it.flags = EcsIterIsValid; + it.other_table = prev_table; + it.set_fields = (1 << 2) - 1; ti->hooks.on_replace(&it); @@ -8697,15 +8700,11 @@ void flecs_copy_id( const void *src_ptr, const ecs_type_info_t *ti) { + (void)entity; ecs_assert(dst_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_ptr != NULL, ECS_INTERNAL_ERROR, NULL); (void)size; - if (ti->hooks.on_replace) { - flecs_invoke_replace_hook( - world, r->table, entity, component, dst_ptr, src_ptr, ti); - } - flecs_type_info_copy(dst_ptr, src_ptr, 1, ti); flecs_table_mark_dirty(world, r->table, component); @@ -8845,9 +8844,9 @@ int flecs_traverse_add( } /* Find existing table */ - ecs_table_t *src_table = NULL, *table = NULL; + ecs_table_t *src_table = NULL, *table = NULL, *init_table = NULL; ecs_record_t *r = flecs_entities_get(world, result); - table = r->table; + init_table = table = r->table; /* Add components from the 'add' array */ if (desc->add) { @@ -8925,7 +8924,11 @@ int flecs_traverse_add( flecs_errstr_2(ecs_get_path(world, result))); const ecs_type_info_t *ti = cr->type_info; - flecs_copy_id(world, result, r, v->type, + if (ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, result, v->type, ptr.ptr, v->ptr, ti, init_table); + } + flecs_copy_id(world, result, r, v->type, flecs_itosize(ti->size), ptr.ptr, v->ptr, ti); } @@ -10179,6 +10182,7 @@ void flecs_set_id_move( } ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *prev_table = r->table; flecs_component_ptr_t dst = flecs_ensure( world, entity, component, r, flecs_uto(int32_t, size)); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); @@ -10188,7 +10192,7 @@ void flecs_set_id_move( if (ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, component, dst.ptr, ptr, ti); + world, r->table, entity, component, dst.ptr, ptr, ti, prev_table); } if (cmd_kind != EcsCmdEmplace) { @@ -10250,7 +10254,8 @@ void ecs_set_id( } ecs_record_t *r = flecs_entities_get(world, entity); - flecs_component_ptr_t dst = flecs_ensure(world, entity, component, r, + ecs_table_t *prev_table = r->table; + flecs_component_ptr_t dst = flecs_ensure(world, entity, component, r, flecs_uto(int32_t, size)); if (component < FLECS_HI_COMPONENT_ID) { @@ -10260,6 +10265,11 @@ void ecs_set_id( } } + if (dst.ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, entity, component, dst.ptr, ptr, dst.ti, prev_table); + } + flecs_copy_id(world, entity, r, component, size, dst.ptr, ptr, dst.ti); done: @@ -26285,9 +26295,10 @@ ecs_cpp_get_mut_t ecs_cpp_set( } ecs_record_t *r = flecs_entities_get(world, entity); - flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r, + ecs_table_t *prev_table = r->table; + flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r, flecs_uto(int32_t, size)); - + result.ptr = dst.ptr; result.world = world; result.stage = stage; @@ -26304,10 +26315,10 @@ ecs_cpp_get_mut_t ecs_cpp_set( if (dst.ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, dst.ptr, new_ptr, dst.ti); + world, r->table, entity, id, dst.ptr, new_ptr, dst.ti, prev_table); } -done: +done: return result; error: return (ecs_cpp_get_mut_t){0}; @@ -26336,12 +26347,13 @@ ecs_cpp_get_mut_t ecs_cpp_assign( } ecs_record_t *r = flecs_entities_get(world, entity); - flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r, + ecs_table_t *prev_table = r->table; + flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r, flecs_uto(int32_t, size)); - ecs_assert(dst.ptr != NULL, ECS_INVALID_OPERATION, + ecs_assert(dst.ptr != NULL, ECS_INVALID_OPERATION, "entity does not have component, use set() instead"); - + result.ptr = dst.ptr; result.world = world; result.stage = stage; @@ -26358,7 +26370,7 @@ ecs_cpp_get_mut_t ecs_cpp_assign( if (dst.ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, dst.ptr, new_ptr, dst.ti); + world, r->table, entity, id, dst.ptr, new_ptr, dst.ti, prev_table); } done: diff --git a/distr/flecs.h b/distr/flecs.h index bac7d32f12..2ec6f3b59c 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -4659,7 +4659,10 @@ struct ecs_type_hooks_t { /** Callback that is invoked with the existing and new value before the * value is assigned. Invoked after on_add and before on_set. Registering * an on_replace hook prevents using operations that return a mutable - * pointer to the component, like get_mut(), ensure(), and emplace(). */ + * pointer to the component, like get_mut(), ensure(), and emplace(). + * The iterator's other_table field is set to the table of the entity + * before the operation. To find out whether the component existed before + * the operation, call ecs_table_has_id() on other_table. */ ecs_iter_action_t on_replace; /** Callback that is invoked before the on_set/OnSet hooks and observers are diff --git a/distr/flecs_no_addons.c b/distr/flecs_no_addons.c index f18672fe03..5fd7ff08f8 100644 --- a/distr/flecs_no_addons.c +++ b/distr/flecs_no_addons.c @@ -2610,7 +2610,8 @@ void flecs_invoke_replace_hook( ecs_id_t id, const void *old_ptr, const void *new_ptr, - const ecs_type_info_t *ti); + const ecs_type_info_t *ti, + ecs_table_t *prev_table); /* Add action for sparse components. */ bool flecs_sparse_on_add( @@ -5930,7 +5931,7 @@ void* flecs_defer_set( /* Call on_replace hook before copying the new value. */ if (ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, ptr.ptr, value, ti); + world, r->table, entity, id, ptr.ptr, value, ti, r->table); } flecs_type_info_copy(ptr.ptr, value, 1, ti); @@ -6006,7 +6007,7 @@ void* flecs_defer_cpp_set( /* Call on_replace hook before copying the new value. */ if (ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, ptr.ptr, value, ti); + world, r->table, entity, id, ptr.ptr, value, ti, r->table); } } @@ -6040,7 +6041,7 @@ void* flecs_defer_cpp_assign( ecs_iter_action_t on_replace = ptr.ti->hooks.on_replace; if (on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, ptr.ptr, value, ptr.ti); + world, r->table, entity, id, ptr.ptr, value, ptr.ti, r->table); } ecs_cmd_t *cmd = flecs_cmd_new(stage); @@ -6409,9 +6410,8 @@ void flecs_cmd_batch_for_entity( void *ptr = cmd->is._1.value; const ecs_type_info_t *ti = dst.ti; if (ti->hooks.on_replace) { - ecs_table_t *prev_table = r->table; - flecs_invoke_replace_hook(world, prev_table, entity, - cmd->id, dst.ptr, ptr, ti); + flecs_invoke_replace_hook(world, r->table, entity, + cmd->id, dst.ptr, ptr, ti, start_table); if (!r->table) { /* Entity was deleted */ goto done; @@ -6931,7 +6931,8 @@ void flecs_invoke_replace_hook( ecs_id_t id, const void *old_ptr, const void *new_ptr, - const ecs_type_info_t *ti) + const ecs_type_info_t *ti, + ecs_table_t *prev_table) { int32_t defer = world->stages[0]->defer; if (defer < 0) { @@ -6964,6 +6965,8 @@ void flecs_invoke_replace_hook( it.count = 1; it.offset = 0; /* Don't set row because we don't want to offset ptrs */ it.flags = EcsIterIsValid; + it.other_table = prev_table; + it.set_fields = (1 << 2) - 1; ti->hooks.on_replace(&it); @@ -8602,15 +8605,11 @@ void flecs_copy_id( const void *src_ptr, const ecs_type_info_t *ti) { + (void)entity; ecs_assert(dst_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_ptr != NULL, ECS_INTERNAL_ERROR, NULL); (void)size; - if (ti->hooks.on_replace) { - flecs_invoke_replace_hook( - world, r->table, entity, component, dst_ptr, src_ptr, ti); - } - flecs_type_info_copy(dst_ptr, src_ptr, 1, ti); flecs_table_mark_dirty(world, r->table, component); @@ -8710,9 +8709,9 @@ int flecs_traverse_add( } /* Find existing table */ - ecs_table_t *src_table = NULL, *table = NULL; + ecs_table_t *src_table = NULL, *table = NULL, *init_table = NULL; ecs_record_t *r = flecs_entities_get(world, result); - table = r->table; + init_table = table = r->table; /* Add components from the 'add' array */ if (desc->add) { @@ -8790,7 +8789,11 @@ int flecs_traverse_add( flecs_errstr_2(ecs_get_path(world, result))); const ecs_type_info_t *ti = cr->type_info; - flecs_copy_id(world, result, r, v->type, + if (ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, result, v->type, ptr.ptr, v->ptr, ti, init_table); + } + flecs_copy_id(world, result, r, v->type, flecs_itosize(ti->size), ptr.ptr, v->ptr, ti); } @@ -10044,6 +10047,7 @@ void flecs_set_id_move( } ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *prev_table = r->table; flecs_component_ptr_t dst = flecs_ensure( world, entity, component, r, flecs_uto(int32_t, size)); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); @@ -10053,7 +10057,7 @@ void flecs_set_id_move( if (ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, component, dst.ptr, ptr, ti); + world, r->table, entity, component, dst.ptr, ptr, ti, prev_table); } if (cmd_kind != EcsCmdEmplace) { @@ -10115,7 +10119,8 @@ void ecs_set_id( } ecs_record_t *r = flecs_entities_get(world, entity); - flecs_component_ptr_t dst = flecs_ensure(world, entity, component, r, + ecs_table_t *prev_table = r->table; + flecs_component_ptr_t dst = flecs_ensure(world, entity, component, r, flecs_uto(int32_t, size)); if (component < FLECS_HI_COMPONENT_ID) { @@ -10125,6 +10130,11 @@ void ecs_set_id( } } + if (dst.ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, entity, component, dst.ptr, ptr, dst.ti, prev_table); + } + flecs_copy_id(world, entity, r, component, size, dst.ptr, ptr, dst.ti); done: diff --git a/distr/flecs_no_addons.h b/distr/flecs_no_addons.h index 32f1607ba3..ab63983413 100644 --- a/distr/flecs_no_addons.h +++ b/distr/flecs_no_addons.h @@ -4632,7 +4632,10 @@ struct ecs_type_hooks_t { /** Callback that is invoked with the existing and new value before the * value is assigned. Invoked after on_add and before on_set. Registering * an on_replace hook prevents using operations that return a mutable - * pointer to the component, like get_mut(), ensure(), and emplace(). */ + * pointer to the component, like get_mut(), ensure(), and emplace(). + * The iterator's other_table field is set to the table of the entity + * before the operation. To find out whether the component existed before + * the operation, call ecs_table_has_id() on other_table. */ ecs_iter_action_t on_replace; /** Callback that is invoked before the on_set/OnSet hooks and observers are diff --git a/include/flecs.h b/include/flecs.h index 54be989281..f3b3ed6ad9 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -1013,7 +1013,10 @@ struct ecs_type_hooks_t { /** Callback that is invoked with the existing and new value before the * value is assigned. Invoked after on_add and before on_set. Registering * an on_replace hook prevents using operations that return a mutable - * pointer to the component, like get_mut(), ensure(), and emplace(). */ + * pointer to the component, like get_mut(), ensure(), and emplace(). + * The iterator's other_table field is set to the table of the entity + * before the operation. To find out whether the component existed before + * the operation, call ecs_table_has_id() on other_table. */ ecs_iter_action_t on_replace; /** Callback that is invoked before the on_set/OnSet hooks and observers are diff --git a/src/addons/flecs_cpp.c b/src/addons/flecs_cpp.c index dfa0c1f0e2..c6987c1419 100644 --- a/src/addons/flecs_cpp.c +++ b/src/addons/flecs_cpp.c @@ -580,9 +580,10 @@ ecs_cpp_get_mut_t ecs_cpp_set( } ecs_record_t *r = flecs_entities_get(world, entity); - flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r, + ecs_table_t *prev_table = r->table; + flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r, flecs_uto(int32_t, size)); - + result.ptr = dst.ptr; result.world = world; result.stage = stage; @@ -599,10 +600,10 @@ ecs_cpp_get_mut_t ecs_cpp_set( if (dst.ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, dst.ptr, new_ptr, dst.ti); + world, r->table, entity, id, dst.ptr, new_ptr, dst.ti, prev_table); } -done: +done: return result; error: return (ecs_cpp_get_mut_t){0}; @@ -631,12 +632,13 @@ ecs_cpp_get_mut_t ecs_cpp_assign( } ecs_record_t *r = flecs_entities_get(world, entity); - flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r, + ecs_table_t *prev_table = r->table; + flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r, flecs_uto(int32_t, size)); - ecs_assert(dst.ptr != NULL, ECS_INVALID_OPERATION, + ecs_assert(dst.ptr != NULL, ECS_INVALID_OPERATION, "entity does not have component, use set() instead"); - + result.ptr = dst.ptr; result.world = world; result.stage = stage; @@ -653,7 +655,7 @@ ecs_cpp_get_mut_t ecs_cpp_assign( if (dst.ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, dst.ptr, new_ptr, dst.ti); + world, r->table, entity, id, dst.ptr, new_ptr, dst.ti, prev_table); } done: diff --git a/src/commands.c b/src/commands.c index b55b0818e1..3daf07aca4 100644 --- a/src/commands.c +++ b/src/commands.c @@ -540,7 +540,7 @@ void* flecs_defer_set( /* Call on_replace hook before copying the new value. */ if (ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, ptr.ptr, value, ti); + world, r->table, entity, id, ptr.ptr, value, ti, r->table); } flecs_type_info_copy(ptr.ptr, value, 1, ti); @@ -616,7 +616,7 @@ void* flecs_defer_cpp_set( /* Call on_replace hook before copying the new value. */ if (ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, ptr.ptr, value, ti); + world, r->table, entity, id, ptr.ptr, value, ti, r->table); } } @@ -650,7 +650,7 @@ void* flecs_defer_cpp_assign( ecs_iter_action_t on_replace = ptr.ti->hooks.on_replace; if (on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, id, ptr.ptr, value, ptr.ti); + world, r->table, entity, id, ptr.ptr, value, ptr.ti, r->table); } ecs_cmd_t *cmd = flecs_cmd_new(stage); @@ -1019,9 +1019,8 @@ void flecs_cmd_batch_for_entity( void *ptr = cmd->is._1.value; const ecs_type_info_t *ti = dst.ti; if (ti->hooks.on_replace) { - ecs_table_t *prev_table = r->table; - flecs_invoke_replace_hook(world, prev_table, entity, - cmd->id, dst.ptr, ptr, ti); + flecs_invoke_replace_hook(world, r->table, entity, + cmd->id, dst.ptr, ptr, ti, start_table); if (!r->table) { /* Entity was deleted */ goto done; diff --git a/src/component_actions.c b/src/component_actions.c index 98b872b33f..83523da65b 100644 --- a/src/component_actions.c +++ b/src/component_actions.c @@ -72,7 +72,8 @@ void flecs_invoke_replace_hook( ecs_id_t id, const void *old_ptr, const void *new_ptr, - const ecs_type_info_t *ti) + const ecs_type_info_t *ti, + ecs_table_t *prev_table) { int32_t defer = world->stages[0]->defer; if (defer < 0) { @@ -105,6 +106,8 @@ void flecs_invoke_replace_hook( it.count = 1; it.offset = 0; /* Don't set row because we don't want to offset ptrs */ it.flags = EcsIterIsValid; + it.other_table = prev_table; + it.set_fields = (1 << 2) - 1; ti->hooks.on_replace(&it); diff --git a/src/component_actions.h b/src/component_actions.h index 09adec5293..6d1c28a5f2 100644 --- a/src/component_actions.h +++ b/src/component_actions.h @@ -31,7 +31,8 @@ void flecs_invoke_replace_hook( ecs_id_t id, const void *old_ptr, const void *new_ptr, - const ecs_type_info_t *ti); + const ecs_type_info_t *ti, + ecs_table_t *prev_table); /* Add action for sparse components. */ bool flecs_sparse_on_add( diff --git a/src/entity.c b/src/entity.c index d5c1da4b33..25622440a7 100644 --- a/src/entity.c +++ b/src/entity.c @@ -868,15 +868,11 @@ void flecs_copy_id( const void *src_ptr, const ecs_type_info_t *ti) { + (void)entity; ecs_assert(dst_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_ptr != NULL, ECS_INTERNAL_ERROR, NULL); (void)size; - if (ti->hooks.on_replace) { - flecs_invoke_replace_hook( - world, r->table, entity, component, dst_ptr, src_ptr, ti); - } - flecs_type_info_copy(dst_ptr, src_ptr, 1, ti); flecs_table_mark_dirty(world, r->table, component); @@ -1016,9 +1012,9 @@ int flecs_traverse_add( } /* Find existing table */ - ecs_table_t *src_table = NULL, *table = NULL; + ecs_table_t *src_table = NULL, *table = NULL, *init_table = NULL; ecs_record_t *r = flecs_entities_get(world, result); - table = r->table; + init_table = table = r->table; /* Add components from the 'add' array */ if (desc->add) { @@ -1096,7 +1092,11 @@ int flecs_traverse_add( flecs_errstr_2(ecs_get_path(world, result))); const ecs_type_info_t *ti = cr->type_info; - flecs_copy_id(world, result, r, v->type, + if (ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, result, v->type, ptr.ptr, v->ptr, ti, init_table); + } + flecs_copy_id(world, result, r, v->type, flecs_itosize(ti->size), ptr.ptr, v->ptr, ti); } @@ -2350,6 +2350,7 @@ void flecs_set_id_move( } ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *prev_table = r->table; flecs_component_ptr_t dst = flecs_ensure( world, entity, component, r, flecs_uto(int32_t, size)); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); @@ -2359,7 +2360,7 @@ void flecs_set_id_move( if (ti->hooks.on_replace) { flecs_invoke_replace_hook( - world, r->table, entity, component, dst.ptr, ptr, ti); + world, r->table, entity, component, dst.ptr, ptr, ti, prev_table); } if (cmd_kind != EcsCmdEmplace) { @@ -2421,7 +2422,8 @@ void ecs_set_id( } ecs_record_t *r = flecs_entities_get(world, entity); - flecs_component_ptr_t dst = flecs_ensure(world, entity, component, r, + ecs_table_t *prev_table = r->table; + flecs_component_ptr_t dst = flecs_ensure(world, entity, component, r, flecs_uto(int32_t, size)); if (component < FLECS_HI_COMPONENT_ID) { @@ -2431,6 +2433,11 @@ void ecs_set_id( } } + if (dst.ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, entity, component, dst.ptr, ptr, dst.ti, prev_table); + } + flecs_copy_id(world, entity, r, component, size, dst.ptr, ptr, dst.ti); done: diff --git a/test/addons/src/main.c b/test/addons/src/main.c index 75c78ebc34..aeb3e135d8 100644 --- a/test/addons/src/main.c +++ b/test/addons/src/main.c @@ -2768,7 +2768,6 @@ const char* MultiThread_worker_kind_param[] = {"thread", "task"}; bake_test_param MultiThread_params[] = { {"worker_kind", (char**)MultiThread_worker_kind_param, 2} }; - const char* MultiThreadStaging_worker_kind_param[] = {"thread", "task"}; bake_test_param MultiThreadStaging_params[] = { {"worker_kind", (char**)MultiThreadStaging_worker_kind_param, 2} diff --git a/test/collections/src/main.c b/test/collections/src/main.c index 4e3ed8a6f6..98332637c7 100644 --- a/test/collections/src/main.c +++ b/test/collections/src/main.c @@ -502,6 +502,7 @@ bake_test_case Allocator_testcases[] = { } }; + static bake_test_suite suites[] = { { "Map", diff --git a/test/core/project.json b/test/core/project.json index 05ad67b827..a6fb908272 100644 --- a/test/core/project.json +++ b/test/core/project.json @@ -1782,6 +1782,12 @@ "on_replace_w_set", "on_replace_w_set_existing", "on_replace_set_2_entities", + "on_replace_other_table_new_entity", + "on_replace_other_table_existing_entity", + "on_replace_other_table_set_existing", + "on_replace_other_table_batched_new_entity", + "on_replace_other_table_batched_existing", + "on_replace_other_table_entity_init", "copy_ctor_w_override", "copy_ctor_w_override_w_emplace", "copy_ctor_w_override_w_ensure", diff --git a/test/core/src/ComponentLifecycle.c b/test/core/src/ComponentLifecycle.c index 7beeeb8487..c6b5bc9950 100644 --- a/test/core/src/ComponentLifecycle.c +++ b/test/core/src/ComponentLifecycle.c @@ -4545,6 +4545,204 @@ void ComponentLifecycle_on_replace_set_2_entities(void) { ecs_fini(world); } +/* ---- on_replace other_table / set_fields tests ---- */ + +typedef struct replace_ctx { + ecs_table_t *other_table; + ecs_termset_t set_fields; + int invoked; +} replace_ctx; + +static +void replace_capture(ecs_iter_t *it) { + replace_ctx *ctx = it->ctx; + ctx->other_table = it->other_table; + ctx->set_fields = it->set_fields; + ctx->invoked++; +} + +void ComponentLifecycle_on_replace_other_table_new_entity(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + + replace_ctx ctx = {0}; + + ecs_set_hooks(world, Position, { + .ctor = flecs_default_ctor, + .on_replace = replace_capture, + .ctx = &ctx, + }); + + ecs_entity_t e = ecs_new(world); + ecs_table_t *prev = ecs_get_table(world, e); /* root table, no Position */ + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + + /* prev_table captured before flecs_ensure: root table, no Position. */ + test_assert(ctx.other_table == prev); + test_assert(ctx.set_fields == 3); + test_assert(!ecs_table_has_id(world, ctx.other_table, ecs_id(Position))); + + ecs_fini(world); +} + +void ComponentLifecycle_on_replace_other_table_existing_entity(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + replace_ctx ctx = {0}; + + ecs_set_hooks(world, Position, { + .ctor = flecs_default_ctor, + .on_replace = replace_capture, + .ctx = &ctx, + }); + + ecs_entity_t e = ecs_new(world); + ecs_add(world, e, Velocity); + ecs_table_t *prev = ecs_get_table(world, e); /* {Velocity}, no Position */ + + test_int(ctx.invoked, 0); + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + + /* prev_table = {Velocity} before ensure: Position not yet in prev. */ + test_assert(ctx.other_table == prev); + test_assert(ctx.set_fields == 3); + test_assert(!ecs_table_has_id(world, ctx.other_table, ecs_id(Position))); + + ecs_fini(world); +} + +void ComponentLifecycle_on_replace_other_table_set_existing(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + + replace_ctx ctx = {0}; + + ecs_set_hooks(world, Position, { + .ctor = flecs_default_ctor, + .on_replace = replace_capture, + .ctx = &ctx, + }); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + + ecs_table_t *table_with_pos = ecs_get_table(world, e); + + ctx = (replace_ctx){0}; + ecs_set(world, e, Position, {11, 21}); + test_int(ctx.invoked, 1); + + /* prev_table = {Position} (unchanged by ensure): Position in prev. */ + test_assert(ctx.other_table == table_with_pos); + test_assert(ctx.set_fields == 3); + test_assert(ecs_table_has_id(world, ctx.other_table, ecs_id(Position))); + + ecs_fini(world); +} + +void ComponentLifecycle_on_replace_other_table_batched_new_entity(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + replace_ctx ctx = {0}; + + ecs_set_hooks(world, Position, { + .ctor = flecs_default_ctor, + .on_replace = replace_capture, + .ctx = &ctx, + }); + + ecs_entity_t e = ecs_new(world); + ecs_table_t *start_table = ecs_get_table(world, e); + test_int(ctx.invoked, 0); + + ecs_defer_begin(world); + ecs_set(world, e, Position, {10, 20}); + ecs_set(world, e, Velocity, {1, 2}); + test_int(ctx.invoked, 0); + ecs_defer_end(world); + test_int(ctx.invoked, 1); + + /* Batch flush second-pass: uses start_table (root, no Position). */ + test_assert(ctx.other_table == start_table); + test_assert(ctx.set_fields == 3); + test_assert(!ecs_table_has_id(world, ctx.other_table, ecs_id(Position))); + + ecs_fini(world); +} + +void ComponentLifecycle_on_replace_other_table_batched_existing(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + replace_ctx ctx = {0}; + + ecs_set_hooks(world, Position, { + .ctor = flecs_default_ctor, + .on_replace = replace_capture, + .ctx = &ctx, + }); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + ecs_table_t *table_with_pos = ecs_get_table(world, e); + + ctx = (replace_ctx){0}; + + /* Setting existing component during defer fires hook immediately via + * EcsCmdAddModified path (not batched), using r->table at that moment. */ + ecs_defer_begin(world); + ecs_set(world, e, Position, {11, 21}); + test_int(ctx.invoked, 1); + test_assert(ctx.other_table == table_with_pos); + test_assert(ctx.set_fields == 3); + test_assert(ecs_table_has_id(world, ctx.other_table, ecs_id(Position))); + ecs_defer_end(world); + + ecs_fini(world); +} + +void ComponentLifecycle_on_replace_other_table_entity_init(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + replace_ctx ctx = {0}; + + ecs_set_hooks(world, Position, { + .ctor = flecs_default_ctor, + .on_replace = replace_capture, + .ctx = &ctx, + }); + + /* init_table = r->table before any components added (root, no Position). */ + ecs_entity_t e = ecs_entity(world, { + .set = ecs_values(ecs_value(Position, {10, 20})) + }); + test_int(ctx.invoked, 1); + + test_assert(ctx.other_table != ecs_get_table(world, e)); + test_assert(ctx.set_fields == 3); + test_assert(!ecs_table_has_id(world, ctx.other_table, ecs_id(Position))); + + ecs_fini(world); +} + void ComponentLifecycle_copy_ctor_w_override(void) { ecs_world_t *world = ecs_mini(); diff --git a/test/core/src/main.c b/test/core/src/main.c index a60d9877bc..a8d12da953 100644 --- a/test/core/src/main.c +++ b/test/core/src/main.c @@ -1721,6 +1721,12 @@ void ComponentLifecycle_on_replace_w_emplace(void); void ComponentLifecycle_on_replace_w_set(void); void ComponentLifecycle_on_replace_w_set_existing(void); void ComponentLifecycle_on_replace_set_2_entities(void); +void ComponentLifecycle_on_replace_other_table_new_entity(void); +void ComponentLifecycle_on_replace_other_table_existing_entity(void); +void ComponentLifecycle_on_replace_other_table_set_existing(void); +void ComponentLifecycle_on_replace_other_table_batched_new_entity(void); +void ComponentLifecycle_on_replace_other_table_batched_existing(void); +void ComponentLifecycle_on_replace_other_table_entity_init(void); void ComponentLifecycle_copy_ctor_w_override(void); void ComponentLifecycle_copy_ctor_w_override_w_emplace(void); void ComponentLifecycle_copy_ctor_w_override_w_ensure(void); @@ -10089,6 +10095,30 @@ bake_test_case ComponentLifecycle_testcases[] = { "on_replace_set_2_entities", ComponentLifecycle_on_replace_set_2_entities }, + { + "on_replace_other_table_new_entity", + ComponentLifecycle_on_replace_other_table_new_entity + }, + { + "on_replace_other_table_existing_entity", + ComponentLifecycle_on_replace_other_table_existing_entity + }, + { + "on_replace_other_table_set_existing", + ComponentLifecycle_on_replace_other_table_set_existing + }, + { + "on_replace_other_table_batched_new_entity", + ComponentLifecycle_on_replace_other_table_batched_new_entity + }, + { + "on_replace_other_table_batched_existing", + ComponentLifecycle_on_replace_other_table_batched_existing + }, + { + "on_replace_other_table_entity_init", + ComponentLifecycle_on_replace_other_table_entity_init + }, { "copy_ctor_w_override", ComponentLifecycle_copy_ctor_w_override @@ -16868,7 +16898,7 @@ static bake_test_suite suites[] = { "ComponentLifecycle", ComponentLifecycle_setup, NULL, - 165, + 171, ComponentLifecycle_testcases }, { diff --git a/test/cpp/project.json b/test/cpp/project.json index 0655268666..3615a09d3f 100644 --- a/test/cpp/project.json +++ b/test/cpp/project.json @@ -389,6 +389,10 @@ "defer_on_replace_w_assign_existing_twice", "defer_on_replace_w_assign_batched_existing", "defer_on_replace_w_assign_batched_existing_twice", + "on_replace_other_table_set_new", + "on_replace_other_table_set_existing", + "on_replace_other_table_assign_new", + "on_replace_other_table_assign_existing", "set_lvalue_to_mutable", "set_lvalue_to_const", "set_rvalue", diff --git a/test/cpp/src/Entity.cpp b/test/cpp/src/Entity.cpp index 73266825f3..f8bb975aa8 100644 --- a/test/cpp/src/Entity.cpp +++ b/test/cpp/src/Entity.cpp @@ -6669,6 +6669,120 @@ void Entity_defer_on_replace_w_assign_batched_existing_twice(void) { test_assert(e.has()); } +struct replace_ctx_t { ecs_table_t *other_table; ecs_termset_t set_fields; int invoked; }; + +static void replace_capture_cpp(ecs_iter_t *it) { + auto *c = static_cast(it->ctx); + c->other_table = it->other_table; + c->set_fields = it->set_fields; + c->invoked++; +} + +void Entity_on_replace_other_table_set_new(void) { + flecs::world world; + + replace_ctx_t ctx = {}; + flecs::entity_t pos_id = world.component().id(); + + ecs_type_hooks_t hooks = {}; + hooks.ctor = flecs_default_ctor; + hooks.on_replace = replace_capture_cpp; + hooks.ctx = &ctx; + ecs_set_hooks_id(world, pos_id, &hooks); + + flecs::entity e = world.entity(); + ecs_table_t *prev = ecs_get_table(world, e); + test_int(ctx.invoked, 0); + + e.set(Position{10, 20}); + test_int(ctx.invoked, 1); + + /* prev captured before ensure: root table, no Position */ + test_assert(ctx.other_table == prev); + test_assert(ctx.set_fields == 3); + test_assert(!ecs_table_has_id(world, ctx.other_table, pos_id)); +} + +void Entity_on_replace_other_table_set_existing(void) { + flecs::world world; + + replace_ctx_t ctx = {}; + flecs::entity_t pos_id = world.component().id(); + + ecs_type_hooks_t hooks = {}; + hooks.ctor = flecs_default_ctor; + hooks.on_replace = replace_capture_cpp; + hooks.ctx = &ctx; + ecs_set_hooks_id(world, pos_id, &hooks); + + flecs::entity e = world.entity().set(Position{10, 20}); + test_int(ctx.invoked, 1); + + ecs_table_t *table_with_pos = ecs_get_table(world, e); + ctx = {}; + + e.set(Position{11, 21}); + test_int(ctx.invoked, 1); + + /* Entity already had Position -> other_table == {Position} table */ + test_assert(ctx.other_table == table_with_pos); + test_assert(ctx.set_fields == 3); + test_assert(ecs_table_has_id(world, ctx.other_table, pos_id)); +} + +void Entity_on_replace_other_table_assign_new(void) { + flecs::world world; + + replace_ctx_t ctx = {}; + flecs::entity_t pos_id = world.component().id(); + + ecs_type_hooks_t hooks = {}; + hooks.ctor = flecs_default_ctor; + hooks.on_replace = replace_capture_cpp; + hooks.ctx = &ctx; + ecs_set_hooks_id(world, pos_id, &hooks); + + flecs::entity e = world.entity().add(); + test_int(ctx.invoked, 0); + + ecs_table_t *table_with_pos = ecs_get_table(world, e); + + e.assign(Position{10, 20}); + test_int(ctx.invoked, 1); + + /* assign requires component to already exist; other_table == {Position} */ + test_assert(ctx.other_table == table_with_pos); + test_assert(ctx.set_fields == 3); + test_assert(ecs_table_has_id(world, ctx.other_table, pos_id)); +} + +void Entity_on_replace_other_table_assign_existing(void) { + flecs::world world; + + replace_ctx_t ctx = {}; + flecs::entity_t pos_id = world.component().id(); + + ecs_type_hooks_t hooks = {}; + hooks.ctor = flecs_default_ctor; + hooks.on_replace = replace_capture_cpp; + hooks.ctx = &ctx; + ecs_set_hooks_id(world, pos_id, &hooks); + + flecs::entity e = world.entity().add(); + e.assign(Position{10, 20}); + test_int(ctx.invoked, 1); + ecs_table_t *table_with_pos = ecs_get_table(world, e); + ctx = {}; + + e.assign(Position{11, 21}); + test_int(ctx.invoked, 1); + + /* Replacing existing via assign: other_table == {Position} */ + test_assert(ctx.other_table == table_with_pos); + test_assert(ctx.set_fields == 3); + test_assert(ecs_table_has_id(world, ctx.other_table, pos_id)); +} + void Entity_set_lvalue_to_mutable(void) { flecs::world world; diff --git a/test/cpp/src/main.cpp b/test/cpp/src/main.cpp index 8979258ac4..b08d892414 100644 --- a/test/cpp/src/main.cpp +++ b/test/cpp/src/main.cpp @@ -378,6 +378,10 @@ void Entity_defer_on_replace_w_assign_existing(void); void Entity_defer_on_replace_w_assign_existing_twice(void); void Entity_defer_on_replace_w_assign_batched_existing(void); void Entity_defer_on_replace_w_assign_batched_existing_twice(void); +void Entity_on_replace_other_table_set_new(void); +void Entity_on_replace_other_table_set_existing(void); +void Entity_on_replace_other_table_assign_new(void); +void Entity_on_replace_other_table_assign_existing(void); void Entity_set_lvalue_to_mutable(void); void Entity_set_lvalue_to_const(void); void Entity_set_rvalue(void); @@ -3192,6 +3196,22 @@ bake_test_case Entity_testcases[] = { "defer_on_replace_w_assign_batched_existing_twice", Entity_defer_on_replace_w_assign_batched_existing_twice }, + { + "on_replace_other_table_set_new", + Entity_on_replace_other_table_set_new + }, + { + "on_replace_other_table_set_existing", + Entity_on_replace_other_table_set_existing + }, + { + "on_replace_other_table_assign_new", + Entity_on_replace_other_table_assign_new + }, + { + "on_replace_other_table_assign_existing", + Entity_on_replace_other_table_assign_existing + }, { "set_lvalue_to_mutable", Entity_set_lvalue_to_mutable @@ -8461,7 +8481,7 @@ static bake_test_suite suites[] = { "Entity", NULL, NULL, - 399, + 403, Entity_testcases }, { diff --git a/test/cpp_addons/src/main.cpp b/test/cpp_addons/src/main.cpp index bb805885bf..47d9bea6f4 100644 --- a/test/cpp_addons/src/main.cpp +++ b/test/cpp_addons/src/main.cpp @@ -399,6 +399,7 @@ bake_test_case Doc_testcases[] = { } }; + static bake_test_suite suites[] = { { "Misc", diff --git a/test/meta/src/main.c b/test/meta/src/main.c index ccdc92aa55..38f1fe47dd 100644 --- a/test/meta/src/main.c +++ b/test/meta/src/main.c @@ -5888,6 +5888,7 @@ bake_test_case RttCompare_testcases[] = { } }; + static bake_test_suite suites[] = { { "PrimitiveTypes", diff --git a/test/query/src/main.c b/test/query/src/main.c index 16fa463af2..08d1082322 100644 --- a/test/query/src/main.c +++ b/test/query/src/main.c @@ -14200,66 +14200,54 @@ const char* Fuzzing_cache_kind_param[] = {"default", "auto"}; bake_test_param Fuzzing_params[] = { {"cache_kind", (char**)Fuzzing_cache_kind_param, 2} }; - const char* Basic_cache_kind_param[] = {"default", "auto"}; bake_test_param Basic_params[] = { {"cache_kind", (char**)Basic_cache_kind_param, 2} }; - const char* Combinations_cache_kind_param[] = {"default", "auto"}; const char* Combinations_on_instantiate_param[] = {"override", "inherit", "dont_inherit"}; bake_test_param Combinations_params[] = { {"cache_kind", (char**)Combinations_cache_kind_param, 2}, {"on_instantiate", (char**)Combinations_on_instantiate_param, 3} }; - const char* Variables_cache_kind_param[] = {"default", "auto"}; bake_test_param Variables_params[] = { {"cache_kind", (char**)Variables_cache_kind_param, 2} }; - const char* Operators_cache_kind_param[] = {"default", "auto"}; bake_test_param Operators_params[] = { {"cache_kind", (char**)Operators_cache_kind_param, 2} }; - const char* BuiltinPredicates_cache_kind_param[] = {"default", "auto"}; bake_test_param BuiltinPredicates_params[] = { {"cache_kind", (char**)BuiltinPredicates_cache_kind_param, 2} }; - const char* Scopes_cache_kind_param[] = {"default", "auto"}; const char* Scopes_on_instantiate_param[] = {"override", "inherit"}; bake_test_param Scopes_params[] = { {"cache_kind", (char**)Scopes_cache_kind_param, 2}, {"on_instantiate", (char**)Scopes_on_instantiate_param, 2} }; - const char* Traversal_cache_kind_param[] = {"default", "auto"}; bake_test_param Traversal_params[] = { {"cache_kind", (char**)Traversal_cache_kind_param, 2} }; - const char* MemberTarget_cache_kind_param[] = {"default", "auto"}; bake_test_param MemberTarget_params[] = { {"cache_kind", (char**)MemberTarget_cache_kind_param, 2} }; - const char* Toggle_cache_kind_param[] = {"default", "auto"}; bake_test_param Toggle_params[] = { {"cache_kind", (char**)Toggle_cache_kind_param, 2} }; - const char* Sparse_cache_kind_param[] = {"default", "auto"}; bake_test_param Sparse_params[] = { {"cache_kind", (char**)Sparse_cache_kind_param, 2} }; - const char* DontFragment_cache_kind_param[] = {"default", "auto"}; bake_test_param DontFragment_params[] = { {"cache_kind", (char**)DontFragment_cache_kind_param, 2} }; - const char* NonFragmentingChildOf_cache_kind_param[] = {"default", "auto"}; bake_test_param NonFragmentingChildOf_params[] = { {"cache_kind", (char**)NonFragmentingChildOf_cache_kind_param, 2} diff --git a/test/script/src/main.c b/test/script/src/main.c index bad313580e..ebf552a0d0 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -8029,7 +8029,6 @@ const char* Expr_folding_param[] = {"enabled", "disabled"}; bake_test_param Expr_params[] = { {"folding", (char**)Expr_folding_param, 2} }; - const char* Deserialize_folding_param[] = {"enabled", "disabled"}; bake_test_param Deserialize_params[] = { {"folding", (char**)Deserialize_folding_param, 2}