# 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 `matchedArchetypes` list, invalidated by `World` whenever archetypes change. - Handles include-only, include + exclude, and empty-exclude descriptors cleanly. - Result is a flat `ReadonlyArray` in archetype-discovery order. ## Usage: Basic Example ```ts 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 ```ts 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 - `excludeMask` is `null` (not an empty `BitSet`) when no exclude is declared, so the predicate skips the `intersects` test entirely in the common case. - Empty `exclude: []` is treated as "no exclude" — the descriptor normalises the difference. - The world owns its `Query` objects; one-off `world.query(descriptor)` calls build an ad-hoc `Query` per call and discard it. Long-lived systems should pin a `Query` instance and call `evaluate` repeatedly. - The cached `matchedArchetypes` list is invalidated whenever the archetype graph mutates (component add/remove that creates a new archetype, or `clear`). 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).