Skip to content

Commit

Permalink
Support piping exclusive systems (bevyengine#7023)
Browse files Browse the repository at this point in the history
# Objective

Fix bevyengine#5248.

## Solution

Support `In<T>` parameters and allow returning arbitrary types in exclusive systems.

---

## Changelog

- Exclusive systems may now be used with system piping.

## Migration Guide

Exclusive systems (systems that access `&mut World`) now support system piping, so the `ExclusiveSystemParamFunction` trait now has generics for the `In`put and `Out`put types.

```rust
// Before
fn my_generic_system<T, Param>(system_function: T)
where T: ExclusiveSystemParamFunction<Param>
{ ... }

// After
fn my_generic_system<T, In, Out, Param>(system_function: T)
where T: ExclusiveSystemParamFunction<In, Out, Param>
{ ... }
```
  • Loading branch information
JoJoJet authored and ItsDoot committed Feb 1, 2023
1 parent 4c00092 commit 95863b2
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 24 deletions.
88 changes: 65 additions & 23 deletions crates/bevy_ecs/src/system/exclusive_function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
schedule::{SystemLabel, SystemLabelId},
system::{
check_system_change_tick, AsSystemLabel, ExclusiveSystemParam, ExclusiveSystemParamItem,
IntoSystem, System, SystemMeta, SystemTypeIdLabel,
In, InputMarker, IntoSystem, System, SystemMeta, SystemTypeIdLabel,
},
world::{World, WorldId},
};
Expand All @@ -19,7 +19,7 @@ use std::{borrow::Cow, marker::PhantomData};
/// [`ExclusiveSystemParam`]s.
///
/// [`ExclusiveFunctionSystem`] must be `.initialized` before they can be run.
pub struct ExclusiveFunctionSystem<Param, Marker, F>
pub struct ExclusiveFunctionSystem<In, Out, Param, Marker, F>
where
Param: ExclusiveSystemParam,
{
Expand All @@ -28,18 +28,21 @@ where
system_meta: SystemMeta,
world_id: Option<WorldId>,
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
marker: PhantomData<fn() -> Marker>,
marker: PhantomData<fn(In) -> (Out, Marker)>,
}

pub struct IsExclusiveFunctionSystem;

