Skip to content
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

Potential surprising "entity does not exist" panic due to asynchronous command execution #7118

Closed
infmagic2047 opened this issue Jan 7, 2023 · 3 comments · Fixed by #17043
Labels
A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use

Comments

@infmagic2047
Copy link
Contributor

Bevy version

0.9.1

What went wrong

These two systems look innocent on their own, but may cause panic if used together.

fn insert(mut commands: Commands, query: Query<Entity, With<Marker>>, ...) {
    for entity in query.iter() {
        if ... {
            // We obtained the entity from the query, so it must be valid, right?
            commands.entity(entity).insert(Foo);
        }
    }
}

fn despawn(mut commands: Commands, query: Query<Entity, With<Marker>>, ...) {
    for entity in query.iter() {
        if ... {
            // This command may execute before the insertion and cause a panic there!
            commands.entity(entity).despawn();
        }
    }
}

This panic is hard to avoid in user code, as there is no way to know if there is a pending despawn command for a certain entity.

Additional information

The Insert command is, as far as I know, the only place where a missing entity in command execution is turned into a panic. We should at least replace the panic with a soft error or warning like the other commands.

We are also currently inconsistent in the way missing entities are reported. Insert uses panics, Remove uses no error reporting at all (we may want to change this), Despawn uses warnings, and DespawnRecursive use debug messages.

@infmagic2047 infmagic2047 added C-Bug An unexpected or incorrect behavior S-Needs-Triage This issue needs to be labelled labels Jan 7, 2023
@infmagic2047
Copy link
Contributor Author

Related issue: #3845.

I strongly disagree with the suggestion of "just use system ordering" in this case. There's no explicit indicator that the two systems are order-independent (the system order ambiguity reporter doesn't catch it, for example), and the panic can be hard to reproduce and only occurs in specific conditions, so unless one is extremely careful with system ordering, they may end up with a game that just randomly crashes for no obvious reasons.

In my code I work around this problem by deferring all despawns to a later stage. I use events to mark entities for removal, and ban all despawning commands outside a dedicated despawning system that reads these events. Clearly this is not the solution we want for all users.

I think most such issues can be solved by applying despawning commands after other commands. Is this a solution we want? It may add more overhead to commands and needs a good benchmark to test.

@mockersf
Copy link
Member

mockersf commented Jan 7, 2023

I think most such issues can be solved by applying despawning commands after other commands. Is this a solution we want? It may add more overhead to commands and needs a good benchmark to test.

Reordering commands across system could be hard as there are independent queue for each system. Doing something specific for one command, like keeping Despawn commands to the end, would not be simple as commands are kept as a dynamic type until their execution time. And then what of other commands that despawn, like DespawnRecursive and friends? And for custom commands that can despawn too?

Currently the consensus is that commands that comes from a "legit" entity (for example, one from a query) should not panic, but just be ignored if the entity is not live when time comes to execute it. It would also be nice to have command batching.

@mockersf mockersf added A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use and removed S-Needs-Triage This issue needs to be labelled C-Bug An unexpected or incorrect behavior labels Jan 7, 2023
@Preston-Harrison
Copy link

Would I be correct in using the following as a workaround?

if !commands.get_entity(entity).is_none() {
    // continue with rest of logic
}

github-merge-queue bot pushed a commit that referenced this issue Jan 7, 2025
## Objective

Fixes #2004
Fixes #3845
Fixes #7118
Fixes #10166

## Solution

- The crux of this PR is the new `Command::with_error_handling` method.
This wraps the relevant command in another command that, when applied,
will apply the original command and handle any resulting errors.
- To enable this, `Command::apply` and `EntityCommand::apply` now return
`Result`.
- `Command::with_error_handling` takes as a parameter an error handler
of the form `fn(&mut World, CommandError)`, which it passes the error
to.
- `CommandError` is an enum that can be either `NoSuchEntity(Entity)` or
`CommandFailed(Box<dyn Error>)`.

### Closures
- Closure commands can now optionally return `Result`, which will be
passed to `with_error_handling`.

### Commands
- Fallible commands can be queued with `Commands::queue_fallible` and
`Commands::queue_fallible_with`, which call `with_error_handling` before
queuing them (using `Commands::queue` will queue them without error
handling).
- `Commands::queue_fallible_with` takes an `error_handler` parameter,
which will be used by `with_error_handling` instead of a command's
default.
- The `command` submodule provides unqueued forms of built-in fallible
commands so that you can use them with `queue_fallible_with`.
- There is also an `error_handler` submodule that provides simple error
handlers for convenience.

