Skip to content

Commit

Permalink
[ecs] command error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
NathanSWard committed Jun 29, 2021
1 parent c893b99 commit 1cf6ebd
Show file tree
Hide file tree
Showing 6 changed files with 682 additions and 57 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ path = "examples/ecs/ecs_guide.rs"
name = "change_detection"
path = "examples/ecs/change_detection.rs"

[[example]]
name = "command_error_handling"
path = "examples/ecs/command_error_handling.rs"

[[example]]
name = "event"
path = "examples/ecs/event.rs"
Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ pub mod prelude {
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
},
system::{
Commands, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem, Local, NonSend,
NonSendMut, Query, QuerySet, RemovedComponents, Res, ResMut, System,
CommandErrorHandler, Commands, FallibleCommand, In, IntoChainSystem,
IntoExclusiveSystem, IntoSystem, Local, NonSend, NonSendMut, Query, QuerySet,
RemovedComponents, Res, ResMut, System,
},
world::{FromWorld, Mut, World},
};
Expand Down
201 changes: 201 additions & 0 deletions crates/bevy_ecs/src/system/commands/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
use crate::{
prelude::{FallibleCommand, World},
system::Command,
};
use bevy_utils::tracing::error;
use std::{
fmt::Debug,
ops::{Deref, DerefMut},
};

#[doc(hidden)]
pub trait AddCommand {
fn add_command(&mut self, command: impl Command);
}

/// Provides configuration mechanisms in case a command errors.
/// You can specify a custom handler via [`FallibleCommandConfig::on_failure`] or
/// use one of the provided implementations.
///
/// ## Note
/// The default error handler logs the error (via [`error!`]), but does not panic.
pub struct FallibleCommandConfig<'a, C, T>
where
C: FallibleCommand,
T: AddCommand,
{
command: Option<C>,
inner: &'a mut T,
}

impl<'a, C, T> Deref for FallibleCommandConfig<'a, C, T>
where
C: FallibleCommand,
T: AddCommand,
{
type Target = T;

#[inline]
fn deref(&self) -> &Self::Target {
self.inner
}
}

impl<'a, C, T> DerefMut for FallibleCommandConfig<'a, C, T>
where
C: FallibleCommand,
T: AddCommand,
{
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
}
}

/// Builtin command error handlers.
pub struct CommandErrorHandler;

impl CommandErrorHandler {
/// If the command failed, log the error.
///
/// ## Note
/// This is the default behavior if no error handler is specified.
pub fn log<E: Debug>(error: E, _ctx: CommandContext) {
error!("Commands failed with error: {:?}", error)
}

/// If the command failed, [`panic!`] with the error.
pub fn panic<E: Debug>(error: E, _ctx: CommandContext) {
panic!("Commands failed with error: {:?}", error)
}

/// If the command failed, ignore the error and silently succeed.
pub fn ignore<E>(_error: E, _ctx: CommandContext) {}
}

pub(crate) struct HandledErrorCommand<C, F>
where
C: FallibleCommand,
F: FnOnce(C::Error, CommandContext) + Send + Sync + 'static,
{
pub(crate) command: C,
pub(crate) error_handler: F,
}

impl<C, F> Command for HandledErrorCommand<C, F>
where
C: FallibleCommand,
F: FnOnce(C::Error, CommandContext) + Send + Sync + 'static,
{
fn write(self: Box<Self>, world: &mut World) {
let HandledErrorCommand {
command,
error_handler,
} = *self;

if let Err(error) = command.try_write(world) {
error_handler(error, CommandContext { world });
}
}
}

#[non_exhaustive]
pub struct CommandContext<'a> {
pub world: &'a mut World,
}

/// Similar to [`FallibleCommandConfig`] however does not
/// implement [`DerefMut`] nor return `&mut T` of the underlying
/// Commands type.
pub struct FinalFallibleCommandConfig<'a, C, T>
where
C: FallibleCommand,
T: AddCommand,
{
command: Option<C>,
inner: &'a mut T,
}

macro_rules! impl_fallible_commands {
($name:ident, $returnty:ty, $returnfunc:ident) => {
impl<'a, C, T> $name<'a, C, T>
where
C: FallibleCommand,
C::Error: Debug,
T: AddCommand,
{
#[inline]
pub(crate) fn new(command: C, inner: &'a mut T) -> Self {
Self {
command: Some(command),
inner,
}
}

#[inline]
#[allow(dead_code)]
fn return_inner(&mut self) -> &mut T {
self.inner
}

#[inline]
#[allow(dead_code)]
fn return_unit(&self) {}
}

impl<'a, C, T> $name<'a, C, T>
where
C: FallibleCommand,
C::Error: Debug,
T: AddCommand,
{
/// If the command failed, run the provided `error_handler`.
///
/// ## Note
/// This is normally used in conjunction with [`CommandErrorHandler`].
/// However, this can also be used with custom error handlers (e.g. closures).
///
/// # Examples
/// ```
/// use bevy_ecs::prelude::*;
///
/// fn system(mut commands: Commands) {
/// // built-in error handler
/// commands.spawn().insert(42).on_err(CommandErrorHandler::ignore);
///
/// // custom error handler
/// commands.spawn().insert(42).on_err(|error, ctx| {});
/// }
/// ```
pub fn on_err(
&mut self,
error_handler: impl FnOnce(C::Error, CommandContext) + Send + Sync + 'static,
) -> $returnty {
let command = self
.command
.take()
.expect("Cannot call `on_err` multiple times for a command error handler.");
self.inner.add_command(HandledErrorCommand {
command,
error_handler,
});
self.$returnfunc()
}
}

impl<'a, C, T> Drop for $name<'a, C, T>
where
C: FallibleCommand,
T: AddCommand,
{
fn drop(&mut self) {
if self.command.is_some() {
self.on_err(CommandErrorHandler::log);
}
}
}
};
}

impl_fallible_commands!(FinalFallibleCommandConfig, (), return_unit);
impl_fallible_commands!(FallibleCommandConfig, &mut T, return_inner);
Loading

0 comments on commit 1cf6ebd

Please sign in to comment.