bevy icon indicating copy to clipboard operation
bevy copied to clipboard

FixedTimeStep systems do not honor their associated app state

Open derchr opened this issue 2 years ago • 5 comments

Bevy version

0.10

What you did

Consider the following Bevy app:

use bevy::prelude::*;

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

fn main() {
    App::new()
        .add_plugins(MinimalPlugins)
        .insert_resource(FixedTime::new_from_secs(1.0))
        .add_state::<AppState>()
        .add_system(transition_state.in_set(OnUpdate(AppState::First)))
        .add_system(
            print_hello
                .in_schedule(CoreSchedule::FixedUpdate)
                .in_set(OnUpdate(AppState::Second)),
        )
        .run();
}

fn transition_state(time: Res<Time>, mut next_state: ResMut<NextState<AppState>>) {
    if time.elapsed_seconds() > 3.0 {
        println!("Transition state!");
        next_state.set(AppState::Second);
    }
}

fn print_hello() {
    println!("Hello!");
}

What went wrong

The print_hello system runs regardless of the application state. It should only run in the AppState::Second state but it runs also in the AppState::First state. The following output is generated:

Hello!
Hello!
Transition state!
Hello!
Hello!

If line the line with .in_schedule(CoreSchedule::FixedUpdate) is commented out, the output is:

Transition state!
Hello!
Hello!
Hello!

It's as expected. But of course the system is now run every frame.

derchr avatar Mar 12 '23 17:03 derchr

Does this work if you use a .run_if(in_state(AppState::Second)) instead? OnUpdate was really only ever intended for the main schedule.

alice-i-cecile avatar Mar 12 '23 17:03 alice-i-cecile

Does this work if you use a .run_if(in_state(AppState::Second)) instead?

Yes, then it works. Thanks!

derchr avatar Mar 12 '23 18:03 derchr

@alice-i-cecile I'm trying to run a set of systems only when the game is in a certain state, and with a fixed timestep.

First I tried this:

app.add_systems(
    (
        system_a,
        system_b,
        system_c,
    )
    .in_set(labels::Lobby)
    .after(labels::Network)
    .in_set(OnUpdate(GameState::Lobby))
    .in_schedule(CoreSchedule::FixedUpdate)
)

which led me to this issue.

Then I tried

app.add_systems(
    (
        system_a,
        system_b,
        system_c,
    )
    .in_set(labels::Lobby)
    .after(labels::Network)
    .run_if(in_state(GameState::Lobby))
    .in_schedule(CoreSchedule::FixedUpdate)
)

Which gave me some unsatisfied trait bounds:

358 | pub struct SystemConfigs {
    | ------------------------
    | |
    | doesn't satisfy `SystemConfigs: IntoSystem<(), (), _>`
    | doesn't satisfy `SystemConfigs: IntoSystemConfig<_>`
    |
    = note: the following trait bounds were not satisfied:
            `SystemConfigs: IntoSystem<(), (), _>`
            which is required by `SystemConfigs: IntoSystemConfig<_>`
            `&SystemConfigs: IntoSystem<(), (), _>`
            which is required by `&SystemConfigs: IntoSystemConfig<_>`
            `&mut SystemConfigs: IntoSystem<(), (), _>`
            which is required by `&mut SystemConfigs: IntoSystemConfig<_>`

I found distributive_run_if() and tried that:

app.add_systems(
    (
        system_a,
        system_b,
        system_c,
    )
    .in_set(labels::Lobby)
    .after(labels::Network)
    .distributive_run_if(in_state(GameState::Lobby))
    .in_schedule(CoreSchedule::FixedUpdate)
)

which gave:

error[E0277]: the trait bound `impl for<'a> FnMut(Res<'a, State<GameState>>) -> bool: Clone` is not satisfied
   --> server/src/systems/lobby.rs:59:38
    |
59  |                 .distributive_run_if(in_state(GameState::Lobby))
    |                  ------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `impl for<'a> FnMut(Res<'a, State<GameState>>) -> bool`
    |                  |
    |                  required by a bound introduced by this call
    |
note: required by a bound in `distributive_run_if`
   --> /Users/brian/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_ecs-0.10.0/src/schedule/config.rs:425:68
    |
425 |     fn distributive_run_if<M>(self, condition: impl Condition<M> + Clone) -> SystemConfigs {
    |                                                                    ^^^^^ required by this bound in `distributive_run_if`
help: use parentheses to call this opaque type
    |
59  |                 .distributive_run_if(in_state(GameState::Lobby)(/* Res<'_, State<GameState>> */))
    |                                                                +++++++++++++++++++++++++++++++++

For more information about this error, try `rustc --explain E0277`.
error: could not compile `sus-server` due to previous error

At this point I'm not sure of the best way to specify that a set of systems should run on a fixed timestep, and only while in a certain state.

If the answer is currently "add the run_if condition on every system in the set", that's totally fine. Just wanted to make sure I wasn't missing something else that'll solve the problem.

Thanks so much for your work on the new stageless API, it's been really cool to see my bevy code shrink in size while remaining functionally the same (along with gaining new ergonomics!)

bschwind avatar Mar 13 '23 14:03 bschwind

@bschwind There is an issue about that https://github.com/bevyengine/bevy/issues/8058. Before it fixed, you can use:

distributive_run_if(|state: Res<State<GameState>>>| *state == GameState::Lobby)

st0rmbtw avatar Mar 13 '23 15:03 st0rmbtw

@st0rmbtw nice! I changed *state to state.0 and it worked perfectly.

bschwind avatar Mar 13 '23 15:03 bschwind

@alice-i-cecile can this be closed now that https://github.com/bevyengine/bevy/pull/8260 has been merged?

hymm avatar Apr 16 '23 02:04 hymm