Path: blob/main/crates/bevy_ecs/src/system/combinator.rs
6604 views
use alloc::{format, vec::Vec};1use bevy_utils::prelude::DebugName;2use core::marker::PhantomData;34use crate::{5component::{CheckChangeTicks, Tick},6prelude::World,7query::FilteredAccessSet,8schedule::InternedSystemSet,9system::{input::SystemInput, SystemIn, SystemParamValidationError},10world::unsafe_world_cell::UnsafeWorldCell,11};1213use super::{IntoSystem, ReadOnlySystem, RunSystemError, System};1415/// Customizes the behavior of a [`CombinatorSystem`].16///17/// # Examples18///19/// ```20/// use bevy_ecs::prelude::*;21/// use bevy_ecs::system::{CombinatorSystem, Combine, RunSystemError};22///23/// // A system combinator that performs an exclusive-or (XOR)24/// // operation on the output of two systems.25/// pub type Xor<A, B> = CombinatorSystem<XorMarker, A, B>;26///27/// // This struct is used to customize the behavior of our combinator.28/// pub struct XorMarker;29///30/// impl<A, B> Combine<A, B> for XorMarker31/// where32/// A: System<In = (), Out = bool>,33/// B: System<In = (), Out = bool>,34/// {35/// type In = ();36/// type Out = bool;37///38/// fn combine<T>(39/// _input: Self::In,40/// data: &mut T,41/// a: impl FnOnce(A::In, &mut T) -> Result<A::Out, RunSystemError>,42/// b: impl FnOnce(B::In, &mut T) -> Result<B::Out, RunSystemError>,43/// ) -> Result<Self::Out, RunSystemError> {44/// Ok(a((), data)? ^ b((), data)?)45/// }46/// }47///48/// # #[derive(Resource, PartialEq, Eq)] struct A(u32);49/// # #[derive(Resource, PartialEq, Eq)] struct B(u32);50/// # #[derive(Resource, Default)] struct RanFlag(bool);51/// # let mut world = World::new();52/// # world.init_resource::<RanFlag>();53/// #54/// # let mut app = Schedule::default();55/// app.add_systems(my_system.run_if(Xor::new(56/// IntoSystem::into_system(resource_equals(A(1))),57/// IntoSystem::into_system(resource_equals(B(1))),58/// // The name of the combined system.59/// "a ^ b".into(),60/// )));61/// # fn my_system(mut flag: ResMut<RanFlag>) { flag.0 = true; }62/// #63/// # world.insert_resource(A(0));64/// # world.insert_resource(B(0));65/// # app.run(&mut world);66/// # // Neither condition passes, so the system does not run.67/// # assert!(!world.resource::<RanFlag>().0);68/// #69/// # world.insert_resource(A(1));70/// # app.run(&mut world);71/// # // Only the first condition passes, so the system runs.72/// # assert!(world.resource::<RanFlag>().0);73/// # world.resource_mut::<RanFlag>().0 = false;74/// #75/// # world.insert_resource(B(1));76/// # app.run(&mut world);77/// # // Both conditions pass, so the system does not run.78/// # assert!(!world.resource::<RanFlag>().0);79/// #80/// # world.insert_resource(A(0));81/// # app.run(&mut world);82/// # // Only the second condition passes, so the system runs.83/// # assert!(world.resource::<RanFlag>().0);84/// # world.resource_mut::<RanFlag>().0 = false;85/// ```86#[diagnostic::on_unimplemented(87message = "`{Self}` can not combine systems `{A}` and `{B}`",88label = "invalid system combination",89note = "the inputs and outputs of `{A}` and `{B}` are not compatible with this combiner"90)]91pub trait Combine<A: System, B: System> {92/// The [input](System::In) type for a [`CombinatorSystem`].93type In: SystemInput;9495/// The [output](System::Out) type for a [`CombinatorSystem`].96type Out;9798/// When used in a [`CombinatorSystem`], this function customizes how99/// the two composite systems are invoked and their outputs are combined.100///101/// See the trait-level docs for [`Combine`] for an example implementation.102fn combine<T>(103input: <Self::In as SystemInput>::Inner<'_>,104data: &mut T,105a: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<A::Out, RunSystemError>,106b: impl FnOnce(SystemIn<'_, B>, &mut T) -> Result<B::Out, RunSystemError>,107) -> Result<Self::Out, RunSystemError>;108}109110/// A [`System`] defined by combining two other systems.111/// The behavior of this combinator is specified by implementing the [`Combine`] trait.112/// For a full usage example, see the docs for [`Combine`].113pub struct CombinatorSystem<Func, A, B> {114_marker: PhantomData<fn() -> Func>,115a: A,116b: B,117name: DebugName,118}119120impl<Func, A, B> CombinatorSystem<Func, A, B> {121/// Creates a new system that combines two inner systems.122///123/// The returned system will only be usable if `Func` implements [`Combine<A, B>`].124pub fn new(a: A, b: B, name: DebugName) -> Self {125Self {126_marker: PhantomData,127a,128b,129name,130}131}132}133134impl<A, B, Func> System for CombinatorSystem<Func, A, B>135where136Func: Combine<A, B> + 'static,137A: System,138B: System,139{140type In = Func::In;141type Out = Func::Out;142143fn name(&self) -> DebugName {144self.name.clone()145}146147#[inline]148fn flags(&self) -> super::SystemStateFlags {149self.a.flags() | self.b.flags()150}151152unsafe fn run_unsafe(153&mut self,154input: SystemIn<'_, Self>,155world: UnsafeWorldCell,156) -> Result<Self::Out, RunSystemError> {157struct PrivateUnsafeWorldCell<'w>(UnsafeWorldCell<'w>);158159Func::combine(160input,161&mut PrivateUnsafeWorldCell(world),162// SAFETY: The world accesses for both underlying systems have been registered,163// so the caller will guarantee that no other systems will conflict with `a` or `b`.164// If either system has `is_exclusive()`, then the combined system also has `is_exclusive`.165// Since we require a `combine` to pass in a mutable reference to `world` and that's a private type166// passed to a function as an unbound non-'static generic argument, they can never be called in parallel167// or re-entrantly because that would require forging another instance of `PrivateUnsafeWorldCell`.168// This means that the world accesses in the two closures will not conflict with each other.169|input, world| unsafe { self.a.run_unsafe(input, world.0) },170// `Self::validate_param_unsafe` already validated the first system,171// but we still need to validate the second system once the first one runs.172// SAFETY: See the comment above.173|input, world| unsafe {174self.b.validate_param_unsafe(world.0)?;175self.b.run_unsafe(input, world.0)176},177)178}179180#[cfg(feature = "hotpatching")]181#[inline]182fn refresh_hotpatch(&mut self) {183self.a.refresh_hotpatch();184self.b.refresh_hotpatch();185}186187#[inline]188fn apply_deferred(&mut self, world: &mut World) {189self.a.apply_deferred(world);190self.b.apply_deferred(world);191}192193#[inline]194fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {195self.a.queue_deferred(world.reborrow());196self.b.queue_deferred(world);197}198199#[inline]200unsafe fn validate_param_unsafe(201&mut self,202world: UnsafeWorldCell,203) -> Result<(), SystemParamValidationError> {204// We only validate parameters for the first system,205// since it may make changes to the world that affect206// whether the second system has valid parameters.207// The second system will be validated in `Self::run_unsafe`.208// SAFETY: Delegate to other `System` implementations.209unsafe { self.a.validate_param_unsafe(world) }210}211212fn initialize(&mut self, world: &mut World) -> FilteredAccessSet {213let mut a_access = self.a.initialize(world);214let b_access = self.b.initialize(world);215a_access.extend(b_access);216a_access217}218219fn check_change_tick(&mut self, check: CheckChangeTicks) {220self.a.check_change_tick(check);221self.b.check_change_tick(check);222}223224fn default_system_sets(&self) -> Vec<InternedSystemSet> {225let mut default_sets = self.a.default_system_sets();226default_sets.append(&mut self.b.default_system_sets());227default_sets228}229230fn get_last_run(&self) -> Tick {231self.a.get_last_run()232}233234fn set_last_run(&mut self, last_run: Tick) {235self.a.set_last_run(last_run);236self.b.set_last_run(last_run);237}238}239240/// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world.241unsafe impl<Func, A, B> ReadOnlySystem for CombinatorSystem<Func, A, B>242where243Func: Combine<A, B> + 'static,244A: ReadOnlySystem,245B: ReadOnlySystem,246{247}248249impl<Func, A, B> Clone for CombinatorSystem<Func, A, B>250where251A: Clone,252B: Clone,253{254/// Clone the combined system. The cloned instance must be `.initialize()`d before it can run.255fn clone(&self) -> Self {256CombinatorSystem::new(self.a.clone(), self.b.clone(), self.name.clone())257}258}259260/// An [`IntoSystem`] creating an instance of [`PipeSystem`].261#[derive(Clone)]262pub struct IntoPipeSystem<A, B> {263a: A,264b: B,265}266267impl<A, B> IntoPipeSystem<A, B> {268/// Creates a new [`IntoSystem`] that pipes two inner systems.269pub const fn new(a: A, b: B) -> Self {270Self { a, b }271}272}273274#[doc(hidden)]275pub struct IsPipeSystemMarker;276277impl<A, B, IA, OA, IB, OB, MA, MB> IntoSystem<IA, OB, (IsPipeSystemMarker, OA, IB, MA, MB)>278for IntoPipeSystem<A, B>279where280IA: SystemInput,281A: IntoSystem<IA, OA, MA>,282B: IntoSystem<IB, OB, MB>,283for<'a> IB: SystemInput<Inner<'a> = OA>,284{285type System = PipeSystem<A::System, B::System>;286287fn into_system(this: Self) -> Self::System {288let system_a = IntoSystem::into_system(this.a);289let system_b = IntoSystem::into_system(this.b);290let name = format!("Pipe({}, {})", system_a.name(), system_b.name());291PipeSystem::new(system_a, system_b, DebugName::owned(name))292}293}294295/// A [`System`] created by piping the output of the first system into the input of the second.296///297/// This can be repeated indefinitely, but system pipes cannot branch: the output is consumed by the receiving system.298///299/// Given two systems `A` and `B`, A may be piped into `B` as `A.pipe(B)` if the output type of `A` is300/// equal to the input type of `B`.301///302/// Note that for [`FunctionSystem`](crate::system::FunctionSystem)s the output is the return value303/// of the function and the input is the first [`SystemParam`](crate::system::SystemParam) if it is304/// tagged with [`In`](crate::system::In) or `()` if the function has no designated input parameter.305///306/// # Examples307///308/// ```309/// use std::num::ParseIntError;310///311/// use bevy_ecs::prelude::*;312///313/// fn main() {314/// let mut world = World::default();315/// world.insert_resource(Message("42".to_string()));316///317/// // pipe the `parse_message_system`'s output into the `filter_system`s input318/// let mut piped_system = IntoSystem::into_system(parse_message_system.pipe(filter_system));319/// piped_system.initialize(&mut world);320/// assert_eq!(piped_system.run((), &mut world).unwrap(), Some(42));321/// }322///323/// #[derive(Resource)]324/// struct Message(String);325///326/// fn parse_message_system(message: Res<Message>) -> Result<usize, ParseIntError> {327/// message.0.parse::<usize>()328/// }329///330/// fn filter_system(In(result): In<Result<usize, ParseIntError>>) -> Option<usize> {331/// result.ok().filter(|&n| n < 100)332/// }333/// ```334pub struct PipeSystem<A, B> {335a: A,336b: B,337name: DebugName,338}339340impl<A, B> PipeSystem<A, B>341where342A: System,343B: System,344for<'a> B::In: SystemInput<Inner<'a> = A::Out>,345{346/// Creates a new system that pipes two inner systems.347pub fn new(a: A, b: B, name: DebugName) -> Self {348Self { a, b, name }349}350}351352impl<A, B> System for PipeSystem<A, B>353where354A: System,355B: System,356for<'a> B::In: SystemInput<Inner<'a> = A::Out>,357{358type In = A::In;359type Out = B::Out;360361fn name(&self) -> DebugName {362self.name.clone()363}364365#[inline]366fn flags(&self) -> super::SystemStateFlags {367self.a.flags() | self.b.flags()368}369370unsafe fn run_unsafe(371&mut self,372input: SystemIn<'_, Self>,373world: UnsafeWorldCell,374) -> Result<Self::Out, RunSystemError> {375let value = self.a.run_unsafe(input, world)?;376// `Self::validate_param_unsafe` already validated the first system,377// but we still need to validate the second system once the first one runs.378self.b.validate_param_unsafe(world)?;379self.b.run_unsafe(value, world)380}381382#[cfg(feature = "hotpatching")]383#[inline]384fn refresh_hotpatch(&mut self) {385self.a.refresh_hotpatch();386self.b.refresh_hotpatch();387}388389fn apply_deferred(&mut self, world: &mut World) {390self.a.apply_deferred(world);391self.b.apply_deferred(world);392}393394fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {395self.a.queue_deferred(world.reborrow());396self.b.queue_deferred(world);397}398399unsafe fn validate_param_unsafe(400&mut self,401world: UnsafeWorldCell,402) -> Result<(), SystemParamValidationError> {403// We only validate parameters for the first system,404// since it may make changes to the world that affect405// whether the second system has valid parameters.406// The second system will be validated in `Self::run_unsafe`.407// SAFETY: Delegate to the `System` implementation for `a`.408unsafe { self.a.validate_param_unsafe(world) }409}410411fn initialize(&mut self, world: &mut World) -> FilteredAccessSet {412let mut a_access = self.a.initialize(world);413let b_access = self.b.initialize(world);414a_access.extend(b_access);415a_access416}417418fn check_change_tick(&mut self, check: CheckChangeTicks) {419self.a.check_change_tick(check);420self.b.check_change_tick(check);421}422423fn default_system_sets(&self) -> Vec<InternedSystemSet> {424let mut default_sets = self.a.default_system_sets();425default_sets.append(&mut self.b.default_system_sets());426default_sets427}428429fn get_last_run(&self) -> Tick {430self.a.get_last_run()431}432433fn set_last_run(&mut self, last_run: Tick) {434self.a.set_last_run(last_run);435self.b.set_last_run(last_run);436}437}438439/// SAFETY: Both systems are read-only, so any system created by piping them will only read from the world.440unsafe impl<A, B> ReadOnlySystem for PipeSystem<A, B>441where442A: ReadOnlySystem,443B: ReadOnlySystem,444for<'a> B::In: SystemInput<Inner<'a> = A::Out>,445{446}447448#[cfg(test)]449mod tests {450451#[test]452fn exclusive_system_piping_is_possible() {453use crate::prelude::*;454455fn my_exclusive_system(_world: &mut World) -> u32 {4561457}458459fn out_pipe(input: In<u32>) {460assert!(input.0 == 1);461}462463let mut world = World::new();464465let mut schedule = Schedule::default();466schedule.add_systems(my_exclusive_system.pipe(out_pipe));467468schedule.run(&mut world);469}470}471472473