/** * @file world.c * @brief World-level API. */ #include "private_api.h" /* Id flags */ const ecs_id_t ECS_PAIR = (1ull << 63); const ecs_id_t ECS_OVERRIDE = (1ull << 62); const ecs_id_t ECS_TOGGLE = (1ull << 61); const ecs_id_t ECS_AND = (1ull << 60); /** Builtin component ids */ const ecs_entity_t ecs_id(EcsComponent) = 1; const ecs_entity_t ecs_id(EcsIdentifier) = 2; const ecs_entity_t ecs_id(EcsIterable) = 3; const ecs_entity_t ecs_id(EcsPoly) = 4; /* Poly target components */ const ecs_entity_t EcsQuery = 5; const ecs_entity_t EcsObserver = 6; const ecs_entity_t EcsSystem = 7; /* Core scopes & entities */ const ecs_entity_t EcsWorld = FLECS_HI_COMPONENT_ID + 0; const ecs_entity_t EcsFlecs = FLECS_HI_COMPONENT_ID + 1; const ecs_entity_t EcsFlecsCore = FLECS_HI_COMPONENT_ID + 2; const ecs_entity_t EcsFlecsInternals = FLECS_HI_COMPONENT_ID + 3; const ecs_entity_t EcsModule = FLECS_HI_COMPONENT_ID + 4; const ecs_entity_t EcsPrivate = FLECS_HI_COMPONENT_ID + 5; const ecs_entity_t EcsPrefab = FLECS_HI_COMPONENT_ID + 6; const ecs_entity_t EcsDisabled = FLECS_HI_COMPONENT_ID + 7; const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 8; const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 9; /* Relationship properties */ const ecs_entity_t EcsWildcard = FLECS_HI_COMPONENT_ID + 10; const ecs_entity_t EcsAny = FLECS_HI_COMPONENT_ID + 11; const ecs_entity_t EcsThis = FLECS_HI_COMPONENT_ID + 12; const ecs_entity_t EcsVariable = FLECS_HI_COMPONENT_ID + 13; const ecs_entity_t EcsTransitive = FLECS_HI_COMPONENT_ID + 14; const ecs_entity_t EcsReflexive = FLECS_HI_COMPONENT_ID + 15; const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 16; const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 17; const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 18; const ecs_entity_t EcsAlwaysOverride = FLECS_HI_COMPONENT_ID + 19; const ecs_entity_t EcsTag = FLECS_HI_COMPONENT_ID + 20; const ecs_entity_t EcsUnion = FLECS_HI_COMPONENT_ID + 21; const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 22; const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 23; const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 24; const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 25; const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 26; /* Builtin relationships */ const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 27; const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 28; const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 29; /* Identifier tags */ const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 30; const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 31; const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 32; /* Events */ const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 33; const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 34; const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 35; const ecs_entity_t EcsUnSet = FLECS_HI_COMPONENT_ID + 36; const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 37; const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 38; const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 39; const ecs_entity_t EcsOnTableEmpty = FLECS_HI_COMPONENT_ID + 40; const ecs_entity_t EcsOnTableFill = FLECS_HI_COMPONENT_ID + 41; const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 46; /* Timers */ const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 47; const ecs_entity_t ecs_id(EcsTimer) = FLECS_HI_COMPONENT_ID + 48; const ecs_entity_t ecs_id(EcsRateFilter) = FLECS_HI_COMPONENT_ID + 49; /* Actions */ const ecs_entity_t EcsRemove = FLECS_HI_COMPONENT_ID + 50; const ecs_entity_t EcsDelete = FLECS_HI_COMPONENT_ID + 51; const ecs_entity_t EcsPanic = FLECS_HI_COMPONENT_ID + 52; /* Misc */ const ecs_entity_t ecs_id(EcsTarget) = FLECS_HI_COMPONENT_ID + 53; const ecs_entity_t EcsFlatten = FLECS_HI_COMPONENT_ID + 54; const ecs_entity_t EcsDefaultChildComponent = FLECS_HI_COMPONENT_ID + 55; /* Builtin predicate ids (used by rule engine) */ const ecs_entity_t EcsPredEq = FLECS_HI_COMPONENT_ID + 56; const ecs_entity_t EcsPredMatch = FLECS_HI_COMPONENT_ID + 57; const ecs_entity_t EcsPredLookup = FLECS_HI_COMPONENT_ID + 58; const ecs_entity_t EcsScopeOpen = FLECS_HI_COMPONENT_ID + 59; const ecs_entity_t EcsScopeClose = FLECS_HI_COMPONENT_ID + 60; /* Systems */ const ecs_entity_t EcsMonitor = FLECS_HI_COMPONENT_ID + 61; const ecs_entity_t EcsEmpty = FLECS_HI_COMPONENT_ID + 62; const ecs_entity_t ecs_id(EcsPipeline) = FLECS_HI_COMPONENT_ID + 63; const ecs_entity_t EcsOnStart = FLECS_HI_COMPONENT_ID + 64; const ecs_entity_t EcsPreFrame = FLECS_HI_COMPONENT_ID + 65; const ecs_entity_t EcsOnLoad = FLECS_HI_COMPONENT_ID + 66; const ecs_entity_t EcsPostLoad = FLECS_HI_COMPONENT_ID + 67; const ecs_entity_t EcsPreUpdate = FLECS_HI_COMPONENT_ID + 68; const ecs_entity_t EcsOnUpdate = FLECS_HI_COMPONENT_ID + 69; const ecs_entity_t EcsOnValidate = FLECS_HI_COMPONENT_ID + 70; const ecs_entity_t EcsPostUpdate = FLECS_HI_COMPONENT_ID + 71; const ecs_entity_t EcsPreStore = FLECS_HI_COMPONENT_ID + 72; const ecs_entity_t EcsOnStore = FLECS_HI_COMPONENT_ID + 73; const ecs_entity_t EcsPostFrame = FLECS_HI_COMPONENT_ID + 74; const ecs_entity_t EcsPhase = FLECS_HI_COMPONENT_ID + 75; /* Meta primitive components (don't use low ids to save id space) */ const ecs_entity_t ecs_id(ecs_bool_t) = FLECS_HI_COMPONENT_ID + 80; const ecs_entity_t ecs_id(ecs_char_t) = FLECS_HI_COMPONENT_ID + 81; const ecs_entity_t ecs_id(ecs_byte_t) = FLECS_HI_COMPONENT_ID + 82; const ecs_entity_t ecs_id(ecs_u8_t) = FLECS_HI_COMPONENT_ID + 83; const ecs_entity_t ecs_id(ecs_u16_t) = FLECS_HI_COMPONENT_ID + 84; const ecs_entity_t ecs_id(ecs_u32_t) = FLECS_HI_COMPONENT_ID + 85; const ecs_entity_t ecs_id(ecs_u64_t) = FLECS_HI_COMPONENT_ID + 86; const ecs_entity_t ecs_id(ecs_uptr_t) = FLECS_HI_COMPONENT_ID + 87; const ecs_entity_t ecs_id(ecs_i8_t) = FLECS_HI_COMPONENT_ID + 88; const ecs_entity_t ecs_id(ecs_i16_t) = FLECS_HI_COMPONENT_ID + 89; const ecs_entity_t ecs_id(ecs_i32_t) = FLECS_HI_COMPONENT_ID + 90; const ecs_entity_t ecs_id(ecs_i64_t) = FLECS_HI_COMPONENT_ID + 91; const ecs_entity_t ecs_id(ecs_iptr_t) = FLECS_HI_COMPONENT_ID + 92; const ecs_entity_t ecs_id(ecs_f32_t) = FLECS_HI_COMPONENT_ID + 93; const ecs_entity_t ecs_id(ecs_f64_t) = FLECS_HI_COMPONENT_ID + 94; const ecs_entity_t ecs_id(ecs_string_t) = FLECS_HI_COMPONENT_ID + 95; const ecs_entity_t ecs_id(ecs_entity_t) = FLECS_HI_COMPONENT_ID + 96; /** Meta module component ids */ const ecs_entity_t ecs_id(EcsMetaType) = FLECS_HI_COMPONENT_ID + 97; const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = FLECS_HI_COMPONENT_ID + 98; const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 99; const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 100; const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 101; const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 102; const ecs_entity_t ecs_id(EcsMemberRanges) = FLECS_HI_COMPONENT_ID + 103; const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 104; const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 105; const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 106; const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 107; const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 108; const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 109; const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 110; const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 111; /* Doc module components */ const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 112; const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 113; const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 114; const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 115; const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 116; /* REST module components */ const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 117; /* Default lookup path */ static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; /* Declarations for addons. Located in world.c to avoid issues during linking of * static library */ #ifdef FLECS_ALERTS ECS_COMPONENT_DECLARE(EcsAlert); ECS_COMPONENT_DECLARE(EcsAlertInstance); ECS_COMPONENT_DECLARE(EcsAlertsActive); ECS_TAG_DECLARE(EcsAlertInfo); ECS_TAG_DECLARE(EcsAlertWarning); ECS_TAG_DECLARE(EcsAlertError); ECS_TAG_DECLARE(EcsAlertCritical); #endif #ifdef FLECS_UNITS ECS_DECLARE(EcsUnitPrefixes); ECS_DECLARE(EcsYocto); ECS_DECLARE(EcsZepto); ECS_DECLARE(EcsAtto); ECS_DECLARE(EcsFemto); ECS_DECLARE(EcsPico); ECS_DECLARE(EcsNano); ECS_DECLARE(EcsMicro); ECS_DECLARE(EcsMilli); ECS_DECLARE(EcsCenti); ECS_DECLARE(EcsDeci); ECS_DECLARE(EcsDeca); ECS_DECLARE(EcsHecto); ECS_DECLARE(EcsKilo); ECS_DECLARE(EcsMega); ECS_DECLARE(EcsGiga); ECS_DECLARE(EcsTera); ECS_DECLARE(EcsPeta); ECS_DECLARE(EcsExa); ECS_DECLARE(EcsZetta); ECS_DECLARE(EcsYotta); ECS_DECLARE(EcsKibi); ECS_DECLARE(EcsMebi); ECS_DECLARE(EcsGibi); ECS_DECLARE(EcsTebi); ECS_DECLARE(EcsPebi); ECS_DECLARE(EcsExbi); ECS_DECLARE(EcsZebi); ECS_DECLARE(EcsYobi); ECS_DECLARE(EcsDuration); ECS_DECLARE(EcsPicoSeconds); ECS_DECLARE(EcsNanoSeconds); ECS_DECLARE(EcsMicroSeconds); ECS_DECLARE(EcsMilliSeconds); ECS_DECLARE(EcsSeconds); ECS_DECLARE(EcsMinutes); ECS_DECLARE(EcsHours); ECS_DECLARE(EcsDays); ECS_DECLARE(EcsTime); ECS_DECLARE(EcsDate); ECS_DECLARE(EcsMass); ECS_DECLARE(EcsGrams); ECS_DECLARE(EcsKiloGrams); ECS_DECLARE(EcsElectricCurrent); ECS_DECLARE(EcsAmpere); ECS_DECLARE(EcsAmount); ECS_DECLARE(EcsMole); ECS_DECLARE(EcsLuminousIntensity); ECS_DECLARE(EcsCandela); ECS_DECLARE(EcsForce); ECS_DECLARE(EcsNewton); ECS_DECLARE(EcsLength); ECS_DECLARE(EcsMeters); ECS_DECLARE(EcsPicoMeters); ECS_DECLARE(EcsNanoMeters); ECS_DECLARE(EcsMicroMeters); ECS_DECLARE(EcsMilliMeters); ECS_DECLARE(EcsCentiMeters); ECS_DECLARE(EcsKiloMeters); ECS_DECLARE(EcsMiles); ECS_DECLARE(EcsPixels); ECS_DECLARE(EcsPressure); ECS_DECLARE(EcsPascal); ECS_DECLARE(EcsBar); ECS_DECLARE(EcsSpeed); ECS_DECLARE(EcsMetersPerSecond); ECS_DECLARE(EcsKiloMetersPerSecond); ECS_DECLARE(EcsKiloMetersPerHour); ECS_DECLARE(EcsMilesPerHour); ECS_DECLARE(EcsAcceleration); ECS_DECLARE(EcsTemperature); ECS_DECLARE(EcsKelvin); ECS_DECLARE(EcsCelsius); ECS_DECLARE(EcsFahrenheit); ECS_DECLARE(EcsData); ECS_DECLARE(EcsBits); ECS_DECLARE(EcsKiloBits); ECS_DECLARE(EcsMegaBits); ECS_DECLARE(EcsGigaBits); ECS_DECLARE(EcsBytes); ECS_DECLARE(EcsKiloBytes); ECS_DECLARE(EcsMegaBytes); ECS_DECLARE(EcsGigaBytes); ECS_DECLARE(EcsKibiBytes); ECS_DECLARE(EcsGibiBytes); ECS_DECLARE(EcsMebiBytes); ECS_DECLARE(EcsDataRate); ECS_DECLARE(EcsBitsPerSecond); ECS_DECLARE(EcsKiloBitsPerSecond); ECS_DECLARE(EcsMegaBitsPerSecond); ECS_DECLARE(EcsGigaBitsPerSecond); ECS_DECLARE(EcsBytesPerSecond); ECS_DECLARE(EcsKiloBytesPerSecond); ECS_DECLARE(EcsMegaBytesPerSecond); ECS_DECLARE(EcsGigaBytesPerSecond); ECS_DECLARE(EcsPercentage); ECS_DECLARE(EcsAngle); ECS_DECLARE(EcsRadians); ECS_DECLARE(EcsDegrees); ECS_DECLARE(EcsBel); ECS_DECLARE(EcsDeciBel); ECS_DECLARE(EcsFrequency); ECS_DECLARE(EcsHertz); ECS_DECLARE(EcsKiloHertz); ECS_DECLARE(EcsMegaHertz); ECS_DECLARE(EcsGigaHertz); ECS_DECLARE(EcsUri); ECS_DECLARE(EcsUriHyperlink); ECS_DECLARE(EcsUriImage); ECS_DECLARE(EcsUriFile); #endif /* -- Private functions -- */ const ecs_stage_t* flecs_stage_from_readonly_world( const ecs_world_t *world) { ecs_assert(ecs_poly_is(world, ecs_world_t) || ecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); if (ecs_poly_is(world, ecs_world_t)) { return &world->stages[0]; } else if (ecs_poly_is(world, ecs_stage_t)) { return ECS_CONST_CAST(ecs_stage_t*, world); } return NULL; } ecs_stage_t* flecs_stage_from_world( ecs_world_t **world_ptr) { ecs_world_t *world = *world_ptr; ecs_assert(ecs_poly_is(world, ecs_world_t) || ecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); if (ecs_poly_is(world, ecs_world_t)) { return &world->stages[0]; } *world_ptr = ((ecs_stage_t*)world)->world; return ECS_CONST_CAST(ecs_stage_t*, world); } ecs_world_t* flecs_suspend_readonly( const ecs_world_t *stage_world, ecs_suspend_readonly_state_t *state) { ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); ecs_poly_assert(world, ecs_world_t); bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); ecs_world_t *temp_world = world; ecs_stage_t *stage = flecs_stage_from_world(&temp_world); if (!is_readonly && !stage->defer) { state->is_readonly = false; state->is_deferred = false; return world; } ecs_dbg_3("suspending readonly mode"); /* Cannot suspend when running with multiple threads */ ecs_assert(!(world->flags & EcsWorldReadonly) || (ecs_get_stage_count(world) <= 1), ECS_INVALID_WHILE_READONLY, NULL); state->is_readonly = is_readonly; state->is_deferred = stage->defer != 0; /* Silence readonly checks */ world->flags &= ~EcsWorldReadonly; /* Hack around safety checks (this ought to look ugly) */ state->defer_count = stage->defer; state->commands = stage->cmd->queue; state->defer_stack = stage->cmd->stack; flecs_stack_init(&stage->cmd->stack); state->scope = stage->scope; state->with = stage->with; stage->defer = 0; ecs_vec_init_t(NULL, &stage->cmd->queue, ecs_cmd_t, 0); return world; } void flecs_resume_readonly( ecs_world_t *world, ecs_suspend_readonly_state_t *state) { ecs_poly_assert(world, ecs_world_t); ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *temp_world = world; ecs_stage_t *stage = flecs_stage_from_world(&temp_world); if (state->is_readonly || state->is_deferred) { ecs_dbg_3("resuming readonly mode"); ecs_run_aperiodic(world, 0); /* Restore readonly state / defer count */ ECS_BIT_COND(world->flags, EcsWorldReadonly, state->is_readonly); stage->defer = state->defer_count; ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); stage->cmd->queue = state->commands; flecs_stack_fini(&stage->cmd->stack); stage->cmd->stack = state->defer_stack; stage->scope = state->scope; stage->with = state->with; } } /* Evaluate component monitor. If a monitored entity changed it will have set a * flag in one of the world's component monitors. Queries can register * themselves with component monitors to determine whether they need to rematch * with tables. */ static void flecs_eval_component_monitor( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); if (!world->monitors.is_dirty) { return; } world->monitors.is_dirty = false; ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); while (ecs_map_next(&it)) { ecs_monitor_t *m = ecs_map_ptr(&it); if (!m->is_dirty) { continue; } m->is_dirty = false; int32_t i, count = ecs_vec_count(&m->queries); ecs_query_t **elems = ecs_vec_first(&m->queries); for (i = 0; i < count; i ++) { ecs_query_t *q = elems[i]; flecs_query_notify(world, q, &(ecs_query_event_t) { .kind = EcsQueryTableRematch }); } } } void flecs_monitor_mark_dirty( ecs_world_t *world, ecs_entity_t id) { ecs_map_t *monitors = &world->monitors.monitors; /* Only flag if there are actually monitors registered, so that we * don't waste cycles evaluating monitors if there's no interest */ if (ecs_map_is_init(monitors)) { ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); if (m) { if (!world->monitors.is_dirty) { world->monitor_generation ++; } m->is_dirty = true; world->monitors.is_dirty = true; } } } void flecs_monitor_register( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_t *monitors = &world->monitors.monitors; ecs_map_init_if(monitors, &world->allocator); ecs_monitor_t *m = ecs_map_ensure_alloc_t(monitors, ecs_monitor_t, id); ecs_vec_init_if_t(&m->queries, ecs_query_t*); ecs_query_t **q = ecs_vec_append_t( &world->allocator, &m->queries, ecs_query_t*); *q = query; } void flecs_monitor_unregister( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_t *monitors = &world->monitors.monitors; if (!ecs_map_is_init(monitors)) { return; } ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); if (!m) { return; } int32_t i, count = ecs_vec_count(&m->queries); ecs_query_t **queries = ecs_vec_first(&m->queries); for (i = 0; i < count; i ++) { if (queries[i] == query) { ecs_vec_remove_t(&m->queries, ecs_query_t*, i); count --; break; } } if (!count) { ecs_vec_fini_t(&world->allocator, &m->queries, ecs_query_t*); ecs_map_remove_free(monitors, id); } if (!ecs_map_count(monitors)) { ecs_map_fini(monitors); } } static void flecs_init_store( ecs_world_t *world) { ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &world->store.records, ecs_table_record_t, 0); ecs_vec_init_t(a, &world->store.marked_ids, ecs_marked_id_t, 0); ecs_vec_init_t(a, &world->store.depth_ids, ecs_entity_t, 0); ecs_map_init(&world->store.entity_to_depth, &world->allocator); /* Initialize entity index */ flecs_entities_init(world); /* Initialize root table */ flecs_sparse_init_t(&world->store.tables, a, &world->allocators.sparse_chunk, ecs_table_t); /* Initialize table map */ flecs_table_hashmap_init(world, &world->store.table_map); /* Initialize one root table per stage */ flecs_init_root_table(world); } static void flecs_clean_tables( ecs_world_t *world) { int32_t i, count = flecs_sparse_count(&world->store.tables); /* Ensure that first table in sparse set has id 0. This is a dummy table * that only exists so that there is no table with id 0 */ ecs_table_t *first = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, 0); (void)first; for (i = 1; i < count; i ++) { ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); flecs_table_free(world, t); } /* Free table types separately so that if application destructors rely on * a type it's still valid. */ for (i = 1; i < count; i ++) { ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); flecs_table_free_type(world, t); } /* Clear the root table */ if (count) { flecs_table_reset(world, &world->store.root); } } static void flecs_fini_root_tables( ecs_world_t *world, ecs_id_record_t *idr, bool fini_targets) { ecs_table_cache_iter_t it; bool has_roots = flecs_table_cache_iter(&idr->cache, &it); ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); (void)has_roots; const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (table->flags & EcsTableHasBuiltins) { continue; /* Filter out modules */ } int32_t i, count = table->data.entities.count; ecs_entity_t *entities = table->data.entities.array; if (fini_targets) { /* Only delete entities that are used as pair target. Iterate * backwards to minimize moving entities around in table. */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) { ecs_delete(world, entities[i]); } } } else { /* Delete remaining entities that are not in use (added to another * entity). This limits table moves during cleanup and delays * cleanup of tags. */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) { ecs_delete(world, entities[i]); } } } } } static void flecs_fini_roots( ecs_world_t *world) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Delete root entities that are not modules. This prioritizes deleting * regular entities first, which reduces the chance of components getting * destructed in random order because it got deleted before entities, * thereby bypassing the OnDeleteTarget policy. */ flecs_defer_begin(world, &world->stages[0]); flecs_fini_root_tables(world, idr, true); flecs_defer_end(world, &world->stages[0]); flecs_defer_begin(world, &world->stages[0]); flecs_fini_root_tables(world, idr, false); flecs_defer_end(world, &world->stages[0]); } static void flecs_fini_store(ecs_world_t *world) { flecs_clean_tables(world); flecs_sparse_fini(&world->store.tables); flecs_table_free(world, &world->store.root); flecs_entities_clear(world); flecs_hashmap_fini(&world->store.table_map); ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &world->store.records, ecs_table_record_t); ecs_vec_fini_t(a, &world->store.marked_ids, ecs_marked_id_t); ecs_vec_fini_t(a, &world->store.depth_ids, ecs_entity_t); ecs_map_fini(&world->store.entity_to_depth); } /* Implementation for iterable mixin */ static bool flecs_world_iter_next( ecs_iter_t *it) { if (ECS_BIT_IS_SET(it->flags, EcsIterIsValid)) { ECS_BIT_CLEAR(it->flags, EcsIterIsValid); ecs_iter_fini(it); return false; } ecs_world_t *world = it->real_world; it->entities = ECS_CONST_CAST(ecs_entity_t*, flecs_entities_ids(world)); it->count = flecs_entities_count(world); flecs_iter_validate(it); return true; } static void flecs_world_iter_init( const ecs_world_t *world, const ecs_poly_t *poly, ecs_iter_t *iter, ecs_term_t *filter) { ecs_poly_assert(poly, ecs_world_t); (void)poly; if (filter) { iter[0] = ecs_term_iter(world, filter); } else { iter[0] = (ecs_iter_t){ .world = ECS_CONST_CAST(ecs_world_t*, world), .real_world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)), .next = flecs_world_iter_next }; } } static void flecs_world_allocators_init( ecs_world_t *world) { ecs_world_allocators_t *a = &world->allocators; flecs_allocator_init(&world->allocator); ecs_map_params_init(&a->ptr, &world->allocator); ecs_map_params_init(&a->query_table_list, &world->allocator); flecs_ballocator_init_t(&a->query_table, ecs_query_table_t); flecs_ballocator_init_t(&a->query_table_match, ecs_query_table_match_t); flecs_ballocator_init_n(&a->graph_edge_lo, ecs_graph_edge_t, FLECS_HI_COMPONENT_ID); flecs_ballocator_init_t(&a->graph_edge, ecs_graph_edge_t); flecs_ballocator_init_t(&a->id_record, ecs_id_record_t); flecs_ballocator_init_n(&a->id_record_chunk, ecs_id_record_t, FLECS_SPARSE_PAGE_SIZE); flecs_ballocator_init_t(&a->table_diff, ecs_table_diff_t); flecs_ballocator_init_n(&a->sparse_chunk, int32_t, FLECS_SPARSE_PAGE_SIZE); flecs_ballocator_init_t(&a->hashmap, ecs_hashmap_t); flecs_table_diff_builder_init(world, &world->allocators.diff_builder); } static void flecs_world_allocators_fini( ecs_world_t *world) { ecs_world_allocators_t *a = &world->allocators; ecs_map_params_fini(&a->ptr); ecs_map_params_fini(&a->query_table_list); flecs_ballocator_fini(&a->query_table); flecs_ballocator_fini(&a->query_table_match); flecs_ballocator_fini(&a->graph_edge_lo); flecs_ballocator_fini(&a->graph_edge); flecs_ballocator_fini(&a->id_record); flecs_ballocator_fini(&a->id_record_chunk); flecs_ballocator_fini(&a->table_diff); flecs_ballocator_fini(&a->sparse_chunk); flecs_ballocator_fini(&a->hashmap); flecs_table_diff_builder_fini(world, &world->allocators.diff_builder); flecs_allocator_fini(&world->allocator); } static void flecs_log_addons(void) { ecs_trace("addons included in build:"); ecs_log_push(); #ifdef FLECS_CPP ecs_trace("FLECS_CPP"); #endif #ifdef FLECS_MODULE ecs_trace("FLECS_MODULE"); #endif #ifdef FLECS_PARSER ecs_trace("FLECS_PARSER"); #endif #ifdef FLECS_PLECS ecs_trace("FLECS_PLECS"); #endif #ifdef FLECS_RULES ecs_trace("FLECS_RULES"); #endif #ifdef FLECS_SNAPSHOT ecs_trace("FLECS_SNAPSHOT"); #endif #ifdef FLECS_STATS ecs_trace("FLECS_STATS"); #endif #ifdef FLECS_MONITOR ecs_trace("FLECS_MONITOR"); #endif #ifdef FLECS_METRICS ecs_trace("FLECS_METRICS"); #endif #ifdef FLECS_SYSTEM ecs_trace("FLECS_SYSTEM"); #endif #ifdef FLECS_PIPELINE ecs_trace("FLECS_PIPELINE"); #endif #ifdef FLECS_TIMER ecs_trace("FLECS_TIMER"); #endif #ifdef FLECS_META ecs_trace("FLECS_META"); #endif #ifdef FLECS_META_C ecs_trace("FLECS_META_C"); #endif #ifdef FLECS_UNITS ecs_trace("FLECS_UNITS"); #endif #ifdef FLECS_EXPR ecs_trace("FLECS_EXPR"); #endif #ifdef FLECS_JSON ecs_trace("FLECS_JSON"); #endif #ifdef FLECS_DOC ecs_trace("FLECS_DOC"); #endif #ifdef FLECS_COREDOC ecs_trace("FLECS_COREDOC"); #endif #ifdef FLECS_LOG ecs_trace("FLECS_LOG"); #endif #ifdef FLECS_JOURNAL ecs_trace("FLECS_JOURNAL"); #endif #ifdef FLECS_APP ecs_trace("FLECS_APP"); #endif #ifdef FLECS_OS_API_IMPL ecs_trace("FLECS_OS_API_IMPL"); #endif #ifdef FLECS_SCRIPT ecs_trace("FLECS_SCRIPT"); #endif #ifdef FLECS_HTTP ecs_trace("FLECS_HTTP"); #endif #ifdef FLECS_REST ecs_trace("FLECS_REST"); #endif ecs_log_pop(); } /* -- Public functions -- */ ecs_world_t *ecs_mini(void) { #ifdef FLECS_OS_API_IMPL ecs_set_os_api_impl(); #endif ecs_os_init(); ecs_trace("#[bold]bootstrapping world"); ecs_log_push(); ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); if (!ecs_os_has_heap()) { ecs_abort(ECS_MISSING_OS_API, NULL); } if (!ecs_os_has_threading()) { ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); } if (!ecs_os_has_time()) { ecs_trace("time management not available"); } flecs_log_addons(); #ifdef FLECS_SANITIZE ecs_trace("sanitize build, rebuild without FLECS_SANITIZE for (much) " "improved performance"); #elif defined(FLECS_DEBUG) ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for improved " "performance"); #else ecs_trace("#[green]release#[reset] build"); #endif #ifdef __clang__ ecs_trace("compiled with clang %s", __clang_version__); #elif defined(__GNUC__) ecs_trace("compiled with gcc %d.%d", __GNUC__, __GNUC_MINOR__); #elif defined (_MSC_VER) ecs_trace("compiled with msvc %d", _MSC_VER); #elif defined (__TINYC__) ecs_trace("compiled with tcc %d", __TINYC__); #endif ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_poly_init(world, ecs_world_t); world->flags |= EcsWorldInit; flecs_world_allocators_init(world); ecs_allocator_t *a = &world->allocator; world->self = world; flecs_sparse_init_t(&world->type_info, a, &world->allocators.sparse_chunk, ecs_type_info_t); ecs_map_init_w_params(&world->id_index_hi, &world->allocators.ptr); world->id_index_lo = ecs_os_calloc_n(ecs_id_record_t, FLECS_HI_ID_RECORD_ID); flecs_observable_init(&world->observable); world->iterable.init = flecs_world_iter_init; world->pending_tables = ecs_os_calloc_t(ecs_sparse_t); flecs_sparse_init_t(world->pending_tables, a, &world->allocators.sparse_chunk, ecs_table_t*); world->pending_buffer = ecs_os_calloc_t(ecs_sparse_t); flecs_sparse_init_t(world->pending_buffer, a, &world->allocators.sparse_chunk, ecs_table_t*); flecs_name_index_init(&world->aliases, a); flecs_name_index_init(&world->symbols, a); ecs_vec_init_t(a, &world->fini_actions, ecs_action_elem_t, 0); world->info.time_scale = 1.0; if (ecs_os_has_time()) { ecs_os_get_time(&world->world_start_time); } ecs_set_stage_count(world, 1); ecs_default_lookup_path[0] = EcsFlecsCore; ecs_set_lookup_path(world, ecs_default_lookup_path); flecs_init_store(world); flecs_bootstrap(world); world->flags &= ~EcsWorldInit; ecs_trace("world ready!"); ecs_log_pop(); return world; } ecs_world_t *ecs_init(void) { ecs_world_t *world = ecs_mini(); #ifdef FLECS_MODULE_H ecs_trace("#[bold]import addons"); ecs_log_push(); ecs_trace("use ecs_mini to create world without importing addons"); #ifdef FLECS_SYSTEM ECS_IMPORT(world, FlecsSystem); #endif #ifdef FLECS_PIPELINE ECS_IMPORT(world, FlecsPipeline); #endif #ifdef FLECS_TIMER ECS_IMPORT(world, FlecsTimer); #endif #ifdef FLECS_META ECS_IMPORT(world, FlecsMeta); #endif #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); #endif #ifdef FLECS_COREDOC ECS_IMPORT(world, FlecsCoreDoc); #endif #ifdef FLECS_SCRIPT ECS_IMPORT(world, FlecsScript); #endif #ifdef FLECS_REST ECS_IMPORT(world, FlecsRest); #endif #ifdef FLECS_UNITS ecs_trace("#[green]module#[reset] flecs.units is not automatically imported"); #endif ecs_trace("addons imported!"); ecs_log_pop(); #endif return world; } ecs_world_t* ecs_init_w_args( int argc, char *argv[]) { ecs_world_t *world = ecs_init(); (void)argc; (void)argv; #ifdef FLECS_DOC if (argc) { char *app = argv[0]; char *last_elem = strrchr(app, '/'); if (!last_elem) { last_elem = strrchr(app, '\\'); } if (last_elem) { app = last_elem + 1; } ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); } #endif return world; } void ecs_quit( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); world->flags |= EcsWorldQuit; error: return; } bool ecs_should_quit( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return ECS_BIT_IS_SET(world->flags, EcsWorldQuit); error: return true; } void flecs_notify_tables( ecs_world_t *world, ecs_id_t id, ecs_table_event_t *event) { ecs_poly_assert(world, ecs_world_t); /* If no id is specified, broadcast to all tables */ if (!id) { ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_table_notify(world, table, event); } /* If id is specified, only broadcast to tables with id */ } else { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return; } ecs_table_cache_iter_t it; const ecs_table_record_t *tr; flecs_table_cache_all_iter(&idr->cache, &it); while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { flecs_table_notify(world, tr->hdr.table, event); } } } void ecs_default_ctor( void *ptr, int32_t count, const ecs_type_info_t *ti) { ecs_os_memset(ptr, 0, ti->size * count); } static void flecs_default_copy_ctor(void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->ctor(dst_ptr, count, ti); cl->copy(dst_ptr, src_ptr, count, ti); } static void flecs_default_move_ctor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->ctor(dst_ptr, count, ti); cl->move(dst_ptr, src_ptr, count, ti); } static void flecs_default_ctor_w_move_w_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->ctor(dst_ptr, count, ti); cl->move(dst_ptr, src_ptr, count, ti); cl->dtor(src_ptr, count, ti); } static void flecs_default_move_ctor_w_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->move_ctor(dst_ptr, src_ptr, count, ti); cl->dtor(src_ptr, count, ti); } static void flecs_default_move(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->move(dst_ptr, src_ptr, count, ti); } static void flecs_default_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { /* When there is no move, destruct the destination component & memcpy the * component to dst. The src component does not have to be destructed when * a component has a trivial move. */ const ecs_type_hooks_t *cl = &ti->hooks; cl->dtor(dst_ptr, count, ti); ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); } static void flecs_default_move_w_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { /* If a component has a move, the move will take care of memcpying the data * and destroying any data in dst. Because this is not a trivial move, the * src component must also be destructed. */ const ecs_type_hooks_t *cl = &ti->hooks; cl->move(dst_ptr, src_ptr, count, ti); cl->dtor(src_ptr, count, ti); } void ecs_set_hooks_id( ecs_world_t *world, ecs_entity_t component, const ecs_type_hooks_t *h) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); /* Ensure that no tables have yet been created for the component */ ecs_assert( ecs_id_in_use(world, component) == false, ECS_ALREADY_IN_USE, ecs_get_name(world, component)); ecs_assert( ecs_id_in_use(world, ecs_pair(component, EcsWildcard)) == false, ECS_ALREADY_IN_USE, ecs_get_name(world, component)); ecs_type_info_t *ti = flecs_type_info_ensure(world, component); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(!ti->component || ti->component == component, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); if (!ti->size) { const EcsComponent *component_ptr = ecs_get( world, component, EcsComponent); /* Cannot register lifecycle actions for things that aren't a component */ ecs_check(component_ptr != NULL, ECS_INVALID_PARAMETER, NULL); /* Cannot register lifecycle actions for components with size 0 */ ecs_check(component_ptr->size != 0, ECS_INVALID_PARAMETER, NULL); ti->size = component_ptr->size; ti->alignment = component_ptr->alignment; } if (h->ctor) ti->hooks.ctor = h->ctor; if (h->dtor) ti->hooks.dtor = h->dtor; if (h->copy) ti->hooks.copy = h->copy; if (h->move) ti->hooks.move = h->move; if (h->copy_ctor) ti->hooks.copy_ctor = h->copy_ctor; if (h->move_ctor) ti->hooks.move_ctor = h->move_ctor; if (h->ctor_move_dtor) ti->hooks.ctor_move_dtor = h->ctor_move_dtor; if (h->move_dtor) ti->hooks.move_dtor = h->move_dtor; if (h->on_add) ti->hooks.on_add = h->on_add; if (h->on_remove) ti->hooks.on_remove = h->on_remove; if (h->on_set) ti->hooks.on_set = h->on_set; if (h->ctx) ti->hooks.ctx = h->ctx; if (h->binding_ctx) ti->hooks.binding_ctx = h->binding_ctx; if (h->ctx_free) ti->hooks.ctx_free = h->ctx_free; if (h->binding_ctx_free) ti->hooks.binding_ctx_free = h->binding_ctx_free; /* If no constructor is set, invoking any of the other lifecycle actions * is not safe as they will potentially access uninitialized memory. For * ease of use, if no constructor is specified, set a default one that * initializes the component to 0. */ if (!h->ctor && (h->dtor || h->copy || h->move)) { ti->hooks.ctor = ecs_default_ctor; } /* Set default copy ctor, move ctor and merge */ if (h->copy && !h->copy_ctor) { ti->hooks.copy_ctor = flecs_default_copy_ctor; } if (h->move && !h->move_ctor) { ti->hooks.move_ctor = flecs_default_move_ctor; } if (!h->ctor_move_dtor) { if (h->move) { if (h->dtor) { if (h->move_ctor) { /* If an explicit move ctor has been set, use callback * that uses the move ctor vs. using a ctor+move */ ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; } else { /* If no explicit move_ctor has been set, use * combination of ctor + move + dtor */ ti->hooks.ctor_move_dtor = flecs_default_ctor_w_move_w_dtor; } } else { /* If no dtor has been set, this is just a move ctor */ ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; } } else { /* If move is not set but move_ctor and dtor is, we can still set * ctor_move_dtor. */ if (h->move_ctor) { if (h->dtor) { ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; } else { ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; } } } } if (!h->move_dtor) { if (h->move) { if (h->dtor) { ti->hooks.move_dtor = flecs_default_move_w_dtor; } else { ti->hooks.move_dtor = flecs_default_move; } } else { if (h->dtor) { ti->hooks.move_dtor = flecs_default_dtor; } } } error: return; } const ecs_type_hooks_t* ecs_get_hooks_id( ecs_world_t *world, ecs_entity_t id) { const ecs_type_info_t *ti = ecs_get_type_info(world, id); if (ti) { return &ti->hooks; } return NULL; } void ecs_atfini( ecs_world_t *world, ecs_fini_action_t action, void *ctx) { ecs_poly_assert(world, ecs_world_t); ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); ecs_action_elem_t *elem = ecs_vec_append_t(NULL, &world->fini_actions, ecs_action_elem_t); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); elem->action = action; elem->ctx = ctx; error: return; } void ecs_run_post_frame( ecs_world_t *world, ecs_fini_action_t action, void *ctx) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_action_elem_t *elem = ecs_vec_append_t(&stage->allocator, &stage->post_frame_actions, ecs_action_elem_t); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); elem->action = action; elem->ctx = ctx; error: return; } /* Unset data in tables */ static void flecs_fini_unset_tables( ecs_world_t *world) { ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_table_remove_actions(world, table); } } /* Invoke fini actions */ static void flecs_fini_actions( ecs_world_t *world) { int32_t i, count = ecs_vec_count(&world->fini_actions); ecs_action_elem_t *elems = ecs_vec_first(&world->fini_actions); for (i = 0; i < count; i ++) { elems[i].action(world, elems[i].ctx); } ecs_vec_fini_t(NULL, &world->fini_actions, ecs_action_elem_t); } /* Cleanup remaining type info elements */ static void flecs_fini_type_info( ecs_world_t *world) { int32_t i, count = flecs_sparse_count(&world->type_info); ecs_sparse_t *type_info = &world->type_info; for (i = 0; i < count; i ++) { ecs_type_info_t *ti = flecs_sparse_get_dense_t(type_info, ecs_type_info_t, i); flecs_type_info_fini(ti); } flecs_sparse_fini(&world->type_info); } ecs_entity_t flecs_get_oneof( const ecs_world_t *world, ecs_entity_t e) { if (ecs_is_alive(world, e)) { if (ecs_has_id(world, e, EcsOneOf)) { return e; } else { return ecs_get_target(world, e, EcsOneOf, 0); } } else { return 0; } } /* The destroyer of worlds */ int ecs_fini( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); ecs_assert(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); ecs_assert(world->stages[0].defer == 0, ECS_INVALID_OPERATION, "call defer_end before destroying world"); ecs_trace("#[bold]shutting down world"); ecs_log_push(); world->flags |= EcsWorldQuit; /* Delete root entities first using regular APIs. This ensures that cleanup * policies get a chance to execute. */ ecs_dbg_1("#[bold]cleanup root entities"); ecs_log_push_1(); flecs_fini_roots(world); ecs_log_pop_1(); world->flags |= EcsWorldFini; /* Run fini actions (simple callbacks ran when world is deleted) before * destroying the storage */ ecs_dbg_1("#[bold]run fini actions"); ecs_log_push_1(); flecs_fini_actions(world); ecs_log_pop_1(); ecs_dbg_1("#[bold]cleanup remaining entities"); ecs_log_push_1(); /* Operations invoked during UnSet/OnRemove/destructors are deferred and * will be discarded after world cleanup */ flecs_defer_begin(world, &world->stages[0]); /* Run UnSet/OnRemove actions for components while the store is still * unmodified by cleanup. */ flecs_fini_unset_tables(world); /* This will destroy all entities and components. */ flecs_fini_store(world); /* Purge deferred operations from the queue. This discards operations but * makes sure that any resources in the queue are freed */ flecs_defer_purge(world, &world->stages[0]); ecs_log_pop_1(); /* All queries are cleaned up, so monitors should've been cleaned up too */ ecs_assert(!ecs_map_is_init(&world->monitors.monitors), ECS_INTERNAL_ERROR, NULL); /* Cleanup world ctx and binding_ctx */ if (world->ctx_free) { world->ctx_free(world->ctx); } if (world->binding_ctx_free) { world->binding_ctx_free(world->binding_ctx); } /* After this point no more user code is invoked */ ecs_dbg_1("#[bold]cleanup world datastructures"); ecs_log_push_1(); flecs_entities_fini(world); flecs_sparse_fini(world->pending_tables); flecs_sparse_fini(world->pending_buffer); ecs_os_free(world->pending_tables); ecs_os_free(world->pending_buffer); flecs_fini_id_records(world); flecs_fini_type_info(world); flecs_observable_fini(&world->observable); flecs_name_index_fini(&world->aliases); flecs_name_index_fini(&world->symbols); ecs_set_stage_count(world, 0); ecs_log_pop_1(); flecs_world_allocators_fini(world); /* End of the world */ ecs_poly_free(world, ecs_world_t); ecs_os_fini(); ecs_trace("world destroyed, bye!"); ecs_log_pop(); return 0; } bool ecs_is_fini( const ecs_world_t *world) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return ECS_BIT_IS_SET(world->flags, EcsWorldFini); } void ecs_dim( ecs_world_t *world, int32_t entity_count) { ecs_poly_assert(world, ecs_world_t); flecs_entities_set_size(world, entity_count + FLECS_HI_COMPONENT_ID); } void flecs_eval_component_monitors( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); flecs_process_pending_tables(world); flecs_eval_component_monitor(world); } void ecs_measure_frame_time( ecs_world_t *world, bool enable) { ecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); if (ECS_EQZERO(world->info.target_fps) || enable) { ECS_BIT_COND(world->flags, EcsWorldMeasureFrameTime, enable); } error: return; } void ecs_measure_system_time( ecs_world_t *world, bool enable) { ecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); ECS_BIT_COND(world->flags, EcsWorldMeasureSystemTime, enable); error: return; } void ecs_set_target_fps( ecs_world_t *world, ecs_ftime_t fps) { ecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); ecs_measure_frame_time(world, true); world->info.target_fps = fps; error: return; } void* ecs_get_ctx( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->ctx; error: return NULL; } void* ecs_get_binding_ctx( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->binding_ctx; error: return NULL; } void ecs_set_ctx( ecs_world_t *world, void *ctx, ecs_ctx_free_t ctx_free) { ecs_poly_assert(world, ecs_world_t); world->ctx = ctx; world->ctx_free = ctx_free; } void ecs_set_binding_ctx( ecs_world_t *world, void *ctx, ecs_ctx_free_t ctx_free) { ecs_poly_assert(world, ecs_world_t); world->binding_ctx = ctx; world->binding_ctx_free = ctx_free; } void ecs_set_entity_range( ecs_world_t *world, ecs_entity_t id_start, ecs_entity_t id_end) { ecs_poly_assert(world, ecs_world_t); ecs_check(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); ecs_check(!id_end || id_end > flecs_entities_max_id(world), ECS_INVALID_PARAMETER, NULL); uint32_t start = (uint32_t)id_start; uint32_t end = (uint32_t)id_end; if (flecs_entities_max_id(world) < start) { flecs_entities_max_id(world) = start - 1; } world->info.min_id = start; world->info.max_id = end; error: return; } bool ecs_enable_range_check( ecs_world_t *world, bool enable) { ecs_poly_assert(world, ecs_world_t); bool old_value = world->range_check_enabled; world->range_check_enabled = enable; return old_value; } ecs_entity_t ecs_get_max_id( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_entities_max_id(world); error: return 0; } void ecs_set_entity_generation( ecs_world_t *world, ecs_entity_t entity_with_generation) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); ecs_assert(!(ecs_is_deferred(world)), ECS_INVALID_OPERATION, NULL); flecs_entities_set_generation(world, entity_with_generation); ecs_record_t *r = flecs_entities_get(world, entity_with_generation); if (r && r->table) { int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_entity_t *entities = r->table->data.entities.array; entities[row] = entity_with_generation; } } const ecs_type_info_t* flecs_type_info_get( const ecs_world_t *world, ecs_entity_t component) { ecs_poly_assert(world, ecs_world_t); ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); return flecs_sparse_try_t(&world->type_info, ecs_type_info_t, component); } ecs_type_info_t* flecs_type_info_ensure( ecs_world_t *world, ecs_entity_t component) { ecs_poly_assert(world, ecs_world_t); ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = flecs_type_info_get(world, component); ecs_type_info_t *ti_mut = NULL; if (!ti) { ti_mut = flecs_sparse_ensure_t( &world->type_info, ecs_type_info_t, component); ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); ti_mut->component = component; } else { ti_mut = ECS_CONST_CAST(ecs_type_info_t*, ti); } if (!ti_mut->name) { const char *sym = ecs_get_symbol(world, component); if (sym) { ti_mut->name = ecs_os_strdup(sym); } else { const char *name = ecs_get_name(world, component); if (name) { ti_mut->name = ecs_os_strdup(name); } } } return ti_mut; } bool flecs_type_info_init_id( ecs_world_t *world, ecs_entity_t component, ecs_size_t size, ecs_size_t alignment, const ecs_type_hooks_t *li) { bool changed = false; flecs_entities_ensure(world, component); ecs_type_info_t *ti = NULL; if (!size || !alignment) { ecs_assert(size == 0 && alignment == 0, ECS_INVALID_COMPONENT_SIZE, NULL); ecs_assert(li == NULL, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); flecs_sparse_remove_t(&world->type_info, ecs_type_info_t, component); } else { ti = flecs_type_info_ensure(world, component); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); changed |= ti->size != size; changed |= ti->alignment != alignment; ti->size = size; ti->alignment = alignment; if (li) { ecs_set_hooks_id(world, component, li); } } /* Set type info for id record of component */ ecs_id_record_t *idr = flecs_id_record_ensure(world, component); changed |= flecs_id_record_set_type_info(world, idr, ti); bool is_tag = idr->flags & EcsIdTag; /* All id records with component as relationship inherit type info */ idr = flecs_id_record_ensure(world, ecs_pair(component, EcsWildcard)); do { if (is_tag) { changed |= flecs_id_record_set_type_info(world, idr, NULL); } else if (ti) { changed |= flecs_id_record_set_type_info(world, idr, ti); } else if ((idr->type_info != NULL) && (idr->type_info->component == component)) { changed |= flecs_id_record_set_type_info(world, idr, NULL); } } while ((idr = idr->first.next)); /* All non-tag id records with component as object inherit type info, * if relationship doesn't have type info */ idr = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, component)); do { if (!(idr->flags & EcsIdTag) && !idr->type_info) { changed |= flecs_id_record_set_type_info(world, idr, ti); } } while ((idr = idr->first.next)); /* Type info of (*, component) should always point to component */ ecs_assert(flecs_id_record_get(world, ecs_pair(EcsWildcard, component))-> type_info == ti, ECS_INTERNAL_ERROR, NULL); return changed; } void flecs_type_info_fini( ecs_type_info_t *ti) { if (ti->hooks.ctx_free) { ti->hooks.ctx_free(ti->hooks.ctx); } if (ti->hooks.binding_ctx_free) { ti->hooks.binding_ctx_free(ti->hooks.binding_ctx); } if (ti->name) { /* Safe to cast away const, world has ownership over string */ ecs_os_free(ECS_CONST_CAST(char*, ti->name)); ti->name = NULL; } } void flecs_type_info_free( ecs_world_t *world, ecs_entity_t component) { if (world->flags & EcsWorldQuit) { /* If world is in the final teardown stages, cleanup policies are no * longer applied and it can't be guaranteed that a component is not * deleted before entities that use it. The remaining type info elements * will be deleted after the store is finalized. */ return; } ecs_type_info_t *ti = flecs_sparse_try_t(&world->type_info, ecs_type_info_t, component); if (ti) { flecs_type_info_fini(ti); flecs_sparse_remove_t(&world->type_info, ecs_type_info_t, component); } } static ecs_ftime_t flecs_insert_sleep( ecs_world_t *world, ecs_time_t *stop) { ecs_poly_assert(world, ecs_world_t); ecs_time_t start = *stop, now = start; ecs_ftime_t delta_time = (ecs_ftime_t)ecs_time_measure(stop); if (ECS_EQZERO(world->info.target_fps)) { return delta_time; } ecs_ftime_t target_delta_time = ((ecs_ftime_t)1.0 / (ecs_ftime_t)world->info.target_fps); /* Calculate the time we need to sleep by taking the measured delta from the * previous frame, and subtracting it from target_delta_time. */ ecs_ftime_t sleep = target_delta_time - delta_time; /* Pick a sleep interval that is 4 times smaller than the time one frame * should take. */ ecs_ftime_t sleep_time = sleep / (ecs_ftime_t)4.0; do { /* Only call sleep when sleep_time is not 0. On some platforms, even * a sleep with a timeout of 0 can cause stutter. */ if (ECS_NEQZERO(sleep_time)) { ecs_sleepf((double)sleep_time); } now = start; delta_time = (ecs_ftime_t)ecs_time_measure(&now); } while ((target_delta_time - delta_time) > (sleep_time / (ecs_ftime_t)2.0)); *stop = now; return delta_time; } static ecs_ftime_t flecs_start_measure_frame( ecs_world_t *world, ecs_ftime_t user_delta_time) { ecs_poly_assert(world, ecs_world_t); ecs_ftime_t delta_time = 0; if ((world->flags & EcsWorldMeasureFrameTime) || (ECS_EQZERO(user_delta_time))) { ecs_time_t t = world->frame_start_time; do { if (world->frame_start_time.nanosec || world->frame_start_time.sec){ delta_time = flecs_insert_sleep(world, &t); } else { ecs_time_measure(&t); if (ECS_NEQZERO(world->info.target_fps)) { delta_time = (ecs_ftime_t)1.0 / world->info.target_fps; } else { /* Best guess */ delta_time = (ecs_ftime_t)1.0 / (ecs_ftime_t)60.0; } } /* Keep trying while delta_time is zero */ } while (ECS_EQZERO(delta_time)); world->frame_start_time = t; /* Keep track of total time passed in world */ world->info.world_time_total_raw += (ecs_ftime_t)delta_time; } return (ecs_ftime_t)delta_time; } static void flecs_stop_measure_frame( ecs_world_t* world) { ecs_poly_assert(world, ecs_world_t); if (world->flags & EcsWorldMeasureFrameTime) { ecs_time_t t = world->frame_start_time; world->info.frame_time_total += (ecs_ftime_t)ecs_time_measure(&t); } } ecs_ftime_t ecs_frame_begin( ecs_world_t *world, ecs_ftime_t user_delta_time) { ecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); ecs_check(ECS_NEQZERO(user_delta_time) || ecs_os_has_time(), ECS_MISSING_OS_API, "get_time"); /* Start measuring total frame time */ ecs_ftime_t delta_time = flecs_start_measure_frame(world, user_delta_time); if (ECS_EQZERO(user_delta_time)) { user_delta_time = delta_time; } world->info.delta_time_raw = user_delta_time; world->info.delta_time = user_delta_time * world->info.time_scale; /* Keep track of total scaled time passed in world */ world->info.world_time_total += world->info.delta_time; ecs_run_aperiodic(world, 0); return world->info.delta_time; error: return (ecs_ftime_t)0; } void ecs_frame_end( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); world->info.frame_count_total ++; ecs_stage_t *stages = world->stages; int32_t i, count = world->stage_count; for (i = 0; i < count; i ++) { flecs_stage_merge_post_frame(world, &stages[i]); } flecs_stop_measure_frame(world); error: return; } const ecs_world_info_t* ecs_get_world_info( const ecs_world_t *world) { world = ecs_get_world(world); return &world->info; } void flecs_delete_table( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); flecs_table_free(world, table); } static void flecs_process_empty_queries( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(ecs_id(EcsPoly), EcsQuery)); if (!idr) { return; } ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Make sure that we defer adding the inactive tags until after iterating * the query */ flecs_defer_begin(world, &world->stages[0]); ecs_table_cache_iter_t it; const ecs_table_record_t *tr; if (flecs_table_cache_iter(&idr->cache, &it)) { while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_query_t *query = queries[i].poly; ecs_entity_t *entities = table->data.entities.array; if (!ecs_query_table_count(query)) { ecs_add_id(world, entities[i], EcsEmpty); } } } } flecs_defer_end(world, &world->stages[0]); } /** Walk over tables that had a state change which requires bookkeeping */ void flecs_process_pending_tables( const ecs_world_t *world_r) { ecs_poly_assert(world_r, ecs_world_t); /* We can't update the administration while in readonly mode, but we can * ensure that when this function is called there are no pending events. */ if (world_r->flags & EcsWorldReadonly) { ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, ECS_INTERNAL_ERROR, NULL); return; } /* Safe to cast, world is not readonly */ ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, world_r); /* If pending buffer is NULL there already is a stackframe that's iterating * the table list. This can happen when an observer for a table event results * in a mutation that causes another table to change state. A typical * example of this is a system that becomes active/inactive as the result of * a query (and as a result, its matched tables) becoming empty/non empty */ if (!world->pending_buffer) { return; } /* Swap buffer. The logic could in theory have been implemented with a * single sparse set, but that would've complicated (and slowed down) the * iteration. Additionally, by using a double buffer approach we can still * keep most of the original ordering of events intact, which is desirable * as it means that the ordering of tables in the internal datastructures is * more predictable. */ int32_t i, count = flecs_sparse_count(world->pending_tables); if (!count) { return; } flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); do { ecs_sparse_t *pending_tables = world->pending_tables; world->pending_tables = world->pending_buffer; world->pending_buffer = NULL; /* Make sure that any ECS operations that occur while delivering the * events does not cause inconsistencies, like sending an Empty * notification for a table that just became non-empty. */ flecs_defer_begin(world, &world->stages[0]); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t( pending_tables, ecs_table_t*, i)[0]; if (!table->id) { /* Table is being deleted, ignore empty events */ continue; } /* For each id in the table, add it to the empty/non empty list * based on its current state */ if (flecs_table_records_update_empty(table)) { int32_t table_count = ecs_table_count(table); if (table->flags & (EcsTableHasOnTableFill|EcsTableHasOnTableEmpty)) { /* Only emit an event when there was a change in the * administration. It is possible that a table ended up in the * pending_tables list by going from empty->non-empty, but then * became empty again. By the time we run this code, no changes * in the administration would actually be made. */ ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; if (ecs_should_log_3()) { ecs_dbg_3("table %u state change (%s)", (uint32_t)table->id, table_count ? "non-empty" : "empty"); } ecs_log_push_3(); flecs_emit(world, world, &(ecs_event_desc_t){ .event = evt, .table = table, .ids = &table->type, .observable = world, .flags = EcsEventTableOnly }); ecs_log_pop_3(); } world->info.empty_table_count += (table_count == 0) * 2 - 1; } } flecs_sparse_clear(pending_tables); ecs_defer_end(world); world->pending_buffer = pending_tables; } while ((count = flecs_sparse_count(world->pending_tables))); flecs_journal_end(); } void flecs_table_set_empty( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_table_count(table)) { table->_->generation = 0; } flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, (uint32_t)table->id)[0] = table; } bool ecs_id_in_use( const ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return false; } return (flecs_table_cache_count(&idr->cache) != 0) || (flecs_table_cache_empty_count(&idr->cache) != 0); } void ecs_run_aperiodic( ecs_world_t *world, ecs_flags32_t flags) { ecs_poly_assert(world, ecs_world_t); if (!flags || (flags & EcsAperiodicEmptyTables)) { flecs_process_pending_tables(world); } if ((flags & EcsAperiodicEmptyQueries)) { flecs_process_empty_queries(world); } if (!flags || (flags & EcsAperiodicComponentMonitors)) { flecs_eval_component_monitors(world); } } int32_t ecs_delete_empty_tables( ecs_world_t *world, ecs_id_t id, uint16_t clear_generation, uint16_t delete_generation, int32_t min_id_count, double time_budget_seconds) { ecs_poly_assert(world, ecs_world_t); /* Make sure empty tables are in the empty table lists */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_time_t start = {0}, cur = {0}; int32_t delete_count = 0, clear_count = 0; bool time_budget = false; if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { ecs_time_measure(&start); } if (ECS_NEQZERO(time_budget_seconds)) { time_budget = true; } if (!id) { id = EcsAny; /* Iterate all empty tables */ } ecs_id_record_t *idr = flecs_id_record_get(world, id); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_empty_iter((ecs_table_cache_t*)idr, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { if (time_budget) { cur = start; if (ecs_time_measure(&cur) > time_budget_seconds) { goto done; } } ecs_table_t *table = tr->hdr.table; ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); if (table->type.count < min_id_count) { continue; } uint16_t gen = ++ table->_->generation; if (delete_generation && (gen > delete_generation)) { flecs_table_free(world, table); delete_count ++; } else if (clear_generation && (gen > clear_generation)) { if (flecs_table_shrink(world, table)) { clear_count ++; } } } } done: if (ecs_should_log_1() && ecs_os_has_time()) { if (delete_count) { ecs_dbg_1("#[red]deleted#[normal] %d empty tables in %.2fs", delete_count, ecs_time_measure(&start)); } if (clear_count) { ecs_dbg_1("#[red]cleared#[normal] %d empty tables in %.2fs", clear_count, ecs_time_measure(&start)); } } return delete_count; }