Bevy: `Query.removed()` is always empty

Created on 28 Nov 2020  路  5Comments  路  Source: bevyengine/bevy

Bevy version
rev 72b2fc9

Operating system & version
Windows 10

What you did
Here is a simple example:

use bevy::prelude::*;

struct MyComponentOne;
struct MyComponentTwo;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_system(spawn.system())
        .add_system(remove.system())
        .add_system(print.system())
        .run();
}

fn spawn(
    commands: &mut Commands,
) {
    commands.spawn((MyComponentOne, MyComponentTwo));
}

fn remove(
    commands: &mut Commands,
    query: Query<Entity>,
) {
    for entity in query.iter() {
        commands.remove_one::<MyComponentOne>(entity);
    }
}

fn print(
    query: Query<Entity>,
) {
    for thing in query.removed::<MyComponentOne>() {
        println!("{:?}", thing);
    }
}

What you expected to happen
To see something printed in the console.

What actually happened
Nothing is printed.

Additional information
It's been suggested in the Discord that perhaps Query.removed() is not meant to be used in this way. If so, how is it meant to be used? We should add this information to the documentation of the function.

If it's not meant to be used in this way, is there some best practice yet for how to check for removed components in Bevy? This would be great knowledge to have in the community as it's been very hard to find any information about this.

Most helpful comment

Your example above doesn't work for a couple of reasons:

  1. all three systems were added to the same (default) stage, so they all run in parallel. the "remove" system should always run after "spawn" and the "print" system must run _after_ components are actually removed.
  2. remove_one and spawn are Commands, which are always applied _after_ a stage has finished executing. this means that even if the parallel execution order _happens_ to line up, query.removed::() will return nothing because the remove commands haven't been applied yet.

You can fix it by adding your systems to different stages to force execution order. This prints the expected output:

.add_system_to_stage(stage::PRE_UPDATE, spawn.system())
.add_system_to_stage(stage::UPDATE, remove.system())
.add_system_to_stage(stage::POST_UPDATE, print.system())

(note that you should create your own stages after stage::UPDATE for scenarios like this instead of adding them to PRE/POST update)

All 5 comments

Your example above doesn't work for a couple of reasons:

  1. all three systems were added to the same (default) stage, so they all run in parallel. the "remove" system should always run after "spawn" and the "print" system must run _after_ components are actually removed.
  2. remove_one and spawn are Commands, which are always applied _after_ a stage has finished executing. this means that even if the parallel execution order _happens_ to line up, query.removed::() will return nothing because the remove commands haven't been applied yet.

You can fix it by adding your systems to different stages to force execution order. This prints the expected output:

.add_system_to_stage(stage::PRE_UPDATE, spawn.system())
.add_system_to_stage(stage::UPDATE, remove.system())
.add_system_to_stage(stage::POST_UPDATE, print.system())

(note that you should create your own stages after stage::UPDATE for scenarios like this instead of adding them to PRE/POST update)

I do want to give optional, more fine grained control over execution order _within_ a stage, but for now using stages is the recommended approach.

I'm closing this issue as it isn't a bug / the system is behaving as intended.

I'd say it's at least something that could be documented better,
possibly by fixing this example and adding it to the repo. I hit this
issue too, and have been all over the repo with grep looking for
instances of removed. It wasn't clear to me that the stage checking
for removal needed to be after the stage where the removal happened. It
also wasn't clear whether removal needed Changed or some other query
operator, or whether it worked on any component regardless of its
presence in the query. This example seems to clarify both questions for
me. Might be nice to submit it as a PR that closes this issue once
merged. It makes sense once you have a deeper understanding of how the
pieces interact, but the learning curve to get there is a bit steep. The
examples are great at shallowing it out a bit. :)

Thanks.

Yeah an example illustrating this issue is a good idea. Feel free to pr it :smile:

I'll get on a PR for the example.

And then the documentation, unless someone else takes it on first.

Just announcing here to avoid any duplicated work.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cart picture cart  路  28Comments

aclysma picture aclysma  路  17Comments

NickelCoating picture NickelCoating  路  15Comments

cart picture cart  路  23Comments

gdox picture gdox  路  13Comments