Querying: Entities and Param sets

Querying entities

In the previous chapter about queries, we've queried, and acted upon, only components. How about we need to act entities?

Both Legion and Bevy use a very similar interface - simply, by adding Entity to the query interface.

This is a query example:

// port: collisions.rs (edited)

pub fn collisions(
    mut commands: Commands,
    enemies_query: Query<(Entity, &PointC), With<Enemy>>,
) {
    for (entity, pos) in enemies_query.iter() {
        // ... act on the entity ...
    }
}

The type Entity is a classic example of handle; it's a lightweight type that supports common operations (copy, comparison, hash...), and therefore, can be conveniently passed around.

Expanding the example above, we can observe how we actually use it:

// port: collisions.rs

pub fn collisions(
    mut commands: Commands,
    player_query: Query<&PointC, With<Player>>,
    enemies_query: Query<(Entity, &PointC), With<Enemy>>,
) {
    let player_pos = player_query.single().0;

    for (entity, pos) in enemies_query.iter() {
        if pos.0 == player_pos {
            commands.entity(entity).despawn()
        }
    }
}

specifically, we pass it to the entity despawning API, when certain component conditions are met (enemy pos matching the player pos).

Watch out! As we'll see later, despawning doesn't have an immediate effect.

Param sets

Param sets are a functionality that is not needed in the port, but it's important to know nonetheless.

Let's say, for the sake of example, that we have the following system:

// port: collisions.rs (edited)

pub fn collisions(
    player_query: Query<&PointC, With<Player>>,
    mut enemies_query: Query<&mut PointC, With<Enemy>>,
) {
    let player_pos = player_query.single().0;

    for mut pos in enemies_query.iter_mut() {
        if pos.0 == player_pos {
            pos.0.x = 0;
            pos.0.y = 0;
        }
    }
}

which model a logic where instead of despawning the enemy entities, we move them away (by changing pos).

The project compiles! But, surprise surprise... once when running, we get a crash:

thread 'main' panicked at 'error[B0001]: Query<(Entity, &mut PointC), With<Enemy>>
in system collisions accesses component(s) PointC in a way that conflicts with a
previous system parameter. Consider using `Without<T>` to create disjoint Queries
or merging conflicting Queries into a `ParamSet`.', /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_ecs-0.7.0/src/system/system_param.rs:173:5
stack backtrace:
   0: rust_begin_unwind
             at /rustc/4cbaac699c14b7ac7cc80e54823b2ef6afeb64af/library/std/src/panicking.rs:584:5
   1: core::panicking::panic_fmt
             at /rustc/4cbaac699c14b7ac7cc80e54823b2ef6afeb64af/library/core/src/panicking.rs:142:14
   2: bevy_ecs::system::system_param::assert_component_access_compatibility
             at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_ecs-0.7.0/src/system/system_param.rs:173:5
   3: <bevy_ecs::query::state::QueryState<Q,F> as bevy_ecs::system::system_param::SystemParamState>::init
             at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_ecs-0.7.0/src/system/system_param.rs:113:9
   4: <(P0,P1,P2) as bevy_ecs::system::system_param::SystemParamState>::init
             at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_ecs-0.7.0/src/system/system_param.rs:1247:21

This is interesting; we're prevented from query PointC in two queries, with incompatible access mode. Bevy doesn't enforce incompatible access compile-time, but it does enforce it runtime.

In order to solve this problem, one of the two solutions referenced is to use ParamSet:

pub fn collisions(
    mut queries: ParamSet<(
        Query<&PointC, With<Player>>,
        Query<&mut PointC, With<Enemy>>,
    )>,
) {
    let player_pos = queries.p0().single().0;

    for mut pos in queries.p1().iter_mut() {
        if pos.0 == player_pos {
            pos.0.x = 0;
            pos.0.y = 0;
        }
    }
}

There's no significant difference; we just group the queries inside a ParamSet, and access them via pN() functions. Considering the constraint, this is impressively ergonomic!