Skip to content

Commit f2a3670

Browse files
joseph-giomyreprise1
authored andcommitted
Allow piping run conditions (bevyengine#7547)
# Objective Run conditions are a special type of system that do not modify the world, and which return a bool. Due to the way they are currently implemented, you can *only* use bare function systems as a run condition. Among other things, this prevents the use of system piping with run conditions. This make very basic constructs impossible, such as `my_system.run_if(my_condition.pipe(not))`. Unblocks a basic solution for bevyengine#7202. ## Solution Add the trait `ReadOnlySystem`, which is implemented for any system whose parameters all implement `ReadOnlySystemParam`. Allow any `-> bool` system implementing this trait to be used as a run condition. --- ## Changelog + Added the trait `ReadOnlySystem`, which is implemented for any `System` type whose parameters all implement `ReadOnlySystemParam`. + Added the function `bevy::ecs::system::assert_is_read_only_system`.
1 parent 177326f commit f2a3670

File tree

5 files changed

+62
-7
lines changed

5 files changed

+62
-7
lines changed

crates/bevy_ecs/src/schedule/condition.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ pub trait Condition<Params>: sealed::Condition<Params> {}
1111
impl<Params, F> Condition<Params> for F where F: sealed::Condition<Params> {}
1212

1313
mod sealed {
14-
use crate::system::{IntoSystem, IsFunctionSystem, ReadOnlySystemParam, SystemParamFunction};
14+
use crate::system::{IntoSystem, ReadOnlySystem};
1515

1616
pub trait Condition<Params>: IntoSystem<(), bool, Params> {}
1717

18-
impl<Params, Marker, F> Condition<(IsFunctionSystem, Params, Marker)> for F
18+
impl<Params, F> Condition<Params> for F
1919
where
20-
F: SystemParamFunction<(), bool, Params, Marker> + Send + Sync + 'static,
21-
Params: ReadOnlySystemParam + 'static,
22-
Marker: 'static,
20+
F: IntoSystem<(), bool, Params>,
21+
F::System: ReadOnlySystem,
2322
{
2423
}
2524
}

crates/bevy_ecs/src/system/function_system.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use crate::{
1010
use bevy_ecs_macros::all_tuples;
1111
use std::{any::TypeId, borrow::Cow, marker::PhantomData};
1212

13+
use super::ReadOnlySystem;
14+
1315
/// The metadata of a [`System`].
1416
#[derive(Clone)]
1517
pub struct SystemMeta {
@@ -528,6 +530,17 @@ where
528530
}
529531
}
530532

533+
/// SAFETY: `F`'s param is `ReadOnlySystemParam`, so this system will only read from the world.
534+
unsafe impl<In, Out, Param, Marker, F> ReadOnlySystem for FunctionSystem<In, Out, Param, Marker, F>
535+
where
536+
In: 'static,
537+
Out: 'static,
538+
Param: ReadOnlySystemParam + 'static,
539+
Marker: 'static,
540+
F: SystemParamFunction<In, Out, Param, Marker> + Send + Sync + 'static,
541+
{
542+
}
543+
531544
/// A trait implemented for all functions that can be used as [`System`]s.
532545
///
533546
/// This trait can be useful for making your own systems which accept other systems,

crates/bevy_ecs/src/system/mod.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ pub use system::*;
117117
pub use system_param::*;
118118
pub use system_piping::*;
119119

120-
/// Ensure that a given function is a system
120+
/// Ensure that a given function is a [system](System).
121121
///
122122
/// This should be used when writing doc examples,
123123
/// to confirm that systems used in an example are
124-
/// valid systems
124+
/// valid systems.
125125
pub fn assert_is_system<In, Out, Params, S: IntoSystem<In, Out, Params>>(sys: S) {
126126
if false {
127127
// Check it can be converted into a system
@@ -130,6 +130,22 @@ pub fn assert_is_system<In, Out, Params, S: IntoSystem<In, Out, Params>>(sys: S)
130130
}
131131
}
132132

133+
/// Ensure that a given function is a [read-only system](ReadOnlySystem).
134+
///
135+
/// This should be used when writing doc examples,
136+
/// to confirm that systems used in an example are
137+
/// valid systems.
138+
pub fn assert_is_read_only_system<In, Out, Params, S: IntoSystem<In, Out, Params>>(sys: S)
139+
where
140+
S::System: ReadOnlySystem,
141+
{
142+
if false {
143+
// Check it can be converted into a system
144+
// TODO: This should ensure that the system has no conflicting system params
145+
IntoSystem::into_system(sys);
146+
}
147+
}
148+
133149
#[cfg(test)]
134150
mod tests {
135151
use std::any::TypeId;

crates/bevy_ecs/src/system/system.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ pub trait System: Send + Sync + 'static {
7878
fn set_last_change_tick(&mut self, last_change_tick: u32);
7979
}
8080

81+
/// [`System`] types that do not modify the [`World`] when run.
82+
/// This is implemented for any systems whose parameters all implement [`ReadOnlySystemParam`].
83+
///
84+
/// [`ReadOnlySystemParam`]: crate::system::ReadOnlySystemParam
85+
///
86+
/// # Safety
87+
///
88+
/// This must only be implemented for system types which do not mutate the `World`.
89+
pub unsafe trait ReadOnlySystem: System {}
90+
8191
/// A convenience type alias for a boxed [`System`] trait object.
8292
pub type BoxedSystem<In = (), Out = ()> = Box<dyn System<In = In, Out = Out>>;
8393

crates/bevy_ecs/src/system/system_piping.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use crate::{
77
};
88
use std::{any::TypeId, borrow::Cow};
99

10+
use super::ReadOnlySystem;
11+
1012
/// A [`System`] created by piping the output of the first system into the input of the second.
1113
///
1214
/// This can be repeated indefinitely, but system pipes cannot branch: the output is consumed by the receiving system.
@@ -153,6 +155,15 @@ impl<SystemA: System, SystemB: System<In = SystemA::Out>> System for PipeSystem<
153155
}
154156
}
155157

158+
/// SAFETY: Both systems are read-only, so piping them together will only read from the world.
159+
unsafe impl<SystemA: System, SystemB: System<In = SystemA::Out>> ReadOnlySystem
160+
for PipeSystem<SystemA, SystemB>
161+
where
162+
SystemA: ReadOnlySystem,
163+
SystemB: ReadOnlySystem,
164+
{
165+
}
166+
156167
/// An extension trait providing the [`IntoPipeSystem::pipe`] method to pass input from one system into the next.
157168
///
158169
/// The first system must have return type `T`
@@ -412,10 +423,16 @@ pub mod adapter {
412423
unimplemented!()
413424
}
414425

426+
fn not(In(val): In<bool>) -> bool {
427+
!val
428+
}
429+
415430
assert_is_system(returning::<Result<u32, std::io::Error>>.pipe(unwrap));
416431
assert_is_system(returning::<Option<()>>.pipe(ignore));
417432
assert_is_system(returning::<&str>.pipe(new(u64::from_str)).pipe(unwrap));
418433
assert_is_system(exclusive_in_out::<(), Result<(), std::io::Error>>.pipe(error));
419434
assert_is_system(returning::<bool>.pipe(exclusive_in_out::<bool, ()>));
435+
436+
returning::<()>.run_if(returning::<bool>.pipe(not));
420437
}
421438
}

0 commit comments

Comments
 (0)