Query

A query is the beating heart of the ECS.

They provide a declarative method to iterate, modify, and inspect the world's entities and their components.

In short a query is a declaration of which components to access, though they allow for so much more, such as filtering or excluding entities with certain components, change detection, relationship and graph traversal and much much more.

use flax::{
    component, entity_ids, CommandBuffer, Component, Debuggable, Entity, EntityBorrow, FetchExt,
    Mutable, Query, QueryBorrow, Schedule, System, World,
};
use glam::{vec2, Vec2};
use rand::{rngs::StdRng, Rng, SeedableRng};

fn main() -> anyhow::Result<()> {
    let mut world = World::new();

    component! {
        position: Vec2 => [ Debuggable ],
        health: f32 => [ Debuggable ],
    }

    // Spawn two entities
    let id = Entity::builder()
        .set(position(), vec2(1.0, 4.0))
        .set(health(), 75.0)
        .spawn(&mut world);

    let id2 = Entity::builder()
        .set(position(), vec2(-1.0, 4.0))
        .set(health(), 75.0)
        .spawn(&mut world);

    let mut query = Query::new((position(), health()));

    for (pos, health) in &mut query.borrow(&world) {
        println!("pos: {pos:?}, health: {health}");
    }


    component! {
        /// Distance to origin
        distance: f32 => [ flax::Debuggable ],
    }

    println!("Spawning id3");
    let id3 = world.spawn();
    world.set(id3, position(), vec2(5.0, 6.0))?;
    world.set(id3, health(), 5.0)?;

    for id in [id, id2, id3] {
        println!("Adding distance to {id}");
        world.set(id, distance(), 0.0)?;
    }

    let mut query = Query::new((entity_ids(), position(), distance().as_mut()))
        .filter(position().modified() & health().gt(0.0));

    println!("Updating distances");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }



    println!("Running query again");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }


    *world.get_mut(id2, position())? = vec2(8.0, 3.0);

    println!("... and again");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }


    #[allow(unused_variables)]
    {
        // Instead of this:
        let query = Query::new((position(), health(), distance()))
            .filter(position().modified() & health().modified());

        // Do this:
        let query = Query::new((position().modified(), health().modified(), distance()));
    }


    let mut update_distance = System::builder()
        .with_name("update_distance")
        .with_query(query)
        .build(
            |mut query: QueryBorrow<(_, Component<Vec2>, Mutable<f32>), _>| {
                for (id, pos, dist) in &mut query {
                    println!("Updating distance for {id} with position: {pos:?}");
                    *dist = pos.length();
                }
            },
        );

    update_distance.run(&mut world);

    let mut update_dist = System::builder()
        .with_name("update_distance")
        .with_query(
            Query::new((entity_ids(), position(), distance().as_mut()))
                .filter(position().modified()),
        )
        .for_each(|(id, pos, dist)| {
            tracing::debug!("Updating distance for {id} with position: {pos:?}");
            *dist = pos.length();
        });

    for _ in 0..16 {
        update_dist.run(&mut world);
    }


    // Despawn all entities with a distance > 50
    let despawn = System::builder()
        .with_name("delete_outside_world")
        .with_query(Query::new((entity_ids(), distance())).filter(distance().gt(50.0)))
        .with_cmd_mut()
        .build(|mut q: QueryBorrow<_, _>, cmd: &mut CommandBuffer| {
            for (id, &dist) in &mut q {
                println!("Despawning {id} at: {dist}");
                cmd.despawn(id);
            }
        });

    let debug_world = System::builder()
        .with_name("debug_world")
        .with_world()
        .build(|world: &_| {
            tracing::debug!("World: {world:?}");
        });


    component! {
        is_static: () => [ flax::Debuggable ],
    }

    // Spawn 150 static entities, which wont move
    let mut rng = StdRng::seed_from_u64(42);

    for _ in 0..150 {
        let pos = vec2(rng.gen_range(-5.0..5.0), rng.gen_range(-5.0..5.0));
        Entity::builder()
            .set(position(), pos)
            .set_default(distance())
            .set_default(is_static())
            .spawn(&mut world);
    }

    // Since this system will move non static entities out from the origin, they will
    // eventually be despawned
    let move_out = System::builder()
        .with_name("move_out")
        .with_query(Query::new(position().as_mut()).filter(is_static().without()))
        .for_each(|pos| {
            let dir = pos.normalize_or_zero();

            *pos += dir;
        });

    // Spawn new entities with a random position each frame
    let spawn = System::builder().with_name("spawner").with_cmd_mut().build(
        move |cmd: &mut CommandBuffer| {
            for _ in 0..100 {
                let pos = vec2(rng.gen_range(-10.0..10.0), rng.gen_range(-10.0..10.0));
                println!("Spawning new entity at: {pos:?}");
                Entity::builder()
                    .set(position(), pos)
                    .set_default(distance())
                    .spawn_into(cmd);
            }
        },
    );

    let mut frame_count = 0;

    // Count the number of entities in the world and log it
    let count = System::builder()
        .with_name("count")
        .with_query(Query::new(()))
        .build(move |mut query: QueryBorrow<()>| {
            let count: usize = query.iter_batched().map(|v| v.len()).sum();
            println!("[{frame_count}]: {count}");
            frame_count += 1;
        });

    // Assemble the schedule, takes care of dependency management
    let mut schedule = Schedule::builder()
        .with_system(update_dist)
        .with_system(despawn)
        .with_system(spawn)
        .with_system(move_out)
        .with_system(debug_world)
        .with_system(count)
        .build();

    println!("{schedule:#?}");

    for i in 0..20 {
        println!("Frame: {i}");
        println!("Batches: {:#?}", schedule.batch_info(&world));
        schedule.execute_par(&mut world)?;
    }


    component! {
        window_width: f32,
        window_height: f32,
        allow_vsync: bool,

        /// A static entity, which is always alive
        resources,
    }

    Entity::builder()
        .set(window_width(), 800.0)
        .set(window_height(), 600.0)
        .set(allow_vsync(), false)
        // Since `resources` is static, it is not required to spawn it
        .append_to(&mut world, resources())
        .unwrap();

    let mut query = Query::new((window_width(), window_height(), allow_vsync()))
        // Change the query strategy to only iterate the `resources` entity
        .entity(resources());

    let mut borrow = query.borrow(&world);
    let (width, height, vsync) = borrow.get().unwrap();
    println!("width: {width} height: {height}, vsync: {vsync}");


    drop(borrow);


    let mut window_system = System::builder()
        .with_query(query)
        .build(|mut q: EntityBorrow<_>| {
            if let Ok((width, height, allow_vsync)) = q.get() {
                println!(
                    "Config changed width: {width}, height: {height}, allow_vsync: {allow_vsync}"
                );
            } else {
                println!("No config change");
            }
        });

    window_system.run(&mut world);
    window_system.run(&mut world);
    world.set(resources(), window_height(), 720.0)?;
    window_system.run(&mut world);


    Ok(())
}

