Bevy system error handling

Decorate your bevy system with the sysfail macro attribute to handle failure.
Before
use bevy::prelude::*;
use bevy::utils::Duration;
use thiserror::Error;
#[derive(Error, Debug)]
enum GizmoError {
#[error("A Gizmo error")]
Error,
}
#[derive(Debug, PartialEq, Eq, Hash, SystemSet, Clone)]
enum TransformGizmoSystem { Drag, Place }
fn main() {
let mut app = App::new();
app.add_plugins(bevy::time::TimePlugin)
.add_systems(Update, (
drag_gizmo
.pipe(print_gizmo_error)
.in_set(TransformGizmoSystem::Drag),
delete_gizmo
.pipe(|In(_)| {})
.after(TransformGizmoSystem::Place),
place_gizmo
.pipe(print_gizmo_error)
.in_set(TransformGizmoSystem::Place)
.after(TransformGizmoSystem::Drag),
));
app.update();
}
fn print_gizmo_error(
In(result): In<Result<(), Box<dyn std::error::Error>>>,
mut last_error_occurence: Local<Option<Duration>>,
time: Res<Time>,
) {
}
fn drag_gizmo(time: Res<Time>) -> Result<(), Box<dyn std::error::Error>> {
println!("drag time is: {}", time.elapsed_seconds());
let _ = Err(GizmoError::Error)?;
println!("This will never print");
Ok(())
}
fn place_gizmo() -> Result<(), Box<dyn std::error::Error>> {
let () = Result::<(), &'static str>::Ok(())?;
println!("this line should actually show up");
let _ = Err("Ah, some creative use of info logging I see")?;
Ok(())
}
fn delete_gizmo(time: Res<Time>) -> Option<()> {
println!("delete time is: {}", time.elapsed_seconds());
let _ = None?;
println!("This will never print");
Some(())
}
After
use bevy::prelude::*;
use bevy_mod_sysfail::prelude::*;
use thiserror::Error;
#[derive(Error, Debug)]
enum GizmoError {
#[error("A Gizmo error")]
Error,
}
fn main() {
let mut app = App::new();
app.add_plugins(bevy::time::TimePlugin)
.add_systems(Update, (
drag_gizmo,
delete_gizmo.after(place_gizmo),
place_gizmo.after(drag_gizmo)
));
app.update();
}
#[sysfail]
fn drag_gizmo(time: Res<Time>) {
println!("drag time is: {}", time.elapsed_seconds());
let _ = Err(GizmoError::Error)?;
println!("This will never print");
}
#[sysfail(Log<&'static str, Info>)]
fn place_gizmo() {
let () = Result::<(), &'static str>::Ok(())?;
println!("this line should actually show up");
let _ = Err("Ah, some creative use of info logging I see")?;
}
#[sysfail(Ignore)]
fn delete_gizmo(time: Res<Time>) {
println!("delete time is: {}", time.elapsed_seconds());
let _ = Err(342_i32)?;
println!("This will never print");
}
sysfail attribute
sysfail is an attribute macro you can slap on top of your systems to define
the handling of errors. Unlike pipe, this is done directly at the definition
site, and not when adding to the app. As a result, it's easy to see at a glance
what kind of error handling is happening in the system, it also allows using
the system name as a label in system dependency specification.
sysfail(E) systems return a value of type Result<(), E>. The return type
is added by the macro, so do not add it yourself!
E is a type that implements the Failure trait. bevy_mod_sysfail exports
several types that implement Failure:
Log<Err, Lvl = Warn>: Will log Err to the tracing logger.
- The first type parameter
Err implements the Dedup trait. You can
implement Dedup for your own types, but you can always use the
anyhow::Error, Box<dyn std::error::Error> and &'static str types,
as those already implement Dedup.
- The second type parameter specifies the level of the log. It is optional
and by default it is
Warn
LogSimply: Is similar to Log, but without deduplication.
Emit<Ev>: Will emit the Ev bevy Event whenever the system returns an Err
Ignore: Ignore errors, do as if nothing happened.
Example usages:
use bevy::prelude::*;
use bevy_mod_sysfail::prelude::*;
use thiserror::Error;
#[derive(Error, Debug)]
enum MyCustomError {
#[error("A Custom error")]
Error,
}
#[sysfail]
fn generic_failure() { }
#[sysfail(Log<&'static str>)]
fn log_a_str_message() {
let _ = Err("Yep, just like that")?;
}
#[sysfail(Log<anyhow::Error>)]
fn log_an_anyhow_error() {
let _ = Err(MyCustomError::Error)?;
}
#[sysfail(LogSimply<MyCustomError, Trace>)]
fn log_trace_on_failure() { }
#[sysfail(LogSimply<MyCustomError, Error>)]
fn log_error_on_failure() { }
use bevy::app::AppExit;
#[derive(Event)]
enum ChangeMenu {
Main,
Tools,
}
#[sysfail(Emit<ChangeMenu>)]
fn change_menu() { }
#[sysfail(Emit<AppExit>)]
fn quit_app_on_error() { }
#[sysfail(Ignore)]
fn do_not_care_about_failure() { }
Exclusive systems
For exclusive systems, use the #[exclusive_sysfail] macro. Note that only
Failure<Param = ()> work with exclusive systems. This excludes Log, so
make sure to use LogSimply instead.
Custom handling
bevy_mod_sysfail is not limited to the predefined set of Failures, you can
define your own by implementing it yourself.
See the custom_failure example for sample code.
Change log
See the CHANGELOG.
Version Matrix
| bevy |
latest supporting version |
| 0.12 |
6.0.0 |
| 0.11 |
4.3.0 |
| 0.10 |
2.0.0 |
| 0.9 |
1.1.0 |
| 0.8 |
0.1.0 |
License
Copyright © 2022 Nicola Papale
This software is licensed under Apache 2.0.