mvlabat / bevy_egui

This crate provides an Egui integration for the Bevy game engine. 🇺🇦 Please support the Ukrainian army: https://savelife.in.ua/en/
MIT License
895 stars 240 forks source link

Clicks lost in reactive mode #302

Open Azorlogh opened 3 weeks ago

Azorlogh commented 3 weeks ago

Button clicks are sometimes lost in reactive mode. To reproduce, run this example:

use bevy::{prelude::*, winit::WinitSettings};
use bevy_egui::{EguiContexts, EguiPlugin};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(EguiPlugin)
        .insert_resource(WinitSettings::desktop_app())
        .add_systems(Update, ui_example_system)
        .run();
}

fn ui_example_system(mut contexts: EguiContexts, mut count: Local<u32>) {
    egui::Window::new("Hello").show(contexts.ctx_mut(), |ui| {
        ui.label(format!("{}", *count));
        if ui.button("world").clicked() {
            *count += 1;
        }
    });
}

Click the button multiple times, and sometimes the counter will not increment

mvlabat commented 3 weeks ago

I think I've noticed it sometimes as well. I haven't got a chance to look into that myself yet, but if anyone wants to submit a PR with a fix, I'm more than happy to merge it.

mvlabat commented 3 weeks ago

Oh, btw, could you try reproducing it with the 0.29 version? There were some changes merged that fix redraws, but I'm not sure if they cover this issue.

Azorlogh commented 3 weeks ago

Just tested with 0.29, it still happens

Azorlogh commented 3 weeks ago

I found a clue: egui ignores clicks that have been pressed for more than 0.8s. If I disable this by setting the delay 1000.0, the bug no longer appears. This would suggest the problem is be timing related. Without this change, even if I click super fast, the button still misses sometimes, so maybe we are somehow exceeding this click duration in a single frame or something :thinking:

Azorlogh commented 3 weeks ago

Well, I think the issue is essentially caused by https://github.com/bevyengine/bevy/issues/14682

The time we report to egui is time.elapsed_seconds_f64(), which is actually delayed by a few frames. This means this sequence occurs (just an handwritten example):

-> tick           actual_time = 1.0        time.elapsed_seconds() = 0.0
-> mouse press    actual_time = 3.0        time.elapsed_seconds() = 1.0
-> mouse release  actual_time = 3.1        time.elapsed_seconds() = 3.0

Even though there was only 0.1s between press and release, the reported elapsed_seconds will be delayed by a few frames, so egui will perceive it as 2s so it will not register the click.

I can see two workarounds, though I'm not sure either of them really address the root issue: