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
This commit is contained in:
François 2024-01-15 16:46:11 +01:00 committed by GitHub
parent 01139b3472
commit 3d628a8191
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -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<Instant>,
/// 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<AppExit>,
redraw_event_reader: &mut ManualEventReader<RequestRedraw>,
) {
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;