Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions crates/bevy_ecs/src/system/function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,8 @@ where
#[doc(hidden)]
pub struct IsFunctionSystem;

impl<Marker, Out, F> IntoSystem<F::In, Out, (IsFunctionSystem, Marker)> for F
impl<Marker, Out, F> IntoSystem<<F::In as SystemInput>::Underlying, Out, (IsFunctionSystem, Marker)>
for F
where
Out: 'static,
Marker: 'static,
Expand Down Expand Up @@ -613,7 +614,7 @@ where
Out: 'static,
F: SystemParamFunction<Marker, Out: IntoResult<Out>>,
{
type In = F::In;
type In = <F::In as SystemInput>::Underlying;
type Out = Out;

#[inline]
Expand Down Expand Up @@ -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)]
Expand Down
48 changes: 47 additions & 1 deletion crates/bevy_ecs/src/system/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Inner<'i> = Self::Inner<'i>>;

/// Converts a [`SystemInput::Inner`] into a [`SystemInput::Param`].
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_>;
Expand Down Expand Up @@ -91,6 +96,7 @@ pub struct In<T>(pub T);
impl<T: 'static> SystemInput for In<T> {
type Param<'i> = In<T>;
type Inner<'i> = T;
type Underlying = In<T>;

fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> {
In(this)
Expand Down Expand Up @@ -150,6 +156,7 @@ pub struct InRef<'i, T: ?Sized>(pub &'i T);
impl<T: ?Sized + 'static> 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)
Expand Down Expand Up @@ -199,6 +206,7 @@ pub struct InMut<'a, T: ?Sized>(pub &'a mut T);
impl<T: ?Sized + 'static> 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)
Expand Down Expand Up @@ -229,6 +237,7 @@ impl<E: Event, B: Bundle> 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
Expand All @@ -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)
Expand All @@ -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,
Expand Down Expand Up @@ -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,
};

Expand Down Expand Up @@ -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<I: SystemInput>(_: StaticSystemInput<I>) {}

fn takes_system<M>(system: impl IntoSystem<In<usize>, (), 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::<In<usize>>);

// The `StaticSystemInput` wrapper is removed even within tuples
fn generic_tuple_system<I1: SystemInput, I2: SystemInput>(
_: (In<usize>, StaticSystemInput<I1>, StaticSystemInput<I2>),
) {
}

fn takes_tuple_system<M>(
system: impl IntoSystem<(In<usize>, In<u32>, 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::<In<u32>, InRef<str>>);
}
}
11 changes: 11 additions & 0 deletions release-content/migration-guides/systeminput_underlying.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
title: "`SystemInput::Underlying` type"
pull_requests: []
---

Higher-order systems using a `StaticSystemInput<I>` 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`.