Skip to content

Impl system condition combinators for Result-ful run conditions #19580

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

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

gwafotapa
Copy link
Contributor

Objective

This is a follow-up of #19553.
I missed something. Right now the combinators (and, or, ...) only work with conditions returning bool. The same goes for condition_changed, condition_changed_to and not. The objective is to fix that to allow conditions returning Result<(), BevyError> and Result<bool, BevyError> to use these methods as well.

Solution

It's essentially just about calling into_condition_system() in the methods. I just have a problem for not because it's implemented differently than the others. Currently not can accept any system whose output implement Not. That's a problem for systems returning Result<(), BevyError> or Result<bool, BevyError>. I see mostly two options:

  1. Leave things as they are and the new conditions cannot be used with not.
  2. Limit not to accept only run conditions. This way not could accept the new run conditions but not any system whose output implements Not.

I would say the second solution has more upsides than downsides because

  • Use cases of not with Not feels more niche than use cases of not with the new conditions
  • It is more coherent (being able to use the new run conditions with run_if but not not is confusing)
  • To invert a system whose output implements Not you can still do system.map(|x| !x)

but I may be missing something. What do you think ?

Testing

I also added a simple test for all the missing methods.

@alice-i-cecile alice-i-cecile changed the title Impl system condition Impl system condition combinators for Result-ful run conditions Jun 11, 2025
@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 11, 2025
@alice-i-cecile
Copy link
Member

@urben1680 want to review this one too?

@urben1680
Copy link
Contributor

I think into_condition_system is the right approach, it aligns into how bevy turns fallible and infallible systems into the same output (Result<(), BevyError>) via the IntoScheduleConfigs impl.

Regarding the Not case I also favor the second solution. It should be consistent.

When this leaves Draft I do a proper review. 👍

@gwafotapa gwafotapa marked this pull request as ready for review June 12, 2025 14:33
@gwafotapa
Copy link
Contributor Author

@urben1680 PR is ready for review.

Copy link
Contributor

@urben1680 urben1680 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would only like to see a change about the not return type becoming anonymous, the other things do not bother me enough. 😄

Comment on lines 1077 to 1085
pub fn not<Marker, In, Out, C>(
condition: C,
) -> AdapterSystem<fn(bool) -> bool, C::ConditionSystem>
where
TOut: core::ops::Not,
T: IntoSystem<(), TOut, Marker>,
In: SystemInput,
C: SystemCondition<Marker, In, Out>,
{
let condition = IntoSystem::into_system(condition);
let name = format!("!{}", condition.name());
NotSystem::new(super::NotMarker, condition, name.into())
let f: fn(bool) -> bool = |x| !x;
IntoSystem::into_system(condition.into_condition_system().map(f))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can return an impl SystemCondition<(), In> here so you don't need to turn that mapping function into a function pointer which requires slower dynamic dispatch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning impl SystemCondition<(), In> caused the compiler to complain about a missing Clone bound. I finally went another way. Using the approach in NotSystem of defining a helper type, I removed the use of a function pointer. I also reworked on the implementations of SystemCondition to remove function pointers there as well.

Comment on lines -1161 to -1164
/// Invokes [`Not`] with the output of another system.
///
/// See [`common_conditions::not`] for examples.
pub type NotSystem<S> = AdapterSystem<NotMarker, S>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why this explicit type was needed and why it needed to be public when all other methods returned impl ....

I think there are two alternatives here:

  1. Write a migration guide that this type alias is gone.
  2. Stick to the explicit type returned by not and change this to this: (maybe with a #[deprecated] as it is unused in the engine now)
type NotSystem<T, In = (), Out = bool> = AdapterSystem<fn(bool) -> bool, <T as SystemCondition<(), In, Out>>::ConditionSystem>;

I strongly prefer 1, also because I think 2 does not technically resemble the original NotSystem alias depending on how exotic the system output were with that Not impl. I am not even sure 2. works.

But I am fine with not do anything here as I assume nobody used this alias.

Copy link
Contributor Author

@gwafotapa gwafotapa Jun 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why this was needed either. I wrote the migration guide. I also removed a line in the documentation example of trait Adapt.

@@ -1214,7 +1232,7 @@ where
fn combine(
input: <Self::In as SystemInput>::Inner<'_>,
a: impl FnOnce(SystemIn<'_, A>) -> A::Out,
b: impl FnOnce(SystemIn<'_, A>) -> B::Out,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, was this ever working? If not, was this ever used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, this was only ever used in conjunction with run_if which only accepts a system with In = (). So this never came into play as this is always called with In = () for both systems A and B. But this might change in the future so we may as well correct it now.

Comment on lines +1484 to +1485
counter *= 3 * 7 * 13 * 17 * 23 * 29;
assert_eq!(world.resource::<Counter>().0, counter);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are ridiculous because who reads them must first understand that these primes can only multiply to the correct value if the expected conditions ran. But all previous tests here look like that so I guess it is fine. I just wanted to say it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. I thought the intent was clear and it is concise this way.

Comment on lines 457 to 458
// This associated type is necessary to let the compiler
// know that `Self::System` is `ReadOnlySystem`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this comment needs to be added on every impl. You can also make it a proper doc of this associated type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. That's better. I'll make a proper doc and add one as well for the other associated type.

@urben1680
Copy link
Contributor

This PR seems to fix #19527

@gwafotapa
Copy link
Contributor Author

gwafotapa commented Jun 14, 2025

This PR seems to fix #19527

Indeed it does. Assuming @lotus128 was using it with a system returning a bool, not whose output required Not.

@@ -16,7 +16,6 @@ use crate::{
/// use bevy_ecs::system::{Adapt, AdapterSystem};
///
/// // A system adapter that inverts the result of a system.
/// // NOTE: Instead of manually implementing this, you can just use `bevy_ecs::schedule::common_conditions::not`.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not() cannot accept any system whose output is Not anymore so this is not a manual implementation of it anymore.

/// Used with [`AdapterSystem`] to inverse the `bool` returned by the system.
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct InverseBoolAdapter;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This used to be NotMarker. It's now outputting a bool instead of a Not. Thought the name bad.

@alice-i-cecile alice-i-cecile added this to the 0.17 milestone Jun 14, 2025
@chescock
Copy link
Contributor

Note that #19145 would address the same issue with an incompatible approach, by making all systems return Result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use S-Needs-Review Needs reviewer attention (from anyone!) to move forward
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants