From fac6110cb6bf960b7e32e97b0480fbb21d2a49f9 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 9 Oct 2023 12:31:02 +0200 Subject: [PATCH] Web: fix `ControlFlow::WaitUntil` to never wake up **before** the given time (#3133) --- CHANGELOG.md | 1 + src/platform_impl/web/web_sys/schedule.rs | 41 +++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 186152dc81..b28a464e1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ And please only add new entries to the top of this list, right below the `# Unre - **Breaking:** remove `DeviceEvent::Text`. - On Android, fix `DeviceId` to contain device id's. - Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now. +- On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time. # 0.29.1-beta diff --git a/src/platform_impl/web/web_sys/schedule.rs b/src/platform_impl/web/web_sys/schedule.rs index b229fd3a2b..0d39836976 100644 --- a/src/platform_impl/web/web_sys/schedule.rs +++ b/src/platform_impl/web/web_sys/schedule.rs @@ -69,7 +69,15 @@ impl Schedule { options.signal(&controller.signal()); if let Some(duration) = duration { - options.delay(duration.as_millis() as f64); + // `Duration::as_millis()` always rounds down (because of truncation), we want to round + // up instead. This makes sure that the we never wake up **before** the given time. + let duration = duration + .as_secs() + .checked_mul(1000) + .and_then(|secs| secs.checked_add(duration_millis_ceil(duration).into())) + .unwrap_or(u64::MAX); + + options.delay(duration as f64); } thread_local! { @@ -121,9 +129,24 @@ impl Schedule { .expect("Failed to send message") }); let handle = if let Some(duration) = duration { + // `Duration::as_millis()` always rounds down (because of truncation), we want to round + // up instead. This makes sure that the we never wake up **before** the given time. + let duration = duration + .as_secs() + .try_into() + .ok() + .and_then(|secs: i32| secs.checked_mul(1000)) + .and_then(|secs: i32| { + let millis: i32 = duration_millis_ceil(duration) + .try_into() + .expect("millis are somehow bigger then 1K"); + secs.checked_add(millis) + }) + .unwrap_or(i32::MAX); + window.set_timeout_with_callback_and_timeout_and_arguments_0( timeout_closure.as_ref().unchecked_ref(), - duration.as_millis() as i32, + duration, ) } else { window.set_timeout_with_callback(timeout_closure.as_ref().unchecked_ref()) @@ -160,6 +183,20 @@ impl Drop for Schedule { } } +// TODO: Replace with `u32::div_ceil()` when we hit Rust v1.73. +fn duration_millis_ceil(duration: Duration) -> u32 { + let micros = duration.subsec_micros(); + + // From . + let d = micros / 1000; + let r = micros % 1000; + if r > 0 && 1000 > 0 { + d + 1 + } else { + d + } +} + fn has_scheduler_support(window: &web_sys::Window) -> bool { thread_local! { static SCHEDULER_SUPPORT: OnceCell = OnceCell::new();