-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Conversation
Result
-ful run conditions
@urben1680 want to review this one too? |
I think Regarding the When this leaves Draft I do a proper review. 👍 |
@urben1680 PR is ready for review. |
There was a problem hiding this 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. 😄
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)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
/// Invokes [`Not`] with the output of another system. | ||
/// | ||
/// See [`common_conditions::not`] for examples. | ||
pub type NotSystem<S> = AdapterSystem<NotMarker, S>; |
There was a problem hiding this comment.
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:
- Write a migration guide that this type alias is gone.
- 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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
counter *= 3 * 7 * 13 * 17 * 23 * 29; | ||
assert_eq!(world.resource::<Counter>().0, counter); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
This PR seems to fix #19527 |
…led::SystemCondition
…on' into impl_system_condition
@@ -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`. |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
Co-authored-by: urben1680 <[email protected]>
Note that #19145 would address the same issue with an incompatible approach, by making all systems return |
# Objective Allow combinator and pipe systems to delay validation of the second system, while still allowing the second system to be skipped. Fixes #18796 Allow fallible systems to be used as one-shot systems, reporting errors to the error handler when used through commands. Fixes #19722 Allow fallible systems to be used as run conditions, including when used with combinators. Alternative to #19580. Always validate parameters when calling the safe `run_without_applying_deferred`, `run`, and `run_readonly` methods on a `System`. ## Solution Have `System::run_unsafe` return a `Result`. We want pipe systems to run the first system before validating the second, since the first system may affect whether the second system has valid parameters. But if the second system skips then we have no output value to return! So, pipe systems must return a `Result` that indicates whether the second system ran. But if we just make pipe systems have `Out = Result<B::Out>`, then chaining `a.pipe(b).pipe(c)` becomes difficult. `c` would need to accept the `Result` from `a.pipe(b)`, which means it would likely need to return `Result` itself, giving `Result<Result<Out>>`! Instead, we make *all* systems return a `Result`! We move the handling of fallible systems from `IntoScheduleConfigs` and `IntoObserverSystem` to `SystemParamFunction` and `ExclusiveSystemParamFunction`, so that an infallible system can be wrapped before being passed to a combinator. As a side effect, this enables fallible systems to be used as run conditions and one-shot systems. Now that the safe `run_without_applying_deferred`, `run`, and `run_readonly` methods return a `Result`, we can have them perform parameter validation themselves instead of requiring each caller to remember to call them. `run_unsafe` will continue to not validate parameters, since it is used in the multi-threaded executor when we want to validate and run in separate tasks. Note that this makes type inference a little more brittle. A function that returns `Result<T>` can be considered either a fallible system returning `T` or an infallible system returning `Result<T>` (and this is important to continue supporting `pipe`-based error handling)! So there are some cases where the output type of a system can no longer be inferred. It will work fine when directly adding to a schedule, since then the output type is fixed to `()` (or `bool` for run conditions). And it will work fine when `pipe`ing to a system with a typed input parameter. I used a dedicated `RunSystemError` for the error type instead of plain `BevyError` so that skipping a system does not box an error or capture a backtrace.
@gwafotapa, #19145 has been merged. Is this still needed? I'm removing it from the 0.17 milestone, as this does not appear to be critical and may be obsolete. |
@alice-i-cecile Not anymore. |
Objective
This is a follow-up of #19553.
I missed something. Right now the combinators (
and
,or
, ...) only work with conditions returningbool
. The same goes forcondition_changed
,condition_changed_to
andnot
. The objective is to fix that to allow conditions returningResult<(), BevyError>
andResult<bool, BevyError>
to use these methods as well.Fixes #19527.
Solution
It's essentially just about calling
into_condition_system()
in the methods. I just have a problem fornot
because it's implemented differently than the others. Currentlynot
can accept any system whose output implementNot
. That's a problem for systems returningResult<(), BevyError>
orResult<bool, BevyError>
. I see mostly two options:not
.not
to accept only run conditions. This waynot
could accept the new run conditions but not any system whose output implementsNot
.I would say the second solution has more upsides than downsides because
not
withNot
feels more niche than use cases ofnot
with the new conditionsrun_if
but notnot
is confusing)Not
you can still dosystem.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.