Tainted \\ Coders

Bevy Queries

Last updated:

In Bevy all your game data for your entities is stored inside components.

Queries are a declarative way of specifying what Component data we want in our systems. They fetch components from your game World according to their specification when you iterate over them.

We specify our queries by adding them as parameters to our systems. Bevy will then inject the parameter into our system when they are scheduled.

These parameters are just like your other SystemParam parameters like Res, Assets<T> or EventWriter.

Specifying a query

The Query system parameter lets us specify the data we want from each entity using the two generic parameters:

//     ------- the world query
//    |  ---- the filter
//    v  v
Query<Q, F>

//      --------- Give us all the `Transform` components (readonly)
//     |     ---- Which have a `Player` component on the same entity
//     v     v
Query<&Ball, With<Player>>

//                     --- NOTE: Each parameter can be a tuple as well
//                    |          we will learn more about this later
//                    |
//                    v
Query<&mut Transform, (With<Player>, With<Living>)>

When you specify a Query as your system parameter it will not immediately fetch the data from your World. Its only when you iterate that query that it becomes an expensive operation.

This means that systems which conditionally iterate over a query will be very low cost. You only pay for what you use.

Component access

In the examples above you might have noticed that the Q parameter specifies either a &T or &mut T for the type. This represents the kind of borrow we want to do on the data we fetch from our world.

When we request &T we are asking for a readonly reference to the data:

Query<&Ball>

This is good because then Bevy can give readonly references to many systems that it will try and run in parallel to give us better performance.

That means we cannot change any of the values of the component unless we ask for it to be &mut T:

Query<&mut Ball>

The downside being that Bevy can’t run this system in parallel with other systems who reference this component.

Tuple parameters

Each generic parameter in Query<Q, F> can itself be a tuple.

Query<(&Ball, &Player)>

When a generic parameter is a tuple then all the types in that tuple must be satisfied by that query. So the above example is like saying:

Fetch me all ball and player components from every entity with both

It works similarly with filters, ensuring that all conditions are met.

Query fetch

The first parameter is your Query<Q, F> is your query fetch which tells Bevy exactly what data you want it to fetch you from your World.

#[derive(Component, Debug)]
struct Player;

fn fetch_players(
    query: Query<&Player>
) {
    for player in &query {
        info!("Player: {:?}", player);
    }
}

This query is equivalent to saying:

Fetch me every player component

But a tuple will change the meaning:

fn fetch_players_with_rocket(
    query: Query<(&Player, &Rocket)>
) {
    for (player, rocket) in &query {
        info!("Player: {:?}", player);
        info!("Rocket: {:?}", rocket);
    }
}

Now we are saying:

Fetch me every player and rocket from all the entities that have both player and rocket components

Simple tuple combinations won’t be enough to specify all the complicated queries we want to. There are convenient types that make expressing more complicated queries easy:

Parameter Description
Option<T> a component but only if it exists, otherwise None
AnyOf<T> fetches entities with any of the components in type T
Ref<T> shared borrow of an entity’s component T with access to change detection
Entity returns the entity

Option

Lets say we wanted to say:

Fetch me all players or astroids for all entities that have either one

We can pass a tuple, but those tuples represent and operations. To express this kind of or logic we can wrap each argument in an Option:

fn fetch_players_or_astroids(
    query: Query<(Option<&Player>, Option<&Astroid>)>
) {
    for (player, astroid) in &query {
        if let Some(player) = player {
            info!("Player: {:?}", player);
        }

        if let Some(astroid) = astroid {
            info!("Astroid: {:?}", astroid);
        }
    }
}

AnyOf

Query<AnyOf<(&Player, &Rocket, &mut Astroid)>>

Here we are saying:

Find me an optional player, rocket, or astroid from entities that have any of these components

That means that expanding it would be equivalent to:

Query<(
        Option<&Player>,
        Option<&Rocket>,
        Option<&mut Astroid>
    ),
    Or<(
        With<Player>,
        With<Rocket>,
        With<Astroid>
    )>>

Each type is returned as an Option<T> as the entities will have any of the types we specified.

Ref

If we wanted to know about how an entity has changed we can use the Ref<T> parameter:

fn react_to_player_spawning(
    query: Query<Ref<Player>>
) {
    for player in &query {
        if player.is_added() {
            // Do something
        }
    }
}

