Query
Queries retrieve entities based on their component composition.
Overview
Queries are the primary way to find entities in bitECS. You specify which components an entity must have (or not have), and get back a list of matching entity IDs.
Pattern Matching
Cached Results
Composable
Basic Queries
The simplest query finds all entities with a set of components:
import { query } from 'bitecs'
// Find all entities with Position
const positioned = query(world, [Position])
// Find all entities with Position AND Velocity
const moving = query(world, [Position, Velocity])
// Iterate over results
for (const entity of moving) {
Position.x[entity] += Velocity.x[entity]
Position.y[entity] += Velocity.y[entity]
}// Queries return arrays of entity IDs
const e1 = addEntity(world) // 1
const e2 = addEntity(world) // 2
const e3 = addEntity(world) // 3
addComponent(world, e1, Position)
addComponent(world, e2, Position)
addComponent(world, e2, Velocity)
addComponent(world, e3, Velocity)
query(world, [Position]) // [1, 2]
query(world, [Velocity]) // [2, 3]
query(world, [Position, Velocity]) // [2]Query Operators
Operators let you create more complex matching patterns:
| Operator | Alias | Description |
|---|---|---|
And(A, B) | All() | Entity must have ALL components (default) |
Or(A, B) | Any() | Entity must have ANY of the components |
Not(A) | None() | Entity must NOT have the component |
And / All
Matches entities that have all specified components. This is the default behavior when you list components in an array:
import { query, And, All } from 'bitecs'
// These are equivalent:
query(world, [Position, Velocity]) // Implicit And
query(world, [And(Position, Velocity)]) // Explicit And
query(world, [All(Position, Velocity)]) // AliasOr / Any
Matches entities that have at least one of the specified components:
import { query, Or, Any } from 'bitecs'
// Match entities with Position OR Velocity (or both)
query(world, [Or(Position, Velocity)])
query(world, [Any(Position, Velocity)])
// Practical use: entities that can take damage
query(world, [Or(Health, Shield, Armor)])Not / None
Excludes entities that have the specified components:
import { query, Not, None } from 'bitecs'
// Entities with Position but NOT Velocity (stationary)
query(world, [Position, Not(Velocity)])
// Exclude multiple components
query(world, [Enemy, None(Dead, Stunned, Frozen)])Complex Queries
Combine operators for sophisticated matching:
// Entities with Position, either Health OR Shield, but not Stunned
query(world, [
Position,
Or(Health, Shield),
Not(Stunned)
])
// Living enemies that can move
query(world, [
Enemy,
Position,
Velocity,
Not(Dead),
Not(Frozen)
])
// AI-controlled entities (not players) that need pathfinding
query(world, [
AI,
Position,
PathfindingTarget,
Not(Player),
Not(Disabled)
])Query Options
Queries accept options or modifiers for different behaviors:
| Option/Modifier | Description |
|---|---|
buffered: true / asBuffer | Return Uint32Array instead of readonly number[] |
commit: false / isNested | Skip committing pending entity removals |
import { query, asBuffer, isNested } from 'bitecs'
// Options object style
query(world, [Position], { commit: false }) // Skip removal commit
query(world, [Position], { buffered: true }) // Return Uint32Array
// Modifier style (more concise)
query(world, [Position], isNested) // Safe for nested iteration
query(world, [Position], asBuffer) // Return Uint32Array
query(world, [Position], asBuffer, isNested) // Combined
// asBuffer is useful for passing to web workers (transferable)
const buffer = query(world, [Position], asBuffer)
worker.postMessage({ entities: buffer }, [buffer.buffer])Nested Queries
By default, entity removals are deferred until a query is called. When nesting queries, use isNested to prevent removals during outer iteration:
// WRONG: Inner query may commit removals during outer iteration
for (const entity of query(world, [Position])) {
for (const other of query(world, [Target])) { // ❌ May break outer loop!
// ...
}
}
// CORRECT: Use isNested for inner queries
for (const entity of query(world, [Position])) {
for (const other of query(world, [Target], isNested)) { // ✓ Safe
// No removals committed during iteration
}
}
// Or use options object
for (const entity of query(world, [Position])) {
for (const other of query(world, [Target], { commit: false })) {
// Also safe
}
}
// Commit removals manually when done
import { commitRemovals } from 'bitecs'
commitRemovals(world)isNested for queries inside loops to avoid iterator invalidation bugs where entities disappear mid-iteration.Relation Queries
Query entities by their relationships using relation components:
import { query, Wildcard } from 'bitecs'
const ChildOf = createRelation()
// Find all children of a specific parent
query(world, [ChildOf(parentEntity)])
// Find all entities that are a child of ANYTHING
query(world, [ChildOf(Wildcard)])
query(world, [ChildOf('*')]) // Shorthand
// Find all entities at hierarchy depth 2
import { Hierarchy } from 'bitecs'
query(world, [Transform, Hierarchy(ChildOf, 2)])
// Process entities in hierarchy order (parents before children)
for (const eid of query(world, [Transform, Hierarchy(ChildOf)])) {
// Parent's worldTransform is guaranteed to be calculated first
updateWorldTransform(eid)
}Performance Tips
Cache Query Results
// Queries are cached - this is efficient
const movementSystem = (world) => {
// bitECS caches this query result and updates incrementally
for (const eid of query(world, [Position, Velocity])) {
Position.x[eid] += Velocity.x[eid]
Position.y[eid] += Velocity.y[eid]
}
}
// For many entities, consider TypedArray iteration
const entities = query(world, [Position, Velocity], asBuffer)
for (let i = 0; i < entities.length; i++) {
const eid = entities[i]
Position.x[eid] += Velocity.x[eid]
// Array index iteration can be faster than for-of for large counts
}