-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Commands
apply at unexpected times with exclusive World
access
#14621
Comments
For the record, this is intended behavior, and I don't think we should change this.
Generally I don't think users should be doing this, precisely because of how complex it makes things to debug. I've already opened a proposal for a lint against this: TheBevyFlock/bevy_cli#77 |
Then should there also be a lint against using |
The only way to add or remove components from within observers and component hooks is to use |
# Objective - Currently adding observers spawns an entity which implicitly flushes the command queue, which can cause undefined behaviour if the `WorldEntityMut` is used after this - The reason `WorldEntityMut` attempted to (unsuccessfully) avoid flushing commands until finished was that such commands may move or despawn the entity being referenced, invalidating the cached location. - With the introduction of hooks and observers, this isn't sensible anymore as running the commands generated by hooks immediately is required to maintain correct ordering of operations and to not expose the world in an inconsistent state - Objective is to make command flushing deterministic and fix the related issues - Fixes #16212 - Fixes #14621 - Fixes #16034 ## Solution - Allow `WorldEntityMut` to exist even when it refers to a despawned entity by allowing `EntityLocation` to be marked invalid - Add checks to all methods to panic if trying to access a despawned entity - Flush command queue after every operation that might trigger hooks or observers - Update entity location always after flushing command queue ## Testing - Added test cases for currently broken behaviour - Added test cases that flushes happen in all operations - Added test cases to ensure hooks and commands are run exactly in correct order when nested --- Todo: - [x] Write migration guide - [x] Add tests that using `EntityWorldMut` on a despawned entity panics - [x] Add tests that commands are flushed after every operation that is supposed to flush them - [x] Add tests that hooks, observers and their spawned commands are run in the correct order when nested --- ## Migration 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 in `EntityWorldMut` 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 an `EntityWorldMut` 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 that `Commands` will panic if called on an already despawned entity. In the extremely rare case where taking a new `EntityWorldMut` reference or otherwise restructuring the code so that this case does not happen is not possible, there's a new `is_despawned` method that can be used to check if the referred entity has been despawned.
…6219) # Objective - Currently adding observers spawns an entity which implicitly flushes the command queue, which can cause undefined behaviour if the `WorldEntityMut` is used after this - The reason `WorldEntityMut` attempted to (unsuccessfully) avoid flushing commands until finished was that such commands may move or despawn the entity being referenced, invalidating the cached location. - With the introduction of hooks and observers, this isn't sensible anymore as running the commands generated by hooks immediately is required to maintain correct ordering of operations and to not expose the world in an inconsistent state - Objective is to make command flushing deterministic and fix the related issues - Fixes bevyengine#16212 - Fixes bevyengine#14621 - Fixes bevyengine#16034 ## Solution - Allow `WorldEntityMut` to exist even when it refers to a despawned entity by allowing `EntityLocation` to be marked invalid - Add checks to all methods to panic if trying to access a despawned entity - Flush command queue after every operation that might trigger hooks or observers - Update entity location always after flushing command queue ## Testing - Added test cases for currently broken behaviour - Added test cases that flushes happen in all operations - Added test cases to ensure hooks and commands are run exactly in correct order when nested --- Todo: - [x] Write migration guide - [x] Add tests that using `EntityWorldMut` on a despawned entity panics - [x] Add tests that commands are flushed after every operation that is supposed to flush them - [x] Add tests that hooks, observers and their spawned commands are run in the correct order when nested --- ## Migration 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 in `EntityWorldMut` 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 an `EntityWorldMut` 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 that `Commands` will panic if called on an already despawned entity. In the extremely rare case where taking a new `EntityWorldMut` reference or otherwise restructuring the code so that this case does not happen is not possible, there's a new `is_despawned` method that can be used to check if the referred entity has been despawned.
Bevy version
0.14.0
andmain
(c1c003d)What you did
world.commands().add(...)
from within another command. Take the following example:When does
BUZZ!
get printed?What went wrong
It ends up being in the place I would least expect, which is why I'm calling this a bug rather than simply lacking documentation.
Loosely ordered by where I would expect it to occur:
&mut World
passed to each command is the same world as the outermost, then I would expectworld.commands()
to always refer to the same queue, so Command 1 is added, Command 2 is added, Command 1 runs, it pushes Command 3 into the queue, Command 2 runs, Command 3 runs.world.flush()
, and the docs forworld.commands()
says they'll run after a call toWorld::flush
. So it should be after D.World
access, soCommands
are a bit redundant. If we ignore the bit aboutWorld::flush
in theWorld::commands
docs, then maybe we'll guess that they simply execute immediately, andWorld::commands
is just for convenience when you have something that accepts aCommands
argument. Or maybe they flush when theCommands
is immediately dropped after ouradd
?World::flush
any time we actually mutate the world or when we read the result of a mutation.And the winner is... after C! It comes down to
World::flush
being implicit in some methods, but not others. There isn't any mention of this in any of their docs. Even if it was called out explicitly in the docs for each method if it does a flush, I wouldn't want to have to keep that context in my head when reasoning about when a command will run.To me, the only three possibilities for when a command should run given exclusive world access are:
Additional information
This comes out of my attempts to get lifecycle
Observer
s to behave the way I want, and they make things even harder to reason about. Specifically, theOnAdd
trigger will occur immediately upon component addition, but if you add commands to the queue in the trigger, they'll be executed whenever the command that did the component addition does a flush, which can be wildly unpredictable. For example, what if the command pre-spawns a bunch of entities, then adds components? Your observer's commands will run at the end of that command after all components have been added. What if instead, it interleaves entity spawning and component adding? Then you'll get a flush before each new entity, and your observer's commands will occur midway through the command that triggered it.The text was updated successfully, but these errors were encountered: