Skip to content

Commit

Permalink
Trait-based behavior control
Browse files Browse the repository at this point in the history
- Removed all deprecated methods
- Replace the `log = "foo"` syntax by `Log<ErrorType, Foo>`.
- Remove `#[quick_sysfail]` in favor of `#[sysfail(Ignore)]`
- Remove support for `Option`.
- Automatically add return type to system. This means that you should remove
  the return type from your `#[sysfail]` systems.
- `Failure` is now a trait on the **error type** returned by the `#[sysfail]`
  system, rather than the whole return type.
- `Failure` now has an associated type: `Param`. It allows accessing arbitrary
  system parameters in the error handling code.
- Now `bevy_mod_sysfail` directly depends on `bevy`, rather than its subcrates
- Added the `full` features, enabled by default. Disabling removes the `bevy`
  dependency, to only depend on `bevy_ecs`, but at the cost of removing
  the `Log` `Failure` definition.
- Renamed `FailureMode` to `Dedup`.
- Added the `Emit` `Failure`, which sends `Err`s as bevy `Event`s.
- Added `LogSimply`, a variant of `Log` that works without `Res<Time>`
- Added example showing how to extend with your own behavior the `Failure` trait.
  • Loading branch information
nicopap committed Dec 6, 2023
1 parent 4b93665 commit b8b7389
Show file tree
Hide file tree
Showing 17 changed files with 579 additions and 454 deletions.
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
# `6.0.0`

Major **Breaking** release.

- Removed all deprecated methods
- Replace the `log = "foo"` syntax by `Log<ErrorType, Foo>`.
- Remove `#[quick_sysfail]` in favor of `#[sysfail(Ignore)]`
- Remove support for `Option`.
- Automatically add return type to system. This means that you should remove
the return type from your `#[sysfail]` systems.
- `Failure` is now a trait on the **error type** returned by the `#[sysfail]`
system, rather than the whole return type.
- `Failure` now has an associated type: `Param`. It allows accessing arbitrary
system parameters in the error handling code.
- Now `bevy_mod_sysfail` directly depends on `bevy`, rather than its subcrates
- Added the `full` features, enabled by default. Disabling removes the `bevy`
dependency, to only depend on `bevy_ecs`, but at the cost of removing
the `Log` `Failure` definition.
- Renamed `FailureMode` to `Dedup`.
- Added the `Emit` `Failure`, which sends `Err`s as bevy `Event`s.
- Added `LogSimply`, a variant of `Log` that works without `Res<Time>`
- Added example showing how to extend with your own behavior the `Failure` trait.

# `4.1.0`

* fix macro dependency of the non-macro crate
Expand Down
15 changes: 11 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,21 @@ exclude = ["assets", ".github"]
version = "5.0.0"
edition = "2021"

[features]
default = ["full"]
full = ["dep:bevy"]

[dependencies]
bevy_ecs = { version = "0.12", default-features = false }
bevy_utils = "0.12"
bevy_log = "0.12"
bevy_time = "0.12"
bevy_ecs = "0.12"

bevy = { version = "0.12", default-features = false, optional = true }
bevy_mod_sysfail_macros = { path = "./macros_impl", version = "4.2.0" }
anyhow = { version = "1.0", default-features = false }

[dev-dependencies]
bevy = { version = "0.12", default-features = false }
bevy = { version = "0.12", default-features = true }
bevy-debug-text-overlay = "7.0.0"
anyhow = "1.0"
thiserror = "1.0"

Expand All @@ -30,5 +35,7 @@ dependent-version = "upgrade"
[package.metadata.release]
pre-release-replacements = [
{search="\\| 0.12 \\| [0-9.]* \\|",replace="| 0.12 | {{version}} |",file="README.md"},
{search="bevy_mod_sysfail/[0-9.]+/",replace="bevy_mod_sysfail/{{version}}/",file="README.md"},
{search="bevy_mod_sysfail/blob/v[0-9.]+/",replace="bevy_mod_sysfail/blob/v{{version}}/",file="README.md"},
]

18 changes: 18 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
CLIPPY_ARGS=-- -D clippy::all -D clippy::pedantic -D clippy::nursery \
-D missing_docs \
-D clippy::undocumented_unsafe_blocks \
-W clippy::needless-pass-by-value \
-A clippy::missing_const_for_fn \
-A clippy::module_name_repetitions \
-A clippy::redundant_pub_crate

