diff --git a/crates/bevy_ecs/src/schedule/set.rs b/crates/bevy_ecs/src/schedule/set.rs index 7c2d718ed6be8..53c6e1565600e 100644 --- a/crates/bevy_ecs/src/schedule/set.rs +++ b/crates/bevy_ecs/src/schedule/set.rs @@ -14,8 +14,8 @@ use crate::{ define_label, intern::Interned, system::{ - ExclusiveFunctionSystem, ExclusiveSystemParamFunction, FunctionSystem, IntoResult, - IsExclusiveFunctionSystem, IsFunctionSystem, SystemParamFunction, + ExclusiveFunctionSystem, ExclusiveSystemParamFunction, FromInput, FunctionSystem, + IntoResult, IsExclusiveFunctionSystem, IsFunctionSystem, SystemParamFunction, }, }; @@ -291,14 +291,13 @@ impl IntoSystemSet<()> for S { impl IntoSystemSet<(IsFunctionSystem, Marker)> for F where Marker: 'static, - F::Out: IntoResult<()>, - F: SystemParamFunction, + F: SystemParamFunction, Out: IntoResult<()>>, { - type Set = SystemTypeSet>; + type Set = SystemTypeSet>; #[inline] fn into_system_set(self) -> Self::Set { - SystemTypeSet::>::new() + SystemTypeSet::>::new() } } diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 5f6653be39834..2543051924b6d 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -6,8 +6,8 @@ use crate::{ query::FilteredAccessSet, schedule::{InternedSystemSet, SystemSet}, system::{ - check_system_change_tick, ReadOnlySystemParam, System, SystemIn, SystemInput, SystemParam, - SystemParamItem, + check_system_change_tick, FromInput, ReadOnlySystemParam, System, SystemIn, SystemInput, + SystemParam, SystemParamItem, }, world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World, WorldId}, }; @@ -232,17 +232,18 @@ macro_rules! impl_build_system { /// Create a [`FunctionSystem`] from a [`SystemState`]. /// This method signature allows type inference of closure parameters for a system with no input. /// You can use [`SystemState::build_system_with_input()`] if you have input, or [`SystemState::build_any_system()`] if you don't need type inference. + #[inline] pub fn build_system< InnerOut: IntoResult, - Out: 'static, + Out, Marker, F: FnMut($(SystemParamItem<$param>),*) -> InnerOut - + SystemParamFunction + + SystemParamFunction > ( self, func: F, - ) -> FunctionSystem + ) -> FunctionSystem { self.build_any_system(func) } @@ -250,17 +251,20 @@ macro_rules! impl_build_system { /// Create a [`FunctionSystem`] from a [`SystemState`]. /// This method signature allows type inference of closure parameters for a system with input. /// You can use [`SystemState::build_system()`] if you have no input, or [`SystemState::build_any_system()`] if you don't need type inference. + #[inline] pub fn build_system_with_input< - Input: SystemInput, + InnerIn: SystemInput + FromInput, + In: SystemInput, InnerOut: IntoResult, - Out: 'static, + Out, Marker, - F: FnMut(Input, $(SystemParamItem<$param>),*) -> InnerOut - + SystemParamFunction, - >( + F: FnMut(InnerIn, $(SystemParamItem<$param>),*) -> InnerOut + + SystemParamFunction + > + ( self, func: F, - ) -> FunctionSystem { + ) -> FunctionSystem { self.build_any_system(func) } } @@ -311,22 +315,20 @@ impl SystemState { /// Create a [`FunctionSystem`] from a [`SystemState`]. /// This method signature allows any system function, but the compiler will not perform type inference on closure parameters. /// You can use [`SystemState::build_system()`] or [`SystemState::build_system_with_input()`] to get type inference on parameters. - pub fn build_any_system(self, func: F) -> FunctionSystem + #[inline] + pub fn build_any_system(self, func: F) -> FunctionSystem where - F: SystemParamFunction>, + In: SystemInput, + F: SystemParamFunction, Out: IntoResult, Param = Param>, { - FunctionSystem { + FunctionSystem::new( func, - #[cfg(feature = "hotpatching")] - current_ptr: subsecond::HotFn::current(>::run) - .ptr_address(), - state: Some(FunctionSystemState { + self.meta, + Some(FunctionSystemState { param: self.param_state, world_id: self.world_id, }), - system_meta: self.meta, - marker: PhantomData, - } + ) } /// Gets the metadata for this instance. @@ -475,7 +477,7 @@ impl FromWorld for SystemState { /// /// The [`Clone`] implementation for [`FunctionSystem`] returns a new instance which /// is NOT initialized. The cloned system must also be `.initialized` before it can be run. -pub struct FunctionSystem +pub struct FunctionSystem where F: SystemParamFunction, { @@ -485,7 +487,7 @@ where state: Option>, system_meta: SystemMeta, // NOTE: PhantomData T> gives this safe Send/Sync impls - marker: PhantomData (Marker, Out)>, + marker: PhantomData (Marker, Out)>, } /// The state of a [`FunctionSystem`], which must be initialized with @@ -500,10 +502,23 @@ struct FunctionSystemState { world_id: WorldId, } -impl FunctionSystem +impl FunctionSystem where F: SystemParamFunction, { + #[inline] + fn new(func: F, system_meta: SystemMeta, state: Option>) -> Self { + Self { + func, + #[cfg(feature = "hotpatching")] + current_ptr: subsecond::HotFn::current(>::run) + .ptr_address(), + state, + system_meta, + marker: PhantomData, + } + } + /// Return this system with a new name. /// /// Useful to give closure systems more readable and unique names for debugging and tracing. @@ -514,7 +529,7 @@ where } // De-initializes the cloned system. -impl Clone for FunctionSystem +impl Clone for FunctionSystem where F: SystemParamFunction + Clone, { @@ -535,23 +550,16 @@ where #[doc(hidden)] pub struct IsFunctionSystem; -impl IntoSystem for F +impl IntoSystem for F where - Out: 'static, Marker: 'static, - F: SystemParamFunction>, + In: SystemInput + 'static, + Out: 'static, + F: SystemParamFunction, Out: IntoResult>, { - type System = FunctionSystem; + type System = FunctionSystem; fn into_system(func: Self) -> Self::System { - FunctionSystem { - func, - #[cfg(feature = "hotpatching")] - current_ptr: subsecond::HotFn::current(>::run) - .ptr_address(), - state: None, - system_meta: SystemMeta::new::(), - marker: PhantomData, - } + FunctionSystem::new(func, SystemMeta::new::(), None) } } @@ -596,7 +604,7 @@ impl IntoResult for Never { } } -impl FunctionSystem +impl FunctionSystem where F: SystemParamFunction, { @@ -607,13 +615,14 @@ where "System's state was not found. Did you forget to initialize this system before running it?"; } -impl System for FunctionSystem +impl System for FunctionSystem where Marker: 'static, + In: SystemInput + 'static, Out: 'static, - F: SystemParamFunction>, + F: SystemParamFunction, Out: IntoResult>, { - type In = F::In; + type In = In; type Out = Out; #[inline] @@ -637,6 +646,8 @@ where let change_tick = world.increment_change_tick(); + let input = F::In::from_inner(input); + let state = self.state.as_mut().expect(Self::ERROR_UNINITIALIZED); assert_eq!(state.world_id, world.id(), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with."); // SAFETY: @@ -748,12 +759,17 @@ where } /// SAFETY: `F`'s param is [`ReadOnlySystemParam`], so this system will only read from the world. -unsafe impl ReadOnlySystem for FunctionSystem +unsafe impl ReadOnlySystem for FunctionSystem where Marker: 'static, + In: SystemInput + 'static, Out: 'static, - F: SystemParamFunction>, - F::Param: ReadOnlySystemParam, + F: SystemParamFunction< + Marker, + In: FromInput, + Out: IntoResult, + Param: ReadOnlySystemParam, + >, { } @@ -799,8 +815,10 @@ where /// let mut world = World::default(); /// 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)); +/// // pipe the `parse_message_system`'s output into the `filter_system`s input. +/// // Type annotations should only needed when using `StaticSystemInput` as input +/// // AND the input type isn't constrained by nearby code. +/// let mut piped_system = IntoSystem::<(), Option, _>::into_system(pipe(parse_message, filter)); /// piped_system.initialize(&mut world); /// assert_eq!(piped_system.run((), &mut world).unwrap(), Some(42)); /// } diff --git a/crates/bevy_ecs/src/system/input.rs b/crates/bevy_ecs/src/system/input.rs index 429f4df018ed6..c37a08cd65be1 100644 --- a/crates/bevy_ecs/src/system/input.rs +++ b/crates/bevy_ecs/src/system/input.rs @@ -55,6 +55,30 @@ pub trait SystemInput: Sized { /// Shorthand way to get the [`System::In`] for a [`System`] as a [`SystemInput::Inner`]. pub type SystemIn<'a, S> = <::In as SystemInput>::Inner<'a>; +/// A type that may be constructed from the input of a [`System`]. +/// This is used to allow systems whose first parameter is a `StaticSystemInput` +/// to take an `In` as input, and can be implemented for user types to allow +/// similar conversions. +pub trait FromInput: SystemInput { + /// Converts the system input's inner representation into this type's + /// inner representation. + fn from_inner<'i>(inner: In::Inner<'i>) -> Self::Inner<'i>; +} + +impl FromInput for In { + #[inline] + fn from_inner<'i>(inner: In::Inner<'i>) -> Self::Inner<'i> { + inner + } +} + +impl<'a, In: SystemInput> FromInput for StaticSystemInput<'a, In> { + #[inline] + fn from_inner<'i>(inner: In::Inner<'i>) -> Self::Inner<'i> { + inner + } +} + /// A [`SystemInput`] type which denotes that a [`System`] receives /// an input value of type `T` from its caller. /// @@ -294,7 +318,7 @@ all_tuples!( #[cfg(test)] mod tests { use crate::{ - system::{In, InMut, InRef, IntoSystem, System}, + system::{assert_is_system, In, InMut, InRef, IntoSystem, StaticSystemInput, System}, world::World, }; @@ -327,4 +351,19 @@ mod tests { by_mut.run((&mut a, b), &mut world).unwrap(); assert_eq!(a, 36); } + + #[test] + fn compatible_input() { + fn takes_usize(In(a): In) -> usize { + a + } + + fn takes_static_usize(StaticSystemInput(b): StaticSystemInput>) -> usize { + b + } + + assert_is_system::, usize, _>(takes_usize); + // test if StaticSystemInput is compatible with its inner type + assert_is_system::, usize, _>(takes_static_usize); + } } diff --git a/release-content/migration-guides/function_system_generics.md b/release-content/migration-guides/function_system_generics.md new file mode 100644 index 0000000000000..a302dd72cf329 --- /dev/null +++ b/release-content/migration-guides/function_system_generics.md @@ -0,0 +1,21 @@ +--- +title: "FunctionSystem Generics" +authors: ["@ecoskey"] +pull_requests: [21917] +--- + +`FunctionSystem` now has a new generic parameter: `In`. + +Old: `FunctionSystem` +New: `FunctionSystem` + +Additionally, there's an extra bound on the `System` and `IntoSystem` impls +related to `FunctionSystem`: + +`::In: FromInput` + +This enabled systems to take as input any *compatible* type, in addition to the +exact one specified by the system function. This shouldn't impact users at all +since it only adds functionality, but users writing heavily generic code may +want to add a similar bound. See `function_system.rs` to see how it works in +practice.