These borrows are immutable and don’t require unique access. So would be most equivalent to a Query<&Player> but we get some additional change tracking methods:

Method Description
is_added returns true if this value was added after the system ran
is_changed returns true if the value was added or mutably dereferenced either since the last time the system ran or, if the system never ran, since the beginning of the program
last_changed returns the change tick recording the time this data was most recently changed

Entity

We can, as part of our queries, request the Entity, which you can imagine as a unique ID.

fn fetch_entities(
    query: Query<Entity>
) {
    // ...
}

The Entity on its own isn’t very useful. But once we have this ID we can use certain methods on our queries to get components from that entity rather than all entities:

fn fetch_rocket_by_player_entity(
    players: Query<Entity, With<Player>>,
    query: Query<&Rocket>
) {
    for player in &players {
        let rocket = query.get(player).unwrap();
    }
}

Query filter

The second argument in your Query<Q, F> is the query filter. These filters are wrapped by a condition type:

With<T> only items with a T component
Without<T> only items without a T component
Or<F> checks if all filters in the tuple F apply
Changed<T> only components of type T that were changed this tick
Added<T> only components of type T that were added this tick

When you use a filter type for the change trackers like:

Query<Player, Added<Player>>

It is basically the same thing as using a Ref<T> and accessing the change trackers directly:

fn react_to_player_spawning(
    query: Query<Ref<Player>>
) {
    for player in &query {
      if player.is_added() {
            // Do something
      }
    }
}

In terms of performance these would be equivalent.

Disjointed queries

When querying for two mutable types that can contain the same components we must use Without to disjoint the set and follow Rust’s borrowing rules:

fn fetch_players_and_rockets(
    players: Query<&mut Player, With<Rocket>>,
    rockets: Query<&mut Player, With<Invincibility>>
) {
    // This will panic at runtime
}

Otherwise it would be too ambiguous whether there can exist entities which have both Rocket and Invincibility, which would lead to duplicated borrows on the same components.

An alternative would be to wrap the two queries into a ParamSet:

fn fetch_with_param_set(
    query: ParamSet<(
        Query<&mut Player, With<Rocket>>,
        Query<&mut Player, With<Invincibility>>
    )>
) {
    // This will not panic at runtime
}

Since Bevy 0.12 we can safely access multiple EntityMut values at once with disjointed queries:

fn disjointed_mutable_access(
    transforms: Query<EntityMut, With<Transform>>,
    entities: Query<EntityMut, Without<Transform>>
) {
    for entity in &entities {
        // Do stuff here
    }
}

This involved changing EntityMut to have a reduced scope. Before it could remove components, despawn entities, and provide mutable access to the entire world. But now they can only modify their own components.

Retrieving components

To retrieve components from our ECS storage our Query system parameter provides several methods:

iter returns an iterator over all items
for_each runs the given function in parallel for each item
iter_many runs a given function for each item matching a list of entities
iter_combinations returns an iterator over all combinations of a specified number of items
par_iter returns a parallel iterator
get returns a query item for a given entity
get_component<T> returns the component for a given entity
many returns a query item for a given list of entities
get_single the safe version of single which returns a Result<T>
single returns the query item while panicking if there are others
is_empty returns true if the query is empty
contains returns true if query contains a given entity

Each method also has a corresponding *_mut variant which will return the components with mutable ownership. This lets us change their data, instead of just reading it.

Retrieving components from a single entity

There are several different ways of retrieving a single entity’s components. Each has its own use case and can panic or not depending on your games logic.

Find a single entity

If we know there is only a single entity in a query we can use single/single_mut:

fn move_player(
    mut query: Query<&mut Transform>
) {
    let mut transform = query.single_mut();
    transform.translation.x += 1.;
}

However, this method would panic if there was more than one entity containing a Transform component.

Find a single entity safely

In cases where you are not 100% sure there will only be a single entity you should prefer the safer access versions get_single which returns a Result instead:

fn move_player_safely(
    mut query: Query<&mut Transform>
) {
    if let Ok(mut transform) = query.get_single_mut() {
        transform.translation.x += 1.
    }
}

Result is a built in type in Rust representing an operation that may or may not be successful. The Result is a union of Ok or Err. In the above example we use the idiomatic if let syntax which lets us change our x position only if the Result is an Ok.

Get a single component

