Skip to content

Commit 1cf6ebd

Browse files
committed
[ecs] command error handling
1 parent c893b99 commit 1cf6ebd

File tree

6 files changed

+682
-57
lines changed

6 files changed

+682
-57
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ path = "examples/ecs/ecs_guide.rs"
262262
name = "change_detection"
263263
path = "examples/ecs/change_detection.rs"
264264

265+
[[example]]
266+
name = "command_error_handling"
267+
path = "examples/ecs/command_error_handling.rs"
268+
265269
[[example]]
266270
name = "event"
267271
path = "examples/ecs/event.rs"

crates/bevy_ecs/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ pub mod prelude {
2929
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
3030
},
3131
system::{
32-
Commands, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem, Local, NonSend,
33-
NonSendMut, Query, QuerySet, RemovedComponents, Res, ResMut, System,
32+
CommandErrorHandler, Commands, FallibleCommand, In, IntoChainSystem,
33+
IntoExclusiveSystem, IntoSystem, Local, NonSend, NonSendMut, Query, QuerySet,
34+
RemovedComponents, Res, ResMut, System,
3435
},
3536
world::{FromWorld, Mut, World},
3637
};
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
use crate::{
2+
prelude::{FallibleCommand, World},
3+
system::Command,
4+
};
5+
use bevy_utils::tracing::error;
6+
use std::{
7+
fmt::Debug,
8+
ops::{Deref, DerefMut},
9+
};
10+
11+
#[doc(hidden)]
12+
pub trait AddCommand {
13+
fn add_command(&mut self, command: impl Command);
14+
}
15+
16+
/// Provides configuration mechanisms in case a command errors.
17+
/// You can specify a custom handler via [`FallibleCommandConfig::on_failure`] or
18+
/// use one of the provided implementations.
19+
///
20+
/// ## Note
21+
/// The default error handler logs the error (via [`error!`]), but does not panic.
22+
pub struct FallibleCommandConfig<'a, C, T>
23+
where
24+
C: FallibleCommand,
25+
T: AddCommand,
26+
{
27+
command: Option<C>,
28+
inner: &'a mut T,
29+
}
30+
31+
impl<'a, C, T> Deref for FallibleCommandConfig<'a, C, T>
32+
where
33+
C: FallibleCommand,
34+
T: AddCommand,
35+
{
36+
type Target = T;
37+
38+
#[inline]
39+
fn deref(&self) -> &Self::Target {
40+
self.inner
41+
}
42+
}
43+
44+
impl<'a, C, T> DerefMut for FallibleCommandConfig<'a, C, T>
45+
where
46+
C: FallibleCommand,
47+
T: AddCommand,
48+
{
49+
#[inline]
50+
fn deref_mut(&mut self) -> &mut Self::Target {
51+
self.inner
52+
}
53+
}
54+
55+
/// Builtin command error handlers.
56+
pub struct CommandErrorHandler;
57+
58+
impl CommandErrorHandler {
59+
/// If the command failed, log the error.
60+
///
61+
/// ## Note
62+
/// This is the default behavior if no error handler is specified.
63+
pub fn log<E: Debug>(error: E, _ctx: CommandContext) {
64+
error!("Commands failed with error: {:?}", error)
65+
}
66+
67+
/// If the command failed, [`panic!`] with the error.
68+
pub fn panic<E: Debug>(error: E, _ctx: CommandContext) {
69+
panic!("Commands failed with error: {:?}", error)
70+
}
71+
72+
/// If the command failed, ignore the error and silently succeed.
73+
pub fn ignore<E>(_error: E, _ctx: CommandContext) {}
74+
}
75+
76+
pub(crate) struct HandledErrorCommand<C, F>
77+
where
78+
C: FallibleCommand,
79+
F: FnOnce(C::Error, CommandContext) + Send + Sync + 'static,
80+
{
81+
pub(crate) command: C,
82+
pub(crate) error_handler: F,
83+
}
84+
85+
impl<C, F> Command for HandledErrorCommand<C, F>
86+
where
87+
C: FallibleCommand,
88+
F: FnOnce(C::Error, CommandContext) + Send + Sync + 'static,
89+
{
90+
fn write(self: Box<Self>, world: &mut World) {
91+
let HandledErrorCommand {
92+
command,
93+
error_handler,
94+
} = *self;
95+
96+
if let Err(error) = command.try_write(world) {
97+
error_handler(error, CommandContext { world });
98+
}
99+
}
100+
}
101+
102+
#[non_exhaustive]
103+
pub struct CommandContext<'a> {
104+
pub world: &'a mut World,
105+
}
106+
107+
/// Similar to [`FallibleCommandConfig`] however does not
108+
/// implement [`DerefMut`] nor return `&mut T` of the underlying
109+
/// Commands type.
110+
pub struct FinalFallibleCommandConfig<'a, C, T>
111+
where
112+
C: FallibleCommand,
113+
T: AddCommand,
114+
{
115+
command: Option<C>,
116+
inner: &'a mut T,
117+
}
118+
119+
macro_rules! impl_fallible_commands {
120+
($name:ident, $returnty:ty, $returnfunc:ident) => {
121+
impl<'a, C, T> $name<'a, C, T>
122+
where
123+
C: FallibleCommand,
124+
C::Error: Debug,
125+
T: AddCommand,
126+
{
127+
#[inline]
128+
pub(crate) fn new(command: C, inner: &'a mut T) -> Self {
129+
Self {
130+
command: Some(command),
131+
inner,
132+
}
133+
}
134+
135+
#[inline]
136+
#[allow(dead_code)]
137+
fn return_inner(&mut self) -> &mut T {
138+
self.inner
139+
}
140+
141+
#[inline]
142+
#[allow(dead_code)]
143+
fn return_unit(&self) {}
144+
}
145+
146+
impl<'a, C, T> $name<'a, C, T>
147+
where
148+
C: FallibleCommand,
149+
C::Error: Debug,
150+
T: AddCommand,
151+
{
152+
/// If the command failed, run the provided `error_handler`.
153+
///
154+
/// ## Note
155+
/// This is normally used in conjunction with [`CommandErrorHandler`].
156+
/// However, this can also be used with custom error handlers (e.g. closures).
157+
///
158+
/// # Examples
159+
/// ```
160+
/// use bevy_ecs::prelude::*;
161+
///
162+
/// fn system(mut commands: Commands) {
163+
/// // built-in error handler
164+
/// commands.spawn().insert(42).on_err(CommandErrorHandler::ignore);
165+
///
166+
/// // custom error handler
167+
/// commands.spawn().insert(42).on_err(|error, ctx| {});
168+
/// }
169+
/// ```
170+
pub fn on_err(
171+
&mut self,
172+
error_handler: impl FnOnce(C::Error, CommandContext) + Send + Sync + 'static,
173+
) -> $returnty {
174+
let command = self
175+
.command
176+
.take()
177+
.expect("Cannot call `on_err` multiple times for a command error handler.");
178+
self.inner.add_command(HandledErrorCommand {
179+
command,
180+
error_handler,
181+
});
182+
self.$returnfunc()
183+
}
184+
}
185+
186+
impl<'a, C, T> Drop for $name<'a, C, T>
187+
where
188+
C: FallibleCommand,
189+
T: AddCommand,
190+
{
191+
fn drop(&mut self) {
192+
if self.command.is_some() {
193+
self.on_err(CommandErrorHandler::log);
194+
}
195+
}
196+
}
197+
};
198+
}
199+
200+
impl_fallible_commands!(FinalFallibleCommandConfig, (), return_unit);
201+
impl_fallible_commands!(FallibleCommandConfig, &mut T, return_inner);

0 commit comments

Comments
 (0)