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<Dog>>) -> 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<String>, 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.
			
			
This commit is contained in:
		
							parent
							
								
									74520c0e95
								
							
						
					
					
						commit
						584d855fd1
					
				@ -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},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -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<impl std::fmt::Debug>) {
 | 
			
		||||
    ///     println!("{x:?}");
 | 
			
		||||
    /// }
 | 
			
		||||
    /// ```
 | 
			
		||||
    pub fn new<T, U>(mut f: impl FnMut(T) -> U) -> impl FnMut(In<T>) -> 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<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<()> {
 | 
			
		||||
    ///     let monster_id = q.iter().next()?;
 | 
			
		||||
    ///     println!("Monster entity is {monster_id:?}");
 | 
			
		||||
    ///     Some(())
 | 
			
		||||
    /// }
 | 
			
		||||
    /// ```
 | 
			
		||||
    pub fn ignore<T>(In(_): In<T>) {}
 | 
			
		||||
 | 
			
		||||
    #[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>() -> T {
 | 
			
		||||
            unimplemented!()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assert_is_system(returning::<Result<u32, std::io::Error>>.chain(unwrap));
 | 
			
		||||
        assert_is_system(returning::<Option<()>>.chain(ignore));
 | 
			
		||||
        assert_is_system(returning::<&str>.chain(new(u64::from_str)).chain(unwrap));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user