Bevy Queries
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, only 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 QueryFetch
which tells Bevy exactly what data you want 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 the player component from each entity that has one
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 ask our game world to:
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. It 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
. You can imagine an Entity
as just 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 QueryFilter
. These filters are wrapped by a condition type:
method | description |
---|---|
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
. This 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:
method | description |
---|---|
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.
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
.
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.