From 3d628a8191b278898831bcf2c5c75082a66ab6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Mon, 15 Jan 2024 16:46:11 +0100 Subject: [PATCH] Fix Reactive and ReactiveLowPower update modes (#11325) # Objective - Partial fix for #11235 - Fixes #11274 - Fixes #11320 - Fixes #11273 ## Solution - check update mode to trigger redraw request, instead of once a redraw request has been triggered - don't ignore device event in case of `Reactive` update mode - make sure that at least 5 updates are triggered on application start to ensure everything is correctly initialized - trigger manual updates instead of relying on redraw requests when there are no window or they are not visible --- crates/bevy_winit/src/lib.rs | 110 ++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 34 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 920f0103a2..d5785cb675 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -254,6 +254,8 @@ struct WinitAppRunnerState { active: ActiveState, /// Is `true` if a new [`WindowEvent`] has been received since the last update. window_event_received: bool, + /// Is `true` if a new [`DeviceEvent`] has been received since the last update. + device_event_received: bool, /// Is `true` if the app has requested a redraw since the last update. redraw_requested: bool, /// Is `true` if enough time has elapsed since `last_update` to run another update. @@ -262,6 +264,17 @@ struct WinitAppRunnerState { last_update: Instant, /// The time the next update is scheduled to start. scheduled_update: Option, + /// Number of "forced" updates to trigger on application start + startup_forced_updates: u32, +} + +impl WinitAppRunnerState { + fn reset_on_update(&mut self) { + self.redraw_requested = false; + self.window_event_received = false; + self.device_event_received = false; + self.wait_elapsed = false; + } } #[derive(PartialEq, Eq)] @@ -287,10 +300,13 @@ impl Default for WinitAppRunnerState { Self { active: ActiveState::NotYetStarted, window_event_received: false, + device_event_received: false, redraw_requested: false, wait_elapsed: false, last_update: Instant::now(), scheduled_update: None, + // 3 seems to be enough, 5 is a safe margin + startup_forced_updates: 5, } } } @@ -364,14 +380,56 @@ pub fn winit_runner(mut app: App) { match event { Event::AboutToWait => { - if runner_state.redraw_requested { + let (config, windows) = focused_windows_state.get(&app.world); + let focused = windows.iter().any(|window| window.focused); + let mut should_update = match config.update_mode(focused) { + UpdateMode::Continuous => { + runner_state.redraw_requested + || runner_state.window_event_received + || runner_state.device_event_received + } + UpdateMode::Reactive { .. } => { + runner_state.wait_elapsed + || runner_state.redraw_requested + || runner_state.window_event_received + || runner_state.device_event_received + } + UpdateMode::ReactiveLowPower { .. } => { + runner_state.wait_elapsed + || runner_state.redraw_requested + || runner_state.window_event_received + } + }; + + // Ensure that an update is triggered on the first iterations for app initialization + if runner_state.startup_forced_updates > 0 { + runner_state.startup_forced_updates -= 1; + should_update = true; + } + + if should_update { + let visible = windows.iter().any(|window| window.visible); let (_, winit_windows, _, _) = event_writer_system_state.get_mut(&mut app.world); - for window in winit_windows.windows.values() { - window.request_redraw(); + if visible { + for window in winit_windows.windows.values() { + window.request_redraw(); + } + } else { + // there are no windows, or they are not visible. + // Winit won't send events on some platforms, so trigger an update manually. + run_app_update_if_should( + &mut runner_state, + &mut app, + &mut focused_windows_state, + event_loop, + &mut create_window_system_state, + &mut app_exit_event_reader, + &mut redraw_event_reader, + ); + event_loop.set_control_flow(ControlFlow::Poll); } } - runner_state.redraw_requested = false; } Event::NewEvents(_) => { if let Some(t) = runner_state.scheduled_update { @@ -638,7 +696,6 @@ pub fn winit_runner(mut app: App) { }); } WindowEvent::RedrawRequested => { - runner_state.redraw_requested = false; run_app_update_if_should( &mut runner_state, &mut app, @@ -659,14 +716,14 @@ pub fn winit_runner(mut app: App) { } } } - Event::DeviceEvent { - event: DeviceEvent::MouseMotion { delta: (x, y) }, - .. - } => { - let (mut event_writers, ..) = event_writer_system_state.get_mut(&mut app.world); - event_writers.mouse_motion.send(MouseMotion { - delta: Vec2::new(x as f32, y as f32), - }); + Event::DeviceEvent { event, .. } => { + runner_state.device_event_received = true; + if let DeviceEvent::MouseMotion { delta: (x, y) } = event { + let (mut event_writers, ..) = event_writer_system_state.get_mut(&mut app.world); + event_writers.mouse_motion.send(MouseMotion { + delta: Vec2::new(x as f32, y as f32), + }); + } } Event::Suspended => { let (mut event_writers, ..) = event_writer_system_state.get_mut(&mut app.world); @@ -700,7 +757,7 @@ pub fn winit_runner(mut app: App) { ) = create_window_system_state.get_mut(&mut app.world); create_windows( - &event_loop, + event_loop, commands, windows.iter_mut(), event_writer, @@ -793,6 +850,8 @@ fn run_app_update_if_should( app_exit_event_reader: &mut ManualEventReader, redraw_event_reader: &mut ManualEventReader, ) { + runner_state.reset_on_update(); + if !runner_state.active.should_run() { return; } @@ -808,32 +867,15 @@ fn run_app_update_if_should( event_loop.set_control_flow(ControlFlow::Wait); } } - let (config, windows) = focused_windows_state.get(&app.world); - let focused = windows.iter().any(|window| window.focused); - let should_update = match config.update_mode(focused) { - // `Reactive`: In order for `event_handler` to have been called, either - // we received a window or raw input event, the `wait` elapsed, or a - // redraw was requested (by the app or the OS). There are no other - // conditions, so we can just return `true` here. - UpdateMode::Continuous | UpdateMode::Reactive { .. } => true, - // TODO(bug): This is currently always true since we only run this function - // if we received a `RequestRedraw` event. - UpdateMode::ReactiveLowPower { .. } => { - runner_state.wait_elapsed - || runner_state.redraw_requested - || runner_state.window_event_received - } - }; - if app.plugins_state() == PluginsState::Cleaned && should_update { - // reset these on each update - runner_state.wait_elapsed = false; + if app.plugins_state() == PluginsState::Cleaned { runner_state.last_update = Instant::now(); app.update(); // decide when to run the next update - let (config, _) = focused_windows_state.get(&app.world); + let (config, windows) = focused_windows_state.get(&app.world); + let focused = windows.iter().any(|window| window.focused); match config.update_mode(focused) { UpdateMode::Continuous => { runner_state.redraw_requested = true;