Files
PixelDefense/engine/libs/flecs/src/world.c

2170 lines
68 KiB
C

/**
* @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;
}