Queries and Iteration
A query is "give me every entity that has at least this set of components." Because component presence is a bit in the archetype mask, the per-table check is a single table.mask & query_mask == query_mask. Tables that fail the check are skipped without looking at a single entity inside them.
Basic queries
#![allow(unused)] fn main() { for entity in world.core.query_entities(LOCAL_TRANSFORM | GLOBAL_TRANSFORM) { if let Some(transform) = world.core.get_local_transform(entity) { let position = transform.translation; } } }
The query yields every entity whose archetype mask is a superset of the requested mask. An entity with LOCAL_TRANSFORM | GLOBAL_TRANSFORM | RENDER_MESH matches a query for LOCAL_TRANSFORM | GLOBAL_TRANSFORM.
The cost of this form is one location-map lookup per get_local_transform call. For read-only loops it is the most ergonomic API. For tight inner loops there are closure-form alternatives that hand the closure raw access to the archetype table, no per-entity lookup. See Querying Entities for that pattern.
Common patterns
Renderable entities
The renderer iterates one combined mask each frame.
#![allow(unused)] fn main() { const RENDERABLE: ComponentFlags = LOCAL_TRANSFORM | GLOBAL_TRANSFORM | RENDER_MESH | MATERIAL_REF; for entity in world.core.query_entities(RENDERABLE) { let transform = world.core.get_global_transform(entity).unwrap(); let mesh = world.core.get_render_mesh(entity).unwrap(); } }
Physics entities
#![allow(unused)] fn main() { for entity in world.core.query_entities(RIGID_BODY | LOCAL_TRANSFORM) { if let Some(rb) = world.core.get_rigid_body(entity) { if rb.body_type == RigidBodyType::Dynamic { // process dynamic bodies } } } }
Animated entities
#![allow(unused)] fn main() { for entity in world.core.query_entities(ANIMATION_PLAYER) { if let Some(player) = world.core.get_animation_player_mut(entity) { player.speed = 1.0; } } }
First match
For singleton-like entities (the player, the active camera, the world origin), pull the first match off the iterator.
#![allow(unused)] fn main() { let player = world.core.query_entities(CHARACTER_CONTROLLER).next(); if let Some(player_entity) = player { let controller = world.core.get_character_controller(player_entity); } }
Filtering
query_entities filters at the table level. A finer per-entity filter is just a normal if inside the loop body.
#![allow(unused)] fn main() { for entity in world.core.query_entities(LIGHT) { let light = world.core.get_light(entity).unwrap(); if light.light_type == LightType::Point { // point lights only } } }
The cost of this shape is that the iterator visits every entity with the requested bits, then filters in Rust. If the secondary filter is hot and selective, splitting the data into two archetypes (a separate flag per variant) is the faster shape.
Entity counts
#![allow(unused)] fn main() { let renderable_count = world.core.query_entities(RENDER_MESH).count(); let light_count = world.core.query_entities(LIGHT).count(); }
count consumes the iterator and walks every matching table's entities.len(). It is O(number of matching tables), not O(number of matching entities).
Looking entities up by name
If entities carry the Name component, the engine keeps a HashMap<String, Entity> in world.resources.entity_names. A direct hash lookup is the fast path.
#![allow(unused)] fn main() { let player = world.resources.entity_names.get("Player").copied(); }
A scan-based fallback is also available if you do not trust the cache or you are doing case-insensitive matching.
#![allow(unused)] fn main() { fn find_by_name(world: &World, name: &str) -> Option<Entity> { for entity in world.core.query_entities(NAME) { if let Some(entity_name) = world.core.get_name(entity) { if entity_name.0 == name { return Some(entity); } } } None } let player = find_by_name(world, "Player"); }
Walking children
Parent-to-child links are not stored on the parent. The engine maintains a HashMap<Entity, Vec<Entity>> cache so the children of a given entity can be found without scanning every PARENT component.
#![allow(unused)] fn main() { if let Some(children) = world.resources.children_cache.get(&parent_entity) { for child in children { if let Some(transform) = world.core.get_local_transform(*child) { // process child } } } }
Collecting for deferred work
Mutating the world during iteration invalidates the iterator. The fix is to collect the matching entity handles first, release the borrow, then operate.
#![allow(unused)] fn main() { let enemies: Vec<Entity> = world.core.query_entities(ENEMY_TAG | HEALTH).collect(); for enemy in &enemies { apply_damage(world, *enemy, 10.0); } }
The cost is one allocation per collected query. For structural mutations during iteration that should happen at a defined point in the frame, the better fit is the command queue described in Tags, Events, and Commands.
Iteration with index
#![allow(unused)] fn main() { for (index, entity) in world.core.query_entities(RENDER_MESH).enumerate() { // index is the position within the iteration, not the entity id } }
enumerate gives the iteration position, not the entity's internal id. For the id itself, read entity.id directly.