2170 lines
68 KiB
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;
|
|
}
|