Skip to content

[Merged by Bors] - Add AND/OR combinators for run conditions #7605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 40 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
50bd3b0
add a struct for making generic system combinators
joseph-gio Feb 10, 2023
7d3c53e
implement `PipeSystem` using `CombinatorSystem`
joseph-gio Feb 10, 2023
f2d5b08
and `and_then`/`or_else` combinators
joseph-gio Feb 10, 2023
6fa420a
tweak a safety comment
joseph-gio Feb 10, 2023
8e2579d
add docs to `and_then` and `or_else`
joseph-gio Feb 10, 2023
d91d906
add docs to `CombinatorSystem`
joseph-gio Feb 10, 2023
78524ba
add a doctest to `Combine`
joseph-gio Feb 10, 2023
d353e06
update doctest
joseph-gio Feb 10, 2023
435c657
fix some mistakes in the doctest
joseph-gio Feb 10, 2023
40ab70a
typo
joseph-gio Feb 10, 2023
2b76184
use `IntoSystem` properly
joseph-gio Feb 10, 2023
5a437c6
`resource` -> `state`
joseph-gio Feb 10, 2023
1fd4d1e
reorder generics
joseph-gio Feb 10, 2023
d76bc89
derive eq
joseph-gio Feb 10, 2023
9d212e8
update renamed variables
joseph-gio Feb 10, 2023
892b1da
use fully qualiified `Cow`
joseph-gio Feb 11, 2023
c25c564
fix a where clause
joseph-gio Feb 11, 2023
b775326
simplify `Combine::combine`
joseph-gio Feb 11, 2023
f6e3fbc
remove `Combine::combine_exclusive`
joseph-gio Feb 11, 2023
6cd5eef
add safety comments to `run_unsafe`
joseph-gio Feb 11, 2023
685c712
remove some unnecessary fully qualified types
joseph-gio Feb 11, 2023
affe2d9
use `UnsafeCell` to retain lifetimes
joseph-gio Feb 11, 2023
c077ed1
improve safety comments for `CombinatorSystem`
joseph-gio Feb 11, 2023
6f2a8d0
add a note pointing to the docs on `Combine`
joseph-gio Feb 12, 2023
3dcfc0b
document members of `Combine` trait
joseph-gio Feb 12, 2023
7a4c5a5
use a correct parameter name
joseph-gio Feb 12, 2023
a09eb71
add a doctest to `and_then`
joseph-gio Feb 12, 2023
0a26bbd
add a doctest to `or_else`
joseph-gio Feb 12, 2023
514e4ca
add a missing derive
joseph-gio Feb 12, 2023
5e948c7
`state` -> `resource`
joseph-gio Feb 12, 2023
ef26671
declare a forgotten function
joseph-gio Feb 12, 2023
b1ddab8
add docs to `AndThen`/`OrElse` typedefs
joseph-gio Feb 12, 2023
96301c8
add `Condition<>` to the prelude
joseph-gio Feb 12, 2023
174e75f
Merge remote-tracking branch 'upstream/main' into combine-system
joseph-gio Feb 12, 2023
caafd21
Update crates/bevy_ecs/src/system/combinator.rs
joseph-gio Feb 12, 2023
1302b62
remove an extra word
joseph-gio Feb 12, 2023
9c1ed08
combine default system sets
joseph-gio Feb 16, 2023
03f7e40
Merge remote-tracking branch 'upstream/main' into combine-system
joseph-gio Feb 18, 2023
5a14d65
add a note about default short circuiting behavior
joseph-gio Feb 18, 2023
35bfba1
add `and_then` to the example for run conditions
joseph-gio Feb 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ pub mod prelude {
query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without},
removal_detection::RemovedComponents,
schedule::{
apply_state_transition, apply_system_buffers, common_conditions::*, IntoSystemConfig,
IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig, IntoSystemSetConfigs, NextState,
OnEnter, OnExit, OnUpdate, Schedule, Schedules, State, States, SystemSet,
apply_state_transition, apply_system_buffers, common_conditions::*, Condition,
IntoSystemConfig, IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig,
IntoSystemSetConfigs, NextState, OnEnter, OnExit, OnUpdate, Schedule, Schedules, State,
States, SystemSet,
},
system::{
adapter as system_adapter,
Expand Down
152 changes: 150 additions & 2 deletions crates/bevy_ecs/src/schedule/condition.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,112 @@
use crate::system::BoxedSystem;
use std::borrow::Cow;

use crate::system::{BoxedSystem, CombinatorSystem, Combine, IntoSystem, System};

pub type BoxedCondition = BoxedSystem<(), bool>;

/// A system that determines if one or more scheduled systems should run.
///
/// Implemented for functions and closures that convert into [`System<In=(), Out=bool>`](crate::system::System)
/// with [read-only](crate::system::ReadOnlySystemParam) parameters.
pub trait Condition<Params>: sealed::Condition<Params> {}
pub trait Condition<Params>: sealed::Condition<Params> {
/// Returns a new run condition that only returns `true`
/// if both this one and the passed `and_then` return `true`.
///
/// The returned run condition is short-circuiting, meaning
/// `and_then` will only be invoked if `self` returns `true`.
///
/// # Examples
///
/// ```should_panic
/// use bevy_ecs::prelude::*;
///
/// #[derive(Resource, PartialEq)]
/// struct R(u32);
///
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # fn my_system() {}
/// app.add_system(
/// // The `resource_equals` run condition will panic since we don't initialize `R`,
/// // just like if we used `Res<R>` in a system.
/// my_system.run_if(resource_equals(R(0))),
/// );
/// # app.run(&mut world);
/// ```
///
/// Use `.and_then()` to avoid checking the condition.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Resource, PartialEq)]
/// # struct R(u32);
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # fn my_system() {}
/// app.add_system(
/// // `resource_equals` will only get run if the resource `R` exists.
/// my_system.run_if(resource_exists::<R>().and_then(resource_equals(R(0)))),
/// );
/// # app.run(&mut world);
/// ```
///
/// Note that in this case, it's better to just use the run condition [`resource_exists_and_equals`].
///
/// [`resource_exists_and_equals`]: common_conditions::resource_exists_and_equals
fn and_then<P, C: Condition<P>>(self, and_then: C) -> AndThen<Self::System, C::System> {
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(and_then);
let name = format!("{} && {}", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
}

/// Returns a new run condition that returns `true`
/// if either this one or the passed `or_else` return `true`.
///
/// The returned run condition is short-circuiting, meaning
/// `or_else` will only be invoked if `self` returns `false`.
///
/// # Examples
///
/// ```
/// use bevy_ecs::prelude::*;
///
/// #[derive(Resource, PartialEq)]
/// struct A(u32);
///
/// #[derive(Resource, PartialEq)]
/// struct B(u32);
///
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # #[derive(Resource)] struct C(bool);
/// # fn my_system(mut c: ResMut<C>) { c.0 = true; }
/// app.add_system(
/// // Only run the system if either `A` or `B` exist.
/// my_system.run_if(resource_exists::<A>().or_else(resource_exists::<B>())),
/// );
/// #
/// # world.insert_resource(C(false));
/// # app.run(&mut world);
/// # assert!(!world.resource::<C>().0);
/// #
/// # world.insert_resource(A(0));
/// # app.run(&mut world);
/// # assert!(world.resource::<C>().0);
/// #
/// # world.remove_resource::<A>();
/// # world.insert_resource(B(0));
/// # world.insert_resource(C(false));
/// # app.run(&mut world);
/// # assert!(world.resource::<C>().0);
/// ```
fn or_else<P, C: Condition<P>>(self, or_else: C) -> OrElse<Self::System, C::System> {
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(or_else);
let name = format!("{} || {}", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
}
}

impl<Params, F> Condition<Params> for F where F: sealed::Condition<Params> {}

Expand Down Expand Up @@ -146,3 +246,51 @@ pub mod common_conditions {
condition.pipe(|In(val): In<bool>| !val)
}
}

/// Combines the outputs of two systems using the `&&` operator.
pub type AndThen<A, B> = CombinatorSystem<AndThenMarker, A, B>;

/// Combines the outputs of two systems using the `||` operator.
pub type OrElse<A, B> = CombinatorSystem<OrElseMarker, A, B>;

#[doc(hidden)]
pub struct AndThenMarker;

impl<In, A, B> Combine<A, B> for AndThenMarker
where
In: Copy,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
type In = In;
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,
) -> Self::Out {
a(input) && b(input)
}
}

#[doc(hidden)]
pub struct OrElseMarker;

impl<In, A, B> Combine<A, B> for OrElseMarker
where
In: Copy,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
type In = In;
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,
) -> Self::Out {
a(input) || b(input)
}
}
Loading