vladbat00 / 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
973 stars 252 forks source link

Clicks lost in reactive mode #302

Open Azorlogh opened 3 months ago

Azorlogh commented 3 months 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

vladbat00 commented 3 months 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.

vladbat00 commented 3 months 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 months ago

Just tested with 0.29, it still happens

Azorlogh commented 3 months 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 months 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: