m01 · Entities Core · reading · 8 min
Queries — how iteration finds its chunks
A query is a description, resolved against archetypes
A query names a set of component requirements — must have these, must not have those, optionally has these:
// must have LocalTransform and Velocity, must not have Frozen
var query = SystemAPI.QueryBuilder()
.WithAll<LocalTransform, Velocity>()
.WithNone<Frozen>()
.Build();
The critical thing about how this executes: the query is not a scan
over every entity checking whether it matches. It is resolved against
the set of archetypes. An archetype either satisfies the requirements
or it doesn’t — that’s a property of its component set, decided once —
and the runtime caches which archetypes match. Iterating the query then
means visiting the
This is why WithAll/WithNone/WithAny are cheap regardless of how
many entities exist: the matching decision is per-archetype (there are
usually tens to low hundreds of archetypes) not per-entity (there may be
millions). The tag lesson’s claim — “filtering by tag is a chunk-level
selection” — is just this mechanism: WithAll<Enemy>() narrows the set
of matching archetypes to those containing Enemy, and you iterate only
their chunks.
Iterating a chunk is the fast path made real
Once you’re inside a matching chunk, you get contiguous LocalTransform and Velocity already resident
by the time it needs them.
Everything upstream — archetypes, chunks, the 16 KB size, SoA — exists so that this innermost loop is cache-optimal without the author doing anything special. You wrote a component-set requirement; you got a prefetcher-friendly linear walk. That’s the whole architecture paying off in one place.
Change filtering: skipping chunks you don’t need to read
Every chunk stamps a change version on each component type whenever that component is written through a system. A query can filter on it:
query.SetChangedVersionFilter(typeof(LocalTransform));
Now iteration visits only the chunks whose LocalTransform was written
since this system last ran. The comparison is a single integer check
per chunk, made against the chunk header — no entity data is touched
to decide whether to skip. A system that reacts to movement can ignore
thousands of stationary entities by skipping their chunks wholesale,
paying only one version comparison per skipped chunk instead of reading
any of their transforms.
The granularity is the thing to understand: change versions are
per-chunk, per-type, not per-entity. Writing one entity’s
LocalTransform bumps the version for the whole chunk, so change
filtering says “something in this chunk changed,” not “these specific
entities changed.” It’s a coarse filter that’s nearly free — exactly the
right tradeoff for skipping large idle populations, and the wrong tool
if you need per-entity change precision.
Order is the runtime’s, and that’s the point
You do not control the order in which a query yields entities. It’s
archetype by archetype, chunk by chunk, in whatever arrangement the
runtime holds them — and that arrangement can shift as entities are
created, destroyed, or moved between archetypes by
This is not a limitation to work around; it’s a precondition for the performance. Because you don’t demand a particular order, the runtime is free to store entities in the layout that’s fastest to iterate, and to hand independent chunks to independent worker threads without reconciling a global order. Order-independence is what makes the parallelism in the Jobs module legal. If you need entities processed in a specific sequence, that ordering has to be expressed as data — a sort key you read — not assumed from iteration.
What this buys you
You can now trace a query from a component-set description down to a cache-optimal loop over exactly the right chunks, and you know the two levers — component requirements and change versions — that decide which chunks get visited at all. That completes the storage-and-iteration picture: archetypes group by set, chunks hold 16 KB of SoA data, tags and shared components shape the partitioning, and queries turn all of it into a linear walk. The lab makes you generate the archetypes yourself and watch the chunk counts move as you change component representations.