In situations where we have a particular Entity (which is basically an ID), we can use get or get_mut.

A good time to use this can be when we store a resource (or anything else) and that component is guaranteed to be available throughout our game.

#[derive(Resource)]
struct PlayerRef(Entity);

fn move_player_by_component(
    mut query: Query<&mut Transform>,
    player: Res<PlayerRef>
) {
    if let Ok(mut transform) = query.get_mut(player.0) {
        transform.translation.x += 1.;
    }
}

Retrieving components from many entities

All queries over many components return an Iterator which will yield a tuple of components according to the Q generic of our Query.

Iterating queries

The most common way to provide components to our systems is to use the iter method to enumerate each component that exists:

fn move_players(
    mut query: Query<&mut Transform>
) {
    // We can enumerate all matches
    for mut transform in query.iter_mut() {
        transform.translation.x += 1.;
    }
}

Because the QueryState is iterable itself we can shorten the above to enumerating the query directly, instead of calling iter:

fn move_players_shorthand(
    mut query: Query<&mut Transform>
) {
    // We can enumerate all matches
    for mut transform in &mut query {
        transform.translation.x += 1.;
    }
}

Combinations

When we want to enumerate two sets of components, zip them up together, and enumerate the tuples of all the combinations, we can use iter_combinations:

#[derive(Component)]
struct Steering(Vec2);

#[derive(Component)]
struct Avoid;

const AVOID_DISTANCE: f32 = 100.;
const AVOIDANCE_FORCE: f32 = 0.1;

fn ship_avoidance_system(
    mut query: Query<(&mut Steering, &Transform), With<Avoid>>
) {
    let mut iter = query.iter_combinations_mut();

    while let Some([
        (mut steering_a, transform_a),
        (mut steering_b, transform_b)
    ]) = iter.fetch_next() {
        let a_to_b = transform_b.translation - transform_a.translation;
        let distance = a_to_b.length_squared();

        if distance < AVOID_DISTANCE {
            // Steer the two ships away from each other
            steering_a.0 += AVOIDANCE_FORCE;
            steering_b.0 -= AVOIDANCE_FORCE;
        }
    }
}

The combination’s that are yielded by the iterator are not guaranteed to have any particular order.

If we wanted combinations of 3 or more we can tweak the K parameter of the function:

fn every_three_transforms(
    mut query: Query<&mut Transform>
) {
    // Set our `K` parameter on the function to 3
    let mut combinations = query.iter_combinations_mut::<3>();

    // Now we get all combinations of 3 items returned
    while let Some([
        transform_a,
        transform_b,
        transform_c
    ]) = combinations.fetch_next() {
        // mutably access components data
    }
}

If we only need readonly access we can use the syntax without querying the iter over and over again:

fn readonly_combinations(query: Query<&Transform>) {
    for [
        transform_a,
        transform_b
    ] in query.iter_combinations() {
        // ...
    }
}

Iterating over specific entities

In cases where we have a list of Entity and we want to iterate over only those entity components we can use iter_many.

#[derive(Component)]
struct Health(pub f32);

#[derive(Resource)]
struct Selection {
    enemies: Vec<Entity>
}

const ATTACK_DAMAGE: f32 = 10.;

fn attack_selected_enemies(
    mut query: Query<&mut Health>,
    selected: Res<Selection>
) {
    let mut iter = query.iter_many_mut(&selected.enemies);
    while let Some(mut health) = iter.fetch_next() {
        health.0 -= ATTACK_DAMAGE;
    }
}

Faster iteration

In cases where we need more performance (but give up any chaining) for our queries we can use a for_each on either an iter or iter_mut:

fn fast_move_players(
    mut query: Query<&mut Transform>
) {
    // We can enumerate all matches
    query.iter_mut().for_each(|mut transform| {
        transform.translation.x += 1.;
    });
}

This will be faster but it cannot be chained into anything else.

Performance concerns

Table component storage type is much more optimized for query iteration than SparseSet.

Two systems cannot be executed in parallel if both access the same component type where at least one of the accesses is mutable.

This happens unless the executor can verify that no entity could be found in both queries. To do this it uses archetypes which speeds up the process of doing this lookup.

for_each methods are seen to be generally faster than their iter version on worlds with high archetype fragmentation. It is advised to use iter methods over for_each unless performance is really needed.

Read more