Skip to content

Commit 0e805eb

Browse files
authored
Implement SystemCondition for systems returning Result<bool, BevyError> and Result<(), BevyError> (#19553)
# Objective Fixes #19403 As described in the issue, the objective is to support the use of systems returning `Result<(), BevyError>` and `Result<bool, BevyError>` as run conditions. In these cases, the run condition would hold on `Ok(())` and `Ok(true)` respectively. ## Solution `IntoSystem<In, bool, M>` cannot be implemented for systems returning `Result<(), BevyError>` and `Result<bool, BevyError>` as that would conflict with their trivial implementation of the trait. That led me to add a method to the sealed trait `SystemCondition` that does the conversion. In the original case of a system returning `bool`, the system is returned as is. With the new types, the system is combined with `map()` to obtain a `bool`. By the way, I'm confused as to why `SystemCondition` has a generic `In` parameter as it is only ever used with `In = ()` as far as I can tell. ## Testing I added a simple test for both type of system. That's minimal but it felt enough. I could not picture the more complicated tests passing for a run condition returning `bool` and failing for the new types. ## Doc I documenting the change on the page of the trait. I had trouble wording it right but I'm not sure how to improve it. The phrasing "the condition returns `true`" which reads naturally is now technically incorrect as the new types return a `Result`. However, the underlying condition system that the implementing system turns into does indeed return `bool`. But talking about the implementation details felt too much. Another possibility is to use another turn of phrase like "the condition holds" or "the condition checks out". I've left "the condition returns `true`" in the documentation of `run_if` and the provided methods for now. I'm perplexed about the examples. In the first one, why not implement the condition directly instead of having a system returning it? Is it from a time of Bevy where you had to implement your conditions that way? In that case maybe that should be updated. And in the second example I'm missing the point entirely. As I stated above, I've only seen conditions used in contexts where they have no input parameter. Here we create a condition with an input parameter (cannot be used by `run_if`) and we are using it with `pipe()` which actually doesn't need our system to implement `SystemCondition`. Both examples are also calling `IntoSystem::into_system` which should not be encouraged. What am I missing?
1 parent 06bb57c commit 0e805eb

File tree

3 files changed

+128
-20
lines changed

3 files changed

+128
-20
lines changed

crates/bevy_ecs/src/schedule/condition.rs

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,18 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
1111

1212
/// A system that determines if one or more scheduled systems should run.
1313
///
14-
/// Implemented for functions and closures that convert into [`System<Out=bool>`](System)
15-
/// with [read-only](crate::system::ReadOnlySystemParam) parameters.
14+
/// `SystemCondition` is sealed and implemented for functions and closures with
15+
/// [read-only](crate::system::ReadOnlySystemParam) parameters that convert into
16+
/// [`System<Out = bool>`](System), [`System<Out = Result<(), BevyError>>`](System) or
17+
/// [`System<Out = Result<bool, BevyError>>`](System).
18+
///
19+
/// `SystemCondition` offers a private method
20+
/// (called by [`run_if`](crate::schedule::IntoScheduleConfigs::run_if) and the provided methods)
21+
/// that converts the implementing system into a condition (system) returning a bool.
22+
/// Depending on the output type of the implementing system:
23+
/// - `bool`: the implementing system is used as the condition;
24+
/// - `Result<(), BevyError>`: the condition returns `true` if and only if the implementing system returns `Ok(())`;
25+
/// - `Result<bool, BevyError>`: the condition returns `true` if and only if the implementing system returns `Ok(true)`.
1626
///
1727
/// # Marker type parameter
1828
///
@@ -31,7 +41,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
3141
/// ```
3242
///
3343
/// # Examples
34-
/// A condition that returns true every other time it's called.
44+
/// A condition that returns `true` every other time it's called.
3545
/// ```
3646
/// # use bevy_ecs::prelude::*;
3747
/// fn every_other_time() -> impl SystemCondition<()> {
@@ -54,7 +64,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
5464
/// # assert!(!world.resource::<DidRun>().0);
5565
/// ```
5666
///
57-
/// A condition that takes a bool as an input and returns it unchanged.
67+
/// A condition that takes a `bool` as an input and returns it unchanged.
5868
///
5969
/// ```
6070
/// # use bevy_ecs::prelude::*;
@@ -71,8 +81,30 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
7181
/// # world.insert_resource(DidRun(false));
7282
/// # app.run(&mut world);
7383
/// # assert!(world.resource::<DidRun>().0);
74-
pub trait SystemCondition<Marker, In: SystemInput = ()>:
75-
sealed::SystemCondition<Marker, In>
84+
/// ```
85+
///
86+
/// A condition returning a `Result<(), BevyError>`
87+
///
88+
/// ```
89+
/// # use bevy_ecs::prelude::*;
90+
/// # #[derive(Component)] struct Player;
91+
/// fn player_exists(q_player: Query<(), With<Player>>) -> Result {
92+
/// Ok(q_player.single()?)
93+
/// }
94+
///
95+
/// # let mut app = Schedule::default();
96+
/// # #[derive(Resource)] struct DidRun(bool);
97+
/// # fn my_system(mut did_run: ResMut<DidRun>) { did_run.0 = true; }
98+
/// app.add_systems(my_system.run_if(player_exists));
99+
/// # let mut world = World::new();
100+
/// # world.insert_resource(DidRun(false));
101+
/// # app.run(&mut world);
102+
/// # assert!(!world.resource::<DidRun>().0);
103+
/// # world.spawn(Player);
104+
/// # app.run(&mut world);
105+
/// # assert!(world.resource::<DidRun>().0);
106+
pub trait SystemCondition<Marker, In: SystemInput = (), Out = bool>:
107+
sealed::SystemCondition<Marker, In, Out>
76108
{
77109
/// Returns a new run condition that only returns `true`
78110
/// if both this one and the passed `and` return `true`.
@@ -371,28 +403,61 @@ pub trait SystemCondition<Marker, In: SystemInput = ()>:
371403
}
372404
}
373405

