Skip to content

Commit

Permalink
Make initial StateTransition run before PreStartup (#14208)
Browse files Browse the repository at this point in the history
# Objective

- Fixes #14206 

## Solution

- Run initial `StateTransition` as a startup schedule before
`PreStartup`, instead of running it inside `Startup` as an exclusive
system.

Related discord discussion:

https://discord.com/channels/691052431525675048/692572690833473578/1259543775668207678

## Testing

Reproduction now works correctly:

```rs
use bevy::prelude::*;

#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
enum AppState {
    #[default]
    Menu,
    InGame,
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_state::<AppState>()
        .add_systems(Startup, setup)
        .add_systems(OnEnter(AppState::Menu), enter_menu_state)
        .run();
}

fn setup(mut next_state: ResMut<NextState<AppState>>) {
    next_state.set(AppState::Menu);
}

fn enter_menu_state() {
    println!("Entered menu state");
}
```


![image](https://github.com/bevyengine/bevy/assets/13040204/96d7a533-c439-4c0b-8f15-49f620903ce1)


---

## Changelog

- Initial `StateTransition` runs before `PreStartup` instead of inside
`Startup`.
  • Loading branch information
MiniaczQ authored Jul 15, 2024
1 parent 6e8d43a commit bc72ced
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 34 deletions.
11 changes: 4 additions & 7 deletions crates/bevy_state/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
use bevy_app::{App, MainScheduleOrder, Plugin, PreUpdate, Startup, SubApp};
use bevy_ecs::{
event::Events,
schedule::{IntoSystemConfigs, ScheduleLabel},
world::FromWorld,
};
use bevy_app::{App, MainScheduleOrder, Plugin, PreStartup, PreUpdate, SubApp};
use bevy_ecs::{event::Events, schedule::IntoSystemConfigs, world::FromWorld};
use bevy_utils::{tracing::warn, warn_once};

use crate::state::{
Expand Down Expand Up @@ -221,7 +217,8 @@ impl Plugin for StatesPlugin {
fn build(&self, app: &mut App) {
let mut schedule = app.world_mut().resource_mut::<MainScheduleOrder>();
schedule.insert_after(PreUpdate, StateTransition);
setup_state_transitions_in_world(app.world_mut(), Some(Startup.intern()));
schedule.insert_startup_before(PreStartup, StateTransition);
setup_state_transitions_in_world(app.world_mut());
}
}

Expand Down
24 changes: 10 additions & 14 deletions crates/bevy_state/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pub use transitions::*;
mod tests {
use bevy_ecs::event::EventRegistry;
use bevy_ecs::prelude::*;
use bevy_ecs::schedule::ScheduleLabel;
use bevy_state_macros::States;
use bevy_state_macros::SubStates;

Expand Down Expand Up @@ -64,7 +63,7 @@ mod tests {

world.insert_resource(schedules);

setup_state_transitions_in_world(&mut world, None);
setup_state_transitions_in_world(&mut world);

world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
Expand Down Expand Up @@ -120,7 +119,7 @@ mod tests {

world.insert_resource(schedules);

setup_state_transitions_in_world(&mut world, None);
setup_state_transitions_in_world(&mut world);

world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
Expand Down Expand Up @@ -180,7 +179,7 @@ mod tests {

world.insert_resource(schedules);

setup_state_transitions_in_world(&mut world, None);
setup_state_transitions_in_world(&mut world);

world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
Expand Down Expand Up @@ -275,7 +274,7 @@ mod tests {

world.insert_resource(schedules);

setup_state_transitions_in_world(&mut world, None);
setup_state_transitions_in_world(&mut world);

world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
Expand Down Expand Up @@ -354,9 +353,6 @@ mod tests {
}
}

#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
struct Startup;

#[test]
fn computed_state_transitions_are_produced_correctly() {
let mut world = World::new();
Expand All @@ -367,7 +363,7 @@ mod tests {
world.init_resource::<State<SimpleState2>>();
world.init_resource::<Schedules>();

setup_state_transitions_in_world(&mut world, Some(Startup.intern()));
setup_state_transitions_in_world(&mut world);

let mut schedules = world
.get_resource_mut::<Schedules>()
Expand Down Expand Up @@ -431,7 +427,7 @@ mod tests {

world.init_resource::<ComputedStateTransitionCounter>();

setup_state_transitions_in_world(&mut world, None);
setup_state_transitions_in_world(&mut world);

assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
Expand Down Expand Up @@ -508,7 +504,7 @@ mod tests {
#[test]
fn same_state_transition_should_emit_event_and_not_run_schedules() {
let mut world = World::new();
setup_state_transitions_in_world(&mut world, None);
setup_state_transitions_in_world(&mut world);
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
world.init_resource::<State<SimpleState>>();
let mut schedules = world.resource_mut::<Schedules>();
Expand Down Expand Up @@ -568,7 +564,7 @@ mod tests {
SubState::register_sub_state_systems(&mut apply_changes);
schedules.insert(apply_changes);
world.insert_resource(schedules);
setup_state_transitions_in_world(&mut world, None);
setup_state_transitions_in_world(&mut world);

world.insert_resource(NextState::Pending(SimpleState::B(true)));
world.run_schedule(StateTransition);
Expand Down Expand Up @@ -599,7 +595,7 @@ mod tests {
TestComputedState::register_computed_state_systems(&mut apply_changes);
schedules.insert(apply_changes);
world.insert_resource(schedules);
setup_state_transitions_in_world(&mut world, None);
setup_state_transitions_in_world(&mut world);

world.insert_resource(NextState::Pending(SimpleState::B(true)));
world.run_schedule(StateTransition);
Expand Down Expand Up @@ -651,7 +647,7 @@ mod tests {
#[test]
fn check_transition_orders() {
let mut world = World::new();
setup_state_transitions_in_world(&mut world, None);
setup_state_transitions_in_world(&mut world);
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<SubState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<TransitionTestingComputedState>>(
Expand Down
15 changes: 2 additions & 13 deletions crates/bevy_state/src/state/transitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ use std::{marker::PhantomData, mem};

use bevy_ecs::{
event::{Event, EventReader, EventWriter},
schedule::{
InternedScheduleLabel, IntoSystemSetConfigs, Schedule, ScheduleLabel, Schedules, SystemSet,
},
schedule::{IntoSystemSetConfigs, Schedule, ScheduleLabel, Schedules, SystemSet},
system::{Commands, In, ResMut},
world::World,
};
Expand Down Expand Up @@ -182,10 +180,7 @@ pub(crate) fn internal_apply_state_transition<S: States>(
///
/// Runs automatically when using `App` to insert states, but needs to
/// be added manually in other situations.
pub fn setup_state_transitions_in_world(
world: &mut World,
startup_label: Option<InternedScheduleLabel>,
) {
pub fn setup_state_transitions_in_world(world: &mut World) {
let mut schedules = world.get_resource_or_insert_with(Schedules::default);
if schedules.contains(StateTransition) {
return;
Expand All @@ -201,12 +196,6 @@ pub fn setup_state_transitions_in_world(
.chain(),
);
schedules.insert(schedule);

if let Some(startup) = startup_label {
schedules.add_systems(startup, |world: &mut World| {
let _ = world.try_run_schedule(StateTransition);
});
}
}

/// Returns the latest state transition event of type `S`, if any are available.
Expand Down

0 comments on commit bc72ced

Please sign in to comment.