check:
cargo check --examples
run:
cargo run --example custom_failure
pre-hook:
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps
cargo clippy --workspace $(CLIPPY_ARGS)
cargo fmt --all -- --check
cargo clippy --workspace --no-default-features
cargo test -j12
141 changes: 81 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Decorate your bevy system with the [`sysfail`] macro attribute to handle failure

#### Before

```rust
```rust,no_run
use bevy::prelude::*;
use bevy::utils::Duration;
Expand Down Expand Up @@ -76,9 +76,9 @@ fn delete_gizmo(time: Res<Time>) -> Option<()> {

#### After

```rust
```rust,no_run
use bevy::prelude::*;
use bevy_mod_sysfail::macros::*;
use bevy_mod_sysfail::prelude::*;
use thiserror::Error;
Expand All @@ -99,26 +99,24 @@ fn main() {
app.update();
}
#[sysfail(log)]
fn drag_gizmo(time: Res<Time>) -> Result<(), anyhow::Error> {
#[sysfail]
fn drag_gizmo(time: Res<Time>) {
println!("drag time is: {}", time.elapsed_seconds());
let _ = Err(GizmoError::Error)?;
println!("This will never print");
Ok(())
}
#[sysfail(log(level = "info"))]
fn place_gizmo() -> Result<(), &'static str> {
#[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")?;
Ok(())
}
#[quick_sysfail]
#[sysfail(Ignore)]
fn delete_gizmo(time: Res<Time>) {
println!("delete time is: {}", time.elapsed_seconds());
let _ = None?;
let _ = Err(342_i32)?;
println!("This will never print");
}
```
Expand All @@ -131,70 +129,88 @@ 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.

The [`sysfail`] attribute can only be used on systems returning a type
implementing the [`Failure`] trait. [`Failure`] is implemented for
`Result<(), impl FailureMode>` and `Option<()>`.
[`sysfail`] takes a single argument, it is one of the following:
`sysfail(E)` systems return a value of type `Result<(), E>`. The return type
is added by the macro, so do not add it yourself!

- `log`: print the `Err` of the `Result` return value, prints a very
generic "A none value" when the return type is `Option`.
By default, most things are logged at `error` level.
- `log(level = "{silent,trace,debug,info,warn,error}")`: This forces
logging of errors at a certain level (make sure to add the quotes)
- `ignore`: This is a shortcut for `log(level = "silent")`
`E` is a type that implements the `Failure` trait. `bevy_mod_sysfail` exports
several types that implement `Failure`:

Note that when the level is not `"silent"`, `bevy_mod_sysfail` adds the
`Res<Time>` and `Local<LoggedErrors>` system parameters to be able to supress
repeating error messages.
- [`Log<Err, Lvl = Warn>`][`Log`]: 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>`][`Emit`]: Will emit the `Ev` bevy [`Event`] whenever the system returns an `Err`
- [`Ignore`]: Ignore errors, do as if nothing happened.

### `quick_sysfail` attribute

[`quick_sysfail`] is like `sysfail(ignore)` but only works on `Option<()>`.
This attribute, unlike `sysfail` allows you to elide the final `Some(())`
and the type signature of the system. It's for the maximally lazy, like
me.
Example usages:

```rust
use bevy_mod_sysfail::macros::*;
use bevy::prelude::*;
use bevy_mod_sysfail::prelude::*;
use thiserror::Error;

// -- Log a value --

#[derive(Error, Debug)]
enum MyCustomError {
#[error("A Custom error")]
Error,
}

#[sysfail(ignore)]
fn place_gizmo() -> Option<()> {
//
Some(())
// Equivalent to #[sysfail(Box<dyn std::error::Error>)]
#[sysfail]
fn generic_failure() { /* ... */ }

#[sysfail(Log<&'static str>)]
fn log_a_str_message() {
let _ = Err("Yep, just like that")?;
}
// equivalent to:
#[quick_sysfail]
fn quick_place_gizmo() {
//

#[sysfail(Log<anyhow::Error>)]
fn log_an_anyhow_error() {
let _ = Err(MyCustomError::Error)?;
}
```

### Traits
#[sysfail(LogSimply<MyCustomError, Trace>)]
fn log_trace_on_failure() { /* ... */ }

How error is handled is not very customizable, but there is a few behaviors
controllable by the user, always through traits.
#[sysfail(LogSimply<MyCustomError, Error>)]
fn log_error_on_failure() { /* ... */ }

#### `Failure` trait
// -- Emit an event --
use bevy::app::AppExit;

[`Failure`] is implemented for `Result<(), impl FailureMode>` and `Option<()>`.
#[derive(Event)]
enum ChangeMenu {
Main,
Tools,
}