374-
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In> for F where
375-
F: sealed::SystemCondition<Marker, In>
406+
impl<Marker, In: SystemInput, Out, F> SystemCondition<Marker, In, Out> for F where
407+
F: sealed::SystemCondition<Marker, In, Out>
376408
{
377409
}
378410

379411
mod sealed {
380-
use crate::system::{IntoSystem, ReadOnlySystem, SystemInput};
412+
use crate::{
413+
error::BevyError,
414+
system::{IntoSystem, ReadOnlySystem, SystemInput},
415+
};
381416

382-
pub trait SystemCondition<Marker, In: SystemInput>:
383-
IntoSystem<In, bool, Marker, System = Self::ReadOnlySystem>
417+
pub trait SystemCondition<Marker, In: SystemInput, Out>:
418+
IntoSystem<In, Out, Marker, System = Self::ReadOnlySystem>
384419
{
385420
// This associated type is necessary to let the compiler
386421
// know that `Self::System` is `ReadOnlySystem`.
387-
type ReadOnlySystem: ReadOnlySystem<In = In, Out = bool>;
422+
type ReadOnlySystem: ReadOnlySystem<In = In, Out = Out>;
423+
424+
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool>;
388425
}
389426

390-
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In> for F
427+
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, bool> for F
391428
where
392429
F: IntoSystem<In, bool, Marker>,
393430
F::System: ReadOnlySystem,
394431
{
395432
type ReadOnlySystem = F::System;
433+
434+
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
435+
IntoSystem::into_system(self)
436+
}
437+
}
438+
439+
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, Result<(), BevyError>> for F
440+
where
441+
F: IntoSystem<In, Result<(), BevyError>, Marker>,
442+
F::System: ReadOnlySystem,
443+
{
444+
type ReadOnlySystem = F::System;
445+
446+
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
447+
IntoSystem::into_system(self.map(|result| result.is_ok()))
448+
}
449+
}
450+
451+
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, Result<bool, BevyError>> for F
452+
where
453+
F: IntoSystem<In, Result<bool, BevyError>, Marker>,
454+
F::System: ReadOnlySystem,
455+
{
456+
type ReadOnlySystem = F::System;
457+
458+
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
459+
IntoSystem::into_system(self.map(|result| matches!(result, Ok(true))))
460+
}
396461
}
397462
}
398463

crates/bevy_ecs/src/schedule/config.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use crate::{
1414
system::{BoxedSystem, InfallibleSystemWrapper, IntoSystem, ScheduleSystem, System},
1515
};
1616

