How to do a nested query in a Bevy system?

Viewed 1289

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?

1 Answers

So I came up with a very inelegant answer, and I'm not sure if it will work well with boids. What you were trying to do won't work due to having two mutable references in the same scope.

I was able to get other boids, and compare them using a Vec<u128> of their ids. I wouldn't use a hashmap, since I heard they have slower lookups but I have not tried it.

I assumed in my example there is constant amount of boids, but you could just use the length of the boid vector. I also made up a random boid struct for my example and used rand.

const NUM_BOIDS: u32 = 10;
struct Boids(Vec<u128>);
struct Boid {
    position: f32,
    velocity: f32,
}
fn main() {
    App::build()
        ...
        .add_resource(Boids(Vec::new()))
        ...
}

fn setup(mut commands: Commands, mut boids: ResMut<Boids>) {
    let mut rng = rand::thread_rng();
    for i in 0..10 {
        // create 10 boids, and add there id's to the boids vec
        if let Some(entity) = commands
            .spawn((Boid {
                position: rng.gen_range(0.0, 100.0),
                velocity: rng.gen_range(0.0, 25.0),
            },))
            .current_entity()
        {
            boids.0.push(entity.id());
        };
    }
}

fn get_boids(mut boids: ResMut<Boids>, mut query: Query<(Entity, &mut Boid)>) {
    // go through all the boid ids
    for (i, boid_id) in boids.0.iter().enumerate() {
        // go through a second time
        for j in 0..NUM_BOIDS {
            // this gets the next boid id unless its at the end
            let other_boid_id = match boids.0.get(j as usize..(j as usize + 1)) {
                Some(e) => e,
                None => {
                    continue;
                }
            };
            // skip if it is the same boid
            if *boid_id == other_boid_id[0] {
                continue;
            }
            
            // since you can't have two mutable references at the same time
            // this gets the positions so those can be compared
            // and if the statement is true, a mutable reference can be gotten again.
            let mut first_position = 0.0;
            {
                let en = Entity::from_id(*boid_id);
                let first_boid: RefMut<Boid> = query.get_mut::<Boid>(en).unwrap();
                first_position = first_boid.position.clone();
            }
            let mut second_position = 0.0;
            {
                let en2 = Entity::from_id(other_boid_id[0]);
                let second_boid: RefMut<Boid> = query.get_mut::<Boid>(en2).unwrap();
                second_position = second_boid.position.clone();
            }
            if first_position - second_position < 0.001 {
                let en = Entity::from_id(*boid_id);
                let mut first_boid: RefMut<Boid> = query.get_mut::<Boid>(en).unwrap();
                first_boid.velocity = 15.0;
            }
        }
    }
}
Related