Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Querying Entities

query_entities(mask) is the basic shape. It returns an iterator over every entity whose archetype is a superset of mask.

#![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;
    }
}
}

Each get_local_transform(entity) inside the loop is a location-map lookup followed by an archetype-mask check. That overhead is fine for read-only outer loops. For inner loops the closure-form world.core.query() / world.core.query_mut() API in the prelude hands the closure raw table access, see the engine's query docs for that pattern.

Common patterns

Renderable entities

#![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 {
        }
    }
}
}

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;
    }
}
}

Filtering inside the loop

query_entities filters at the table level. A finer per-entity check goes in 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 {
    }
}
}

If the secondary filter is hot and selective, splitting the data into two archetypes is faster than filtering in Rust.

Checking components

#![allow(unused)]
fn main() {
if world.core.entity_has_components(entity, RENDER_MESH) {
}

if world.core.entity_has_components(entity, ANIMATION_PLAYER | SKIN) {
}
}

The check is one bitwise AND against the archetype mask. It does not read the component data.

First match

For singleton entities, next() pulls the first 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);
}
}

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 is O(number of matching tables), not O(number of matching entities). The iterator stride is the per-table entities.len().

Named lookup

world.resources.entity_names is a HashMap<String, Entity> populated for every entity carrying the Name component. Direct hash lookup is the fast path.

#![allow(unused)]
fn main() {
let player = world.resources.entity_names.get("Player").copied();
}

The scan fallback is also fine.

#![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");
}

Children of a parent

The engine keeps a HashMap<Entity, Vec<Entity>> of parent-to-children in world.resources.children_cache.

#![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) {
        }
    }
}
}

Iteration with index

#![allow(unused)]
fn main() {
for (index, entity) in world.core.query_entities(RENDER_MESH).enumerate() {
}
}

enumerate gives the iteration position. For the entity's internal id, read entity.id directly.

Collecting

Mutating the world during iteration invalidates the iterator. Collect the matching 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 deferred structural mutations that should happen at a defined point in the frame, the command queue in Tags, Events, and Commands is the right tool.