-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Flush commands after every mutation in WorldEntityMut
#16219
base: main
Are you sure you want to change the base?
Conversation
90f42bc
to
82340bf
Compare
Yes please, I'd like a small Migration Guide note here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is pretty much what I was thinking. I assume branch prediction should hide any overhead from this since the condition should basically never be true.
I think hitting this case should issue a warning. And we might want to have a #[track_caller]
on the affected EntityWorldMut
methods so that anyone who sees the warning knows where to start looking.
Something like...
#[inline(never)]
#[cold]
fn on_entity_invalid(entity: Entity, caller: &'static Location) {
warn!("error[B<insert-error-number-here>]: {caller} tried to modify the entity {:?}, but it no longer exists. See: https://bevyengine.org/learn/errors/b<insert-error-number-here>", entity);
}
Ah, wait. Since Some of the |
|
Hello! 👋 I lack context about the changes in this PR, but by petition of @alice-i-cecile , I checked out this branch to see if I can still reproduce the crash of The answer is that no, I cannot. The thing is, I can't repro it in bevy main neither, so this bug must have been fixed at some point between Bevy 0.14.2 (the version where I encountered it) and current Bevy main atow Here, for reference: this is my minimal reproduceable example, on top of Bevy 0.14.2: https://github.com/Maximetinu/bevy/commits/minimal-reproduceable-bug-on-remove-hook/ see changes in latest commit running that modified example does reproduce the bug notice that the cause is just spawning an empty entity during a on_remove hook This is my minimal reproduceable example cherry-picked to this branch: see changes in latest commit, they are the same as in the branch above running that modified example does not reproduce the bug so, this branch works 🥳 However, bevy main also works: https://github.com/Maximetinu/bevy/commits/reproducing-on-remove-hook-panic-on-bevy-main/ which means that, well, this branch does not fix per se this bug, because it is already fixed on Bevy main but at least it means that it's not re-introducing it neither 🙂 Maybe my minimal reproduceable example can help to write a test covering from this specific scenario I hope this helps! |
I'd love to see that turned into a test, either in this PR or separately :) |
I have made a test for this, that fails at 0.14.2 but passes in current bevy main, so it should cover from this regression #[test]
fn test_spawn_entities_during_on_remove_hook() {
#[derive(Component)]
struct MyComponent;
App::new()
.add_plugins(MinimalPlugins)
.add_systems(Startup, |world: &mut World| {
world.register_component_hooks::<MyComponent>().on_remove(
|mut world, _entity, _component_id| {
world.commands().spawn_empty();
},
);
})
.add_systems(
Update,
|mut commands: Commands,
my_component_q: Query<Entity, With<MyComponent>>,
mut exit_event: EventWriter<AppExit>,
mut after_first_frame: Local<bool>| {
for entity in &my_component_q {
commands.entity(entity).despawn();
}
commands.spawn(MyComponent);
if *after_first_frame {
exit_event.send(AppExit::Success);
}
*after_first_frame = true;
},
)
.run();
} It's a bit of an unusual test (I'm not even sure if |
Let's spin this out into a new issue :) |
@maniwani Updated design (just quick notes for now):
|
12d4e58
to
ef4fad4
Compare
I changed this pull to instead panic rather than warn and try to behave if nothing is wrong. The reason for this is that if we only try to warn, there's a quite wide ranging knock-off effect into |
@alice-i-cecile @maniwani I'd love to get feedback if the new panicing approach is good or bad. Also, is it reasonable to add a panics section to all the methods which might panic for this. It should truly be a corner case when this happens, so I'm not sure if it's sensible to spam it everywhere (and it cannot be put into some of the non-fallible |
Implementation of panic is still placeholderish, just to note. |
For what it's worth, this behavior is widely hated :) I think I'm broadly on board with panicking here though, especially as a first pass. This really is an edge case, and I think that trying to figure out better ways to prevent this bad state from ever arising is more useful than bubbling the error up. |
59f2798
to
5075420
Compare
edc5cd2
to
eddf012
Compare
eddf012
to
aca60b6
Compare
b90ed0b
to
4668d66
Compare
If #16499 gets merged before this one, one of the test cases will break, as it should when the ordering of hooks and observers is changed. |
Objective
WorldEntityMut
is used after thisWorldEntityMut
attempted to (unsuccessfully) avoid flushing commands until finished was that such commands may move or despawn the entity being referenced, invalidating the cached location.observe
onWorldEntityMut
may cause the reference to be invalidated #16212Commands
apply at unexpected times with exclusiveWorld
access #14621Solution
WorldEntityMut
to exist even when it refers to a despawned entity by allowingEntityLocation
to be marked invalidTesting
Todo:
EntityWorldMut
on a despawned entity panicsMigration Guide
Previously
EntityWorldMut
triggered command queue flushes in unpredictable places, which could interfere with hooks and observers. Now the command queue is flushed always immediately after any call inEntityWorldMut
that spawns or despawns an entity, or adds, removes or replaces a component. This means hooks and observers will run their commands in the correct order.As a side effect, there is a possibility that a hook or observer could despawn the entity that is being referred to by
EntityWorldMut
. This could already currently happen if an observer was added while keeping anEntityWorldMut
referece and would cause unsound behaviour. If the entity has been despawned, calling any methods which require the entity location will panic. This matches the behaviour thatCommands
will panic if called on an already despawned entity. In the extremely rare case where taking a newEntityWorldMut
reference or otherwise restructuring the code so that this case does not happen is not possible, there's a newis_despawned
method that can be used to check if the referred entity has been despawned.