Skip to content

Commit c7ec456

Browse files
ItsDootSkiFire13
andauthored
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 <[email protected]>
1 parent 4c087da commit c7ec456

17 files changed

+745
-294
lines changed

crates/bevy_app/src/app.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use bevy_ecs::{
88
intern::Interned,
99
prelude::*,
1010
schedule::{ScheduleBuildSettings, ScheduleLabel},
11-
system::{IntoObserverSystem, SystemId},
11+
system::{IntoObserverSystem, SystemId, SystemInput},
1212
};
1313
#[cfg(feature = "trace")]
1414
use bevy_utils::tracing::info_span;
@@ -302,10 +302,14 @@ impl App {
302302
/// This allows for running systems in a push-based fashion.
303303
/// Using a [`Schedule`] is still preferred for most cases
304304
/// due to its better performance and ability to run non-conflicting systems simultaneously.
305-
pub fn register_system<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
305+
pub fn register_system<I, O, M>(
306306
&mut self,
307-
system: S,
308-
) -> SystemId<I, O> {
307+
system: impl IntoSystem<I, O, M> + 'static,
308+
) -> SystemId<I, O>
309+
where
310+
I: SystemInput + 'static,
311+
O: 'static,
312+
{
309313
self.main_mut().register_system(system)
310314
}
311315

crates/bevy_app/src/sub_app.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use bevy_ecs::{
33
event::EventRegistry,
44
prelude::*,
55
schedule::{InternedScheduleLabel, ScheduleBuildSettings, ScheduleLabel},
6-
system::SystemId,
6+
system::{SystemId, SystemInput},
77
};
88

99
#[cfg(feature = "trace")]
@@ -189,10 +189,14 @@ impl SubApp {
189189
}
190190

191191
/// See [`App::register_system`].
192-
pub fn register_system<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
192+
pub fn register_system<I, O, M>(
193193
&mut self,
194-
system: S,
195-
) -> SystemId<I, O> {
194+
system: impl IntoSystem<I, O, M> + 'static,
195+
) -> SystemId<I, O>
196+
where
197+
I: SystemInput + 'static,
198+
O: 'static,
199+
{
196200
self.world.register_system(system)
197201
}
198202

crates/bevy_ecs/src/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ pub mod prelude {
6363
IntoSystemSetConfigs, Schedule, Schedules, SystemSet,
6464
},
6565
system::{
66-
Commands, Deferred, EntityCommand, EntityCommands, In, IntoSystem, Local, NonSend,
67-
NonSendMut, ParallelCommands, ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource,
68-
System, SystemParamBuilder, SystemParamFunction,
66+
Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local,
67+
NonSend, NonSendMut, ParallelCommands, ParamSet, Query, ReadOnlySystem, Res, ResMut,
68+
Resource, System, SystemIn, SystemInput, SystemParamBuilder, SystemParamFunction,
6969
},
7070
world::{
7171
Command, EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove,

crates/bevy_ecs/src/observer/mod.rs

+15
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::{archetype::ArchetypeFlags, system::IntoObserverSystem, world::*};
1313
use crate::{component::ComponentId, prelude::*, world::DeferredWorld};
1414
use bevy_ptr::Ptr;
1515
use bevy_utils::HashMap;
16+
use std::ops::{Deref, DerefMut};
1617
use std::{fmt::Debug, marker::PhantomData};
1718

1819
/// 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> {
122123
}
123124
}
124125

