Skip to content

Commit ea9c8f8

Browse files
joseph-giojames7132
authored andcommitted
Add a module for common system chain/pipe adapters (bevyengine#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.
1 parent de0bbdd commit ea9c8f8

File tree

2 files changed

+131
-3
lines changed

2 files changed

+131
-3
lines changed

crates/bevy_ecs/src/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ pub mod prelude {
3939
StageLabel, State, SystemLabel, SystemSet, SystemStage,
4040
},
4141
system::{
42-
Commands, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem, Local, NonSend,
43-
NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut,
44-
Resource, System, SystemParamFunction,
42+
adapter as system_adapter, Commands, In, IntoChainSystem, IntoExclusiveSystem,
43+
IntoSystem, Local, NonSend, NonSendMut, ParallelCommands, ParamSet, Query,
44+
RemovedComponents, Res, ResMut, Resource, System, SystemParamFunction,
4545
},
4646
world::{FromWorld, Mut, World},
4747
};

crates/bevy_ecs/src/system/system_chaining.rs

+128
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,131 @@ where
142142
}
143143
}
144144
}
145+
146+
/// A collection of common adapters for [chaining](super::ChainSystem) the result of a system.
147+
pub mod adapter {
148+
use crate::system::In;
149+
use std::fmt::Debug;
150+
151+
/// Converts a regular function into a system adapter.
152+
///
153+
/// # Examples
154+
/// ```
155+
/// use bevy_ecs::prelude::*;
156+
///
157+
/// return1
158+
/// .chain(system_adapter::new(u32::try_from))
159+
/// .chain(system_adapter::unwrap)
160+
/// .chain(print);
161+
///
162+
/// fn return1() -> u64 { 1 }
163+
/// fn print(In(x): In<impl std::fmt::Debug>) {
164+
/// println!("{x:?}");
165+
/// }
166+
/// ```
167+
pub fn new<T, U>(mut f: impl FnMut(T) -> U) -> impl FnMut(In<T>) -> U {
168+
move |In(x)| f(x)
169+
}
170+
171+
/// System adapter that unwraps the `Ok` variant of a [`Result`].
172+
/// This is useful for fallible systems that should panic in the case of an error.
173+
///
174+
/// There is no equivalent adapter for [`Option`]. Instead, it's best to provide
175+
/// an error message and convert to a `Result` using `ok_or{_else}`.
176+
///
177+
/// # Examples
178+
///
179+
/// Panicking on error
180+
///
181+
/// ```
182+
/// use bevy_ecs::prelude::*;
183+
/// #
184+
/// # #[derive(StageLabel)]
185+
/// # enum CoreStage { Update };
186+
///
187+
/// // Building a new schedule/app...
188+
/// # use bevy_ecs::schedule::SystemStage;
189+
/// # let mut sched = Schedule::default(); sched
190+
/// # .add_stage(CoreStage::Update, SystemStage::single_threaded())
191+
/// .add_system_to_stage(
192+
/// CoreStage::Update,
193+
/// // Panic if the load system returns an error.
194+
/// load_save_system.chain(system_adapter::unwrap)
195+
/// )
196+
/// // ...
197+
/// # ;
198+
/// # let mut world = World::new();
199+
/// # sched.run(&mut world);
200+
///
201+
/// // A system which may fail irreparably.
202+
/// fn load_save_system() -> Result<(), std::io::Error> {
203+
/// let save_file = open_file("my_save.json")?;
204+
/// dbg!(save_file);
205+
/// Ok(())
206+
/// }
207+
/// # fn open_file(name: &str) -> Result<&'static str, std::io::Error>
208+
/// # { Ok("hello world") }
209+
/// ```
210+
pub fn unwrap<T, E: Debug>(In(res): In<Result<T, E>>) -> T {
211+
res.unwrap()
212+
}
213+
214+
/// System adapter that ignores the output of the previous system in a chain.
215+
/// This is useful for fallible systems that should simply return early in case of an `Err`/`None`.
216+
///
217+
/// # Examples
218+
///
219+
/// Returning early
220+
///
221+
/// ```
222+
/// use bevy_ecs::prelude::*;
223+
///
224+
/// // Marker component for an enemy entity.
225+
/// #[derive(Component)]
226+
/// struct Monster;
227+
/// #
228+
/// # #[derive(StageLabel)]
229+
/// # enum CoreStage { Update };
230+
///
231+
/// // Building a new schedule/app...
232+
/// # use bevy_ecs::schedule::SystemStage;
233+
/// # let mut sched = Schedule::default(); sched
234+
/// # .add_stage(CoreStage::Update, SystemStage::single_threaded())
235+
/// .add_system_to_stage(
236+
/// CoreStage::Update,
237+
/// // If the system fails, just move on and try again next frame.
238+
/// fallible_system.chain(system_adapter::ignore)
239+
/// )
240+
/// // ...
241+
/// # ;
242+
/// # let mut world = World::new();
243+
/// # sched.run(&mut world);
244+
///
245+
/// // A system which may return early. It's more convenient to use the `?` operator for this.
246+
/// fn fallible_system(
247+
/// q: Query<Entity, With<Monster>>
248+
/// ) -> Option<()> {
249+
/// let monster_id = q.iter().next()?;
250+
/// println!("Monster entity is {monster_id:?}");
251+
/// Some(())
252+
/// }
253+
/// ```
254+
pub fn ignore<T>(In(_): In<T>) {}
255+
256+
#[cfg(test)]
257+
#[test]
258+
fn assert_systems() {
259+
use std::str::FromStr;
260+
261+
use crate::{prelude::*, system::assert_is_system};
262+
263+
/// Mocks a system that returns a value of type `T`.
264+
fn returning<T>() -> T {
265+
unimplemented!()
266+
}
267+
268+
assert_is_system(returning::<Result<u32, std::io::Error>>.chain(unwrap));
269+
assert_is_system(returning::<Option<()>>.chain(ignore));
270+
assert_is_system(returning::<&str>.chain(new(u64::from_str)).chain(unwrap));
271+
}
272+
}

0 commit comments

Comments
 (0)