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: 5 additions & 6 deletions crates/bevy_ecs/src/schedule/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};

Expand Down Expand Up @@ -291,14 +291,13 @@ impl<S: SystemSet> IntoSystemSet<()> for S {
impl<Marker, F> IntoSystemSet<(IsFunctionSystem, Marker)> for F
where
Marker: 'static,
F::Out: IntoResult<()>,
F: SystemParamFunction<Marker>,
F: SystemParamFunction<Marker, In: FromInput<()>, Out: IntoResult<()>>,
{
type Set = SystemTypeSet<FunctionSystem<Marker, (), F>>;
type Set = SystemTypeSet<FunctionSystem<Marker, (), (), F>>;

#[inline]
fn into_system_set(self) -> Self::Set {
SystemTypeSet::<FunctionSystem<Marker, (), F>>::new()
SystemTypeSet::<FunctionSystem<Marker, (), (), F>>::new()
}
}

Expand Down
112 changes: 65 additions & 47 deletions crates/bevy_ecs/src/system/function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -232,35 +232,39 @@ 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>,
Out: 'static,
Out,
Marker,
F: FnMut($(SystemParamItem<$param>),*) -> InnerOut
+ SystemParamFunction<Marker, Param = ($($param,)*), In = (), Out = InnerOut>
+ SystemParamFunction<Marker, In = (), Out = InnerOut, Param = ($($param,)*)>
>
(
self,
func: F,
) -> FunctionSystem<Marker, Out, F>
) -> FunctionSystem<Marker, (), Out, F>
{
self.build_any_system(func)
}

