Skip to content

Fix SubStates with multiple source states not reacting to all source changes #19595

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 4 commits into
base: main
Choose a base branch
from

Conversation

mgi388
Copy link
Contributor

@mgi388 mgi388 commented Jun 12, 2025

Objective

  • Fix issue where SubStates depending on multiple source states would only react when all source states changed simultaneously.
  • SubStates should be created/destroyed whenever any of their source states transitions, not only when all change together.

Solution

fn register_sub_state_systems_in_schedule<T: SubStates<SourceStates = Self>>(schedule: &mut Schedule) {
  let apply_state_transition = |(mut ereader0, mut ereader1, mut ereader2): (
      EventReader<StateTransitionEvent<S0::RawState>>,
      EventReader<StateTransitionEvent<S1::RawState>>,
      EventReader<StateTransitionEvent<S2::RawState>>,
  ),
      event: EventWriter<StateTransitionEvent<T>>,
      commands: Commands,
      current_state_res: Option<ResMut<State<T>>>,
      next_state_res: Option<ResMut<NextState<T>>>,
      (s0, s1, s2): (
          Option<Res<State<S0::RawState>>>,
          Option<Res<State<S1::RawState>>>,
          Option<Res<State<S2::RawState>>>,
  )| {
    // With `||` we can correctly count parent changed if any of the sources changed.
    let parent_changed = (ereader0.read().last().is_some()
        || ereader1.read().last().is_some()
        || ereader2.read().last().is_some());
    let next_state = take_next_state(next_state_res);
    if !parent_changed && next_state.is_none() {
        return;
    }
    // ...
  }
}

Testing

  • Add new test.
  • Check the fix worked in my game.

…changes

- Fix issue where `SubStates` depending on multiple source states
  would only react when _all_ source states changed simultaneously.
- SubStates should be created/destroyed whenever _any_ of their
  source states transitions, not only when all change together.

- Changed the "did parent change" detection logic from AND to OR.
  We need to check if _any_ of the event readers changed, not if
  _all_ of them changed.

- Add new test.
@mgi388 mgi388 added A-States App-level states machines C-Bug An unexpected or incorrect behavior D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 12, 2025
@alice-i-cecile alice-i-cecile added this to the 0.16.2 milestone Jun 12, 2025
@mgi388 mgi388 requested a review from MiniaczQ June 12, 2025 12:41
@mgi388
Copy link
Contributor Author

mgi388 commented Jun 12, 2025

@benfrankel @MiniaczQ care to review?

Copy link
Contributor

@benfrankel benfrankel left a comment

Choose a reason for hiding this comment

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

This fix seems correct. I'm curious why the computed states version checks event_reader.is_empty() && ... while the sub states version checks !(event_reader.read().last().is_some() || ...), though. It seems like they're trying to do the same thing.

@mgi388
Copy link
Contributor Author

mgi388 commented Jun 13, 2025

@benfrankel

This fix seems correct. I'm curious why the computed states version checks event_reader.is_empty() && ... while the sub states version checks !(event_reader.read().last().is_some() || ...), though. It seems like they're trying to do the same thing.

Yeah I am unsure as well. As you say, the docs for ComputedStates say:

This function gets called whenever one of the SourceStates changes.

And the same for SubStates:

This function gets called whenever one of the SourceStates changes.

I added a new similar test ComputedStates, doing the same thing, and it works with the existing implementation.

I'm inclined to not try and change the internals of either though. The two new tests prove the expected behavior and so theoretically they can be refactored in the future as long as the tests still pass.

@mgi388 mgi388 added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-States App-level states machines C-Bug An unexpected or incorrect behavior D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants