Support systems that take references as input (#15184)

# Objective

- Fixes #14924
- Closes #9584

## Solution

- We introduce a new trait, `SystemInput`, that serves as a type
function from the `'static` form of the input, to its lifetime'd
version, similarly to `SystemParam` or `WorldQuery`.
- System functions now take the lifetime'd wrapped version,
`SystemInput::Param<'_>`, which prevents the issue presented in #14924
(i.e. `InRef<T>`).
- Functions for running systems now take the lifetime'd unwrapped
version, `SystemInput::Inner<'_>` (i.e. `&T`).
- Due to the above change, system piping had to be re-implemented as a
standalone type, rather than `CombinatorSystem` as it was previously.
- Removes the `Trigger<'static, E, B>` transmute in observer runner
code.

## Testing

- All current tests pass.
- Added additional tests and doc-tests.

---

## Showcase

```rust
let mut world = World::new();

let mut value = 2;

// Currently possible:
fn square(In(input): In<usize>) -> usize {
    input * input
}
value = world.run_system_once_with(value, square);

// Now possible:
fn square_mut(InMut(input): InMut<usize>) {
    *input *= *input;
}
world.run_system_once_with(&mut value, square_mut);

// Or:
fn square_ref(InRef(input): InRef<usize>) -> usize {
    *input * *input
}
value = world.run_system_once_with(&value, square_ref);
```

## Migration Guide

- All current explicit usages of the following types must be changed in
the way specified:
    - `SystemId<I, O>` to `SystemId<In<I>, O>`
    - `System<In = T>` to `System<In = In<T>>`
    - `IntoSystem<I, O, M>` to `IntoSystem<In<I>, O, M>`
    - `Condition<M, T>` to `Condition<M, In<T>>`
- `In<Trigger<E, B>>` is no longer a valid input parameter type. Use
`Trigger<E, B>` directly, instead.

---------

Co-authored-by: Giacomo Stevanato <giaco.stevanato@gmail.com>
This commit is contained in:
Christian Hughes 2024-09-23 12:37:29 -05:00 committed by GitHub
parent 4c087daa20
commit c7ec456e50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 746 additions and 295 deletions

View file

@ -8,7 +8,7 @@ use bevy_ecs::{
intern::Interned,
prelude::*,
schedule::{ScheduleBuildSettings, ScheduleLabel},
system::{IntoObserverSystem, SystemId},
system::{IntoObserverSystem, SystemId, SystemInput},
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
@ -302,10 +302,14 @@ impl App {
/// This allows for running systems in a push-based fashion.
/// Using a [`Schedule`] is still preferred for most cases
/// due to its better performance and ability to run non-conflicting systems simultaneously.
pub fn register_system<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
pub fn register_system<I, O, M>(
&mut self,
system: S,
) -> SystemId<I, O> {
system: impl IntoSystem<I, O, M> + 'static,
) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
self.main_mut().register_system(system)
}

View file

@ -3,7 +3,7 @@ use bevy_ecs::{
event::EventRegistry,
prelude::*,
schedule::{InternedScheduleLabel, ScheduleBuildSettings, ScheduleLabel},
system::SystemId,
system::{SystemId, SystemInput},
};
#[cfg(feature = "trace")]
@ -189,10 +189,14 @@ impl SubApp {
}
/// See [`App::register_system`].
pub fn register_system<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
pub fn register_system<I, O, M>(
&mut self,
system: S,
) -> SystemId<I, O> {
system: impl IntoSystem<I, O, M> + 'static,
) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
self.world.register_system(system)
}

View file

@ -63,9 +63,9 @@ pub mod prelude {
IntoSystemSetConfigs, Schedule, Schedules, SystemSet,
},
system::{
Commands, Deferred, EntityCommand, EntityCommands, In, IntoSystem, Local, NonSend,
NonSendMut, ParallelCommands, ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource,
System, SystemParamBuilder, SystemParamFunction,
Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local,
NonSend, NonSendMut, ParallelCommands, ParamSet, Query, ReadOnlySystem, Res, ResMut,
Resource, System, SystemIn, SystemInput, SystemParamBuilder, SystemParamFunction,
},
world::{
Command, EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove,

View file

@ -13,6 +13,7 @@ use crate::{archetype::ArchetypeFlags, system::IntoObserverSystem, world::*};
use crate::{component::ComponentId, prelude::*, world::DeferredWorld};
use bevy_ptr::Ptr;
use bevy_utils::HashMap;
use std::ops::{Deref, DerefMut};
use std::{fmt::Debug, marker::PhantomData};
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
@ -122,6 +123,20 @@ impl<'w, E: Debug, B: Bundle> Debug for Trigger<'w, E, B> {
}
}
impl<'w, E, B: Bundle> Deref for Trigger<'w, E, B> {
type Target = E;
fn deref(&self) -> &Self::Target {
self.event
}
}
impl<'w, E, B: Bundle> DerefMut for Trigger<'w, E, B> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.event
}
}
/// A description of what an [`Observer`] observes.
#[derive(Default, Clone)]
pub struct ObserverDescriptor {

View file

@ -357,13 +357,6 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
propagate,
observer_trigger,
);
// SAFETY: the static lifetime is encapsulated in Trigger / cannot leak out.
// Additionally, IntoObserverSystem is only implemented for functions starting
// with for<'a> Trigger<'a>, meaning users cannot specify Trigger<'static> manually,
// allowing the Trigger<'static> to be moved outside of the context of the system.
// This transmute is obviously not ideal, but it is safe. Ideally we can remove the
// static constraint from ObserverSystem, but so far we have not found a way.
let trigger: Trigger<'static, E, B> = unsafe { std::mem::transmute(trigger) };
// SAFETY:
// - observer was triggered so must have an `Observer` component.
// - observer cannot be dropped or mutated until after the system pointer is already dropped.

View file

