/** * @file addons/ipeline/pipeline.c * @brief Functions for building and running pipelines. */ #include "flecs.h" #include "../system/system.h" #ifdef FLECS_PIPELINE #include "pipeline.h" static void flecs_pipeline_free( ecs_pipeline_state_t *p) { if (p) { ecs_world_t *world = p->query->filter.world; ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &p->ops, ecs_pipeline_op_t); ecs_vec_fini_t(a, &p->systems, ecs_entity_t); ecs_os_free(p->iters); ecs_query_fini(p->query); ecs_os_free(p); } } static ECS_MOVE(EcsPipeline, dst, src, { flecs_pipeline_free(dst->state); dst->state = src->state; src->state = NULL; }) static ECS_DTOR(EcsPipeline, ptr, { flecs_pipeline_free(ptr->state); }) typedef enum ecs_write_kind_t { WriteStateNone = 0, WriteStateToStage, } ecs_write_kind_t; typedef struct ecs_write_state_t { bool write_barrier; ecs_map_t ids; ecs_map_t wildcard_ids; } ecs_write_state_t; static ecs_write_kind_t flecs_pipeline_get_write_state( ecs_write_state_t *write_state, ecs_id_t id) { ecs_write_kind_t result = WriteStateNone; if (write_state->write_barrier) { /* Any component could have been written */ return WriteStateToStage; } if (id == EcsWildcard) { /* Using a wildcard for id indicates read barrier. Return true if any * components could have been staged */ if (ecs_map_count(&write_state->ids) || ecs_map_count(&write_state->wildcard_ids)) { return WriteStateToStage; } } if (!ecs_id_is_wildcard(id)) { if (ecs_map_get(&write_state->ids, id)) { result = WriteStateToStage; } } else { ecs_map_iter_t it = ecs_map_iter(&write_state->ids); while (ecs_map_next(&it)) { if (ecs_id_match(ecs_map_key(&it), id)) { return WriteStateToStage; } } } if (ecs_map_count(&write_state->wildcard_ids)) { ecs_map_iter_t it = ecs_map_iter(&write_state->wildcard_ids); while (ecs_map_next(&it)) { if (ecs_id_match(id, ecs_map_key(&it))) { return WriteStateToStage; } } } return result; } static void flecs_pipeline_set_write_state( ecs_write_state_t *write_state, ecs_id_t id) { if (id == EcsWildcard) { /* If writing to wildcard, flag all components as written */ write_state->write_barrier = true; return; } ecs_map_t *ids; if (ecs_id_is_wildcard(id)) { ids = &write_state->wildcard_ids; } else { ids = &write_state->ids; } ecs_map_ensure(ids, id)[0] = true; } static void flecs_pipeline_reset_write_state( ecs_write_state_t *write_state) { ecs_map_clear(&write_state->ids); ecs_map_clear(&write_state->wildcard_ids); write_state->write_barrier = false; } static bool flecs_pipeline_check_term( ecs_world_t *world, ecs_term_t *term, bool is_active, ecs_write_state_t *write_state) { (void)world; ecs_term_id_t *src = &term->src; if (src->flags & EcsInOutNone) { return false; } ecs_id_t id = term->id; ecs_oper_kind_t oper = term->oper; ecs_inout_kind_t inout = term->inout; bool from_any = ecs_term_match_0(term); bool from_this = ecs_term_match_this(term); bool is_shared = !from_any && (!from_this || !(src->flags & EcsSelf)); ecs_write_kind_t ws = flecs_pipeline_get_write_state(write_state, id); if (from_this && ws >= WriteStateToStage) { /* A staged write could have happened for an id that's matched on the * main storage. Even if the id isn't read, still insert a merge so that * a write to the main storage after the staged write doesn't get * overwritten. */ return true; } if (inout == EcsInOutDefault) { if (from_any) { /* If no inout kind is specified for terms without a source, this is * not interpreted as a read/write annotation but just a (component) * id that's passed to a system. */ return false; } else if (is_shared) { inout = EcsIn; } else { /* Default for owned terms is InOut */ inout = EcsInOut; } } if (oper == EcsNot && inout == EcsOut) { /* If a Not term is combined with Out, it signals that the system * intends to add a component that the entity doesn't yet have */ from_any = true; } if (from_any) { switch(inout) { case EcsOut: case EcsInOut: if (is_active) { /* Only flag component as written if system is active */ flecs_pipeline_set_write_state(write_state, id); } break; case EcsInOutDefault: case EcsInOutNone: case EcsIn: break; } switch(inout) { case EcsIn: case EcsInOut: if (ws == WriteStateToStage) { /* If a system does a get/get_mut, the component is fetched from * the main store so it must be merged first */ return true; } /* fall through */ case EcsInOutDefault: case EcsInOutNone: case EcsOut: break; } } return false; } static bool flecs_pipeline_check_terms( ecs_world_t *world, ecs_filter_t *filter, bool is_active, ecs_write_state_t *ws) { bool needs_merge = false; ecs_term_t *terms = filter->terms; int32_t t, term_count = filter->term_count; /* Check This terms first. This way if a term indicating writing to a stage * was added before the term, it won't cause merging. */ for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (ecs_term_match_this(term)) { needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); } } /* Now check staged terms */ for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (!ecs_term_match_this(term)) { needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); } } return needs_merge; } static EcsPoly* flecs_pipeline_term_system( ecs_iter_t *it) { int32_t index = ecs_table_get_column_index( it->real_world, it->table, ecs_poly_id(EcsSystem)); ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); EcsPoly *poly = ecs_table_get_column(it->table, index, it->offset); ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); return poly; } static bool flecs_pipeline_build( ecs_world_t *world, ecs_pipeline_state_t *pq) { ecs_iter_t it = ecs_query_iter(world, pq->query); if (pq->match_count == pq->query->match_count) { /* No need to rebuild the pipeline */ ecs_iter_fini(&it); return false; } world->info.pipeline_build_count_total ++; pq->rebuild_count ++; ecs_allocator_t *a = &world->allocator; ecs_pipeline_op_t *op = NULL; ecs_write_state_t ws = {0}; ecs_map_init(&ws.ids, a); ecs_map_init(&ws.wildcard_ids, a); ecs_vec_reset_t(a, &pq->ops, ecs_pipeline_op_t); ecs_vec_reset_t(a, &pq->systems, ecs_entity_t); bool multi_threaded = false; bool no_readonly = false; bool first = true; /* Iterate systems in pipeline, add ops for running / merging */ while (ecs_query_next(&it)) { EcsPoly *poly = flecs_pipeline_term_system(&it); bool is_active = ecs_table_get_type_index(world, it.table, EcsEmpty) == -1; int32_t i; for (i = 0; i < it.count; i ++) { ecs_poly_assert(poly[i].poly, ecs_system_t); ecs_system_t *sys = (ecs_system_t*)poly[i].poly; ecs_query_t *q = sys->query; bool needs_merge = false; needs_merge = flecs_pipeline_check_terms( world, &q->filter, is_active, &ws); if (is_active) { if (first) { multi_threaded = sys->multi_threaded; no_readonly = sys->no_readonly; first = false; } if (sys->multi_threaded != multi_threaded) { needs_merge = true; multi_threaded = sys->multi_threaded; } if (sys->no_readonly != no_readonly) { needs_merge = true; no_readonly = sys->no_readonly; } } if (no_readonly) { needs_merge = true; } if (needs_merge) { /* After merge all components will be merged, so reset state */ flecs_pipeline_reset_write_state(&ws); /* An inactive system can insert a merge if one of its * components got written, which could make the system * active. If this is the only system in the pipeline operation, * it results in an empty operation when we get here. If that's * the case, reuse the empty operation for the next op. */ if (op && op->count) { op = NULL; } /* Re-evaluate columns to set write flags if system is active. * If system is inactive, it can't write anything and so it * should not insert unnecessary merges. */ needs_merge = false; if (is_active) { needs_merge = flecs_pipeline_check_terms( world, &q->filter, true, &ws); } /* The component states were just reset, so if we conclude that * another merge is needed something is wrong. */ ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL); } if (!op) { op = ecs_vec_append_t(a, &pq->ops, ecs_pipeline_op_t); op->offset = ecs_vec_count(&pq->systems); op->count = 0; op->multi_threaded = false; op->no_readonly = false; op->time_spent = 0; op->commands_enqueued = 0; } /* Don't increase count for inactive systems, as they are ignored by * the query used to run the pipeline. */ if (is_active) { ecs_vec_append_t(a, &pq->systems, ecs_entity_t)[0] = it.entities[i]; if (!op->count) { op->multi_threaded = multi_threaded; op->no_readonly = no_readonly; } op->count ++; } } } if (op && !op->count && ecs_vec_count(&pq->ops) > 1) { ecs_vec_remove_last(&pq->ops); } ecs_map_fini(&ws.ids); ecs_map_fini(&ws.wildcard_ids); op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); if (!op) { ecs_dbg("#[green]pipeline#[reset] is empty"); return true; } else { /* Add schedule to debug tracing */ ecs_dbg("#[bold]pipeline rebuild"); ecs_log_push_1(); ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", op->multi_threaded, !op->no_readonly); ecs_log_push_1(); int32_t i, count = ecs_vec_count(&pq->systems); int32_t op_index = 0, ran_since_merge = 0; ecs_entity_t *systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); for (i = 0; i < count; i ++) { ecs_entity_t system = systems[i]; const EcsPoly *poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); ecs_poly_assert(poly->poly, ecs_system_t); ecs_system_t *sys = (ecs_system_t*)poly->poly; #ifdef FLECS_LOG_1 char *path = ecs_get_fullpath(world, system); const char *doc_name = NULL; #ifdef FLECS_DOC const EcsDocDescription *doc_name_id = ecs_get_pair(world, system, EcsDocDescription, EcsName); if (doc_name_id) { doc_name = doc_name_id->value; } #endif if (doc_name) { ecs_dbg("#[green]system#[reset] %s (%s)", path, doc_name); } else { ecs_dbg("#[green]system#[reset] %s", path); } ecs_os_free(path); #endif ecs_assert(op[op_index].offset + ran_since_merge == i, ECS_INTERNAL_ERROR, NULL); ran_since_merge ++; if (ran_since_merge == op[op_index].count) { ecs_dbg("#[magenta]merge#[reset]"); ecs_log_pop_1(); ran_since_merge = 0; op_index ++; if (op_index < ecs_vec_count(&pq->ops)) { ecs_dbg( "#[green]schedule#[reset]: " "threading: %d, staging: %d:", op[op_index].multi_threaded, !op[op_index].no_readonly); } ecs_log_push_1(); } if (sys->last_frame == (world->info.frame_count_total + 1)) { if (op_index < ecs_vec_count(&pq->ops)) { pq->cur_op = &op[op_index]; pq->cur_i = i; } else { pq->cur_op = NULL; pq->cur_i = 0; } } } ecs_log_pop_1(); ecs_log_pop_1(); } pq->match_count = pq->query->match_count; ecs_assert(pq->cur_op <= ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t), ECS_INTERNAL_ERROR, NULL); return true; } static void flecs_pipeline_next_system( ecs_pipeline_state_t *pq) { if (!pq->cur_op) { return; } pq->cur_i ++; if (pq->cur_i >= (pq->cur_op->offset + pq->cur_op->count)) { pq->cur_op ++; if (pq->cur_op > ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t)) { pq->cur_op = NULL; } } } bool flecs_pipeline_update( ecs_world_t *world, ecs_pipeline_state_t *pq, bool start_of_frame) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); /* If any entity mutations happened that could have affected query matching * notify appropriate queries so caches are up to date. This includes the * pipeline query. */ if (start_of_frame) { ecs_run_aperiodic(world, 0); } ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); bool rebuilt = flecs_pipeline_build(world, pq); if (start_of_frame) { /* Initialize iterators */ int32_t i, count = pq->iter_count; for (i = 0; i < count; i ++) { ecs_world_t *stage = ecs_get_stage(world, i); pq->iters[i] = ecs_query_iter(stage, pq->query); } pq->cur_op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); pq->cur_i = 0; } else { flecs_pipeline_next_system(pq); } return rebuilt; } void ecs_run_pipeline( ecs_world_t *world, ecs_entity_t pipeline, ecs_ftime_t delta_time) { if (!pipeline) { pipeline = world->pipeline; } /* create any worker task threads request */ if (ecs_using_task_threads(world)) { flecs_create_worker_threads(world); } EcsPipeline *p = ECS_CONST_CAST(EcsPipeline*, ecs_get(world, pipeline, EcsPipeline)); flecs_workers_progress(world, p->state, delta_time); if (ecs_using_task_threads(world)) { /* task threads were temporary and may now be joined */ flecs_join_worker_threads(world); } } int32_t flecs_run_pipeline_ops( ecs_world_t* world, ecs_stage_t* stage, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time) { ecs_pipeline_state_t* pq = world->pq; ecs_pipeline_op_t* op = pq->cur_op; int32_t i = pq->cur_i; ecs_assert(!stage_index || op->multi_threaded, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(&pq->systems); ecs_entity_t* systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); int32_t ran_since_merge = i - op->offset; for (; i < count; i++) { ecs_entity_t system = systems[i]; const EcsPoly* poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); ecs_poly_assert(poly->poly, ecs_system_t); ecs_system_t* sys = (ecs_system_t*)poly->poly; /* Keep track of the last frame for which the system has ran, so we * know from where to resume the schedule in case the schedule * changes during a merge. */ sys->last_frame = world->info.frame_count_total + 1; ecs_stage_t* s = NULL; if (!op->no_readonly) { /* If system is no_readonly it operates on the actual world, not * the stage. Only pass stage to system if it's readonly. */ s = stage; } ecs_run_intern(world, s, system, sys, stage_index, stage_count, delta_time, 0, 0, NULL); world->info.systems_ran_frame++; ran_since_merge++; if (ran_since_merge == op->count) { /* Merge */ break; } } return i; } void flecs_run_pipeline( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time) { ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); ecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); int32_t stage_count = ecs_get_stage_count(world); ecs_assert(!stage_index, ECS_INVALID_OPERATION, NULL); bool multi_threaded = ecs_get_stage_count(world) > 1; // Update the pipeline the workers will execute world->pq = pq; // Update the pipeline before waking the workers. flecs_pipeline_update(world, pq, true); // If there are no operations to execute in the pipeline bail early, // no need to wake the workers since they have nothing to do. while (pq->cur_op != NULL) { if (pq->cur_i == ecs_vec_count(&pq->systems)) { flecs_pipeline_update(world, pq, false); continue; } bool no_readonly = pq->cur_op->no_readonly; bool op_multi_threaded = multi_threaded && pq->cur_op->multi_threaded; pq->no_readonly = no_readonly; if (!no_readonly) { ecs_readonly_begin(world); } ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, op_multi_threaded); ecs_assert(world->workers_waiting == 0, ECS_INTERNAL_ERROR, NULL); if (op_multi_threaded) { flecs_signal_workers(world); } ecs_time_t st = { 0 }; bool measure_time = world->flags & EcsWorldMeasureSystemTime; if (measure_time) { ecs_time_measure(&st); } const int32_t i = flecs_run_pipeline_ops( world, stage, stage_index, stage_count, delta_time); if (measure_time) { /* Don't include merge time in system time */ world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); } if (op_multi_threaded) { flecs_wait_for_sync(world); } if (!no_readonly) { ecs_time_t mt = { 0 }; if (measure_time) { ecs_time_measure(&mt); } int32_t si; for (si = 0; si < stage_count; si ++) { ecs_stage_t *s = &world->stages[si]; pq->cur_op->commands_enqueued += ecs_vec_count(&s->cmd->queue); } ecs_readonly_end(world); if (measure_time) { pq->cur_op->time_spent += ecs_time_measure(&mt); } } /* Store the current state of the schedule after we synchronized the * threads, to avoid race conditions. */ pq->cur_i = i; flecs_pipeline_update(world, pq, false); } } static void flecs_run_startup_systems( ecs_world_t *world) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_dependson(EcsOnStart)); if (!idr || !flecs_table_cache_count(&idr->cache)) { /* Don't bother creating startup pipeline if no systems exist */ return; } ecs_dbg_2("#[bold]startup#[reset]"); ecs_log_push_2(); int32_t stage_count = world->stage_count; world->stage_count = 1; /* Prevents running startup systems on workers */ /* Creating a pipeline is relatively expensive, but this only happens * for the first frame. The startup pipeline is deleted afterwards, which * eliminates the overhead of keeping its query cache in sync. */ ecs_dbg_2("#[bold]create startup pipeline#[reset]"); ecs_log_push_2(); ecs_entity_t start_pip = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ .query = { .filter.terms = { { .id = EcsSystem }, { .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn }, { .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot } }, .order_by = flecs_entity_compare } }); ecs_log_pop_2(); /* Run & delete pipeline */ ecs_dbg_2("#[bold]run startup systems#[reset]"); ecs_log_push_2(); ecs_assert(start_pip != 0, ECS_INTERNAL_ERROR, NULL); const EcsPipeline *p = ecs_get(world, start_pip, EcsPipeline); ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL); flecs_workers_progress(world, p->state, 0); ecs_log_pop_2(); ecs_dbg_2("#[bold]delete startup pipeline#[reset]"); ecs_log_push_2(); ecs_delete(world, start_pip); ecs_log_pop_2(); world->stage_count = stage_count; ecs_log_pop_2(); error: return; } bool ecs_progress( ecs_world_t *world, ecs_ftime_t user_delta_time) { ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time); /* If this is the first frame, run startup systems */ if (world->info.frame_count_total == 0) { flecs_run_startup_systems(world); } /* create any worker task threads request */ if (ecs_using_task_threads(world)) { flecs_create_worker_threads(world); } ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time); ecs_log_push_3(); const EcsPipeline *p = ecs_get(world, world->pipeline, EcsPipeline); ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL); flecs_workers_progress(world, p->state, delta_time); ecs_log_pop_3(); ecs_frame_end(world); if (ecs_using_task_threads(world)) { /* task threads were temporary and may now be joined */ flecs_join_worker_threads(world); } return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); error: return false; } void ecs_set_time_scale( ecs_world_t *world, ecs_ftime_t scale) { world->info.time_scale = scale; } void ecs_reset_clock( ecs_world_t *world) { world->info.world_time_total = 0; world->info.world_time_total_raw = 0; } void ecs_set_pipeline( ecs_world_t *world, ecs_entity_t pipeline) { ecs_poly_assert(world, ecs_world_t); ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL, ECS_INVALID_PARAMETER, "not a pipeline"); world->pipeline = pipeline; error: return; } ecs_entity_t ecs_get_pipeline( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->pipeline; error: return 0; } ecs_entity_t ecs_pipeline_init( ecs_world_t *world, const ecs_pipeline_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = desc->entity; if (!result) { result = ecs_new(world, 0); } ecs_query_desc_t qd = desc->query; if (!qd.order_by) { qd.order_by = flecs_entity_compare; } qd.filter.entity = result; ecs_query_t *query = ecs_query_init(world, &qd); if (!query) { ecs_delete(world, result); return 0; } ecs_check(query->filter.terms != NULL, ECS_INVALID_PARAMETER, "pipeline query cannot be empty"); ecs_check(query->filter.terms[0].id == EcsSystem, ECS_INVALID_PARAMETER, "pipeline must start with System term"); ecs_pipeline_state_t *pq = ecs_os_calloc_t(ecs_pipeline_state_t); pq->query = query; pq->match_count = -1; pq->idr_inactive = flecs_id_record_ensure(world, EcsEmpty); ecs_set(world, result, EcsPipeline, { pq }); return result; error: return 0; } /* -- Module implementation -- */ static void FlecsPipelineFini( ecs_world_t *world, void *ctx) { (void)ctx; if (ecs_get_stage_count(world)) { ecs_set_threads(world, 0); } ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); } #define flecs_bootstrap_phase(world, phase, depends_on)\ flecs_bootstrap_tag(world, phase);\ flecs_bootstrap_phase_(world, phase, depends_on) static void flecs_bootstrap_phase_( ecs_world_t *world, ecs_entity_t phase, ecs_entity_t depends_on) { ecs_add_id(world, phase, EcsPhase); if (depends_on) { ecs_add_pair(world, phase, EcsDependsOn, depends_on); } } void FlecsPipelineImport( ecs_world_t *world) { ECS_MODULE(world, FlecsPipeline); ECS_IMPORT(world, FlecsSystem); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsPipeline); flecs_bootstrap_tag(world, EcsPhase); /* Create anonymous phases to which the builtin phases will have DependsOn * relationships. This ensures that, for example, EcsOnUpdate doesn't have a * direct DependsOn relationship on EcsPreUpdate, which ensures that when * the EcsPreUpdate phase is disabled, EcsOnUpdate still runs. */ ecs_entity_t phase_0 = ecs_new(world, 0); ecs_entity_t phase_1 = ecs_new_w_pair(world, EcsDependsOn, phase_0); ecs_entity_t phase_2 = ecs_new_w_pair(world, EcsDependsOn, phase_1); ecs_entity_t phase_3 = ecs_new_w_pair(world, EcsDependsOn, phase_2); ecs_entity_t phase_4 = ecs_new_w_pair(world, EcsDependsOn, phase_3); ecs_entity_t phase_5 = ecs_new_w_pair(world, EcsDependsOn, phase_4); ecs_entity_t phase_6 = ecs_new_w_pair(world, EcsDependsOn, phase_5); ecs_entity_t phase_7 = ecs_new_w_pair(world, EcsDependsOn, phase_6); ecs_entity_t phase_8 = ecs_new_w_pair(world, EcsDependsOn, phase_7); flecs_bootstrap_phase(world, EcsOnStart, 0); flecs_bootstrap_phase(world, EcsPreFrame, 0); flecs_bootstrap_phase(world, EcsOnLoad, phase_0); flecs_bootstrap_phase(world, EcsPostLoad, phase_1); flecs_bootstrap_phase(world, EcsPreUpdate, phase_2); flecs_bootstrap_phase(world, EcsOnUpdate, phase_3); flecs_bootstrap_phase(world, EcsOnValidate, phase_4); flecs_bootstrap_phase(world, EcsPostUpdate, phase_5); flecs_bootstrap_phase(world, EcsPreStore, phase_6); flecs_bootstrap_phase(world, EcsOnStore, phase_7); flecs_bootstrap_phase(world, EcsPostFrame, phase_8); ecs_set_hooks(world, EcsPipeline, { .ctor = ecs_default_ctor, .dtor = ecs_dtor(EcsPipeline), .move = ecs_move(EcsPipeline) }); world->pipeline = ecs_pipeline(world, { .entity = ecs_entity(world, { .name = "BuiltinPipeline" }), .query = { .filter.terms = { { .id = EcsSystem }, { .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn }, { .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot } }, .order_by = flecs_entity_compare } }); /* Cleanup thread administration when world is destroyed */ ecs_atfini(world, FlecsPipelineFini, NULL); } #endif