Files
Joseph 12952486ef fix: improve semantic clarity for run condition combinators (#22690)
# Objective

We include several run condition combinators, such as `and`, `or`, etc.,
which short-circuit depending on the output of the first condition in
the combinator.

This is incredibly error-prone due to the subtle way that
short-circuiting interacts with change detection -- rather than reacting
to changes frame-by-frame, the second condition in short-circuiting
combinator will react to _the last time that the first condition did not
short circuit_. This can easily lead to confusing bugs if the user does
not expect this, and I suspect that most users will not expect this.
For this reason, when combining multiple run conditions added via
`.run_if()`, all run conditions are intentionally eagerly evaluated.

## Solution

Add new run condition combinators `and_then`, `and_eager`, `or_else`,
`or_eager`, etc., for clarity, and deprecate the previous methods,
pointing users to the new ones.

After the previous combinators have been removed for a few release
cycles, we should consider renaming combinators such as `and_eager` to
simply `and`.

# Migration Guide

Bevy supports run condition combinators (`and`, `or`, `nan`, `nor`),
which have historically short-circuited. While familiar,
short-circuiting interacts with Bevy’s change detection in a subtle way:
when the left-hand condition short-circuits, the right-hand condition is
not evaluated and therefore does not observe changes on that frame.
Instead, it reacts based on the last frame it ran, which can lead to
confusing and non-local bugs.

By contrast, Bevy's scheduler combines multiple .run_if(...) conditions
using eager evaluation, which avoids this known pitfall.

To make intent explicit and reduce footguns, short-circuiting
combinators have been renamed and eagerly-evaluated variants have been
added.

## Examples

Most users should use eager evaluation, which ensures all conditions
participate in change detection every frame:

```rust
// Before (deprecated)
cond_a.and(cond_b)
cond_a.or(cond_b)
cond_a.nand(cond_b)
cond_a.nor(cond_b)

// After (recommended default)
cond_a.and_eager(cond_b)
cond_a.or_eager(cond_b)
cond_a.nand_eager(cond_b)
cond_a.nor_eager(cond_b)
```

If you *intentionally rely on short-circuiting* for correctness, use the
explicit short-circuiting variants:

```rust
// Explicit short-circuiting
cond_a.and_then(cond_b)
cond_a.or_else(cond_b)
cond_a.nand_then(cond_b)
cond_a.nor_else(cond_b)
```

`xor` and `xnor` are unchanged, as they cannot short-circuit by nature.

## Future naming note

The `_eager` suffix exists to ease migration without changing the
behavior of existing code that relied on short-circuiting. After the
deprecated combinators have been removed for a few release cycles, we
expect to revisit naming and likely remove the _eager suffix, keeping
`and_then` / `or_else` as the explicit short-circuiting forms.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Mike <mike.hsu@gmail.com>
2026-02-17 00:13:33 +00:00
..