126+
impl<'w, E, B: Bundle> Deref for Trigger<'w, E, B> {
127+
type Target = E;
128+
129+
fn deref(&self) -> &Self::Target {
130+
self.event
131+
}
132+
}
133+
134+
impl<'w, E, B: Bundle> DerefMut for Trigger<'w, E, B> {
135+
fn deref_mut(&mut self) -> &mut Self::Target {
136+
self.event
137+
}
138+
}
139+
125140
/// A description of what an [`Observer`] observes.
126141
#[derive(Default, Clone)]
127142
pub struct ObserverDescriptor {

crates/bevy_ecs/src/observer/runner.rs

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

crates/bevy_ecs/src/schedule/condition.rs

+53-48
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use std::borrow::Cow;
22
use std::ops::Not;
33

44
use crate::system::{
5-
Adapt, AdapterSystem, CombinatorSystem, Combine, IntoSystem, ReadOnlySystem, System,
5+
Adapt, AdapterSystem, CombinatorSystem, Combine, IntoSystem, ReadOnlySystem, System, SystemIn,
6+
SystemInput,
67
};
78

89
/// A type-erased run condition stored in a [`Box`].
@@ -57,7 +58,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
5758
///
5859
/// ```
5960
/// # use bevy_ecs::prelude::*;
60-
/// fn identity() -> impl Condition<(), bool> {
61+
/// fn identity() -> impl Condition<(), In<bool>> {
6162
/// IntoSystem::into_system(|In(x)| x)
6263
/// }
6364
///
@@ -70,7 +71,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
7071
/// # world.insert_resource(DidRun(false));
7172
/// # app.run(&mut world);
7273
/// # assert!(world.resource::<DidRun>().0);
73-
pub trait Condition<Marker, In = ()>: sealed::Condition<Marker, In> {
74+
pub trait Condition<Marker, In: SystemInput = ()>: sealed::Condition<Marker, In> {
7475
/// Returns a new run condition that only returns `true`
7576
/// if both this one and the passed `and` return `true`.
7677
///
@@ -466,20 +467,20 @@ pub trait Condition<Marker, In = ()>: sealed::Condition<Marker, In> {
466467
}
467468
}
468469

469-
impl<Marker, In, F> Condition<Marker, In> for F where F: sealed::Condition<Marker, In> {}
470+
impl<Marker, In: SystemInput, F> Condition<Marker, In> for F where F: sealed::Condition<Marker, In> {}
470471

471472
mod sealed {
472-
use crate::system::{IntoSystem, ReadOnlySystem};
473+
use crate::system::{IntoSystem, ReadOnlySystem, SystemInput};
473474

474-
pub trait Condition<Marker, In>:
475+
pub trait Condition<Marker, In: SystemInput>:
475476
IntoSystem<In, bool, Marker, System = Self::ReadOnlySystem>
476477
{
477478
// This associated type is necessary to let the compiler
478479
// know that `Self::System` is `ReadOnlySystem`.
479480
type ReadOnlySystem: ReadOnlySystem<In = In, Out = bool>;
480481
}
481482

482-
impl<Marker, In, F> Condition<Marker, In> for F
483+
impl<Marker, In: SystemInput, F> Condition<Marker, In> for F
483484
where
484485
F: IntoSystem<In, bool, Marker>,
485486
F::System: ReadOnlySystem,
@@ -496,7 +497,7 @@ pub mod common_conditions {
496497
event::{Event, EventReader},
497498
prelude::{Component, Query, With},
498499
removal_detection::RemovedComponents,
499-
system::{In, IntoSystem, Local, Res, Resource, System},
500+
system::{In, IntoSystem, Local, Res, Resource, System, SystemInput},
500501
};
501502

502503
/// A [`Condition`]-satisfying system that returns `true`
@@ -1110,10 +1111,12 @@ pub mod common_conditions {
11101111
/// app.run(&mut world);
11111112
/// assert_eq!(world.resource::<Counter>().0, 2);
11121113
/// ```
1113-
pub fn condition_changed<Marker, CIn, C: Condition<Marker, CIn>>(
1114-
condition: C,
1115-
) -> impl Condition<(), CIn> {
1116-
condition.pipe(|In(new): In<bool>, mut prev: Local<bool>| -> bool {
1114+
pub fn condition_changed<Marker, CIn, C>(condition: C) -> impl Condition<(), CIn>
1115+
where
1116+
CIn: SystemInput,
1117+
C: Condition<Marker, CIn>,
1118+
{
1119+
condition.pipe(|In(new): In<bool>, mut prev: Local<bool>| {
11171120
let changed = *prev != new;
11181121
*prev = new;
11191122
changed
@@ -1164,10 +1167,11 @@ pub mod common_conditions {
11641167
/// app.run(&mut world);
11651168
/// assert_eq!(world.resource::<Counter>().0, 2);
11661169
/// ```
1167-
pub fn condition_changed_to<Marker, CIn, C: Condition<Marker, CIn>>(
1168-
to: bool,
1169-
condition: C,
1170-
) -> impl Condition<(), CIn> {
1170+
pub fn condition_changed_to<Marker, CIn, C>(to: bool, condition: C) -> impl Condition<(), CIn>
1171+
where
1172+
CIn: SystemInput,
1173+
C: Condition<Marker, CIn>,
1174+
{
11711175
condition.pipe(move |In(new): In<bool>, mut prev: Local<bool>| -> bool {
11721176
let now_true = *prev != new && new == to;
11731177
*prev = new;
@@ -1179,21 +1183,22 @@ pub mod common_conditions {
11791183
/// Invokes [`Not`] with the output of another system.
11801184
///
11811185
/// See [`common_conditions::not`] for examples.
1182-
pub type NotSystem<T> = AdapterSystem<NotMarker, T>;
1186+
pub type NotSystem<S> = AdapterSystem<NotMarker, S>;
11831187

11841188
/// Used with [`AdapterSystem`] to negate the output of a system via the [`Not`] operator.
11851189
#[doc(hidden)]
11861190
#[derive(Clone, Copy)]
11871191
pub struct NotMarker;
11881192

1189-
impl<T: System> Adapt<T> for NotMarker
1190-
where
1191-
T::Out: Not,
1192-
{
1193-
type In = T::In;
1194-
type Out = <T::Out as Not>::Output;
1193+
impl<S: System<Out: Not>> Adapt<S> for NotMarker {
1194+
type In = S::In;
1195+
type Out = <S::Out as Not>::Output;
11951196

1196-
fn adapt(&mut self, input: Self::In, run_system: impl FnOnce(T::In) -> T::Out) -> Self::Out {
1197+
fn adapt(
1198+
&mut self,
1199+
input: <Self::In as SystemInput>::Inner<'_>,
1200+
run_system: impl FnOnce(SystemIn<'_, S>) -> S::Out,
1201+
) -> Self::Out {
11971202
!run_system(input)
11981203
}
11991204
}
@@ -1221,17 +1226,17 @@ pub struct AndMarker;
12211226

12221227
impl<In, A, B> Combine<A, B> for AndMarker
12231228
where
1224-
In: Copy,
1229+
for<'a> In: SystemInput<Inner<'a>: Copy>,
12251230
A: System<In = In, Out = bool>,
12261231
B: System<In = In, Out = bool>,
12271232
{
12281233
type In = In;
12291234
type Out = bool;
12301235

12311236
fn combine(
1232-
input: Self::In,
1233-
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
1234-
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
1237+
input: <Self::In as SystemInput>::Inner<'_>,
1238+
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
1239+
b: impl FnOnce(SystemIn<'_, A>) -> B::Out,
12351240
) -> Self::Out {
12361241
a(input) && b(input)
12371242
}
@@ -1242,17 +1247,17 @@ pub struct NandMarker;
12421247

12431248
impl<In, A, B> Combine<A, B> for NandMarker
12441249
where
1245-
In: Copy,
1250+
for<'a> In: SystemInput<Inner<'a>: Copy>,
12461251
A: System<In = In, Out = bool>,
12471252
B: System<In = In, Out = bool>,
12481253
{
12491254
type In = In;
12501255
type Out = bool;
12511256

12521257
fn combine(
1253-
input: Self::In,
1254-
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
1255-
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
1258+
input: <Self::In as SystemInput>::Inner<'_>,
1259+
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
1260+
b: impl FnOnce(SystemIn<'_, B>) -> B::Out,
12561261
) -> Self::Out {
12571262
!(a(input) && b(input))
12581263
}
@@ -1263,17 +1268,17 @@ pub struct NorMarker;
12631268

12641269
impl<In, A, B> Combine<A, B> for NorMarker
12651270
where
1266-
In: Copy,
1271+
for<'a> In: SystemInput<Inner<'a>: Copy>,
12671272
A: System<In = In, Out = bool>,
12681273
B: System<In = In, Out = bool>,
12691274
{
12701275
type In = In;
12711276
type Out = bool;
12721277

12731278
fn combine(
1274-
input: Self::In,
1275-
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
1276-
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
1279+
input: <Self::In as SystemInput>::Inner<'_>,
1280+
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
1281+
b: impl FnOnce(SystemIn<'_, B>) -> B::Out,
12771282
) -> Self::Out {
12781283
!(a(input) || b(input))
12791284
}
@@ -1284,17 +1289,17 @@ pub struct OrMarker;
12841289

12851290
impl<In, A, B> Combine<A, B> for OrMarker
12861291
where
1287-
In: Copy,
1292+
for<'a> In: SystemInput<Inner<'a>: Copy>,
12881293
A: System<In = In, Out = bool>,
12891294
B: System<In = In, Out = bool>,
12901295
{
12911296
type In = In;
12921297
type Out = bool;
12931298

12941299
fn combine(
1295-
input: Self::In,
1296-
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
1297-
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
1300+
input: <Self::In as SystemInput>::Inner<'_>,
1301+
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
1302+
b: impl FnOnce(SystemIn<'_, B>) -> B::Out,
12981303
) -> Self::Out {
12991304
a(input) || b(input)
13001305
}
@@ -1305,17 +1310,17 @@ pub struct XnorMarker;
13051310

13061311
impl<In, A, B> Combine<A, B> for XnorMarker
13071312
where
1308-
In: Copy,
1313+
for<'a> In: SystemInput<Inner<'a>: Copy>,
13091314
A: System<In = In, Out = bool>,
13101315
B: System<In = In, Out = bool>,
13111316
{
13121317
type In = In;
13131318
type Out = bool;
13141319

13151320
fn combine(
1316-
input: Self::In,
1317-
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
1318-
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
1321+
input: <Self::In as SystemInput>::Inner<'_>,
1322+
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
1323+
b: impl FnOnce(SystemIn<'_, B>) -> B::Out,
13191324
) -> Self::Out {
13201325
!(a(input) ^ b(input))
13211326
}
@@ -1326,17 +1331,17 @@ pub struct XorMarker;
13261331

13271332
impl<In, A, B> Combine<A, B> for XorMarker
13281333
where
1329-
In: Copy,
1334+
for<'a> In: SystemInput<Inner<'a>: Copy>,
13301335
A: System<In = In, Out = bool>,
13311336
B: System<In = In, Out = bool>,
13321337
{
13331338
type In = In;
13341339
type Out = bool;
13351340

13361341
fn combine(
1337-
input: Self::In,
1338-
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
1339-
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
1342+
input: <Self::In as SystemInput>::Inner<'_>,
1343+
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
1344+
b: impl FnOnce(SystemIn<'_, B>) -> B::Out,
13401345
) -> Self::Out {
13411346
a(input) ^ b(input)
13421347
}

0 commit comments

Comments
 (0)