Queries
Queries match archetype tables and return component arrays in batches.
Create A Query
Section titled “Create A Query”ecs_query_id_t moving = ecs_query({ .terms = { ecs_inout(Position), ecs_in(Velocity) },});auto moving = ecs::query() .require<Position>() .require<Velocity>();The terms array is a zero-terminated list of component terms.
| Term | Meaning |
|---|---|
ecs_in(T) | T must exist locally or through inheritance and is returned by ecs_field() for reading. |
ecs_out(T) | T must exist locally and is returned by ecs_field() for writing. |
ecs_inout(T) | T must exist locally and is returned by ecs_field() for reading and writing. |
ecs_in_optional(T) | T may be absent and is returned as a nullable read field. |
ecs_inout_optional(T) | Local T may be absent and is returned as a nullable read/write field. |
ecs_filter(T) | T must exist but is not returned. |
ecs_not(T) | T must not exist. |
Current descriptor limits:
| Field | Maximum entries |
|---|---|
terms | 16 |
Queries exclude entities with the built-in Disabled component by default. This
implicit ecs_not(Disabled) term is added only when the query does not already
mention Disabled.
Use is_a when a query should only match entities that inherit from a specific
base entity:
ecs_query_id_t enemies = ecs_query({
.is_a = enemy_base,
.terms = { ecs_in(Position) },
});#include <siecs.h>
ecs_query_id_t enemies = ecs_query({
.is_a = enemy_base,
.terms = { ecs_in(Position) },
});Iterate
Section titled “Iterate”ecs_iter_t it = ecs_query_iter(moving);
while (ecs_iter_next(&it)) {
Position *positions = ecs_field(&it, 0);
Velocity *velocities = ecs_field(&it, 1);
for (uint32_t i = 0; i < it.count; i++) {
positions[i].x += velocities[i].x;
positions[i].y += velocities[i].y;
}
}#include <siecs.h>
ecs_iter_t it = ecs_query_iter(moving);
while (ecs_iter_next(&it)) {
Position *positions = ecs_field(&it, 0);
Velocity *velocities = ecs_field(&it, 1);
for (uint32_t i = 0; i < it.count; i++) {
positions[i].x += velocities[i].x;
positions[i].y += velocities[i].y;
}
}ecs_iter_next() advances to the next non-empty batch. it.count is the number
of entities in the current batch.
ecs_field(&it, index) returns component arrays for ecs_in, ecs_out,
ecs_inout, ecs_in_optional, and ecs_inout_optional terms in declaration
order. ecs_filter and ecs_not do not create field indexes.
In the C++ API, ecs::res<T> and ecs::res<const T> callback parameters are
resources, not query terms. They are resolved from the world before table
iteration and do not consume field indexes:
ecs::query().each([](ecs::res<const Time> time, Position &position, const Velocity &velocity) { position.x += velocity.x * time->dt;});Required And Excluded Components
Section titled “Required And Excluded Components”Use ecs_filter when a component must exist but does not need to be returned:
ecs_query_id_t visible_positions = ecs_query({
.terms = { ecs_inout(Position), ecs_filter(Visible) },
});#include <siecs.h>
ecs_query_id_t visible_positions = ecs_query({
.terms = { ecs_inout(Position), ecs_filter(Visible) },
});Use ecs_not to skip entities with a component:
ecs_query_id_t active_positions = ecs_query({
.terms = { ecs_inout(Position), ecs_not(Disabled) },
});#include <siecs.h>
ecs_query_id_t active_positions = ecs_query({
.terms = { ecs_inout(Position), ecs_not(Disabled) },
});The ecs_not(Disabled) term is implicit for normal queries, so the previous
example can usually be written as:
ecs_query_id_t active_positions = ecs_query({
.terms = { ecs_inout(Position) },
});#include <siecs.h>
ecs_query_id_t active_positions = ecs_query({
.terms = { ecs_inout(Position) },
});To match disabled entities, mention Disabled explicitly:
ecs_query_id_t disabled_positions = ecs_query({
.terms = { ecs_inout(Position), ecs_filter(Disabled) },
});#include <siecs.h>
ecs_query_id_t disabled_positions = ecs_query({
.terms = { ecs_inout(Position), ecs_filter(Disabled) },
});Optional Components
Section titled “Optional Components”Optional terms let one query handle tables where a component may or may not be present:
ecs_query_id_t q = ecs_query({
.terms = { ecs_inout(Position), ecs_in_optional(Velocity) },
});
ecs_iter_t it = ecs_query_iter(q);
while (ecs_iter_next(&it)) {
Position *positions = ecs_field(&it, 0);
const Velocity *velocities = ecs_field(&it, 1);
for (uint32_t i = 0; i < it.count; i++) {
if (velocities != NULL) {
positions[i].x += velocities[i].x;
}
}
}#include <siecs.h>
ecs_query_id_t q = ecs_query({
.terms = { ecs_inout(Position), ecs_in_optional(Velocity) },
});
ecs_iter_t it = ecs_query_iter(q);
while (ecs_iter_next(&it)) {
Position *positions = ecs_field(&it, 0);
const Velocity *velocities = ecs_field(&it, 1);
for (uint32_t i = 0; i < it.count; i++) {
if (velocities != NULL) {
positions[i].x += velocities[i].x;
}
}
}The optional field pointer is either a component array for the whole batch or
NULL for the whole batch. It does not vary per entity inside the same batch.
Temporary Query Loop
Section titled “Temporary Query Loop”ecs_query_each() is useful for setup, tests, tools, and short one-off scans:
ecs_query_each(it, i, ecs_in(Position)) {
const Position *positions = ecs_field(&it, 0);
(void)positions[i];
}#include <siecs.h>
ecs_query_each(it, i, ecs_in(Position)) {
const Position *positions = ecs_field(&it, 0);
(void)positions[i];
}It creates and destroys a temporary query. For repeated frame work, create a
persistent query once and iterate it with ecs_query_iter().
Destroy A Query
Section titled “Destroy A Query”ecs_query_fini(moving);#include <siecs.h>
ecs_query_fini(moving);Destroy queries before destroying the world if you no longer need them. The
world also owns query storage and releases it during ecs_fini().