diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 3b3e3e2b18..d129f4c833 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -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, + IntoSystem, Local, NonSend, NonSendMut, ParallelCommands, ParamSet, Query, + RemovedComponents, Res, ResMut, Resource, System, SystemParamFunction, }, world::{FromWorld, Mut, World}, }; diff --git a/crates/bevy_ecs/src/system/system_chaining.rs b/crates/bevy_ecs/src/system/system_chaining.rs index 0fac40e231..0a338cf0f4 100644 --- a/crates/bevy_ecs/src/system/system_chaining.rs +++ b/crates/bevy_ecs/src/system/system_chaining.rs @@ -142,3 +142,131 @@ where } } } + +/// A collection of common adapters for [chaining](super::ChainSystem) the result of a system. +pub mod adapter { + use crate::system::In; + use std::fmt::Debug; + + /// Converts a regular function into a system adapter. + /// + /// # Examples + /// ``` + /// use bevy_ecs::prelude::*; + /// + /// return1 + /// .chain(system_adapter::new(u32::try_from)) + /// .chain(system_adapter::unwrap) + /// .chain(print); + /// + /// fn return1() -> u64 { 1 } + /// fn print(In(x): In) { + /// println!("{x:?}"); + /// } + /// ``` + pub fn new(mut f: impl FnMut(T) -> U) -> impl FnMut(In) -> U { + move |In(x)| f(x) + } + + /// 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(In(res): In>) -> 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> + /// ) -> Option<()> { + /// let monster_id = q.iter().next()?; + /// println!("Monster entity is {monster_id:?}"); + /// Some(()) + /// } + /// ``` + pub fn ignore(In(_): In) {} + + #[cfg(test)] + #[test] + fn assert_systems() { + use std::str::FromStr; + + use crate::{prelude::*, system::assert_is_system}; + + /// Mocks a system that returns a value of type `T`. + fn returning() -> T { + unimplemented!() + } + + assert_is_system(returning::>.chain(unwrap)); + assert_is_system(returning::>.chain(ignore)); + assert_is_system(returning::<&str>.chain(new(u64::from_str)).chain(unwrap)); + } +}