Ralith / hecs

A handy ECS
Apache License 2.0
921 stars 79 forks source link

How do you run commands after performing a query? #369

Closed musjj closed 2 months ago

musjj commented 2 months ago

Is there a way to run commands after performing a query? I tried using a CommandBuffer, but this doesn't satisfy the borrow checker:

fn foobar(mut world: World) {
    let mut cmd = CommandBuffer::new();
    let foobar_query = world.query::<&mut f32>();
    cmd.run_on(&mut world);
}
error[E0502]: cannot borrow `world` as mutable because it is also borrowed as immutable
   --> src/main.rs:407:16
    |
406 |     let foobar_query = world.query::<&mut f32>();
    |                        ----- immutable borrow occurs here
407 |     cmd.run_on(&mut world);
    |                ^^^^^^^^^^ mutable borrow occurs here
408 | }
    | - immutable borrow might be used here, when `foobar_query` is dropped and runs the `Drop` code for type `QueryBorrow`

It seems to work after manually dropping foobar_query:

fn foobar(mut world: World) {
    let mut cmd = CommandBuffer::new();
    let foobar_query = world.query::<&mut f32>();
    drop(foobar_query);
    cmd.run_on(&mut world);
}

But that's not really ideal in terms of ergonomics.

adamreichold commented 2 months ago

The more idiomatic approach would most likely be

fn foobar(mut world: World) {
    let mut cmd = CommandBuffer::new();
    {
        let foobar_query = world.query::<&mut f32>();
        // use the query here
    }
    cmd.run_on(&mut world);
}

but in principle, yes the query and its borrow of the world must end before cmd.run_on(..) can have exclusive/mutable access to world.

Ralith commented 2 months ago

Typically you consume queries in a for loop or some sort of iterator construction, as illustrated in various examples. It They are then implicitly dropped after you're done with them.

musjj commented 2 months ago

Thanks, that works for me.