@ -2,7 +2,8 @@ use std::borrow::Cow;
use std::ops::Not;
use crate::system::{
Adapt, AdapterSystem, CombinatorSystem, Combine, IntoSystem, ReadOnlySystem, System,
Adapt, AdapterSystem, CombinatorSystem, Combine, IntoSystem, ReadOnlySystem, System, SystemIn,
SystemInput,
};
/// A type-erased run condition stored in a [`Box`].
@ -57,7 +58,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
///
/// ```
/// # use bevy_ecs::prelude::*;
/// fn identity() -> impl Condition<(), bool> {
/// fn identity() -> impl Condition<(), In<bool>> {
/// IntoSystem::into_system(|In(x)| x)
/// }
///
@ -70,7 +71,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
/// # world.insert_resource(DidRun(false));
/// # app.run(&mut world);
/// # assert!(world.resource::<DidRun>().0);
pub trait Condition<Marker, In = ()>: sealed::Condition<Marker, In> {
pub trait Condition<Marker, In: SystemInput = ()>: sealed::Condition<Marker, In> {
/// Returns a new run condition that only returns `true`
/// if both this one and the passed `and` return `true`.
///
@ -466,12 +467,12 @@ pub trait Condition<Marker, In = ()>: sealed::Condition<Marker, In> {
}
}
impl<Marker, In, F> Condition<Marker, In> for F where F: sealed::Condition<Marker, In> {}
impl<Marker, In: SystemInput, F> Condition<Marker, In> for F where F: sealed::Condition<Marker, In> {}
mod sealed {
use crate::system::{IntoSystem, ReadOnlySystem};
use crate::system::{IntoSystem, ReadOnlySystem, SystemInput};
pub trait Condition<Marker, In>:
pub trait Condition<Marker, In: SystemInput>:
IntoSystem<In, bool, Marker, System = Self::ReadOnlySystem>
{
// This associated type is necessary to let the compiler
@ -479,7 +480,7 @@ mod sealed {
type ReadOnlySystem: ReadOnlySystem<In = In, Out = bool>;
}
impl<Marker, In, F> Condition<Marker, In> for F
impl<Marker, In: SystemInput, F> Condition<Marker, In> for F
where
F: IntoSystem<In, bool, Marker>,
F::System: ReadOnlySystem,
@ -496,7 +497,7 @@ pub mod common_conditions {
event::{Event, EventReader},
prelude::{Component, Query, With},
removal_detection::RemovedComponents,
system::{In, IntoSystem, Local, Res, Resource, System},
system::{In, IntoSystem, Local, Res, Resource, System, SystemInput},
};
/// A [`Condition`]-satisfying system that returns `true`
@ -1110,10 +1111,12 @@ pub mod common_conditions {
/// app.run(&mut world);
/// assert_eq!(world.resource::<Counter>().0, 2);
/// ```
pub fn condition_changed<Marker, CIn, C: Condition<Marker, CIn>>(
condition: C,
) -> impl Condition<(), CIn> {
condition.pipe(|In(new): In<bool>, mut prev: Local<bool>| -> bool {
pub fn condition_changed<Marker, CIn, C>(condition: C) -> impl Condition<(), CIn>
where
CIn: SystemInput,
C: Condition<Marker, CIn>,
{
condition.pipe(|In(new): In<bool>, mut prev: Local<bool>| {
let changed = *prev != new;
*prev = new;
changed
@ -1164,10 +1167,11 @@ pub mod common_conditions {
/// app.run(&mut world);
/// assert_eq!(world.resource::<Counter>().0, 2);
/// ```
pub fn condition_changed_to<Marker, CIn, C: Condition<Marker, CIn>>(
to: bool,
condition: C,
) -> impl Condition<(), CIn> {
pub fn condition_changed_to<Marker, CIn, C>(to: bool, condition: C) -> impl Condition<(), CIn>
where
CIn: SystemInput,
C: Condition<Marker, CIn>,
{
condition.pipe(move |In(new): In<bool>, mut prev: Local<bool>| -> bool {
let now_true = *prev != new && new == to;
*prev = new;
@ -1179,21 +1183,22 @@ pub mod common_conditions {
/// Invokes [`Not`] with the output of another system.
///
/// See [`common_conditions::not`] for examples.
pub type NotSystem<T> = AdapterSystem<NotMarker, T>;
pub type NotSystem<S> = AdapterSystem<NotMarker, S>;
/// Used with [`AdapterSystem`] to negate the output of a system via the [`Not`] operator.
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct NotMarker;
impl<T: System> Adapt<T> for NotMarker
where
T::Out: Not,
{
type In = T::In;
type Out = <T::Out as Not>::Output;
impl<S: System<Out: Not>> Adapt<S> for NotMarker {
type In = S::In;
type Out = <S::Out as Not>::Output;
fn adapt(&mut self, input: Self::In, run_system: impl FnOnce(T::In) -> T::Out) -> Self::Out {
fn adapt(
&mut self,
input: <Self::In as SystemInput>::Inner<'_>,
run_system: impl FnOnce(SystemIn<'_, S>) -> S::Out,
) -> Self::Out {
!run_system(input)
}
}
@ -1221,7 +1226,7 @@ pub struct AndMarker;
impl<In, A, B> Combine<A, B> for AndMarker
where
In: Copy,
for<'a> In: SystemInput<Inner<'a>: Copy>,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
@ -1229,9 +1234,9 @@ where
type Out = bool;
fn combine(
input: Self::In,
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
input: <Self::In as SystemInput>::Inner<'_>,
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
b: impl FnOnce(SystemIn<'_, A>) -> B::Out,
) -> Self::Out {
a(input) && b(input)
}
@ -1242,7 +1247,7 @@ pub struct NandMarker;
impl<In, A, B> Combine<A, B> for NandMarker
where
In: Copy,
for<'a> In: SystemInput<Inner<'a>: Copy>,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
@ -1250,9 +1255,9 @@ where
type Out = bool;
fn combine(
input: Self::In,
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
input: <Self::In as SystemInput>::Inner<'_>,
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
b: impl FnOnce(SystemIn<'_, B>) -> B::Out,
) -> Self::Out {
!(a(input) && b(input))
}
@ -1263,7 +1268,7 @@ pub struct NorMarker;
impl<In, A, B> Combine<A, B> for NorMarker
where
In: Copy,
for<'a> In: SystemInput<Inner<'a>: Copy>,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
@ -1271,9 +1276,9 @@ where
type Out = bool;
fn combine(
input: Self::In,
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
input: <Self::In as SystemInput>::Inner<'_>,
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
b: impl FnOnce(SystemIn<'_, B>) -> B::Out,
) -> Self::Out {
!(a(input) || b(input))
}
@ -1284,7 +1289,7 @@ pub struct OrMarker;
impl<In, A, B> Combine<A, B> for OrMarker
where
In: Copy,
for<'a> In: SystemInput<Inner<'a>: Copy>,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
@ -1292,9 +1297,9 @@ where
type Out = bool;
fn combine(
input: Self::In,
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
input: <Self::In as SystemInput>::Inner<'_>,
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
b: impl FnOnce(SystemIn<'_, B>) -> B::Out,
) -> Self::Out {
a(input) || b(input)
}
@ -1305,7 +1310,7 @@ pub struct XnorMarker;
impl<In, A, B> Combine<A, B> for XnorMarker
where
In: Copy,
for<'a> In: SystemInput<Inner<'a>: Copy>,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
@ -1313,9 +1318,9 @@ where
type Out = bool;
fn combine(
input: Self::In,
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
input: <Self::In as SystemInput>::Inner<'_>,
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
b: impl FnOnce(SystemIn<'_, B>) -> B::Out,
) -> Self::Out {
!(a(input) ^ b(input))
}
@ -1326,7 +1331,7 @@ pub struct XorMarker;
impl<In, A, B> Combine<A, B> for XorMarker
where
In: Copy,
for<'a> In: SystemInput<Inner<'a>: Copy>,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
@ -1334,9 +1339,9 @@ where
type Out = bool;
fn combine(
input: Self::In,
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
input: <Self::In as SystemInput>::Inner<'_>,
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
b: impl FnOnce(SystemIn<'_, B>) -> B::Out,
) -> Self::Out {
a(input) ^ b(input)
}

View file

@ -1,7 +1,11 @@
use std::borrow::Cow;
use super::{ReadOnlySystem, System};
use crate::{schedule::InternedSystemSet, world::unsafe_world_cell::UnsafeWorldCell};
use crate::{
schedule::InternedSystemSet,
system::{input::SystemInput, SystemIn},
world::unsafe_world_cell::UnsafeWorldCell,
};
/// Customizes the behavior of an [`AdapterSystem`]
///
@ -28,8 +32,8 @@ use crate::{schedule::InternedSystemSet, world::unsafe_world_cell::UnsafeWorldCe
///
/// fn adapt(
/// &mut self,
/// input: Self::In,
/// run_system: impl FnOnce(S::In) -> S::Out,
/// input: <Self::In as SystemInput>::Inner<'_>,
/// run_system: impl FnOnce(SystemIn<'_, S>) -> S::Out,
/// ) -> Self::Out {
/// !run_system(input)
/// }
@ -45,13 +49,17 @@ use crate::{schedule::InternedSystemSet, world::unsafe_world_cell::UnsafeWorldCe
)]
pub trait Adapt<S: System>: Send + Sync + 'static {
/// The [input](System::In) type for an [`AdapterSystem`].
type In;
type In: SystemInput;
/// The [output](System::Out) type for an [`AdapterSystem`].
type Out;
/// When used in an [`AdapterSystem`], this function customizes how the system
/// is run and how its inputs/outputs are adapted.
fn adapt(&mut self, input: Self::In, run_system: impl FnOnce(S::In) -> S::Out) -> Self::Out;
fn adapt(
&mut self,
input: <Self::In as SystemInput>::Inner<'_>,
run_system: impl FnOnce(SystemIn<'_, S>) -> S::Out,
) -> Self::Out;
}
/// A [`System`] that takes the output of `S` and transforms it by applying `Func` to it.
@ -109,7 +117,11 @@ where
}
#[inline]
unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out {
unsafe fn run_unsafe(
&mut self,
input: SystemIn<'_, Self>,
world: UnsafeWorldCell,
) -> Self::Out {
// SAFETY: `system.run_unsafe` has the same invariants as `self.run_unsafe`.
self.func.adapt(input, |input| unsafe {
self.system.run_unsafe(input, world)
@ -117,7 +129,7 @@ where
}
#[inline]
fn run(&mut self, input: Self::In, world: &mut crate::prelude::World) -> Self::Out {
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut crate::prelude::World) -> Self::Out {
self.func
.adapt(input, |input| self.system.run(input, world))
}
@ -173,13 +185,17 @@ where
impl<F, S, Out> Adapt<S> for F
where
S: System,
F: Send + Sync + 'static + FnMut(S::Out) -> Out,
S: System,
{
type In = S::In;
type Out = Out;
fn adapt(&mut self, input: S::In, run_system: impl FnOnce(S::In) -> S::Out) -> Out {
fn adapt(
&mut self,
input: <Self::In as SystemInput>::Inner<'_>,
run_system: impl FnOnce(SystemIn<'_, S>) -> S::Out,
) -> Out {
self(run_system(input))
}
}

View file

@ -6,6 +6,7 @@ use crate::{
prelude::World,
query::Access,
schedule::InternedSystemSet,
system::{input::SystemInput, SystemIn},
world::unsafe_world_cell::UnsafeWorldCell,
};
@ -88,7 +89,7 @@ use super::{ReadOnlySystem, System};
)]
pub trait Combine<A: System, B: System> {
/// The [input](System::In) type for a [`CombinatorSystem`].
type In;
type In: SystemInput;
/// The [output](System::Out) type for a [`CombinatorSystem`].
type Out;
@ -98,9 +99,9 @@ pub trait Combine<A: System, B: System> {
///
/// See the trait-level docs for [`Combine`] for an example implementation.
fn combine(
input: Self::In,
a: impl FnOnce(A::In) -> A::Out,
b: impl FnOnce(B::In) -> B::Out,
input: <Self::In as SystemInput>::Inner<'_>,
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
b: impl FnOnce(SystemIn<'_, B>) -> B::Out,
) -> Self::Out;
}
@ -165,7 +166,11 @@ where
self.a.has_deferred() || self.b.has_deferred()
}
unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out {
unsafe fn run_unsafe(
&mut self,
input: SystemIn<'_, Self>,
world: UnsafeWorldCell,
) -> Self::Out {
Func::combine(
input,
// SAFETY: The world accesses for both underlying systems have been registered,
@ -180,7 +185,7 @@ where
)
}
fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out {
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
let world = world.as_unsafe_world_cell();
Func::combine(
input,
@ -249,7 +254,7 @@ where
}
/// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world.
unsafe impl<A, B, Func> ReadOnlySystem for CombinatorSystem<Func, A, B>
unsafe impl<Func, A, B> ReadOnlySystem for CombinatorSystem<Func, A, B>
where
Func: Combine<A, B> + 'static,
A: ReadOnlySystem,
@ -307,25 +312,140 @@ where
/// result.ok().filter(|&n| n < 100)
/// }
/// ```
pub type PipeSystem<SystemA, SystemB> = CombinatorSystem<Pipe, SystemA, SystemB>;
pub struct PipeSystem<A, B> {
a: A,
b: B,
name: Cow<'static, str>,
component_access: Access<ComponentId>,
archetype_component_access: Access<ArchetypeComponentId>,
}
#[doc(hidden)]
pub struct Pipe;
impl<A, B> Combine<A, B> for Pipe
impl<A, B> PipeSystem<A, B>
where
A: System,
B: System<In = A::Out>,
B: System,
for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
{
/// Creates a new system that pipes two inner systems.
pub const fn new(a: A, b: B, name: Cow<'static, str>) -> Self {
Self {
a,
b,
name,
component_access: Access::new(),
archetype_component_access: Access::new(),
}
}
}
impl<A, B> System for PipeSystem<A, B>
where
A: System,
B: System,
for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
{
type In = A::In;
type Out = B::Out;
fn combine(
input: Self::In,
a: impl FnOnce(A::In) -> A::Out,
b: impl FnOnce(B::In) -> B::Out,
fn name(&self) -> Cow<'static, str> {
self.name.clone()
}
fn component_access(&self) -> &Access<ComponentId> {
&self.component_access
}
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
&self.archetype_component_access
}
fn is_send(&self) -> bool {
self.a.is_send() && self.b.is_send()
}
fn is_exclusive(&self) -> bool {
self.a.is_exclusive() || self.b.is_exclusive()
}
fn has_deferred(&self) -> bool {
self.a.has_deferred() || self.b.has_deferred()
}
unsafe fn run_unsafe(
&mut self,
input: SystemIn<'_, Self>,
world: UnsafeWorldCell,
) -> Self::Out {
let value = a(input);
b(value)
let value = self.a.run_unsafe(input, world);
self.b.run_unsafe(value, world)
}
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
let value = self.a.run(input, world);
self.b.run(value, world)
}
fn apply_deferred(&mut self, world: &mut World) {
self.a.apply_deferred(world);
self.b.apply_deferred(world);
}
fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {
self.a.queue_deferred(world.reborrow());
self.b.queue_deferred(world);
}
unsafe fn validate_param_unsafe(&self, world: UnsafeWorldCell) -> bool {
self.a.validate_param_unsafe(world) && self.b.validate_param_unsafe(world)
}
fn validate_param(&mut self, world: &World) -> bool {
self.a.validate_param(world) && self.b.validate_param(world)
}
fn initialize(&mut self, world: &mut World) {
self.a.initialize(world);
self.b.initialize(world);
self.component_access.extend(self.a.component_access());
self.component_access.extend(self.b.component_access());
}
fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
self.a.update_archetype_component_access(world);
self.b.update_archetype_component_access(world);
self.archetype_component_access
.extend(self.a.archetype_component_access());
self.archetype_component_access
.extend(self.b.archetype_component_access());
}
fn check_change_tick(&mut self, change_tick: Tick) {
self.a.check_change_tick(change_tick);
self.b.check_change_tick(change_tick);
}
fn default_system_sets(&self) -> Vec<InternedSystemSet> {
let mut default_sets = self.a.default_system_sets();
default_sets.append(&mut self.b.default_system_sets());
default_sets
}
fn get_last_run(&self) -> Tick {
self.a.get_last_run()
}
fn set_last_run(&mut self, last_run: Tick) {
self.a.set_last_run(last_run);
self.b.set_last_run(last_run);
}
}
/// SAFETY: Both systems are read-only, so any system created by piping them will only read from the world.
unsafe impl<A, B> ReadOnlySystem for PipeSystem<A, B>
where
A: ReadOnlySystem,
B: ReadOnlySystem,
for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
{
}

View file

@ -14,7 +14,7 @@ use crate::{
entity::{Entities, Entity},
event::{Event, SendEvent},
observer::{Observer, TriggerEvent, TriggerTargets},
system::{RunSystemWithInput, SystemId},
system::{input::SystemInput, RunSystemWithInput, SystemId},
world::{
command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, Command, CommandQueue,
EntityWorldMut, FromWorld, SpawnBatchIter, World,
@ -713,7 +713,10 @@ impl<'w, 's> Commands<'w, 's> {
/// There is no way to get the output of a system when run as a command, because the
/// execution of the system happens later. To get the output of a system, use
/// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command.
pub fn run_system_with_input<I: 'static + Send>(&mut self, id: SystemId<I>, input: I) {
pub fn run_system_with_input<I>(&mut self, id: SystemId<I>, input: I::Inner<'static>)
where
I: SystemInput<Inner<'static>: Send> + 'static,
{
self.queue(RunSystemWithInput::new_with_input(id, input));
}
@ -766,15 +769,14 @@ impl<'w, 's> Commands<'w, 's> {
/// # assert_eq!(1, world.resource::<Counter>().0);
/// # bevy_ecs::system::assert_is_system(register_system);
/// ```
pub fn register_system<
I: 'static + Send,
O: 'static + Send,
M,
S: IntoSystem<I, O, M> + 'static,
>(
pub fn register_system<I, O, M>(
&mut self,
system: S,
) -> SystemId<I, O> {
system: impl IntoSystem<I, O, M> + 'static,
) -> SystemId<I, O>
where
I: SystemInput + Send + 'static,
O: Send + 'static,
{
let entity = self.spawn_empty().id();
self.queue(RegisterSystem::new(system, entity));
SystemId::from_entity(entity)
@ -792,15 +794,12 @@ impl<'w, 's> Commands<'w, 's> {
/// [`CachedSystemId`](crate::system::CachedSystemId) resource.
///
/// See [`World::register_system_cached`] for more information.
pub fn run_system_cached_with<
I: 'static + Send,
pub fn run_system_cached_with<I, M, S>(&mut self, system: S, input: I::Inner<'static>)
where
I: SystemInput<Inner<'static>: Send> + Send + 'static,
M: 'static,
S: IntoSystem<I, (), M> + 'static,
>(
&mut self,
system: S,
input: I,
) {
{
self.queue(RunSystemCachedWith::new(system, input));
}

View file

@ -4,8 +4,8 @@ use crate::{
query::Access,
schedule::{InternedSystemSet, SystemSet},
system::{
check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, In, IntoSystem,
System, SystemMeta,
check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, IntoSystem,
System, SystemIn, SystemInput, SystemMeta,
},
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
@ -108,11 +108,15 @@ where
}
#[inline]
unsafe fn run_unsafe(&mut self, _input: Self::In, _world: UnsafeWorldCell) -> Self::Out {
unsafe fn run_unsafe(
&mut self,
_input: SystemIn<'_, Self>,
_world: UnsafeWorldCell,
) -> Self::Out {
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: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
world.last_change_tick_scope(self.system_meta.last_run, |world| {
#[cfg(feature = "trace")]
let _span_guard = self.system_meta.system_span.enter();
@ -190,7 +194,7 @@ where
)]
pub trait ExclusiveSystemParamFunction<Marker>: Send + Sync + 'static {
/// The input type to this system. See [`System::In`].
type In;
type In: SystemInput;
/// The return type of this system. See [`System::Out`].
type Out;
@ -202,17 +206,22 @@ pub trait ExclusiveSystemParamFunction<Marker>: Send + Sync + 'static {
fn run(
&mut self,
world: &mut World,
input: Self::In,
input: <Self::In as SystemInput>::Inner<'_>,
param_value: ExclusiveSystemParamItem<Self::Param>,
) -> Self::Out;
}
/// A marker type used to distinguish exclusive function systems with and without input.
#[doc(hidden)]
pub struct HasExclusiveSystemInput;
macro_rules! impl_exclusive_system_function {
($($param: ident),*) => {
#[allow(non_snake_case)]
impl<Out, Func: Send + Sync + 'static, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<fn($($param,)*) -> Out> for Func
impl<Out, Func, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<fn($($param,)*) -> Out> for Func
where
for <'a> &'a mut Func:
Func: Send + Sync + 'static,
for <'a> &'a mut Func:
FnMut(&mut World, $($param),*) -> Out +
FnMut(&mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out,
Out: 'static,
@ -237,30 +246,33 @@ macro_rules! impl_exclusive_system_function {
call_inner(self, world, $($param),*)
}
}
#[allow(non_snake_case)]
impl<Input, Out, Func: Send + Sync + 'static, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<fn(In<Input>, $($param,)*) -> Out> for Func
impl<In, Out, Func, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<(HasExclusiveSystemInput, fn(In, $($param,)*) -> Out)> for Func
where
for <'a> &'a mut Func:
FnMut(In<Input>, &mut World, $($param),*) -> Out +
FnMut(In<Input>, &mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out,
Func: Send + Sync + 'static,
for <'a> &'a mut Func:
FnMut(In, &mut World, $($param),*) -> Out +
FnMut(In::Param<'_>, &mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out,
In: SystemInput + 'static,
Out: 'static,
{
type In = Input;
type In = In;
type Out = Out;
type Param = ($($param,)*);
#[inline]
fn run(&mut self, world: &mut World, input: Input, param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out {
fn run(&mut self, world: &mut World, input: In::Inner<'_>, param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out {
// Yes, this is strange, but `rustc` fails to compile this impl
// without using this function. It fails to recognize 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,
fn call_inner<In: SystemInput, Out, $($param,)*>(
mut f: impl FnMut(In::Param<'_>, &mut World, $($param,)*) -> Out,
input: In::Inner<'_>,
world: &mut World,
$($param: $param,)*
) -> Out {
f(In(input), world, $($param,)*)
f(In::wrap(input), world, $($param,)*)
}
let ($($param,)*) = param_value;
call_inner(self, input, world, $($param),*)
@ -274,11 +286,13 @@ all_tuples!(impl_exclusive_system_function, 0, 16, F);
#[cfg(test)]
mod tests {
use crate::system::input::SystemInput;
use super::*;
#[test]
fn into_system_type_id_consistency() {
fn test<T, In, Out, Marker>(function: T)
fn test<T, In: SystemInput, Out, Marker>(function: T)
where
T: IntoSystem<In, Out, Marker> + Copy,
{

View file

@ -4,7 +4,10 @@ use crate::{
prelude::FromWorld,
query::{Access, FilteredAccessSet},
schedule::{InternedSystemSet, SystemSet},
system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem},
system::{
check_system_change_tick, ReadOnlySystemParam, System, SystemIn, SystemInput, SystemParam,
SystemParamItem,
},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World, WorldId},
};
@ -237,7 +240,7 @@ macro_rules! impl_build_system {
/// 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.
pub fn build_system_with_input<
Input,
Input: SystemInput,
Out: 'static,
Marker,
F: FnMut(In<Input>, $(SystemParamItem<$param>),*) -> Out
@ -612,7 +615,11 @@ where
}
#[inline]
unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out {
unsafe fn run_unsafe(
&mut self,
input: SystemIn<'_, Self>,
world: UnsafeWorldCell,
) -> Self::Out {
#[cfg(feature = "trace")]
let _span_guard = self.system_meta.system_span.enter();
@ -731,19 +738,21 @@ where
/// use std::num::ParseIntError;
///
/// use bevy_ecs::prelude::*;
/// use bevy_ecs::system::StaticSystemInput;
///
/// /// Pipe creates a new system which calls `a`, then calls `b` with the output of `a`
/// pub fn pipe<A, B, AMarker, BMarker>(
/// mut a: A,
/// mut b: B,
/// ) -> impl FnMut(In<A::In>, ParamSet<(A::Param, B::Param)>) -> B::Out
/// ) -> impl FnMut(StaticSystemInput<A::In>, ParamSet<(A::Param, B::Param)>) -> B::Out
/// where
/// // We need A and B to be systems, add those bounds
/// A: SystemParamFunction<AMarker>,
/// B: SystemParamFunction<BMarker, In = A::Out>,
/// B: SystemParamFunction<BMarker>,
/// for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
/// {
/// // The type of `params` is inferred based on the return of this function above
/// move |In(a_in), mut params| {
/// move |StaticSystemInput(a_in), mut params| {
/// let shared = a.run(a_in, params.p0());
/// b.run(shared, params.p1())
/// }
@ -778,9 +787,8 @@ where
label = "invalid system"
)]
pub trait SystemParamFunction<Marker>: Send + Sync + 'static {
/// The input type to this system. See [`System::In`].
type In;
/// The input type of this system. See [`System::In`].
type In: SystemInput;
/// The return type of this system. See [`System::Out`].
type Out;
@ -788,17 +796,27 @@ pub trait SystemParamFunction<Marker>: Send + Sync + 'static {
type Param: SystemParam;
/// Executes this system once. See [`System::run`] or [`System::run_unsafe`].
fn run(&mut self, input: Self::In, param_value: SystemParamItem<Self::Param>) -> Self::Out;
fn run(
&mut self,
input: <Self::In as SystemInput>::Inner<'_>,
param_value: SystemParamItem<Self::Param>,
) -> Self::Out;
}
/// A marker type used to distinguish function systems with and without input.
#[doc(hidden)]
pub struct HasSystemInput;
macro_rules! impl_system_function {
($($param: ident),*) => {
#[allow(non_snake_case)]
impl<Out, Func: Send + Sync + 'static, $($param: SystemParam),*> SystemParamFunction<fn($($param,)*) -> Out> for Func
impl<Out, Func, $($param: SystemParam),*> SystemParamFunction<fn($($param,)*) -> Out> for Func
where
for <'a> &'a mut Func:
Func: Send + Sync + 'static,
for <'a> &'a mut Func:
FnMut($($param),*) -> Out +
FnMut($(SystemParamItem<$param>),*) -> Out, Out: 'static
FnMut($(SystemParamItem<$param>),*) -> Out,
Out: 'static
{
type In = ();
type Out = Out;
@ -821,27 +839,30 @@ macro_rules! impl_system_function {
}
#[allow(non_snake_case)]
impl<Input, Out, Func: Send + Sync + 'static, $($param: SystemParam),*> SystemParamFunction<fn(In<Input>, $($param,)*) -> Out> for Func
impl<In, Out, Func, $($param: SystemParam),*> SystemParamFunction<(HasSystemInput, fn(In, $($param,)*) -> Out)> for Func
where
for <'a> &'a mut Func:
FnMut(In<Input>, $($param),*) -> Out +
FnMut(In<Input>, $(SystemParamItem<$param>),*) -> Out, Out: 'static
Func: Send + Sync + 'static,
for <'a> &'a mut Func:
FnMut(In, $($param),*) -> Out +
FnMut(In::Param<'_>, $(SystemParamItem<$param>),*) -> Out,
In: SystemInput + 'static,
Out: 'static
{
type In = Input;
type In = In;
type Out = Out;
type Param = ($($param,)*);
#[inline]
fn run(&mut self, input: Input, param_value: SystemParamItem< ($($param,)*)>) -> Out {
fn run(&mut self, input: In::Inner<'_>, param_value: SystemParamItem< ($($param,)*)>) -> Out {
#[allow(clippy::too_many_arguments)]
fn call_inner<Input, Out, $($param,)*>(
mut f: impl FnMut(In<Input>, $($param,)*)->Out,
input: In<Input>,
fn call_inner<In: SystemInput, Out, $($param,)*>(
mut f: impl FnMut(In::Param<'_>, $($param,)*)->Out,
input: In::Inner<'_>,
$($param: $param,)*
)->Out{
f(input, $($param,)*)
f(In::wrap(input), $($param,)*)
}
let ($($param,)*) = param_value;
call_inner(self, In(input), $($param),*)
call_inner(self, input, $($param),*)
}
}
};
@ -857,7 +878,7 @@ mod tests {
#[test]
fn into_system_type_id_consistency() {
fn test<T, In, Out, Marker>(function: T)
fn test<T, In: SystemInput, Out, Marker>(function: T)
where
T: IntoSystem<In, Out, Marker> + Copy,
{

View file

@ -0,0 +1,229 @@
use std::ops::{Deref, DerefMut};
use crate::{bundle::Bundle, prelude::Trigger, system::System};
/// Trait for types that can be used as input to [`System`]s.
///
/// Provided implementations are:
/// - `()`: No input
/// - [`In<T>`]: For values
/// - [`InRef<T>`]: For read-only references to values
/// - [`InMut<T>`]: For mutable references to values
/// - [`Trigger<E, B>`]: For [`ObserverSystem`]s
/// - [`StaticSystemInput<I>`]: For arbitrary [`SystemInput`]s in generic contexts
///
/// [`ObserverSystem`]: crate::system::ObserverSystem
pub trait SystemInput: Sized {
/// The wrapper input type that is defined as the first argument to [`FunctionSystem`]s.
///
/// [`FunctionSystem`]: crate::system::FunctionSystem
type Param<'i>: SystemInput;
/// The inner input type that is passed to functions that run systems,
/// such as [`System::run`].
///
/// [`System::run`]: crate::system::System::run
type Inner<'i>;
/// Converts a [`SystemInput::Inner`] into a [`SystemInput::Param`].
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_>;
}
/// 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>;
/// [`SystemInput`] type for systems that take no input.
impl SystemInput for () {
type Param<'i> = ();
type Inner<'i> = ();
fn wrap(_this: Self::Inner<'_>) -> Self::Param<'_> {}
}
/// A [`SystemInput`] type which denotes that a [`System`] receives
/// an input value of type `T` from its caller.
///
/// [`System`]s may take an optional input which they require to be passed to them when they
/// are being [`run`](System::run). For [`FunctionSystem`]s the input may be marked
/// with this `In` type, but only the first param of a function may be tagged as an input. This also
/// means a system can only have one or zero input parameters.
///
/// # Examples
///
/// Here is a simple example of a system that takes a [`usize`] and returns the square of it.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// fn square(In(input): In<usize>) -> usize {
/// input * input
/// }
///
/// let mut world = World::new();
/// let mut square_system = IntoSystem::into_system(square);
/// square_system.initialize(&mut world);
///
/// assert_eq!(square_system.run(12, &mut world), 144);
/// ```
///
/// [`SystemParam`]: crate::system::SystemParam
/// [`FunctionSystem`]: crate::system::FunctionSystem
#[derive(Debug)]
pub struct In<T>(pub T);
impl<T: 'static> SystemInput for In<T> {
type Param<'i> = In<T>;
type Inner<'i> = T;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> {
In(this)
}
}
impl<T> Deref for In<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for In<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
/// A [`SystemInput`] type which denotes that a [`System`] receives
/// a read-only reference to a value of type `T` from its caller.
///
/// This is similar to [`In`] but takes a reference to a value instead of the value itself.
/// See [`InMut`] for the mutable version.
///
/// # Examples
///
/// Here is a simple example of a system that logs the passed in message.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use std::fmt::Write as _;
/// #
/// #[derive(Resource, Default)]
/// struct Log(String);
///
/// fn log(InRef(msg): InRef<str>, mut log: ResMut<Log>) {
/// writeln!(log.0, "{}", msg).unwrap();
/// }
///
/// let mut world = World::new();
/// world.init_resource::<Log>();
/// let mut log_system = IntoSystem::into_system(log);
/// log_system.initialize(&mut world);
///
/// log_system.run("Hello, world!", &mut world);
/// # assert_eq!(world.get_resource::<Log>().unwrap().0, "Hello, world!\n");
/// ```
///
/// [`SystemParam`]: crate::system::SystemParam
#[derive(Debug)]
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;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> {
InRef(this)
}
}
impl<'i, T: ?Sized> Deref for InRef<'i, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0
}
}
/// A [`SystemInput`] type which denotes that a [`System`] receives
/// a mutable reference to a value of type `T` from its caller.
///
/// This is similar to [`In`] but takes a mutable reference to a value instead of the value itself.
/// See [`InRef`] for the read-only version.
///
/// # Examples
///
/// Here is a simple example of a system that takes a `&mut usize` and squares it.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// fn square(InMut(input): InMut<usize>) {
/// *input *= *input;
/// }
///
/// let mut world = World::new();
/// let mut square_system = IntoSystem::into_system(square);
/// square_system.initialize(&mut world);
///
/// let mut value = 12;
/// square_system.run(&mut value, &mut world);
/// assert_eq!(value, 144);
/// ```
///
/// [`SystemParam`]: crate::system::SystemParam
#[derive(Debug)]
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;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> {
InMut(this)
}
}
impl<'i, T: ?Sized> Deref for InMut<'i, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<'i, T: ?Sized> DerefMut for InMut<'i, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0
}
}
/// Used for [`ObserverSystem`]s.
///
/// [`ObserverSystem`]: crate::system::ObserverSystem
impl<E: 'static, B: Bundle> SystemInput for Trigger<'_, E, B> {
type Param<'i> = Trigger<'i, E, B>;
type Inner<'i> = Trigger<'i, E, B>;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> {
this
}
}
/// A helper for using [`SystemInput`]s in generic contexts.
///
/// This type is a [`SystemInput`] adapter which always has
/// `Self::Param == Self` (ignoring lifetimes for brevity),
/// no matter the argument [`SystemInput`] (`I`).
///
/// This makes it useful for having arbitrary [`SystemInput`]s in
/// function systems.
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>;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> {
StaticSystemInput(this)
}
}

View file

@ -108,6 +108,7 @@ mod commands;
mod exclusive_function_system;
mod exclusive_system_param;
mod function_system;
mod input;
mod observer_system;
mod query;
#[allow(clippy::module_inception)]
@ -125,6 +126,7 @@ pub use commands::*;
pub use exclusive_function_system::*;
pub use exclusive_system_param::*;
pub use function_system::*;
pub use input::*;
pub use observer_system::*;
pub use query::*;
pub use system::*;
@ -155,7 +157,7 @@ use crate::world::World;
message = "`{Self}` is not a valid system with input `{In}` and output `{Out}`",
label = "invalid system"
)]
pub trait IntoSystem<In, Out, Marker>: Sized {
pub trait IntoSystem<In: SystemInput, Out, Marker>: Sized {
/// The type of [`System`] that this instance converts into.
type System: System<In = In, Out = Out>;
@ -166,9 +168,11 @@ pub trait IntoSystem<In, Out, Marker>: Sized {
///
/// The second system must have [`In<T>`](crate::system::In) as its first parameter,
/// where `T` is the return type of the first system.
fn pipe<B, Final, MarkerB>(self, system: B) -> PipeSystem<Self::System, B::System>
fn pipe<B, BIn, BOut, MarkerB>(self, system: B) -> PipeSystem<Self::System, B::System>
where
B: IntoSystem<Out, Final, MarkerB>,
Out: 'static,
B: IntoSystem<BIn, BOut, MarkerB>,
for<'a> BIn: SystemInput<Inner<'a> = Out>,
{
let system_a = IntoSystem::into_system(self);
let system_b = IntoSystem::into_system(system);
@ -219,34 +223,6 @@ impl<T: System> IntoSystem<T::In, T::Out, ()> for T {
}
}
/// Wrapper type to mark a [`SystemParam`] as an input.
///
/// [`System`]s may take an optional input which they require to be passed to them when they
/// are being [`run`](System::run). For [`FunctionSystems`](FunctionSystem) the input may be marked
/// with this `In` type, but only the first param of a function may be tagged as an input. This also
/// means a system can only have one or zero input parameters.
///
/// # Examples
///
/// Here is a simple example of a system that takes a [`usize`] returning the square of it.
///
/// ```
/// use bevy_ecs::prelude::*;
///
/// fn main() {
/// let mut square_system = IntoSystem::into_system(square);
///
/// let mut world = World::default();
/// square_system.initialize(&mut world);
/// assert_eq!(square_system.run(12, &mut world), 144);
/// }
///
/// fn square(In(input): In<usize>) -> usize {
/// input * input
/// }
/// ```
pub struct In<In>(pub In);
/// Ensure that a given function is a [system](System).
///
/// This should be used when writing doc examples,
@ -271,7 +247,7 @@ pub struct In<In>(pub In);
///
/// assert_is_system(my_system);
/// ```
pub fn assert_is_system<In: 'static, Out: 'static, Marker>(
pub fn assert_is_system<In: SystemInput, Out: 'static, Marker>(
system: impl IntoSystem<In, Out, Marker>,
) {
let mut system = IntoSystem::into_system(system);
@ -304,8 +280,10 @@ pub fn assert_is_system<In: 'static, Out: 'static, Marker>(
///
/// assert_is_read_only_system(my_system);
/// ```
pub fn assert_is_read_only_system<In: 'static, Out: 'static, Marker, S>(system: S)
pub fn assert_is_read_only_system<In, Out, Marker, S>(system: S)
where
In: SystemInput,
Out: 'static,
S: IntoSystem<In, Out, Marker>,
S::System: ReadOnlySystem,
{
@ -324,20 +302,6 @@ pub fn assert_system_does_not_conflict<Out, Params, S: IntoSystem<(), Out, Param
system.run((), &mut world);
}
impl<T> std::ops::Deref for In<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> std::ops::DerefMut for In<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(test)]
mod tests {
use bevy_utils::default;

View file

@ -1,8 +1,6 @@
use bevy_utils::all_tuples;
use crate::{
prelude::{Bundle, Trigger},
system::{System, SystemParam, SystemParamFunction, SystemParamItem},
system::System,
};
use super::IntoSystem;
@ -55,37 +53,6 @@ where
}
}
macro_rules! impl_system_function {
($($param: ident),*) => {
#[allow(non_snake_case)]
impl<E: 'static, B: Bundle, Out, Func: Send + Sync + 'static, $($param: SystemParam),*> SystemParamFunction<fn(Trigger<E, B>, $($param,)*)> for Func
where
for <'a> &'a mut Func:
FnMut(Trigger<E, B>, $($param),*) -> Out +
FnMut(Trigger<E, B>, $(SystemParamItem<$param>),*) -> Out, Out: 'static
{
type In = Trigger<'static, E, B>;
type Out = Out;
type Param = ($($param,)*);
#[inline]
fn run(&mut self, input: Trigger<'static, E, B>, param_value: SystemParamItem< ($($param,)*)>) -> Out {
#[allow(clippy::too_many_arguments)]
fn call_inner<E: 'static, B: Bundle, Out, $($param,)*>(
mut f: impl FnMut(Trigger<'static, E, B>, $($param,)*) -> Out,
input: Trigger<'static, E, B>,
$($param: $param,)*
) -> Out{
f(input, $($param,)*)
}
let ($($param,)*) = param_value;
call_inner(self, input, $($param),*)
}
}
}
}
all_tuples!(impl_system_function, 0, 16, F);
#[cfg(test)]
mod tests {
use crate::{

View file

@ -3,6 +3,8 @@ use core::fmt::Debug;
use crate::component::Tick;
use crate::schedule::InternedSystemSet;
use crate::system::input::SystemInput;
use crate::system::SystemIn;
use crate::world::unsafe_world_cell::UnsafeWorldCell;
use crate::world::DeferredWorld;
use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Access, world::World};
@ -25,9 +27,8 @@ use super::IntoSystem;
/// see [`IntoSystemConfigs`](crate::schedule::IntoSystemConfigs).
#[diagnostic::on_unimplemented(message = "`{Self}` is not a system", label = "invalid system")]
pub trait System: Send + Sync + 'static {
/// The system's input. See [`In`](crate::system::In) for
/// [`FunctionSystem`](crate::system::FunctionSystem)s.
type In;
/// The system's input.
type In: SystemInput;
/// The system's output.
type Out;
/// Returns the system's name.
@ -65,7 +66,8 @@ pub trait System: Send + Sync + 'static {
/// - The method [`System::update_archetype_component_access`] must be called at some
/// point before this one, with the same exact [`World`]. If [`System::update_archetype_component_access`]
/// panics (or otherwise does not return for any reason), this method must not be called.
unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out;
unsafe fn run_unsafe(&mut self, input: SystemIn<'_, Self>, world: UnsafeWorldCell)
-> Self::Out;
/// Runs the system with the given input in the world.
///
@ -74,7 +76,7 @@ pub trait System: Send + Sync + 'static {
/// Unlike [`System::run_unsafe`], this will apply deferred parameters *immediately*.
///
/// [`run_readonly`]: ReadOnlySystem::run_readonly
fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out {
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
let world_cell = world.as_unsafe_world_cell();
self.update_archetype_component_access(world_cell);
// SAFETY:
@ -175,7 +177,7 @@ pub unsafe trait ReadOnlySystem: System {
///
/// Unlike [`System::run`], this can be called with a shared reference to the world,
/// since this system is known not to modify the world.
fn run_readonly(&mut self, input: Self::In, world: &World) -> Self::Out {
fn run_readonly(&mut self, input: SystemIn<'_, Self>, world: &World) -> Self::Out {
let world = world.as_unsafe_world_cell_readonly();
self.update_archetype_component_access(world);
// SAFETY:
@ -199,7 +201,11 @@ pub(crate) fn check_system_change_tick(last_run: &mut Tick, this_run: Tick, syst
}
}
impl<In: 'static, Out: 'static> Debug for dyn System<In = In, Out = Out> {
impl<In, Out> Debug for dyn System<In = In, Out = Out>
where
In: SystemInput + 'static,
Out: 'static,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("System")
.field("name", &self.name())
@ -306,24 +312,34 @@ impl<In: 'static, Out: 'static> Debug for dyn System<In = In, Out = Out> {
/// ```
pub trait RunSystemOnce: Sized {
/// Runs a system and applies its deferred parameters.
fn run_system_once<T: IntoSystem<(), Out, Marker>, Out, Marker>(self, system: T) -> Out {
fn run_system_once<T, Out, Marker>(self, system: T) -> Out
where
T: IntoSystem<(), Out, Marker>,
{
self.run_system_once_with((), system)
}
/// Runs a system with given input and applies its deferred parameters.
fn run_system_once_with<T: IntoSystem<In, Out, Marker>, In, Out, Marker>(
fn run_system_once_with<T, In, Out, Marker>(
self,
input: In,
input: SystemIn<'_, T::System>,
system: T,
) -> Out;
) -> Out
where
T: IntoSystem<In, Out, Marker>,
In: SystemInput;
}
impl RunSystemOnce for &mut World {
fn run_system_once_with<T: IntoSystem<In, Out, Marker>, In, Out, Marker>(
fn run_system_once_with<T, In, Out, Marker>(
self,
input: In,
input: SystemIn<'_, T::System>,
system: T,
) -> Out {
) -> Out
where
T: IntoSystem<In, Out, Marker>,
In: SystemInput,
{
let mut system: T::System = IntoSystem::into_system(system);
system.initialize(self);
system.run(input, self)

View file

@ -1826,7 +1826,7 @@ pub mod lifetimeless {
/// A helper for using system parameters in generic contexts
///
/// This type is a [`SystemParam`] adapter which always has
/// `Self::State::Item == Self` (ignoring lifetimes for brevity),
/// `Self::Item == Self` (ignoring lifetimes for brevity),
/// no matter the argument [`SystemParam`] (`P`) (other than
/// that `P` must be `'static`)
///

View file

@ -1,7 +1,8 @@
use crate::bundle::Bundle;
use crate::change_detection::Mut;
use crate::entity::Entity;
use crate::system::{BoxedSystem, IntoSystem, System};
use crate::system::input::SystemInput;
use crate::system::{BoxedSystem, IntoSystem, System, SystemIn};
use crate::world::{Command, World};
use crate::{self as bevy_ecs};
use bevy_ecs_macros::{Component, Resource};
@ -44,12 +45,12 @@ impl<I, O> RemovedSystem<I, O> {
///
/// These are opaque identifiers, keyed to a specific [`World`],
/// and are created via [`World::register_system`].
pub struct SystemId<I = (), O = ()> {
pub struct SystemId<I: SystemInput = (), O = ()> {
pub(crate) entity: Entity,
pub(crate) marker: std::marker::PhantomData<fn(I) -> O>,
}
impl<I, O> SystemId<I, O> {
impl<I: SystemInput, O> SystemId<I, O> {
/// Transforms a [`SystemId`] into the [`Entity`] that holds the one-shot system's state.
///
/// It's trivial to convert [`SystemId`] into an [`Entity`] since a one-shot system
@ -72,33 +73,33 @@ impl<I, O> SystemId<I, O> {
}
}
impl<I, O> Eq for SystemId<I, O> {}
impl<I: SystemInput, O> Eq for SystemId<I, O> {}
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
impl<I, O> Copy for SystemId<I, O> {}
impl<I: SystemInput, O> Copy for SystemId<I, O> {}
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
impl<I, O> Clone for SystemId<I, O> {
impl<I: SystemInput, O> Clone for SystemId<I, O> {
fn clone(&self) -> Self {
*self
}
}
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
impl<I, O> PartialEq for SystemId<I, O> {
impl<I: SystemInput, O> PartialEq for SystemId<I, O> {
fn eq(&self, other: &Self) -> bool {
self.entity == other.entity && self.marker == other.marker
}
}
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
impl<I, O> std::hash::Hash for SystemId<I, O> {
impl<I: SystemInput, O> std::hash::Hash for SystemId<I, O> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.entity.hash(state);
}
}
impl<I, O> std::fmt::Debug for SystemId<I, O> {
impl<I: SystemInput, O> std::fmt::Debug for SystemId<I, O> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("SystemId").field(&self.entity).finish()
}
@ -133,10 +134,14 @@ impl World {
/// This allows for running systems in a pushed-based fashion.
/// Using a [`Schedule`](crate::schedule::Schedule) is still preferred for most cases
/// due to its better performance and ability to run non-conflicting systems simultaneously.
pub fn register_system<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
pub fn register_system<I, O, M>(
&mut self,
system: S,
) -> SystemId<I, O> {
system: impl IntoSystem<I, O, M> + 'static,
) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
self.register_boxed_system(Box::new(IntoSystem::into_system(system)))
}
@ -144,10 +149,11 @@ impl World {
///
/// This is useful if the [`IntoSystem`] implementor has already been turned into a
/// [`System`] trait object and put in a [`Box`].
pub fn register_boxed_system<I: 'static, O: 'static>(
&mut self,
system: BoxedSystem<I, O>,
) -> SystemId<I, O> {
pub fn register_boxed_system<I, O>(&mut self, system: BoxedSystem<I, O>) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
let entity = self.spawn(system_bundle(system)).id();
SystemId::from_entity(entity)
}
@ -158,10 +164,14 @@ impl World {
///
/// If no system corresponds to the given [`SystemId`], this method returns an error.
/// Systems are also not allowed to remove themselves, this returns an error too.
pub fn remove_system<I: 'static, O: 'static>(
pub fn remove_system<I, O>(
&mut self,
id: SystemId<I, O>,
) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>> {
) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
{
match self.get_entity_mut(id.entity) {
Some(mut entity) => {
let registered_system = entity
@ -297,11 +307,15 @@ impl World {
/// ```
///
/// See [`World::run_system`] for more examples.
pub fn run_system_with_input<I: 'static, O: 'static>(
pub fn run_system_with_input<I, O>(
&mut self,
id: SystemId<I, O>,
input: I,
) -> Result<O, RegisteredSystemError<I, O>> {
input: I::Inner<'_>,
) -> Result<O, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
{
// lookup
let mut entity = self
.get_entity_mut(id.entity)
@ -351,10 +365,12 @@ impl World {
/// If you want to access values from the environment within a system, consider passing them in
/// as inputs via [`World::run_system_cached_with`]. If that's not an option, consider
/// [`World::register_system`] instead.
pub fn register_system_cached<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
&mut self,
system: S,
) -> SystemId<I, O> {
pub fn register_system_cached<I, O, M, S>(&mut self, system: S) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
S: IntoSystem<I, O, M> + 'static,
{
const {
assert!(
size_of::<S>() == 0,
@ -383,10 +399,15 @@ impl World {
/// Removes a cached system and its [`CachedSystemId`] resource.
///
/// See [`World::register_system_cached`] for more information.
pub fn remove_system_cached<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
pub fn remove_system_cached<I, O, M, S>(
&mut self,
_system: S,
) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>> {
) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
S: IntoSystem<I, O, M> + 'static,
{
let id = self
.remove_resource::<CachedSystemId<S::System>>()
.ok_or(RegisteredSystemError::SystemNotCached)?;
@ -406,11 +427,16 @@ impl World {
/// Runs a cached system with an input, registering it if necessary.
///
/// See [`World::register_system_cached`] for more information.
pub fn run_system_cached_with<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
pub fn run_system_cached_with<I, O, M, S>(
&mut self,
system: S,
input: I,
) -> Result<O, RegisteredSystemError<I, O>> {
input: I::Inner<'_>,
) -> Result<O, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
S: IntoSystem<I, O, M> + 'static,
{
let id = self.register_system_cached(system);
self.run_system_with_input(id, input)
}
@ -428,9 +454,9 @@ impl World {
/// execution of the system happens later. To get the output of a system, use
/// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command.
#[derive(Debug, Clone)]
pub struct RunSystemWithInput<I: 'static> {
pub struct RunSystemWithInput<I: SystemInput + 'static> {
system_id: SystemId<I>,
input: I,
input: I::Inner<'static>,
}
/// The [`Command`] type for [`World::run_system`].
@ -453,15 +479,18 @@ impl RunSystem {
}
}
impl<I: 'static> RunSystemWithInput<I> {
impl<I: SystemInput + 'static> RunSystemWithInput<I> {
/// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands)
/// in order to run the specified system with the provided [`In<_>`](crate::system::In) input value.
pub fn new_with_input(system_id: SystemId<I>, input: I) -> Self {
pub fn new_with_input(system_id: SystemId<I>, input: I::Inner<'static>) -> Self {
Self { system_id, input }
}
}
impl<I: 'static + Send> Command for RunSystemWithInput<I> {
impl<I> Command for RunSystemWithInput<I>
where
I: SystemInput<Inner<'static>: Send> + 'static,
{
#[inline]
fn apply(self, world: &mut World) {
let _ = world.run_system_with_input(self.system_id, self.input);
@ -471,12 +500,16 @@ impl<I: 'static + Send> Command for RunSystemWithInput<I> {
/// The [`Command`] type for registering one shot systems from [`Commands`](crate::system::Commands).
///
/// This command needs an already boxed system to register, and an already spawned entity.
pub struct RegisterSystem<I: 'static, O: 'static> {
pub struct RegisterSystem<I: SystemInput + 'static, O: 'static> {
system: BoxedSystem<I, O>,
entity: Entity,
}
impl<I: 'static, O: 'static> RegisterSystem<I, O> {
impl<I, O> RegisterSystem<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
/// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands).
pub fn new<M, S: IntoSystem<I, O, M> + 'static>(system: S, entity: Entity) -> Self {
Self {
@ -486,7 +519,11 @@ impl<I: 'static, O: 'static> RegisterSystem<I, O> {
}
}
impl<I: 'static + Send, O: 'static + Send> Command for RegisterSystem<I, O> {
impl<I, O> Command for RegisterSystem<I, O>
where
I: SystemInput + Send + 'static,
O: Send + 'static,
{
fn apply(self, world: &mut World) {
if let Some(mut entity) = world.get_entity_mut(self.entity) {
entity.insert(system_bundle(self.system));
@ -500,13 +537,16 @@ impl<I: 'static + Send, O: 'static + Send> Command for RegisterSystem<I, O> {
/// See [`World::register_system_cached`] for more information.
pub struct RunSystemCachedWith<S: System<Out = ()>> {
system: S,
input: S::In,
input: SystemIn<'static, S>,
}
impl<S: System<Out = ()>> RunSystemCachedWith<S> {
/// Creates a new [`Command`] struct, which can be added to
/// [`Commands`](crate::system::Commands).
pub fn new<M>(system: impl IntoSystem<S::In, (), M, System = S>, input: S::In) -> Self {
pub fn new<M>(
system: impl IntoSystem<S::In, (), M, System = S>,
input: SystemIn<'static, S>,
) -> Self {
Self {
system: IntoSystem::into_system(system),
input,
@ -516,7 +556,7 @@ impl<S: System<Out = ()>> RunSystemCachedWith<S> {
impl<S: System<Out = ()>> Command for RunSystemCachedWith<S>
where
S::In: Send,
S::In: SystemInput<Inner<'static>: Send>,
{
fn apply(self, world: &mut World) {
let _ = world.run_system_cached_with(self.system, self.input);
@ -525,7 +565,7 @@ where
/// An operation with stored systems failed.
#[derive(Error)]
pub enum RegisteredSystemError<I = (), O = ()> {
pub enum RegisteredSystemError<I: SystemInput = (), O = ()> {
/// A system was run by id, but no system with that id was found.
///
/// Did you forget to register it?
@ -544,7 +584,7 @@ pub enum RegisteredSystemError<I = (), O = ()> {
SelfRemove(SystemId<I, O>),
}
impl<I, O> std::fmt::Debug for RegisteredSystemError<I, O> {
impl<I: SystemInput, O> std::fmt::Debug for RegisteredSystemError<I, O> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SystemIdNotRegistered(arg0) => {
@ -729,7 +769,7 @@ mod tests {
use crate::system::SystemId;
#[derive(Component)]
struct Callback(SystemId<u8>, u8);
struct Callback(SystemId<In<u8>>, u8);
fn nested(query: Query<&Callback>, mut commands: Commands) {
for callback in query.iter() {
@ -782,4 +822,48 @@ mod tests {
let output = world.run_system_cached_with(four, ());
assert!(matches!(output, Ok(x) if x == four()));
}
#[test]
fn system_with_input_ref() {
fn with_ref(InRef(input): InRef<u8>, mut counter: ResMut<Counter>) {
counter.0 += *input;
}
let mut world = World::new();
world.insert_resource(Counter(0));
let id = world.register_system(with_ref);
world.run_system_with_input(id, &2).unwrap();
assert_eq!(*world.resource::<Counter>(), Counter(2));
}
#[test]
fn system_with_input_mut() {
#[derive(Event)]
struct MyEvent {
cancelled: bool,
}
fn post(InMut(event): InMut<MyEvent>, counter: ResMut<Counter>) {
if counter.0 > 0 {
event.cancelled = true;
}
}
let mut world = World::new();
world.insert_resource(Counter(0));
let post_system = world.register_system(post);
let mut event = MyEvent { cancelled: false };
world
.run_system_with_input(post_system, &mut event)
.unwrap();
assert!(!event.cancelled);
world.resource_mut::<Counter>().0 = 1;
world
.run_system_with_input(post_system, &mut event)
.unwrap();
assert!(event.cancelled);
}
}