/// 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>,
In: SystemInput,
InnerOut: IntoResult<Out>,
Out: 'static,
Out,
Marker,
F: FnMut(Input, $(SystemParamItem<$param>),*) -> InnerOut
+ SystemParamFunction<Marker, Param = ($($param,)*), In = Input, Out = InnerOut>,
>(
F: FnMut(InnerIn, $(SystemParamItem<$param>),*) -> InnerOut
+ SystemParamFunction<Marker, In = InnerIn, Out = InnerOut, Param = ($($param,)*)>
>
(
self,
func: F,
) -> FunctionSystem<Marker, Out, F> {
) -> FunctionSystem<Marker, In, Out, F> {
self.build_any_system(func)
}
}
Expand Down Expand Up @@ -311,22 +315,20 @@ impl<Param: SystemParam> SystemState<Param> {
/// 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<Marker, Out, F>(self, func: F) -> FunctionSystem<Marker, Out, F>
#[inline]
pub fn build_any_system<Marker, In, Out, F>(self, func: F) -> FunctionSystem<Marker, In, Out, F>
where
F: SystemParamFunction<Marker, Param = Param, Out: IntoResult<Out>>,
In: SystemInput,
F: SystemParamFunction<Marker, In: FromInput<In>, Out: IntoResult<Out>, Param = Param>,
{
FunctionSystem {
FunctionSystem::new(
func,
#[cfg(feature = "hotpatching")]
current_ptr: subsecond::HotFn::current(<F as SystemParamFunction<Marker>>::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.
Expand Down Expand Up @@ -475,7 +477,7 @@ impl<Param: SystemParam> FromWorld for SystemState<Param> {
///
/// 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<Marker, Out, F>
pub struct FunctionSystem<Marker, In, Out, F>
where
F: SystemParamFunction<Marker>,
{
Expand All @@ -485,7 +487,7 @@ where
state: Option<FunctionSystemState<F::Param>>,
system_meta: SystemMeta,
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
marker: PhantomData<fn() -> (Marker, Out)>,
marker: PhantomData<fn(In) -> (Marker, Out)>,
}

/// The state of a [`FunctionSystem`], which must be initialized with
Expand All @@ -500,10 +502,23 @@ struct FunctionSystemState<P: SystemParam> {
world_id: WorldId,
}

impl<Marker, Out, F> FunctionSystem<Marker, Out, F>
impl<Marker, In, Out, F> FunctionSystem<Marker, In, Out, F>
where
F: SystemParamFunction<Marker>,
{
#[inline]
fn new(func: F, system_meta: SystemMeta, state: Option<FunctionSystemState<F::Param>>) -> Self {
Self {
func,
#[cfg(feature = "hotpatching")]
current_ptr: subsecond::HotFn::current(<F as SystemParamFunction<Marker>>::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.
Expand All @@ -514,7 +529,7 @@ where
}

// De-initializes the cloned system.
impl<Marker, Out, F> Clone for FunctionSystem<Marker, Out, F>
impl<Marker, In, Out, F> Clone for FunctionSystem<Marker, In, Out, F>
where
F: SystemParamFunction<Marker> + Clone,
{
Expand All @@ -535,23 +550,16 @@ where
#[doc(hidden)]
pub struct IsFunctionSystem;

impl<Marker, Out, F> IntoSystem<F::In, Out, (IsFunctionSystem, Marker)> for F
impl<Marker, In, Out, F> IntoSystem<In, Out, (IsFunctionSystem, Marker)> for F
where
Out: 'static,
Marker: 'static,
F: SystemParamFunction<Marker, Out: IntoResult<Out>>,
In: SystemInput + 'static,
Out: 'static,
F: SystemParamFunction<Marker, In: FromInput<In>, Out: IntoResult<Out>>,
{
type System = FunctionSystem<Marker, Out, F>;
type System = FunctionSystem<Marker, In, Out, F>;
fn into_system(func: Self) -> Self::System {
FunctionSystem {
func,
#[cfg(feature = "hotpatching")]
current_ptr: subsecond::HotFn::current(<F as SystemParamFunction<Marker>>::run)
.ptr_address(),
state: None,
system_meta: SystemMeta::new::<F>(),
marker: PhantomData,
}
FunctionSystem::new(func, SystemMeta::new::<F>(), None)
}
}

Expand Down Expand Up @@ -596,7 +604,7 @@ impl IntoResult<bool> for Never {
}
}

impl<Marker, Out, F> FunctionSystem<Marker, Out, F>
impl<Marker, In, Out, F> FunctionSystem<Marker, In, Out, F>
where
F: SystemParamFunction<Marker>,
{
Expand All @@ -607,13 +615,14 @@ where
"System's state was not found. Did you forget to initialize this system before running it?";
}

impl<Marker, Out, F> System for FunctionSystem<Marker, Out, F>
impl<Marker, In, Out, F> System for FunctionSystem<Marker, In, Out, F>
where
Marker: 'static,
In: SystemInput + 'static,
Out: 'static,
F: SystemParamFunction<Marker, Out: IntoResult<Out>>,
F: SystemParamFunction<Marker, In: FromInput<In>, Out: IntoResult<Out>>,
{
type In = F::In;
type In = In;
type Out = Out;

#[inline]
Expand All @@ -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:
Expand Down Expand Up @@ -748,12 +759,17 @@ where
}

/// SAFETY: `F`'s param is [`ReadOnlySystemParam`], so this system will only read from the world.
unsafe impl<Marker, Out, F> ReadOnlySystem for FunctionSystem<Marker, Out, F>
unsafe impl<Marker, In, Out, F> ReadOnlySystem for FunctionSystem<Marker, In, Out, F>
where
Marker: 'static,
In: SystemInput + 'static,
Out: 'static,
F: SystemParamFunction<Marker, Out: IntoResult<Out>>,
F::Param: ReadOnlySystemParam,
F: SystemParamFunction<
Marker,
In: FromInput<In>,
Out: IntoResult<Out>,
Param: ReadOnlySystemParam,
>,
{
}

Expand Down Expand Up @@ -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<usize>, _>::into_system(pipe(parse_message, filter));
/// piped_system.initialize(&mut world);
/// assert_eq!(piped_system.run((), &mut world).unwrap(), Some(42));
/// }
Expand Down
41 changes: 40 additions & 1 deletion crates/bevy_ecs/src/system/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> = <<S as System>::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<In>`
/// to take an `In` as input, and can be implemented for user types to allow
/// similar conversions.
pub trait FromInput<In: SystemInput>: 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<In: SystemInput> FromInput<In> for In {
#[inline]
fn from_inner<'i>(inner: In::Inner<'i>) -> Self::Inner<'i> {
inner
}
}

impl<'a, In: SystemInput> FromInput<In> 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.
///
Expand Down Expand Up @@ -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,
};

Expand Down Expand Up @@ -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>) -> usize {
a
}

fn takes_static_usize(StaticSystemInput(b): StaticSystemInput<In<usize>>) -> usize {
b
}

assert_is_system::<In<usize>, usize, _>(takes_usize);
// test if StaticSystemInput is compatible with its inner type
assert_is_system::<In<usize>, usize, _>(takes_static_usize);
}
}
21 changes: 21 additions & 0 deletions release-content/migration-guides/function_system_generics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: "FunctionSystem Generics"
authors: ["@ecoskey"]
pull_requests: [21917]
---

`FunctionSystem` now has a new generic parameter: `In`.

Old: `FunctionSystem<Marker, Out, F>`
New: `FunctionSystem<Marker, In, Out, F>`

Additionally, there's an extra bound on the `System` and `IntoSystem` impls
related to `FunctionSystem`:

`<F as SystemParamFunction>::In: FromInput<In>`

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.