10 releases (major breaking)
7.0.0 | Feb 18, 2024 |
---|---|
6.0.0 | Jan 29, 2024 |
5.0.0 | Nov 4, 2023 |
4.3.0 | Oct 13, 2023 |
0.1.0 | Sep 7, 2022 |
#236 in Game dev
354 downloads per month
Used in 10 crates
(8 directly)
39KB
184 lines
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>,
) {
// error boilerplate, may include
// - avoiding printing multiple times the same error
// - Formatting and chosing the log level
}
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 logErr
to the tracing logger.- The first type parameter
Err
implements theDedup
trait. You can implementDedup
for your own types, but you can always use theanyhow::Error
,Box<dyn std::error::Error>
and&'static str
types, as those already implementDedup
. - The second type parameter specifies the level of the log. It is optional
and by default it is
Warn
- The first type parameter
LogSimply
: Is similar toLog
, but without deduplication.Emit<Ev>
: Will emit theEv
bevyEvent
whenever the system returns anErr
Ignore
: Ignore errors, do as if nothing happened.
Example usages:
use bevy::prelude::*;
use bevy_mod_sysfail::prelude::*;
use thiserror::Error;
// -- Log a value --
#[derive(Error, Debug)]
enum MyCustomError {
#[error("A Custom error")]
Error,
}
// Equivalent to #[sysfail(Log<Box<dyn std::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() { /* ... */ }
// -- Emit an event --
use bevy::app::AppExit;
#[derive(Event)]
enum ChangeMenu {
Main,
Tools,
}
#[sysfail(Emit<ChangeMenu>)]
fn change_menu() { /* ... */ }
#[sysfail(Emit<AppExit>)]
fn quit_app_on_error() { /* ... */ }
// -- Ignore all errors --
#[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 Failure
s, 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.13 | 7.0.0 |
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.
Dependencies
~9–37MB
~552K SLoC