### Entity Commands
- `EntityCommand` now automatically checks if the entity exists before
executing the command, and returns `NoSuchEntity` if it doesn't.
- Since all entity commands might need to return an error, they are
always queued with error handling.
- `EntityCommands::queue_with` takes an `error_handler` parameter, which
will be used by `with_error_handling` instead of a command's default.
- The `entity_command` submodule provides unqueued forms of built-in
entity commands so that you can use them with `queue_with`.

### Defaults
- In the future, commands should all fail according to the global error
handling setting. That doesn't exist yet though.
- For this PR, commands all fail the way they do on `main`.
- Both now and in the future, the defaults can be overridden by
`Commands::override_error_handler` (or equivalent methods on
`EntityCommands` and `EntityEntryCommands`).
- `override_error_handler` takes an error handler (`fn(&mut World,
CommandError)`) and passes it to every subsequent command queued with
`Commands::queue_fallible` or `EntityCommands::queue`.
- The `_with` variants of the queue methods will still provide an error
handler directly to the command.
- An override can be reset with `reset_error_handler`.

## Future Work

- After a universal error handling mode is added, we can change all
commands to fail that way by default.
- Once we have all commands failing the same way (which would require
either the full removal of `try` variants or just making them useless
while they're deprecated), `queue_fallible_with_default` could be
removed, since its only purpose is to enable commands having different
defaults.
mrchantey pushed a commit to mrchantey/bevy that referenced this issue Feb 4, 2025
…17043)

## Objective

Fixes bevyengine#2004
Fixes bevyengine#3845
Fixes bevyengine#7118
Fixes bevyengine#10166

## Solution

- The crux of this PR is the new `Command::with_error_handling` method.
This wraps the relevant command in another command that, when applied,
will apply the original command and handle any resulting errors.
- To enable this, `Command::apply` and `EntityCommand::apply` now return
`Result`.
- `Command::with_error_handling` takes as a parameter an error handler
of the form `fn(&mut World, CommandError)`, which it passes the error
to.
- `CommandError` is an enum that can be either `NoSuchEntity(Entity)` or
`CommandFailed(Box<dyn Error>)`.

### Closures
- Closure commands can now optionally return `Result`, which will be
passed to `with_error_handling`.

### Commands
- Fallible commands can be queued with `Commands::queue_fallible` and
`Commands::queue_fallible_with`, which call `with_error_handling` before
queuing them (using `Commands::queue` will queue them without error
handling).
- `Commands::queue_fallible_with` takes an `error_handler` parameter,
which will be used by `with_error_handling` instead of a command's
default.
- The `command` submodule provides unqueued forms of built-in fallible
commands so that you can use them with `queue_fallible_with`.
- There is also an `error_handler` submodule that provides simple error
handlers for convenience.

### Entity Commands
- `EntityCommand` now automatically checks if the entity exists before
executing the command, and returns `NoSuchEntity` if it doesn't.
- Since all entity commands might need to return an error, they are
always queued with error handling.
- `EntityCommands::queue_with` takes an `error_handler` parameter, which
will be used by `with_error_handling` instead of a command's default.
- The `entity_command` submodule provides unqueued forms of built-in
entity commands so that you can use them with `queue_with`.

### Defaults
- In the future, commands should all fail according to the global error
handling setting. That doesn't exist yet though.
- For this PR, commands all fail the way they do on `main`.
- Both now and in the future, the defaults can be overridden by
`Commands::override_error_handler` (or equivalent methods on
`EntityCommands` and `EntityEntryCommands`).
- `override_error_handler` takes an error handler (`fn(&mut World,
CommandError)`) and passes it to every subsequent command queued with
`Commands::queue_fallible` or `EntityCommands::queue`.
- The `_with` variants of the queue methods will still provide an error
handler directly to the command.
- An override can be reset with `reset_error_handler`.

## Future Work

- After a universal error handling mode is added, we can change all
commands to fail that way by default.
- Once we have all commands failing the same way (which would require
either the full removal of `try` variants or just making them useless
while they're deprecated), `queue_fallible_with_default` could be
removed, since its only purpose is to enable commands having different
defaults.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use
Projects
None yet
3 participants