Skip to content

Unexpected behavior using States and Events #18428

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
TheGrungringMachine opened this issue Mar 19, 2025 · 1 comment
Open

Unexpected behavior using States and Events #18428

TheGrungringMachine opened this issue Mar 19, 2025 · 1 comment
Labels
A-ECS Entities, components, systems, and events A-States App-level states machines C-Bug An unexpected or incorrect behavior S-Needs-Investigation This issue requires detective work to figure out what's going wrong

Comments

@TheGrungringMachine
Copy link

Bevy version

0.15.3

What you did

use bevy::prelude::*;

#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
pub enum Phases {
    #[default]
    None,
    Phase1,
    Phase2,
}

#[derive(Event, Debug, Default)]
pub struct AnEvent;

#[derive(Resource, Debug)]
struct ChangeDirectionTimer {
    timer: Timer,
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_state::<Phases>()
        .add_event::<AnEvent>()
        .insert_resource(ChangeDirectionTimer {
                timer: Timer::from_seconds(1., TimerMode::Once),
            })
        .add_systems(Startup, start)
        .add_systems(OnEnter(Phases::Phase1), send_event1)
        .add_systems(Update, end_phase1
            .run_if(in_state(Phases::Phase1).and(on_event::<AnEvent>)))
        .add_systems(OnEnter(Phases::Phase2), send_event2)
        .add_systems(Update, end_phase2
            .run_if(in_state(Phases::Phase2).and(on_event::<AnEvent>)))
        .run();
}

fn start(mut next_state: ResMut<NextState<Phases>>) {
    next_state.set(Phases::Phase1);
}

fn send_event1(
    mut event: EventWriter<AnEvent>,
) {
    println!("Sending event 1");
    event.send_default();
}

fn end_phase1(
    mut event: EventReader<AnEvent>,
    mut next_state: ResMut<NextState<Phases>>,
) {
    for _ in event.read() {
        println!("Ending phase 1");
        next_state.set(Phases::Phase2);
    }
}

fn send_event2(
    mut event: EventWriter<AnEvent>,
    time: Res<Time>,
    mut timer: ResMut<ChangeDirectionTimer>,
) {
    timer.timer.tick(time.delta());
    if timer.timer.just_finished() {
        println!("Sending event 2");
        event.send_default();
    }
}

fn end_phase2(
    mut event: EventReader<AnEvent>,
    mut next_state: ResMut<NextState<Phases>>,
) {
    for _ in event.read() {
        println!("Ending phase 2");
        next_state.set(Phases::None);
    }
}

What went wrong

The output of this code is:

Sending event 1
Ending phase 1
Ending phase 2

As the system end_phase2 only runs in the Phase2 state, it should be triggered by the delayed event 2, but it's like it is triggered still by the first event.

This is confirmed by commenting out the if statement in send_event2, which results in:

Sending event 1
Ending phase 1
Sending event 2
Ending phase 2
Ending phase 2

So end_phase2 is actually triggered twice despite the first event is triggered in another state with respect to the one during which end_phase2 should be running.

Additional information

Am I missing something or is it a bug?

Thanks!

@TheGrungringMachine TheGrungringMachine added C-Bug An unexpected or incorrect behavior S-Needs-Triage This issue needs to be labelled labels Mar 19, 2025
@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events S-Needs-Investigation This issue requires detective work to figure out what's going wrong A-States App-level states machines and removed S-Needs-Triage This issue needs to be labelled labels Mar 19, 2025
@gigamicro
Copy link

gigamicro commented Mar 27, 2025

  1. ChangeDirectionTimer is only ticked once per event in Phase2
  2. in_state(Phases::Phase2).and(on_event::<AnEvent>) shortcircuits, thus never clearing the AnEvent in on_event
    fix 2. by swapping the on_event and in_state conditions, but imo the better solution is .add_systems(Update, (end_phase1.run_if(in_state(Phases::Phase1)), end_phase2.run_if(in_state(Phases::Phase2))).run_if(on_event::<AnEvent>))
    fix 1. by putting an unconditional ticker on it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events A-States App-level states machines C-Bug An unexpected or incorrect behavior S-Needs-Investigation This issue requires detective work to figure out what's going wrong
Projects
None yet
Development

No branches or pull requests

3 participants