mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 20:53:53 +00:00
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
2 changed files with 131 additions and 3 deletions
|
@ -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 a new issue