3.6 KiB
3.6 KiB
Query
(no submodules)
Purpose
Query compiles a declarative QueryDescriptor (include / optional exclude schemas) into bitset masks and evaluates it against the archetype graph. Because archetypes group entities by their exact component set, a query only needs to test each archetype's mask once — every entity inside a matching archetype is automatically a match. The result is O(archetypes) filtering plus O(entities-in-matching-archetypes) collection.
defineQuery is the canonical factory; raw Query instances are exposed for advanced consumers building custom system runners.
Features
- Bitset-only match predicate (
containsAll+intersects). - Cached
matchedArchetypeslist, invalidated byWorldwhenever archetypes change. - Handles include-only, include + exclude, and empty-exclude descriptors cleanly.
- Result is a flat
ReadonlyArray<Entity>in archetype-discovery order.
Usage: Basic Example
import { defineComponent, defineQuery, createWorld } from "@suaveplan/ecs";
const Position = defineComponent("Position", { x: 0, y: 0 });
const world = createWorld();
const entity = world.spawn();
world.addComponent(entity, Position);
const result = world.query(defineQuery([Position]));
console.log([...result]); // [1]
Usage: Advanced Example
import {
defineComponent,
defineQuery,
Query,
createWorld,
} from "@suaveplan/ecs";
const Position = defineComponent("Position", {});
const Disabled = defineComponent("Disabled", {});
const world = createWorld();
world.registerComponent(Position);
world.registerComponent(Disabled);
// Build a long-lived Query and re-evaluate on every tick.
const activeMovers = new Query(
defineQuery([Position], [Disabled]),
world.registry,
);
function tick() {
const entities = activeMovers.evaluate(
// archetype graph is exposed indirectly via world.query; advanced
// consumers run their own archetype scans through reflection.
[],
);
// ... iterate entities ...
}
API Reference
| Member | Description |
|---|---|
new Query(descriptor, registry) |
Compile masks from a descriptor. Throws if descriptor references unregistered schemas. |
defineQuery(include, exclude?) |
Build a descriptor with optional exclude. |
matches(archetype) |
Predicate: does this archetype's mask satisfy the query? |
evaluate(archetypes) |
Collect every entity from every matching archetype. Result is cached until invalidate(). |
invalidate() |
Drop the cached match list; the next evaluate rebuilds it. |
includeMask / excludeMask |
Read-only masks for debug / inspection. |
Implementation Notes
excludeMaskisnull(not an emptyBitSet) when no exclude is declared, so the predicate skips theintersectstest entirely in the common case.- Empty
exclude: []is treated as "no exclude" — the descriptor normalises the difference. - The world owns its
Queryobjects; one-offworld.query(descriptor)calls build an ad-hocQueryper call and discard it. Long-lived systems should pin aQueryinstance and callevaluaterepeatedly. - The cached
matchedArchetypeslist is invalidated whenever the archetype graph mutates (component add/remove that creates a new archetype, orclear). Mutations within an existing archetype do not invalidate — entity lists are read fresh on every evaluate. - Iteration order is "every entity in archetype 0, then every entity in archetype 1, …". Within an archetype the order matches insertion order (subject to swap-remove side-effects).