17-
fn new_condition<M>(condition: impl SystemCondition<M>) -> BoxedCondition {
18-
let condition_system = IntoSystem::into_system(condition);
17+
fn new_condition<M, Out>(condition: impl SystemCondition<M, (), Out>) -> BoxedCondition {
18+
let condition_system = condition.into_condition_system();
1919
assert!(
2020
condition_system.is_send(),
2121
"SystemCondition `{}` accesses `NonSend` resources. This is not currently supported.",
@@ -447,7 +447,7 @@ pub trait IntoScheduleConfigs<T: Schedulable<Metadata = GraphInfo, GroupMetadata
447447
///
448448
/// Use [`distributive_run_if`](IntoScheduleConfigs::distributive_run_if) if you want the
449449
/// condition to be evaluated for each individual system, right before one is run.
450-
fn run_if<M>(self, condition: impl SystemCondition<M>) -> ScheduleConfigs<T> {
450+
fn run_if<M, Out>(self, condition: impl SystemCondition<M, (), Out>) -> ScheduleConfigs<T> {
451451
self.into_configs().run_if(condition)
452452
}
453453

@@ -535,7 +535,7 @@ impl<T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>> IntoScheduleCo
535535
self
536536
}
537537

538-
fn run_if<M>(mut self, condition: impl SystemCondition<M>) -> ScheduleConfigs<T> {
538+
fn run_if<M, Out>(mut self, condition: impl SystemCondition<M, (), Out>) -> ScheduleConfigs<T> {
539539
self.run_if_dyn(new_condition(condition));
540540
self
541541
}

crates/bevy_ecs/src/schedule/mod.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ mod tests {
2929
use alloc::{string::ToString, vec, vec::Vec};
3030
use core::sync::atomic::{AtomicU32, Ordering};
3131

32+
use crate::error::BevyError;
3233
pub use crate::{
3334
prelude::World,
3435
resource::Resource,
@@ -49,10 +50,10 @@ mod tests {
4950
struct SystemOrder(Vec<u32>);
5051

5152
#[derive(Resource, Default)]
52-
struct RunConditionBool(pub bool);
53+
struct RunConditionBool(bool);
5354

5455
#[derive(Resource, Default)]
55-
struct Counter(pub AtomicU32);
56+
struct Counter(AtomicU32);
5657

5758
fn make_exclusive_system(tag: u32) -> impl FnMut(&mut World) {
5859
move |world| world.resource_mut::<SystemOrder>().0.push(tag)
@@ -252,12 +253,13 @@ mod tests {
252253
}
253254

254255
mod conditions {
256+
255257
use crate::change_detection::DetectChanges;
256258

257259
use super::*;
258260

259261
#[test]
260-
fn system_with_condition() {
262+
fn system_with_condition_bool() {
261263
let mut world = World::default();
262264
let mut schedule = Schedule::default();
263265

@@ -276,6 +278,47 @@ mod tests {
276278
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
277279
}
278280

281+
#[test]
282+
fn system_with_condition_result_unit() {
283+
let mut world = World::default();
284+
let mut schedule = Schedule::default();
285+
286+
world.init_resource::<SystemOrder>();
287+
288+
schedule.add_systems(
289+
make_function_system(0).run_if(|| Err::<(), BevyError>(core::fmt::Error.into())),
290+
);
291+
292+
schedule.run(&mut world);
293+
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
294+
295+
schedule.add_systems(make_function_system(1).run_if(|| Ok(())));
296+
297+
schedule.run(&mut world);
298+
assert_eq!(world.resource::<SystemOrder>().0, vec![1]);
299+
}
300+
301+
#[test]
302+
fn system_with_condition_result_bool() {
303+
let mut world = World::default();
304+
let mut schedule = Schedule::default();
305+
306+
world.init_resource::<SystemOrder>();
307+
308+
schedule.add_systems((
309+
make_function_system(0).run_if(|| Err::<bool, BevyError>(core::fmt::Error.into())),
310+
make_function_system(1).run_if(|| Ok(false)),
311+
));
312+
313+
schedule.run(&mut world);
314+
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
315+
316+
schedule.add_systems(make_function_system(2).run_if(|| Ok(true)));
317+
318+
schedule.run(&mut world);
319+
assert_eq!(world.resource::<SystemOrder>().0, vec![2]);
320+
}
321+
279322
#[test]
280323
fn systems_with_distributive_condition() {
281324
let mut world = World::default();

0 commit comments

Comments
 (0)