A query accepts any type which implements Fetch, such as

See Queries for more details

Filters

A query allows for filters, such as skipping entities which have a certain components, or where the value of a component satisfies some condition. This will efficiently skip entire archetypes or ranges of entities.

The following example shows a query which will update the distance to origin when an entity moves for every entity.

#![allow(unused)]

fn main() {
    component! {
        /// Distance to origin
        distance: f32 => [ flax::Debuggable ],
    }

    println!("Spawning id3");
    let id3 = world.spawn();
    world.set(id3, position(), vec2(5.0, 6.0))?;
    world.set(id3, health(), 5.0)?;

    for id in [id, id2, id3] {
        println!("Adding distance to {id}");
        world.set(id, distance(), 0.0)?;
    }

    let mut query = Query::new((entity_ids(), position(), distance().as_mut()))
        .filter(position().modified() & health().gt(0.0));

    println!("Updating distances");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }

}

The same query can be run again, but since all changes have been visited, it yields nothing.

#![allow(unused)]

fn main() {
    println!("Running query again");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }
}

However, if the position were to be modified, the query would yield that one change.

#![allow(unused)]

fn main() {
    *world.get_mut(id2, position())? = vec2(8.0, 3.0);

    println!("... and again");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }

}

For situations where an or combined filter is used in conjunction with a fetch of the same components, the filter may be attached directly to the query fetch instead.

#![allow(unused)]
fn main() {
        // Instead of this:
        let query = Query::new((position(), health(), distance()))
            .filter(position().modified() & health().modified());

        // Do this:
        let query = Query::new((position().modified(), health().modified(), distance()));
}