-
-
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
Add Result
handling to Commands
and EntityCommands
#17043
base: main
Are you sure you want to change the base?
Conversation
commit b6c4d28 Author: JaySpruce <[email protected]> Date: Sun Dec 29 17:17:44 2024 -0600 ci docs commit 492e558 Author: JaySpruce <[email protected]> Date: Sun Dec 29 16:59:31 2024 -0600 messed up the conflict resolve commit 74592fa Merge: e0c6d65 0f2b2de Author: JaySpruce <[email protected]> Date: Sun Dec 29 16:48:37 2024 -0600 Merge branch 'main' into refactor_hierarchy_commands commit e0c6d65 Author: JaySpruce <[email protected]> Date: Sun Dec 29 13:28:07 2024 -0600 refactor to remove structs
Pinging @alice-i-cecile as requested. Not totally polished, but I felt like this could use some feedback/cooperation in regards to other fallible stuff going on. |
self | ||
} | ||
|
||
/// |
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.
Reminder to finish these doc strings before we merge :)
/// | ||
/// # Note | ||
/// | ||
/// This won't clean up external references to the entity (such as parent-child relationships | ||
/// if you're using `bevy_hierarchy`), which may leave the world in an invalid state. | ||
#[track_caller] | ||
fn despawn(log_warning: bool) -> impl EntityCommand { | ||
fn despawn() -> impl EntityCommand<World> { | ||
#[cfg(feature = "track_change_detection")] |
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.
This change seems correct regardless, maybe we should split this out? Ditto the docs above.
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.
I think that would need the impl EntityCommand for impl FnOnce(EntityWorldMut)
changes. I do think that should be pulled out of this pr as that is something that could be considered separately.
#[derive(Clone, Copy)] | ||
pub enum EntityFetchError<'w> { | ||
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)] | ||
pub enum EntityFetchError { |
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.
Wow I much prefer removing the lifetime + UnsafeWorldCell from this. Can we split this out?
crates/bevy_ecs/src/world/mod.rs
Outdated
/// Fail silently. | ||
Silent, | ||
/// Send a warning. | ||
Warn, |
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.
Follow-up: we should add an error level too.
I'm willing to eat a 5% (worst case) performance regression on Commands here. This is a huge usability problem with Bevy, and I'm convinced that we can't solve this without somehow regressing perf there. The big performance gains to be found here are in command batching anyways. |
I'd like to do a proper review of this, if I can find the time. Writing this as a reminder to myself. |
I agree with you, this approach makes a lot of sense. I do think we can find a more optimized solution though. Having proper error handling for commands is worth the 5% decrease in performance but I'm positive we can minimize it. I'll try to find time over the weekend to play around with this PR and give more based feedback. |
@@ -81,6 +86,10 @@ pub use parallel_scope::*; | |||
pub struct Commands<'w, 's> { | |||
queue: InternalQueue<'s>, | |||
entities: &'w Entities, | |||
/// How the [`Commands`] instance will response upon encountering an error. | |||
/// | |||
/// If this is unset ([`None`]), commands can choose their error mode individually. |
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.
This feels weird to me. Why would users want to set the error handling at the level of a Commands
queue?
My mental model is:
- Global default, set per project and per build mode (release, alpha testing, dev...)
- Per command overrides to completely silence useless warnings or respond to failures by recovering.
This feels like an intermediate level that doesn't serve anyone well.
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.
I agree with this assessment. I'll add that currently the global handling mode is effectively based on Box<dyn Error>
and always panics.
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.
It's the only way I could think for it to work without vastly changing how commands work. Calling queue
immediately turns the command into a bunch of unknown bytes, so if you want to customize the command, it has to know what you want before queue
is called
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.
Alright, I'm fine with this for now. Leave a comment to this effect on this field please :)
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.
Actually, here's an alternative: we could re-add those helper functions we just removed that return the commands as closures, and we could make them pub
to let people queue built-in commands with their own error handlers:
commands.entity(entity).queue_with(insert(bundle), |world, error| { /* whatever */ });
crates/bevy_ecs/src/world/mod.rs
Outdated
/// | ||
/// `fn()` can be a closure if it doesn't capture its environment. | ||
/// | ||
/// If `CommandErrorMode` is used in a situation without `&mut World` access |
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.
This seems like an odd situation. What methods would try to resolve and handle commands without exclusive world access?
CommandErrorMode::Error => error!( | ||
"Attempted to create an EntityCommands for entity {entity}, which does not exist; returned invalid EntityCommands" | ||
), | ||
CommandErrorMode::Panic | CommandErrorMode::Custom(_) => panic!( |
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.
Aha, here's the panic. This should be called out more clearly at least, but I think that passing down a more complex Result or adding an error arm is the better way. The custom error handler should be able to know whether or not the entity failed to exist at all, and respond accordingly.
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.
I was kind of hoping we wouldn't have to put handling logic on this level. I feel like this sort of match statement should ideally only appear once, and everything should be funneled through it on a lower level. That's just a feeling on my part.
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.
Do we need to remove the panic here? I feel like if the user doesn't want it to panic they should just be using get_entity
. I feel like this pr should just be focused on the errors for when commands are applied.
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.
Eventually I think the plan is to collapse our panicking and non-panicking variants into the same call.
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.
Yeah, I'd like to remove get_entity
completely in favor of this error handling technique. It's frustrating that there's so many different edge cases to guard for, and no unified way to make sure you've handled them all.
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.
That makes sense, but I still feel like this pr should be focused on the deferred errors. Or are you saying that creating a EntityCommands will only panic when the command queue is applied?
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.
That makes sense, but I still feel like this pr should be focused on the deferred errors
I agree with that, but I think we can do some future-proofing here. It's fine to leave it for follow-up though: I just think it's a bit messy as is.
/// | ||
/// `fn()` can be a closure if it doesn't capture its environment. | ||
/// | ||
/// If an error is encountered in a situation without `&mut World` access |
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.
Same note here about docs / alternative fix for this problem.
crates/bevy_ecs/src/world/mod.rs
Outdated
/// | ||
/// If `CommandErrorMode` is used in a situation without `&mut World` access | ||
/// (e.g. a non-deferred [`Commands`] method), this mode will instead panic. | ||
Custom(fn(&mut World, Result)), |
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.
I thought about this a bit more: this should actually have a Box<dyn Error>
, not a Result
. There's no need for error handling if everything went okay, so we can save our users some repetitive matching.
Then, combined with the ideas about how commands.entity(my_entity)
shouldn't panic, we get:
enum CommandError {
NoSuchEntity(Entity),
CommandFailed(Box<dyn Error>)
}
which we pass to the custom error handlers to give them the context needed to solve the problem.
I like this direction a lot, but I do have a gripe with You have These are all suggestions mind you. You're doing great work, and I wouldn't block the current line of work over any of this. It just strikes me that using function pointers might be more extensible. It might also be lower overhead with a specific |
@@ -444,23 +457,25 @@ impl<'w, 's> Commands<'w, 's> { | |||
#[inline] | |||
#[track_caller] | |||
pub fn entity(&mut self, entity: Entity) -> EntityCommands { | |||
#[inline(never)] | |||
#[cold] | |||
#[track_caller] |
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.
Some of the perf regressions are probably from losing these annotations. We should try to add them back.
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.
Good catch, that would do it.
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.
I did smaller benchmarks before and after making that change (only the insert_commands::insert
benchmark, because that had been the most volatile) and didn't see any difference. We're aiming to remove the panic from entity
anyway, I'm not sure there's a place for those
Hmm, I'm trying to figure out what would even cause regressions here if it's not the #[cold] annotation. I feel like there hasn't been much change to the code paths for fake_commands and sized_commands_0_bytes. The only thing I really see is that we now need to drop the result from |
I think I'll revert |
How would y'all feel about something like this:
So that users could do this if they wanted: I'm unsure what the stance is on using |
this isn't relevant to this PR at all anymore, it should be separate
Objective
todo!();
Redux of #15929
Fixes #2004
Fixes #3845
Fixes #7118
Fixes #10166
Solution
todo!();
Benchmarks
This PR compared to main (all benchmarks with "commands" in their name)
Future Updates
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.