#include static ECS_DECLARE(TagA); static ECS_DECLARE(TagB); ECS_COMPONENT_DECLARE(Position); ECS_COMPONENT_DECLARE(Velocity); static int sys_a_invoked; static int sys_b_invoked; static int sys_c_invoked; static int sys_d_invoked; static int sys_e_invoked; static int sys_f_invoked; static bool sys_a_real_world; static bool sys_b_real_world; static bool sys_c_real_world; static bool sys_d_real_world; static bool sys_e_real_world; static bool sys_f_real_world; static bool sys_a_world_readonly; static bool sys_b_world_readonly; static bool sys_c_world_readonly; static bool sys_d_world_readonly; static bool sys_e_world_readonly; static bool sys_f_world_readonly; static ecs_ftime_t sys_a_delta_time = 0; static ecs_ftime_t sys_b_delta_time = 0; void SysA(ecs_iter_t *it) { ecs_os_ainc(&sys_a_invoked); sys_a_real_world = it->world == it->real_world; sys_a_world_readonly = ecs_stage_is_readonly(it->real_world); sys_a_delta_time = it->delta_time; } void SysB(ecs_iter_t *it) { test_assert(sys_a_invoked != 0); ecs_os_ainc(&sys_b_invoked); sys_b_real_world = it->world == it->real_world; sys_b_world_readonly = ecs_stage_is_readonly(it->real_world); sys_b_delta_time = it->delta_time; } void SysC(ecs_iter_t *it) { test_assert(sys_b_invoked != 0); ecs_os_ainc(&sys_c_invoked); sys_c_real_world = it->world == it->real_world; sys_c_world_readonly = ecs_stage_is_readonly(it->real_world); } void SysD(ecs_iter_t *it) { test_assert(sys_c_invoked != 0); ecs_os_ainc(&sys_d_invoked); sys_d_real_world = it->world == it->real_world; sys_d_world_readonly = ecs_stage_is_readonly(it->real_world); } void SysE(ecs_iter_t *it) { test_assert(sys_d_invoked != 0); ecs_os_ainc(&sys_e_invoked); sys_e_real_world = it->world == it->real_world; sys_e_world_readonly = ecs_stage_is_readonly(it->real_world); } void SysF(ecs_iter_t *it) { test_assert(sys_d_invoked != 0); ecs_os_ainc(&sys_f_invoked); sys_f_real_world = it->world == it->real_world; sys_f_world_readonly = ecs_stage_is_readonly(it->real_world); } void Pipeline_system_order_same_phase(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); ECS_SYSTEM(world, SysC, EcsOnUpdate, Position); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 3); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_system_order_same_phase_after_disable(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); ECS_SYSTEM(world, SysC, EcsOnUpdate, Position); ecs_enable(world, SysB, false); test_assert( ecs_has_id(world, SysB, EcsDisabled)); ecs_enable(world, SysB, true); test_assert( !ecs_has_id(world, SysB, EcsDisabled)); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 3); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_system_order_different_phase(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysC, EcsPostUpdate, Position); ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); ECS_SYSTEM(world, SysA, EcsPreUpdate, Position); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 3); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_system_order_different_phase_after_disable(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysC, EcsPostUpdate, Position); ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); ECS_SYSTEM(world, SysA, EcsPreUpdate, Position); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_enable(world, SysB, false); test_assert( ecs_has_id(world, SysB, EcsDisabled)); ecs_enable(world, SysB, true); test_assert( !ecs_has_id(world, SysB, EcsDisabled)); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 3); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_system_order_same_phase_after_activate(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); ECS_SYSTEM(world, SysB, EcsOnUpdate, Velocity); /* System is deactivated */ ECS_SYSTEM(world, SysC, EcsOnUpdate, Position); const ecs_world_info_t *stats = ecs_get_world_info(world); test_assert( ecs_has_id(world, SysB, EcsEmpty)); ecs_add(world, E, Velocity); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 3); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); test_assert( !ecs_has_id(world, SysB, EcsEmpty)); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_system_order_different_phase_after_activate(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysC, EcsPostUpdate, Position); ECS_SYSTEM(world, SysB, EcsOnUpdate, Velocity); /* System is deactivated */ ECS_SYSTEM(world, SysA, EcsPreUpdate, Position); const ecs_world_info_t *stats = ecs_get_world_info(world); test_assert( ecs_has_id(world, SysB, EcsEmpty)); ecs_add(world, E, Velocity); ecs_progress(world, 1); test_assert( !ecs_has_id(world, SysB, EcsEmpty)); test_int(stats->systems_ran_frame, 3); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_system_order_after_new_system_lower_id(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position); ecs_entity_t Sys = ecs_new(world, 0); ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); ECS_SYSTEM(world, SysC, EcsOnUpdate, Position); const ecs_world_info_t *stats = ecs_get_world_info(world); /* Create new system with Sys id */ ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, {.id = Sys, .name = "SysA", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysA }); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 3); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_system_order_after_new_system_inbetween_id(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); ecs_entity_t Sys = ecs_new(world, 0); ECS_SYSTEM(world, SysC, EcsOnUpdate, Position); const ecs_world_info_t *stats = ecs_get_world_info(world); /* Create new system with Sys id */ ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, {.id = Sys, .name = "SysB", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysB }); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 3); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_system_order_after_new_system_higher_id(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); ecs_entity_t Sys = ecs_new(world, 0); const ecs_world_info_t *stats = ecs_get_world_info(world); /* Create new system with Sys id */ ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, {.id = Sys, .name = "SysC", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysC }); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 3); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } static int sys_out_invoked; static int sys_in_invoked; static void SysOut(ecs_iter_t *it) { ecs_id_t ecs_id(Velocity) = ecs_field_id(it, 2); sys_out_invoked ++; int i; for (i = 0; i < it->count; i ++) { ecs_set(it->world, it->entities[i], Velocity, {10, 20}); } } static void SysOutMain(ecs_iter_t *it) { Velocity *v = ecs_field(it, Velocity, 2); sys_out_invoked ++; int i; for (i = 0; i < it->count; i ++) { v[i].x = 10; v[i].y = 20; } } static void SysIn(ecs_iter_t *it) { ecs_id_t ecs_id(Velocity) = ecs_field_id(it, 1); test_assert(sys_out_invoked != 0); sys_in_invoked ++; int i; for (i = 0; i < it->count; i ++) { ecs_entity_t e = it->entities[i]; test_assert( ecs_has(it->world, e, Velocity)); const Velocity *v_ptr = ecs_get(it->world, e, Velocity); test_int(v_ptr->x, 10); test_int(v_ptr->y, 20); } } static void SysInMain(ecs_iter_t *it) { Velocity *v = ecs_field(it, Velocity, 1); ecs_id_t ecs_id(Velocity) = ecs_field_id(it, 1); test_assert(sys_out_invoked != 0); sys_in_invoked ++; int i; for (i = 0; i < it->count; i ++) { ecs_entity_t e = it->entities[i]; test_assert( ecs_has(it->world, e, Velocity)); test_int(v[i].x, 10); test_int(v[i].y, 20); } } void Pipeline_merge_after_staged_out(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysOut, EcsOnUpdate, Position, [out] Velocity()); ECS_SYSTEM(world, SysInMain, EcsOnUpdate, Velocity); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 2); test_int(stats->merge_count_total, 2); test_int(stats->pipeline_build_count_total, 2); test_int(sys_out_invoked, 1); test_int(sys_in_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 2); ecs_fini(world); } void Pipeline_merge_after_not_out(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysOut, EcsOnUpdate, Position, [out] !Velocity); ECS_SYSTEM(world, SysInMain, EcsOnUpdate, Velocity); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 2); test_int(stats->merge_count_total, 2); test_int(stats->pipeline_build_count_total, 2); test_int(sys_out_invoked, 1); test_int(sys_in_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 2); ecs_fini(world); } void Pipeline_no_merge_after_main_out(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position, Velocity); ECS_SYSTEM(world, SysOutMain, EcsOnUpdate, Position, Velocity); ECS_SYSTEM(world, SysInMain, EcsOnUpdate, Velocity); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 2); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); test_int(sys_out_invoked, 1); test_int(sys_in_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_merge_after_staged_in_out(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position, Velocity); /* Requires merge, because getting value in 2nd system cannot access data * written to stage from first system */ ECS_SYSTEM(world, SysOut, EcsOnUpdate, Position, [out] Velocity()); ECS_SYSTEM(world, SysIn, EcsOnUpdate, [in] Velocity()); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 2); test_int(stats->merge_count_total, 2); test_int(stats->pipeline_build_count_total, 1); test_int(sys_out_invoked, 1); test_int(sys_in_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_merge_after_staged_inout_main_implicit_inout(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysA, EcsOnUpdate, Position, Velocity()); ECS_SYSTEM(world, SysB, EcsOnUpdate, Velocity); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 1); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_merge_after_staged_inout_main_inout(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position, Velocity); ECS_SYSTEM(world, SysOut, EcsOnUpdate, Position, [inout] Velocity()); ECS_SYSTEM(world, SysIn, EcsOnUpdate, Velocity); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 2); test_int(stats->merge_count_total, 2); test_int(stats->pipeline_build_count_total, 1); test_int(sys_out_invoked, 1); test_int(sys_in_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 1); ecs_fini(world); } void Pipeline_merge_after_staged_out_before_owned(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysOut, EcsOnUpdate, Position, [out] Velocity()); ECS_SYSTEM(world, SysInMain, EcsOnUpdate, Velocity); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_progress(world, 1); test_int(stats->systems_ran_frame, 2); test_int(stats->merge_count_total, 2); test_int(stats->pipeline_build_count_total, 2); test_int(sys_out_invoked, 1); test_int(sys_in_invoked, 1); ecs_progress(world, 1); test_int(stats->pipeline_build_count_total, 2); ecs_fini(world); } void Pipeline_switch_pipeline(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); ECS_SYSTEM(world, SysC, EcsOnUpdate, Position); ecs_add(world, ecs_id(SysC), Tag); ECS_PIPELINE(world, P1, flecs.system.System, flecs.pipeline.Phase(cascade(DependsOn)), !Tag); ecs_progress(world, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_set_pipeline(world, P1); ecs_progress(world, 1); test_int(sys_a_invoked, 2); test_int(sys_b_invoked, 2); test_int(sys_c_invoked, 1); ecs_fini(world); } void Pipeline_run_pipeline(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); ECS_SYSTEM(world, SysC, EcsPostUpdate, Position); ecs_add(world, ecs_id(SysC), Tag); ECS_PIPELINE(world, P1, flecs.system.System, flecs.pipeline.Phase(cascade(DependsOn)), !Tag); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_progress(world, 0); test_int(stats->systems_ran_frame, 3); test_int(stats->merge_count_total, 1); test_int(stats->pipeline_build_count_total, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_run_pipeline(world, P1, 1); test_int(stats->pipeline_build_count_total, 2); test_int(sys_a_invoked, 2); test_int(sys_b_invoked, 2); test_int(sys_c_invoked, 1); ecs_fini(world); } void Pipeline_get_pipeline_from_stage(void) { ecs_world_t *world = ecs_init(); ecs_entity_t pipeline = ecs_get_pipeline(world); test_assert(pipeline != 0); /* Get default stage */ ecs_world_t *stage = ecs_get_stage(world, 0); test_assert(stage != NULL); test_assert(pipeline == ecs_get_pipeline(stage)); ecs_fini(world); } void Pipeline_3_systems_3_types(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysA", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysA }); ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = NULL, .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysB }); ecs_entity_t s3 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysC", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position()", .callback = SysC }); test_assert(s1 != 0); test_assert(s2 != 0); test_assert(s3 != 0); ecs_add_id(world, s3, Tag); ecs_new(world, Position); ecs_progress(world, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_fini(world); } static void RandomWrite(ecs_iter_t *it) { ecs_entity_t ecs_id(Position) = ecs_field_id(it, 2); int i; for (i = 0; i < it->count; i ++) { ecs_set(it->world, it->entities[i], Position, {1, 2}); } } static void RandomRead(ecs_iter_t *it) { ecs_entity_t ecs_id(Position) = ecs_lookup(it->world, "Position"); int i; for (i = 0; i < it->count; i ++) { const Position *p = ecs_get(it->world, it->entities[i], Position); test_assert(p != NULL); test_int(p->x, 1); test_int(p->y, 2); } } static void RandomReadWrite(ecs_iter_t *it) { ecs_entity_t ecs_id(Position) = ecs_lookup(it->world, "Position"); int i; for (i = 0; i < it->count; i ++) { const Position *p = ecs_get(it->world, it->entities[i], Position); test_assert(p != NULL); test_int(p->x, 1); test_int(p->y, 2); ecs_set(it->world, it->entities[i], Position, {p->x + 1, p->y + 1}); } } static void RandomReadAfterRW(ecs_iter_t *it) { ecs_entity_t ecs_id(Position) = ecs_lookup(it->world, "Position"); int i; for (i = 0; i < it->count; i ++) { const Position *p = ecs_get(it->world, it->entities[i], Position); test_assert(p != NULL); test_int(p->x, 2); test_int(p->y, 3); } } static void RandomRead_Not(ecs_iter_t *it) { ecs_entity_t ecs_id(Position) = ecs_field_id(it, 2); int i; for (i = 0; i < it->count; i ++) { test_assert(!ecs_has(it->world, it->entities[i], Position)); const Position *p = ecs_get(it->world, it->entities[i], Position); test_assert(p == NULL); } } void Pipeline_random_read_after_random_write_out_in(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] Position()); ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [in] Position()); ecs_entity_t e = ecs_new(world, Tag); ecs_progress(world, 1); test_assert(ecs_has(world, e, Position)); const Position *p = ecs_get(world, e, Position); test_int(p->x, 1); test_int(p->y, 2); ecs_fini(world); } void Pipeline_random_read_after_random_write_inout_in(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [inout] Position()); ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [in] Position()); ecs_entity_t e = ecs_new(world, Tag); ecs_progress(world, 1); test_assert(ecs_has(world, e, Position)); const Position *p = ecs_get(world, e, Position); test_int(p->x, 1); test_int(p->y, 2); ecs_fini(world); } void Pipeline_random_read_after_random_write_out_inout(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] Position()); ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [inout] Position()); ecs_entity_t e = ecs_new(world, Tag); ecs_progress(world, 1); test_assert(ecs_has(world, e, Position)); const Position *p = ecs_get(world, e, Position); test_int(p->x, 1); test_int(p->y, 2); ecs_fini(world); } void Pipeline_random_read_after_random_write_inout_inout(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [inout] Position()); ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [inout] Position()); ecs_entity_t e = ecs_new(world, Tag); ecs_progress(world, 1); test_assert(ecs_has(world, e, Position)); const Position *p = ecs_get(world, e, Position); test_int(p->x, 1); test_int(p->y, 2); ecs_fini(world); } void Pipeline_random_read_after_random_write_w_not_write(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] !Position); ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [in] Position()); ecs_entity_t e = ecs_new(world, Tag); ecs_progress(world, 1); test_assert(ecs_has(world, e, Position)); const Position *p = ecs_get(world, e, Position); test_int(p->x, 1); test_int(p->y, 2); ecs_fini(world); } void Pipeline_random_read_after_random_write_w_not_read(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] Position()); ECS_SYSTEM(world, RandomRead_Not, EcsOnUpdate, Tag, [in] !Position); ecs_entity_t e = ecs_new(world, Tag); ecs_progress(world, 1); test_assert(ecs_has(world, e, Position)); const Position *p = ecs_get(world, e, Position); test_int(p->x, 1); test_int(p->y, 2); ecs_fini(world); } void Pipeline_random_read_after_random_write_w_wildcard(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] Position()); ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [in] *()); ecs_entity_t e = ecs_new(world, Tag); ecs_progress(world, 1); test_assert(ecs_has(world, e, Position)); const Position *p = ecs_get(world, e, Position); test_int(p->x, 1); test_int(p->y, 2); ecs_fini(world); } void Pipeline_random_in_after_random_inout_after_random_out(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] Position()); ECS_SYSTEM(world, RandomReadWrite, EcsOnUpdate, Tag, [inout] Position()); ECS_SYSTEM(world, RandomReadAfterRW, EcsOnUpdate, Tag, [in] Position()); ecs_entity_t e = ecs_new(world, Tag); ecs_progress(world, 1); test_assert(ecs_has(world, e, Position)); const Position *p = ecs_get(world, e, Position); test_int(p->x, 2); test_int(p->y, 3); ecs_fini(world); } static void cb_first(ecs_iter_t *it) { int32_t *count = it->ctx; test_int(count[0], 0); count[0] ++; } static void cb_second(ecs_iter_t *it) { int32_t *count = it->ctx; test_int(count[0], 1); count[0] ++; } static void cb_third(ecs_iter_t *it) { int32_t *count = it->ctx; test_int(count[0], 2); count[0] ++; } void Pipeline_system_reverse_order_by_phase_custom_pipeline(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ecs_entity_t PreFrame = ecs_new_w_id(world, EcsPhase); ecs_entity_t OnFrame = ecs_new_w_id(world, EcsPhase); ecs_entity_t PostFrame = ecs_new_w_id(world, EcsPhase); ecs_add_pair(world, OnFrame, EcsDependsOn, PreFrame); ecs_add_pair(world, PostFrame, EcsDependsOn, OnFrame); ECS_PIPELINE(world, P, flecs.system.System, flecs.pipeline.Phase(cascade(DependsOn)), Tag); int count = 0; ecs_system_init(world, &(ecs_system_desc_t){ .callback = cb_third, .ctx = &count, .entity = ecs_entity(world, {.add = {Tag, ecs_pair(EcsDependsOn, PostFrame)}}) }); ecs_system_init(world, &(ecs_system_desc_t){ .callback = cb_second, .ctx = &count, .entity = ecs_entity(world, {.add = {Tag, ecs_pair(EcsDependsOn, OnFrame)}}) }); ecs_system_init(world, &(ecs_system_desc_t){ .callback = cb_first, .ctx = &count, .entity = ecs_entity(world, {.add = {Tag, ecs_pair(EcsDependsOn, PreFrame)}}) }); test_int(count, 0); ecs_set_pipeline(world, P); ecs_progress(world, 0); test_int(count, 3); ecs_fini(world); } void Pipeline_stage_write_before_read(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysA", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysA }); ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysB", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "[out] Position(), Position", .callback = SysB }); ecs_entity_t s3 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysC", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysC }); ECS_PIPELINE(world, P, flecs.system.System, flecs.pipeline.Phase(cascade(DependsOn)), Tag); ecs_set_pipeline(world, P); test_assert(s1 != 0); test_assert(s2 != 0); test_assert(s3 != 0); ecs_new(world, Position); ecs_progress(world, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_pipeline_stats_t stats = {0}; test_bool(ecs_pipeline_stats_get(world, ecs_get_pipeline(world), &stats), true); test_int(ecs_vec_count(&stats.systems), 5); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[0], s1); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[1], s2); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[2], 0); /* merge */ test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[3], s3); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[4], 0); /* merge */ ecs_pipeline_stats_fini(&stats); ecs_fini(world); } static void Pipeline_mixed_multithreaded_internal(bool task_threads) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysA", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysA, .multi_threaded = true }); ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysB", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysB }); ecs_entity_t s3 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysC", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysC }); ecs_entity_t s4 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysD", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysD, .multi_threaded = true }); ecs_entity_t s5 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysE", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysE, .multi_threaded = true }); ecs_entity_t s6 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysF", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysF }); ECS_PIPELINE(world, P, flecs.system.System, flecs.pipeline.Phase(cascade(DependsOn)), Tag); ecs_set_pipeline(world, P); test_assert(s1 != 0); test_assert(s2 != 0); test_assert(s3 != 0); test_assert(s4 != 0); test_assert(s5 != 0); test_assert(s6 != 0); ecs_new(world, Position); ecs_new(world, Position); if (task_threads) { ecs_set_task_threads(world, 2); } else { ecs_set_threads(world, 2); } ecs_progress(world, 1); test_int(sys_a_invoked, 2); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); test_int(sys_d_invoked, 2); test_int(sys_e_invoked, 2); test_int(sys_f_invoked, 1); ecs_pipeline_stats_t stats = {0}; test_bool(ecs_pipeline_stats_get(world, ecs_get_pipeline(world), &stats), true); test_int(ecs_vec_count(&stats.systems), 10); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[0], s1); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[1], 0); /* merge */ test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[2], s2); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[3], s3); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[4], 0); /* merge */ test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[5], s4); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[6], s5); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[7], 0); /* merge */ test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[8], s6); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[9], 0); /* merge */ ecs_pipeline_stats_fini(&stats); ecs_fini(world); } void Pipeline_mixed_multithreaded(void) { Pipeline_mixed_multithreaded_internal(false); } void Pipeline_mixed_multithreaded_tasks(void) { Pipeline_mixed_multithreaded_internal(true); } void Pipeline_mixed_staging(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysA", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysA, .no_readonly = true }); ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysB", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysB }); ecs_entity_t s3 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysC", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysC }); ecs_entity_t s4 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysD", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysD, .no_readonly = true }); ecs_entity_t s5 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysE", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysE, .no_readonly = true }); ecs_entity_t s6 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysF", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysF }); ECS_PIPELINE(world, P, flecs.system.System, flecs.pipeline.Phase(cascade(DependsOn)), Tag); ecs_set_pipeline(world, P); test_assert(s1 != 0); test_assert(s2 != 0); test_assert(s3 != 0); test_assert(s4 != 0); test_assert(s5 != 0); test_assert(s6 != 0); ecs_new(world, Position); ecs_new(world, Position); ecs_progress(world, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); test_int(sys_d_invoked, 1); test_int(sys_e_invoked, 1); test_int(sys_f_invoked, 1); test_int(sys_a_real_world, true); test_int(sys_b_real_world, false); test_int(sys_c_real_world, false); test_int(sys_d_real_world, true); test_int(sys_e_real_world, true); test_int(sys_f_real_world, false); test_int(sys_a_world_readonly, false); test_int(sys_b_world_readonly, true); test_int(sys_c_world_readonly, true); test_int(sys_d_world_readonly, false); test_int(sys_e_world_readonly, false); test_int(sys_f_world_readonly, true); ecs_progress(world, 1); test_int(sys_a_invoked, 2); test_int(sys_b_invoked, 2); test_int(sys_c_invoked, 2); test_int(sys_d_invoked, 2); test_int(sys_e_invoked, 2); test_int(sys_f_invoked, 2); test_int(sys_a_real_world, true); test_int(sys_b_real_world, false); test_int(sys_c_real_world, false); test_int(sys_d_real_world, true); test_int(sys_e_real_world, true); test_int(sys_f_real_world, false); test_int(sys_a_world_readonly, false); test_int(sys_b_world_readonly, true); test_int(sys_c_world_readonly, true); test_int(sys_d_world_readonly, false); test_int(sys_e_world_readonly, false); test_int(sys_f_world_readonly, true); ecs_pipeline_stats_t stats = {0}; test_bool(ecs_pipeline_stats_get(world, ecs_get_pipeline(world), &stats), true); test_int(ecs_vec_count(&stats.systems), 11); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[0], s1); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[1], 0); /* merge */ test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[2], s2); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[3], s3); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[4], 0); /* merge */ test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[5], s4); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[6], 0); /* merge */ test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[7], s5); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[8], 0); /* merge */ test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[9], s6); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[10], 0); /* merge */ ecs_pipeline_stats_fini(&stats); ecs_fini(world); } static void WritePosition(ecs_iter_t *it) { if (*(bool*)it->ctx) { ecs_entity_t ecs_id(Position) = ecs_field_id(it, 2); for (int i = 0; i < it->count; i ++) { ecs_add(it->world, it->entities[i], Position); } } } void Pipeline_single_threaded_pipeline_change(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); bool write_position = false; ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysA", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Tag, !Position", .callback = SysA }); ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysB", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Tag, Position", .callback = SysB }); ecs_entity_t s3 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "WritePosition", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Tag, [out] Position()", .callback = WritePosition, .ctx = &write_position }); ecs_entity_t s4 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysC", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Tag, Position", .callback = SysC }); ecs_entity_t s5 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysD", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Tag, !Position", .callback = SysD }); /* Initialize to 1 so asserts inside system won't trigger */ sys_a_invoked = 1; sys_b_invoked = 1; sys_c_invoked = 1; sys_d_invoked = 1; ecs_new(world, Tag); ecs_new(world, Tag); test_assert(s1 != 0); test_assert(s2 != 0); test_assert(s3 != 0); test_assert(s4 != 0); test_assert(s5 != 0); ecs_progress(world, 0); ecs_progress(world, 0); ecs_progress(world, 0); test_int(sys_a_invoked, 1 + 3); test_int(sys_b_invoked, 1 + 0); test_int(sys_c_invoked, 1 + 0); test_int(sys_d_invoked, 1 + 3); write_position = true; ecs_progress(world, 0); test_int(sys_a_invoked, 1 + 4); test_int(sys_b_invoked, 1 + 0); test_int(sys_c_invoked, 1 + 1); test_int(sys_d_invoked, 1 + 3); ecs_progress(world, 0); test_int(sys_a_invoked, 1 + 4); test_int(sys_b_invoked, 1 + 1); test_int(sys_c_invoked, 1 + 2); test_int(sys_d_invoked, 1 + 3); ecs_fini(world); } static void Pipeline_multi_threaded_pipeline_change_internal(bool task_threads) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); bool write_position = false; ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysA", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Tag, !Position", .callback = SysA, .multi_threaded = true }); ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysB", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Tag, Position, [out] Position()", .callback = SysB, .multi_threaded = true }); ecs_entity_t s3 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "WritePosition", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Tag, [out] Position(), [in] ?Position", .callback = WritePosition, .ctx = &write_position, .multi_threaded = true }); ecs_entity_t s4 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysC", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Tag, Position", .callback = SysC, .multi_threaded = true }); ecs_entity_t s5 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysD", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Tag, !Position", .callback = SysD, .multi_threaded = true }); if (task_threads) { ecs_set_task_threads(world, 2); } else { ecs_set_threads(world, 2); } /* Initialize to 1 so asserts inside system won't trigger */ sys_a_invoked = 1; sys_b_invoked = 1; sys_c_invoked = 1; sys_d_invoked = 1; ecs_new(world, Tag); ecs_new(world, Tag); test_assert(s1 != 0); test_assert(s2 != 0); test_assert(s3 != 0); test_assert(s4 != 0); test_assert(s5 != 0); ecs_progress(world, 0); ecs_progress(world, 0); ecs_progress(world, 0); test_int(sys_a_invoked, 1 + 3 * 2); test_int(sys_b_invoked, 1 + 0 * 2); test_int(sys_c_invoked, 1 + 0 * 2); test_int(sys_d_invoked, 1 + 3 * 2); write_position = true; ecs_progress(world, 0); test_int(sys_a_invoked, 1 + 4 * 2); test_int(sys_b_invoked, 1 + 0 * 2); test_int(sys_c_invoked, 1 + 1 * 2); test_int(sys_d_invoked, 1 + 3 * 2); ecs_progress(world, 0); test_int(sys_a_invoked, 1 + 4 * 2); test_int(sys_b_invoked, 1 + 1 * 2); test_int(sys_c_invoked, 1 + 2 * 2); test_int(sys_d_invoked, 1 + 3 * 2); ecs_fini(world); } void Pipeline_multi_threaded_pipeline_change(void) { Pipeline_multi_threaded_pipeline_change_internal(false); } void Pipeline_multi_threaded_pipeline_change_tasks(void) { Pipeline_multi_threaded_pipeline_change_internal(true); } void Pipeline_activate_after_add(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); bool write_position = false; ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "WritePosition", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Tag, [out] Position()", .callback = WritePosition, .ctx = &write_position }); ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysA", .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysA }); ecs_new(world, Tag); test_assert(s1 != 0); test_assert(s2 != 0); ecs_progress(world, 0); ecs_progress(world, 0); ecs_progress(world, 0); test_int(sys_a_invoked, 0); write_position = true; ecs_progress(world, 0); test_int(sys_a_invoked, 1); ecs_fini(world); } static ecs_query_t *q_result; static void CreateQuery(ecs_iter_t *it) { test_assert(it->real_world == it->world); q_result = ecs_query_new(it->world, "Position"); } void Pipeline_no_staging_system_create_query(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ecs_entity_t s = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "CreateQuery", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .callback = CreateQuery, .no_readonly = true }); ECS_PIPELINE(world, P, flecs.system.System, flecs.pipeline.Phase(cascade(DependsOn)), Tag); ecs_set_pipeline(world, P); test_assert(s != 0); ecs_progress(world, 1.0); test_assert(q_result != NULL); ecs_pipeline_stats_t stats = {0}; test_bool(ecs_pipeline_stats_get(world, ecs_get_pipeline(world), &stats), true); test_int(ecs_vec_count(&stats.systems), 2); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[0], s); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[1], 0); /* merge */ ecs_pipeline_stats_fini(&stats); ecs_fini(world); } static int set_singleton_invoked = 0; static int match_singleton_invoked = 0; static int match_all_invoked = 0; static void set_singleton(ecs_iter_t *it) { ecs_singleton_add(it->world, TagB); set_singleton_invoked ++; } static void match_singleton(ecs_iter_t *it) { match_singleton_invoked ++; } static void match_all(ecs_iter_t *it) { match_all_invoked ++; } void Pipeline_match_all_after_pipeline_rebuild(void) { ecs_world_t *world = ecs_init(); ECS_TAG_DEFINE(world, TagA); ECS_TAG_DEFINE(world, TagB); ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "[out] TagB()", .callback = set_singleton }); ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "TagB", .callback = match_singleton }); ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .add = {ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "?TagA", .callback = match_all }); ecs_progress(world, 0); test_int(set_singleton_invoked, 1); test_int(match_singleton_invoked, 1); test_assert(match_all_invoked >= 1); ecs_fini(world); } void Pipeline_empty_pipeline(void) { ecs_world_t *world = ecs_mini(); ECS_IMPORT(world, FlecsPipeline); const ecs_world_info_t *info = ecs_get_world_info(world); test_int(info->frame_count_total, 0); ecs_progress(world, 0); test_int(info->frame_count_total, 1); ecs_fini(world); } void Pipeline_custom_pipeline_w_system_macro(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ECS_ENTITY(world, E, Position); ECS_PIPELINE(world, P, flecs.system.System, Tag); ECS_SYSTEM(world, SysA, Tag, Position); ECS_SYSTEM(world, SysB, Tag, Position); ECS_SYSTEM(world, SysC, Tag, Position); ecs_set_pipeline(world, P); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); ecs_fini(world); } void Pipeline_pipeline_w_short_notation(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ecs_entity_t p = ecs_pipeline(world, { .query.filter.expr = "flecs.system.System, Tag" }); test_assert(p != 0); ecs_fini(world); } void Pipeline_stack_allocator_after_progress(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); ecs_filter_t *f = ecs_filter(world, { .terms = {{ ecs_id(Position) }} }); ecs_iter_t it = ecs_filter_iter(world, f); ecs_stack_cursor_t cursor = *it.priv.cache.stack_cursor; ecs_iter_fini(&it); ecs_progress(world, 1); test_int(sys_a_invoked, 1); it = ecs_filter_iter(world, f); test_assert(it.priv.cache.stack_cursor->page == cursor.page); test_assert(it.priv.cache.stack_cursor->sp == cursor.sp); ecs_iter_fini(&it); ecs_filter_fini(f); ecs_fini(world); } void Pipeline_stack_allocator_after_progress_w_pipeline_change(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_ENTITY(world, E, Position); ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); ecs_filter_t *f = ecs_filter(world, { .terms = {{ ecs_id(Position) }} }); ecs_iter_t it = ecs_filter_iter(world, f); ecs_stack_cursor_t cursor = *it.priv.cache.stack_cursor; ecs_iter_fini(&it); ecs_progress(world, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); it = ecs_filter_iter(world, f); test_assert(it.priv.cache.stack_cursor->page == cursor.page); test_assert(it.priv.cache.stack_cursor->sp == cursor.sp); ecs_iter_fini(&it); ecs_enable(world, SysB, false); ecs_progress(world, 1); test_int(sys_a_invoked, 2); test_int(sys_b_invoked, 1); it = ecs_filter_iter(world, f); test_assert(it.priv.cache.stack_cursor->page == cursor.page); test_assert(it.priv.cache.stack_cursor->sp == cursor.sp); ecs_iter_fini(&it); ecs_filter_fini(f); ecs_fini(world); } static void Sys_w_MainWorldIter(ecs_iter_t *it) { ecs_id_t ecs_id(Position) = ecs_field_id(it, 1); ecs_filter_t *f = ecs_filter(it->real_world, { .terms = {{ ecs_id(Position) }} }); ecs_iter_t fit = ecs_filter_iter(it->real_world, f); test_bool(true, ecs_filter_next(&fit)); test_int(1, fit.count); test_bool(false, ecs_filter_next(&fit)); ecs_filter_fini(f); } static void Pipeline_iter_from_world_in_singlethread_system_multitead_app_internal(bool task_threads) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ecs_new(world, Position); ECS_SYSTEM(world, Sys_w_MainWorldIter, EcsOnUpdate, Position()); if (task_threads) { ecs_set_task_threads(world, 2); } else { ecs_set_threads(world, 2); } ecs_progress(world, 0); ecs_fini(world); } void Pipeline_iter_from_world_in_singlethread_system_multitead_app(void) { Pipeline_iter_from_world_in_singlethread_system_multitead_app_internal(false); } void Pipeline_iter_from_world_in_singlethread_system_multitead_app_tasks(void) { Pipeline_iter_from_world_in_singlethread_system_multitead_app_internal(true); } static int staging_system_invoked = 0; static void StagingSystem(ecs_iter_t *it) { test_assert( ecs_stage_is_readonly(it->real_world)); staging_system_invoked ++; } static int no_staging_system_invoked = 0; static void NoStagingSystem(ecs_iter_t *it) { test_assert( !ecs_stage_is_readonly(it->real_world)); no_staging_system_invoked ++; } void Pipeline_no_staging_after_inactive_system(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .query.filter.terms = {{ Tag, .oper = EcsNot }}, .callback = StagingSystem }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .query.filter.terms = {{ ecs_id(Position) }, { Tag, .oper = EcsNot }}, .callback = StagingSystem }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .callback = NoStagingSystem, .no_readonly = true }); ecs_progress(world, 0); test_assert(staging_system_invoked != 0); test_int(no_staging_system_invoked, 1); staging_system_invoked = 0; no_staging_system_invoked = 0; ecs_progress(world, 0); test_assert(staging_system_invoked != 0); test_int(no_staging_system_invoked, 1); ecs_fini(world); } static ecs_entity_t create_position_e = 0; static ecs_entity_t create_velocity_e = 0; static int no_staging_create_position_invoked = 0; static int no_staging_create_velocity_invoked = 0; static void NoStagingSystemCreatePosition(ecs_iter_t *it) { ecs_defer_end(it->world); create_position_e = ecs_new_id(it->world); ecs_set(it->world, create_position_e, Position, {0, 0}); ecs_filter_t *f = ecs_filter(it->world, { .terms = {{ ecs_id(Position) }} }); ecs_iter_t fit = ecs_filter_iter(it->world, f); test_bool(true, ecs_filter_next(&fit)); test_int(fit.count, 1); test_uint(fit.entities[0], create_position_e); test_bool(false, ecs_filter_next(&fit)); ecs_filter_fini(f); ecs_defer_begin(it->world); no_staging_create_position_invoked ++; } static void NoStagingSystemCreateVelocity(ecs_iter_t *it) { ecs_defer_end(it->world); create_velocity_e = ecs_new_id(it->world); ecs_set(it->world, create_velocity_e, Velocity, {0, 0}); ecs_filter_t *f = ecs_filter(it->world, { .terms = {{ ecs_id(Velocity) }} }); ecs_iter_t fit = ecs_filter_iter(it->world, f); test_bool(true, ecs_filter_next(&fit)); test_int(fit.count, 1); test_uint(fit.entities[0], create_velocity_e); test_bool(false, ecs_filter_next(&fit)); ecs_filter_fini(f); ecs_defer_begin(it->world); no_staging_create_velocity_invoked ++; } static int32_t read_position_invoked = 0; static void ReadPosition(ecs_iter_t *it) { ecs_os_ainc(&read_position_invoked); } static int32_t read_velocity_invoked = 0; static void ReadVelocity(ecs_iter_t *it) { ecs_os_ainc(&read_velocity_invoked); } void Pipeline_inactive_system_after_no_staging_system_no_defer_w_filter(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT_DEFINE(world, Position); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .callback = NoStagingSystemCreatePosition, .no_readonly = true }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .query.filter.terms = {{ ecs_id(Position) }}, .callback = ReadPosition }); ecs_progress(world, 0); test_int(no_staging_create_position_invoked, 1); test_int(read_position_invoked, 1); test_assert(create_position_e != 0); test_assert(ecs_has(world, create_position_e, Position)); ecs_delete(world, create_position_e); ecs_progress(world, 0); test_int(no_staging_create_position_invoked, 2); test_int(read_position_invoked, 2); ecs_fini(world); } void Pipeline_inactive_system_after_no_staging_system_no_defer_w_filter_w_no_staging_at_end(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT_DEFINE(world, Position); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .callback = NoStagingSystemCreatePosition, .no_readonly = true }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .query.filter.terms = {{ ecs_id(Position) }}, .callback = ReadPosition }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .callback = SysA, .no_readonly = true }); ecs_progress(world, 0); test_int(no_staging_create_position_invoked, 1); test_int(read_position_invoked, 1); test_assert(create_position_e != 0); test_assert(ecs_has(world, create_position_e, Position)); ecs_delete(world, create_position_e); ecs_progress(world, 0); test_int(no_staging_create_position_invoked, 2); test_int(read_position_invoked, 2); ecs_fini(world); } void Pipeline_inactive_system_after_2_no_staging_system_no_defer_w_filter(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT_DEFINE(world, Position); ECS_COMPONENT_DEFINE(world, Velocity); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .callback = NoStagingSystemCreatePosition, .no_readonly = true }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .callback = NoStagingSystemCreateVelocity, .no_readonly = true }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .query.filter.terms = {{ ecs_id(Position) }}, .callback = ReadPosition }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .query.filter.terms = {{ ecs_id(Velocity) }}, .callback = ReadVelocity }); ecs_progress(world, 0); test_int(no_staging_create_position_invoked, 1); test_int(no_staging_create_velocity_invoked, 1); test_int(read_position_invoked, 1); test_int(read_velocity_invoked, 1); test_assert(create_position_e != 0); test_assert(ecs_has(world, create_position_e, Position)); ecs_delete(world, create_position_e); test_assert(create_velocity_e != 0); test_assert(ecs_has(world, create_velocity_e, Velocity)); ecs_delete(world, create_velocity_e); ecs_progress(world, 0); test_int(no_staging_create_position_invoked, 2); test_int(no_staging_create_velocity_invoked, 2); test_int(read_position_invoked, 2); test_int(read_velocity_invoked, 2); ecs_fini(world); } static void Pipeline_inactive_multithread_system_after_no_staging_system_no_defer_internal(bool task_threads) { ecs_world_t *world = ecs_init(); ECS_COMPONENT_DEFINE(world, Position); ECS_COMPONENT_DEFINE(world, Velocity); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .callback = NoStagingSystemCreatePosition, .no_readonly = true }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .query.filter.terms = {{ ecs_id(Position) }}, .callback = ReadPosition, .multi_threaded = true }); if (task_threads) { ecs_set_task_threads(world, 2); } else { ecs_set_threads(world, 2); } ecs_progress(world, 0); test_int(no_staging_create_position_invoked, 1); test_int(read_position_invoked, 1); test_assert(create_position_e != 0); test_assert(ecs_has(world, create_position_e, Position)); ecs_delete(world, create_position_e); ecs_progress(world, 0); test_int(no_staging_create_position_invoked, 2); test_int(read_position_invoked, 2); ecs_fini(world); } void Pipeline_inactive_multithread_system_after_no_staging_system_no_defer(void) { Pipeline_inactive_multithread_system_after_no_staging_system_no_defer_internal(false); } void Pipeline_inactive_multithread_tasks_system_after_no_staging_system_no_defer(void) { Pipeline_inactive_multithread_system_after_no_staging_system_no_defer_internal(true); } static void Pipeline_inactive_multithread_system_after_no_staging_system_no_defer_w_no_staging_at_end_internal(bool task_threads) { ecs_world_t *world = ecs_init(); ECS_COMPONENT_DEFINE(world, Position); ECS_COMPONENT_DEFINE(world, Velocity); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .callback = NoStagingSystemCreatePosition, .no_readonly = true }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .query.filter.terms = {{ ecs_id(Position) }}, .callback = ReadPosition, .multi_threaded = true }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate )} }), .callback = ReadPosition, .no_readonly = true }); if (task_threads) { ecs_set_task_threads(world, 2); } else { ecs_set_threads(world, 2); } ecs_progress(world, 0); test_int(no_staging_create_position_invoked, 1); test_int(read_position_invoked, 2); test_assert(create_position_e != 0); test_assert(ecs_has(world, create_position_e, Position)); ecs_delete(world, create_position_e); ecs_progress(world, 0); test_int(no_staging_create_position_invoked, 2); test_int(read_position_invoked, 4); ecs_fini(world); } void Pipeline_inactive_multithread_system_after_no_staging_system_no_defer_w_no_staging_at_end(void) { Pipeline_inactive_multithread_system_after_no_staging_system_no_defer_w_no_staging_at_end_internal(false); } void Pipeline_inactive_multithread_tasks_system_after_no_staging_system_no_defer_w_no_staging_at_end(void) { Pipeline_inactive_multithread_system_after_no_staging_system_no_defer_w_no_staging_at_end_internal(true); } static int add_id_invoked = 0; static int foo_system_invoked = 0; static void AddId(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_id_t id = ecs_field_id(it, 1); int i; for (i = 0; i < it->count; i++) { ecs_add_id(world, it->entities[i], id); } add_id_invoked ++; } static void FooSystem(ecs_iter_t *it) { foo_system_invoked ++; } static void Pipeline_multi_threaded_pipeline_change_w_only_singlethreaded_internal(bool task_threads) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ECS_SYSTEM(world, FooSystem, EcsOnUpdate, Position); ECS_SYSTEM(world, AddId, EcsOnUpdate, !Position, Tag); if (task_threads) { ecs_set_task_threads(world, 2); } else { ecs_set_threads(world, 2); } ecs_entity_t e = ecs_new(world, Tag); ecs_progress(world, 0); test_assert(ecs_has(world, e, Position)); test_int(foo_system_invoked, 0); test_int(add_id_invoked, 1); ecs_fini(world); } void Pipeline_multi_threaded_pipeline_change_w_only_singlethreaded(void) { Pipeline_multi_threaded_pipeline_change_w_only_singlethreaded_internal(false); } void Pipeline_multi_threaded_tasks_pipeline_change_w_only_singlethreaded(void) { Pipeline_multi_threaded_pipeline_change_w_only_singlethreaded_internal(true); } static void SetPosition(ecs_iter_t *it) { Position *p = ecs_field(it, Position, 1); for (int i = 0; i < it->count; i ++) { p[i].x = 10; p[i].y = 20; } } void Pipeline_sync_after_not_out_for_out(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ECS_SYSTEM(world, AddId, EcsOnUpdate, [out] !Position, Tag); ECS_SYSTEM(world, SetPosition, EcsOnUpdate, [out] Position); ecs_entity_t e = ecs_new(world, Tag); const ecs_world_info_t *wi = ecs_get_world_info(world); ecs_progress(world, 0); test_int(wi->merge_count_total, 2); 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 Pipeline_pair_wildcard_read_after_staged_write(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_TAG(world, Rel); ECS_COMPONENT(world, Position); ECS_SYSTEM(world, AddId, EcsOnUpdate, [out] !(Rel, Position), Tag); ECS_SYSTEM(world, SysA, EcsOnUpdate, [in] (Rel, *)); ecs_new(world, Tag); const ecs_world_info_t *wi = ecs_get_world_info(world); ecs_progress(world, 0); test_int(wi->merge_count_total, 2); test_int(add_id_invoked, 1); test_int(sys_a_invoked, 1); ecs_fini(world); } static int add_pair_invoked = 0; static void AddPair(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_id_t id = ecs_field_id(it, 1); int i; for (i = 0; i < it->count; i++) { ecs_add_pair(world, it->entities[i], ECS_PAIR_FIRST(id), ecs_id(Position)); } add_pair_invoked ++; } void Pipeline_pair_read_after_staged_wildcard_write(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_TAG(world, Rel); ECS_COMPONENT_DEFINE(world, Position); ECS_SYSTEM(world, AddPair, EcsOnUpdate, [out] !(Rel, *), Tag); ECS_SYSTEM(world, SysA, EcsOnUpdate, [in] (Rel, Position)); ecs_new(world, Tag); const ecs_world_info_t *wi = ecs_get_world_info(world); ecs_progress(world, 0); test_int(wi->merge_count_total, 2); test_int(add_pair_invoked, 1); test_int(sys_a_invoked, 1); ecs_fini(world); } void Pipeline_no_sync_after_pair_wildcard_read_after_unmatching_staged_write(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_TAG(world, Rel); ECS_TAG(world, Rel2); ECS_COMPONENT_DEFINE(world, Position); ECS_SYSTEM(world, AddPair, EcsOnUpdate, [out] !(Rel2, Position), Tag); ECS_SYSTEM(world, SysA, EcsOnUpdate, [in] (Rel, *)); ecs_new(world, Tag); const ecs_world_info_t *wi = ecs_get_world_info(world); ecs_progress(world, 0); test_int(wi->merge_count_total, 1); test_int(add_pair_invoked, 1); test_int(sys_a_invoked, 0); test_int(1, ecs_count_id(world, ecs_pair(Rel2, ecs_id(Position)))); ecs_fini(world); } void Pipeline_no_merge_after_from_nothing_w_default_inout(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ECS_SYSTEM(world, SysA, EcsOnUpdate, Tag, Position()); ECS_SYSTEM(world, SysB, EcsOnUpdate, Tag, Position); ecs_new(world, Tag); const ecs_world_info_t *wi = ecs_get_world_info(world); ecs_progress(world, 0); test_int(wi->merge_count_total, 1); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 0); ecs_fini(world); } static int sys_add_tag_invoked = 0; static int sys_no_readonly_invoked = 0; static void sys_add_tag(ecs_iter_t *it) { ecs_new(it->world, TagA); ecs_new(it->world, TagB); sys_add_tag_invoked ++; test_assert(sys_a_invoked == 0); } static void sys_no_readonly(ecs_iter_t *it) { test_assert(sys_a_invoked == 1); test_assert(it->world == it->real_world); test_assert(!ecs_stage_is_readonly(it->real_world)); sys_no_readonly_invoked ++; } void Pipeline_on_merge_activate_system_before_merge(void) { ecs_world_t *world = ecs_init(); ECS_TAG_DEFINE(world, TagA); ECS_TAG_DEFINE(world, TagB); // system is annotated with TagA but writes both TagA, TagB ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) }}), .query.filter.terms = {{ TagA, .inout = EcsOut, .src.flags = EcsIsEntity }}, .callback = sys_add_tag }); // no merge inserted between systems, but system activates after merge ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) }}), .query.filter.terms = {{ TagB, .inout = EcsIn }}, .callback = SysA }); // read TagA, causes insertion of merge ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) }}), .query.filter.terms = {{ TagA, .inout = EcsIn }}, .no_readonly = true, .callback = sys_no_readonly }); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_add_tag_invoked, 1); test_int(sys_no_readonly_invoked, 1); test_int(ecs_count(world, TagA), 1); test_int(ecs_count(world, TagB), 1); ecs_fini(world); } void Pipeline_disable_phase(void) { ecs_world_t *world = ecs_init(); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) }}), .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsPostUpdate) }}), .callback = SysB }); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); ecs_enable(world, EcsOnUpdate, false); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 2); ecs_enable(world, EcsOnUpdate, true); ecs_enable(world, EcsPostUpdate, false); ecs_progress(world, 0); test_int(sys_a_invoked, 2); test_int(sys_b_invoked, 2); ecs_enable(world, EcsPostUpdate, true); ecs_progress(world, 0); test_int(sys_a_invoked, 3); test_int(sys_b_invoked, 3); ecs_fini(world); } void Pipeline_disable_parent(void) { ecs_world_t *world = ecs_init(); ecs_entity_t p1 = ecs_new_id(world); ecs_entity_t p2 = ecs_new_id(world); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_childof(p1), ecs_dependson(EcsOnUpdate) }}), .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_childof(p2), ecs_dependson(EcsPostUpdate) }}), .callback = SysB }); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); ecs_enable(world, p1, false); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 2); ecs_enable(world, p1, true); ecs_enable(world, p2, false); ecs_progress(world, 0); test_int(sys_a_invoked, 2); test_int(sys_b_invoked, 2); ecs_enable(world, p2, true); ecs_progress(world, 0); test_int(sys_a_invoked, 3); test_int(sys_b_invoked, 3); ecs_fini(world); } static int no_staging_add_position_invoked = 0; static void NoReadonlyAddPosition(ecs_iter_t *it) { test_assert(it->world == it->real_world); no_staging_add_position_invoked ++; ecs_entity_t e = ecs_new_id(it->world); ecs_add(it->world, e, Position); } static void Pipeline_multi_threaded_no_staging_w_add_after_read_internal(bool task_threads) { ecs_world_t *world = ecs_init(); ECS_COMPONENT_DEFINE(world, Position); if (task_threads) { ecs_set_task_threads(world, 2); } else { ecs_set_threads(world, 2); } ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) }}), .query.filter = { .terms = {{ ecs_id(Position) }}}, .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) }}), .callback = NoReadonlyAddPosition, .no_readonly = true }); ecs_progress(world, 0); test_int(sys_a_invoked, 0); test_int(no_staging_add_position_invoked, 1); test_int(ecs_count(world, Position), 1); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(no_staging_add_position_invoked, 2); test_int(ecs_count(world, Position), 2); ecs_fini(world); } void Pipeline_multi_threaded_no_staging_w_add_after_read(void) { Pipeline_multi_threaded_no_staging_w_add_after_read_internal(false); } void Pipeline_multi_threaded_tasks_no_staging_w_add_after_read(void) { Pipeline_multi_threaded_no_staging_w_add_after_read_internal(true); } void Pipeline_1_startup_system(void) { ecs_world_t *world = ecs_init(); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnStart) }}), .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) }}), .callback = SysB }); test_int(stats->merge_count_total, 0); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(stats->merge_count_total, 2); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 2); test_int(stats->merge_count_total, 3); test_assert(sys_a_delta_time == 0); ecs_fini(world); } void Pipeline_2_startup_systems(void) { ecs_world_t *world = ecs_init(); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnStart) }}), .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnStart) }}), .callback = SysB }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) }}), .callback = SysC }); test_int(stats->merge_count_total, 0); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); test_int(stats->merge_count_total, 2); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 2); test_int(stats->merge_count_total, 3); test_assert(sys_a_delta_time == 0); test_assert(sys_b_delta_time == 0); ecs_fini(world); } void Pipeline_2_startup_phases(void) { ecs_world_t *world = ecs_init(); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_entity_t AfterStart = ecs_new_w_id(world, EcsPhase); ecs_add_pair(world, AfterStart, EcsDependsOn, EcsOnStart); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(AfterStart) }}), .callback = SysB }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnStart) }}), .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) }}), .callback = SysC }); test_int(stats->merge_count_total, 0); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); test_int(stats->merge_count_total, 2); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 2); test_int(stats->merge_count_total, 3); test_assert(sys_a_delta_time == 0); test_assert(sys_b_delta_time == 0); ecs_fini(world); } void Pipeline_2_startup_systems_w_merge(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); ecs_new(world, Position); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnStart) }}), .query.filter = { .terms = {{ ecs_id(Position), .src.flags = EcsIsEntity, .inout = EcsOut }}}, .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnStart) }}), .query.filter = { .terms = {{ ecs_id(Position) }}}, .callback = SysB }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) }}), .callback = SysC }); test_int(stats->merge_count_total, 0); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); test_int(stats->merge_count_total, 3); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 2); test_int(stats->merge_count_total, 4); ecs_fini(world); } void Pipeline_inactive_last_system_merge_count(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); ECS_TAG(world, TagC); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) } }), .query.filter.terms = { { TagA }, { TagB, .src.flags = EcsIsEntity, .inout = EcsOut } }, .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) } }), .query.filter.terms = { { TagB }, { TagC, .src.flags = EcsIsEntity, .inout = EcsOut } }, .callback = SysB }); test_int(stats->merge_count_total, 0); ecs_new(world, TagA); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 0); test_int(stats->merge_count_total, 1); ecs_fini(world); } void Pipeline_inactive_middle_system_merge_count(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); ECS_TAG(world, TagC); ECS_TAG(world, TagD); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) } }), .query.filter.terms = { { TagA }, { TagB, .src.flags = EcsIsEntity, .inout = EcsOut } }, .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) } }), .query.filter.terms = { { TagB }, { TagC, .src.flags = EcsIsEntity, .inout = EcsOut } }, .callback = SysC }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) } }), .query.filter.terms = { { TagC }, { TagD, .src.flags = EcsIsEntity, .inout = EcsOut } }, .callback = SysD }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) } }), .query.filter.terms = { { TagD } }, .callback = SysB }); test_int(stats->merge_count_total, 0); ecs_new(world, TagA); ecs_new(world, TagD); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 0); test_int(sys_d_invoked, 0); test_int(stats->merge_count_total, 2); ecs_fini(world); } static void CreateEntity(ecs_iter_t *it) { ecs_id_t tag = ecs_field_id(it, 1); ecs_new_w_id(it->world, tag); } void Pipeline_last_no_readonly_system_merge_count(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); ECS_TAG(world, TagC); ECS_TAG(world, TagD); const ecs_world_info_t *stats = ecs_get_world_info(world); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) } }), .query.filter.terms = { { TagA }, { TagB, .src.flags = EcsIsEntity, .inout = EcsOut } }, .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, { .add = { ecs_dependson(EcsOnUpdate) } }), .query.filter.terms = { { TagD } }, .callback = CreateEntity, .no_readonly = true }); test_int(stats->merge_count_total, 0); ecs_new(world, TagA); ecs_new(world, TagD); test_int(1, ecs_count(world, TagD)); ecs_progress(world, 0); test_int(2, ecs_count(world, TagD)); test_int(stats->merge_count_total, 1); ecs_fini(world); } void Pipeline_2_pipelines_1_system(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Phase1); ECS_TAG(world, Phase2); ecs_entity_t p1 = ecs_pipeline(world, { .query = { .filter.terms = { { .id = EcsSystem }, { .id = Phase1 } } } }); ecs_entity_t p2 = ecs_pipeline(world, { .query = { .filter.terms = { { .id = EcsSystem }, { .id = Phase2 } } } }); ecs_system(world, { .entity = ecs_entity(world, { .add = { Phase1 } }), .callback = SysA }); ecs_progress(world, 0); test_int(sys_a_invoked, 0); ecs_set_pipeline(world, p1); ecs_progress(world, 0); test_int(sys_a_invoked, 1); ecs_set_pipeline(world, p2); ecs_progress(world, 0); test_int(sys_a_invoked, 1); ecs_fini(world); } void Pipeline_builtin_pipeline_w_self_system_term(void) { ecs_world_t *world = ecs_init(); ecs_entity_t pipeline = ecs_get_pipeline(world); test_assert(pipeline != 0); const EcsPoly *p = ecs_get_pair(world, pipeline, EcsPoly, EcsQuery); test_assert(p != NULL); test_assert(ecs_poly_is(p->poly, ecs_query_t)); ecs_query_t *q = p->poly; const ecs_filter_t *f = ecs_query_get_filter(q); test_assert(f != NULL); test_assert((f->terms[0].src.flags & EcsTraverseFlags) == EcsSelf); ecs_fini(world); } void Pipeline_custom_pipeline_w_self_system_term(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ecs_entity_t pipeline = ecs_pipeline(world, { .query.filter.terms = { { EcsSystem }, { Tag } } }); test_assert(pipeline != 0); const EcsPoly *p = ecs_get_pair(world, pipeline, EcsPoly, EcsQuery); test_assert(p != NULL); test_assert(ecs_poly_is(p->poly, ecs_query_t)); ecs_query_t *q = p->poly; const ecs_filter_t *f = ecs_query_get_filter(q); test_assert(f != NULL); test_assert((f->terms[0].src.flags & EcsTraverseFlags) == EcsSelf); ecs_fini(world); } void Pipeline_switch_from_threads_to_tasks(void) { ecs_world_t* world = ecs_init(); ECS_COMPONENT_DEFINE(world, Position); ecs_system(world, { .entity = ecs_entity(world, {.add = { ecs_dependson(EcsOnUpdate) }}), .query.filter = {.terms = {{ ecs_id(Position) }}}, .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, {.add = { ecs_dependson(EcsOnUpdate) }}), .callback = NoReadonlyAddPosition, .no_readonly = true }); ecs_set_threads(world, 2); ecs_progress(world, 0); test_int(sys_a_invoked, 0); test_int(no_staging_add_position_invoked, 1); test_int(ecs_count(world, Position), 1); ecs_set_task_threads(world, 2); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(no_staging_add_position_invoked, 2); test_int(ecs_count(world, Position), 2); ecs_fini(world); } void Pipeline_switch_from_tasks_to_threads(void) { ecs_world_t* world = ecs_init(); ECS_COMPONENT_DEFINE(world, Position); ecs_system(world, { .entity = ecs_entity(world, {.add = { ecs_dependson(EcsOnUpdate) }}), .query.filter = {.terms = {{ ecs_id(Position) }}}, .callback = SysA }); ecs_system(world, { .entity = ecs_entity(world, {.add = { ecs_dependson(EcsOnUpdate) }}), .callback = NoReadonlyAddPosition, .no_readonly = true }); ecs_set_threads(world, 2); ecs_progress(world, 0); test_int(sys_a_invoked, 0); test_int(no_staging_add_position_invoked, 1); test_int(ecs_count(world, Position), 1); ecs_set_task_threads(world, 2); ecs_progress(world, 0); test_int(sys_a_invoked, 1); test_int(no_staging_add_position_invoked, 2); test_int(ecs_count(world, Position), 2); ecs_fini(world); } void Pipeline_run_pipeline_multithreaded_internal(bool task_threads) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ECS_COMPONENT(world, Position); ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysA", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysA, .multi_threaded = true }); ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysB", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysB }); ecs_entity_t s3 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysC", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysC }); ecs_entity_t s4 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysD", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysD, .multi_threaded = true }); ecs_entity_t s5 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysE", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysE, .multi_threaded = true }); ecs_entity_t s6 = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "SysF", .add = {Tag, ecs_pair(EcsDependsOn, EcsOnUpdate)} }), .query.filter.expr = "Position", .callback = SysF }); ECS_PIPELINE(world, P, flecs.system.System, flecs.pipeline.Phase(cascade(DependsOn)), Tag); test_assert(s1 != 0); test_assert(s2 != 0); test_assert(s3 != 0); test_assert(s4 != 0); test_assert(s5 != 0); test_assert(s6 != 0); ecs_new(world, Position); ecs_new(world, Position); if (task_threads) { ecs_set_task_threads(world, 2); } else { ecs_set_threads(world, 2); } ecs_run_pipeline(world, P, 1); test_int(sys_a_invoked, 2); test_int(sys_b_invoked, 1); test_int(sys_c_invoked, 1); test_int(sys_d_invoked, 2); test_int(sys_e_invoked, 2); test_int(sys_f_invoked, 1); ecs_pipeline_stats_t stats = {0}; test_bool(ecs_pipeline_stats_get(world, P, &stats), true); test_int(ecs_vec_count(&stats.systems), 10); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[0], s1); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[1], 0); /* merge */ test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[2], s2); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[3], s3); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[4], 0); /* merge */ test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[5], s4); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[6], s5); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[7], 0); /* merge */ test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[8], s6); test_int(ecs_vec_get_t(&stats.systems, ecs_entity_t, 0)[9], 0); /* merge */ ecs_pipeline_stats_fini(&stats); ecs_fini(world); } void Pipeline_run_pipeline_multithreaded(void) { Pipeline_run_pipeline_multithreaded_internal(false); } void Pipeline_run_pipeline_multithreaded_tasks(void) { Pipeline_run_pipeline_multithreaded_internal(true); } void Pipeline_pipeline_init_no_terms(void) { install_test_abort(); ecs_world_t *world = ecs_init(); test_expect_abort(); ecs_pipeline(world, { 0 }); } void Pipeline_pipeline_init_no_system_term(void) { install_test_abort(); ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); test_expect_abort(); ecs_pipeline(world, { .query.filter.terms = {{ ecs_id(Position) }} }); }