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

Configurable error handling in commands #2004

Open
alice-i-cecile opened this issue Apr 24, 2021 · 8 comments · May be fixed by #11184 or #15929
Open

Configurable error handling in commands #2004

alice-i-cecile opened this issue Apr 24, 2021 · 8 comments · May be fixed by #11184 or #15929
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible C-Usability A targeted quality-of-life change that makes Bevy easier to use

Comments

@alice-i-cecile
Copy link
Member

What problem does this solve or what need does it fill?

Commands (like removing components or despawning entities) may fail for various reasons. Right now, for the sake of idempotency (the same function applied twice should do the same thing as the function applied once), robustness and not spamming the user endlessly, these silently fail.

This is not always the desired behavior, and should be configurable at the call site.

Possible solutions

Approach to config

Global config is convenient for reducing boilerplate, but the desired behavior may fail by call site.

Local config is useful for both debugging and persistent configuration.

Default behavior

This could panic, warn or fail silently.

Patterns

More methods: this is bad for API explosion.

Extra argument: this is very verbose for common operations because Rust doesn't have default arguments

Builder pattern: nice, as long as we don't have to use it.

Additional context

@Frizi, @BoxyUwU and myself ran into this while discussing new commands for relations in bevyengine/rfcs#18

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible S-Needs-Triage This issue needs to be labelled core 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 labels Apr 24, 2021
@Frizi
Copy link
Contributor

Frizi commented Apr 24, 2021

The "robustness" argument actually backfires here in some circumstances. Things might not get done for various reason, but the logic might rely on them being done. That means we get extremally hard to detect bugs, and often impossible to solve when no opportunity to handle those failure modes is given.

An especially involved example comes from drafted relations RFC:

commands.entity(target_mass).change_target::<Spring>(m1, m2);
 

Here we intend to modify Spring relation target_mass -> m1 to target_mass -> m2. There are multiple ways it can fail:

  • the new target m2 might not exist at command execution time, what should we do with relation target_mass -> m1?
  • the old target m1 might already be deleted, which also deletes it's relations. We end up with no relation on pointing to m2.
  • any other combination of m1, m2, or target_mass can be deleted
  • there might be no relation to move (relation target_mass -> m1 was not there to begin with)

Doing nothing at all in all those situations by default is a little scary. Some of those failures might be unexpected, and influence correctness of implemented game logic if they happen. Saying "things might get done if all stars align" sounds very fragile and hard to rely on.

Also in some cases, there are still decisions to be made at the last second, like what to do with the relation that was supposed to be moved out, but there is nowhere to go?

One way to fix that would be to allow adding extra FnOnce + 'commands closure parameter to handle those failures.

commands.entity(target_mass)
	.change_target::<Spring>(m1, m2)
	.on_failure(|failure_mode| {
		match failure_mode {
			// when move failed, remove relation that was supposed to be moved
			ChangeTargetFailure::NewTargetMissing(decision) => decision.remove_relation(),
			// expected, do nothing
			ChangeTargetFailure::OldTargetMissing | ChangeTargetFailure::RelationMissing => {},
			_ => panic!("unexpected failure {:?}", failure_mode)
		}
	});

A lot of things to bikeshed here of course, but I hope this gets the general idea through.

This would allow logging on some unexpected behaviour, panic on straight up algorithm correctness violations, or do last-second decisions on what action to take. This of course applies not only to relations, but things like moving a component between entities or other actions that can fail.

Things that can be done are of course limited by the closure lifetime, but this can be further expanded if needed. Adding some more context to that closure could possibly allow more complex behaviours in response to fails, e.g. by allowing to issue more commands.

@TehPers
Copy link
Contributor

TehPers commented Apr 27, 2021

I like the idea of having a callback for failures. This means that something more robust can be built with MPSC using Sender in the callback and another system that reads for failures from a Receiver. That way if a command fails, you can have more advanced error handling like updating the game state to an error state or changing how some entities are spawned or such.

@cart
Copy link
Member

cart commented Apr 27, 2021

In theory these callbacks could have arbitrary World access, as they would be executed during command application. They could even be Systems (although this would likely incur overhead if every entity needs to initialize the same system). That flexibility might also change if we ever adopt more granular command synchronization (ex: the "stageless" design we've been discussing).

@NathanSWard
Copy link
Contributor

We should note #2219 as being an example of this issue directly affecting a developer and causing a quite bad UX.
Once I get settled on a better bevy-development-workflow, this appears to be once of the issues we should prioritize.
I have some testing going locally for possible solutions to this and may present them in the near future.

@mockersf
Copy link
Member

They could even be Systems

I think that's a very good idea. We should do #2192 first, and then error handling could build on that?

@NathanSWard
Copy link
Contributor

I think that's a very good idea. We should do #2192 first, and then error handling could build on that?

I'm not the biggest fan of this necessarily, mostly because then we have to wrap the error handler in a Box where a currently implementation I'm working on doesn't require this extra allocation.
Also, a Command might have a specific Error type that it wants to hand the error_handler but this won't be easily done if the handler has to be a system.

@NathanSWard
Copy link
Contributor

If anyone if curious, I have an initial implementation of this at #2241

@alice-i-cecile
Copy link
Member Author

I've implemented a few simple methods (try_insert, try_remove and try_add_child) in this PR: Leafwing-Studios/Emergence#765 externally.

Feel free to steal it directly (MIT license) either for Bevy or your own projects. Works perfectly for what I need.

@alice-i-cecile alice-i-cecile modified the milestones: 0.11, 0.12 Jun 19, 2023
@alice-i-cecile alice-i-cecile removed this from the 0.12 milestone Oct 17, 2023
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-Feature A new feature, making something new possible C-Usability A targeted quality-of-life change that makes Bevy easier to use
Projects
None yet
6 participants