Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add a module for common system chain/pipe adapters #5776

Closed
wants to merge 12 commits into from
6 changes: 3 additions & 3 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ pub mod prelude {
StageLabel, State, SystemLabel, SystemSet, SystemStage,
},
system::{
Commands, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem, Local, NonSend,
NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut,
Resource, System, SystemParamFunction,
adapter as system_adapter, Commands, In, IntoChainSystem, IntoExclusiveSystem,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
adapter as system_adapter, Commands, In, IntoChainSystem, IntoExclusiveSystem,
adapter as chain_adapter, Commands, In, IntoChainSystem, IntoExclusiveSystem,

Wouldn't it be better to use chain_adapter as those adapters can only be used on ChainSystems?

Copy link
Member Author

@JoJoJet JoJoJet Aug 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current plan is to rename .chain() to .pipe(), so I'd rather not give it a name that we'd have to change later.

Also, system_adapter feels more consistent to me. The two ways of accessing this module are bevy::ecs::system::adapter and bevy::prelude::system_adapter. Like how bevy_ecs and bevy::ecs mean the same thing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sense

IntoSystem, Local, NonSend, NonSendMut, ParallelCommands, ParamSet, Query,
RemovedComponents, Res, ResMut, Resource, System, SystemParamFunction,
},
world::{FromWorld, Mut, World},
};
Expand Down
127 changes: 127 additions & 0 deletions crates/bevy_ecs/src/system/system_chaining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,130 @@ where
}
}
}

/// A collection of common adapters for [chaining](super::ChainSystem) the result of a system.
pub mod adapter {
JoJoJet marked this conversation as resolved.
Show resolved Hide resolved
use crate::system::In;
use std::fmt::Debug;

/// System adapter that converts [`Result<T, _>`] into [`Option<T>`].
pub fn ok<T, E>(In(res): In<Result<T, E>>) -> Option<T> {
res.ok()
}

/// System adapter that maps [`None`] -> [`Err`].
///
/// Often, it is better to call `Option::ok_or` at the place where the `?` operator is invoked,
/// as the extra context allows you to provide a better error message.
///
/// Sometimes, it is more conevenient to handle it system-wide, though.
pub fn ok_or<T, E: Clone>(err: E) -> impl FnMut(In<Option<T>>) -> Result<T, E> {
ok_or_else(move || err.clone())
}

/// System adapter that maps [`None`] -> [`Err`], calling a closure to produce each error.
///
/// Often, it is better to call `Option::ok_or_else` at the place where the `?` operator is invoked,
/// as the extra context allows you to provide a better error message.
///
/// Sometimes, it is more convenient to handle it system-wide, though.
pub fn ok_or_else<T, E>(mut f: impl FnMut() -> E) -> impl FnMut(In<Option<T>>) -> Result<T, E> {
move |In(x)| x.ok_or_else(&mut f)
}

/// System adapter that unwraps the `Ok` variant of a [`Result`].
/// This is useful for fallible systems that should panic in the case of an error.
///
/// There is no equivalent adapter for [`Option`]. Instead, it's best to provide
/// an error message and convert to a `Result` using `ok_or{_else}`.
///
/// # Examples
///
/// Panicking on error
///
/// ```
/// use bevy_ecs::prelude::*;
/// #
/// # #[derive(StageLabel)]
/// # enum CoreStage { Update };
///
/// // Building a new schedule/app...
/// # use bevy_ecs::schedule::SystemStage;
/// # let mut sched = Schedule::default(); sched
/// # .add_stage(CoreStage::Update, SystemStage::single_threaded())
/// .add_system_to_stage(
/// CoreStage::Update,
/// // Panic if the load system returns an error.
/// load_save_system.chain(system_adapter::unwrap)
/// )
/// // ...
/// # ;
/// # let mut world = World::new();
/// # sched.run(&mut world);
///
/// // A system which may fail irreparably.
/// fn load_save_system() -> Result<(), std::io::Error> {
/// let save_file = open_file("my_save.json")?;
/// dbg!(save_file);
/// Ok(())
/// }
/// # fn open_file(name: &str) -> Result<&'static str, std::io::Error>
/// # { Ok("hello world") }
/// ```
pub fn unwrap<T, E: Debug>(In(res): In<Result<T, E>>) -> T {
res.unwrap()
}

/// System adapter that ignores the output of the previous system in a chain.
/// This is useful for fallible systems that should simply return early in case of an `Err`/`None`.
///
/// # Examples
///
/// Returning early
///
/// ```
/// use bevy_ecs::prelude::*;
///
/// // Marker component for an enemy entity.
/// #[derive(Component)]
/// struct Monster;
/// #
/// # #[derive(StageLabel)]
/// # enum CoreStage { Update };
///
/// // Building a new schedule/app...
/// # use bevy_ecs::schedule::SystemStage;
/// # let mut sched = Schedule::default(); sched
/// # .add_stage(CoreStage::Update, SystemStage::single_threaded())
/// .add_system_to_stage(
/// CoreStage::Update,
/// // If the system fails, just move on and try again next frame.
/// fallible_system.chain(system_adapter::ignore)
/// )
/// // ...
/// # ;
/// # let mut world = World::new();
/// # sched.run(&mut world);
///
/// // A system which may return early. It's more convenient to use the `?` operator for this.
/// fn fallible_system(
/// q: Query<Entity, With<Monster>>
/// ) -> Option<()> {
/// # // Note: This *will* fail, since we never create a monster entity.
JoJoJet marked this conversation as resolved.
Show resolved Hide resolved
/// let monster_id = q.iter().next()?;
/// eprintln!("Monster entity is {monster_id:?}");
JoJoJet marked this conversation as resolved.
Show resolved Hide resolved
/// Some(())
/// }
/// ```
pub fn ignore<T>(In(_): In<T>) {}

/// System adapter that converts the output of a system to type `T`, via the [`Into`] trait.
pub fn into<T>(In(val): In<impl Into<T>>) -> T {
val.into()
}

/// System adapter that attempts to convert the output of a system to type `T`, via the [`TryInto`] trait.
pub fn try_into<T, U: TryInto<T>>(In(val): In<U>) -> Result<T, U::Error> {
val.try_into()
}
}