Skip to content

Resources

Resources are typed values stored once per world. Use them for state that is unique to the world, not attached to each entity: frame time, input state, renderer handles, asset registries, shared config, or global simulation settings.

Resources have their own ecs_resource_t ids. They do not consume component ids, they are not added to an entity, they are not query terms, and they do not consume an ecs_field() slot.

Resources are declared once and stored per world:

ECS_RESOURCE_DECLARE(Time, {
float dt;
float elapsed;
});
ECS_RESOURCE_DEFINE(Time);
ECS_RESOURCE_REGISTER(world, Time);

The resource macros intentionally mirror component macros, but resources are registered in a dedicated resource registry.

ecs_set_resource() creates or replaces the resource value:

ecs_set_resource(Time, {
.dt = 0.016f,
.elapsed = 0.0f,
});

Use ecs_get_resource() when the resource must exist:

Time *time = ecs_get_resource(Time);
time->elapsed += time->dt;

Use ecs_get_resource_read() when the caller only needs read access:

const Time *time = ecs_get_resource_read(Time);

If absence is valid, use the nullable helpers:

Time *time = ecs_try_get_resource(Time);
if (time != NULL) {
time->elapsed += time->dt;
}
const Time *read_time = ecs_try_get_resource_read(Time);

ecs_get_resource() and ecs_get_resource_read() assert when the resource does not exist. The try variants return NULL.

Check whether a resource exists with ecs_has_resource():

if (!ecs_has_resource(Time)) {
ecs_set_resource(Time, {
.dt = 0.0f,
.elapsed = 0.0f,
});
}

Remove it with ecs_remove_resource():

ecs_remove_resource(Time);

Removing a missing resource is a no-op.

In C, systems receive an ecs_iter_t *. Read resources from it->world before the entity loop:

static void move_system(ecs_iter_t *it) {
const Time *time = ecs_get_resource_read(it->world, Time);
Position *positions = ecs_field(it, 0);
const Velocity *velocities = ecs_field(it, 1);
for (uint32_t i = 0; i < it->count; i++) {
positions[i].x += velocities[i].x * time->dt;
positions[i].y += velocities[i].y * time->dt;
}
}
ecs_system({
.name = "Move",
.phase = EcsOnUpdate,
.query = {
.terms = { ecs_inout(Position), ecs_in(Velocity) },
},
.callback = move_system,
});

The resource is not part of the query. The system above matches entities with Position and Velocity; it does not require entities to have Time.

Typed helpers are wrappers around the id-based API:

void ecs_set_resource_rid(ecs_resource_t id, const void *data);
void *ecs_resource_rid(ecs_resource_t id);
void *ecs_try_resource_rid(ecs_resource_t id);
bool ecs_has_resource_rid(const ecs_resource_t id);
void ecs_remove_resource_rid(ecs_resource_t id);

This is useful for generic module code:

ecs_resource_t time_id = ecs_id(Time);

if (ecs_has_resource_rid(time_id)) {
    const Time *time = ecs_resource_rid(time_id);
    /* use time */
}

Resources have dedicated resource hooks:

  • on_set runs when ecs_set_resource() writes a value.
  • on_remove runs when ecs_remove_resource() removes the value.
  • on_remove also runs during ecs_fini() for resources still present in the world.

Hooks receive only the world and the resource value pointer.

static void on_time_set(const void *data) {
    (void)world;

    const Time *time = data;
    if (time->dt < 0.0f) {
        /* reject or record invalid data */
    }
}

Resource lookup is a direct index by ecs_resource_t, so access is O(1). The value is stored once in the world and is independent from table iteration.

For hot C systems, resolve the resource once before the loop over it->count, then reuse the pointer for every entity in the batch.