Path: blob/main/crates/bevy_ecs/src/system/combinator.rs
9421 views
use alloc::{format, vec::Vec};1use bevy_utils::prelude::DebugName;2use core::marker::PhantomData;34use crate::{5change_detection::{CheckChangeTicks, Tick},6error::ErrorContext,7prelude::World,8query::FilteredAccessSet,9schedule::InternedSystemSet,10system::{input::SystemInput, SystemIn, SystemParamValidationError},11world::unsafe_world_cell::UnsafeWorldCell,12};1314use super::{IntoSystem, ReadOnlySystem, RunSystemError, System};1516/// Customizes the behavior of a [`CombinatorSystem`].17///18/// # Examples19///20/// ```21/// use bevy_ecs::prelude::*;22/// use bevy_ecs::system::{CombinatorSystem, Combine, RunSystemError};23///24/// // A system combinator that performs an exclusive-or (XOR)25/// // operation on the output of two systems.26/// pub type Xor<A, B> = CombinatorSystem<XorMarker, A, B>;27///28/// // This struct is used to customize the behavior of our combinator.29/// pub struct XorMarker;30///31/// impl<A, B> Combine<A, B> for XorMarker32/// where33/// A: System<In = (), Out = bool>,34/// B: System<In = (), Out = bool>,35/// {36/// type In = ();37/// type Out = bool;38///39/// fn combine<T>(40/// _input: Self::In,41/// data: &mut T,42/// a: impl FnOnce(A::In, &mut T) -> Result<A::Out, RunSystemError>,43/// b: impl FnOnce(B::In, &mut T) -> Result<B::Out, RunSystemError>,44/// ) -> Result<Self::Out, RunSystemError> {45/// Ok(a((), data).unwrap_or(false) ^ b((), data).unwrap_or(false))46/// }47/// }48///49/// # #[derive(Resource, PartialEq, Eq)] struct A(u32);50/// # #[derive(Resource, PartialEq, Eq)] struct B(u32);51/// # #[derive(Resource, Default)] struct RanFlag(bool);52/// # let mut world = World::new();53/// # world.init_resource::<RanFlag>();54/// #55/// # let mut app = Schedule::default();56/// app.add_systems(my_system.run_if(Xor::new(57/// IntoSystem::into_system(resource_equals(A(1))),58/// IntoSystem::into_system(resource_equals(B(1))),59/// // The name of the combined system.60/// "a ^ b".into(),61/// )));62/// # fn my_system(mut flag: ResMut<RanFlag>) { flag.0 = true; }63/// #64/// # world.insert_resource(A(0));65/// # world.insert_resource(B(0));66/// # app.run(&mut world);67/// # // Neither condition passes, so the system does not run.68/// # assert!(!world.resource::<RanFlag>().0);69/// #70/// # world.insert_resource(A(1));71/// # app.run(&mut world);72/// # // Only the first condition passes, so the system runs.73/// # assert!(world.resource::<RanFlag>().0);74/// # world.resource_mut::<RanFlag>().0 = false;75/// #76/// # world.insert_resource(B(1));77/// # app.run(&mut world);78/// # // Both conditions pass, so the system does not run.79/// # assert!(!world.resource::<RanFlag>().0);80/// #81/// # world.insert_resource(A(0));82/// # app.run(&mut world);83/// # // Only the second condition passes, so the system runs.84/// # assert!(world.resource::<RanFlag>().0);85/// # world.resource_mut::<RanFlag>().0 = false;86/// ```87#[diagnostic::on_unimplemented(88message = "`{Self}` can not combine systems `{A}` and `{B}`",89label = "invalid system combination",90note = "the inputs and outputs of `{A}` and `{B}` are not compatible with this combiner"91)]92pub trait Combine<A: System, B: System> {93/// The [input](System::In) type for a [`CombinatorSystem`].94type In: SystemInput;9596/// The [output](System::Out) type for a [`CombinatorSystem`].97type Out;9899/// When used in a [`CombinatorSystem`], this function customizes how100/// the two composite systems are invoked and their outputs are combined.101///102/// See the trait-level docs for [`Combine`] for an example implementation.103fn combine<T>(104input: <Self::In as SystemInput>::Inner<'_>,105data: &mut T,106a: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<A::Out, RunSystemError>,107b: impl FnOnce(SystemIn<'_, B>, &mut T) -> Result<B::Out, RunSystemError>,108) -> Result<Self::Out, RunSystemError>;109}110111/// A [`System`] defined by combining two other systems.112/// The behavior of this combinator is specified by implementing the [`Combine`] trait.113/// For a full usage example, see the docs for [`Combine`].114pub struct CombinatorSystem<Func, A, B> {115_marker: PhantomData<fn() -> Func>,116a: A,117b: B,118name: DebugName,119}120121impl<Func, A, B> CombinatorSystem<Func, A, B> {122/// Creates a new system that combines two inner systems.123///124/// The returned system will only be usable if `Func` implements [`Combine<A, B>`].125pub fn new(a: A, b: B, name: DebugName) -> Self {126Self {127_marker: PhantomData,128a,129b,130name,131}132}133}134135impl<A, B, Func> System for CombinatorSystem<Func, A, B>136where137Func: Combine<A, B> + 'static,138A: System,139B: System,140{141type In = Func::In;142type Out = Func::Out;143144fn name(&self) -> DebugName {145self.name.clone()146}147148#[inline]149fn flags(&self) -> super::SystemStateFlags {150self.a.flags() | self.b.flags()151}152153unsafe fn run_unsafe(154&mut self,155input: SystemIn<'_, Self>,156world: UnsafeWorldCell,157) -> Result<Self::Out, RunSystemError> {158struct PrivateUnsafeWorldCell<'w>(UnsafeWorldCell<'w>);159160// Since control over handling system run errors is passed on to the161// implementation of `Func::combine`, which may run the two closures162// however it wants, errors must be intercepted here if they should be163// handled by the world's error handler.164unsafe fn run_system<S: System>(165system: &mut S,166input: SystemIn<S>,167world: &mut PrivateUnsafeWorldCell,168) -> Result<S::Out, RunSystemError> {169// SAFETY: see comment on `Func::combine` call170match (|| unsafe {171system.validate_param_unsafe(world.0)?;172system.run_unsafe(input, world.0)173})() {174// let the world's default error handler handle the error if `Failed(_)`175Err(RunSystemError::Failed(err)) => {176// SAFETY: We registered access to DefaultErrorHandler in `initialize`.177(unsafe { world.0.default_error_handler() })(178err,179ErrorContext::System {180name: system.name(),181last_run: system.get_last_run(),182},183);184185// Since the error handler takes the error by value, create a new error:186// The original error has already been handled, including187// the reason for the failure here isn't important.188Err(format!("System `{}` failed", system.name()).into())189}190// `Skipped(_)` and `Ok(_)` are passed through:191// system skipping is not an error, and isn't passed to the192// world's error handler by the executors.193result @ (Ok(_) | Err(RunSystemError::Skipped(_))) => result,194}195}196197Func::combine(198input,199&mut PrivateUnsafeWorldCell(world),200// SAFETY: The world accesses for both underlying systems have been registered,201// so the caller will guarantee that no other systems will conflict with (`a` or `b`) and the `DefaultErrorHandler` resource.202// If either system has `is_exclusive()`, then the combined system also has `is_exclusive`.203// Since we require a `combine` to pass in a mutable reference to `world` and that's a private type204// passed to a function as an unbound non-'static generic argument, they can never be called in parallel205// or re-entrantly because that would require forging another instance of `PrivateUnsafeWorldCell`.206// This means that the world accesses in the two closures will not conflict with each other.207// The closure's access to the DefaultErrorHandler does not208// conflict with any potential access to the DefaultErrorHandler by209// the systems since the closures are not run in parallel.210|input, world| unsafe { run_system(&mut self.a, input, world) },211// SAFETY: See the comment above.212|input, world| unsafe { run_system(&mut self.b, input, world) },213)214}215216#[cfg(feature = "hotpatching")]217#[inline]218fn refresh_hotpatch(&mut self) {219self.a.refresh_hotpatch();220self.b.refresh_hotpatch();221}222223#[inline]224fn apply_deferred(&mut self, world: &mut World) {225self.a.apply_deferred(world);226self.b.apply_deferred(world);227}228229#[inline]230fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {231self.a.queue_deferred(world.reborrow());232self.b.queue_deferred(world);233}234235#[inline]236unsafe fn validate_param_unsafe(237&mut self,238_world: UnsafeWorldCell,239) -> Result<(), SystemParamValidationError> {240// Both systems are validated in `Self::run_unsafe`, so that we get the241// chance to run the second system even if the first one fails to242// validate.243Ok(())244}245246fn initialize(&mut self, world: &mut World) -> FilteredAccessSet {247let mut a_access = self.a.initialize(world);248let b_access = self.b.initialize(world);249a_access.extend(b_access);250251// We might need to read the default error handler after the component252// systems have run to report failures.253let error_resource = world.register_resource::<crate::error::DefaultErrorHandler>();254a_access.add_unfiltered_resource_read(error_resource);255a_access256}257258fn check_change_tick(&mut self, check: CheckChangeTicks) {259self.a.check_change_tick(check);260self.b.check_change_tick(check);261}262263fn default_system_sets(&self) -> Vec<InternedSystemSet> {264let mut default_sets = self.a.default_system_sets();265default_sets.append(&mut self.b.default_system_sets());266default_sets267}268269fn get_last_run(&self) -> Tick {270self.a.get_last_run()271}272273fn set_last_run(&mut self, last_run: Tick) {274self.a.set_last_run(last_run);275self.b.set_last_run(last_run);276}277}278279// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world.280unsafe impl<Func, A, B> ReadOnlySystem for CombinatorSystem<Func, A, B>281where282Func: Combine<A, B> + 'static,283A: ReadOnlySystem,284B: ReadOnlySystem,285{286}287288impl<Func, A, B> Clone for CombinatorSystem<Func, A, B>289where290A: Clone,291B: Clone,292{293/// Clone the combined system. The cloned instance must be `.initialize()`d before it can run.294fn clone(&self) -> Self {295CombinatorSystem::new(self.a.clone(), self.b.clone(), self.name.clone())296}297}298299/// An [`IntoSystem`] creating an instance of [`PipeSystem`].300#[derive(Clone)]301pub struct IntoPipeSystem<A, B> {302a: A,303b: B,304}305306impl<A, B> IntoPipeSystem<A, B> {307/// Creates a new [`IntoSystem`] that pipes two inner systems.308pub const fn new(a: A, b: B) -> Self {309Self { a, b }310}311}312313#[doc(hidden)]314pub struct IsPipeSystemMarker;315316impl<A, B, IA, OA, IB, OB, MA, MB> IntoSystem<IA, OB, (IsPipeSystemMarker, OA, IB, MA, MB)>317for IntoPipeSystem<A, B>318where319IA: SystemInput,320A: IntoSystem<IA, OA, MA>,321B: IntoSystem<IB, OB, MB>,322for<'a> IB: SystemInput<Inner<'a> = OA>,323{324type System = PipeSystem<A::System, B::System>;325326fn into_system(this: Self) -> Self::System {327let system_a = IntoSystem::into_system(this.a);328let system_b = IntoSystem::into_system(this.b);329let name = format!("Pipe({}, {})", system_a.name(), system_b.name());330PipeSystem::new(system_a, system_b, DebugName::owned(name))331}332}333334/// A [`System`] created by piping the output of the first system into the input of the second.335///336/// This can be repeated indefinitely, but system pipes cannot branch: the output is consumed by the receiving system.337///338/// Given two systems `A` and `B`, A may be piped into `B` as `A.pipe(B)` if the output type of `A` is339/// equal to the input type of `B`.340///341/// Note that for [`FunctionSystem`](crate::system::FunctionSystem)s the output is the return value342/// of the function and the input is the first [`SystemParam`](crate::system::SystemParam) if it is343/// tagged with [`In`](crate::system::In) or `()` if the function has no designated input parameter.344///345/// # Examples346///347/// ```348/// use std::num::ParseIntError;349///350/// use bevy_ecs::prelude::*;351///352/// fn main() {353/// let mut world = World::default();354/// world.insert_resource(Message("42".to_string()));355///356/// // pipe the `parse_message_system`'s output into the `filter_system`s input357/// let mut piped_system = IntoSystem::into_system(parse_message_system.pipe(filter_system));358/// piped_system.initialize(&mut world);359/// assert_eq!(piped_system.run((), &mut world).unwrap(), Some(42));360/// }361///362/// #[derive(Resource)]363/// struct Message(String);364///365/// fn parse_message_system(message: Res<Message>) -> Result<usize, ParseIntError> {366/// message.0.parse::<usize>()367/// }368///369/// fn filter_system(In(result): In<Result<usize, ParseIntError>>) -> Option<usize> {370/// result.ok().filter(|&n| n < 100)371/// }372/// ```373pub struct PipeSystem<A, B> {374a: A,375b: B,376name: DebugName,377}378379impl<A, B> PipeSystem<A, B>380where381A: System,382B: System,383for<'a> B::In: SystemInput<Inner<'a> = A::Out>,384{385/// Creates a new system that pipes two inner systems.386pub fn new(a: A, b: B, name: DebugName) -> Self {387Self { a, b, name }388}389}390391impl<A, B> System for PipeSystem<A, B>392where393A: System,394B: System,395for<'a> B::In: SystemInput<Inner<'a> = A::Out>,396{397type In = A::In;398type Out = B::Out;399400fn name(&self) -> DebugName {401self.name.clone()402}403404#[inline]405fn flags(&self) -> super::SystemStateFlags {406self.a.flags() | self.b.flags()407}408409unsafe fn run_unsafe(410&mut self,411input: SystemIn<'_, Self>,412world: UnsafeWorldCell,413) -> Result<Self::Out, RunSystemError> {414// SAFETY: Upheld by caller415unsafe {416let value = self.a.run_unsafe(input, world)?;417// `Self::validate_param_unsafe` already validated the first system,418// but we still need to validate the second system once the first one runs.419self.b.validate_param_unsafe(world)?;420self.b.run_unsafe(value, world)421}422}423424#[cfg(feature = "hotpatching")]425#[inline]426fn refresh_hotpatch(&mut self) {427self.a.refresh_hotpatch();428self.b.refresh_hotpatch();429}430431fn apply_deferred(&mut self, world: &mut World) {432self.a.apply_deferred(world);433self.b.apply_deferred(world);434}435436fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {437self.a.queue_deferred(world.reborrow());438self.b.queue_deferred(world);439}440441unsafe fn validate_param_unsafe(442&mut self,443world: UnsafeWorldCell,444) -> Result<(), SystemParamValidationError> {445// We only validate parameters for the first system,446// since it may make changes to the world that affect447// whether the second system has valid parameters.448// The second system will be validated in `Self::run_unsafe`.449// SAFETY: Delegate to the `System` implementation for `a`.450unsafe { self.a.validate_param_unsafe(world) }451}452453fn initialize(&mut self, world: &mut World) -> FilteredAccessSet {454let mut a_access = self.a.initialize(world);455let b_access = self.b.initialize(world);456a_access.extend(b_access);457a_access458}459460fn check_change_tick(&mut self, check: CheckChangeTicks) {461self.a.check_change_tick(check);462self.b.check_change_tick(check);463}464465fn default_system_sets(&self) -> Vec<InternedSystemSet> {466let mut default_sets = self.a.default_system_sets();467default_sets.append(&mut self.b.default_system_sets());468default_sets469}470471fn get_last_run(&self) -> Tick {472self.a.get_last_run()473}474475fn set_last_run(&mut self, last_run: Tick) {476self.a.set_last_run(last_run);477self.b.set_last_run(last_run);478}479}480481// SAFETY: Both systems are read-only, so any system created by piping them will only read from the world.482unsafe impl<A, B> ReadOnlySystem for PipeSystem<A, B>483where484A: ReadOnlySystem,485B: ReadOnlySystem,486for<'a> B::In: SystemInput<Inner<'a> = A::Out>,487{488}489490#[cfg(test)]491mod tests {492use crate::error::DefaultErrorHandler;493use crate::prelude::*;494use bevy_utils::prelude::DebugName;495496use crate::{497schedule::OrMarker,498system::{assert_system_does_not_conflict, CombinatorSystem},499};500501#[test]502fn combinator_with_error_handler_access() {503fn my_system(_: ResMut<DefaultErrorHandler>) {}504fn a() -> bool {505true506}507fn b(_: ResMut<DefaultErrorHandler>) -> bool {508true509}510fn asdf(_: In<bool>) {}511512let mut world = World::new();513world.insert_resource(DefaultErrorHandler::default());514515let system = CombinatorSystem::<OrMarker, _, _>::new(516IntoSystem::into_system(a),517IntoSystem::into_system(b),518DebugName::borrowed("a OR b"),519);520521// `system` should not conflict with itself by mutably accessing the error handler resource.522assert_system_does_not_conflict(system.clone());523524let mut schedule = Schedule::default();525schedule.add_systems((my_system, system.pipe(asdf)));526schedule.initialize(&mut world).unwrap();527528// `my_system` should conflict with the combinator system because the combinator reads the error handler resource.529assert!(!schedule.graph().conflicting_systems().is_empty());530531schedule.run(&mut world);532}533534#[test]535fn exclusive_system_piping_is_possible() {536fn my_exclusive_system(_world: &mut World) -> u32 {5371538}539540fn out_pipe(input: In<u32>) {541assert!(input.0 == 1);542}543544let mut world = World::new();545546let mut schedule = Schedule::default();547schedule.add_systems(my_exclusive_system.pipe(out_pipe));548549schedule.run(&mut world);550}551}552553554