Systems marked with the [`sysfail`] attribute **must** return a type implementing
[`Failure`].
#[sysfail(Emit<ChangeMenu>)]
fn change_menu() { /* ... */ }

#### `FailureMode` trait
#[sysfail(Emit<AppExit>)]
fn quit_app_on_error() { /* ... */ }

[`FailureMode`] defines how the failure is handled. By implementing the
trait on your own error types, you can specify:
// -- Ignore all errors --

- What constitutes "distinct" error types.
- How long an error must not be produced in order to be displayed again.
#[sysfail(Ignore)]
fn do_not_care_about_failure() { /* ... */ }
```

[`FailureMode`] is implemented for `Box<dyn Error>`, `anyhow::Error`, `()`
and `&'static str`.
### 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 [CHANGELOG.md](./CHANGELOG.md)
See the [CHANGELOG].

### Version Matrix

Expand All @@ -212,8 +228,13 @@ Copyright © 2022 Nicola Papale

This software is licensed under Apache 2.0.


[`FailureMode`]: https://docs.rs/bevy_mod_sysfail/latest/bevy_mod_sysfail/trait.FailureMode.html
[`Failure`]: https://docs.rs/bevy_mod_sysfail/latest/bevy_mod_sysfail/trait.Failure.html
[`quick_sysfail`]: https://docs.rs/bevy_mod_sysfail/latest/bevy_mod_sysfail/attr.quick_sysfail.html
[`sysfail`]: https://docs.rs/bevy_mod_sysfail/latest/bevy_mod_sysfail/attr.sysfail.html
[CHANGELOG]: https://github.com/nicopap/bevy_mod_sysfail/blob/v5.0.0/CHANGELOG.md
[custom_failure example]: https://github.com/nicopap/bevy_mod_sysfail/blob/v5.0.0/examples/custom_failure.rs
[`Dedup`]: https://docs.rs/bevy_mod_sysfail/5.0.0/bevy_mod_sysfail/trait.Dedup.html
[`Failure`]: https://docs.rs/bevy_mod_sysfail/5.0.0/bevy_mod_sysfail/trait.Failure.html
[`sysfail`]: https://docs.rs/bevy_mod_sysfail/5.0.0/bevy_mod_sysfail/attr.sysfail.html
[`Emit`]: https://docs.rs/bevy_mod_sysfail/5.0.0/bevy_mod_sysfail/prelude/struct.Emit.html
[`Log`]: https://docs.rs/bevy_mod_sysfail/5.0.0/bevy_mod_sysfail/prelude/struct.Log.html
[`LogSimply`]: https://docs.rs/bevy_mod_sysfail/5.0.0/bevy_mod_sysfail/prelude/struct.LogSimply.html
[`Ignore`]: https://docs.rs/bevy_mod_sysfail/5.0.0/bevy_mod_sysfail/prelude/struct.Ignore.html
[`Event`]: https://docs.rs/bevy/0.12/bevy/ecs/event/trait.Event.html
21 changes: 13 additions & 8 deletions examples/all_attributes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bevy::prelude::*;
use bevy_mod_sysfail::*;
use bevy_mod_sysfail::prelude::*;
use bevy_mod_sysfail::Dedup;

use thiserror::Error;

Expand All @@ -12,6 +13,12 @@ enum GizmoError {
Error,
}

impl Dedup for GizmoError {
type ID = ();

fn identify(&self) {}
}

fn main() {
let mut app = App::new();
app.add_plugins((MinimalPlugins, bevy::log::LogPlugin::default()))
Expand All @@ -24,25 +31,23 @@ fn drag_gizmo(time: Res<Time>) {
println!("drag time is: {}", time.elapsed_seconds());
let _ = Err(GizmoError::Error)?;
println!("This will never print");
Ok(())
}

#[sysfail]
fn place_gizmo() -> Result<(), Log<&'static str, Info>> {
#[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")?;
Ok(())
}

/// This also has some doc
#[quick_sysfail]
#[sysfail(Ignore)]
fn delete_gizmo(time: Res<Time>, mut query: Query<&mut Transform>, foos: Query<Entity, With<Foo>>) {
println!("delete time is: {}", time.elapsed_seconds());
for foo in &foos {
let mut trans = query.get_mut(foo).ok()?;
let mut trans = query.get_mut(foo)?;
trans.translation += Vec3::Y;
}
let _ = None?;
let _ = Err(())?;
println!("This will never print");
}
Loading

0 comments on commit b8b7389

Please sign in to comment.