diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 5f6653be39834..2ed2615551431 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -535,7 +535,8 @@ where #[doc(hidden)] pub struct IsFunctionSystem; -impl IntoSystem for F +impl IntoSystem<::Underlying, Out, (IsFunctionSystem, Marker)> + for F where Out: 'static, Marker: 'static, @@ -613,7 +614,7 @@ where Out: 'static, F: SystemParamFunction>, { - type In = F::In; + type In = ::Underlying; type Out = Out; #[inline] @@ -800,9 +801,9 @@ where /// world.insert_resource(Message("42".to_string())); /// /// // pipe the `parse_message_system`'s output into the `filter_system`s input -/// let mut piped_system = IntoSystem::into_system(pipe(parse_message, filter)); -/// piped_system.initialize(&mut world); -/// assert_eq!(piped_system.run((), &mut world).unwrap(), Some(42)); +/// let mut piped_system = pipe(parse_message, filter); +/// let result = world.run_system_cached(piped_system).unwrap(); +/// assert_eq!(result, Some(42)); /// } /// /// #[derive(Resource)] diff --git a/crates/bevy_ecs/src/system/input.rs b/crates/bevy_ecs/src/system/input.rs index 429f4df018ed6..7f929fdccdd72 100644 --- a/crates/bevy_ecs/src/system/input.rs +++ b/crates/bevy_ecs/src/system/input.rs @@ -47,6 +47,11 @@ pub trait SystemInput: Sized { /// /// [`System::run`]: crate::system::System::run type Inner<'i>; + /// The input type after any [`StaticSystemInput`] wrappers are removed. + /// This is normally the same as `Self`, + /// but allows a function taking a `StaticSystemInput` parameter to be used + /// as a system taking the underlying input parameter. + type Underlying: 'static + for<'i> SystemInput = Self::Inner<'i>>; /// Converts a [`SystemInput::Inner`] into a [`SystemInput::Param`]. fn wrap(this: Self::Inner<'_>) -> Self::Param<'_>; @@ -91,6 +96,7 @@ pub struct In(pub T); impl SystemInput for In { type Param<'i> = In; type Inner<'i> = T; + type Underlying = In; fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { In(this) @@ -150,6 +156,7 @@ pub struct InRef<'i, T: ?Sized>(pub &'i T); impl SystemInput for InRef<'_, T> { type Param<'i> = InRef<'i, T>; type Inner<'i> = &'i T; + type Underlying = InRef<'static, T>; fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { InRef(this) @@ -199,6 +206,7 @@ pub struct InMut<'a, T: ?Sized>(pub &'a mut T); impl SystemInput for InMut<'_, T> { type Param<'i> = InMut<'i, T>; type Inner<'i> = &'i mut T; + type Underlying = InMut<'static, T>; fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { InMut(this) @@ -229,6 +237,7 @@ impl SystemInput for On<'_, '_, E, B> { // the `On` implementation. type Param<'i> = On<'i, 'i, E, B>; type Inner<'i> = On<'i, 'i, E, B>; + type Underlying = On<'static, 'static, E, B>; fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { this @@ -250,6 +259,7 @@ pub struct StaticSystemInput<'a, I: SystemInput>(pub I::Inner<'a>); impl<'a, I: SystemInput> SystemInput for StaticSystemInput<'a, I> { type Param<'i> = StaticSystemInput<'i, I>; type Inner<'i> = I::Inner<'i>; + type Underlying = I::Underlying; fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { StaticSystemInput(this) @@ -262,6 +272,7 @@ macro_rules! impl_system_input_tuple { impl<$($name: SystemInput),*> SystemInput for ($($name,)*) { type Param<'i> = ($($name::Param<'i>,)*); type Inner<'i> = ($($name::Inner<'i>,)*); + type Underlying = ($($name::Underlying,)*); #[expect( clippy::allow_attributes, @@ -294,7 +305,8 @@ all_tuples!( #[cfg(test)] mod tests { use crate::{ - system::{In, InMut, InRef, IntoSystem, System}, + prelude::Schedule, + system::{In, InMut, InRef, IntoSystem, StaticSystemInput, System, SystemInput}, world::World, }; @@ -327,4 +339,38 @@ mod tests { by_mut.run((&mut a, b), &mut world).unwrap(); assert_eq!(a, 36); } + + #[test] + fn static_system_input_usable_as_underlying_input() { + fn generic_system(_: StaticSystemInput) {} + + fn takes_system(system: impl IntoSystem, (), M> + 'static) { + let mut world = World::new(); + world.run_system_cached_with(system, 0).unwrap(); + } + + // `run_system_cached` and `add_systems` require `In = ()`, not `In = StaticSystemInput<()>` + let mut world = World::new(); + world.run_system_cached(generic_system::<()>).unwrap(); + + let mut schedule = Schedule::default(); + schedule.add_systems(generic_system::<()>); + + takes_system(generic_system::>); + + // The `StaticSystemInput` wrapper is removed even within tuples + fn generic_tuple_system( + _: (In, StaticSystemInput, StaticSystemInput), + ) { + } + + fn takes_tuple_system( + system: impl IntoSystem<(In, In, InRef<'static, str>), (), M> + 'static, + ) { + let mut world = World::new(); + world.run_system_cached_with(system, (0, 0, "")).unwrap(); + } + + takes_tuple_system(generic_tuple_system::, InRef>); + } } diff --git a/release-content/migration-guides/systeminput_underlying.md b/release-content/migration-guides/systeminput_underlying.md new file mode 100644 index 0000000000000..2ecfa253a0256 --- /dev/null +++ b/release-content/migration-guides/systeminput_underlying.md @@ -0,0 +1,11 @@ +--- +title: "`SystemInput::Underlying` type" +pull_requests: [] +--- + +Higher-order systems using a `StaticSystemInput` parameter +were not usable as a system taking the inner `I` as input. +To fix this, a new associated type `Underlying` has been added to the `SystemInput` trait. + +Manual implementations of `SystemInput` should add the new type, +which should normally be equal to `Self` with all lifetimes replaced by `'static`.