Commands
apply at unexpected times with exclusive World
access
#14621
Labels
A-ECS
Entities, components, systems, and events
C-Bug
An unexpected or incorrect behavior
C-Usability
A targeted quality-of-life change that makes Bevy easier to use
S-Needs-Design
This issue requires design work to think about how it would best be accomplished
X-Controversial
There is active debate or serious implications around merging this PR
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: