From a410c557d63219de4dda0a221394bfd91bbe5821 Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Tue, 30 Aug 2022 00:17:20 +0000 Subject: [PATCH] Add a module for common system `chain`/`pipe` adapters (#5776) # Objective Right now, users have to implement basic system adapters such as `Option` <-> `Result` conversions by themselves. This is slightly annoying and discourages the use of system chaining. ## Solution Add the module `system_adapter` to the prelude, which contains a collection of common adapters. This is very ergonomic in practice. ## Examples Convenient early returning. ```rust use bevy::prelude::*; App::new() // If the system fails, just try again next frame. .add_system(pet_dog.chain(system_adapter::ignore)) .run(); #[derive(Component)] struct Dog; fn pet_dog(dogs: Query<(&Name, Option<&Parent>), With>) -> Option<()> { let (dog, dad) = dogs.iter().next()?; println!("You pet {dog}. He/she/they are a good boy/girl/pupper."); let (dad, _) = dogs.get(dad?.get()).ok()?; println!("Their dad's name is {dad}"); Some(()) } ``` Converting the output of a system ```rust use bevy::prelude::*; App::new() .add_system( find_name .chain(system_adapter::new(String::from)) .chain(spawn_with_name), ) .run(); fn find_name() -> &'static str { /* ... */ } fn spawn_with_name(In(name): In, mut commands: Commands) { commands.spawn().insert(Name::new(name)); } ``` --- ## Changelog * Added the module `bevy_ecs::prelude::system_adapter`, which contains a collection of common system chaining adapters. * `new` - Converts a regular fn to a system adapter. * `unwrap` - Similar to `Result::unwrap` * `ignore` - Discards the output of the previous system. --- crates/bevy_ecs/src/lib.rs | 6 +- crates/bevy_ecs/src/system/system_chaining.rs | 128 ++++++++++++++++++ 2 files changed, 131 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 3b3e3e2b18d37a..d129f4c833d681 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 0fac40e2313897..0a338cf0f48630 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)); + } +}