Skip to content

Commit ffe5cf4

Browse files
committed
Rework Command Error Handling
1 parent 4259e4d commit ffe5cf4

File tree

13 files changed

+369
-572
lines changed

13 files changed

+369
-572
lines changed

benches/benches/bevy_ecs/world/commands.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use core::hint::black_box;
22

33
use bevy_ecs::{
44
component::Component,
5-
result::Result,
65
system::{Command, Commands},
76
world::{CommandQueue, World},
87
};
@@ -137,18 +136,16 @@ struct FakeCommandA;
137136
struct FakeCommandB(u64);
138137

139138
impl Command for FakeCommandA {
140-
fn apply(self, world: &mut World) -> Result {
139+
fn apply(self, world: &mut World) {
141140
black_box(self);
142141
black_box(world);
143-
Ok(())
144142
}
145143
}
146144

147145
impl Command for FakeCommandB {
148-
fn apply(self, world: &mut World) -> Result {
146+
fn apply(self, world: &mut World) {
149147
black_box(self);
150148
black_box(world);
151-
Ok(())
152149
}
153150
}
154151

@@ -183,10 +180,9 @@ pub fn fake_commands(criterion: &mut Criterion) {
183180
struct SizedCommand<T: Default + Send + Sync + 'static>(T);
184181

185182
impl<T: Default + Send + Sync + 'static> Command for SizedCommand<T> {
186-
fn apply(self, world: &mut World) -> Result {
183+
fn apply(self, world: &mut World) {
187184
black_box(self);
188185
black_box(world);
189-
Ok(())
190186
}
191187
}
192188

crates/bevy_ecs/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ bevy_reflect = ["dep:bevy_reflect"]
2828
## Extends reflection support to functions.
2929
reflect_functions = ["bevy_reflect", "bevy_reflect/functions"]
3030

31+
## Use the configurable global error handler as the default error handler
32+
configurable_error_handler = []
33+
3134
# Debugging Features
3235

3336
## Enables `tracing` integration, allowing spans and other metrics to be reported

crates/bevy_ecs/src/reflect/entity_commands.rs

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -169,30 +169,24 @@ pub trait ReflectCommandExt {
169169

170170
impl ReflectCommandExt for EntityCommands<'_> {
171171
fn insert_reflect(&mut self, component: Box<dyn PartialReflect>) -> &mut Self {
172-
self.queue(move |entity: Entity, world: &mut World| {
173-
if let Ok(mut entity) = world.get_entity_mut(entity) {
174-
entity.insert_reflect(component);
175-
}
172+
self.queue(move |mut entity: EntityWorldMut| {
173+
entity.insert_reflect(component);
176174
})
177175
}
178176

179177
fn insert_reflect_with_registry<T: Resource + AsRef<TypeRegistry>>(
180178
&mut self,
181179
component: Box<dyn PartialReflect>,
182180
) -> &mut Self {
183-
self.queue(move |entity: Entity, world: &mut World| {
184-
if let Ok(mut entity) = world.get_entity_mut(entity) {
185-
entity.insert_reflect_with_registry::<T>(component);
186-
}
181+
self.queue(move |mut entity: EntityWorldMut| {
182+
entity.insert_reflect_with_registry::<T>(component);
187183
})
188184
}
189185

190186
fn remove_reflect(&mut self, component_type_path: impl Into<Cow<'static, str>>) -> &mut Self {
191187
let component_type_path: Cow<'static, str> = component_type_path.into();
192-
self.queue(move |entity: Entity, world: &mut World| {
193-
if let Ok(mut entity) = world.get_entity_mut(entity) {
194-
entity.remove_reflect(component_type_path);
195-
}
188+
self.queue(move |mut entity: EntityWorldMut| {
189+
entity.remove_reflect(component_type_path);
196190
})
197191
}
198192

@@ -201,10 +195,8 @@ impl ReflectCommandExt for EntityCommands<'_> {
201195
component_type_path: impl Into<Cow<'static, str>>,
202196
) -> &mut Self {
203197
let component_type_path: Cow<'static, str> = component_type_path.into();
204-
self.queue(move |entity: Entity, world: &mut World| {
205-
if let Ok(mut entity) = world.get_entity_mut(entity) {
206-
entity.remove_reflect_with_registry::<T>(component_type_path);
207-
}
198+
self.queue(move |mut entity: EntityWorldMut| {
199+
entity.remove_reflect_with_registry::<T>(component_type_path);
208200
})
209201
}
210202
}

crates/bevy_ecs/src/system/commands/command.rs

Lines changed: 36 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ use crate::{
1212
entity::Entity,
1313
event::{Event, Events},
1414
observer::TriggerTargets,
15-
result::Result,
15+
result::{Error, Result},
1616
schedule::ScheduleLabel,
17-
system::{CommandError, IntoSystem, Resource, SystemId, SystemInput},
17+
system::{error_handler, IntoSystem, Resource, SystemId, SystemInput},
1818
world::{FromWorld, SpawnBatchIter, World},
1919
};
2020

@@ -45,91 +45,57 @@ use crate::{
4545
/// commands.queue(AddToCounter(42));
4646
/// }
4747
/// ```
48-
///
49-
/// # Note on Generic
50-
///
51-
/// The `Marker` generic is necessary to allow multiple blanket implementations
52-
/// of `Command` for closures, like so:
53-
/// ```ignore (This would conflict with the real implementations)
54-
/// impl Command for FnOnce(&mut World)
55-
/// impl Command<Result> for FnOnce(&mut World) -> Result
56-
/// ```
57-
/// Without the generic, Rust would consider the two implementations to be conflicting.
58-
///
59-
/// The type used for `Marker` has no connection to anything else in the implementation.
60-
pub trait Command<Marker = ()>: Send + 'static {
48+
pub trait Command<T = ()>: Send + 'static {
6149
/// Applies this command, causing it to mutate the provided `world`.
6250
///
6351
/// This method is used to define what a command "does" when it is ultimately applied.
6452
/// Because this method takes `self`, you can store data or settings on the type that implements this trait.
6553
/// This data is set by the system or other source of the command, and then ultimately read in this method.
66-
fn apply(self, world: &mut World) -> Result;
67-
68-
/// Applies this command and converts any resulting error into a [`CommandError`].
69-
///
70-
/// Overwriting this method allows an implementor to return a `CommandError` directly
71-
/// and avoid erasing the error's type.
72-
fn apply_internal(self, world: &mut World) -> Result<(), CommandError>
73-
where
74-
Self: Sized,
75-
{
76-
match self.apply(world) {
77-
Ok(_) => Ok(()),
78-
Err(error) => Err(CommandError::CommandFailed(error)),
79-
}
80-
}
81-
82-
/// Returns a new [`Command`] that, when applied, will apply the original command
83-
/// and pass any resulting error to the provided `error_handler`.
84-
fn with_error_handling(
85-
self,
86-
error_handler: Option<fn(&mut World, CommandError)>,
87-
) -> impl Command
88-
where
89-
Self: Sized,
90-
{
91-
move |world: &mut World| {
92-
if let Err(error) = self.apply_internal(world) {
93-
// TODO: Pass the error to the global error handler if `error_handler` is `None`.
94-
let error_handler = error_handler.unwrap_or(|_, error| panic!("{error}"));
95-
error_handler(world, error);
96-
}
97-
}
98-
}
54+
fn apply(self, world: &mut World) -> T;
9955
}
10056

101-
impl<F> Command for F
57+
impl<F, T> Command<T> for F
10258
where
103-
F: FnOnce(&mut World) + Send + 'static,
59+
F: FnOnce(&mut World) -> T + Send + 'static,
10460
{
105-
fn apply(self, world: &mut World) -> Result {
106-
self(world);
107-
Ok(())
61+
fn apply(self, world: &mut World) -> T {
62+
self(world)
10863
}
10964
}
11065

111-
impl<F> Command<Result> for F
112-
where
113-
F: FnOnce(&mut World) -> Result + Send + 'static,
114-
{
115-
fn apply(self, world: &mut World) -> Result {
116-
self(world)
66+
/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into
67+
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
68+
pub trait HandleError {
69+
/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into
70+
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
71+
fn handle_error_with(self, error_handler: fn(&mut World, Error)) -> impl Command;
72+
/// Takes a [`Command`] that returns a Result and uses the default error handler function to convert it into
73+
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
74+
fn handle_error(self) -> impl Command
75+
where
76+
Self: Sized,
77+
{
78+
self.handle_error_with(error_handler::default())
11779
}
11880
}
11981

120-
/// Necessary to avoid erasing the type of the `CommandError` in
121-
/// [`EntityCommand::with_entity`](crate::system::EntityCommand::with_entity).
122-
impl<F> Command<(Result, CommandError)> for F
123-
where
124-
F: FnOnce(&mut World) -> Result<(), CommandError> + Send + 'static,
125-
{
126-
fn apply(self, world: &mut World) -> Result {
127-
self(world)?;
128-
Ok(())
82+
impl<C: Command<Result>> HandleError for C {
83+
fn handle_error_with(self, error_handler: fn(&mut World, Error)) -> impl Command {
84+
move |world: &mut World| match self.apply(world) {
85+
Ok(_) => {}
86+
Err(err) => (error_handler)(world, err),
87+
}
12988
}
89+
}
13090

131-
fn apply_internal(self, world: &mut World) -> Result<(), CommandError> {
132-
self(world)
91+
/// Takes a [`Command`] that returns a [`Result`] with an error that can be converted into the [`Error`] type
92+
/// and returns a [`Command`] that internally converts that error to [`Error`] (if it occurs).
93+
pub fn map_command_err<T, E: Into<Error>>(
94+
command: impl Command<Result<T, E>>,
95+
) -> impl Command<Result<T, Error>> {
96+
move |world: &mut World| match command.apply(world) {
97+
Ok(result) => Ok(result),
98+
Err(err) => Err(err.into()),
13399
}
134100
}
135101

0 commit comments

Comments
 (0)