Skip to content

Commit 955b222

Browse files
authored
fix updatemode::reactive (#21295)
# Objective `WinitSettings::update_mode` setting `UpdateMode::Reactive` is documented as setting the time "from the start of one update to the next". currently it sets the winit wake time to the wait time after the start of the current tick, which shifts each frame by whatever additional latency exists before the frame is triggered. also, redraws only seem to trigger every 2 wakeups. in wasm this is the only way to cap framerates below default refresh-rate (on native we can just sleep), but this issue manifests on both native and wasm. ## Solution solve 1 by recording scheduled start time and setting the next wakeup as scheduled + wait time. solve 2 by setting redraw_requested explicitly when wait time elapses ## Testing ```rs const FPS: u32 = 10; #[wasm_bindgen] pub fn engine_run() { #[cfg(target_arch="wasm32")] let _ = console_log::init_with_level(log::Level::Info); App::new() .insert_resource(WinitSettings { focused_mode: UpdateMode::Reactive { wait: Duration::from_micros((1.0 / (FPS as f32) * 1000000.0) as u64), react_to_device_events: false, react_to_user_events: false, react_to_window_events: false, }, unfocused_mode: UpdateMode::Reactive { wait: Duration::from_micros((1.0 / (FPS as f32) * 1000000.0) as u64), react_to_device_events: false, react_to_user_events: false, react_to_window_events: false, }, ..Default::default() }) .add_plugins(DefaultPlugins) .add_plugins(FrameTimeDiagnosticsPlugin::default()) .add_plugins(LogDiagnosticsPlugin::default()) .run(); } ```
1 parent 560ffd7 commit 955b222

File tree

1 file changed

+21
-5
lines changed

1 file changed

+21
-5
lines changed

crates/bevy_winit/src/state.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ pub(crate) struct WinitAppRunnerState<T: Message> {
9494
),
9595
>,
9696
)>,
97+
/// time at which next tick is scheduled to run when `update_mode` is [`UpdateMode::Reactive`]
98+
scheduled_tick_start: Option<Instant>,
9799
}
98100

99101
impl<M: Message> WinitAppRunnerState<M> {
@@ -125,6 +127,7 @@ impl<M: Message> WinitAppRunnerState<M> {
125127
raw_winit_events: Vec::new(),
126128
_marker: PhantomData,
127129
message_writer_system_state,
130+
scheduled_tick_start: None,
128131
}
129132
}
130133

@@ -650,6 +653,8 @@ impl<M: Message> WinitAppRunnerState<M> {
650653
self.redraw_requested = true;
651654
// Consider the wait as elapsed since it could have been cancelled by a user event
652655
self.wait_elapsed = true;
656+
// reset the scheduled start time
657+
self.scheduled_tick_start = None;
653658

654659
self.update_mode = update_mode;
655660
}
@@ -690,11 +695,22 @@ impl<M: Message> WinitAppRunnerState<M> {
690695
}
691696
}
692697
UpdateMode::Reactive { wait, .. } => {
693-
// Set the next timeout, starting from the instant before running app.update() to avoid frame delays
694-
if let Some(next) = begin_frame_time.checked_add(wait)
695-
&& self.wait_elapsed
696-
{
697-
event_loop.set_control_flow(ControlFlow::WaitUntil(next));
698+
// Set the next timeout, starting from the instant we were scheduled to begin
699+
if self.wait_elapsed {
700+
self.redraw_requested = true;
701+
702+
let begin_instant = self.scheduled_tick_start.unwrap_or(begin_frame_time);
703+
if let Some(next) = begin_instant.checked_add(wait) {
704+
let now = Instant::now();
705+
if next < now {
706+
// request next redraw as soon as possible if we are already past the next scheduled frame start time
707+
event_loop.set_control_flow(ControlFlow::Poll);
708+
self.scheduled_tick_start = Some(now);
709+
} else {
710+
event_loop.set_control_flow(ControlFlow::WaitUntil(next));
711+
self.scheduled_tick_start = Some(next);
712+
}
713+
}
698714
}
699715
}
700716
}

0 commit comments

Comments
 (0)