Make initial StateTransition run before PreStartup (#14208)

# 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`.
This commit is contained in:
MiniaczQ 2024-07-15 17:08:54 +02:00 committed by François
parent 7f3fea9a5b
commit 524fb01457
No known key found for this signature in database
3 changed files with 16 additions and 34 deletions

View file

@ -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::{
@ -215,7 +211,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());
}
}

View file

@ -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;
@ -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);
@ -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);
@ -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);
@ -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);
@ -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();
@ -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>()
@ -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);
@ -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>();
@ -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);
@ -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);
@ -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>>(

View file

@ -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,
};
@ -181,10 +179,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;
@ -200,12 +195,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.