Skip to content
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

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

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