Search code examples
bevy

How to do a nested query in a Bevy system?


I'm working on a little boids toy with Bevy, and each boid's velocity/acceleration depends on the position and velocity values of the boids around it. This means that for each boid, I want to run some logic that depends on some subset of the other boids.

This seems like it could be a nested for-loop basically:

for boid in boids {
    for other_boid in boids {
        if boid.id == other_boid.id {
            continue;
        }
        
        if boid.position.distance_to(other_boid.position) < PERCEPTION_DISTANCE {
            // change boid's velocity / acceleration
        }
    }
}

However, I'm not sure how to do this with queries in Bevy. Let's say I have a system move_boids:

fn move_boids(mut query: Query<&Boid>) {
    for boid in &mut query.iter() {
        // I can't iterate over *other* boids here
    }
}

I get an error something like this, because I'm borrowing query mutably in both loops:

error[E0499]: cannot borrow `query` as mutable more than once at a time
  --> src\main.rs:10:32
   |
10 |     for boid in &mut query.iter() {
   |                      ------------
   |                      |          |
   |                      |          ... and the first borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `bevy::bevy_ecs::system::query::QueryBorrow`
   |                      first mutable borrow occurs here
   |                      a temporary with access to the first borrow is created here ...
...
11 |         for other_boid in &mut query.iter() {}
   |                                ^^^^^ second mutable borrow occurs here

I can't do nested iteration over the same Query, so I'm not sure the best way to get information about surrounding boids for each boid. Should I copy each boid's position and velocity information from the first query into a HashMap<Entity, BoidData> and then do lookups in that? Is there something more idiomatic I could do?


Solution

  • Answering my own question a couple years later. The Bevy 0.6 release introduced Query::iter_combinations and Query::iter_combinations_mut, the latter of which is what you want here.

    So, to use the example system from the question, it would look like this:

    fn move_boids(mut query: Query<&mut Boid>) {
        let mut combinations = query.iter_combinations_mut();
        while let Some([mut a, mut b]) = combinations.fetch_next() {
            // update velocity and acceleration
        }
    }
    

    Note, from the Bevy docs:

    The returned value is not an Iterator, because that would lead to aliasing of mutable references. In order to iterate it, use fetch_next method with while let Some(..) loop pattern.

    See also the official example in the Bevy repo of iter_combinations.