#include static ECS_COMPONENT_DECLARE(Position); static ECS_COMPONENT_DECLARE(String); static ECS_COMPONENT_DECLARE(Entity); void ComponentLifecycle_setup(void) { ecs_log_set_level(-3); } typedef struct xtor_ctx { ecs_entity_t component; size_t size; int32_t count; int32_t invoked; } xtor_ctx; typedef struct copy_ctx { ecs_entity_t component; size_t size; int32_t count; int32_t invoked; } copy_ctx; typedef struct cl_ctx { xtor_ctx ctor; xtor_ctx dtor; copy_ctx copy; copy_ctx move; copy_ctx ctor_move_dtor; copy_ctx move_dtor; } cl_ctx; static void comp_ctor( void *ptr, int32_t count, const ecs_type_info_t *info) { cl_ctx *data = info->hooks.ctx; data->ctor.component = info->component; data->ctor.size = info->size; data->ctor.count += count; data->ctor.invoked ++; Position *p = ptr; int i; for (i = 0; i < count; i ++) { p[i].x = 10; p[i].y = 20; } } static void comp_dtor( void *ptr, int32_t count, const ecs_type_info_t *info) { cl_ctx *data = info->hooks.ctx; data->dtor.component = info->component; data->dtor.size = info->size; data->dtor.count += count; data->dtor.invoked ++; } static void comp_copy( void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *info) { cl_ctx *data = info->hooks.ctx; data->copy.component = info->component; data->copy.size = info->size; data->copy.count += count; data->copy.invoked ++; memcpy(dst_ptr, src_ptr, info->size * count); } static void comp_move( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *info) { cl_ctx *data = info->hooks.ctx; data->move.component = info->component; data->move.size = info->size; data->move.count = count; data->move.invoked ++; memcpy(dst_ptr, src_ptr, info->size * count); } static void comp_move_dtor( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *info) { cl_ctx *data = info->hooks.ctx; data->move_dtor.component = info->component; data->move_dtor.size = info->size; data->move_dtor.count += count; data->move_dtor.invoked ++; memcpy(dst_ptr, src_ptr, info->size * count); } static void comp_pos_ctor_move_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *info) { cl_ctx *data = info->hooks.ctx; data->ctor_move_dtor.component = info->component; data->ctor_move_dtor.size = info->size; data->ctor_move_dtor.count += count; data->ctor_move_dtor.invoked ++; Position *p = src_ptr; int i; for (i = 0; i < count; i ++) { p[i].x = 10; p[i].y = 20; } memcpy(dst_ptr, src_ptr, info->size * count); } /* Position */ static int ctor_position = 0; static ECS_CTOR(Position, ptr, { ptr->x = 0; ptr->y = 0; ctor_position ++; }) static int dtor_position = 0; static ECS_DTOR(Position, ptr, { dtor_position ++; }) static int copy_position = 0; static ECS_COPY(Position, dst, src, { copy_position ++; dst->x = src->x; dst->y = src->y; }) static int move_position = 0; static ECS_MOVE(Position, dst, src, { move_position ++; dst->x = src->x; dst->y = src->y; }) static int on_add_position = 0; static void ecs_on_add(Position)(ecs_iter_t *it) { test_assert(it->count >= 1); test_assert(it->event == EcsOnAdd); Position *p = ecs_field(it, Position, 0); for (int i = 0; i < it->count; i ++) { on_add_position ++; test_int(p[i].x, 0); test_int(p[i].y, 0); } } static void on_add_position_count(ecs_iter_t *it) { on_add_position += it->count; } /* Velocity */ static int ctor_velocity = 0; static ECS_CTOR(Velocity, ptr, { ctor_velocity ++; }) static int dtor_velocity = 0; static ECS_DTOR(Velocity, ptr, { dtor_velocity ++; }) static int copy_velocity = 0; static ECS_COPY(Velocity, dst, src, { copy_velocity ++; }) static int move_velocity = 0; static ECS_MOVE(Velocity, dst, src, { move_velocity ++; }) /* Mass */ static int ctor_mass = 0; static ECS_CTOR(Mass, ptr, { ctor_mass ++; }) static int dtor_mass = 0; static ECS_DTOR(Mass, ptr, { dtor_mass ++; }) static int copy_mass = 0; static ECS_COPY(Mass, dst, src, { copy_mass ++; }) static int move_mass = 0; static ECS_MOVE(Mass, dst, src, { move_mass ++; }) /* Rotation */ static int ctor_rotation = 0; static ECS_CTOR(Rotation, ptr, { ctor_rotation ++; }) static int dtor_rotation = 0; static ECS_DTOR(Rotation, ptr, { dtor_rotation ++; }) static int copy_rotation = 0; static ECS_COPY(Rotation, dst, src, { copy_rotation ++; }) static int move_rotation = 0; static ECS_MOVE(Rotation, dst, src, { move_rotation ++; }) void ComponentLifecycle_ctor_on_add(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .ctor = comp_ctor, .ctx = &ctx }); ecs_entity_t e = ecs_new(world); test_int(ctx.ctor.invoked, 0); ecs_add(world, e, Position); test_assert(ctx.ctor.invoked != 0); test_int(ctx.ctor.component, ecs_id(Position)); test_int(ctx.ctor.size, sizeof(Position)); test_int(ctx.ctor.count, 1); ecs_fini(world); } void ComponentLifecycle_ctor_on_new(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .ctor = comp_ctor, .ctx = &ctx }); ecs_new_w(world, Position); test_assert(ctx.ctor.invoked != 0); test_int(ctx.ctor.component, ecs_id(Position)); test_int(ctx.ctor.size, sizeof(Position)); test_int(ctx.ctor.count, 1); ecs_fini(world); } void ComponentLifecycle_dtor_on_remove(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .dtor = comp_dtor, .ctx = &ctx }); ecs_entity_t e = ecs_new_w(world, Position); test_int(ctx.dtor.invoked, 0); ecs_remove(world, e, Position); test_assert(ctx.dtor.invoked != 0); test_int(ctx.dtor.component, ecs_id(Position)); test_int(ctx.dtor.size, sizeof(Position)); test_int(ctx.dtor.count, 1); ecs_fini(world); } void ComponentLifecycle_dtor_on_delete(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .dtor = comp_dtor, .ctx = &ctx }); ecs_entity_t e = ecs_new_w(world, Position); test_int(ctx.dtor.invoked, 0); ecs_delete(world, e); test_assert(ctx.dtor.invoked != 0); test_int(ctx.dtor.component, ecs_id(Position)); test_int(ctx.dtor.size, sizeof(Position)); test_int(ctx.dtor.count, 1); ecs_fini(world); } void ComponentLifecycle_copy_on_set(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .copy = comp_copy, .ctx = &ctx }); ecs_entity_t e = ecs_new(world); test_int(ctx.copy.invoked, 0); ecs_set(world, e, Position, {0, 0}); test_assert(ctx.copy.invoked != 0); test_int(ctx.copy.component, ecs_id(Position)); test_int(ctx.copy.size, sizeof(Position)); test_int(ctx.copy.count, 1); ecs_fini(world); } void ComponentLifecycle_copy_on_override(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_add_pair(world, ecs_id(Position), EcsOnInstantiate, EcsInherit); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .copy = comp_copy, .ctx = &ctx }); ecs_entity_t base = ecs_new_w(world, Position); test_int(ctx.copy.invoked, 0); ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); test_int(ctx.copy.invoked, 0); ecs_add(world, e, Position); test_assert(ctx.copy.invoked != 0); test_int(ctx.copy.component, ecs_id(Position)); test_int(ctx.copy.size, sizeof(Position)); test_int(ctx.copy.count, 1); ecs_fini(world); } void ComponentLifecycle_copy_on_clone(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .copy = comp_copy, .ctx = &ctx }); ecs_entity_t e = ecs_insert(world, ecs_value(Position, {10, 20})); test_assert(ctx.copy.invoked != 0); memset(&ctx, 0, sizeof(ctx)); ecs_clone(world, 0, e, true); test_assert(ctx.copy.invoked != 0); test_int(ctx.copy.component, ecs_id(Position)); test_int(ctx.copy.size, sizeof(Position)); test_int(ctx.copy.count, 1); ecs_fini(world); } void ComponentLifecycle_no_copy_on_move(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .copy = comp_copy, .ctx = &ctx }); ecs_entity_t e = ecs_insert(world, ecs_value(Position, {10, 20})); test_assert(ctx.copy.invoked != 0); memset(&ctx, 0, sizeof(ctx)); ecs_add(world, e, Velocity); test_int(ctx.copy.invoked, 0); ecs_fini(world); } void ComponentLifecycle_ctor_on_tag(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_TAG(world, Tag); test_expect_abort(); ecs_set_hooks_id(world, Tag, &(ecs_type_hooks_t){ .ctor = comp_ctor }); ecs_fini(world); } void ComponentLifecycle_dtor_on_tag(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_TAG(world, Tag); test_expect_abort(); ecs_set_hooks_id(world, Tag, &(ecs_type_hooks_t){ .dtor = comp_dtor }); ecs_fini(world); } void ComponentLifecycle_copy_on_tag(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_TAG(world, Tag); test_expect_abort(); ecs_set_hooks_id(world, Tag, &(ecs_type_hooks_t){ .copy = comp_copy }); ecs_fini(world); } void ComponentLifecycle_move_on_tag(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_TAG(world, Tag); test_expect_abort(); ecs_set_hooks_id(world, Tag, &(ecs_type_hooks_t){ .move = comp_move }); ecs_fini(world); } void ComponentLifecycle_merge_to_different_table(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_COMPONENT(world, Mass); ECS_COMPONENT(world, Rotation); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .dtor = ecs_dtor(Position), .copy = ecs_copy(Position), .move = ecs_move(Position) }); ecs_set_hooks(world, Velocity, { .ctor = ecs_ctor(Velocity), .dtor = ecs_dtor(Velocity), .copy = ecs_copy(Velocity), .move = ecs_move(Velocity) }); ecs_set_hooks(world, Mass, { .ctor = ecs_ctor(Mass), .dtor = ecs_dtor(Mass), .copy = ecs_copy(Mass), .move = ecs_move(Mass) }); ecs_set_hooks(world, Rotation, { .ctor = ecs_ctor(Rotation), .dtor = ecs_dtor(Rotation), .copy = ecs_copy(Rotation), .move = ecs_move(Rotation) }); ECS_ENTITY(world, e, Position, Velocity, Rotation); ctor_position = 0; dtor_position = 0; copy_position = 0; move_position = 0; ctor_velocity = 0; dtor_velocity = 0; copy_velocity = 0; move_velocity = 0; ctor_rotation = 0; dtor_rotation = 0; copy_rotation = 0; move_rotation = 0; ecs_defer_begin(world); ecs_remove(world, e, Position); test_int(ctor_position, 0); test_int(dtor_position, 0); test_int(copy_position, 0); test_int(move_position, 0); test_int(ctor_velocity, 0); test_int(dtor_velocity, 0); test_int(copy_velocity, 0); test_int(move_velocity, 0); test_int(ctor_rotation, 0); test_int(dtor_rotation, 0); test_int(copy_rotation, 0); test_int(move_rotation, 0); ecs_add(world, e, Mass); test_int(ctor_mass, 0); test_int(ctor_velocity, 0); test_int(dtor_velocity, 0); test_int(copy_velocity, 0); test_int(move_velocity, 0); test_int(ctor_rotation, 0); test_int(dtor_rotation, 0); test_int(copy_rotation, 0); test_int(move_rotation, 0); ecs_remove(world, e, Rotation); test_int(ctor_rotation, 0); test_int(dtor_rotation, 0); test_int(copy_rotation, 0); test_int(move_rotation, 0); test_int(ctor_velocity, 0); test_int(dtor_velocity, 0); test_int(copy_velocity, 0); test_int(move_velocity, 0); ecs_defer_end(world); test_assert(!ecs_has(world, e, Position)); test_assert(ecs_has(world, e, Velocity)); test_assert(ecs_has(world, e, Mass)); test_assert(!ecs_has(world, e, Rotation)); test_int(ctor_position, 0); test_int(dtor_position, 1); // removed first, no moves test_int(copy_position, 0); test_int(move_position, 0); test_assert(ctor_velocity != 0); test_assert(dtor_velocity != 0); test_int(copy_velocity, 0); test_assert(move_velocity != 0); test_assert(ctor_rotation == 0); test_assert(dtor_rotation != 0); test_int(copy_rotation, 0); test_assert(move_rotation == 0); test_assert(ctor_mass != 0); // got added, moved once test_assert(dtor_mass == 0); test_int(copy_mass, 0); test_assert(move_mass == 0); ecs_fini(world); } void ComponentLifecycle_merge_to_new_table(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_entity_t e = ecs_new(world); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .dtor = ecs_dtor(Position), .copy = ecs_copy(Position), .move = ecs_move(Position) }); ecs_defer_begin(world); ecs_add(world, e, Position); ecs_defer_end(world); test_int(ctor_position, 1); test_int(move_position, 0); ecs_fini(world); } void ComponentLifecycle_delete_in_stage(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_COMPONENT(world, Mass); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .dtor = ecs_dtor(Position), .copy = ecs_copy(Position), .move = ecs_move(Position) }); ecs_set_hooks(world, Velocity, { .ctor = ecs_ctor(Velocity), .dtor = ecs_dtor(Velocity), .copy = ecs_copy(Velocity), .move = ecs_move(Velocity) }); ecs_set_hooks(world, Mass, { .ctor = ecs_ctor(Mass), .dtor = ecs_dtor(Mass), .copy = ecs_copy(Mass), .move = ecs_move(Mass) }); ECS_ENTITY(world, e, Position, Velocity, Mass); ctor_position = 0; dtor_position = 0; copy_position = 0; move_position = 0; ctor_velocity = 0; dtor_velocity = 0; copy_velocity = 0; move_velocity = 0; ctor_mass = 0; dtor_mass = 0; copy_mass = 0; move_mass = 0; ecs_defer_begin(world); /* None of the components should be destructed while in the stage as they * were never copied to the stage */ ecs_delete(world, e); test_int(dtor_position, 0); test_int(dtor_velocity, 0); test_int(dtor_mass, 0); ecs_defer_end(world); test_assert(ecs_exists(world, e)); test_assert(!ecs_is_alive(world, e)); /* Position is destroyed in main stage */ test_int(ctor_position, 0); test_int(dtor_position, 1); test_int(copy_position, 0); test_int(move_position, 0); /* Velocity is destroyed in main stage */ test_int(ctor_velocity, 0); test_int(dtor_velocity, 1); test_int(copy_velocity, 0); test_int(move_velocity, 0); /* Mass is destroyed in main stage */ test_int(ctor_mass, 0); test_int(dtor_mass, 1); test_int(copy_mass, 0); test_int(move_mass, 0); ecs_fini(world); } typedef struct Pair { float value_1; float value_2; } Pair; void ComponentLifecycle_ctor_on_add_pair(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Pair); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Pair, { .ctor = comp_ctor, .ctx = &ctx }); ecs_entity_t e = ecs_new(world); test_int(ctx.ctor.invoked, 0); ecs_add_pair(world, e, ecs_id(Pair), ecs_id(Position)); test_assert(ctx.ctor.invoked != 0); test_int(ctx.ctor.component, ecs_id(Pair)); test_int(ctx.ctor.size, sizeof(Pair)); test_int(ctx.ctor.count, 1); ecs_fini(world); } void ComponentLifecycle_ctor_on_add_pair_tag(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, Pair); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .ctor = comp_ctor, .ctx = &ctx }); ecs_entity_t e = ecs_new(world); test_int(ctx.ctor.invoked, 0); ecs_add_pair(world, e, Pair, ecs_id(Position)); test_assert(ctx.ctor.invoked != 0); test_int(ctx.ctor.component, ecs_id(Position)); test_int(ctx.ctor.size, sizeof(Position)); test_int(ctx.ctor.count, 1); ecs_fini(world); } void ComponentLifecycle_ctor_on_move_pair(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Pair); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Pair, { .ctor = comp_ctor, .ctx = &ctx }); /* Create entity in existing table */ ecs_entity_t e = ecs_new_w(world, Position); test_int(ctx.ctor.invoked, 0); /* Add pair to existing table */ ecs_add_pair(world, e, ecs_id(Pair), ecs_id(Position)); test_assert(ctx.ctor.invoked != 0); test_int(ctx.ctor.component, ecs_id(Pair)); test_int(ctx.ctor.size, sizeof(Pair)); test_int(ctx.ctor.count, 1); ecs_fini(world); } void ComponentLifecycle_move_on_realloc(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .ctor = comp_ctor, .move = comp_move, .ctx = &ctx }); ecs_entity_t e = ecs_new(world); test_int(ctx.ctor.invoked, 0); ecs_add(world, e, Position); test_assert(ctx.ctor.invoked != 0); test_int(ctx.move.invoked, 0); test_int(ctx.ctor.component, ecs_id(Position)); test_int(ctx.ctor.size, sizeof(Position)); test_int(ctx.ctor.count, 1); ctx = (cl_ctx){ { 0 } }; /* Trigger realloc & move */ ecs_new_w(world, Position); ecs_new_w(world, Position); test_assert(ctx.ctor.invoked != 0); test_assert(ctx.move.invoked != 0); ecs_fini(world); } void ComponentLifecycle_move_on_bulk_new(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .ctor = comp_ctor, .move = comp_move, .ctx = &ctx }); ecs_entity_t e = ecs_new(world); test_int(ctx.ctor.invoked, 0); ecs_add(world, e, Position); test_assert(ctx.ctor.invoked != 0); test_int(ctx.move.invoked, 0); test_int(ctx.ctor.component, ecs_id(Position)); test_int(ctx.ctor.size, sizeof(Position)); test_int(ctx.ctor.count, 1); ctx = (cl_ctx){ { 0 } }; ecs_bulk_new(world, Position, 1000); test_assert(ctx.ctor.invoked != 0); test_assert(ctx.move.invoked != 0); ecs_fini(world); } void ComponentLifecycle_on_add_on_bulk_new(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .on_add = ecs_on_add(Position) }); ecs_bulk_new(world, Position, 1000); test_int(ctor_position, 1000); test_int(on_add_position, 1000); ecs_fini(world); } void ComponentLifecycle_move_on_delete(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .ctor = comp_ctor, .move = comp_move, .ctx = &ctx }); ecs_entity_t e1 = ecs_new_w(world, Position); ecs_new_w(world, Position); test_assert(ctx.ctor.invoked != 0); ctx = (cl_ctx){ { 0 } }; ecs_delete(world, e1); /* Should trigger move of e2 */ test_int(ctx.ctor.invoked, 0); test_assert(ctx.move.invoked != 0); test_int(ctx.move.component, ecs_id(Position)); test_int(ctx.move.size, sizeof(Position)); test_int(ctx.move.count, 1); ecs_fini(world); } void ComponentLifecycle_move_dtor_on_delete(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .dtor = comp_dtor, .move = comp_move, .ctx = &ctx }); ecs_entity_t e1 = ecs_new_w(world, Position); ecs_new_w(world, Position); ctx = (cl_ctx){ { 0 } }; ecs_delete(world, e1); /* Should trigger move of e2 */ test_assert(ctx.dtor.invoked != 0); test_assert(ctx.move.invoked != 0); test_int(ctx.dtor.component, ecs_id(Position)); test_int(ctx.dtor.size, sizeof(Position)); test_int(ctx.dtor.count, 1); test_int(ctx.move.component, ecs_id(Position)); test_int(ctx.move.size, sizeof(Position)); test_int(ctx.move.count, 1); ecs_fini(world); } void ComponentLifecycle_copy_on_override_pair(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Pair); ecs_add_pair(world, ecs_id(Pair), EcsOnInstantiate, EcsInherit); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Pair, { .ctor = comp_ctor, .copy = comp_copy, .ctx = &ctx }); ecs_entity_t base = ecs_new(world); ecs_add_pair(world, base, ecs_id(Pair), ecs_id(Position)); test_assert(ctx.ctor.invoked != 0); test_int(ctx.copy.invoked, 0); ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, base); test_assert(ctx.ctor.invoked != 0); /* No change */ test_int(ctx.copy.invoked, 0); ctx = (cl_ctx){ { 0 } }; /* Override */ ecs_add_pair(world, instance, ecs_id(Pair), ecs_id(Position)); test_assert(ctx.ctor.invoked != 0); test_assert(ctx.copy.invoked != 0); test_int(ctx.copy.component, ecs_id(Pair)); test_int(ctx.copy.size, sizeof(Pair)); test_int(ctx.copy.count, 1); ecs_fini(world); } void ComponentLifecycle_copy_on_override_pair_tag(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, Pair); ecs_add_pair(world, Pair, EcsOnInstantiate, EcsInherit); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .ctor = comp_ctor, .copy = comp_copy, .ctx = &ctx }); ecs_entity_t base = ecs_new(world); ecs_add_pair(world, base, Pair, ecs_id(Position)); test_assert(ctx.ctor.invoked != 0); test_int(ctx.copy.invoked, 0); ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, base); test_assert(ctx.ctor.invoked != 0); /* No change */ test_int(ctx.copy.invoked, 0); ctx = (cl_ctx){ { 0 } }; /* Override */ ecs_add_pair(world, instance, Pair, ecs_id(Position)); test_assert(ctx.ctor.invoked != 0); test_assert(ctx.copy.invoked != 0); test_int(ctx.copy.component, ecs_id(Position)); test_int(ctx.copy.size, sizeof(Position)); test_int(ctx.copy.count, 1); ecs_fini(world); } void ComponentLifecycle_copy_on_set_pair(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Pair); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Pair, { .copy = comp_copy, .ctx = &ctx }); ecs_entity_t e = ecs_new(world); test_int(ctx.copy.invoked, 0); ecs_set_pair(world, e, Pair, ecs_id(Position), {0, 0}); test_assert(ctx.copy.invoked != 0); test_int(ctx.copy.component, ecs_id(Pair)); test_int(ctx.copy.size, sizeof(Pair)); test_int(ctx.copy.count, 1); ecs_fini(world); } void ComponentLifecycle_copy_on_set_pair_tag(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, Pair); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .copy = comp_copy, .ctx = &ctx }); ecs_entity_t e = ecs_new(world); test_int(ctx.copy.invoked, 0); ecs_set_pair_second(world, e, Pair, Position, {0, 0}); test_assert(ctx.copy.invoked != 0); test_int(ctx.copy.component, ecs_id(Position)); test_int(ctx.copy.size, sizeof(Position)); test_int(ctx.copy.count, 1); ecs_fini(world); } void ComponentLifecycle_allow_lifecycle_overwrite_equal_callbacks(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position) }); /* Same actions, should be ok */ ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position) }); ecs_new_w(world, Position); test_int(ctor_position, 1); ecs_fini(world); } static void AddPosition(ecs_iter_t *it) { } void ComponentLifecycle_set_lifecycle_after_trigger(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_OBSERVER(world, AddPosition, EcsOnAdd, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position) }); ecs_new_w(world, Position); test_int(ctor_position, 1); ecs_fini(world); } static int dummy_dtor_invoked = 0; typedef struct Dummy { ecs_world_t *world; ecs_entity_t e; int value; } Dummy; static void dummy_dtor( void *ptr, int32_t count, const ecs_type_info_t *info) { Dummy *d = ptr; int i; for (i = 0; i < count; i ++) { test_assert(ecs_is_valid(d[i].world, d[i].e)); } dummy_dtor_invoked ++; } void ComponentLifecycle_valid_entity_in_dtor_after_delete(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Dummy); ecs_set_hooks(world, Dummy, { .dtor = dummy_dtor }); ecs_entity_t e = ecs_new_w(world, Dummy); test_assert(e != 0); ecs_set(world, e, Dummy, {world, e, 0}); ecs_delete(world, e); test_assert(!ecs_is_valid(world, e)); test_assert(!ecs_is_alive(world, e)); test_int(dummy_dtor_invoked, 1); ecs_fini(world); } void ComponentLifecycle_ctor_w_emplace(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .ctor = comp_ctor, .ctx = &ctx }); ecs_entity_t e = ecs_new(world); test_assert(e != 0); const Position *ptr = ecs_emplace(world, e, Position, NULL); test_assert(ptr != NULL); test_int(ctx.ctor.invoked, 0); ecs_fini(world); } void ComponentLifecycle_ctor_w_emplace_defer(void) { ecs_world_t* world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position) }); test_int(ctor_position, 0); ecs_entity_t e = ecs_new(world); test_assert(e != 0); ecs_defer_begin(world); Position *ptr = ecs_emplace(world, e, Position, NULL); test_assert(ptr != NULL); test_int(ctor_position, 0); ptr->x = 10; ptr->y = 20; test_assert(!ecs_has(world, e, Position)); test_int(ctor_position, 0); ecs_defer_end(world); test_assert(ecs_has(world, e, Position)); test_int(ctor_position, 0); const Position *p = ecs_get(world, e, Position); test_assert(p != NULL); test_int(p->x, 10); test_int(p->y, 20); ecs_fini(world); } void ComponentLifecycle_on_add_w_emplace(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_add = on_add_position_count }); ecs_entity_t e = ecs_new(world); test_assert(e != 0); test_int(on_add_position, 0); const Position *ptr = ecs_emplace(world, e, Position, NULL); test_assert(ptr != NULL); test_int(on_add_position, 1); ecs_fini(world); } void ComponentLifecycle_on_add_w_emplace_existing(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .on_add = on_add_position_count }); ecs_entity_t e = ecs_new_w(world, Velocity); test_assert(e != 0); test_int(ctor_position, 0); test_int(on_add_position, 0); const Position *ptr = ecs_emplace(world, e, Position, NULL); test_assert(ptr != NULL); test_int(ctor_position, 0); test_int(on_add_position, 1); ecs_fini(world); } void ComponentLifecycle_on_add_w_emplace_defer(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_add = on_add_position_count }); ecs_entity_t e = ecs_new(world); test_assert(e != 0); ecs_defer_begin(world); test_int(on_add_position, 0); const Position *ptr = ecs_emplace(world, e, Position, NULL); test_assert(ptr != NULL); test_int(on_add_position, 0); ecs_defer_end(world); test_int(on_add_position, 1); ecs_fini(world); } static int move_ctor_position = 0; static void position_move_ctor( void *dst, void *src, int32_t count, const ecs_type_info_t *info) { *((Position*)dst) = *((Position*)src); move_ctor_position ++; } static int move_dtor_position = 0; static void position_move_dtor( void *dst, void *src, int32_t count, const ecs_type_info_t *info) { *((Position*)dst) = *((Position*)src); move_dtor_position ++; } void ComponentLifecycle_ctor_w_emplace_defer_use_move_ctor(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .move = ecs_move(Position), .move_ctor = position_move_ctor }); ecs_entity_t e = ecs_new(world); test_assert(e != 0); ecs_defer_begin(world); test_int(on_add_position, 0); Position *ptr = ecs_emplace(world, e, Position, NULL); ptr->x = 10; ptr->y = 20; test_assert(ptr != NULL); test_int(ctor_position, 0); test_int(move_position, 0); test_int(move_ctor_position, 0); ecs_defer_end(world); test_int(ctor_position, 0); test_int(move_position, 0); test_int(move_ctor_position, 1); const Position *p = ecs_get(world, e, Position); test_int(p->x, 10); test_int(p->y, 20); ecs_fini(world); } void ComponentLifecycle_ctor_w_emplace_defer_twice(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .move = ecs_move(Position), .move_ctor = position_move_ctor, .move_dtor = position_move_dtor }); ecs_entity_t e = ecs_new(world); test_assert(e != 0); ecs_defer_begin(world); { bool is_new = false; Position *ptr = ecs_emplace(world, e, Position, &is_new); test_bool(is_new, true); test_assert(ptr != NULL); ptr->x = 10; ptr->y = 20; } { bool is_new = false; Position *ptr = ecs_emplace(world, e, Position, &is_new); test_bool(is_new, true); test_assert(ptr != NULL); ptr->x = 20; ptr->y = 30; } test_int(ctor_position, 0); test_int(move_position, 0); test_int(move_ctor_position, 0); test_int(move_dtor_position, 0); ecs_defer_end(world); test_int(ctor_position, 0); test_int(move_position, 0); test_int(move_ctor_position, 1); test_int(move_dtor_position, 1); const Position *p = ecs_get(world, e, Position); test_int(p->x, 20); test_int(p->y, 30); ecs_fini(world); } void ComponentLifecycle_ctor_w_emplace_defer_existing(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .move = ecs_move(Position), .move_ctor = position_move_ctor, .move_dtor = position_move_dtor }); ecs_entity_t e = ecs_new_w(world, Position); test_assert(e != 0); test_int(ctor_position, 1); ctor_position = 0; ecs_defer_begin(world); { bool is_new = false; Position *ptr = ecs_emplace(world, e, Position, &is_new); test_bool(is_new, false); test_assert(ptr != NULL); ptr->x = 10; ptr->y = 20; } { bool is_new = false; Position *ptr = ecs_emplace(world, e, Position, &is_new); test_bool(is_new, false); test_assert(ptr != NULL); ptr->x = 20; ptr->y = 30; } test_int(ctor_position, 0); test_int(move_position, 0); test_int(move_ctor_position, 0); test_int(move_dtor_position, 0); ecs_defer_end(world); test_int(ctor_position, 0); test_int(move_position, 0); test_int(move_ctor_position, 0); test_int(move_dtor_position, 0); const Position *p = ecs_get(world, e, Position); test_int(p->x, 20); test_int(p->y, 30); ecs_fini(world); } void ComponentLifecycle_merge_async_stage_w_emplace(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .copy = ecs_copy(Position), .move_ctor = position_move_ctor, .dtor = ecs_dtor(Position) }); ecs_entity_t e = ecs_new(world); ecs_world_t *async = ecs_stage_new(world); Position *p = ecs_emplace(async, e, Position, NULL); test_assert(!ecs_has(world, e, Position)); test_int(ctor_position, 0); test_int(copy_position, 0); test_int(move_position, 0); test_int(move_ctor_position, 0); test_int(dtor_position, 0); p->x = 10; p->y = 20; ecs_merge(async); test_assert(ecs_has(world, e, Position)); test_int(ctor_position, 0); test_int(copy_position, 0); test_int(move_position, 0); test_int(move_ctor_position, 1); test_int(dtor_position, 1); const Position *ptr = ecs_get(world, e, Position); test_int(ptr->x, 10); test_int(ptr->y, 20); ecs_stage_free(async); ecs_fini(world); } void ComponentLifecycle_merge_async_stage_w_emplace_to_deferred_world(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .copy = ecs_copy(Position) }); ecs_entity_t e = ecs_new(world); ecs_world_t *async = ecs_stage_new(world); Position *p = ecs_emplace(async, e, Position, NULL); test_assert(!ecs_has(world, e, Position)); test_int(ctor_position, 0); p->x = 10; p->y = 20; ecs_defer_begin(world); test_expect_abort(); ecs_merge(async); } static void invalid_ctor(void *ptr, int count, const ecs_type_info_t *ti) { test_assert(false); } void ComponentLifecycle_emplace_grow_w_existing_component(void) { test_quarantine("5 Feb 2023"); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ecs_set_hooks(world, Position, { .ctor = invalid_ctor, .copy = ecs_copy(Position), .move = ecs_move(Position), .dtor = ecs_dtor(Position) }); ecs_entity_t e1 = ecs_new_w(world, Velocity); ecs_entity_t e2 = ecs_new_w(world, Velocity); ecs_entity_t e3 = ecs_new_w(world, Velocity); { Position *p = ecs_emplace(world, e1, Position, NULL); p->x = 10; p->y = 20; } { Position *p = ecs_emplace(world, e2, Position, NULL); p->x = 30; p->y = 40; } { Position *p = ecs_emplace(world, e3, Position, NULL); p->x = 50; p->y = 60; } test_assert(ecs_has(world, e1, Position)); test_assert(ecs_has(world, e2, Position)); test_assert(ecs_has(world, e3, Position)); { const Position *p = ecs_get(world, e1, Position); test_assert(p != NULL); test_int(p->x, 10); test_int(p->y, 20); } { const Position *p = ecs_get(world, e2, Position); test_assert(p != NULL); test_int(p->x, 30); test_int(p->y, 40); } { const Position *p = ecs_get(world, e3, Position); test_assert(p != NULL); test_int(p->x, 50); test_int(p->y, 60); } ecs_fini(world); } void ComponentLifecycle_dtor_on_fini(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Dummy); ecs_set_hooks(world, Dummy, { .dtor = dummy_dtor }); ecs_entity_t e = ecs_new_w(world, Dummy); test_assert(e != 0); ecs_set(world, e, Dummy, {world, e}); test_int(dummy_dtor_invoked, 0); ecs_fini(world); test_int(dummy_dtor_invoked, 1); } static void type_dtor( void *ptr, int32_t count, const ecs_type_info_t *info) { test_int(count, 1); Dummy *d = ptr; ecs_world_t *world = d->world; ecs_entity_t e = d->e; test_assert(world != NULL); test_assert(e != 0); const ecs_type_t *type = ecs_get_type(world, e); test_assert(type != NULL); test_int(type->count, 3); dummy_dtor_invoked = 1; } typedef struct { ecs_world_t *world; ecs_entity_t e; ecs_entity_t other; } Entity; typedef struct { ecs_world_t *world; ecs_entity_t e; char *str; } String; static int other_dtor_invoked; static int other_dtor_valid_entity; static void other_type_dtor( void *ptr, int32_t count, const ecs_type_info_t *info) { test_int(count, 1); test_assert(ptr != NULL); Entity *comp = ptr; ecs_world_t *world = comp->world; ecs_entity_t e = comp->e; test_assert(e != 0); test_assert(world != NULL); test_assert(ecs_is_valid(world, e)); test_assert(comp->other != 0); if (ecs_is_valid(world, comp->other)) { const ecs_type_t *type = ecs_get_type(world, comp->other); test_assert(type != NULL); test_int(type->count, 2); other_dtor_valid_entity ++; } other_dtor_invoked ++; } ECS_MOVE(Entity, dst, src, { dst->world = src->world; dst->e = src->e; dst->other = src->other; src->world = NULL; src->e = 0; src->other = 0; }) static void other_delete_dtor( void *ptr, int32_t count, const ecs_type_info_t *info) { test_int(count, 1); test_assert(ptr != NULL); Entity *comp = ptr; ecs_world_t *world = comp->world; ecs_entity_t e = comp->e; test_assert(e != 0); test_assert(ecs_is_valid(world, e)); test_assert(comp->other != 0); if (ecs_is_valid(world, comp->other)) { ecs_delete(world, comp->other); other_dtor_valid_entity ++; test_assert(ecs_is_valid(world, e)); test_assert(ecs_get(world, e, Entity) == comp); test_assert(ecs_is_valid(world, comp->other)); } other_dtor_invoked ++; } static void self_delete_dtor( void *ptr, int32_t count, const ecs_type_info_t *info) { test_int(count, 1); test_assert(ptr != NULL); Dummy *d = ptr; ecs_world_t *world = d->world; ecs_entity_t e = d->e; test_assert(world != 0); test_assert(e != 0); if (ecs_is_valid(world, e)) { ecs_delete(world, e); // Delete should be deferred test_assert(ecs_is_valid(world, e)); } dummy_dtor_invoked ++; } static void string_dtor( void *ptr, int32_t count, const ecs_type_info_t *info) { test_int(count, 1); test_assert(ptr != NULL); String *comp = ptr; ecs_world_t *world = comp->world; ecs_entity_t e = comp->e; test_assert(e != 0); test_assert(ecs_is_valid(world, e)); test_assert(comp->str != NULL); ecs_os_free(comp->str); } void ComponentLifecycle_valid_type_in_dtor_on_fini(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_COMPONENT(world, Dummy); ecs_set_hooks(world, Dummy, { .dtor = type_dtor }); ecs_entity_t e = ecs_new_w(world, Dummy); test_assert(e != 0); ecs_set(world, e, Dummy, {world, e}); ecs_add(world, e, Position); ecs_add(world, e, Velocity); test_int(dummy_dtor_invoked, 0); ecs_fini(world); test_int(dummy_dtor_invoked, 1); } void ComponentLifecycle_valid_other_type_of_entity_in_dtor_on_fini(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_COMPONENT_DEFINE(world, Entity); ecs_set_hooks(world, Entity, { .dtor = other_type_dtor }); ecs_entity_t e1 = ecs_new(world); ecs_entity_t e2 = ecs_new_w(world, Position); ecs_set(world, e2, Entity, {world, e2, e1}); ecs_add(world, e1, Velocity); ecs_set(world, e1, Entity, {world, e1, e2}); test_int(other_dtor_invoked, 0); ecs_fini(world); test_int(other_dtor_invoked, 2); test_int(other_dtor_valid_entity, 1); } void ComponentLifecycle_delete_in_dtor_other_type_on_fini(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT_DEFINE(world, String); ECS_COMPONENT_DEFINE(world, Entity); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ecs_set_hooks(world, String, { .dtor = string_dtor }); ecs_set_hooks(world, Entity, { .dtor = other_delete_dtor }); ecs_entity_t e1 = ecs_new_w(world, Position); ecs_entity_t e2 = ecs_new_w(world, Velocity); ecs_set(world, e2, Entity, {world, e2, e1}); ecs_set(world, e1, Entity, {world, e1, e2}); ecs_set(world, e1, String, {world, e1, ecs_os_strdup("Foo")}); ecs_set(world, e2, String, {world, e2, ecs_os_strdup("Foo")}); test_int(other_dtor_invoked, 0); ecs_fini(world); test_int(other_dtor_invoked, 2); test_int(other_dtor_valid_entity, 1); } void ComponentLifecycle_delete_in_dtor_other_type_on_delete_parent(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT_DEFINE(world, String); ECS_COMPONENT_DEFINE(world, Entity); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ecs_set_hooks(world, String, { .dtor = string_dtor }); ecs_set_hooks(world, Entity, { .dtor = other_delete_dtor }); ecs_entity_t parent = ecs_new(world); ecs_entity_t e1 = ecs_new_w(world, Position); ecs_entity_t e2 = ecs_new_w(world, Velocity); ecs_add_pair(world, e1, EcsChildOf, parent); ecs_add_pair(world, e2, EcsChildOf, parent); ecs_set(world, e2, Entity, {world, e2, e1}); ecs_set(world, e1, Entity, {world, e1, e2}); ecs_set(world, e1, String, {world, e1, ecs_os_strdup("Foo")}); ecs_set(world, e2, String, {world, e2, ecs_os_strdup("Foo")}); test_int(other_dtor_invoked, 0); ecs_delete(world, parent); test_int(other_dtor_invoked, 2); test_int(other_dtor_valid_entity, 1); test_assert(!ecs_is_alive(world, e1)); test_assert(!ecs_is_alive(world, e2)); ecs_fini(world); } void ComponentLifecycle_delete_in_dtor_other_type_on_delete(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT_DEFINE(world, String); ECS_COMPONENT_DEFINE(world, Entity); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ecs_set_hooks(world, String, { .dtor = string_dtor }); ecs_set_hooks(world, Entity, { .dtor = other_delete_dtor }); ecs_entity_t e1 = ecs_new_w(world, Position); ecs_entity_t e2 = ecs_new_w(world, Velocity); ecs_set(world, e2, Entity, {world, e2, e1}); ecs_set(world, e1, Entity, {world, e1, e2}); ecs_set(world, e1, String, {world, e1, ecs_os_strdup("Foo")}); ecs_set(world, e2, String, {world, e2, ecs_os_strdup("Foo")}); test_int(other_dtor_invoked, 0); ecs_delete(world, e1); ecs_delete(world, e2); test_int(other_dtor_invoked, 2); test_int(other_dtor_valid_entity, 1); ecs_fini(world); } void ComponentLifecycle_delete_self_in_dtor_on_delete(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Dummy); ecs_set_hooks(world, Dummy, { .dtor = self_delete_dtor }); ecs_entity_t e1 = ecs_new_w(world, Dummy); ecs_entity_t e2 = ecs_new_w(world, Dummy); ecs_entity_t e3 = ecs_new_w(world, Dummy); ecs_set(world, e1, Dummy, {world, e1}); ecs_set(world, e2, Dummy, {world, e2}); ecs_set(world, e3, Dummy, {world, e3}); test_int(dummy_dtor_invoked, 0); ecs_delete(world, e1); ecs_delete(world, e2); ecs_delete(world, e3); test_int(dummy_dtor_invoked, 3); ecs_fini(world); } static int position_on_set_invoked; static int velocity_on_set_invoked; ECS_ON_SET(Position, ptr, { ptr->x += 1; ptr->y += 2; position_on_set_invoked ++; }) ECS_ON_SET(Velocity, ptr, { ptr->x += 1; ptr->y += 2; velocity_on_set_invoked ++; }) void ComponentLifecycle_on_set_after_set(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .on_set = ecs_on_set(Position) }); ecs_entity_t e = ecs_new_w(world, Position); test_int(ctor_position, 1); test_int(position_on_set_invoked, 0); const Position *p = ecs_get(world, e, Position); test_assert(p != NULL); test_int(p->x, 0); test_int(p->y, 0); ecs_set(world, e, Position, {10, 20}); test_int(ctor_position, 1); test_int(position_on_set_invoked, 1); test_assert(p == ecs_get(world, e, Position)); test_int(p->x, 11); test_int(p->y, 22); ecs_fini(world); } void ComponentLifecycle_on_add_after_new(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = flecs_default_ctor, .on_add = ecs_on_add(Position) }); ecs_new_w(world, Position); test_int(on_add_position, 1); ecs_new_w(world, Position); test_int(on_add_position, 2); ecs_new_w(world, Position); test_int(on_add_position, 3); ecs_fini(world); } void ComponentLifecycle_on_add_after_add(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = flecs_default_ctor, .on_add = ecs_on_add(Position) }); ecs_entity_t e1 = ecs_new(world); ecs_entity_t e2 = ecs_new(world); ecs_entity_t e3 = ecs_new(world); ecs_add(world, e1, Position); test_int(on_add_position, 1); ecs_add(world, e2, Position); test_int(on_add_position, 2); ecs_add(world, e3, Position); test_int(on_add_position, 3); ecs_fini(world); } void ComponentLifecycle_on_add_after_set(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = flecs_default_ctor, .on_add = ecs_on_add(Position) }); ecs_insert(world, ecs_value(Position, {10, 20})); test_int(on_add_position, 1); ecs_insert(world, ecs_value(Position, {10, 20})); test_int(on_add_position, 2); ecs_insert(world, ecs_value(Position, {10, 20})); test_int(on_add_position, 3); ecs_fini(world); } static int on_remove_position = 0; static void ecs_on_remove(Position)(ecs_iter_t *it) { test_assert(it->count >= 1); test_assert(it->event == EcsOnRemove); Position *p = ecs_field(it, Position, 0); for (int i = 0; i < it->count; i ++) { on_remove_position ++; test_int(p[i].x, 10); test_int(p[i].y, 20); } } void ComponentLifecycle_on_remove_after_remove(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_remove = ecs_on_remove(Position) }); ecs_entity_t e1 = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_entity_t e2 = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_entity_t e3 = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_remove(world, e1, Position); test_int(on_remove_position, 1); ecs_remove(world, e2, Position); test_int(on_remove_position, 2); ecs_remove(world, e3, Position); test_int(on_remove_position, 3); ecs_fini(world); } void ComponentLifecycle_on_remove_after_clear(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_remove = ecs_on_remove(Position) }); ecs_entity_t e1 = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_entity_t e2 = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_entity_t e3 = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_clear(world, e1); test_int(on_remove_position, 1); ecs_clear(world, e2); test_int(on_remove_position, 2); ecs_clear(world, e3); test_int(on_remove_position, 3); ecs_fini(world); } void ComponentLifecycle_on_remove_after_delete(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_remove = ecs_on_remove(Position) }); ecs_entity_t e1 = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_entity_t e2 = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_entity_t e3 = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_delete(world, e1); test_int(on_remove_position, 1); ecs_delete(world, e2); test_int(on_remove_position, 2); ecs_delete(world, e3); test_int(on_remove_position, 3); ecs_fini(world); } static int on_remove_tag_set_position_invoked = 0; static void on_remove_tag_set_position(ecs_iter_t *it) { for (int i = 0; i < it->count; i ++) { ecs_set(it->world, it->entities[i], Position, {10, 20}); on_remove_tag_set_position_invoked ++; } } static void on_remove_tag_set_position_pair(ecs_iter_t *it) { for (int i = 0; i < it->count; i ++) { ecs_set_pair(it->world, it->entities[i], Position, ecs_new(it->world), {10, 20}); on_remove_tag_set_position_invoked ++; } } static void on_remove_tag_set_position_obj_pair(ecs_iter_t *it) { for (int i = 0; i < it->count; i ++) { ecs_set_pair_second(it->world, it->entities[i], ecs_new(it->world), Position, {10, 20}); on_remove_tag_set_position_invoked ++; } } void ComponentLifecycle_free_component_new_id_while_fini(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT_DEFINE(world, Position); ECS_TAG(world, Tag); ecs_observer_init(world, &(ecs_observer_desc_t){ .query.terms[0].id = Tag, .events = {EcsOnRemove}, .callback = on_remove_tag_set_position }); ecs_new_w(world, Tag); ecs_fini(world); test_int(on_remove_tag_set_position_invoked, 1); } void ComponentLifecycle_dtor_component_new_id_while_fini(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT_DEFINE(world, Position); ECS_TAG(world, Tag); ecs_observer_init(world, &(ecs_observer_desc_t){ .query.terms[0].id = Tag, .events = {EcsOnRemove}, .callback = on_remove_tag_set_position }); ecs_set_hooks(world, Position, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(Position) }); ecs_new_w(world, Tag); test_int(dtor_position, 0); ecs_fini(world); test_int(on_remove_tag_set_position_invoked, 1); test_int(dtor_position, 1); } void ComponentLifecycle_free_component_new_pair_id_while_fini(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT_DEFINE(world, Position); ECS_TAG(world, Tag); ecs_observer_init(world, &(ecs_observer_desc_t){ .query.terms[0].id = Tag, .events = {EcsOnRemove}, .callback = on_remove_tag_set_position_pair }); ecs_new_w(world, Tag); ecs_fini(world); test_int(on_remove_tag_set_position_invoked, 1); } void ComponentLifecycle_dtor_component_new_pair_id_while_fini(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT_DEFINE(world, Position); ECS_TAG(world, Tag); ecs_observer_init(world, &(ecs_observer_desc_t){ .query.terms[0].id = Tag, .events = {EcsOnRemove}, .callback = on_remove_tag_set_position_pair }); ecs_set_hooks(world, Position, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(Position) }); ecs_new_w(world, Tag); test_int(dtor_position, 0); ecs_fini(world); test_int(on_remove_tag_set_position_invoked, 1); test_int(dtor_position, 1); } void ComponentLifecycle_free_component_new_obj_pair_id_while_fini(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT_DEFINE(world, Position); ECS_TAG(world, Tag); ecs_observer_init(world, &(ecs_observer_desc_t){ .query.terms[0].id = Tag, .events = {EcsOnRemove}, .callback = on_remove_tag_set_position_obj_pair }); ecs_new_w(world, Tag); ecs_fini(world); test_int(on_remove_tag_set_position_invoked, 1); } void ComponentLifecycle_dtor_component_new_obj_pair_id_while_fini(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT_DEFINE(world, Position); ECS_TAG(world, Tag); ecs_observer_init(world, &(ecs_observer_desc_t){ .query.terms[0].id = Tag, .events = {EcsOnRemove}, .callback = on_remove_tag_set_position_obj_pair }); ecs_set_hooks(world, Position, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(Position) }); ecs_new_w(world, Tag); test_int(dtor_position, 0); ecs_fini(world); test_int(on_remove_tag_set_position_invoked, 1); test_int(dtor_position, 1); } void ComponentLifecycle_ctor_move_dtor_after_resize(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .ctor = comp_ctor, .copy = comp_copy, .move = comp_move, .dtor = comp_dtor, .ctx = &ctx }); ecs_entity_t e1 = ecs_new(world); ecs_entity_t e2 = ecs_new(world); ecs_entity_t e3 = ecs_new(world); ecs_add(world, e1, Position); test_int(ctx.ctor.count, 1); test_int(ctx.copy.count, 0); test_int(ctx.move.count, 0); test_int(ctx.dtor.count, 0); ecs_os_zeromem(&ctx); ecs_add(world, e2, Position); test_int(ctx.ctor.count, 1); test_int(ctx.copy.count, 0); test_int(ctx.move.count, 0); test_int(ctx.dtor.count, 0); ecs_os_zeromem(&ctx); ecs_add(world, e3, Position); test_int(ctx.ctor.count, 3); test_int(ctx.copy.count, 0); test_int(ctx.move.count, 2); test_int(ctx.dtor.count, 2); ecs_fini(world); } static int component_user_ctx = 0; static int component_binding_ctx = 0; static int component_lifecycle_ctx = 0; static void component_ctx_free(void *ctx) { test_assert(ctx == &component_user_ctx); component_user_ctx ++; } void ComponentLifecycle_ctx_free(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctx = &component_user_ctx, .ctx_free = component_ctx_free }); ecs_fini(world); test_int(1, component_user_ctx); } void ComponentLifecycle_binding_ctx_free(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .binding_ctx = &component_user_ctx, .binding_ctx_free = component_ctx_free }); ecs_fini(world); test_int(1, component_user_ctx); } void ComponentLifecycle_lifecycle_ctx_free(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .lifecycle_ctx = &component_user_ctx, .lifecycle_ctx_free = component_ctx_free }); ecs_fini(world); test_int(1, component_user_ctx); } void ComponentLifecycle_ctx_free_after_delete_component(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctx = &component_user_ctx, .ctx_free = component_ctx_free }); ecs_remove_pair(world, ecs_id(Position), EcsOnDelete, EcsPanic); ecs_delete(world, ecs_id(Position)); test_int(1, component_user_ctx); ecs_fini(world); } void ComponentLifecycle_binding_ctx_free_after_delete_component(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .binding_ctx = &component_user_ctx, .binding_ctx_free = component_ctx_free }); ecs_remove_pair(world, ecs_id(Position), EcsOnDelete, EcsPanic); ecs_delete(world, ecs_id(Position)); test_int(1, component_user_ctx); ecs_fini(world); } void ComponentLifecycle_lifecycle_ctx_free_after_delete_component(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .lifecycle_ctx = &component_user_ctx, .lifecycle_ctx_free = component_ctx_free }); ecs_remove_pair(world, ecs_id(Position), EcsOnDelete, EcsPanic); ecs_delete(world, ecs_id(Position)); test_int(1, component_user_ctx); ecs_fini(world); } static void test_hook_ctx(ecs_iter_t *it) { test_assert(it->ctx == &component_user_ctx); test_assert(it->callback_ctx == &component_binding_ctx); component_user_ctx ++; component_binding_ctx ++; component_lifecycle_ctx ++; } void ComponentLifecycle_on_add_ctx(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_add = test_hook_ctx, .ctx = &component_user_ctx, .binding_ctx = &component_binding_ctx, .lifecycle_ctx = &component_lifecycle_ctx }); ecs_new_w(world, Position); test_int(1, component_user_ctx); test_int(1, component_binding_ctx); test_int(1, component_lifecycle_ctx); ecs_fini(world); test_int(1, component_user_ctx); test_int(1, component_binding_ctx); test_int(1, component_lifecycle_ctx); } void ComponentLifecycle_on_remove_ctx(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_remove = test_hook_ctx, .ctx = &component_user_ctx, .binding_ctx = &component_binding_ctx, .lifecycle_ctx = &component_lifecycle_ctx }); ecs_entity_t e = ecs_new_w(world, Position); test_int(0, component_user_ctx); test_int(0, component_binding_ctx); test_int(0, component_lifecycle_ctx); ecs_remove(world, e, Position); test_int(1, component_user_ctx); test_int(1, component_binding_ctx); test_int(1, component_lifecycle_ctx); ecs_fini(world); test_int(1, component_user_ctx); test_int(1, component_binding_ctx); test_int(1, component_lifecycle_ctx); } void ComponentLifecycle_on_set_ctx(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_set = test_hook_ctx, .ctx = &component_user_ctx, .binding_ctx = &component_binding_ctx, .lifecycle_ctx = &component_lifecycle_ctx }); ecs_entity_t e = ecs_new_w(world, Position); test_int(0, component_user_ctx); test_int(0, component_binding_ctx); test_int(0, component_lifecycle_ctx); ecs_set(world, e, Position, {10, 20}); test_int(1, component_user_ctx); test_int(1, component_binding_ctx); test_int(1, component_lifecycle_ctx); ecs_fini(world); test_int(1, component_user_ctx); test_int(1, component_binding_ctx); test_int(1, component_lifecycle_ctx); } static int test_on_event_invoked = 0; static void test_on_event(ecs_iter_t *it) { test_assert(it->ctx != NULL); test_assert(*(ecs_entity_t*)it->ctx == it->event); test_on_event_invoked ++; } void ComponentLifecycle_on_add_w_existing_component(void) { ecs_world_t* world = ecs_mini(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_add = test_on_event, .ctx = (void*)&EcsOnAdd }); ecs_entity_t e = ecs_entity(world, { .name = "Foo" }); ecs_add(world, e, Position); test_int(1, test_on_event_invoked); ecs_fini(world); } void ComponentLifecycle_on_remove_w_existing_component(void) { ecs_world_t* world = ecs_mini(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_remove = test_on_event, .ctx = (void*)&EcsOnRemove }); ecs_entity_t e = ecs_entity(world, { .name = "Foo" }); ecs_add(world, e, Position); test_int(0, test_on_event_invoked); ecs_remove(world, e, Position); test_int(1, test_on_event_invoked); ecs_fini(world); } static int on_add_count = 0; static int on_remove_count = 0; static void test_on_add(ecs_iter_t *it) { on_add_count ++; } static void test_on_remove(ecs_iter_t *it) { on_remove_count ++; } void ComponentLifecycle_component_init_set_hooks(void) { ecs_world_t* world = ecs_mini(); ecs_entity_t c = ecs_component_init(world, &(ecs_component_desc_t){ .entity = ecs_entity(world, {.name = "Position"}), .type = { .size = ECS_SIZEOF(Position), .alignment = ECS_ALIGNOF(Position), .hooks = { .on_add = test_on_add, .on_remove = test_on_remove } } }); const ecs_type_info_t *ti = ecs_get_type_info(world, c); test_assert(ti != NULL); test_uint(ti->size, ECS_SIZEOF(Position)); test_uint(ti->alignment, ECS_ALIGNOF(Position)); test_assert(ti->hooks.on_add == test_on_add); test_assert(ti->hooks.on_remove == test_on_remove); test_int(0, on_add_count); test_int(0, on_remove_count); ecs_entity_t e = ecs_new(world); ecs_add_id(world, e, c); test_int(1, on_add_count); test_int(0, on_remove_count); ecs_remove_id(world, e, c); test_int(1, on_add_count); test_int(1, on_remove_count); ecs_fini(world); test_int(1, on_add_count); test_int(1, on_remove_count); } void ComponentLifecycle_component_init_name_from_type_info(void) { ecs_world_t* world = ecs_mini(); ecs_entity_t c = ecs_component_init(world, &(ecs_component_desc_t){ .type = { .name = "Position", .size = ECS_SIZEOF(Position), .alignment = ECS_ALIGNOF(Position), } }); test_assert(c != 0); test_assert(c == ecs_lookup(world, "Position")); const EcsComponent *ptr = ecs_get(world, c, EcsComponent); test_assert(ptr != NULL); test_int(ptr->size, ECS_SIZEOF(Position)); test_int(ptr->size, ECS_SIZEOF(Velocity)); ecs_fini(world); } void ComponentLifecycle_component_init_scoped_name_from_type_info(void) { ecs_world_t* world = ecs_mini(); ecs_entity_t c = ecs_component_init(world, &(ecs_component_desc_t){ .type = { .name = "ns.Position", .size = ECS_SIZEOF(Position), .alignment = ECS_ALIGNOF(Position), } }); test_assert(c != 0); test_assert(c == ecs_lookup(world, "ns.Position")); const EcsComponent *ptr = ecs_get(world, c, EcsComponent); test_assert(ptr != NULL); test_int(ptr->size, ECS_SIZEOF(Position)); test_int(ptr->size, ECS_SIZEOF(Velocity)); ecs_fini(world); } void ComponentLifecycle_component_init_w_recycled_id(void) { ecs_world_t *world = ecs_init(); { ecs_entity_t c = ecs_component(world, { .entity = ecs_new(world), .type.size = 4, .type.alignment = 4 }); test_assert(c != 0); test_assert(ecs_has(world, c, EcsComponent)); ecs_delete(world, c); } { ecs_entity_t c = ecs_component(world, { .entity = ecs_new(world), .type.size = 4, .type.alignment = 4 }); test_assert(c != 0); test_assert(ecs_has(world, c, EcsComponent)); ecs_delete(world, c); } ecs_fini(world); } void ComponentLifecycle_component_init_w_recycled_non_component_id(void) { ecs_world_t *world = ecs_init(); { ecs_entity_t c = ecs_component(world, { .entity = ecs_new(world), .type.size = 4, .type.alignment = 4 }); test_assert(c != 0); test_assert(ecs_has(world, c, EcsComponent)); ecs_delete(world, c); } { ecs_entity_t c = ecs_new(world); test_assert(c != 0); test_assert(!ecs_has(world, c, EcsComponent)); ecs_delete(world, c); } { ecs_entity_t c = ecs_component(world, { .entity = ecs_new(world), .type.size = 4, .type.alignment = 4 }); test_assert(c != 0); test_assert(ecs_has(world, c, EcsComponent)); ecs_delete(world, c); } ecs_fini(world); } static int ctor_before_on_add_count = 0; static int on_add_after_ctor_count = 0; static void ctor_before_on_add( void *ptr, int32_t count, const ecs_type_info_t *info) { test_int(0, on_add_after_ctor_count); ctor_before_on_add_count ++; } static void on_add_after_ctor(ecs_iter_t *it) { test_int(1, ctor_before_on_add_count); on_add_after_ctor_count ++; } void ComponentLifecycle_on_add_after_ctor_w_add(void) { ecs_world_t* world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ctor_before_on_add, .on_add = on_add_after_ctor }); test_int(0, ctor_before_on_add_count); test_int(0, on_add_after_ctor_count); ecs_new_w(world, Position); test_int(1, ctor_before_on_add_count); test_int(1, on_add_after_ctor_count); ecs_fini(world); } void ComponentLifecycle_on_add_after_ctor_w_add_to(void) { ecs_world_t* world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ecs_set_hooks(world, Position, { .ctor = ctor_before_on_add, .on_add = on_add_after_ctor }); test_int(0, ctor_before_on_add_count); test_int(0, on_add_after_ctor_count); ecs_entity_t e = ecs_new_w(world, Tag); test_int(0, ctor_before_on_add_count); test_int(0, on_add_after_ctor_count); ecs_add(world, e, Position); test_int(1, ctor_before_on_add_count); test_int(1, on_add_after_ctor_count); ecs_fini(world); } void ComponentLifecycle_with_before_hooks(void) { ecs_world_t* world = ecs_mini(); ecs_entity_t pos_id = ecs_new(world); ecs_entity_t tag = ecs_new_w_pair(world, EcsWith, pos_id); ecs_entity_t ecs_id(Position) = ecs_component_init(world, &(ecs_component_desc_t){ .entity = pos_id, .type = { .size = ECS_SIZEOF(Position), .alignment = ECS_ALIGNOF(Position), .hooks = { .ctor = ecs_ctor(Position) } } }); test_assert(ecs_id(Position) == pos_id); ecs_entity_t e = ecs_new_w_id(world, tag); test_assert(ecs_has(world, e, Position)); test_assert(ecs_has_id(world, e, tag)); test_assert(ecs_get(world, e, Position) != NULL); test_int(ctor_position, 1); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.ctor == ecs_ctor(Position)); ecs_fini(world); } void ComponentLifecycle_with_component_on_add(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t ecs_id(Position) = ecs_component(world, { .type = { .size = ECS_SIZEOF(Position), .alignment = ECS_ALIGNOF(Position), .hooks = { .ctor = flecs_default_ctor, .on_add = ecs_on_add(Position) } } }); ECS_TAG(world, Foo); ecs_add_pair(world, Foo, EcsWith, ecs_id(Position)); test_int(on_add_position, 0); ecs_entity_t e = ecs_new_w(world, Foo); test_assert(ecs_has(world, e, Foo)); test_assert(ecs_has(world, e, Position)); test_int(on_add_position, 1); ecs_fini(world); } void ComponentLifecycle_move_ctor_on_move(void) { ecs_world_t* world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .dtor = ecs_dtor(Position), .move = ecs_move(Position), .move_ctor = position_move_ctor, }); ecs_entity_t e1 = ecs_new(world); ecs_entity_t e2 = ecs_new(world); ecs_entity_t p = ecs_new(world); Position *p1 = ecs_emplace(world, e1, Position, NULL); test_assert(p1 != NULL); Position *p2 = ecs_emplace(world, e2, Position, NULL); test_assert(p2 != NULL); test_int(ctor_position, 0); ecs_add_pair(world, e1, EcsChildOf, p); test_int(ctor_position, 0); test_int(move_ctor_position, 1); // move e1 to other table test_int(move_position, 1); // move e2 to old position of e1 test_int(dtor_position, 1); // dtor old position for e2 ecs_add_pair(world, e2, EcsChildOf, p); ecs_fini(world); } typedef struct { void *ptr; } TestSelf; ECS_COPY(TestSelf, dst, src, { test_assert(dst->ptr == dst); }) ECS_MOVE(TestSelf, dst, src, { test_assert(dst->ptr == dst); test_assert(src->ptr == src); }) ECS_CTOR(TestSelf, ptr, { ptr->ptr = ptr; }) ECS_DTOR(TestSelf, ptr, { test_assert(ptr->ptr == ptr); }) void ComponentLifecycle_ptr_to_self(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, TestSelf); ecs_set_hooks(world, TestSelf, { .ctor = ecs_ctor(TestSelf), .copy = ecs_copy(TestSelf), .move = ecs_move(TestSelf), .dtor = ecs_dtor(TestSelf) }); ecs_entity_t role = ecs_entity(world, { .name = "MyRole" }); ecs_entity_t e1 = ecs_new(world); ecs_set(world, e1, TestSelf, {"a"}); ecs_entity_t e2 = ecs_new(world); ecs_set(world, e2, TestSelf, {"a"}); ecs_entity_t e3 = ecs_new(world); ecs_add_pair(world, e2, e3, role); ecs_entity_t e4 = ecs_new(world); ecs_set(world, e4, TestSelf, {"a"}); ecs_delete(world, role); ecs_delete_with(world, ecs_id(TestSelf)); ecs_fini(world); } void ComponentLifecycle_ctor_move_dtor_from_move_ctor(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .copy = ecs_copy(Position), .move_ctor = position_move_ctor }); ecs_entity_t e = ecs_new(world); ecs_world_t *async = ecs_stage_new(world); Position *p = ecs_emplace(async, e, Position, NULL); test_assert(!ecs_has(world, e, Position)); test_int(ctor_position, 0); test_int(copy_position, 0); test_int(move_ctor_position, 0); p->x = 10; p->y = 20; ecs_merge(async); test_assert(ecs_has(world, e, Position)); test_int(ctor_position, 0); test_int(copy_position, 0); test_int(move_ctor_position, 1); const Position *ptr = ecs_get(world, e, Position); test_int(ptr->x, 10); test_int(ptr->y, 20); ecs_stage_free(async); ecs_fini(world); } static int hook_w_offset_invoked = 0; static int hook_w_offset_offset = 0; static Position hook_w_offset_position; static void hook_w_offset(ecs_iter_t *it) { Position *p = ecs_field(it, Position, 0); test_int(it->count, 1); hook_w_offset_offset = it->offset; hook_w_offset_invoked ++; hook_w_offset_position = *p; } void ComponentLifecycle_on_add_hook_check_offset(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_add = hook_w_offset }); ecs_insert(world, ecs_value(Position, {10, 20})); test_int(hook_w_offset_invoked, 1); test_int(hook_w_offset_offset, 0); ecs_insert(world, ecs_value(Position, {30, 40})); test_int(hook_w_offset_invoked, 2); test_int(hook_w_offset_offset, 1); ecs_insert(world, ecs_value(Position, {50, 60})); test_int(hook_w_offset_invoked, 3); test_int(hook_w_offset_offset, 2); ecs_fini(world); } void ComponentLifecycle_on_remove_hook_check_offset(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_remove = hook_w_offset }); ecs_entity_t e1 = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_entity_t e2 = ecs_insert(world, ecs_value(Position, {30, 40})); ecs_entity_t e3 = ecs_insert(world, ecs_value(Position, {50, 60})); test_int(hook_w_offset_invoked, 0); ecs_remove(world, e3, Position); test_int(hook_w_offset_invoked, 1); test_int(hook_w_offset_offset, 2); test_int(hook_w_offset_position.x, 50); test_int(hook_w_offset_position.y, 60); ecs_remove(world, e2, Position); test_int(hook_w_offset_invoked, 2); test_int(hook_w_offset_offset, 1); test_int(hook_w_offset_position.x, 30); test_int(hook_w_offset_position.y, 40); ecs_remove(world, e1, Position); test_int(hook_w_offset_invoked, 3); test_int(hook_w_offset_offset, 0); test_int(hook_w_offset_position.x, 10); test_int(hook_w_offset_position.y, 20); ecs_fini(world); } void ComponentLifecycle_on_set_hook_check_offset(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_set = hook_w_offset }); ecs_insert(world, ecs_value(Position, {10, 20})); test_int(hook_w_offset_invoked, 1); test_int(hook_w_offset_offset, 0); test_int(hook_w_offset_position.x, 10); test_int(hook_w_offset_position.y, 20); ecs_insert(world, ecs_value(Position, {30, 40})); test_int(hook_w_offset_invoked, 2); test_int(hook_w_offset_offset, 1); test_int(hook_w_offset_position.x, 30); test_int(hook_w_offset_position.y, 40); ecs_insert(world, ecs_value(Position, {50, 60})); test_int(hook_w_offset_invoked, 3); test_int(hook_w_offset_offset, 2); test_int(hook_w_offset_position.x, 50); test_int(hook_w_offset_position.y, 60); ecs_fini(world); } static int on_set_position_invoked = 0; static void on_set_position(ecs_iter_t *it) { Position *p = ecs_field(it, Position, 0); test_int(it->count, 1); test_int(p->x, 10); test_int(p->y, 20); on_set_position_invoked ++; } void ComponentLifecycle_on_set_hook_on_override(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_set = on_set_position }); ecs_add_pair(world, ecs_id(Position), EcsOnInstantiate, EcsInherit); ecs_entity_t p = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_add_id(world, p, EcsPrefab); test_int(on_set_position_invoked, 1); ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, p); test_int(on_set_position_invoked, 1); ecs_add(world, i, Position); test_int(on_set_position_invoked, 2); { const Position *p = ecs_get(world, i, Position); test_assert(p != NULL); test_int(p->x, 10); test_int(p->y, 20); } ecs_fini(world); } void ComponentLifecycle_on_set_hook_on_auto_override(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_add_pair(world, ecs_id(Position), EcsOnInstantiate, EcsInherit); ecs_set_hooks(world, Position, { .on_set = on_set_position }); ecs_entity_t p = ecs_insert(world, ecs_value(Position, {10, 20})); ecs_add_id(world, p, ECS_AUTO_OVERRIDE | ecs_id(Position)); ecs_add_id(world, p, EcsPrefab); test_int(on_set_position_invoked, 1); ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, p); test_int(on_set_position_invoked, 2); { const Position *p = ecs_get(world, i, Position); test_assert(p != NULL); test_int(p->x, 10); test_int(p->y, 20); } ecs_fini(world); } void ComponentLifecycle_batched_set_new_component_w_lifecycle(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .move = ecs_move(Position), .copy = ecs_copy(Position), .dtor = ecs_dtor(Position) }); ecs_defer_begin(world); ecs_entity_t e = ecs_new(world); ecs_set(world, e, Position, {10, 20}); ecs_add(world, e, Tag); // to hit command batching test_int(ctor_position, 1); test_int(move_position, 0); test_int(copy_position, 1); test_int(dtor_position, 0); ecs_defer_end(world); test_int(ctor_position, 2); test_int(move_position, 1); test_int(copy_position, 1); test_int(dtor_position, 1); ecs_delete(world, e); test_int(ctor_position, 2); test_int(move_position, 1); test_int(copy_position, 1); test_int(dtor_position, 2); ecs_fini(world); } void ComponentLifecycle_batched_ensure_new_component_w_lifecycle(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .move = ecs_move(Position), .copy = ecs_copy(Position), .dtor = ecs_dtor(Position) }); ecs_defer_begin(world); ecs_entity_t e = ecs_new(world); ecs_ensure(world, e, Position); ecs_add(world, e, Tag); // to hit command batching test_int(ctor_position, 1); test_int(move_position, 0); test_int(copy_position, 0); test_int(dtor_position, 0); ecs_defer_end(world); test_int(ctor_position, 2); test_int(move_position, 1); test_int(copy_position, 0); test_int(dtor_position, 1); ecs_delete(world, e); test_int(ctor_position, 2); test_int(move_position, 1); test_int(copy_position, 0); test_int(dtor_position, 2); ecs_fini(world); } void ComponentLifecycle_on_nested_prefab_copy_test_invokes_copy_count(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_add_pair(world, ecs_id(Position), EcsOnInstantiate, EcsInherit); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .copy = comp_copy, .ctx = &ctx }); ecs_entity_t child = ecs_new_w(world, Position); ecs_add_id(world, child, ECS_AUTO_OVERRIDE | ecs_id(Position)); ecs_add_id(world, child, EcsPrefab); test_int(ctx.copy.invoked, 0); ecs_entity_t parent = ecs_entity(world, { .name = "parent"}); ecs_add_id(world, parent, EcsPrefab); ecs_entity_t child_e1 = ecs_entity(world, { .name = "e1"}); ecs_add_id(world, child_e1, EcsPrefab); ecs_add_pair(world, child_e1, EcsIsA, child); ecs_add_pair(world, child_e1, EcsChildOf, parent); test_int(ctx.copy.invoked, 1); ecs_entity_t child_e2 = ecs_entity(world, { .name = "e2"}); ecs_add_id(world, child_e2, EcsPrefab); ecs_add_pair(world, child_e2, EcsIsA, child); ecs_add_pair(world, child_e2, EcsChildOf, parent); test_int(ctx.copy.invoked, 2); ecs_entity_t child_e3 = ecs_entity(world, { .name = "e3"}); ecs_add_id(world, child_e3, EcsPrefab); ecs_add_pair(world, child_e3, EcsIsA, child); ecs_add_pair(world, child_e3, EcsChildOf, parent); test_int(ctx.copy.invoked, 3); ecs_entity_t a_parent = ecs_new_w_pair(world, EcsIsA, parent); (void)a_parent; test_int(ctx.copy.invoked, 7); test_int(ctx.copy.count, 9); ecs_fini(world); } // Tests if neither move nor move_ctor are set but move_dtor and ctor_move_dtor are set, // the move_dtor does not get invoked when a component is moved within the table. // Instead ctor_move_dtor should be invoked for a destructive move operation. // Hence in total ctor_move_dtor should be invoked 2 times. // Once for the initial move, and once for the move within the table. void ComponentLifecycle_no_move_no_move_ctor_with_move_dtor_with_ctor_move_dtor(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); cl_ctx ctx = { { 0 } }; ecs_set_hooks(world, Position, { .ctor = NULL, .move = NULL, .move_ctor = NULL, .dtor = comp_dtor, .ctor_move_dtor = comp_pos_ctor_move_dtor, .move_dtor = comp_move_dtor, .ctx = &ctx }); ecs_entity_t e = ecs_new(world); ecs_add(world, e, Position); ecs_entity_t e2 = ecs_new(world); ecs_add(world, e2, Position); ecs_entity_t e3 = ecs_new(world); ecs_add(world, e3, Position); ctx.ctor_move_dtor.invoked = 0; ctx.move_dtor.invoked = 0; ecs_add(world, e2, Velocity); test_int(ctx.move_dtor.invoked, 0); test_int(ctx.ctor_move_dtor.invoked, 2); ecs_fini(world); } void ComponentLifecycle_new_w_table_ctor(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, Bar); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position) }); ecs_table_t *table = ecs_table_add_id(world, NULL, ecs_id(Position)); table = ecs_table_add_id(world, table, Bar); test_int(ctor_position, 0); ecs_entity_t e = ecs_new_w_table(world, table); test_assert(e != 0); test_assert(ecs_has(world, e, Position)); test_assert(ecs_has(world, e, Bar)); test_assert(ecs_get(world, e, Position) != NULL); test_assert(table == ecs_get_table(world, e)); test_int(ctor_position, 1); ecs_fini(world); } void ComponentLifecycle_new_w_table_on_add_hook(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, Bar); ecs_set_hooks(world, Position, { .on_add = on_add_position_count }); ecs_table_t *table = ecs_table_add_id(world, NULL, ecs_id(Position)); table = ecs_table_add_id(world, table, Bar); test_int(ctor_position, 0); ecs_entity_t e = ecs_new_w_table(world, table); test_assert(e != 0); test_assert(ecs_has(world, e, Position)); test_assert(ecs_has(world, e, Bar)); test_assert(ecs_get(world, e, Position) != NULL); test_assert(table == ecs_get_table(world, e)); test_int(on_add_position, 1); ecs_fini(world); } static int hook_count = 0; static void hook_w_count(ecs_iter_t *it) { hook_count = ecs_count_id(it->world, ecs_field_id(it, 0)); test_int(hook_count, 1); } void ComponentLifecycle_count_in_on_add(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_add = hook_w_count }); ecs_entity_t e = ecs_new_w(world, Position); test_int(hook_count, 1); ecs_iter_t it = ecs_each(world, Position); test_bool(true, ecs_each_next(&it)); test_int(1, it.count); test_uint(e, it.entities[0]); test_bool(false, ecs_each_next(&it)); ecs_fini(world); } void ComponentLifecycle_count_in_on_remove(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_remove = hook_w_count }); ecs_entity_t e = ecs_new_w(world, Position); test_int(hook_count, 0); ecs_delete(world, e); test_int(hook_count, 1); ecs_iter_t it = ecs_each(world, Position); test_bool(false, ecs_each_next(&it)); ecs_fini(world); } void ComponentLifecycle_illegal_ctor(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .flags = ECS_TYPE_HOOK_CTOR_ILLEGAL }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.ctor != NULL); /* Make sure hook panics */ test_expect_abort(); ti->hooks.ctor(&(Position){10, 20}, 1, ti); ecs_fini(world); } void ComponentLifecycle_illegal_dtor(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .flags = ECS_TYPE_HOOK_DTOR_ILLEGAL }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.dtor != NULL); /* Make sure hook panics */ test_expect_abort(); ti->hooks.dtor(&(Position){10, 20}, 1, ti); ecs_fini(world); } void ComponentLifecycle_illegal_move(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .flags = ECS_TYPE_HOOK_MOVE_ILLEGAL }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.move != NULL); /* Make sure hook panics */ test_expect_abort(); ti->hooks.move(&(Position){10, 20}, &(Position){10, 20}, 1, ti); ecs_fini(world); } void ComponentLifecycle_illegal_copy(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.copy != NULL); /* Make sure hook panics */ test_expect_abort(); ti->hooks.copy(&(Position){10, 20}, &(Position){10, 20}, 1, ti); ecs_fini(world); } void ComponentLifecycle_illegal_move_ctor(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .flags = ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.move_ctor != NULL); /* Make sure hook panics */ test_expect_abort(); ti->hooks.move_ctor(&(Position){10, 20}, &(Position){10, 20}, 1, ti); ecs_fini(world); } void ComponentLifecycle_illegal_move_and_ctor(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .flags = ECS_TYPE_HOOK_CTOR_ILLEGAL|ECS_TYPE_HOOK_MOVE_ILLEGAL }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.ctor != NULL); test_assert(ti->hooks.move != NULL); /* Trivial move ctor hook allowed */ test_assert(ti->hooks.move_ctor == NULL); ecs_fini(world); } void ComponentLifecycle_illegal_copy_ctor(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .flags = ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.copy_ctor != NULL); /* Make sure hook panics */ test_expect_abort(); ti->hooks.copy_ctor(&(Position){10, 20}, &(Position){10, 20}, 1, ti); ecs_fini(world); } void ComponentLifecycle_illegal_copy_and_ctor(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .flags = ECS_TYPE_HOOK_CTOR_ILLEGAL|ECS_TYPE_HOOK_COPY_ILLEGAL }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.ctor != NULL); test_assert(ti->hooks.copy != NULL); /* Trivial copy ctor hook allowed */ test_assert(ti->hooks.copy_ctor == NULL); ecs_fini(world); } void ComponentLifecycle_illegal_ctor_w_ctor(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); test_expect_abort(); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .flags = ECS_TYPE_HOOK_CTOR_ILLEGAL }); } void ComponentLifecycle_illegal_dtor_w_dtor(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); test_expect_abort(); ecs_set_hooks(world, Position, { .dtor = ecs_dtor(Position), .flags = ECS_TYPE_HOOK_DTOR_ILLEGAL }); } void ComponentLifecycle_illegal_move_w_move(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); test_expect_abort(); ecs_set_hooks(world, Position, { .move = ecs_move(Position), .flags = ECS_TYPE_HOOK_MOVE_ILLEGAL }); } void ComponentLifecycle_illegal_copy_w_copy(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); test_expect_abort(); ecs_set_hooks(world, Position, { .copy = ecs_copy(Position), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); } void ComponentLifecycle_illegal_move_ctor_w_move_ctor(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); test_expect_abort(); ecs_set_hooks(world, Position, { .move_ctor = ecs_move(Position), .flags = ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL }); } void ComponentLifecycle_illegal_copy_ctor_w_copy_ctor(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); test_expect_abort(); ecs_set_hooks(world, Position, { .copy_ctor = ecs_copy(Position), .flags = ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL }); } void ComponentLifecycle_illegal_move_ctor_w_ctor_and_move(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .move = ecs_move(Position), .flags = ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.ctor != NULL); test_assert(ti->hooks.move != NULL); test_assert(ti->hooks.move_ctor != NULL); /* Make sure hook panics */ test_expect_abort(); ti->hooks.move_ctor(&(Position){10, 20}, &(Position){10, 20}, 1, ti); ecs_fini(world); } void ComponentLifecycle_illegal_copy_ctor_w_ctor_and_copy(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .copy = ecs_copy(Position), .flags = ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.ctor != NULL); test_assert(ti->hooks.copy != NULL); test_assert(ti->hooks.copy_ctor != NULL); /* Make sure hook panics */ test_expect_abort(); ti->hooks.copy_ctor(&(Position){10, 20}, &(Position){10, 20}, 1, ti); ecs_fini(world); } void ComponentLifecycle_ctor_flags(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.ctor != NULL); test_assert(ti->hooks.flags == (ECS_TYPE_HOOK_CTOR| ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS| ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL)); ecs_fini(world); } void ComponentLifecycle_dtor_flags(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .dtor = ecs_ctor(Position), }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.ctor != NULL); test_assert(ti->hooks.dtor != NULL); test_assert(ti->hooks.move_dtor != NULL); test_assert(ti->hooks.flags == (ECS_TYPE_HOOK_CTOR|ECS_TYPE_HOOK_DTOR|ECS_TYPE_HOOK_MOVE_DTOR| ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS| ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL)); ecs_fini(world); } void ComponentLifecycle_move_flags(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .move = ecs_move(Position), }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.move != NULL); test_assert(ti->hooks.flags == (ECS_TYPE_HOOK_CTOR|ECS_TYPE_HOOK_MOVE|ECS_TYPE_HOOK_MOVE_CTOR| ECS_TYPE_HOOK_CTOR_MOVE_DTOR|ECS_TYPE_HOOK_MOVE_DTOR| ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS| ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL)); ecs_fini(world); } void ComponentLifecycle_copy_flags(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .copy = ecs_copy(Position), }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.copy != NULL); test_assert(ti->hooks.flags == (ECS_TYPE_HOOK_CTOR|ECS_TYPE_HOOK_COPY|ECS_TYPE_HOOK_COPY_CTOR| ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS| ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL)); ecs_fini(world); } void ComponentLifecycle_ctor_move_dtor_flags(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .dtor = ecs_dtor(Position), .move = ecs_move(Position) }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.ctor != NULL); test_assert(ti->hooks.dtor != NULL); test_assert(ti->hooks.move != NULL); test_assert(ti->hooks.move_ctor != NULL); test_assert(ti->hooks.ctor_move_dtor != NULL); test_assert(ti->hooks.move_dtor != NULL); test_assert(ti->hooks.flags == (ECS_TYPE_HOOK_CTOR|ECS_TYPE_HOOK_DTOR|ECS_TYPE_HOOK_MOVE| ECS_TYPE_HOOK_MOVE_CTOR|ECS_TYPE_HOOK_CTOR_MOVE_DTOR|ECS_TYPE_HOOK_MOVE_DTOR| ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS| ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL)); ecs_fini(world); } void ComponentLifecycle_move_dtor_flags(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .dtor = ecs_dtor(Position), .move = ecs_move(Position) }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.dtor != NULL); test_assert(ti->hooks.move != NULL); test_assert(ti->hooks.move_dtor != NULL); test_assert(ti->hooks.flags == (ECS_TYPE_HOOK_CTOR|ECS_TYPE_HOOK_MOVE_CTOR|ECS_TYPE_HOOK_CTOR_MOVE_DTOR| ECS_TYPE_HOOK_DTOR|ECS_TYPE_HOOK_MOVE|ECS_TYPE_HOOK_MOVE_DTOR| ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS| ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL)); ecs_fini(world); } int compare_Position(const void *a, const void *b, const ecs_type_info_t *ti) { Position *pa = (Position*) a; Position *pb = (Position*) b; double ma = (pa->x) * (pa->x) + (pa->y) * (pa->y); double mb = (pb->x) * (pb->x) + (pb->y) * (pb->y); return mamb ? 1 : 0; } bool equals_Position(const void *a, const void *b, const ecs_type_info_t *ti) { Position *pa = (Position*) a; Position *pb = (Position*) b; return (pa->x == pb->x) && (pa->y == pb->y); } void ComponentLifecycle_cmp_flags(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .cmp = compare_Position, }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.cmp != NULL); /* equals is autogenerated from valid cmp */ test_assert(ti->hooks.equals != NULL); test_assert(ti->hooks.flags == (ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS)); ecs_fini(world); } void ComponentLifecycle_equals_flags(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .equals = equals_Position, }); const ecs_type_info_t *ti = ecs_get_type_info(world, ecs_id(Position)); test_assert(ti != NULL); test_assert(ti->hooks.equals != NULL); test_assert(ti->hooks.flags == ( ECS_TYPE_HOOK_CMP| ECS_TYPE_HOOK_EQUALS| ECS_TYPE_HOOK_CMP_ILLEGAL )); ecs_fini(world); } static int replace_Position_invoked = 0; static void replace_Position(ecs_iter_t *it) { Position *old = ecs_field(it, Position, 0); Position *new = ecs_field(it, Position, 1); ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(new != NULL, ECS_INTERNAL_ERROR, NULL); test_int(it->count, 1); switch(replace_Position_invoked) { case 0: test_int(old->x, 0); test_int(old->y, 0); test_int(new->x, 10); test_int(new->y, 20); break; case 1: test_int(old->x, 10); test_int(old->y, 20); test_int(new->x, 11); test_int(new->y, 21); break; default: test_assert(false); break; } replace_Position_invoked ++; } void ComponentLifecycle_on_replace_w_get_mut(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_replace = replace_Position, }); ecs_entity_t e = ecs_new(world); test_expect_abort(); ecs_get_mut(world, e, Position); } void ComponentLifecycle_on_replace_w_ensure(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_replace = replace_Position, }); ecs_entity_t e = ecs_new(world); test_expect_abort(); ecs_ensure(world, e, Position); } void ComponentLifecycle_on_replace_w_emplace(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .on_replace = replace_Position, }); ecs_entity_t e = ecs_new(world); test_expect_abort(); ecs_emplace(world, e, Position, NULL); } void ComponentLifecycle_on_replace_w_set(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = flecs_default_ctor, .on_replace = replace_Position, }); ecs_entity_t e = ecs_new(world); test_int(replace_Position_invoked, 0); ecs_set(world, e, Position, {10, 20}); test_int(replace_Position_invoked, 1); ecs_fini(world); } void ComponentLifecycle_on_replace_w_set_existing(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = flecs_default_ctor, .on_replace = replace_Position, }); ecs_entity_t e = ecs_new(world); test_int(replace_Position_invoked, 0); ecs_set(world, e, Position, {10, 20}); test_int(replace_Position_invoked, 1); ecs_set(world, e, Position, {11, 21}); test_int(replace_Position_invoked, 2); ecs_fini(world); } void ComponentLifecycle_on_replace_set_2_entities(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = flecs_default_ctor, .on_replace = replace_Position, }); ecs_entity_t e1 = ecs_new(world); test_int(replace_Position_invoked, 0); ecs_set(world, e1, Position, {10, 20}); test_int(replace_Position_invoked, 1); ecs_set(world, e1, Position, {11, 21}); test_int(replace_Position_invoked, 2); replace_Position_invoked = 0; ecs_entity_t e2 = ecs_new(world); test_int(replace_Position_invoked, 0); ecs_set(world, e2, Position, {10, 20}); test_int(replace_Position_invoked, 1); ecs_set(world, e2, Position, {11, 21}); test_int(replace_Position_invoked, 2); ecs_fini(world); } void ComponentLifecycle_copy_ctor_w_override(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .copy_ctor = ecs_copy(Position) }); test_int(ctor_position, 0); test_int(copy_position, 0); ecs_entity_t p = ecs_new_w_id(world, EcsPrefab); ecs_set(world, p, Position, {10, 20}); test_int(ctor_position, 1); test_int(copy_position, 0); ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, p); test_assert(ecs_has(world, i, Position)); { const Position *ptr = ecs_get(world, i, Position); test_assert(ptr != NULL); test_int(ptr->x, 10); test_int(ptr->y, 20); } test_int(ctor_position, 1); test_int(copy_position, 1); ecs_fini(world); } void ComponentLifecycle_copy_ctor_w_override_w_emplace(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_add_pair(world, ecs_id(Position), EcsOnInstantiate, EcsInherit); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .copy_ctor = ecs_copy(Position) }); test_int(ctor_position, 0); test_int(copy_position, 0); ecs_entity_t p = ecs_new_w_id(world, EcsPrefab); ecs_set(world, p, Position, {10, 20}); test_int(ctor_position, 1); test_int(copy_position, 0); ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, p); test_assert(ecs_has(world, i, Position)); { const Position *ptr = ecs_get(world, i, Position); test_assert(ptr != NULL); test_int(ptr->x, 10); test_int(ptr->y, 20); } test_int(ctor_position, 1); test_int(copy_position, 0); bool is_new = false; Position *ptr = ecs_emplace(world, i, Position, &is_new); test_assert(ptr != NULL); test_bool(is_new, true); test_assert(ecs_owns(world, i, Position)); test_int(ctor_position, 1); test_int(copy_position, 0); ecs_fini(world); } void ComponentLifecycle_copy_ctor_w_override_w_ensure(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_add_pair(world, ecs_id(Position), EcsOnInstantiate, EcsInherit); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .copy_ctor = ecs_copy(Position) }); test_int(ctor_position, 0); test_int(copy_position, 0); ecs_entity_t p = ecs_new_w_id(world, EcsPrefab); ecs_set(world, p, Position, {10, 20}); test_int(ctor_position, 1); test_int(copy_position, 0); ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, p); test_assert(ecs_has(world, i, Position)); { const Position *ptr = ecs_get(world, i, Position); test_assert(ptr != NULL); test_int(ptr->x, 10); test_int(ptr->y, 20); } test_int(ctor_position, 1); test_int(copy_position, 0); Position *ptr = ecs_ensure(world, i, Position); test_assert(ptr != NULL); test_assert(ecs_owns(world, i, Position)); test_int(ctor_position, 1); test_int(copy_position, 1); test_int(ptr->x, 10); test_int(ptr->y, 20); ecs_fini(world); } void ComponentLifecycle_shrink(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .move = ecs_move(Position), .copy = ecs_copy(Position), .dtor = ecs_dtor(Position) }); ecs_entity_t e1 = ecs_insert(world, ecs_value(Position, {11, 20})); test_int(ctor_position, 1); test_int(dtor_position, 0); test_int(move_position, 0); test_int(copy_position, 1); ecs_entity_t e2 = ecs_insert(world, ecs_value(Position, {12, 20})); test_int(ctor_position, 2); test_int(dtor_position, 0); test_int(move_position, 0); test_int(copy_position, 2); ecs_entity_t e3 = ecs_insert(world, ecs_value(Position, {13, 20})); test_int(ctor_position, 5); // grow test_int(dtor_position, 2); test_int(move_position, 2); test_int(copy_position, 3); ecs_entity_t e4 = ecs_insert(world, ecs_value(Position, {14, 20})); test_int(ctor_position, 6); test_int(dtor_position, 2); test_int(move_position, 2); test_int(copy_position, 4); ecs_delete(world, e4); ecs_delete(world, e3); ecs_delete(world, e2); test_int(ctor_position, 6); test_int(dtor_position, 5); test_int(move_position, 2); test_int(copy_position, 4); ctor_position = 0; dtor_position = 0; move_position = 0; copy_position = 0; test_int(ecs_table_size(ecs_get_table(world, e1)), 4); test_int(ecs_table_count(ecs_get_table(world, e1)), 1); ecs_shrink(world); test_int(ctor_position, 1); test_int(dtor_position, 1); test_int(move_position, 1); test_int(copy_position, 0); test_int(ecs_table_size(ecs_get_table(world, e1)), 1); test_int(ecs_table_count(ecs_get_table(world, e1)), 1); ecs_fini(world); } void ComponentLifecycle_dtor_after_add_exclusive_component(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_add_id(world, ecs_id(Position), EcsExclusive); ECS_COMPONENT(world, Velocity); ECS_COMPONENT(world, Mass); ECS_TAG(world, TgtA); ECS_TAG(world, TgtB); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .move = ecs_move(Position), .copy = ecs_copy(Position), .dtor = ecs_dtor(Position) }); ecs_entity_t e1 = ecs_insert(world, ecs_value_pair(Position, TgtB, {10, 20})); ecs_insert(world, ecs_value_pair(Position, TgtB, {20, 30})); ecs_set_pair(world, e1, Position, TgtA, {10, 20}); test_int(dtor_position, 1); ecs_fini(world); } void ComponentLifecycle_dtor_after_add_exclusive_component_last(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_add_id(world, ecs_id(Position), EcsExclusive); ECS_COMPONENT(world, Velocity); ECS_COMPONENT(world, Mass); ECS_TAG(world, TgtA); ECS_TAG(world, TgtB); ecs_set_hooks(world, Position, { .ctor = ecs_ctor(Position), .move = ecs_move(Position), .copy = ecs_copy(Position), .dtor = ecs_dtor(Position) }); ecs_entity_t e = ecs_new(world); ecs_set_pair(world, e, Position, TgtB, {10, 20}); ecs_set_pair(world, e, Position, TgtA, {10, 20}); test_int(dtor_position, 1); ecs_fini(world); }