7

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?

peter
  • 91
  • 6
  • I noticed a couple things in your code. First, you are using, `Query<&Boid>` instead of `Query<&mut Boid>`. Second, you are using `for boid in &mut query.iter()` instead of `for mut boid in &mut query.iter()`. This means that you are borrowing as immutable. If this is acceptable, you could simply ask for a second query: `fn move_boids(mut query: Query<&Boid>, mut query2: Query<&Boid>)` This would give you two identical iterators that you could loop through. The only caveat is you would have to decide another way to modify the `boid`s that you found. I don't know if this is idiomatic, though. – STF_ZBR Sep 07 '20 at 03:36
  • This is a common problem in rust for a lot of algorithms and isn't unique to bevy. One solution is to convert your iterator of into an iterator of Rc>. Then you can get a non-mutable reference to both things and only make it mutable when you need to. – sdfgeoff Mar 02 '21 at 09:33
  • Something like `let boid_array = query.iter_mut().map(|x| Rc::new(RefCell::new(x)));` and then you can do nested loops on boid_array. When you need to modify a boid, you can then use `boid.borrow_mut()`. – sdfgeoff Mar 02 '21 at 09:34

2 Answers2

2

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;
            }
        }
    }
}
Will
  • 1,059
  • 2
  • 10
  • 22
2

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.

peter
  • 91
  • 6