impl<Param, Marker, F> IntoSystem<(), (), (IsExclusiveFunctionSystem, Param, Marker)> for F
impl<In, Out, Param, Marker, F> IntoSystem<In, Out, (IsExclusiveFunctionSystem, Param, Marker)>
for F
where
In: 'static,
Out: 'static,
Param: ExclusiveSystemParam + 'static,
Marker: 'static,
F: ExclusiveSystemParamFunction<Param, Marker> + Send + Sync + 'static,
F: ExclusiveSystemParamFunction<In, Out, Param, Marker> + Send + Sync + 'static,
{
type System = ExclusiveFunctionSystem<Param, Marker, F>;
type System = ExclusiveFunctionSystem<In, Out, Param, Marker, F>;
fn into_system(func: Self) -> Self::System {
ExclusiveFunctionSystem {
func,
Expand All @@ -53,14 +56,16 @@ where

const PARAM_MESSAGE: &str = "System's param_state was not found. Did you forget to initialize this system before running it?";

impl<Param, Marker, F> System for ExclusiveFunctionSystem<Param, Marker, F>
impl<In, Out, Param, Marker, F> System for ExclusiveFunctionSystem<In, Out, Param, Marker, F>
where
In: 'static,
Out: 'static,
Param: ExclusiveSystemParam + 'static,
Marker: 'static,
F: ExclusiveSystemParamFunction<Param, Marker> + Send + Sync + 'static,
F: ExclusiveSystemParamFunction<In, Out, Param, Marker> + Send + Sync + 'static,
{
type In = ();
type Out = ();
type In = In;
type Out = Out;

#[inline]
fn name(&self) -> Cow<'static, str> {
Expand Down Expand Up @@ -90,20 +95,22 @@ where
panic!("Cannot run exclusive systems with a shared World reference");
}

fn run(&mut self, _input: Self::In, world: &mut World) -> Self::Out {
fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out {
let saved_last_tick = world.last_change_tick;
world.last_change_tick = self.system_meta.last_change_tick;

let params = Param::get_param(
self.param_state.as_mut().expect(PARAM_MESSAGE),
&self.system_meta,
);
self.func.run(world, params);
let out = self.func.run(world, input, params);

let change_tick = world.change_tick.get_mut();
self.system_meta.last_change_tick = *change_tick;
*change_tick = change_tick.wrapping_add(1);
world.last_change_tick = saved_last_tick;

out
}

#[inline]
Expand Down Expand Up @@ -147,8 +154,11 @@ where
}
}

impl<Param: ExclusiveSystemParam, Marker, T: ExclusiveSystemParamFunction<Param, Marker>>
AsSystemLabel<(Param, Marker, IsExclusiveFunctionSystem)> for T
impl<In, Out, Param, Marker, T> AsSystemLabel<(In, Out, Param, Marker, IsExclusiveFunctionSystem)>
for T
where
Param: ExclusiveSystemParam,
T: ExclusiveSystemParamFunction<In, Out, Param, Marker>,
{
#[inline]
fn as_system_label(&self) -> SystemLabelId {
Expand All @@ -160,38 +170,70 @@ impl<Param: ExclusiveSystemParam, Marker, T: ExclusiveSystemParamFunction<Param,
///
/// This trait can be useful for making your own systems which accept other systems,
/// sometimes called higher order systems.
pub trait ExclusiveSystemParamFunction<Param: ExclusiveSystemParam, Marker>:
pub trait ExclusiveSystemParamFunction<In, Out, Param: ExclusiveSystemParam, Marker>:
Send + Sync + 'static
{
fn run(&mut self, world: &mut World, param_value: ExclusiveSystemParamItem<Param>);
fn run(
&mut self,
world: &mut World,
input: In,
param_value: ExclusiveSystemParamItem<Param>,
) -> Out;
}

macro_rules! impl_exclusive_system_function {
($($param: ident),*) => {
#[allow(non_snake_case)]
impl<Func: Send + Sync + 'static, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<($($param,)*), ()> for Func
impl<Out, Func: Send + Sync + 'static, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<(), Out, ($($param,)*), ()> for Func
where
for <'a> &'a mut Func:
FnMut(&mut World, $($param),*) +
FnMut(&mut World, $(ExclusiveSystemParamItem<$param>),*)
FnMut(&mut World, $($param),*) -> Out +
FnMut(&mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out,
Out: 'static,
{
#[inline]
fn run(&mut self, world: &mut World, param_value: ExclusiveSystemParamItem< ($($param,)*)>) {
fn run(&mut self, world: &mut World, _in: (), param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out {
// Yes, this is strange, but `rustc` fails to compile this impl
// without using this function. It fails to recognise that `func`
// is a function, potentially because of the multiple impls of `FnMut`
#[allow(clippy::too_many_arguments)]
fn call_inner<$($param,)*>(
mut f: impl FnMut(&mut World, $($param,)*),
fn call_inner<Out, $($param,)*>(
mut f: impl FnMut(&mut World, $($param,)*) -> Out,
world: &mut World,
$($param: $param,)*
) {
) -> Out {
f(world, $($param,)*)
}
let ($($param,)*) = param_value;
call_inner(self, world, $($param),*)
}
}
#[allow(non_snake_case)]
impl<Input, Out, Func: Send + Sync + 'static, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<Input, Out, ($($param,)*), InputMarker> for Func
where
for <'a> &'a mut Func:
FnMut(In<Input>, &mut World, $($param),*) -> Out +
FnMut(In<Input>, &mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out,
Out: 'static,
{
#[inline]
fn run(&mut self, world: &mut World, input: Input, param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out {
// Yes, this is strange, but `rustc` fails to compile this impl
// without using this function. It fails to recognise that `func`
// is a function, potentially because of the multiple impls of `FnMut`
#[allow(clippy::too_many_arguments)]
fn call_inner<Input, Out, $($param,)*>(
mut f: impl FnMut(In<Input>, &mut World, $($param,)*) -> Out,
input: Input,
world: &mut World,
$($param: $param,)*
) -> Out {
f(In(input), world, $($param,)*)
}
let ($($param,)*) = param_value;
call_inner(self, input, world, $($param),*)
}
}
};
}
// Note that we rely on the highest impl to be <= the highest order of the tuple impls
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/system/function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ where
world_id: Option<WorldId>,
archetype_generation: ArchetypeGeneration,
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
marker: PhantomData<fn() -> (In, Out, Marker)>,
marker: PhantomData<fn(In) -> (Out, Marker)>,
}

pub struct IsFunctionSystem;
Expand Down
7 changes: 7 additions & 0 deletions crates/bevy_ecs/src/system/system_piping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,15 @@ pub mod adapter {
unimplemented!()
}

/// Mocks an exclusive system that takes an input and returns an output.
fn exclusive_in_out<A, B>(_: In<A>, _: &mut World) -> B {
unimplemented!()
}

assert_is_system(returning::<Result<u32, std::io::Error>>.pipe(unwrap));
assert_is_system(returning::<Option<()>>.pipe(ignore));
assert_is_system(returning::<&str>.pipe(new(u64::from_str)).pipe(unwrap));
assert_is_system(exclusive_in_out::<(), Result<(), std::io::Error>>.pipe(error));
assert_is_system(returning::<bool>.pipe(exclusive_in_out::<bool, ()>));
}
}

0 comments on commit 95863b2

Please sign in to comment.